目录
[二、2.4 强制模型调用工具](#二、2.4 强制模型调用工具)
[三、2.5 工具调用属性](#三、2.5 工具调用属性)
[四、2.6 将工具执行结果传回模型](#四、2.6 将工具执行结果传回模型)
[五、2.7 LangChain 提供的工具](#五、2.7 LangChain 提供的工具)
[六、使用 TavilySearch 查询实时信息](#六、使用 TavilySearch 查询实时信息)
[1. 安装依赖](#1. 安装依赖)
[2. 创建并绑定搜索工具](#2. 创建并绑定搜索工具)
[3. 完整搜索调用代码](#3. 完整搜索调用代码)
[1. 使用 Pydantic 定义输出结构](#1. 使用 Pydantic 定义输出结构)
[2. 结构化输出适合哪些场景](#2. 结构化输出适合哪些场景)
[3. 工具调用与结构化输出的关系](#3. 工具调用与结构化输出的关系)
[1. 同步流式输出:stream()](#1. 同步流式输出:stream())
[2. 结合 StrOutputParser](#2. 结合 StrOutputParser)
[3. 异步流式输出:astream()](#3. 异步流式输出:astream())
[九、使用 LangSmith 跟踪 LLM 应用](#九、使用 LangSmith 跟踪 LLM 应用)
[1. 配置 LangSmith](#1. 配置 LangSmith)
[2. LangSmith 的实际作用](#2. LangSmith 的实际作用)
上一篇我们已经学习了如何创建工具,以及如何通过 bind_tools() 将工具绑定到聊天模型。
本篇将继续完成工具调用的完整闭环,并进一步学习 LangChain 聊天模型的结构化输出、流式传输和 LangSmith 跟踪能力。
一、工具的使用
1. 工具调用
在上一篇中,我们已经通过 bind_tools() 将工具绑定到了聊天模型。
例如,定义两个简单的计算工具:
python
from langchain_core.tools import tool
from typing_extensions import Annotated
@tool
def add(
a: Annotated[int, ..., "第一个整数"],
b: Annotated[int, ..., "第二个整数"]
) -> int:
"""计算两个整数的和。"""
return a + b
@tool
def multiply(
a: Annotated[int, ..., "第一个整数"],
b: Annotated[int, ..., "第二个整数"]
) -> int:
"""计算两个整数的乘积。"""
return a * b
绑定工具:
python
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o-mini")
tools = [add, multiply]
model_with_tools = model.bind_tools(tools)
此时的 model_with_tools 依然是一个 Runnable 对象,因此可以使用 invoke() 方法调用。
python
result = model_with_tools.invoke("9 乘 6 等于多少?")
print(result)
模型返回的通常是一个 AIMessage,但它的 content 可能为空。
原因是模型此时并没有直接回答"54",而是在告诉程序:
python
我判断这个问题应该调用 multiply 工具。
请使用参数 a=9、b=6 执行它。
也就是说,模型返回的是"工具调用请求"。
可以通过下面的方式查看:
python
print(result.tool_calls)
输出结构大致如下:
python
[
{
"name": "multiply",
"args": {
"a": 9,
"b": 6
},
"id": "call_xxx",
"type": "tool_call"
}
]
| 字段 | 含义 |
|---|---|
name |
模型希望调用的工具名称 |
args |
调用工具所需的参数 |
id |
本次工具调用的唯一标识 |
type |
工具调用类型,通常为 tool_call |
这里需要再次强调:
模型只负责决定调用什么工具、传入什么参数。
工具本身不会自动执行,真正执行工具的是我们编写的 Python 程序。
2. 强制模型调用工具
默认情况下,模型会自行判断是否需要调用工具。
python
result = model_with_tools.invoke("你好")
print(result.content)
print(result.tool_calls)
因为"你好"不需要加法或乘法,模型通常会直接输出普通回答,而不会调用工具。
但有些业务场景中,我们希望模型必须调用某个工具。
例如:
- 必须先查询数据库再回答;
- 必须先搜索网页再给出新闻结论;
- 必须执行权限校验工具;
- 必须调用订单系统确认订单状态。
这时可以在绑定工具时使用
tool_choice。
python
model_with_tools = model.bind_tools(
tools,
tool_choice="any"
)
tool_choice="any" 表示:
python
至少调用一个工具。
再次调用:
result = model_with_tools.invoke("你好")
print(result.tool_calls)
即使用户只是简单问候,模型也会尝试选择一个工具。
常见的 tool_choice 配置如下:
| 配置 | 含义 |
|---|---|
"auto" |
由模型自行判断是否调用工具 |
"none" |
不允许模型调用工具 |
"any" 或 "required" |
强制至少调用一个工具 |
"工具名称" |
强制调用指定工具 |
None |
使用模型默认行为 |
例如强制调用 multiply:
python
model_with_tools = model.bind_tools(
tools,
tool_choice="multiply"
)
实际开发中,是否强制调用工具,需要根据业务决定。
如果问题本身必须依赖实时数据,例如天气、库存、订单状态,则通常应该强制使用工具或在 Prompt 中明确约束。
3. 工具调用属性
模型返回的 AIMessage 中,有一个很重要的属性:
result.tool_calls
例如:
python
result = model_with_tools.invoke("9 乘 6 等于多少?")
print(result.tool_calls)
输出:
python
[
{
"name": "multiply",
"args": {
"a": 9,
"b": 6
},
"id": "call_xxx",
"type": "tool_call"
}
]
如果用户一次提出多个计算问题:
python
result = model_with_tools.invoke(
"9 乘 6 等于多少?5 加 3 等于多少?"
)
print(result.tool_calls)
模型可能会一次返回多个工具调用请求:
python
[
{
"name": "multiply",
"args": {
"a": 9,
"b": 6
},
"id": "call_001",
"type": "tool_call"
},
{
"name": "add",
"args": {
"a": 5,
"b": 3
},
"id": "call_002",
"type": "tool_call"
}
]
因此,我们在实际开发中通常需要遍历 tool_calls:
python
for tool_call in result.tool_calls:
print(tool_call["name"])
print(tool_call["args"])
对于支持并行工具调用的模型,多个工具请求可以同时返回。
如果不希望并行调用,可以配置:
python
model_with_tools = model.bind_tools(
tools,
parallel_tool_calls=False
)
4.将工具执行结果传回模型
模型知道要调用工具后,我们需要完成后续三步:
python
执行工具
↓
获得工具结果
↓
将工具结果作为 ToolMessage 发送回模型
完整代码如下:
python
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from typing_extensions import Annotated
model = ChatOpenAI(model="gpt-4o-mini")
@tool
def add(
a: Annotated[int, ..., "第一个整数"],
b: Annotated[int, ..., "第二个整数"]
) -> int:
"""计算两个整数的和。"""
return a + b
@tool
def multiply(
a: Annotated[int, ..., "第一个整数"],
b: Annotated[int, ..., "第二个整数"]
) -> int:
"""计算两个整数的乘积。"""
return a * b
tools = [add, multiply]
model_with_tools = model.bind_tools(tools)
messages = [
HumanMessage(content="9 乘 6 等于多少?5 加 3 等于多少?")
]
ai_message = model_with_tools.invoke(messages)
messages.append(ai_message)
tool_map = {
"add": add,
"multiply": multiply
}
for tool_call in ai_message.tool_calls:
tool_name = tool_call["name"].lower()
selected_tool = tool_map[tool_name]
tool_message = selected_tool.invoke(tool_call)
messages.append(tool_message)
result = model.invoke(messages)
print(result.content)
最终输出:
9 乘 6 等于 54,5 加 3 等于 8。
整个过程可以表示为:
python
HumanMessage
用户:9 乘 6 等于多少?5 加 3 等于多少?
↓
AIMessage
模型:请调用 multiply(a=9, b=6) 和 add(a=5, b=3)
↓
ToolMessage
multiply 工具执行结果:54
add 工具执行结果:8
↓
AIMessage
模型:9 乘 6 等于 54,5 加 3 等于 8。
为什么必须把工具结果包装成 ToolMessage?因为聊天模型接收的是消息列表。工具执行结果并不是普通用户消息,也不是模型回答,它应该以 tool 角色发送给模型,告诉模型:这是你刚刚请求调用的工具返回的数据。
5. LangChain 提供的工具
除了自己使用 @tool 定义工具外,LangChain 也已经集成了很多现成工具与工具包。
常见能力包括:
- 搜索引擎;
- 网页读取;
- SQL 数据库;
- Redis;
- GitHub;
- 邮件;
- 文件系统;
- 浏览器;
- Python 代码执行;
- 第三方 API;
- 向量数据库。
可以理解为:
python
自定义工具:适合调用自己公司的服务、数据库、内部接口。
LangChain 内置工具:适合快速接入成熟的第三方服务。
工具通常用于解决模型无法独立完成的问题,例如:
| 用户需求 | 适合的工具 |
|---|---|
| 今天北京天气如何 | 搜索工具、天气 API |
| 查询订单状态 | 订单系统 API |
| 查询员工信息 | 数据库工具 |
| 搜索最新技术新闻 | Web 搜索工具 |
| 查询知识库资料 | Retriever 检索器 |
| 计算复杂表达式 | Python 或计算器工具 |
二、使用TavilySearch查询实时信息
Tavily 是一个面向 AI Agent 场景设计的搜索服务,适合实时新闻、天气、百科、资料检索等需求。
1. 安装依赖
python
pip install -U langchain-tavily
配置环境变量:
TAVILY_API_KEY=你的Tavily_API_Key
2. 创建并绑定搜索工具
python
from langchain_openai import ChatOpenAI
from langchain_tavily import TavilySearch
model = ChatOpenAI(model="gpt-4o-mini")
search_tool = TavilySearch(max_results=3)
model_with_tools = model.bind_tools([search_tool])
其中:
max_results=3
表示最多返回三条搜索结果。
3. 完整搜索调用代码
python
from langchain_core.messages import HumanMessage
messages = [
HumanMessage(content="请查询今天西安的天气情况")
]
ai_message = model_with_tools.invoke(messages)
messages.append(ai_message)
for tool_call in ai_message.tool_calls:
tool_message = search_tool.invoke(tool_call)
messages.append(tool_message)
result = model.invoke(messages)
print(result.content)
这个例子说明了工具调用最常见的应用场景:
python
模型负责分析问题
↓
判断需要搜索实时信息
↓
搜索工具负责查询
↓
模型根据搜索结果组织回答
相比让模型直接"猜天气",这种方式明显更可靠。
三、聊天模型的结构化输出
工具调用解决的是"如何拿到外部数据"。
结构化输出解决的是"如何让模型返回程序需要的数据格式"。
例如,普通模型可能返回:
西安今天多云,气温 18 到 25 摄氏度,东南风 2 级。
如果我们想将结果写入数据库,通常希望得到:
python
{
"city": "西安",
"weather": "多云",
"low_temperature": 18,
"high_temperature": 25,
"wind": "东南风 2 级"
}
1. 使用 Pydantic 定义输出结构
python
from pydantic import BaseModel, Field
from langchain_openai import ChatOpenAI
class WeatherInfo(BaseModel):
city: str = Field(description="城市名称")
weather: str = Field(description="天气情况")
low_temperature: int = Field(description="最低温度")
high_temperature: int = Field(description="最高温度")
wind: str = Field(description="风力信息")
model = ChatOpenAI(model="gpt-4o-mini")
structured_model = model.with_structured_output(WeatherInfo)
result = structured_model.invoke(
"请生成西安今天的天气信息"
)
print(result)
输出示例:
python
city='西安' weather='多云' low_temperature=18 high_temperature=25 wind='东南风 2 级'
此时 result 是一个 WeatherInfo 对象,而不是普通字符串。
python
print(result.city)
print(result.weather)
2. 结构化输出适合哪些场景
结构化输出常用于:
- 简历信息提取;
- 订单字段提取;
- 新闻内容分类;
- 文本实体提取;
- JSON API 返回;
- 数据库写入;
- 表单自动填充;
- 模型结果二次处理。
3. 工具调用与结构化输出的关系
两者可以组合使用:
python
工具调用:获取真实、实时、外部数据。
结构化输出:将最终结果转换为稳定对象。
先搜索西安天气
↓
再让模型整理为 WeatherInfo 对象
在复杂场景中,往往需要多次模型调用和状态管理。后续学习 LangGraph 时,会更方便地处理这种工作流。
四、聊天模型的流式传输
普通调用使用 invoke():
python
result = model.invoke("写一篇 1000 字的人工智能介绍")
print(result.content)
只有模型全部生成完成后,用户才能看到结果。如果模型生成时间较长,用户体验并不好。流式输出可以让模型边生成、边返回内容。
1. 同步流式输出:stream()
python
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o-mini")
for chunk in model.stream("请用三句话介绍 LangChain"):
print(chunk.content, end="", flush=True)
stream() 返回的是一个迭代器。其中每一个 chunk 通常是 AIMessageChunk,表示模型回答的一小部分内容。
2. 结合 StrOutputParser
如果只想获得纯文本片段,可以接入 StrOutputParser。
python
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
chain = model | parser
for token in chain.stream("介绍一下 RAG 的作用"):
print(token, end="", flush=True)
这样可以避免手动读取:
chunk.content
3. 异步流式输出:astream()
异步方式更适合 Web 服务场景。
python
import asyncio
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o-mini")
async def async_stream():
async for chunk in model.astream("介绍一下向量数据库"):
print(chunk.content, end="", flush=True)
asyncio.run(async_stream())
在 FastAPI、WebSocket、SSE 等场景中,通常优先使用异步流式输出。
五、使用 LangSmith 跟踪 LLM 应用
随着工具调用、RAG、Prompt、结构化输出等能力不断组合,LLM 应用会变得越来越复杂。
例如:
python
用户问题
↓
模型判断是否调用工具
↓
调用搜索工具
↓
获得搜索结果
↓
再次调用模型
↓
结构化输出
如果最终答案不正确,只使用 print() 很难判断问题出现在哪一步。LangSmith 可以帮助我们记录整个调用链路。
它可以查看:
- 模型输入;
- 模型输出;
- Prompt 内容;
- 工具调用参数;
- 工具执行结果;
- Token 使用量;
- 每一步耗时;
- 解析错误;
- 调用异常。
1. 配置 LangSmith
LANGSMITH_TRACING=true
LANGSMITH_API_KEY=你的LangSmith_API_Key
配置后,正常运行 LangChain 代码即可。
2. LangSmith 的实际作用
例如,我们可以通过 LangSmith 排查:
模型为什么没有调用工具?
模型传入工具的参数为什么不正确?
RAG 检索到了哪些文档?
哪一步调用最耗时?
哪个 Prompt 消耗 Token 最多?
结构化输出为什么解析失败?
对于复杂的 AI 应用来说,可观测性是非常重要的工程能力。
六、总结
本文承接上一篇的工具绑定内容,继续学习了聊天模型的完整进阶流程。
tool_calls:模型返回工具调用请求;tool_choice:控制模型是否必须调用工具;ToolMessage:将工具执行结果返回给模型;- TavilySearch:让模型获得实时搜索能力;
- 结构化输出:让模型返回 JSON 或 Pydantic 对象;
stream()与astream():实现流式回答;- LangSmith:跟踪和调试复杂 LLM 调用链路。
下一篇将进入 LangChain 核心组件,重点学习消息、提示词模板和 Few-Shot 少样本提示。