‹ 博客列表

LangChain 1.2 入门(二十六):ToolStrategy 三个参数与流式输出七种模式

tool_message_content 省 token、handle_errors 的五档错误处理与两类异常,再用 stream_mode 的七种粒度解决「Agent 跑十几秒没动静」,最后组装一个多功能助手。

LangChain 1.2 入门(二十六):ToolStrategy 三个参数与流式输出七种模式

ToolStrategy 是 Agent 结构化输出的默认选择,它能跑通不代表好用。这一篇讲它的三个参数怎么调,然后解决另一个体验问题:Agent 跑十几秒没动静,用户以为卡死了——这就轮到流式输出。

26.1 tool_message_content:给伪消息换个说法

ToolStrategy 靠工具调用实现结构化输出,所以 LangChain 会在消息列表末尾追加一条 ToolMessage 让链路完整。但这条消息是伪的——没有真正的工具执行。默认内容长这样:

Name: ContactInfo
Returning structured response: name='小明' email='shkstart@atguigu.com' phone='12345678912'

问题在于:这段数据会进入后续轮次的上下文,既占 token,又对最终用户不友好。用 tool_message_content 换成人话:

agent = create_agent(
    model=model,
    response_format=ToolStrategy(
        ContactInfo,
        tool_message_content="格式化输出成功!",
    ),
)
================================= Tool Message =================================
Name: ContactInfo
格式化输出成功!

两个收益很实际:对话流里是自然语言而不是数据块;长数据被一句确认替代,省下上下文窗口的 token。

有个细节必须记清:不管 tool_message_content 怎么设置,结构化结果都会完整存进 result["structured_response"]。自定义的只是对话历史里那一条记录,不影响取数。

26.2 handle_errors:五档错误处理

模型能力有限,输出偶尔不合规。ToolStrategy 用 handle_errors 决定「出错了怎么办」,默认是 True:

取值 行为 适用
True(默认) 捕获所有异常,用 LangChain 内置错误模板提示模型重试 大多数通用场景
False 关闭重试,异常直接抛出,中断程序 希望显式感知失败
"自定义字符串" 捕获异常,但用你写的固定文本作为错误提示 想给模型统一、友好的引导
异常类型 / 元组 只对指定类型重试,其他异常照常抛出 精准控制重试范围
callable 自定义函数处理异常,可按异常类型返回不同提示 复杂、精细化错误处理

一个能稳定复现错误的例子:response_format 给了 Union[ContactInfo, EventDetails],而输入里同时包含联系人和活动信息,模型会同时输出两个结构化结果——这违背「只能有一个」的约定。

agent = create_agent(
    model=model,
    response_format=ToolStrategy(
        Union[ContactInfo, EventDetails],
        tool_message_content="提取完成!",
        handle_errors=True,
        # handle_errors="请检查输入数据",
    ),
)

result = agent.invoke({"messages": [{
    "role": "user",
    "content": "请提取以下文本中内容:姓名:张三,电子邮箱:zhang3@atguigu.com,"
               "活动名称:公司年会,活动日期:2026-07-15",
}]})

三种设置的结果对比:

True:模型收到一条 ToolMessage,内容是 Error: Model incorrectly returned multiple structured responses (ContactInfo, EventDetails) when only one is expected.,随后重新推理,挑一个最符合的 Schema 再输出,最终拿到 structured_response。

False:直接抛 MultipleStructuredOutputsError,程序中断。

"请检查输入数据":错误消息换成你写的这句话,模型据此调整后再试。

注意 tool_message_content 只管成功后的消息,不影响错误消息——两者是两条独立通道。

26.3 两类一定会遇到的异常

LangChain 默认拦截并处理两类结构化异常:

  • MultipleStructuredOutputsError:返回的工具调用请求数量大于 1(多结构化输出错误);
  • StructuredOutputValidationError:输出格式不符合结构化要求(校验失败)。

想只对其中一类重试,就传具体类型:

from langchain.agents.structured_output import (
    ToolStrategy,
    StructuredOutputValidationError,
    MultipleStructuredOutputsError,
)

response_format=ToolStrategy(
    Union[ContactInfo, EventDetails],
    handle_errors=(MultipleStructuredOutputsError, StructuredOutputValidationError),
)

关于 Union 还有一句提醒:Union[A, B] 只是告诉框架「可能有两种结构」,最终只会输出一种。模型选哪种靠语义判断,输入含糊时它就会贪心地都输出,这才触发了上面那个错误。能拆成两次调用就别用 Union。

26.4 流式输出:别让用户盯着空白

agent.invoke() 内部可能经历多轮模型调用和工具执行,几十秒才返回是常态。用户可以接受「慢慢出字」,不能接受「什么都不发生」。所以用 agent.stream():

for chunk in agent.stream(
    {"messages": [{"role": "user", "content": "..."}]},
    stream_mode="messages",
):
    print(chunk)

