【AI Agent】什么是AI Agent?如何做一个自己的智能体

什么是agent

一个决策------执行------反馈的循环系统

特性 AIGC (如 ChatGPT) AI Agent (如 Manus, Operator)
核心能力 内容生成 任务规划与自主执行
交互模式 被动响应,依赖提示词 主动规划,自主决策
输出结果 建议、方案、内容(需人工后续处理) 可交付的最终成果(如已发送的邮件、整理好的报表)
与外界交互 有限,主要通过文本 强大,可通过工具调用操作现实世界系统

循环决策机制

LLM大脑(思考)+工具(执行)+循环决策机制

bash 复制代码
用户:我想去北京旅行,推荐一下穿搭

思考------调用获取北京当天的天气工具------思考------调用穿搭组合工具------思考------选定穿搭------返回结果

Agent 核心架构与原理概览

维度 核心要素 核心定位 / 作用 详细说明
三重核心能力 (核心价值) 动态任务路由 自动规划路径 能够根据输入内容自动规划执行路径,灵活切换工具调用逻辑。
生态化工具集成 访问外部工具 可以访问 300+ 预置工具接口,覆盖搜索引擎、数据计算、数据库交互等多领域。
全周期记忆管理 维护上下文与知识 能够同时维护短期对话上下文与长期知识存储,支持复杂任务的跨轮次协作。
角色模型 (像"智能项目经理") Planner (规划者) 拆解目标 接收任务需求后负责拆解目标。
Router (路由者) 匹配最优工具 根据任务类型匹配最合适的工具。
Executor (执行者) 调度与交付 调度工具按步骤执行,最后整合结果生成交付物。
核心组件详解 (技术实现) 模型 (Model) Agent 的大脑 (负责推理和决策) - LangChain 1.0 中被抽象为统一接口(支持 OpenAI、Anthropic、Google等)。 - 核心职责 :分析状态和请求,决定是否/调用哪些工具,并解析返回结果形成最终响应。 - 推理过程通常基于思维链 (Chain-of-Thought) 模式。
工具 (Tools) 与外部世界交互的 能力扩展接口 - 封装特定功能(如搜索、数据库操作、代码执行等)。 - LangChain 支持大量预置选项及开发者自定义。 - 设计原则 :功能单一性、输入验证和异常处理。 - 架构意义:将认知能力与具体执行能力分离,让 Agent 专注决策。
记忆 (Memory) 提供上下文感知能力 - 使其能记住交互历史并基于上下文做出决策。 - 分类:短期记忆(维护当前对话)和长期记忆(跨会话持久化)。 - LangChain 1.0 中与 LangGraph 的状态管理深度融合,支持复杂结构化状态保持。
AgentExecutor 执行协调器 - 负责迭代运行代理直至满足停止条件。 - LangChain 1.0 新版基于 LangGraph 构建,流程控制能力更强。 - 特性包括:循环控制(防无限循环)、异常处理(解析错误处理)、可观测性(输出详细执行日志)。

大模型的不足以及解决方案

  • 不具备记忆能力,上下文窗口限制
  • 实时信息更新慢,新旧知识难以区分
  • 无法灵活的操作外部系统
  • 无法为领域问题提供专业靠谱的答案

思考

  1. 如果接入真实API,要考虑哪些问题?
  2. 如果多个工具都能回答,Agent怎么选?
  3. 如何提升Agent的"记忆能力"?

初识langchain

:::info

LLM大模型与AI应用的粘合剂

:::

LangChain是一个开源框架 ,旨在简化 使用大模型构建端到端应用程序的过程,它也是ReAct论文的落地实现

2025年 langchain和langgragh发布1.0版本,正式进入AI agent时代

知识点 1:安装依赖

bash 复制代码
pip install python-dotenv       # 环境变量管理,从 .env 文件加载配置
pip install openai              # OpenAI SDK,也可用于调用 DeepSeek API
pip install langchain           # LangChain 核心框架
pip install langchain-deepseek  # LangChain 官方 DeepSeek 集成包

相关文档:


知识点 2:环境变量管理

使用 python-dotenv.env 文件加载敏感配置,避免把密钥写在代码里。

python 复制代码
# .env 文件
DEEPSEEK_API_KEY="你的API密钥"
DEEPSEEK_BASE_URL="https://api.deepseek.com"

# env_utils.py
import os
from dotenv import load_dotenv

load_dotenv(override=True)  # override=True 表示 .env 会覆盖系统已有的同名环境变量
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL")

相关文档:


知识点 3:创建 LLM 客户端

使用 langchain-deepseek 官方集成包创建 DeepSeek 客户端。

python 复制代码
from langchain_deepseek import ChatDeepSeek

llm = ChatDeepSeek(
    model="deepseek-chat",        # 模型名称
    api_key=DEEPSEEK_API_KEY,     # API 密钥
    temperature=0.7,              # 温度越高回复越随机,越低越确定
)

