解决方案
第一个智能体(入门)

LangChain v1 入门:用 create_agent 跑通第一个智能体(无需 API Key)

大多数入门教程把“跑通”当作终点:贴一段代码,打印一句回答,结束。但真正动手之后,新手遇到的问题往往出现在跑通之后:invoke 返回的到底是什么?模型每一轮收到了哪些消息?为什么换了一种输入写法,程序不报错,问题却没有送到模型?

本页写给刚接触 LangChain v1 的中文开发者:用 create_agent 搭一个会调用工具的智能体,然后把一次调用拆开来看,最后把新手最常踩的坑逐个复现。

为了让每个读者都能原样复现,本页不使用真实模型。 实验里的“模型”是一个脚本化的替身:它按预设顺序回放固定的消息(先决定调用工具,再给出最终回答),同时记录自己每一轮收到了什么。因此不需要 API Key、不联网,每次运行的输出完全一致。代价是:本页不能回答“真实模型会不会选对工具、回答得好不好”,这取决于具体模型,需要你换上真实模型后自己验证(见第五节)。

适用版本与验证信息

项目内容
验证日期2026-10-26
Python3.12.12
包版本langchain==1.4.7、langchain-core==1.7.0、langgraph==1.2.17、langchain-openai==1.7.2、openai==3.37.0、pydantic==2.14.1(见下方版本脚本的输出)
模型脚本化替身模型,不联网、不需要 API Key;第四节第 7 项只构造模型对象,不发出任何请求
官方来源Agents (opens in a new tab)、Quickstart (opens in a new tab)、Tools (opens in a new tab),对照官方文档仓库 langchain-ai/docs (opens in a new tab) 提交 b8d4ced(2026-10-16)
实验代码站点仓库 labs/first-agent/,依赖锁定在 labs/requirements/v1.txt

先确认你的环境和上表一致。下面是本页实验在验证环境里打印的版本,你可以用同样的脚本对照自己的环境:

versions.py
"""Print the interpreter and package versions the other scripts in this lab were recorded with."""
import importlib.metadata as metadata
import sys
 
print("python", sys.version.split()[0])
for package in ["langchain", "langchain-core", "langgraph", "langchain-openai", "openai", "pydantic"]:
    print(package, metadata.version(package))
python 3.12.12
langchain 1.4.7
langchain-core 1.7.0
langgraph 1.2.17
langchain-openai 1.7.2
openai 3.37.0
pydantic 2.14.1

先说结论

  1. 智能体就是“模型在循环里调用工具,直到任务完成”,这是官方文档对 Agent 的定义。create_agent 只需要三样东西:模型、工具列表、系统提示词。
  2. invoke 返回的是一个字典,本页的例子里只有一个键 messages。最终回答是 result["messages"][-1].content,而不是 result.content。
  3. 一次“调用工具再回答”的问答,模型被调用了两轮:第 1 轮收到 2 条消息(系统提示词和用户问题),第 2 轮收到 4 条(多了模型自己的工具调用和工具返回值)。每一轮都带着完整历史。
  4. 模型看不到你的 Python 函数,它只看到一份 JSON 定义:工具名、描述(来自 docstring)、参数名和类型。
  5. 输入必须写成 {"messages": ...}。 把整个输入写成字符串会报错;而沿用旧版 AgentExecutor 的 {"input": ...},或者传空消息列表,不会报错,但问题根本没有送到模型,模型第 1 轮只收到系统提示词。这是本页最值得记住的一条。
  6. 有些错误在构造时就暴露,有些要到第一次 invoke 才暴露。 模型没有实现 bind_tools 就属于后者,而且异常没有任何说明文字。

一、最小可运行的智能体

下面的脚本是完整的最小例子:定义一个查天气的工具,创建智能体,问一句“北京天气怎么样?”,再把返回值逐条打印出来。RecordingModel 和 get_weather 定义在同目录的 shared.py(get_weather 用 @tool 声明,是官方快速入门里“天气工具”的中文版本):

