1. 本章目标 #

上一章讲清了 Agent = Model + 驾驭层(Harness):模型负责推理与决策,驾驭层负责「模型 ↔ 工具」循环、提示词,以及后续可叠加的中间件。
概念有了,还缺一次「亲手跑通」——让模型真的去调一个工具,而不是凭训练数据瞎编天气。

本章目标:

跑通一个可调用工具的天气助手,并看懂它的执行轨迹。

学完你应能:

参考文档:

跑本章示例前请确认:

2. 第一个 Agent:天气助手 #

最小闭环只有三步:

定义工具函数  →  create_agent(...)  →  agent.invoke(...)

为什么选「查天气」作第一例?它够简单,又能逼出完整的 Agent 行为:

  1. 用户问的是事实型问题(今天北京天气),模型不该瞎编
  2. 必须通过工具拿结果(本章用模拟字符串代替真实 API)
  3. 模型还要把工具结果组织成自然语言再回复用户

分工记牢:

角色 做什么 不做什么
模型 决定是否调工具、填参数、组织最终回答 真的去访问天气网站
工具函数 执行真实 / 模拟查询 代替模型做决策
驾驭层(create_agent) 调度循环、执行工具、维护消息 替你写业务逻辑

工具可以是普通 Python 函数。模型能「看懂」它,主要靠两样:类型注解(参数名与类型)和 docstring(工具用途说明)。说明写糊了,模型就容易选错工具或填错参数。

2.1. 1.weather_agent.py #

先整段跑通,再对照 §2.2 看内部循环。

`python agent.py

从 langchain.agents 导入 create_agent,用于创建智能体 #

from langchain.agents import create_agent

从 dotenv 导入 load_dotenv,用于从 .env 加载环境变量 #

from dotenv import load_dotenv

加载 .env 中的密钥等变量;override=True 表示覆盖已存在的同名变量 #

load_dotenv(override=True)

定义获取天气的工具函数:参数 city 为城市名,返回值为字符串 #

def get_weather(city: str) -> str:

# 工具说明(docstring),会作为工具描述提供给模型,帮助它决定何时调用
"""获取指定城市的天气信息。"""
# 返回模拟的天气结果(真实项目里这里会调天气 API)
return f"{city} 今天晴,气温 25°C。"

调用 create_agent 创建智能体实例 #

agent = create_agent(

# 指定供应商与模型名:deepseek 的 deepseek-v4-flash
model="deepseek:deepseek-v4-flash",
# 将 get_weather 注册为可调用工具(普通函数即可)
tools=[get_weather],
# 系统提示:设定人设,并要求需要天气时必须调用工具、不要编造
system_prompt="你是一个简洁、可靠的中文助手。需要天气信息时请调用工具,不要编造。",
# 结束 create_agent 调用

)

调用智能体处理用户请求,并拿到完整执行结果 #

result = agent.invoke(

# 输入是状态字典;messages 为对话消息列表,角色为 user
{"messages": [{"role": "user", "content": "北京今天天气怎么样?"}]}
# 结束 agent.invoke 调用

)

取消息轨迹中的最后一条(通常是助手最终回复),打印其文本内容 #

print(result["messages"][-1].content)


预期类似:

```text
北京今天晴,气温 25°C。

读代码时抓住四个点:

  1. load_dotenv:把 .env 里的密钥注入进程;漏掉这一步,常见报错就是 401。
  2. 工具函数:city: str + docstring 决定模型「怎么填参、何时该调」。
  3. create_agent:把模型字符串、工具列表、系统提示绑成一个可 invoke 的 Agent。
  4. 输入形态:{"messages": [...]} 是状态字典;本章先用 dict 写法,第 4 章再系统讲消息对象。

若模型不调工具、自己编造天气,先检查 system_prompt 是否写明「必须调用工具」,以及工具 docstring 是否清楚。

2.2. 这段代码在做什么 #

一次 invoke 外表像「问一句、答一句」,内部其实可能多步。这正是驾驭层替你省下的手写循环:

用户:「北京今天天气怎么样?」
        │
        ▼
   create_agent 循环
        │
        ├─ 模型判断:需要天气 → 调用 get_weather(city="北京")
        ├─ 工具返回:"北京 今天晴,气温 25°C。"
        ├─ 模型根据工具结果组织自然语言回答
        └─ 结束循环,返回完整 messages

对照第 1 章的公式:

要点回顾:

参数 / API 含义
model "deepseek:deepseek-v4-flash" 或已初始化的模型实例(第 3 章细讲)
tools 可调用对象列表;docstring + 类型注解帮助模型选对工具
system_prompt 系统行为设定(人设、何时必须调工具、禁止编造等)
invoke({...}) 同步执行一轮(内部可能多步工具调用)

输入是带 messages 的状态字典;输出也是完整状态。
result["messages"][-1].content 只是「最后一句」;真正调试时要看整条轨迹(下一节)。

3. 看懂返回的消息轨迹 #

只打印最后一句,只能确认「好像答对了」,看不出:

调试 Agent 时,优先看完整 messages。每条消息都是状态的一帧;跑完后这份列表就是把黑盒变白盒的入口。第 4 章会系统讲消息类型,本章先建立阅读习惯。

3.1. inspect_messages.py #

对每条消息关注三件事:

看什么 为什么重要
类型名(HumanMessage / AIMessage / ToolMessage) 知道这一帧是谁产生的
content 用户原文、工具返回、最终回答多在这里
tool_calls(若有) 模型发起了哪些工具调用、参数是什么
# 从 langchain.agents 导入 create_agent,用于创建智能体
from langchain.agents import create_agent
# 从 dotenv 导入 load_dotenv,用于从 .env 加载环境变量
from dotenv import load_dotenv
# 加载 .env 中的密钥等变量;override=True 表示覆盖已存在的同名变量
load_dotenv(override=True)

# 定义获取天气的工具函数:参数 city 为城市名,返回值为字符串
def get_weather(city: str) -> str:
    # 工具说明(docstring),供模型理解该工具用途
    """获取指定城市的天气信息。"""
    # 返回模拟的天气结果字符串
    return f"{city} 今天晴,气温 25°C。"

# 调用 create_agent 创建智能体实例
agent = create_agent(
    # 指定使用 DeepSeek 的 deepseek-v4-flash 模型
    model="deepseek:deepseek-v4-flash",
    # 注册天气工具
    tools=[get_weather],
    # 系统提示:需要天气信息时请调用工具
    system_prompt="你是一个简洁、可靠的中文助手。需要天气信息时请调用工具。",
    # 结束 create_agent 调用
)
# 调用智能体,询问上海天气,并拿到完整结果
result = agent.invoke(
    # 构造包含用户问题的消息列表
    {"messages": [{"role": "user", "content": "上海天气如何?"}]}
    # 结束 agent.invoke 调用
)
# 遍历完整消息轨迹,同时拿到索引 i 与消息对象 msg
for i, msg in enumerate(result["messages"]):
    # 取出消息类型名,例如 HumanMessage / AIMessage / ToolMessage
    msg_type = type(msg).__name__
    # 打印当前消息的序号与类型,便于分段阅读轨迹
    print(f"\n===== [{i}] {msg_type} =====")
    # 安全读取消息的文本内容;没有 content 属性时得到 None
    content = getattr(msg, "content", None)
    # 若存在文本内容,则打印出来(工具结果、最终回答等多在这里)
    if content:
        # 打印该消息的文本内容
        print("content:", content)
    # 安全读取模型发起的工具调用列表;没有则得到 None
    tool_calls = getattr(msg, "tool_calls", None)
    # 若本条消息包含工具调用请求,则打印出来
    if tool_calls:
        # 打印 tool_calls 详情(工具名、参数等)
        print("tool_calls:", tool_calls)

典型轨迹(简化):

[0] HumanMessage     → 用户问题
[1] AIMessage        → 带 tool_calls:get_weather(city="上海")
[2] ToolMessage      → 工具返回字符串
[3] AIMessage        → 最终自然语言回答

如何读:

这就是驾驭层帮你自动完成的「模型 ↔ 工具」循环。第 4 章会系统讲消息类型;第 8 章会深入工具定义与错误处理。

4. 多工具助手 #