stream_mode 决定「每次吐出什么粒度」,七种取值如下:

Agent 流式输出的七种 stream_mode 对照:values、updates、messages、tasks、debug、checkpoints、custom 各自的输出内容与典型用途

几个最常用的:

updates(默认)每步只给增量,最适合看 Agent 的执行过程:先看到「决定调用工具」,再看到工具返回值,最后看到答案。

for chunk in customer_service_agent.stream(
    {"messages": [{"role": "user", "content": "查询客户 ID 为 CUST123456 的个人信息和可用优惠"}]},
    stream_mode="updates",
):
    rprint(chunk)

values 每步给完整状态,适合需要全量快照或做持久化的场景;messages 给流式 token 加元数据(能看出这条来自 model 还是 tool),是做打字机效果的唯一选择。

custom 最有意思:让工具自己往外发进度。用 get_stream_writer():

from langgraph.config import get_stream_writer
import time

@tool
def generate_sales_report() -> str:
    """生成销售报告"""
    writer = get_stream_writer()
    writer({"type": "生成销售报告", "message": "开始生成销售报告"})
    for i in range(1, 4):
        time.sleep(0.5)
        writer({"type": "生成销售报告", "message": f"生成销售报告进度百分比:{i * 25}%"})
    writer({"type": "生成销售报告", "message": "报告生成完成"})
    return "销售报告:总收入 150 万元,同比增长 12%"

for chunk in reporting_agent.stream(
    {"messages": [{"role": "user", "content": "生成销售报告和库存报告"}]},
    stream_mode="custom",
):
    print(chunk)
{'type': '生成销售报告', 'message': '开始生成销售报告'}
--------------------------------------------------
{'type': '生成销售报告', 'message': '生成销售报告进度百分比:25%'}
--------------------------------------------------
...
{'type': '生成销售报告', 'message': '报告生成完成'}

checkpoints 需要先给 Agent 配 checkpointer(例如 InMemorySaver)并用 thread_id 标识会话,才看得到快照——这是做断点续跑和持久化会话的基础。

多个模式可以一起要,传列表即可,遍历时按 key 分发:

for mode, chunk in agent.stream(inputs, stream_mode=["updates", "messages"]):
    print(mode, chunk)

26.5 实战:多功能智能助手

把一章的零件装成一个东西:天气、数学、时间、货币、搜索五个能力,一个 Agent 调度。

import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
import math
from datetime import datetime, timedelta

load_dotenv(override=True)
model = init_chat_model(
    model="gpt-5.4-mini",
    model_provider="openai",
    api_key=os.getenv("CLOSEAI_API_KEY"),
    base_url=os.getenv("CLOSEAI_BASE_URL"),
)

@tool
def get_weather(city: str) -> str:
    """获取指定城市的实时天气信息
    Args:
        city: 城市名称,如 "北京"、"上海"、"深圳" 等
    Returns:
        包含温度、天气状况、空气质量的详细信息
    """
    return f"{city}:多云,15-22℃,空气质量良"

其余四个工具同理:calculate(数学运算,注意用 math 而不是 eval)、get_current_time(当前时间与日期计算,用 timedelta)、convert_currency(汇率换算)、search_info(信息搜索)。然后创建 Agent 并跑主程序:

from langchain.agents import create_agent

assistant = create_agent(
    model=model,
    tools=[get_weather, calculate, get_current_time, convert_currency, search_info],
    system_prompt="""你是一个多功能智能助手,可以查询天气、做数学计算、查询时间、换算货币、搜索信息。
需要精确数据时必须调用工具,不要凭记忆回答。""",
)

for chunk in assistant.stream(
    {"messages": [{"role": "user", "content": "北京天气怎么样?顺便算一下 128 * 37"}]},
    stream_mode="updates",
):
    rprint(chunk)

五个工具正好落在这个上限里(2–5 个)。真要继续加,就该考虑拆分 Agent,而不是往一个 Agent 里塞——工具一多,选错率会明显上升,这一点和工具那一章的结论完全一致。

26.6 小结

ToolStrategy 的三个参数各有分工:schema 定结构(支持 Pydantic、TypedDict、JSON Schema、dataclass 和 Union)、tool_message_content 优化对话历史、handle_errors 控制错误重试。流式输出则按粒度选模式:看过程用 updates、要打字机用 messages、要业务进度用 custom。

到这里,LangChain 1.2 的主线——模型、消息、提示词、工具、结构化输出、智能体——已经走完一遍。下一章进入中间件与上下文:为什么 Agent 需要「在工具调用前后插一脚」,以及提示词、状态、记忆怎么随会话动态变化。

上一篇:Agent 名称、系统提示词与结构化输出;系列第一篇:为什么需要 LangChain。

本文根据公开课程课件整理,并结合 LangChain 官方文档核对与补充。

本文来自 https://aimock.cn/blog,站内重新编排。
内容仅供学习交流,转载请注明出处。