hello_agent.py
"""The smallest useful agent: one tool, one system prompt, one invoke call."""
from langchain.agents import create_agent
 
from shared import RecordingModel, get_weather, weather_script
 
model = RecordingModel(script=weather_script())  # stands in for a real chat model
agent = create_agent(model, tools=[get_weather], system_prompt="你是天气助手,用中文简短回答。")
 
result = agent.invoke({"messages": [{"role": "user", "content": "北京天气怎么样?"}]})
 
print("result type:", type(result).__name__)
print("result keys:", list(result))
for message in result["messages"]:
    if message.type == "ai" and message.tool_calls:
        call = message.tool_calls[0]
        print(f"ai: 调用工具 {call['name']}({call['args']})")
    else:
        print(f"{message.type}: {message.content}")
print("最终回答:", result["messages"][-1].content)
result type: dict
result keys: ['messages']
human: 北京天气怎么样?
ai: 调用工具 get_weather({'city': '北京'})
tool: 晴,22°C
ai: 北京现在晴,22°C。
最终回答: 北京现在晴,22°C。

逐行看这份输出:

  • create_agent(model, tools=[...], system_prompt=...):三个参数就是智能体的全部必需配置。官方文档说明,model 可以是形如 "provider:model" 的字符串,也可以是已经构造好的模型对象;本例传的是模型对象,所以不涉及密钥;
  • 返回值是字典:result type: dict,result keys: ['messages']。智能体的状态里有一个消息序列,invoke 把它整体返回。本例没有配置结构化输出等其他功能,所以只有这一个键;
  • 消息序列有 4 条:human(用户问题)、ai(模型决定调用 get_weather,参数是 {'city': '北京'})、tool(工具的返回值)、ai(模型根据工具结果给出最终回答)。这就是“循环”的一次完整转动:模型 → 工具 → 模型;
  • 最终回答在最后一条消息里:result["messages"][-1].content。官方快速入门打印的是 .content_blocks,本页只使用 .content 取文本,没有验证 .content_blocks。

二、一次调用里,模型到底收到什么

第一节只看了输出。要真正理解智能体,需要知道模型每一轮收到的输入。下面的脚本让替身模型把自己收到的内容记下来,同时打印工具被转换成什么样的定义:

model_inputs.py
"""What the model actually receives: the tool definition and the messages of every round."""
import json
 
from langchain.agents import create_agent
from langchain_core.utils.function_calling import convert_to_openai_tool
 
from shared import RecordingModel, get_weather, weather_script
 
print("-- 模型收到的工具定义 --")
print(json.dumps(convert_to_openai_tool(get_weather), ensure_ascii=False, indent=2))
 
model = RecordingModel(script=weather_script())
agent = create_agent(model, tools=[get_weather], system_prompt="你是天气助手,用中文简短回答。")
agent.invoke({"messages": [{"role": "user", "content": "北京天气怎么样?"}]})
 
 
def show(message):
    if message.type == "ai" and message.tool_calls:
        return f"ai(tool_calls={[(c['name'], c['args']) for c in message.tool_calls]})"
    return f"{message.type}({message.content})"
 
 
print("-- 绑定到模型的工具 --")
print(model.bound)
for number, received in enumerate(model.inputs, start=1):
    print(f"-- 第 {number} 轮,模型收到 {len(received)} 条消息 --")
    for message in received:
        print(" ", show(message))
-- 模型收到的工具定义 --
{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "查询城市当前天气。",
    "parameters": {
      "properties": {
        "city": {
          "type": "string"
        }
      },
      "required": [
        "city"
      ],
      "type": "object"
    }
  }
}
-- 绑定到模型的工具 --
['get_weather']
-- 第 1 轮,模型收到 2 条消息 --
  system(你是天气助手,用中文简短回答。)
  human(北京天气怎么样?)
-- 第 2 轮,模型收到 4 条消息 --
  system(你是天气助手,用中文简短回答。)
  human(北京天气怎么样?)
  ai(tool_calls=[('get_weather', {'city': '北京'})])
  tool(晴,22°C)

几个值得注意的点:

  • 工具定义只有名称、描述和参数。 description 就是 get_weather 的 docstring 查询城市当前天气。;参数 city 只有类型 string,没有任何参数说明。想让模型明白每个参数的含义,要用 args_schema 或 parse_docstring=True,详见工具调用 schema 排障;
  • 工具被绑定到了模型上:['get_weather'] 是 create_agent 在调用模型前传给 bind_tools 的工具名;
  • 第 1 轮 2 条消息,第 2 轮 4 条。 第 2 轮多出来的两条正是第 1 轮的产物:模型的工具调用和工具的返回值。智能体没有“记住”什么,它只是把越来越长的历史整体再发给模型一遍。这也解释了为什么对话越长、工具调用越多,每一轮的输入就越长,通常成本和延迟也随之增加;
  • 系统提示词是每一轮的第一条消息。

理解这一点之后,很多现象都有了解释:模型“忘记”了前面的内容,是因为那些消息没有出现在这一轮的输入里;多轮对话要接着聊,需要你让历史保存下来并带上 thread_id,这是下一步要学的内容,见ConversationBufferMemory 迁移到 v1。

三、invoke 的输入:哪些写法能用,哪些会悄悄出错

从旧教程复制代码的新手,最容易在输入的写法上出问题。下面把 7 种常见写法逐个交给同一个智能体,记录三件事:会不会报错;如果不报错,第 1 轮模型收到了哪些消息;用户的问题有没有真的送到模型:

input_forms.py
"""Which shapes of invoke() input does create_agent accept, and what happens to the question."""
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
 
from shared import RecordingModel, get_weather, weather_script
 
QUESTION = "北京天气怎么样?"
CASES = [
    ("messages 是 dict 列表", {"messages": [{"role": "user", "content": QUESTION}]}),
    ("messages 是元组列表", {"messages": [("user", QUESTION)]}),
    ("messages 是 HumanMessage 列表", {"messages": [HumanMessage(QUESTION)]}),
    ("messages 直接是字符串", {"messages": QUESTION}),
    ("整个输入是字符串", QUESTION),
    ('旧写法 {"input": ...}', {"input": QUESTION}),
    ('空列表 {"messages": []}', {"messages": []}),
]
 
for label, payload in CASES:
    model = RecordingModel(script=weather_script())
    agent = create_agent(model, tools=[get_weather], system_prompt="你是天气助手,用中文简短回答。")
    try:
        agent.invoke(payload)
    except Exception as error:  # report the exception type and its first line
        first_line = (str(error).splitlines() or ["(无说明文字)"])[0]
        print(f"{label}: 报错 {type(error).__name__}: {first_line}")
        continue
    first_round = [f"{m.type}" for m in model.inputs[0]]
    sent = QUESTION in "".join(str(m.content) for m in model.inputs[0])
    print(f"{label}: 不报错;第 1 轮模型收到 {first_round};问题送达模型: {sent}")
messages 是 dict 列表: 不报错;第 1 轮模型收到 ['system', 'human'];问题送达模型: True
messages 是元组列表: 不报错;第 1 轮模型收到 ['system', 'human'];问题送达模型: True
messages 是 HumanMessage 列表: 不报错;第 1 轮模型收到 ['system', 'human'];问题送达模型: True
messages 直接是字符串: 不报错;第 1 轮模型收到 ['system', 'human'];问题送达模型: True
整个输入是字符串: 报错 InvalidUpdateError: Expected dict, got 北京天气怎么样?
旧写法 {"input": ...}: 不报错;第 1 轮模型收到 ['system'];问题送达模型: False
空列表 {"messages": []}: 不报错;第 1 轮模型收到 ['system'];问题送达模型: False

把结果整理成一张表:

写法结果问题是否送达模型
{"messages": [{"role": "user", "content": ...}]}正常,官方快速入门的写法是
{"messages": [("user", ...)]}正常是
{"messages": [HumanMessage(...)]}正常是
{"messages": "..."}(直接给字符串)正常,被当作一条用户消息是
"..."(整个输入是字符串)报错 InvalidUpdateError: Expected dict, got ...否
{"input": "..."}(旧版 AgentExecutor 写法)不报错否,模型只收到系统提示词
{"messages": []}不报错否,模型只收到系统提示词

最危险的是最后两行:程序正常运行、正常返回,但模型从头到尾没有看到用户的问题。 本页的替身模型是按脚本回放的,所以仍然“回答”了天气;换成真实模型,它会面对一个只有系统提示词的请求,结果可能是胡乱回答,也可能被模型服务拒绝,具体取决于模型和服务,本页没有测试。

为什么会这样?官方文档说明,调用智能体是向它的状态传入一次更新,所有智能体的状态里都有一个消息序列,实测里携带它的键就是 messages。input 不是这个键,传进去的内容没有成为任何一条消息,框架也没有因此报错。这是实测观察到的行为,官方文档没有专门说明这种情况。

实用建议:

  • 不要让业务代码直接拼 invoke 的输入。写一个只接受字符串的小函数,统一生成 {"messages": [{"role": "user", "content": text}]};
  • 从 AgentExecutor 迁移过来时,全局搜索 "input" 键,这类调用不会触发任何异常,只会让回答质量悄悄变差,迁移背景见迁移 Runbook;
  • 上线后在日志里记录“模型第 1 轮收到的消息里是否有用户消息”,它能直接发现这类问题。

四、新手常见错误对照

下面这组脚本把 7 种新手常见的操作逐个执行,记录报错的类型和第一行文字,以及它是在构造智能体时还是在第一次调用时暴露:

common_mistakes.py
"""Beginner mistakes around create_agent, each reproduced with its exact outcome."""
from langchain.agents import create_agent
from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.messages import AIMessage
from langchain_core.outputs import ChatGeneration, ChatResult
 
from shared import RecordingModel, get_weather, weather_script
 
INPUT = {"messages": [{"role": "user", "content": "北京天气怎么样?"}]}
 
 
def first_line(error: Exception) -> str:
    return (str(error).splitlines() or ["(无说明文字)"])[0]
 
 
def report(label: str, action) -> None:
    print(label)
    try:
        outcome = action()
    except Exception as error:
        print(f"    -> {type(error).__name__}: {first_line(error)}")
    else:
        print(f"    -> 成功: {outcome}")
 
 
def agent_with(tools):
    return create_agent(RecordingModel(script=weather_script()), tools=tools)
 
 
class NoToolCallingModel(BaseChatModel):
    """A chat model that never implemented bind_tools()."""
 
    @property
    def _llm_type(self) -> str:
        return "no-tool-calling"
 
    def _generate(self, messages, stop=None, run_manager=None, **kwargs) -> ChatResult:
        return ChatResult(generations=[ChatGeneration(message=AIMessage(content="你好"))])
 
 
def plain_function(city: str) -> str:
    """查询城市当前天气。"""
    return "晴"
 
 
def without_docstring(city: str) -> str:
    return "晴"
 
 
def read_result_as_message():
    result = agent_with([get_weather]).invoke(INPUT)
    return result.content
 
 
def read_result_correctly():
    result = agent_with([get_weather]).invoke(INPUT)
    return result["messages"][-1].content
 
 
def model_without_tool_calling():
    agent = create_agent(NoToolCallingModel(), tools=[get_weather])
    print("    (create_agent 返回了,没有报错)")
    return agent.invoke(INPUT)["messages"][-1].content
 
 
report("1. 把 invoke 的返回值当消息读:result.content", read_result_as_message)
report('2. 正确读法:result["messages"][-1].content', read_result_correctly)
report("3. tools 传了单个工具而不是列表:tools=get_weather", lambda: agent_with(get_weather))
report("4. 不用 @tool,直接传带类型标注和 docstring 的普通函数", lambda: agent_with([plain_function]).invoke(INPUT)["messages"][-1].content)
report("5. 普通函数没有 docstring", lambda: agent_with([without_docstring]))
report("6. 模型没有实现 bind_tools", model_without_tool_calling)
report('7. 用字符串指定模型但没有设置密钥:create_agent("openai:gpt-5.5", ...)', lambda: create_agent("openai:gpt-5.5", tools=[get_weather]))
1. 把 invoke 的返回值当消息读:result.content
    -> AttributeError: 'dict' object has no attribute 'content'
2. 正确读法:result["messages"][-1].content
    -> 成功: 北京现在晴,22°C。
3. tools 传了单个工具而不是列表:tools=get_weather
    -> ValueError: The first argument must be a string or a callable with a __name__ for tool decorator. Got <class 'tuple'>
4. 不用 @tool,直接传带类型标注和 docstring 的普通函数
    -> 成功: 北京现在晴,22°C。
5. 普通函数没有 docstring
    -> ValueError: Function must have a docstring if description not provided.
6. 模型没有实现 bind_tools
    (create_agent 返回了,没有报错)
    -> NotImplementedError: (无说明文字)
7. 用字符串指定模型但没有设置密钥:create_agent("openai:gpt-5.5", ...)
    -> OpenAIError: Missing credentials. Please pass an `api_key`, `workload_identity`, `admin_api_key`, or set the `OPENAI_API_KEY` or `OPENAI_ADMIN_KEY` environment variable.

对照表:

现象原因何时暴露处理
AttributeError: 'dict' object has no attribute 'content'把 invoke 的返回值当成消息读读取结果时用 result["messages"][-1].content
ValueError: The first argument must be a string or a callable ... Got <class 'tuple'>tools= 传了单个工具而不是列表;报错文字提到 tuple,容易让人摸不着头脑构造时写成 tools=[get_weather]
ValueError: Function must have a docstring if description not provided.工具函数没有 docstring,也没有显式给 description构造时补 docstring,或显式传描述;详见工具调用 schema 排障
NotImplementedError,没有任何说明文字模型类没有实现 bind_tools,不支持工具调用;create_agent 本身不报错第一次 invoke换用支持工具调用的模型;接入前先做验收,见OpenAI 兼容模型接入验收
OpenAIError: Missing credentials. ...用 "openai:..." 字符串指定模型,但没有设置密钥环境变量构造时在环境变量里设置密钥,不要写进代码;报错文字里列出了可用的环境变量名

另外两项没有出错,而是值得记住的好消息:不用 @tool 也行,带类型标注和 docstring 的普通 Python 函数可以直接放进 tools=[...](第 4 项,与官方文档“传入任何可调用对象”的说法一致);而普通函数缺少 docstring 时,和 @tool 一样会在构造时报错(第 5 项)。

这张表里“何时暴露”这一列很重要:构造时暴露的错误最容易处理,一启动就看到;第一次调用才暴露的错误,在测试覆盖不足时会拖到线上。所以哪怕只是入门项目,也建议在启动阶段做一次最小的冒烟调用。

五、换成真实模型,要改哪里

要换成真实模型,只需要改 create_agent 的第一个参数。官方文档给出的写法是 "provider:model" 字符串,例如(这段代码没有在本站运行,因为需要密钥和网络):

from langchain.agents import create_agent
 
agent = create_agent(
    model="openai:gpt-5.5",  # 官方文档示例中的模型字符串;可用的模型名以官方文档为准
    tools=[get_weather],
    system_prompt="你是天气助手,用中文简短回答。",
)

密钥通过环境变量提供(第四节第 7 项的报错文字里已经列出变量名),不要写进代码,也不要提交到仓库。如果你使用的是兼容 OpenAI 接口的第三方服务,需要自定义服务地址,接入前的验收清单见OpenAI 兼容模型接入验收。

换模型之后,本页的替身无法替你验证的有三件事,建议按顺序检查:

  1. 模型真的发出了工具调用吗? 打印 result["messages"],确认有带 tool_calls 的 ai 消息,而不是直接回答;
  2. 工具参数合法吗? 参数不合法时,框架会把错误当作 ToolMessage 交还给模型,运行不会中断,排查方法见故障诊断中心;
  3. 超时和重试怎么设? 默认配置下的最坏延迟可能出乎意料,见模型调用的延迟预算。

六、本站术语约定

LangChain 的资料中英文夹杂,同一个概念在不同文章里译法不一。下表是本站正文的用法约定,不是官方译法,目的是让你在站内不同页面之间读到一致的词。遇到官方英文资料时,请按左列的英文对照:

英文本站用法说明
Agent智能体;正文常直接写 Agent官方定义:模型在循环里调用工具,直到任务完成
tool / tool calling工具 / 工具调用工具调用指模型输出“要调用哪个工具、参数是什么”,真正执行工具的是框架
message消息分 human(用户)、ai(模型)、tool(工具返回值)、system(系统提示词)等类型
system prompt系统提示词每一轮请求的第一条消息
state状态智能体的状态里有一个 messages 序列
thread / thread_id会话 / thread_id用同一个 thread_id 才能接着上次的历史,需要配置 checkpointer
checkpoint / checkpointer检查点 / 检查点存储,正文常保留英文见checkpoint 与故障恢复
middleware中间件见Agent 中间件与安全护栏
prompt提示词
trace追踪见LangSmith 追踪上线前核对
streaming流式输出本页未涉及
structured output结构化输出本页未涉及

约定的用词会随站点内容增加而补充;某个词在你看来有更好的译法,欢迎通过联系我们提出。

失败与回滚

  • 输出和本页不一致:先运行上文的版本脚本,对照版本表。本页的实验锁定了具体版本,其中报错文字(尤其是第四节第 7 项来自 openai 包的那一行)会随版本变化;
  • 回到验证过的版本:pip install "langchain==1.4.7" "langchain-core==1.7.0" "langgraph==1.2.17",需要同时锁定 langchain-openai==1.7.2 和 openai==3.37.0 才能完全复现第四节第 7 项;
  • 从这里往下走遇到问题:把报错原文拿到故障诊断中心对号入座;
  • 局限:本页只验证了框架的行为,没有验证任何真实模型。替身模型不会犯“选错工具”“参数写错”这类错误,真实模型会,所以本页的所有“成功”只代表链路本身是通的。

相关阅读

来源

  • Agents (opens in a new tab):Agent 的定义(模型在循环里调用工具)、model 可以是 "provider:model" 字符串或模型对象、tools 接受任何可调用对象、调用智能体是向状态传入更新、状态里有消息序列;
  • Quickstart (opens in a new tab):create_agent、天气工具、{"messages": [{"role": "user", ...}]} 的调用方式、result["messages"][-1] 的读取方式;
  • Tools (opens in a new tab):工具定义与 docstring 作为描述。

以上来源均于 2026-10-26 核对。本页所有输出来自站点仓库 labs/first-agent/ 在上表版本下的实际运行。

维护信息

  • 编辑:LangChain 中文网编辑部(内容标准见编辑与纠错说明);
  • 最近验证:2026-10-26;
  • 复现:先执行 labs/setup-envs.sh v1 重建环境,再执行 python3 labs/run.py --lab first-agent;
  • 触发更新:langchain 主版本变化、create_agent 的输入或工具参数行为变化、官方快速入门的写法变化;
  • 发现错误请按联系我们反馈。