真实项目很少只有一个工具。模型要按用户意图在多个工具里选型,甚至一次问题里连调多个。

下面加「天气 + 加法」,观察它如何按需选择(更复杂的多工具协作见第 9 章)。
引导选型靠两样,缺一不可:

  1. 工具的 名字 + 类型注解 + docstring(模型「看得见」的说明书)
  2. system_prompt 里的使用策略(何时调哪个、禁止心算等业务约束)

工具一多,常见失败不是「调不动」,而是「调错」:该用计算器却心算、该查天气却空答。所以多工具阶段要养成:改完提示 / 说明后,用 §3 的轨迹检查实际调用了谁。

4.1. multi_tools_agent.py #

本例用 @tool 显式注册;入门阶段把普通函数直接放进 tools=[...] 往往也行。更关键的是说明写没写清楚。

# 从 langchain.agents 导入 create_agent,用于创建智能体
from langchain.agents import create_agent
# 从 langchain.tools 导入 tool 装饰器,用于把函数注册为工具
from langchain.tools import tool
# 从 dotenv 导入 load_dotenv,用于从 .env 加载环境变量
from dotenv import load_dotenv
# 加载 .env 中的密钥等变量;override=True 表示覆盖已存在的同名变量
load_dotenv(override=True)

# 使用 @tool 装饰器,把下方函数注册为可供 Agent 调用的工具
@tool
# 定义获取天气的工具函数:参数 city 为城市名,返回值为字符串
def get_weather(city: str) -> str:
    # 工具说明(docstring),会作为工具描述提供给模型
    """获取指定城市的天气信息。"""
    # 返回模拟的天气结果字符串
    return f"{city} 今天晴,气温 25°C。"

# 使用 @tool 装饰器,把下方函数注册为可供 Agent 调用的工具
@tool
# 定义整数加法工具:接收两个整数 a、b,返回它们的和
def add(a: int, b: int) -> int:
    # 工具说明(docstring),会作为工具描述提供给模型
    """计算两个整数之和。"""
    # 返回 a 与 b 的加法结果
    return a + b

# 调用 create_agent 创建多工具智能体
agent = create_agent(
    # 指定使用 DeepSeek 的 deepseek-v4-flash 模型
    model="deepseek:deepseek-v4-flash",
    # 同时注册天气工具与加法工具
    tools=[get_weather, add],
    # 系统提示:说明何时调哪个工具,并禁止心算加法
    system_prompt=(
        # 设定助手角色为办公助手
        "你是办公助手。"
        # 查天气时必须调用 get_weather
        "查天气时调用 get_weather;"
        # 做整数加法时必须调用 add,不要心算
        "做整数加法时调用 add,不要心算。"
    # 结束 system_prompt 字符串拼接
    ),
    # 结束 create_agent 调用
)
# 一次提问里同时触发两类工具,调用智能体并拿到结果
result = agent.invoke(
    # 构造输入状态字典
    {
        # messages 为对话消息列表
        "messages": [
            {
                # 角色为用户
                "role": "user",
                # 内容同时包含查天气与做加法两个请求
                "content": "北京天气怎么样?另外帮我算 128 + 256。",
            # 结束单条用户消息字典
            }
        # 结束 messages 列表
        ]
    # 结束输入状态字典
    }
    # 结束 agent.invoke 调用
)
# 打印消息轨迹中最后一条的文本内容(助手最终回复)
print(result["messages"][-1].content)

@tool 会把函数名、参数 schema、docstring 整理成模型可见的工具描述。
建议用 §3 的方法打印轨迹:正常情况下你会看到至少两段工具相关步骤(天气一次、加法一次;顺序可能因模型而异)。

观察清单:

5. 流式输出 #

回答较长,或想观察「模型步 / 工具步如何交替」时,用 agent.stream。
注意概念分界(第 3 章还会再对照一次):

API 流出来的是什么 适合
model.stream 模型 token / 文本块 打字机式展示纯生成
agent.stream 图节点更新(模型步、工具步等) 观察 Agent 中间过程

第一次看 updates 可能较碎:你会陆续看到模型节点、工具节点的增量字典,而不是一整段干净的最终句子。这是正常现象。

