第 3 章 / 共 14 章
十行拿到一个会循环的 agent
3.1 循环这件事,为什么值得让别人写
回到第 1 章那个手写的 while True。它其实是对的——2022 年那篇 ReAct 论文提出的思路就是让模型交替产出推理和行动,用行动去外部取信息,再用新信息更新计划。这个循环的骨架就那么几行。
问题是骨架之外的东西:工具抛异常了要不要重试、重试几次;模型连续调用二十次工具还不停怎么办;上下文塞满了要不要摘要;某个工具执行前要不要先问人一句。这些都不属于「循环」,但每一条都得在循环里插一刀。你自己写的话,那个 while 会在三个月内长到两百行,并且每一刀都和别的刀纠缠在一起。
create_agent 提供的正是这个:一个稳定的循环 + 一整套定义好的插刀位置(下一章讲)。
3.2 十行
from langchain.agents import create_agent
agent = create_agent(
model="anthropic:claude-sonnet-4-6",
tools=[lookup_order, check_refund_policy],
system_prompt="你是退款工单助手。先查订单,再对照政策,最后给出结论。",
)
result = agent.invoke({
"messages": [{"role": "user", "content": "工单 #4821:买家说收到的杯子有裂纹,要求退款。订单号 ORD-2026-000123。"}]
})
print(result["messages"][-1].content)
它的完整签名(照抄官方 API 参考):
create_agent(
model, tools=None, *,
system_prompt=None, middleware=(), response_format=None,
state_schema=None, context_schema=None,
checkpointer=None, store=None,
interrupt_before=None, interrupt_after=None,
debug=False, name=None, cache=None, transformers=None,
) -> CompiledStateGraph
三个值得注意的地方:
- 除了
model和tools,其余全是关键字参数(签名里那个*)。位置传参会直接报错。 - 返回值是
CompiledStateGraph,不是一个笼统的可调用对象。这句话的含义是:它本来就是一张 LangGraph 图。第 5 章你要自己画的那种图,create_agent已经替你画好了一张最常用的。 tools传空或不传时,「agent 就只剩一个模型节点,没有工具调用循环」——官方原话。
3.3 它编译出来的图长什么样
不用猜,直接打印(本机实测输出):
print(agent.get_graph().draw_mermaid())
graph TD;
__start__([__start__]):::first
model(model)
tools(tools)
__end__([__end__]):::last
__start__ --> model;
model -.-> __end__;
model -.-> tools;
tools -.-> model;
两个节点,一条实线三条虚线。虚线是条件边——模型返回之后判断一下:有 tool_calls 就去 tools,没有就去 __end__;工具跑完无条件回到 model。

3.4 一个可以马上验证的真实运行
下面是本机跑出来的完整消息序列(用了 3.5 节的技巧,所以没有真实模型参与,但图的执行路径是真的):
HumanMessage '帮我找 langgraph 的笔记'
AIMessage '' tool_calls=[{'name': 'search_notes',
'args': {'query': 'langgraph', 'limit': 2},
'id': 'call_1', 'type': 'tool_call'}]
ToolMessage 'note-a.md, note-b.md'
AIMessage '我找到了 2 条笔记:note-a.md 和 note-b.md。' tool_calls=[]
四条消息,对应图上的四步:入口 → model(决定调工具)→ tools(执行)→ model(收尾)。agent 返回的是整个消息列表,不只是最后一句。这一点在调试时很重要——你要看的往往是中间那两条。
3.5 没有 API key 也能跑通:脚本化假模型
这是本教程最实用的一个小技巧,值得单独一节。
学 agent 框架最烦的一点是:想验证一下图对不对,就得花钱调一次模型,而且模型每次的行为还不一样。LangChain 自带假模型,但直接喂给 create_agent 会失败:
File ".../langchain_core/language_models/chat_models.py", line 2383, in bind_tools
raise NotImplementedError
NotImplementedError
During task with name 'model' and id '...'
原因是 create_agent 会对模型调 bind_tools,而基础假模型没实现它。补五行就行:
from langchain_core.language_models.fake_chat_models import FakeMessagesListChatModel
class ScriptedChatModel(FakeMessagesListChatModel):
"""按剧本依次返回消息的假模型,用于在没有 API key 时验证图结构。"""
def bind_tools(self, tools, **kwargs):
return self
用法是给它一个消息剧本:
from langchain_core.messages import AIMessage
model = ScriptedChatModel(responses=[
AIMessage(content="", tool_calls=[
{"name": "lookup_order", "args": {"order_id": "ORD-2026-000123"}, "id": "c1"}
]),
AIMessage(content="订单符合七天无理由,建议全额退款 89 元。"),
])
agent = create_agent(model=model, tools=[lookup_order])
这样你就能确定性地测试:工具有没有被正确调用、参数对不对、中断有没有触发、状态最后长什么样。本教程后面几章里所有带 agent 的实测输出都是这么跑出来的。
3.6 什么时候该离开预置 agent
官方的说法是「先用高层的,需要时再往下掉一层」。落到具体判断,出现下面任意一条,就该考虑第 5 章了:
- 流程里有确定性步骤必须在模型之前或之后执行(比如「不管模型说什么,金额超过 500 一律走人工」)。
- 需要多个模型节点走不同的分支,而不是一个模型循环。
- 需要在流程中间存自定义状态,而不只是消息列表。
- 需要扇出:把一条工单拆成 N 个子任务并行处理再汇总。
但先别急着跳。第 4 章的中间件能解决上面第一条和一部分第三条,代价比自己画图低得多。