10_create_agent一行创建智能体

概述

前面几篇我们已经分别讲了:

  • 模型如何初始化。
  • Prompt 如何工程化。
  • LCEL 如何组织调用流程。
  • RAG 如何接外部知识。
  • Memory 如何保留会话状态。
  • Tool 如何连接外部动作。
  • 结构化输出如何返回可验证数据。

到了这里,你会发现一个自然的问题:

这些能力最后怎么组合成一个真正能干活的 Agent?

LangChain 当前的答案是 create_agent()

它不是"再包一层"的语法糖那么简单。

它更像一个 harness,把模型、工具、系统提示词、状态、上下文、中间件和执行循环组装起来。

可以把 Agent 理解成:

text 复制代码
Agent = Model + Harness

其中 harness 负责的事情是:

  • 在合适的时机给模型合适的上下文。
  • 让模型在需要时调用工具。
  • 把工具结果回传给模型继续推理。
  • 持久化会话状态。
  • 在必要时做重试、摘要、人工审批、限流、guardrail。

create_agent() 的价值,不是让你少写一行代码,而是把 Agent 的整套运行机制标准化。

先看最小例子:一行创建一个 Agent

最小版本可以长这样:

python 复制代码
from langchain.agents import create_agent

agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[],
)

这已经是一个 Agent 了。

如果只看这一行,你可能觉得没什么。

但实际上,这一行已经隐含了一个完整的执行框架:

  • 模型调用。
  • 工具选择。
  • 状态管理。
  • 运行时配置。
  • 逐步执行。

如果再加上工具和系统提示词:

python 复制代码
from langchain.agents import create_agent
from langchain.tools import tool


@tool
def get_weather(city: str) -> str:
    """查询指定城市的实时天气。用户问天气、温度、降雨、出行建议时使用。"""
    return f"{city} 今天晴,26 摄氏度。"


agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[get_weather],
    system_prompt="你是一个出行助手。用户问天气时必须调用天气工具。",
)

这个 Agent 就能根据用户问题决定是否调用 get_weather

最小 Agent = 模型 + 工具 + 系统提示词。

Agent 的核心循环:思考、工具调用、观察、再思考

Agent 和普通模型调用最大的区别,是它不是只执行一次。

它会在一个循环里不断推进,直到任务完成。