相关文档:


知识点 4:消息类型

LangChain 用专门的消息类表示对话中的不同角色。

python 复制代码
from langchain_core.messages import SystemMessage, HumanMessage

messages = [
    SystemMessage(content="你是一个翻译助手"),  # 系统提示词,定义模型角色
    HumanMessage(content="你好"),              # 用户消息
]

response = llm.invoke(messages)  # 返回 AIMessage 对象
print(response.content)          # 获取文本内容

LangChain 提供的消息类型:

  • SystemMessage --- 系统提示词,告诉模型扮演什么角色
  • HumanMessage --- 用户发送的消息
  • AIMessage --- 模型返回的消息
  • ToolMessage --- 工具调用返回的消息

相关文档:


知识点 5:流式输出

llm.stream() 让模型逐字返回,实现打字机效果,而不是等全部生成完再返回。

python 复制代码
for chunk in llm.stream(messages):
    if chunk.content:
        # end="" 不换行,flush=True 立即输出不缓冲
        print(chunk.content, end="", flush=True)

相关文档:


知识点 6:深度思考模式

通过 extra_body 开启 DeepSeek 的思考模式,模型会先输出思维链再给出答案。

python 复制代码
# llm.bind() 创建一个带有额外参数的临时实例,不会修改原始 llm
thinking_llm = llm.bind(extra_body={"thinking": {"type": "enabled"}})

response = thinking_llm.invoke(messages)

# 思考过程在 additional_kwargs 中
reasoning = response.additional_kwargs.get("reasoning_content", "")
# 最终回复在 content 中
content = response.content

注意:ChatOpenAI 不支持提取 DeepSeek 的 reasoning_content 字段,

必须使用 ChatDeepSeek(langchain-deepseek)才能正确获取思考内容。

相关文档:


知识点 7:速率限制

LangChain 内置速率限制器,使用令牌桶算法控制 API 调用频率。

python 复制代码
from langchain_core.rate_limiters import InMemoryRateLimiter

rate_limiter = InMemoryRateLimiter(
    requests_per_second=1,       # 每秒最多 1 次请求
    check_every_n_seconds=0.1,   # 每 0.1 秒检查一次是否可以发请求
    max_bucket_size=10,          # 最多积攒 10 次请求额度(闲置时积攒)
)

llm = ChatDeepSeek(
    model="deepseek-chat",
    api_key=DEEPSEEK_API_KEY,
    rate_limiter=rate_limiter,   # 传入后自动限速
)

令牌桶算法简单理解:

  • 每秒往桶里放 1 个令牌
  • 每次请求消耗 1 个令牌
  • 桶最多存 10 个(闲置时积攒额度)
  • 令牌不够时自动等待,不会报错

相关文档:


知识点 8:结构化输出

让模型按照预定义的数据结构返回结果,而不是自由文本。

python 复制代码
from pydantic import BaseModel, Field

# 第一步:用 Pydantic 定义数据结构
# Field(description=...) 帮助模型理解每个字段应该填什么
class MovieReview(BaseModel):
    name: str = Field(description="电影名称")
    director: str = Field(description="导演")
    rating: float = Field(description="评分,1-10 分")

# 第二步:创建结构化输出模型
structured_llm = llm.with_structured_output(MovieReview)

# 第三步:调用,返回的是 MovieReview 对象而不是文本
result = structured_llm.invoke("评价一下电影《星际穿越》")
print(result.name)    # 星际穿越
print(result.rating)  # 9.5

相关文档:


知识点 9:IDE 类型警告处理

使用 OpenAI SDK 调用 DeepSeek 时,IDE 会报类型警告,因为 "deepseek-chat" 不在 OpenAI 的模型名称列表中。

python 复制代码
# 方法 1:model 显式标注为 str,避免被推断为 Literal["deepseek-chat"]
model: str = "deepseek-chat"

# 方法 2:messages 使用 SDK 提供的类型,而非普通 dict
from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam

messages = [
    ChatCompletionSystemMessageParam(role="system", content="..."),
    ChatCompletionUserMessageParam(role="user", content="..."),
]

注意:使用 LangChain 的 ChatDeepSeek 不会有这个问题。

写一个简单的Agent(发送邮件)

Agent = 大模型 + 工具。大模型负责理解意图和决策,工具负责执行具体操作。

核心流程:用户输入自然语言 → Agent 理解意图 → 提取参数 → 调用工具 → 返回结果

python 复制代码
from langchain_core.tools import tool
from langchain.agents import create_agent

