RSS

第 3 章 / 共 14 章

十行拿到一个会循环的 agent

约 9 分钟 更新于

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

三个值得注意的地方:

  1. 除了 model 和 tools,其余全是关键字参数(签名里那个 *)。位置传参会直接报错。
  2. 返回值是 CompiledStateGraph,不是一个笼统的可调用对象。这句话的含义是:它本来就是一张 LangGraph 图。第 5 章你要自己画的那种图,create_agent 已经替你画好了一张最常用的。
  3. 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章:十行拿到一个会循环的 agent
图 3.1:预置 agent 的全部结构就是这个两节点循环。判断在 model 出来的那条边上,出口只有一个——模型不再要求调用工具。记住这一点,第 7 章你会自己写这条边。

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 章的中间件能解决上面第一条和一部分第三条,代价比自己画图低得多。

3.7 练习与检查点