一个简化循环如下:
#mermaid-svg-tCAyffgVIF3nyQiF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tCAyffgVIF3nyQiF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tCAyffgVIF3nyQiF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tCAyffgVIF3nyQiF .error-icon{fill:#552222;}#mermaid-svg-tCAyffgVIF3nyQiF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tCAyffgVIF3nyQiF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tCAyffgVIF3nyQiF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tCAyffgVIF3nyQiF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tCAyffgVIF3nyQiF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tCAyffgVIF3nyQiF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tCAyffgVIF3nyQiF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tCAyffgVIF3nyQiF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tCAyffgVIF3nyQiF .marker.cross{stroke:#333333;}#mermaid-svg-tCAyffgVIF3nyQiF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tCAyffgVIF3nyQiF p{margin:0;}#mermaid-svg-tCAyffgVIF3nyQiF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tCAyffgVIF3nyQiF .cluster-label text{fill:#333;}#mermaid-svg-tCAyffgVIF3nyQiF .cluster-label span{color:#333;}#mermaid-svg-tCAyffgVIF3nyQiF .cluster-label span p{background-color:transparent;}#mermaid-svg-tCAyffgVIF3nyQiF .label text,#mermaid-svg-tCAyffgVIF3nyQiF span{fill:#333;color:#333;}#mermaid-svg-tCAyffgVIF3nyQiF .node rect,#mermaid-svg-tCAyffgVIF3nyQiF .node circle,#mermaid-svg-tCAyffgVIF3nyQiF .node ellipse,#mermaid-svg-tCAyffgVIF3nyQiF .node polygon,#mermaid-svg-tCAyffgVIF3nyQiF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tCAyffgVIF3nyQiF .rough-node .label text,#mermaid-svg-tCAyffgVIF3nyQiF .node .label text,#mermaid-svg-tCAyffgVIF3nyQiF .image-shape .label,#mermaid-svg-tCAyffgVIF3nyQiF .icon-shape .label{text-anchor:middle;}#mermaid-svg-tCAyffgVIF3nyQiF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tCAyffgVIF3nyQiF .rough-node .label,#mermaid-svg-tCAyffgVIF3nyQiF .node .label,#mermaid-svg-tCAyffgVIF3nyQiF .image-shape .label,#mermaid-svg-tCAyffgVIF3nyQiF .icon-shape .label{text-align:center;}#mermaid-svg-tCAyffgVIF3nyQiF .node.clickable{cursor:pointer;}#mermaid-svg-tCAyffgVIF3nyQiF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tCAyffgVIF3nyQiF .arrowheadPath{fill:#333333;}#mermaid-svg-tCAyffgVIF3nyQiF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tCAyffgVIF3nyQiF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tCAyffgVIF3nyQiF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tCAyffgVIF3nyQiF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tCAyffgVIF3nyQiF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tCAyffgVIF3nyQiF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tCAyffgVIF3nyQiF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tCAyffgVIF3nyQiF .cluster text{fill:#333;}#mermaid-svg-tCAyffgVIF3nyQiF .cluster span{color:#333;}#mermaid-svg-tCAyffgVIF3nyQiF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tCAyffgVIF3nyQiF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tCAyffgVIF3nyQiF rect.text{fill:none;stroke-width:0;}#mermaid-svg-tCAyffgVIF3nyQiF .icon-shape,#mermaid-svg-tCAyffgVIF3nyQiF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tCAyffgVIF3nyQiF .icon-shape p,#mermaid-svg-tCAyffgVIF3nyQiF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tCAyffgVIF3nyQiF .icon-shape .label rect,#mermaid-svg-tCAyffgVIF3nyQiF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tCAyffgVIF3nyQiF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tCAyffgVIF3nyQiF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tCAyffgVIF3nyQiF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否

用户输入
模型
需要工具?
直接回答
生成 tool call
执行工具
把结果写回 messages

可以把这个循环理解成:

  1. 模型先"想一想"下一步。
  2. 如果需要外部信息,就调用工具。
  3. 系统执行工具。
  4. 工具结果回到上下文。
  5. 模型根据新信息继续想,直到不再需要工具。

这也是官方文档对 Agent 的基本定义。

Agent 不是一次生成,而是"模型 + 工具"的循环执行。

create_agent() 到底接收哪些核心参数?

最常用的参数其实就几个。

参数 作用 什么时候用
model 指定模型 必须
tools 指定可调用工具 大多数 Agent 都需要
system_prompt 定义 Agent 角色和任务边界 基本都会用
response_format 定义结构化输出 schema 需要稳定返回数据时用
checkpointer 持久化会话状态 需要多轮记忆时用
middleware 定制模型/工具调用行为 需要摘要、审批、重试等能力时用
name 给 Agent 命名 作为子图嵌入多 Agent 系统时很有用
state_schema 扩展状态字段 需要额外 state 时用
context_schema 定义每次调用的上下文 需要把 user_id、feature flag 等传给工具和 middleware 时用

你可以把这些参数分成三层:

  • 核心层:model、tools、system_prompt
  • 输出层:response_format、name
  • 运行层:checkpointer、state_schema、context_schema、middleware

create_agent() 的参数不是乱的,它们分别对应 Agent 的核心能力、输出约束和运行时治理。

model:可以是字符串,也可以是模型实例

官方文档支持两种写法:

写法一:直接传模型标识字符串

python 复制代码
agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[],
)

写法二:传已经初始化好的模型实例

python 复制代码
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent

model = init_chat_model(
    "gpt-4o-mini",
    model_provider="openai",
    temperature=0,
)

agent = create_agent(
    model,
    tools=[],
)

两种方式都可以。

