LangChain - 代理(Agents)
一、代理到底是什么?
▎ 代理将语言模型与工具相结合,创建能够推理任务、决定使用哪些工具并迭代地寻找解决方案的系统。
翻译成大白话:普通的 LLM 只能"动嘴"——你问它答,它不能真正去查数据库、查天气、做计算。代理 = 一个会"动手"的 LLM。它接到任务后,会自己想"我该用哪个工具?"→调用工具 → 看结果 → 再想下一步 → ……直到能给你最终答案。
▎ LLM 代理在一个循环中运行工具以实现目标。代理持续运行,直到满足停止条件——即模型发出最终输出或达到迭代限制。
也就是说,代理不是"调一次就完",而是循环跑的,下面这个 ReAct 流程就是它的核心工作方式。
二、代理是怎么干活的?—— ReAct 循环
ReAct 模式(“推理 + 行动”):在"简短的推理"和"有针对性的工具调用"之间来回交替,把工具返回的结果喂给下一步,直到能给出最终答案。
例子:
▎ 任务:找出当前最火的无线耳机,并确认是否有货。
代理的内部过程是这样的:
- 推理:"流行度是时间敏感的,我得用搜索工具。"→ 行动:调用 search_products(“wireless headphones”)
- 工具返回:“找到 5 个结果,最火的是 WH-1000XM5……”
- 推理:“回答前我得确认这货有没有库存。” → 行动:调用 check_inventory(“WH-1000XM5”)
- 工具返回:“WH-1000XM5 库存 10 件”
- 推理:“型号和库存都有了,可以回答了。” → 行动:生成最终答案
它不是一开始就知道答案,而是一边查一边想,查够了才回答。这就是"代理"和"普通聊天"的本质区别。
create_agent 这个函数底层是用 LangGraph 的"图"来实现的——图由节点和边组成,代理就在这张图上跑,经过"调用模型"节点、"执行工具"节点等。
三、怎么创建一个代理?(核心代码)
页面把代理的组成拆成了几个核心组件:模型 + 工具 + 系统提示。我们从最简单的开始。
1. 最小可用代理:模型 + 工具
from langchain.tools import tool
from langchain.agents import create_agent
第一步:定义工具(就是一些普通的 Python 函数,用 @tool 装饰)
@tool
def search(query: str) -> str:
"""Search for information."""
return f"Results for: {query}"
@tool
def get_weather(location: str) -> str:
"""Get weather information for a location."""
return f"Weather in {location}: Sunny, 72°F"
第二步:把"模型 + 工具"组装成代理
模型可以直接用字符串标识符,如 "openai:gpt-5.4"
agent = create_agent("openai:gpt-5.4", tools=[search, get_weather])
要点:
- 模型是代理的"推理引擎";可以用字符串(如 “openai:gpt-5.4”,还支持自动推断)指定,也可以用模型实例(如 ChatOpenAI(…))来精细控制temperature、max_tokens、timeout 等参数。
- 工具就是被 @tool 装饰的普通函数或协程,可以指定名称、描述、参数 schema。
- 如果传一个空工具列表,代理就退化成一个"没有工具调用能力的纯 LLM 节点"。
2. 调用代理
代理通过更新它的 State(状态)来调用,而所有代理的状态都包含一个消息序列。所以要跟代理说话,就是传一条新消息:
result = agent.invoke(
{"messages": [{"role": "user", "content": "What's the weather in San Francisco?"}]}
)
这一句下去,代理就会进入上面说的 ReAct 循环:它可能会先调 get_weather,拿到结果后再生成最终回答。
四、给代理加"性格":系统提示
你可以用 system_prompt 告诉代理它是谁、该怎么做事:
agent = create_agent(
model,
tools,
system_prompt=“You are a helpful assistant. Be concise and accurate.”
)
页面说:如果不给 system_prompt,代理会直接从消息里自己推断任务。system_prompt 可以传字符串,也可以传 SystemMessage(后者能利用比如 Anthropic的提示缓存功能,降低延迟和成本)。
还可以给代理起个 name(建议用 snake_case,因为有些模型厂商会拒绝带空格的名字),这个名字在把它作为子图接入多代理系统时会用作节点标识。
五、让输出格式固定:结构化输出
有时你不想要一段自由文本,而是想要一个结构化的对象。页面提供了 response_format 参数,有两种策略:
from pydantic import BaseModel
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
class ContactInfo(BaseModel):
name: str
email: str
phone: str
agent = create_agent(
model="gpt-5.4-mini",
tools=[search_tool],
response_format=ToolStrategy(ContactInfo) # 用"工具调用"方式产出结构化输出
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Extract contact info from: John Doe, john@example.com, (555) 123-4567"}]
})
result["structured_response"]
ContactInfo(name='John Doe', email='john@example.com', phone='(555) 123-4567')
两种策略的区别:
- ToolStrategy:用"假装调一个工具"的方式生成结构化输出,任何支持工具调用的模型都能用。当原生结构化输出不可用/不可靠时用它。
- ProviderStrategy:用模型厂商原生的结构化输出能力,更可靠,但只适用于支持该功能的厂商。
- 从 langchain 1.0 起,直接传一个 schema(如 response_format=ContactInfo)会优先用 ProviderStrategy,不支持时回退到 ToolStrategy。
六、让代理"记住"东西:内存/状态
代理通过消息状态自动维护对话历史(这相当于短期记忆)。你还能让它记住额外信息——通过自定义状态 schema(必须继承 AgentState,且必须是 TypedDict):
from langchain.agents import AgentState
class CustomState(AgentState):
user_preferences: dict # 在消息之外,再多记一份"用户偏好"
agent = create_agent(
model,
tools=[tool1, tool2],
state_schema=CustomState
)
result = agent.invoke({
"messages": [{"role": "user", "content": "I prefer technical explanations"}],
"user_preferences": {"style": "technical", "verbosity": "detailed"},
})
从 langchain 1.0 起,自定义状态必须是 TypedDict,不再支持 Pydantic/dataclass;并且更推荐"通过中间件定义状态"这种方式。
七、实时看到进度:流式处理
代理可能要跑好几步,invoke 会等全部跑完才返回。想看中间过程就用 stream:
from langchain.messages import AIMessage, HumanMessage
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "Search for AI news and summarize the findings"}]},
stream_mode="values"
):
latest_message = chunk["messages"][-1]
if latest_message.content:
if isinstance(latest_message, HumanMessage):
print(f"User: {latest_message.content}")
elif isinstance(latest_message, AIMessage):
print(f"Agent: {latest_message.content}")
elif latest_message.tool_calls:
print(f"Calling tools: {[tc['name'] for tc in latest_message.tool_calls]}")
每个 chunk 是"到那一刻为止的完整状态",这样你就能实时打印出"代理正在调哪个工具、说了什么"。
八、进阶能力
页面还列了几项高级特性,先知道"还能干这些",需要时再去查:
- 动态模型:在运行时根据状态/上下文换模型(比如对话短用便宜模型、对话长用强模型),用@wrap_model_call 中间件实现。
- 动态工具:工具集在运行时改变——可以"预注册一堆工具再按权限筛选",也可以"运行时从 MCP 服务器等动态加载新工具"(后者需要同时写 wrap_model_call 和 wrap_tool_call
两个钩子)。 - 工具错误处理:用 @wrap_tool_call 中间件捕获工具异常,返回自定义错误信息给模型让它重试,而不是直接崩掉。
- 动态系统提示:用 @dynamic_prompt 中间件,根据运行时上下文(比如用户是新手还是专家)动态生成不同的系统提示。
- 中间件(Middleware):以上动态模型/动态工具/错误处理/动态提示全都靠中间件实现。中间件可以在"调模型前处理状态"“修改模型响应”“处理工具执行”“动态选模型”"加日志监控"等关键点拦截数据流,而不用改代理核心逻辑。页面明确说这是"强大的可扩展性"来源。
一句话总结
▎ 代理 = 模型(大脑)+ 工具(手脚)+ 循环(ReAct:想了就做、做完再想)+ 状态(记忆)+ 中间件(可插拔的扩展点)。 用 create_agent(模型, tools=[…]) 创建,用agent.invoke({“messages”: […]}) 调用,它就会自己循环调工具直到给你答案。
更多推荐



所有评论(0)