# 第一步:用 @tool 装饰器定义工具
# 函数的 docstring 会告诉 Agent 这个工具是做什么的
@tool
def send_email(to: str, subject: str, body: str) -> str:
    """发送邮件。

    Args:
        to: 收件人邮箱地址
        subject: 邮件主题
        body: 邮件正文内容
    """
    print(f"收件人: {to}, 主题: {subject}, 正文: {body}")
    return f"邮件发送成功!收件人: {to}, 主题: {subject}"

# 第二步:创建 Agent,绑定模型和工具
agent = create_agent(
    model=llm,                # 大模型实例
    tools=[send_email],       # 工具列表,可以绑定多个工具
    system_prompt="你是一个邮件助手。请始终使用send_email工具"
)

# 第三步:运行 Agent
result = agent.invoke({
    "messages": [
        {"role": "user", "content": "给 test@qq.com 发一封邮件,主题是测试,内容是你好"}
    ]
})
print(result["messages"][-1].content)

相关文档:


LangChain 提供了 Studio + LangSmith 集成的测试环境,可以可视化、交互和调试 Agent。

安装 LangGraph CLI

bash 复制代码
pip install --upgrade "langgraph-cli[inmem]"

项目结构

plain 复制代码
agent/
├── .env              ← 环境变量(DEEPSEEK_API_KEY、LANGSMITH_API_KEY)
├── env_utils.py      ← 加载环境变量
├── langgraph.json    ← LangGraph 配置文件
└── src/
    └── agent.py      ← Agent 代码

langgraph.json 配置

json 复制代码
{
  "dependencies": ["."],
  "graphs": {
    "agent": "./src/agent.py:agent"
  },
  "env": ".env"
}
  • dependencies:项目依赖路径
  • graphs:指定 Agent 入口,格式为 文件路径:变量名
  • env:环境变量文件路径

启动 Studio

bash 复制代码
cd agent
langgraph dev

启动后访问输出的 Studio UI 地址,即可在浏览器中与 Agent 交互调试。

注意:agent/.env 中需要包含所有必要的环境变量(如 DEEPSEEK_API_KEY),

因为 langgraph dev 只会加载 langgraph.jsonenv 指定的文件。

相关文档:

切换到chat模式,直接对话使用即可。

工具定义

@tool 装饰器

最简单的方式,把一个普通函数变成 Agent 可以调用的工具。

函数的 docstring 非常重要,Agent 会根据它来决定什么时候调用这个工具。

python 复制代码
from langchain_core.tools import tool

# 基本用法:函数签名自动推断参数类型
@tool
def search(query: str) -> str:
    """根据关键词搜索信息。

    Args:
        query: 搜索关键词
    """
    return f"搜索结果: {query}"

# 自定义工具名称和描述
@tool("calculator", description="计算两个数的和")
def add(a: int, b: int) -> int:
    """加法计算。"""
    return a + b

# 使用 Pydantic 定义更严格的输入格式
from pydantic import BaseModel, Field

class EmailInput(BaseModel):
    to: str = Field(description="收件人邮箱")
    subject: str = Field(description="邮件主题")

@tool(args_schema=EmailInput)
def send_email(to: str, subject: str) -> str:
    """发送邮件。"""
    return f"已发送给 {to}"

@tool 装饰器的常用参数:

  • description:工具描述(不传则用 docstring)
  • args_schema:Pydantic 模型,定义输入参数的类型和描述
  • return_direct:设为 True 时,工具返回结果后 Agent 直接停止,不再继续推理
  • parse_docstring:设为 True 时,自动从 docstring 的 Args 部分提取参数描述

相关文档:

工具参数总览

参数 类型 必填 作用 示例
name str 否(默认用函数名) 工具的唯一标识,Agent 用它来识别和调用工具 @tool("search")
description str 否(默认用 docstring) 工具的功能描述,Agent 根据它决定何时调用 @tool(description="搜索信息")
args_schema BaseModel 用 Pydantic 模型定义输入参数的类型、描述和校验规则 @tool(args_schema=SearchInput)
return_direct bool 否(默认 False) 设为 True 时,工具返回后 Agent 直接停止,不再继续推理 @tool(return_direct=True)
parse_docstring bool 否(默认 False) 设为 True 时,自动从 docstring 的 Args 部分提取每个参数的描述 @tool(parse_docstring=True)
response_format str 否(默认 "content") "content" 返回纯文本;"content_and_artifact" 可返回附件 @tool(response_format="content_and_artifact")
infer_schema bool 否(默认 True) 是否从函数签名自动推断参数类型 @tool(infer_schema=True)

相关文档:



继承 BaseTool

适合需要维护状态、有复杂逻辑的工具。通过继承 BaseTool 类来定义。

python 复制代码
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field


# 第一步:定义输入参数的数据结构(可选)
class CalculatorInput(BaseModel):
    a: int = Field(description="第一个数")
    b: int = Field(description="第二个数")
    operation: str = Field(description="运算类型: add, subtract, multiply, divide")