如果你只是快速原型,字符串形式更短。

如果你已经对模型做了额外配置,比如 temperaturetimeoutmax_retriesbind,传实例会更清晰。

model 既可以是 "provider:model" 字符串,也可以是已经配置好的模型对象。

tools:模型能用哪些外部能力

工具可以是:

  • 普通 Python 可调用对象。
  • @tool 装饰器定义的工具。
  • 工具字典。

最简单的例子:

python 复制代码
from langchain.tools import tool


@tool
def search_faq(query: str) -> str:
    """搜索常见问题知识库。用户问规则、流程、说明时使用。"""
    faq = {
        "退款": "退款需要在 7 天内申请,并保留订单号。",
        "发货": "订单通常在 24 小时内发出。",
    }
    return faq.get(query, "没有找到相关 FAQ。")

然后交给 Agent:

python 复制代码
agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[search_faq],
)

模型会根据问题判断是否要调用 search_faq

如果工具很多,模型的选择空间也会变大。

这不一定是好事。

工具越多,描述冲突、选择错误、调试困难的概率越高。

所以不要把几十个工具无脑塞给一个 Agent。

tools 决定 Agent 能做什么,但工具越多不代表 Agent 越强。

system_prompt:定义 Agent 的角色、边界和风格

system_prompt 是 Agent 的第一层约束。

它可以是字符串,也可以是 SystemMessage

python 复制代码
agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[search_faq],
    system_prompt="你是一个严谨的客服助手。回答要准确、简洁,不能编造信息。",
)

这个提示词通常应该写清楚:

  • Agent 是什么角色。
  • 优先遵循什么规则。
  • 哪些事情必须调用工具。
  • 哪些事情不能编造。
  • 什么时候转人工。
  • 输出风格是什么。

例如:

python 复制代码
system_prompt = (
    "你是一个企业客服助手。"
    "用户询问订单、退款、发货、售后时,优先调用工具。"
    "如果工具没有答案,明确说明不知道,不要编造。"
    "回答要简洁、礼貌、准确。"
)

system_prompt 解决的是"Agent 应该怎么思考和说话"。

但如果你要动态变换提示词,例如根据用户等级、租户、语言切换,就应该考虑 middleware,而不是把所有动态逻辑都塞进静态 system prompt。

system_prompt 负责稳定角色和边界,动态变化的部分更适合放到 middleware 或 context。

response_format:让 Agent 最终返回结构化结果

如果你希望 Agent 最后不是只返回自然语言,而是返回可以直接进入程序逻辑的对象,就用 response_format

例如:

python 复制代码
from pydantic import BaseModel, Field


class Answer(BaseModel):
    summary: str = Field(description="Short answer")
    confidence: float = Field(description="Confidence from 0 to 1", ge=0, le=1)

Agent:

python 复制代码
agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[],
    response_format=Answer,
)

result = agent.invoke({
    "messages": [
        {"role": "user", "content": "Summarize why RAG is useful"}
    ]
})

print(result["structured_response"])

返回值会包含:

python 复制代码
result["structured_response"]

而不是让你从最后一条消息里手动扒 JSON。

这一点和第 09 篇的结构化输出是一致的。

如果 Agent 需要先调用工具,再输出结构化结果,这也可以统一在 response_format 里处理。

response_format 让 Agent 的最终输出可以直接进入业务代码。

name:当 Agent 作为子图时尤其有用

name 是一个容易被忽略,但在多 Agent 里很有用的参数。

python 复制代码
agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[search_faq],
    name="customer_support_agent",
)

它的作用不是给用户看标题,而是给系统看名字。

当 Agent 被嵌入到更大的图或多 Agent 系统里时,name 可以帮助:

  • 识别子 Agent。
  • 调试日志。
  • 追踪调用路径。
  • 做多 Agent 协作。

这部分在第 13 篇会更详细。

name 主要是给系统和图结构看的,不是给终端用户看的。

checkpointer:让 Agent 记住 thread