5.1. stream_agent.py #

# 从 langchain.agents 导入 create_agent,用于创建智能体
from langchain.agents import create_agent
# 从 dotenv 导入 load_dotenv,用于从 .env 加载环境变量
from dotenv import load_dotenv
# 加载 .env 中的密钥等变量;override=True 表示覆盖已存在的同名变量
load_dotenv(override=True)

# 定义获取天气的工具函数:参数 city 为城市名,返回值为字符串
def get_weather(city: str) -> str:
    # 工具说明(docstring),供模型理解该工具用途
    """获取指定城市的天气信息。"""
    # 返回模拟的天气结果字符串
    return f"{city} 今天晴,气温 25°C。"

# 调用 create_agent 创建智能体实例
agent = create_agent(
    # 指定使用 DeepSeek 的 deepseek-v4-flash 模型
    model="deepseek:deepseek-v4-flash",
    # 注册天气工具
    tools=[get_weather],
    # 系统提示:设定为简洁的中文助手
    system_prompt="你是一个简洁的中文助手。",
    # 结束 create_agent 调用
)
# 以流式方式运行 Agent;stream_mode="updates" 表示按图节点更新推送
for chunk in agent.stream(
    # 输入状态字典:用户询问深圳今天天气
    {"messages": [{"role": "user", "content": "深圳今天天气?"}]},
    # 指定流式模式为 updates;也可用 "values" 查看完整状态快照
    stream_mode="updates",
    # 结束 agent.stream 调用
):
    # 打印每一个流式块(可能包含模型节点或工具节点的增量更新)
    print(chunk)

stream_mode 常见选择:

实践建议:

6. 常见问题排查 #

按出现频率排查:

现象 可能原因 处理
模型直接编造天气、不调工具 系统提示太弱,或 docstring 不清晰 在 system_prompt 明确「必须调用工具」;补全工具说明;用 §3 确认有无 tool_calls
调了工具但城市参数怪异 抽取不稳 在系统提示约束「城市用短名称」;工具参数名尽量直白(如 city)
AuthenticationError / 401 密钥无效、过期,或未加载 .env 检查 DEEPSEEK_API_KEY;确认执行了 load_dotenv(override=True)
create_agent 导入失败 LangChain 版本过旧 pip install -U langchain
找不到 deepseek 集成 未装集成包 pip install -U langchain-deepseek
LangSmith 无数据 未开启追踪或 key 错误 确认 LANGSMITH_TRACING=true 与 API Key

建议尽早打开 LangSmith Tracing:比起只在终端 print 最后一句,更容易看清每一步工具调用、耗时与失败点。后面 Agent / RAG 变复杂时,这个习惯能少猜很多。

7. 练习 #

每题做完后,尽量用 §3 的方式打印轨迹,而不是只看最终一句。

  1. 改城市:把用户问题换成「广州和杭州天气分别怎么样?」观察是否多次调用 get_weather(一次一城,或一次请求里带两个调用,取决于模型行为)。
  2. 加工具:再写一个 get_time(city: str) -> str(可返回固定字符串),让助手能回答「现在几点」;确认轨迹里出现该工具名。
  3. 收紧人设:把 system_prompt 改成「只回答天气;其他问题礼貌拒绝」,测一句无关问题(如「帮我写诗」),看是否拒答且不乱调工具。
  4. 对照轨迹:用 inspect_messages.py 打印完整 messages,标出哪一步是 tool_calls,哪一步是 ToolMessage。

8. 本章小结 #

  1. 最小闭环:定义工具 → create_agent → invoke。
  2. 工具可以是普通函数;写清 类型注解 + docstring,再用 system_prompt 收紧行为。
  3. 调试看 完整 messages,不要只看最后一句;关注 tool_calls 与 ToolMessage。
  4. 多工具时靠「工具说明书 + 系统策略」引导选型,并用轨迹验证。
  5. agent.stream 流的是图节点更新;尽早打开 LangSmith,为后面复杂 Agent / RAG 打底。

下一章:Chat Models 聊天模型——统一用 init_chat_model 切换供应商、调参数、做流式输出,并学会把模型实例交给 Agent。