# 第二步:继承 BaseTool,实现 _run 方法
class CalculatorTool(BaseTool):
    # name 和 description 是必须定义的属性
    name: str = "calculator"
    description: str = "执行加减乘除运算"
    # args_schema 告诉 Agent 这个工具需要哪些参数
    args_schema: type[BaseModel] = CalculatorInput

    # _run 是工具被调用时执行的方法(必须实现)
    def _run(self, a: int, b: int, operation: str) -> str:
        if operation == "add":
            return f"{a} + {b} = {a + b}"
        elif operation == "subtract":
            return f"{a} - {b} = {a - b}"
        elif operation == "multiply":
            return f"{a} * {b} = {a * b}"
        elif operation == "divide":
            if b == 0:
                return "错误:不能除以零"
            return f"{a} / {b} = {a / b}"
        return f"未知运算: {operation}"

    # _arun 是异步版本(可选,不实现则不支持异步调用)
    async def _arun(self, a: int, b: int, operation: str) -> str:
        return self._run(a, b, operation)


# 第三步:创建工具实例,传给 Agent
calculator = CalculatorTool()
result = calculator.invoke({"a": 10, "b": 5, "operation": "add"})
print(result)  # "10 + 5 = 15"

BaseTool 的必要属性和方法:

  • name:工具名称,Agent 用它来识别工具
  • description:工具描述,Agent 根据它决定何时使用
  • _run()必须实现,工具被调用时执行的逻辑
  • _arun():可选,异步版本
  • args_schema:可选,Pydantic 模型定义输入参数

@tool vs BaseTool 对比

特性 @tool 装饰器 继承 BaseTool
复杂度 简单,几行代码 较多,需要定义类
适用场景 简单函数 复杂逻辑、需要维护状态
参数推断 自动从函数签名推断 手动定义 args_schema
自定义能力 有限 完全控制

相关文档:


从 MCP 服务器获取工具

MCP(Model Context Protocol,模型上下文协议)是一个开放标准,

让 AI 模型可以连接外部工具和数据源。通过 MCP,Agent 可以直接使用其他服务提供的工具,

而不需要自己重新定义。

安装依赖

bash 复制代码
pip install langchain-mcp-adapters

使用 MCP 工具

python 复制代码
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent

# 第一步:配置 MCP 服务器连接
# 每个 MCP 服务器可以提供多个工具
client = MultiServerMCPClient(
    {
        # 服务器名称(自定义)
        "filesystem": {
            # MCP 服务器的启动命令
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
            "transport": "stdio",  # 通过标准输入输出通信
        },
        # 可以配置多个 MCP 服务器
        "weather": {
            "url": "http://localhost:8080/sse",
            "transport": "sse",  # 通过 SSE(Server-Sent Events)通信
        },
    }
)

# 第二步:获取 MCP 服务器提供的所有工具
tools = await client.get_tools()

# 第三步:将工具传给 Agent 使用
agent = create_agent(
    model=llm,
    tools=tools,
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "列出 /tmp 目录下的文件"}]
})

MCP 通信方式

方式 说明 适用场景
stdio 通过标准输入输出通信,启动本地进程 本地 MCP 服务器
sse 通过 HTTP SSE 通信 远程 MCP 服务器

MCP 的优势

  • 不用重复造轮子:直接使用社区已有的 MCP 服务器(文件系统、数据库、GitHub 等)
  • 标准化协议:任何支持 MCP 的工具都能被任何支持 MCP 的 AI 模型使用
  • 动态发现:Agent 可以自动发现 MCP 服务器提供的所有工具

相关文档:

多Agent模式(muti-agent)

当有多个 Agent 各自负责不同领域时,可以用 Supervisor 模式让一个"主管"自动根据用户意图分派任务,不需要手动切换 Agent。

安装依赖

bash 复制代码
pip install langgraph-supervisor

核心概念

plain 复制代码
用户输入 → Supervisor(主管)判断意图 → 分派给子 Agent → 子 Agent 调用工具 → 返回结果给 Supervisor
  • Supervisor(主管):理解用户意图,决定将任务交给哪个子 Agent
  • 子 Agent:各自负责一个领域,拥有自己的工具
  • Handoff(转交)create_supervisor 会自动为每个子 Agent 生成转交工具,Supervisor 通过调用这些工具来分派任务

使用示例

python 复制代码
from langchain.agents import create_agent
from langgraph_supervisor import create_supervisor

# 第一步:创建子 Agent
# name 参数很重要,Supervisor 通过名称来识别和分派任务
email_agent = create_agent(
    model=llm,
    tools=[send_email],
    name="email_expert",           # 子 Agent 名称,Supervisor 用它来识别
    system_prompt="你是一个邮件专家。请使用 send_email 工具发送邮件。",
)