如果你想让 Agent 记住上一轮对话,就需要 checkpointer

本地开发最简单的是 InMemorySaver

python 复制代码
from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[search_faq],
    checkpointer=InMemorySaver(),
)

然后调用时传 thread_id

python 复制代码
config = {"configurable": {"thread_id": "support-001"}}

result = agent.invoke(
    {"messages": [{"role": "user", "content": "我的名字是 Bob。"}]},
    config=config,
)

下一轮仍然传同一个 thread_id

python 复制代码
result = agent.invoke(
    {"messages": [{"role": "user", "content": "我叫什么名字?"}]},
    config=config,
)

如果 checkpointer 存在,Agent 就能沿着同一个线程继续。

如果没有 checkpointer,thread_id 就没有持久化意义。

checkpointer + thread_id 是 Agent 会话记忆的核心组合。

context_schema:把每次运行时的数据传给 Agent

有些数据不是会话历史,而是每次调用都可能变化的运行时上下文。

例如:

  • user_id
  • locale
  • tenant_id
  • feature_flags
  • api_key
  • environment

这些数据不应该硬塞进 messages。

更好的方式是定义 context_schema

python 复制代码
from dataclasses import dataclass


@dataclass
class SupportContext:
    user_id: str
    locale: str = "zh-CN"

Agent:

python 复制代码
agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[search_faq],
    context_schema=SupportContext,
)

调用时传 context:

python 复制代码
result = agent.invoke(
    {"messages": [{"role": "user", "content": "我有哪些订单?"}]},
    config={"configurable": {"thread_id": "support-001"}},
    context=SupportContext(user_id="user_123", locale="zh-CN"),
)

工具和 middleware 可以通过运行时上下文读取这些值。

这比让模型从用户自然语言里猜 user_id 安全得多。

context 是运行时参数,不是聊天历史。

state_schema:当你要扩展 AgentState 时

如果默认状态不够用,可以扩展 state_schema

这适合在状态里保存额外字段,例如:

  • 当前任务阶段。
  • 用户偏好。
  • 评分结果。
  • 中间摘要。
  • 自定义标记。

示例:

python 复制代码
from langchain.agents import AgentState
from typing_extensions import NotRequired


class SupportState(AgentState):
    user_tier: NotRequired[str]
    current_ticket_id: NotRequired[str]

然后:

python 复制代码
agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[search_faq],
    state_schema=SupportState,
)

这和第 07 篇的记忆系统是连在一起的。

state_schema 解决的是"Agent 当前状态还能带什么额外信息"。

state_schema 是给 Agent 状态加字段,不是给用户加消息。

middleware:把 Agent 变成可治理的 harness

如果说 system_prompt 负责内容,middleware 负责行为治理。

LangChain 官方把 middleware 看成 Agent 的控制层。

常见用途包括:

  • 动态 prompt。
  • 动态模型选择。
  • 动态工具选择。
  • 重试。
  • fallback。
  • 限流。
  • PII 检测。
  • 人工审批。
  • 摘要压缩。
  • 早停。

最简单的用法:

python 复制代码
from langchain.agents.middleware import SummarizationMiddleware

agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[search_faq],
    middleware=[
        SummarizationMiddleware(model="openai:gpt-4o-mini"),
    ],
)

更明确地说,middleware 插在 Agent loop 的关键位置上:

  • before_agent
  • before_model
  • after_model
  • after_agent
  • wrap_model_call
  • wrap_tool_call

这部分第 11 篇会深讲。

这里先记住一句话:

create_agent() 不是固定死的 Agent,它是一个可以通过 middleware 持续扩展的 harness。

thread_idcontext 的区别

这两个概念很容易混。

字段 作用 是否持久化
thread_id 识别同一段对话线程
context 每次调用携带的运行时数据

例如:

python 复制代码
config = {"configurable": {"thread_id": "support-001"}}
context = SupportContext(user_id="user_123", locale="zh-CN")

thread_id 让 Agent 记住这段对话。

