什么是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 构建,流程控制能力更强。 - 特性包括:循环控制(防无限循环)、异常处理(解析错误处理)、可观测性(输出详细执行日志)。 |
大模型的不足以及解决方案
- 不具备记忆能力,上下文窗口限制
- 实时信息更新慢,新旧知识难以区分
- 无法灵活的操作外部系统
- 无法为领域问题提供专业靠谱的答案
思考
- 如果接入真实API,要考虑哪些问题?
- 如果多个工具都能回答,Agent怎么选?
- 如何提升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.json 中 env 指定的文件。
相关文档:
切换到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()实例的变量名
启动后:
- API 服务:http://localhost:8000
- Swagger 文档:http://localhost:8000/docs(可在线测试每个接口)
- ReDoc 文档:http://localhost:8000/redoc(更适合阅读的文档格式)
测试接口
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 应用 |
相关文档: