一、代理到底是什么?

▎ 代理将语言模型与工具相结合,创建能够推理任务、决定使用哪些工具并迭代地寻找解决方案的系统。

翻译成大白话:普通的 LLM 只能"动嘴"——你问它答,它不能真正去查数据库、查天气、做计算。代理 = 一个会"动手"的 LLM。它接到任务后,会自己想"我该用哪个工具?"→调用工具 → 看结果 → 再想下一步 → ……直到能给你最终答案。

▎ LLM 代理在一个循环中运行工具以实现目标。代理持续运行,直到满足停止条件——即模型发出最终输出或达到迭代限制。

也就是说,代理不是"调一次就完",而是循环跑的,下面这个 ReAct 流程就是它的核心工作方式。


二、代理是怎么干活的?—— ReAct 循环

ReAct 模式(“推理 + 行动”):在"简短的推理"和"有针对性的工具调用"之间来回交替,把工具返回的结果喂给下一步,直到能给出最终答案。

例子:

▎ 任务:找出当前最火的无线耳机,并确认是否有货。

代理的内部过程是这样的:

  1. 推理:"流行度是时间敏感的,我得用搜索工具。"→ 行动:调用 search_products(“wireless headphones”)
  2. 工具返回:“找到 5 个结果,最火的是 WH-1000XM5……”
  3. 推理:“回答前我得确认这货有没有库存。” → 行动:调用 check_inventory(“WH-1000XM5”)
  4. 工具返回:“WH-1000XM5 库存 10 件”
  5. 推理:“型号和库存都有了,可以回答了。” → 行动:生成最终答案

它不是一开始就知道答案,而是一边查一边想,查够了才回答。这就是"代理"和"普通聊天"的本质区别。

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 是"到那一刻为止的完整状态",这样你就能实时打印出"代理正在调哪个工具、说了什么"。


八、进阶能力

页面还列了几项高级特性,先知道"还能干这些",需要时再去查:

  1. 动态模型:在运行时根据状态/上下文换模型(比如对话短用便宜模型、对话长用强模型),用@wrap_model_call 中间件实现。
  2. 动态工具:工具集在运行时改变——可以"预注册一堆工具再按权限筛选",也可以"运行时从 MCP 服务器等动态加载新工具"(后者需要同时写 wrap_model_call 和 wrap_tool_call
    两个钩子)。
  3. 工具错误处理:用 @wrap_tool_call 中间件捕获工具异常,返回自定义错误信息给模型让它重试,而不是直接崩掉。
  4. 动态系统提示:用 @dynamic_prompt 中间件,根据运行时上下文(比如用户是新手还是专家)动态生成不同的系统提示。
  5. 中间件(Middleware):以上动态模型/动态工具/错误处理/动态提示全都靠中间件实现。中间件可以在"调模型前处理状态"“修改模型响应”“处理工具执行”“动态选模型”"加日志监控"等关键点拦截数据流,而不用改代理核心逻辑。页面明确说这是"强大的可扩展性"来源。

一句话总结

▎ 代理 = 模型(大脑)+ 工具(手脚)+ 循环(ReAct:想了就做、做完再想)+ 状态(记忆)+ 中间件(可插拔的扩展点)。 用 create_agent(模型, tools=[…]) 创建,用agent.invoke({“messages”: […]}) 调用,它就会自己循环调工具直到给你答案。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