weather_agent = create_agent(
    model=llm,
    tools=[WeatherTool()],
    name="weather_expert",
    system_prompt="你是一个天气专家。请使用 weather 工具查询天气信息。",
)

# 第二步:创建 Supervisor(主管)
workflow = create_supervisor(
    [email_agent, weather_agent],  # 子 Agent 列表
    model=llm,                     # Supervisor 自己的 LLM
    prompt=(                       # 告诉 Supervisor 如何分派任务
        "你是一个团队主管,管理着以下专家:\n"
        "- email_expert:负责发送邮件\n"
        "- weather_expert:负责查询天气\n\n"
        "请根据用户的需求,将任务分派给合适的专家。"
    ),
)

# 第三步:编译并运行
agent = workflow.compile()

result = agent.invoke({
    "messages": [{"role": "user", "content": "北京今天天气怎么样?"}]
})
# Supervisor 自动将任务转交给 weather_expert → 调用天气工具 → 返回结果

create_supervisor 参数

参数 类型 必填 作用
agents list 子 Agent 列表,Supervisor 可以将任务分派给其中任意一个
model LLM Supervisor 使用的大模型,用于理解意图和决策分派
prompt str 系统提示,告诉 Supervisor 每个子 Agent 的职责
output_mode str "full_history"(默认)保留完整对话历史;"last_message" 只返回最后一条消息

在 LangGraph Studio 中使用

langgraph.json 中注册 Supervisor:

json 复制代码
{
  "graphs": {
    "agent_supervisor": "./src/agent_supervisor.py:agent"
  }
}

启动后在 Studio UI 中选择 agent_supervisor,所有子 Agent 的工具都可以自动调用,无需手动切换。

相关文档:

异步工具

前面的工具都是同步的(def),执行时会阻塞等待。异步工具(async def)在等待网络请求、文件读写等 I/O 操作时不会阻塞,适合需要并发执行的场景。

安装依赖

bash 复制代码
pip install aiohttp  # 异步 HTTP 请求库

同步 vs 异步

plain 复制代码
同步:发请求 → 等待响应(阻塞,什么都不能做) → 处理结果
异步:发请求 → 去做别的事(不阻塞) → 响应回来再处理

方式一:@tool + async def(最简单)

只需把 def 改成 async def,LangChain 自动识别为异步工具。

python 复制代码
import aiohttp
from langchain_core.tools import tool

@tool
async def query_ip(ip: str) -> str:
    """查询 IP 地址的归属地信息。

    Args:
        ip: 要查询的 IP 地址,如 8.8.8.8
    """
    url = f"http://ip-api.com/json/{ip}?lang=zh-CN"

    # async with 确保请求完成后自动关闭连接,避免资源泄漏
    async with aiohttp.ClientSession() as session:
        # await 表示"等待这个异步操作完成",期间不会阻塞其他任务
        async with session.get(url) as response:
            data = await response.json()

    if data.get("status") == "success":
        return f"IP: {ip}, 城市: {data.get('city')}"
    return f"查询失败: {data.get('message')}"

方式二:BaseTool + _arun 方法

继承 BaseTool 时,通过实现 _arun 方法来支持异步。Agent 会优先调用异步版本。

python 复制代码
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field

class DomainInput(BaseModel):
    domain: str = Field(description="要查询的域名,如 google.com")

class DomainLookupTool(BaseTool):
    name: str = "domain_lookup"
    description: str = "查询域名对应的 IP 和归属地"
    args_schema: type[BaseModel] = DomainInput

    # 同步版本(fallback,当无法使用异步时调用)
    def _run(self, domain: str) -> str:
        return "请使用异步方式调用此工具"

    # 异步版本(Agent 优先调用)
    async def _arun(self, domain: str) -> str:
        url = f"http://ip-api.com/json/{domain}?lang=zh-CN"
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                data = await response.json()
        return f"域名: {domain}, IP: {data.get('query')}, 城市: {data.get('city')}"

调用异步 Agent

异步工具需要使用 ainvoke(异步版的 invoke)来调用:

python 复制代码
import asyncio

agent = create_agent(
    model=llm,
    tools=[query_ip, DomainLookupTool()],
    system_prompt="你是一个网络工具助手。",
)

async def main():
    # 注意:用 ainvoke 而不是 invoke
    result = await agent.ainvoke({
        "messages": [{"role": "user", "content": "查一下 8.8.8.8 是哪里的"}]
    })
    print(result["messages"][-1].content)

asyncio.run(main())

同步工具 vs 异步工具对比

特性 同步工具 异步工具
函数定义 def _run() async def _arun()
装饰器 @tool + def @tool + async def
调用方式 agent.invoke() await agent.ainvoke()
I/O 等待时 阻塞(干等) 不阻塞(可做其他事)
适用场景 简单计算、本地操作 网络请求、文件读写、数据库查询

