LangChain能力详解!:从工具调用到 LangSmith——让聊天模型具备实时交互能力!

目录

一、工具调用

[二、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 应用来说,可观测性是非常重要的工程能力。

六、总结

本文承接上一篇的工具绑定内容,继续学习了聊天模型的完整进阶流程。

  1. tool_calls:模型返回工具调用请求;
  2. tool_choice:控制模型是否必须调用工具;
  3. ToolMessage:将工具执行结果返回给模型;
  4. TavilySearch:让模型获得实时搜索能力;
  5. 结构化输出:让模型返回 JSON 或 Pydantic 对象;
  6. stream()astream():实现流式回答;
  7. LangSmith:跟踪和调试复杂 LLM 调用链路。

下一篇将进入 LangChain 核心组件,重点学习消息、提示词模板和 Few-Shot 少样本提示。

相关推荐
richard_yuu1 小时前
AOI 实战第五篇:c_broken 漏检严重,V4 迭代背后的权衡
开发语言·深度学习·yolo
m0_734571761 小时前
深入理解C++ 析构函数<一>基本概念
开发语言·c++
滕州市燕猫虎计算机科技工作室个体工商户1 小时前
Java面试题汇总
java·开发语言
孙启超2 小时前
【AI开发之Rust】第 3 课:字符串与复合类型 —— 数据怎么放
开发语言·人工智能·后端·rust·llm·transformer
t-think2 小时前
C++ string 类(上)—— string类初识与常用接口详解
开发语言·c++
cpp_learners2 小时前
C++ 实现责任链模式(Chain of Responsibility):从一堆 if-else 到可插拔的处理管道
开发语言·c++·责任链模式
2601_962298273 小时前
Python自动化脚本的编写01
python·appium·自动化脚本·web自动化·应收账款管理
君顾13 小时前
外卖CPS软件开发实战:从系统架构到部署全流程指南
java·开发语言·外卖