context 告诉 Agent 这次调用的用户是谁、环境是什么、feature flag 是什么。

一个是"会话历史",一个是"运行时输入"。

不要把它们混用。

thread_id 管历史,context 管当前运行环境。

Streaming:Agent 也可以边跑边看

如果 Agent 需要多步工具调用,用户可能不想等结果全部完成才看到反馈。

可以流式读取:

python 复制代码
from langchain.agents import create_agent

agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[search_faq],
)

for chunk in agent.stream(
    {"messages": [{"role": "user", "content": "搜索 AI 新闻并总结"}]},
    stream_mode="values",
):
    latest_message = chunk["messages"][-1]
    if latest_message.content:
        print(latest_message.content)

stream_mode="values" 的意思是每个 chunk 都包含当前 state 的完整快照。

你可以从里面取最后一条消息,判断:

  • 是用户消息。
  • 是模型消息。
  • 还是工具调用消息。

如果 Agent 要执行多步动作,流式输出对用户体验很重要。

invoke 拿最终结果,stream 看 Agent 一步步怎么跑。

一个完整的 Agent 执行图

可以把 create_agent() 返回的 Agent 想成这样:

text 复制代码
输入 messages
   |
   v
模型调用
   |
   +-- 若需要工具 -> 工具执行 -> messages 更新
   |
   +-- 若不需要工具 -> 直接结束
   |
   v
结构化输出 / 最终回答

如果再加上状态和中间件:
#mermaid-svg-ByQHLiEw8BxeJVsk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ByQHLiEw8BxeJVsk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ByQHLiEw8BxeJVsk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ByQHLiEw8BxeJVsk .error-icon{fill:#552222;}#mermaid-svg-ByQHLiEw8BxeJVsk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ByQHLiEw8BxeJVsk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ByQHLiEw8BxeJVsk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ByQHLiEw8BxeJVsk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ByQHLiEw8BxeJVsk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ByQHLiEw8BxeJVsk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ByQHLiEw8BxeJVsk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ByQHLiEw8BxeJVsk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ByQHLiEw8BxeJVsk .marker.cross{stroke:#333333;}#mermaid-svg-ByQHLiEw8BxeJVsk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ByQHLiEw8BxeJVsk p{margin:0;}#mermaid-svg-ByQHLiEw8BxeJVsk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ByQHLiEw8BxeJVsk .cluster-label text{fill:#333;}#mermaid-svg-ByQHLiEw8BxeJVsk .cluster-label span{color:#333;}#mermaid-svg-ByQHLiEw8BxeJVsk .cluster-label span p{background-color:transparent;}#mermaid-svg-ByQHLiEw8BxeJVsk .label text,#mermaid-svg-ByQHLiEw8BxeJVsk span{fill:#333;color:#333;}#mermaid-svg-ByQHLiEw8BxeJVsk .node rect,#mermaid-svg-ByQHLiEw8BxeJVsk .node circle,#mermaid-svg-ByQHLiEw8BxeJVsk .node ellipse,#mermaid-svg-ByQHLiEw8BxeJVsk .node polygon,#mermaid-svg-ByQHLiEw8BxeJVsk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ByQHLiEw8BxeJVsk .rough-node .label text,#mermaid-svg-ByQHLiEw8BxeJVsk .node .label text,#mermaid-svg-ByQHLiEw8BxeJVsk .image-shape .label,#mermaid-svg-ByQHLiEw8BxeJVsk .icon-shape .label{text-anchor:middle;}#mermaid-svg-ByQHLiEw8BxeJVsk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ByQHLiEw8BxeJVsk .rough-node .label,#mermaid-svg-ByQHLiEw8BxeJVsk .node .label,#mermaid-svg-ByQHLiEw8BxeJVsk .image-shape .label,#mermaid-svg-ByQHLiEw8BxeJVsk .icon-shape .label{text-align:center;}#mermaid-svg-ByQHLiEw8BxeJVsk .node.clickable{cursor:pointer;}#mermaid-svg-ByQHLiEw8BxeJVsk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ByQHLiEw8BxeJVsk .arrowheadPath{fill:#333333;}#mermaid-svg-ByQHLiEw8BxeJVsk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ByQHLiEw8BxeJVsk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ByQHLiEw8BxeJVsk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ByQHLiEw8BxeJVsk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ByQHLiEw8BxeJVsk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ByQHLiEw8BxeJVsk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ByQHLiEw8BxeJVsk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ByQHLiEw8BxeJVsk .cluster text{fill:#333;}#mermaid-svg-ByQHLiEw8BxeJVsk .cluster span{color:#333;}#mermaid-svg-ByQHLiEw8BxeJVsk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ByQHLiEw8BxeJVsk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ByQHLiEw8BxeJVsk rect.text{fill:none;stroke-width:0;}#mermaid-svg-ByQHLiEw8BxeJVsk .icon-shape,#mermaid-svg-ByQHLiEw8BxeJVsk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ByQHLiEw8BxeJVsk .icon-shape p,#mermaid-svg-ByQHLiEw8BxeJVsk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ByQHLiEw8BxeJVsk .icon-shape .label rect,#mermaid-svg-ByQHLiEw8BxeJVsk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ByQHLiEw8BxeJVsk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ByQHLiEw8BxeJVsk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ByQHLiEw8BxeJVsk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否

输入 messages + config + context
before_agent
before_model
模型调用
是否有 tool_calls?
after_model
wrap_tool_call / tools
after_agent
最终 state / structured_response

这个图可以帮你理解:

  • Agent 不是单个模型。
  • Agent 是一套可配置执行流。
  • middleware 可以在关键节点修改行为。
  • checkpointer 让这个流可恢复。

create_agent() 为什么比手写循环更稳?

你当然可以自己写 Agent 循环:

python 复制代码
while True:
    response = model.invoke(messages)
    if not response.tool_calls:
        break
    tool_result = run_tool(response.tool_calls[0])
    messages.append(tool_result)

这没错,但一旦你开始加这些东西,复杂度就上来了:

  • 多工具。
  • 多个 tool_calls。
  • 状态持久化。
  • 流式输出。
  • 结构化输出。
  • 中间件。
  • 重试。
  • Fallback。
  • 人工审批。
  • 上下文管理。

create_agent() 的价值,就是把这些通用问题收敛成一个稳定 harness。

你不用每个项目都自己重写一套 Agent 框架。

create_agent() 让 Agent 的样板逻辑标准化,让你把精力集中在业务能力上。

一个实际场景:客服 Agent

下面给一个更完整的例子。

定义工具

python 复制代码
from langchain.tools import tool


@tool
def query_order_status(order_id: str) -> str:
    """根据订单号查询订单状态。用户问订单、物流、发货、送达时间时使用。"""
    orders = {
        "A1001": "订单 A1001 已发货,物流单号 SF123456,预计明天送达。",
        "A1002": "订单 A1002 正在仓库拣货,预计今天 18:00 前发出。",
    }
    return orders.get(order_id, f"没有查到订单 {order_id}。")


@tool
def search_faq(query: str) -> str:
    """搜索客服 FAQ。用户问退款、发货、售后、流程规则时使用。"""
    faq = {
        "退款": "退款需要在 7 天内申请,并保留订单号。",
        "发货": "订单通常在 24 小时内发出。",
        "售后": "售后问题请先提供订单号和问题描述。",
    }
    return faq.get(query, "没有找到相关 FAQ。")

定义结构化输出

python 复制代码
from pydantic import BaseModel, Field


class SupportReply(BaseModel):
    summary: str = Field(description="Concise answer")
    need_human: bool = Field(description="Whether to transfer to human support")
    confidence: float = Field(description="Confidence from 0 to 1", ge=0, le=1)

创建 Agent

python 复制代码
from dataclasses import dataclass
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver


@dataclass
class SupportContext:
    user_id: str
    locale: str = "zh-CN"


