外观
05. 使用 create_agent 构建 Agent Loop
学习目标
完成本章后,你应该能够:
- 说明
create_agent在 LangChain Agent 中扮演的角色。 - 区分直接 tool calling 和 Agent Loop。
- 使用模型、工具和 system prompt 构建一个最小 Agent。
- 理解 Agent 如何在模型调用和工具执行之间循环。
- 按步骤定位 Agent 调用失败的问题。
从模型到 Agent
直接调用模型时,你通常只得到一次模型回复。即使模型请求了工具,仍然需要你自己读取 tool_calls、执行工具、把结果组织回上下文,再决定是否继续调用模型。
create_agent 是更高层的 Agent harness。它把模型、工具和 system prompt 组合起来,负责驱动“模型思考、请求工具、执行工具、把工具结果交回模型、生成最终回复”的循环。
这并不意味着 Agent 变成了魔法。工具仍然是普通 Python 函数,模型仍然可能选错工具或生成错误参数。create_agent 只是帮你管理常见的执行流程。
运行示例
运行本章示例:
bash
uv run python examples/04_agent_cli.py示例创建聊天模型:
python
model = ChatOllama(
model=config.ollama_model,
base_url=config.ollama_base_url,
temperature=0,
)然后交给 create_agent:
python
agent = create_agent(
model=model,
tools=[add, multiply],
system_prompt="...",
)用户输入会以 message 形式传入:
python
agent.invoke({"messages": [{"role": "user", "content": user_input}]})Agent 返回的结果中包含完整消息列表,示例只打印最后一条 AI message 的内容。
直接 tool calling 与 create_agent 的区别
直接 tool calling 更接近底层机制:你能清楚看到模型原始内容、工具调用请求和工具执行结果。它适合学习、调试和掌握工具调用协议。
create_agent 更适合构建实际 Agent:它封装了工具执行循环,让你把注意力放在工具设计、系统提示词和用户体验上。你不需要为每一轮都手写“检查 tool_calls、执行工具、追加 tool message、再次调用模型”的样板代码。
两者的关系可以这样理解:
- 直接模型调用:模型只负责生成下一条回复或工具请求。
- 直接 tool calling:程序手动执行模型请求的工具。
create_agent:LangChain 帮你组织模型、工具和中间消息,直到得到最终回复。
调试重点
调试 Agent 时,不要只看最终答案。建议按这个顺序排查:
- 先运行上一章的直接 tool calling 示例,确认模型能产生结构化工具调用。
- 确认工具函数能独立运行,例如
add.invoke({"a": 2, "b": 3})。 - 检查 system prompt 是否明确要求算术使用工具,最终回答保持简短。
- 打印 Agent 返回的
messages,查看中间是否出现工具调用和工具结果。 - 如果本地模型不支持工具调用,先换支持工具调用的 Ollama 模型,再调整 prompt。
当 Agent 输出异常时,优先判断问题发生在哪一层:模型没有请求工具、工具参数错误、工具执行失败,还是工具结果返回给模型后没有被正确总结。
Agent Loop 的消息变化
一次成功的工具调用通常会经历下面的消息变化:
- 用户消息进入 Agent。
- 模型返回一个 AI 消息,其中包含 tool call 请求。
- 程序执行工具,生成 tool message。
- tool message 被追加到上下文。
- 模型再次读取上下文,生成最终回答。
这就是为什么 Agent 调试必须看中间消息。如果只看最终回答,你无法判断模型是否真的调用了工具,也无法判断工具结果是否正确进入上下文。
代码阅读重点
阅读 examples/04_agent_cli.py 时,重点看:
create_agent(model=model, tools=[...], system_prompt=...)agent.invoke({"messages": [...]})_final_message_content()如何取出最后一条消息try/except如何把工具调用失败转成中文提示
这个示例把工具执行循环交给 LangChain 管理。你不再手动遍历 tool_calls,但你仍然要设计清楚工具和系统提示。
实验练习
- 给 Agent 增加一个
subtract工具。 - 把系统提示里的“算术必须使用工具”删掉,观察模型是否还会调用工具。
- 输入一个多步计算问题,例如
(5 + 7) * (9 - 3),观察模型是否会拆分工具调用。
自测问题
create_agent相比直接model.invoke()多做了什么?- 为什么
examples/03_tools_cli.py仍然有价值? - 如果 Agent 没有调用工具,你会先改系统提示还是先验证底层 tool calling?
深入理解:Agent Loop 的停止条件
Agent loop 不可能无限运行。它必须有停止条件。常见停止方式包括:
- 模型返回最终回答,不再请求工具。
- 达到最大迭代次数。
- 工具调用失败,程序中断或返回错误。
- 外部策略判断风险过高,要求人工确认。
- 用户取消任务。
教学示例里,create_agent 帮你处理大部分循环细节。但在真实系统里,你仍然要关心循环是否可能失控。例如模型反复调用同一个检索工具、反复请求参数错误的工具、或者在没有新信息时继续推理。
如果 Agent 行为开始不可控,通常不是“再写一句 prompt”就能解决,而是需要增加运行策略:最大步数、工具白名单、重试次数、人工确认和日志。
create_agent 适合的任务形态
create_agent 适合“模型主导下一步”的任务:
- 用户问题不确定,需要模型判断是否查资料。
- 工具有几个,但调用顺序不固定。
- 失败后可以让用户重试。
- 状态主要是短期消息历史。
它不适合把强约束业务流程完全交给模型。例如“必须先校验权限,再查询余额,再确认用户意图,再执行扣款”,这种流程应该由程序或 LangGraph 显式表达,模型只能参与其中某些节点。
排查树
当 create_agent 没有按预期工作时,可以按下面路径排查:
- 直接调用模型是否正常?
bind_tools直接工具调用是否正常?- 工具函数独立运行是否正常?
create_agent的 system prompt 是否要求使用工具?- Agent 返回的 messages 里是否出现 tool call?
- tool result 是否进入后续模型输入?
- 最终回答是否正确引用工具结果?
这个顺序能把问题拆开:先验证模型和工具能力,再验证 Agent loop。
工程建议
在真实项目里,不要只把 Agent 当作一个函数调用。至少要围绕它建立:
- 输入校验。
- 工具调用日志。
- 最大步骤数。
- 错误分类。
- 评估样例。
- 人工接管路径。
这会让 Agent 从“演示能跑”变成“失败可解释、可恢复、可改进”。
完整示例
python
from __future__ import annotations
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).resolve().parents[1]))
from langchain.agents import create_agent
from langchain_core.messages import AIMessage
from langchain_core.tools import tool
from langchain_ollama import ChatOllama
from examples.common.config import load_config, ollama_client_kwargs
from examples.common.ollama_check import assert_model_available, assert_ollama_running
@tool
def add(a: int, b: int) -> int:
"""Add two integers and return the sum."""
return a + b
@tool
def multiply(a: int, b: int) -> int:
"""Multiply two integers and return the product."""
return a * b
def _final_message_content(agent_response: dict) -> str:
messages = agent_response.get("messages", [])
if not messages:
return ""
final_message = messages[-1]
if isinstance(final_message, AIMessage):
return str(final_message.content)
return str(getattr(final_message, "content", final_message))
def main() -> None:
config = load_config()
try:
assert_ollama_running(config.ollama_base_url)
assert_model_available(config.ollama_base_url, config.ollama_model)
except RuntimeError as exc:
print(f"错误:{exc}")
return
model = ChatOllama(
model=config.ollama_model,
base_url=config.ollama_base_url,
client_kwargs=ollama_client_kwargs(),
temperature=0,
)
agent = create_agent(
model=model,
tools=[add, multiply],
system_prompt=(
"You are a concise arithmetic assistant. Use the provided tools for arithmetic "
"instead of doing calculations mentally. Give the final answer briefly in Chinese."
),
)
print("LangChain create_agent 工具示例。输入 `exit` 或 `quit` 退出。")
while True:
user_input = input("\n你:").strip()
if user_input.lower() in {"exit", "quit"}:
break
if not user_input:
continue
try:
response = agent.invoke({"messages": [{"role": "user", "content": user_input}]})
except Exception as exc: # Local model tool calls can fail because of malformed args.
print(f"错误:Agent 执行失败:{exc}")
print("建议:先运行 examples/03_tools_cli.py 检查模型是否能稳定产生工具调用。")
continue
print(f"AI:{_final_message_content(response)}")
if __name__ == "__main__":
main()