Python 异步关键字速查

关键字 作用 示例
async def 定义异步函数 async def fetch():
await 等待异步操作完成 data = await response.json()
async with 异步上下文管理器,自动清理资源 async with session.get(url) as resp:
asyncio.run() 运行异步主函数的入口 asyncio.run(main())

相关文档:

用 FastAPI 部署 Agent 为 HTTP 服务

将 Agent 包装成 FastAPI 接口,让前端、移动端或其他服务可以通过 HTTP 调用。

什么是 FastAPI?

FastAPI 是一个现代、高性能的 Python Web 框架,专门用于构建 API。它是目前 Python 社区最流行的 API 框架之一。

FastAPI 的核心特点:

特点 说明
高性能 基于 Starlette(异步框架)和 Uvicorn(ASGI 服务器),性能接近 Node.js 和 Go
类型驱动 利用 Python 类型注解自动完成参数验证、序列化和文档生成
自动文档 启动后自动生成 Swagger UI(/docs)和 ReDoc(/redoc)交互式 API 文档
原生异步 天然支持 async/await,非常适合调用 LLM 等 I/O 密集型操作
基于 Pydantic 请求/响应模型用 Pydantic 定义,和 LangChain 的结构化输出无缝配合

为什么选 FastAPI 而不是 Flask/Django?

plain 复制代码
Flask:轻量但不原生支持异步 → 调用 LLM 时会阻塞
Django:功能全但过于重量级 → 只是做 API 不需要模板、ORM 等
FastAPI:轻量 + 原生异步 + 自动文档 → 最适合 AI/LLM 服务

安装依赖

bash 复制代码
pip install fastapi uvicorn
  • fastapi:Web 框架本身,提供路由、请求解析、响应序列化等功能
  • uvicorn:ASGI 服务器,负责接收 HTTP 请求并转发给 FastAPI 处理

FastAPI 核心概念

1. 应用实例
python 复制代码
from fastapi import FastAPI

app = FastAPI(title="LangChain Agent API")  # 创建应用实例,title 会显示在自动文档中

FastAPI() 的常用参数:

参数 说明 示例
title API 标题,显示在文档页面 "LangChain Agent API"
description API 描述 "基于 LangChain 的智能助手"
version API 版本号 "1.0.0"
docs_url Swagger 文档路径,设为 None 可禁用 "/docs"(默认)
redoc_url ReDoc 文档路径 "/redoc"(默认)
2. 路由装饰器

用装饰器定义 API 接口,装饰器名对应 HTTP 方法:

python 复制代码
@app.get("/items")          # GET 请求 --- 获取数据
@app.post("/items")         # POST 请求 --- 创建数据
@app.put("/items/{id}")     # PUT 请求 --- 更新数据
@app.delete("/items/{id}")  # DELETE 请求 --- 删除数据

装饰器的常用参数:

参数 说明 示例
response_model 指定响应数据结构,自动序列化和验证 response_model=AgentResponse
status_code 成功时的 HTTP 状态码 status_code=201
tags 文档中的分组标签 tags=["agent"]
summary 接口简短描述 summary="调用 Agent"
3. 请求/响应模型(Pydantic)

FastAPI 用 Pydantic 模型定义请求体和响应体的数据结构。这和 LangChain 的结构化输出使用的是同一套体系。

python 复制代码
from pydantic import BaseModel

# 请求模型:定义客户端发来的 JSON 结构
class AgentRequest(BaseModel):
    message: str                              # 必填字段

# 带默认值的请求模型
class ChatRequest(BaseModel):
    message: str                              # 必填
    system_prompt: str = "You are a helpful assistant"  # 可选,有默认值

# 响应模型:定义返回给客户端的 JSON 结构
class AgentResponse(BaseModel):
    content: str

FastAPI 会自动完成:

  • 请求验证 :如果客户端没传 message 字段,自动返回 422 错误和详细的错误信息
  • 类型转换:自动将 JSON 转为 Pydantic 对象
  • 响应序列化:自动将 Pydantic 对象转为 JSON 响应
  • 文档生成:在 Swagger UI 中展示请求/响应的字段和类型
4. 同步 vs 异步接口
python 复制代码
# 同步接口 --- 处理请求时会阻塞当前进程
@app.post("/sync")
def sync_handler(req: AgentRequest):
    result = agent.invoke(...)  # 阻塞等待
    return result

# 异步接口 --- 等待 I/O 时可以处理其他请求(推荐)
@app.post("/async")
async def async_handler(req: AgentRequest):
    result = await agent.ainvoke(...)  # 不阻塞
    return result

调用 LLM/Agent 属于 I/O 密集操作,**务必使用 async def + **await

否则一个请求在等待 LLM 回复时,其他请求都无法处理。

整体架构

plain 复制代码
客户端 (curl/前端/App)
    ↓ HTTP POST /agent