agent = create_agent(
    model="openai:gpt-4o-mini",
    tools=[query_order_status, search_faq],
    system_prompt=(
        "你是一个企业客服助手。"
        "用户询问订单、发货、退款、售后时,优先调用工具。"
        "如果工具没有答案,不要编造。"
        "如果涉及高风险操作,提示人工客服介入。"
    ),
    response_format=SupportReply,
    checkpointer=InMemorySaver(),
    context_schema=SupportContext,
    name="support_agent",
)

调用 Agent

python 复制代码
config = {"configurable": {"thread_id": "support-001"}}
context = SupportContext(user_id="user_123", locale="zh-CN")

result = agent.invoke(
    {"messages": [{"role": "user", "content": "帮我查一下 A1001 到哪了?"}]},
    config=config,
    context=context,
)

print(result["structured_response"])

这个例子同时展示了:

  • tools:订单查询和 FAQ 检索。
  • system_prompt:定义客服边界。
  • response_format:最后输出结构化回复。
  • checkpointer + thread_id:保留会话。
  • context_schema + context:传入运行时用户信息。
  • name:便于后续作为子图使用。

这就是 create_agent() 最实际的一面。

常见错误一:没加 checkpointer 就想要多轮记忆

错误:

python 复制代码
agent = create_agent("openai:gpt-4o-mini", tools=[search_faq])

然后:

python 复制代码
config = {"configurable": {"thread_id": "support-001"}}

你以为会记住上下文,但没有 checkpointer,状态不会持久化。

正确做法:

python 复制代码
agent = create_agent(
    "openai:gpt-4o-mini",
    tools=[search_faq],
    checkpointer=InMemorySaver(),
)

常见错误二:把所有业务字段塞进 messages

错误:

python 复制代码
{"messages": [{"role": "user", "content": "user_id=user_123, locale=zh-CN, 帮我查订单"}]}

这样会把运行时上下文和用户输入混在一起。

更好的方式是:

python 复制代码
context = SupportContext(user_id="user_123", locale="zh-CN")

消息只放用户真正说的话。

常见错误三:工具太多,Agent 不知道选哪个

工具太多时,模型会在相似工具之间犹豫。

如果你有:

  • query_order_status
  • query_order_detail
  • query_order_history
  • search_faq
  • search_docs
  • search_policy

模型可能会选错。

更好的做法:

  • 合并相近工具。
  • 提高描述质量。
  • 按场景拆 Agent。
  • 复杂场景让上层路由,而不是让单个 Agent 盲猜。

常见错误四:把高风险动作直接交给 Agent

不要让 Agent 直接执行:

  • 删除数据。
  • 发邮件。
  • 改权限。
  • 转账。
  • 生产配置变更。

这类动作要加:

  • 人工审批。
  • 工单确认。
  • 二次校验。
  • 白名单。
  • 审计日志。

Agent 能建议动作,但不能替代权限系统。

常见错误五:忽略输出结构

如果业务需要后续代码处理,就不要只看最后一句自然语言。

用:

python 复制代码
result["structured_response"]

而不是:

python 复制代码
result["messages"][-1].content

这是很多人把 Agent 当聊天框用时最容易忽略的问题。

总结

create_agent() 的本质是什么?如果只记住一句话:

create_agent() 是 LangChain 提供的高层 Agent harness,用来把模型、工具、提示词、状态、上下文、中间件和结构化输出组合成一个可运行的 Agent。

再具体一点:

  • model 决定 Agent 用什么模型。
  • tools 决定 Agent 能做什么。
  • system_prompt 决定 Agent 的角色和边界。
  • response_format 决定最终输出是否结构化。
  • checkpointer + thread_id 决定多轮状态能否持久化。
  • context_schema + context 决定运行时数据如何注入。
  • state_schema 决定状态还能扩展哪些字段。
  • middleware 决定 Agent 如何被控制、增强和治理。
  • name 决定这个 Agent 在更大图里如何被识别。

如果你把这几个参数想清楚,create_agent() 就不再是黑盒。