FastAPI 接收请求,Pydantic 验证参数
    ↓ ainvoke / astream_events
Supervisor Agent → 判断意图 → 分派给子 Agent → 子 Agent 调用工具 → 返回结果
    ↓
AgentResponse(JSON) 或 SSE 流式推送

本项目提供三个接口:

接口 方法 说明
/agent POST 调用 Supervisor Agent,一次性返回完整结果
/agent/stream POST 调用 Supervisor Agent,SSE 流式推送(打字机效果)
/chat POST 简单对话,直接调用 LLM,不经过 Agent

完整示例(fastapi_app.py)

python 复制代码
import sys
import os

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "agent", "src"))

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel

from langchain.agents import create_agent
from langgraph_supervisor import create_supervisor

from my_langchain_llm import llm, _build_messages
from agent_email import send_email
from agent_weather import WeatherTool
from agent_async_tool import query_ip, DomainLookupTool

app = FastAPI(title="LangChain Agent API")

# ========== 构建 Supervisor Agent ==========

email_agent = create_agent(
    model=llm, tools=[send_email],
    name="email_expert",
    system_prompt="你是一个邮件专家。请使用 send_email 工具发送邮件。",
)

weather_agent = create_agent(
    model=llm, tools=[WeatherTool()],
    name="weather_expert",
    system_prompt="你是一个天气专家。请使用 weather 工具查询天气信息。",
)

network_agent = create_agent(
    model=llm, tools=[query_ip, DomainLookupTool()],
    name="network_expert",
    system_prompt="你是一个网络工具助手。",
)

workflow = create_supervisor(
    [email_agent, weather_agent, network_agent],
    model=llm,
    prompt="你是一个团队主管,根据用户需求分派给合适的专家。",
)

agent = workflow.compile()

# ========== 请求/响应模型 ==========

class AgentRequest(BaseModel):
    message: str

class AgentResponse(BaseModel):
    content: str

class ChatRequest(BaseModel):
    message: str
    system_prompt: str = "You are a helpful assistant"

# ========== API 接口 ==========

@app.post("/agent", response_model=AgentResponse)
async def agent_chat(req: AgentRequest):
    """调用 Supervisor Agent,一次性返回完整结果"""
    result = await agent.ainvoke({
        "messages": [{"role": "user", "content": req.message}]
    })
    return AgentResponse(content=result["messages"][-1].content)


@app.post("/agent/stream")
async def agent_stream(req: AgentRequest):
    """调用 Supervisor Agent,流式返回 (SSE)"""
    async def generate():
        async for event in agent.astream_events(
            {"messages": [{"role": "user", "content": req.message}]},
            version="v2",
        ):
            if event["event"] == "on_chat_model_stream":
                chunk = event["data"]["chunk"]
                if chunk.content:
                    yield f"data: {chunk.content}\n\n"
        yield "data: [DONE]\n\n"

    return StreamingResponse(generate(), media_type="text/event-stream")


@app.post("/chat", response_model=AgentResponse)
async def chat(req: ChatRequest):
    """简单对话(不经过 Agent,直接调用 LLM)"""
    messages = _build_messages(req.message, req.system_prompt)
    response = await llm.ainvoke(messages)
    return AgentResponse(content=response.content)


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

代码逐段解析

模块导入与路径配置
python 复制代码
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "agent", "src"))

因为 Agent 代码在 agent/src/ 目录下,不在 Python 默认搜索路径中,

所以需要手动将它加入 sys.path,否则 from agent_email import ... 会报 ModuleNotFoundError

StreamingResponse 流式响应
python 复制代码
from fastapi.responses import StreamingResponse

@app.post("/agent/stream")
async def agent_stream(req: AgentRequest):
    async def generate():
        # 异步生成器:每产生一段内容就立即推送给客户端
        async for event in agent.astream_events(...):
            if event["event"] == "on_chat_model_stream":
                chunk = event["data"]["chunk"]
                if chunk.content:
                    # SSE 格式:每条消息以 "data: " 开头,以 "\n\n" 结尾
                    yield f"data: {chunk.content}\n\n"
        yield "data: [DONE]\n\n"  # 约定的结束标记

    # media_type 告诉客户端这是 SSE 流
    return StreamingResponse(generate(), media_type="text/event-stream")

关键点:

  • StreamingResponse 接收一个异步生成器 ,每 yield 一次就推送一次数据
  • SSE 格式要求每条消息以 data: 开头、\n\n 结尾
  • [DONE] 是前端常用的约定,表示流结束(OpenAI API 也用这个标记)
__main__ 入口
python 复制代码
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

这样可以直接用 python fastapi_app.py 启动,等价于 uvicorn fastapi_app:app --host 0.0.0.0 --port 8000

区别是不支持 --reload 热重载,开发阶段建议用 uvicorn 命令行启动。

启动服务

bash 复制代码
# 开发模式(推荐):--reload 会监听文件变化自动重启
uvicorn fastapi_app:app --reload --port 8000

# 生产模式:指定 workers 数量
uvicorn fastapi_app:app --host 0.0.0.0 --port 8000 --workers 4

# 或直接运行 Python 文件
python fastapi_app.py

uvicorn 命令格式:uvicorn 模块名:应用变量名

  • fastapi_app --- Python 文件名(不带 .py
  • app --- 文件中 FastAPI() 实例的变量名

启动后:

测试接口

bash 复制代码
# 查天气(自动分派给 weather_expert)
curl -X POST http://localhost:8000/agent \
  -H "Content-Type: application/json" \
  -d '{"message": "北京今天天气怎么样?"}'

# 发邮件(自动分派给 email_expert)
curl -X POST http://localhost:8000/agent \
  -H "Content-Type: application/json" \
  -d '{"message": "给 test@qq.com 发邮件,主题是测试,内容是你好"}'

# 查 IP(自动分派给 network_expert)
curl -X POST http://localhost:8000/agent \
  -H "Content-Type: application/json" \
  -d '{"message": "查一下 8.8.8.8 是哪里的IP"}'

# 流式输出(-N 禁用缓冲,实时看到数据推送)
curl -N http://localhost:8000/agent/stream \
  -H "Content-Type: application/json" \
  -d '{"message": "北京天气怎么样?"}'

# 简单对话(不经过 Agent)
curl -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "你好", "system_prompt": "你是一个友好的助手"}'

关键 API 说明

方法 说明 适用场景
agent.ainvoke() 异步调用,等待完整结果返回 普通接口,前端等待完整回复
agent.astream_events() 异步流式,逐步推送事件 SSE 接口,打字机效果
llm.ainvoke() 直接调用 LLM(不经过 Agent) 简单对话,不需要工具

astream_events 常用事件类型

astream_events 会推送 Agent 执行过程中的所有事件,可以按 event["event"] 过滤:

python 复制代码
async for event in agent.astream_events(input, version="v2"):
    match event["event"]:
        case "on_chat_model_stream":    # LLM 输出的每个 token
            chunk = event["data"]["chunk"]
        case "on_chat_model_start":     # LLM 开始生成
            model_name = event["name"]
        case "on_chat_model_end":       # LLM 生成完毕
            full_output = event["data"]["output"]
        case "on_tool_start":           # 工具开始调用
            tool_name = event["name"]
            tool_input = event["data"]["input"]
        case "on_tool_end":             # 工具调用完成
            tool_result = event["data"]["output"]
事件 触发时机 常用数据
on_chat_model_stream LLM 每输出一个 token event["data"]["chunk"].content
on_chat_model_start LLM 开始生成 event["name"](模型名)
on_chat_model_end LLM 生成完毕 event["data"]["output"](完整输出)
on_tool_start 工具开始执行 event["name"]event["data"]["input"]
on_tool_end 工具执行完毕 event["data"]["output"](工具返回值)

FastAPI vs Flask vs Django 对比

特性 FastAPI Flask Django
异步支持 原生支持 async/await 需要额外配置(Quart) Django 4.1+ 部分支持
性能 高(基于 Starlette) 中等 中等
自动文档 内置 Swagger + ReDoc 需要 flask-swagger 等扩展 需要 DRF + drf-spectacular
数据验证 内置(Pydantic) 需要 marshmallow 等 内置(Django Forms/DRF Serializers)
学习曲线 较高
适合场景 API 服务、AI/LLM 应用 小型 Web 应用 全栈 Web 应用

相关文档:

相关推荐
IT 行者2 小时前
实战LangChain4j集成MCP Server:让Java AI应用具备工具调用能力
java·开发语言·人工智能
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章44-直线卡尺
图像处理·人工智能·opencv·算法·计算机视觉
OidEncoder2 小时前
绝对值编码器在AGV舵轮上的应用与调试(含硬件对接+故障排查+代码实例)
人工智能·物联网·自动化·智慧城市·信息与通信
BitaHub20242 小时前
Bitahub算力上新 RTX3080 10G重磅登场
人工智能·bitahub·rtx3080 10g显卡
新缸中之脑2 小时前
在树莓派上运行OpenClaw
人工智能
ccLianLian2 小时前
深度学习·GAN系列
人工智能·深度学习·生成对抗网络
不一样的故事1262 小时前
软件测试在未来10年
大数据·网络·人工智能·安全
Techblog of HaoWANG2 小时前
目标检测与跟踪(13)-- Jetson Xavier NX / Orin NX 松灵机械臂PiPer SDK、ROS功能包、官方文档解读
人工智能·目标检测·计算机视觉·机械臂·智能机器人·松零·piper