LangChain Chat Model 高级用法
前面我们已经学习了如何通过 init_chat_model() 初始化模型,以及如何使用 invoke() 调用模型。
本章节介绍 LangChain Chat Model 的两个重要功能:
- bind_tools():将工具定义绑定到模型,使模型能够根据任务生成工具调用请求。
- with_structured_output():按照指定的数据结构(Schema)解析模型输出,方便程序直接使用。
这两个功能经常用于 Agent、数据提取、自动化任务和 AI 应用开发。
需要注意,bind_tools() 与 with_structured_output() 的用途不同,但底层实现可能都涉及模型的工具调用能力。
一、安装与环境准备
本教程使用 LangChain 1.x,并以 DeepSeek 官方推荐的 deepseek-flash 模型为主要示例。
首先安装依赖:
pip install -U langchain langchain-deepseek pydantic python-dotenv
在项目目录创建 .env 文件:
DEEPSEEK_API_KEY=your_deepseek_api_key
然后初始化模型:
实例
from langchain.chat_models import init_chat_model
load_dotenv()
model = init_chat_model(
"deepseek:deepseek-flash"
)
response = model.invoke("你好,LangChain!")
print(response.content)
以下示例默认已经完成上述初始化,除非示例中重新创建模型。
需要注意,工具调用和结构化输出功能是否可用,以及具体支持的参数,取决于所使用的模型、API 和 LangChain 集成版本。
二、bind_tools():为模型绑定工具
bind_tools() 用于向聊天模型提供工具定义,让模型知道当前有哪些工具可以使用,以及工具需要哪些参数。
例如,一个天气查询工具可以接收城市名称:
get_weather(city="杭州")
当用户询问天气时,模型可以生成一个工具调用请求,指定工具名称和参数。
注意:bind_tools() 不负责真正执行 Python 函数。
它只是向模型提供工具定义,并允许模型在响应中返回 tool_calls。
bind_tools() 基本语法
model_with_tools = model.bind_tools(tools)
其中,tools 是工具定义列表。
LangChain 支持多种工具定义方式,包括:
- 使用
@tool装饰器定义的 LangChain 工具。 - 普通 Python 函数(由集成转换其定义)。
- Pydantic 模型类。
- 符合要求的 OpenAI Tool Calling 格式字典。
实例 1:使用字典定义工具
下面通过 OpenAI Function Calling 风格的字典描述一个天气查询工具。
实例
from langchain.chat_models import init_chat_model
load_dotenv()
model = init_chat_model(
"deepseek:deepseek-flash"
)
# 描述一个天气查询工具
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查询指定城市的模拟天气",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如杭州、北京"
}
},
"required": ["city"]
}
}
}
]
# 将工具定义绑定到模型
model_with_tools = model.bind_tools(tools)
# 请求模型使用工具
response = model_with_tools.invoke(
"请使用 get_weather 查询杭州的模拟天气。"
)
# 查看工具调用
if response.tool_calls:
for tool_call in response.tool_calls:
print("工具名称:", tool_call["name"])
print("工具参数:", tool_call["args"])
print("调用 ID:", tool_call["id"])
else:
print("模型直接回答:", response.content)
如果模型选择调用工具,可能输出类似:
工具名称: get_weather
工具参数: {'city': '杭州'}
调用 ID: call_xxxxx
其中,调用 ID 由模型服务生成,不同请求的值通常不同。
还需要注意,即使已经绑定工具,模型也不保证每次都会调用工具。是否调用由模型和相关配置共同决定。
tool_calls 返回结构
LangChain 会将模型返回的工具调用请求标准化到 AIMessage.tool_calls 中。
典型结构如下:
[
{
"name": "get_weather",
"args": {
"city": "杭州"
},
"id": "call_xxxxx",
"type": "tool_call"
}
]
| 字段 | 说明 |
|---|---|
name |
工具名称 |
args |
模型生成的工具参数字典 |
id |
工具调用标识,用于关联工具返回结果 |
type |
调用内容类型 |
如果模型返回了格式不正确的工具调用,还可能出现 invalid_tool_calls。因此,在实际开发中不能假设每一次工具调用都有效。
三、使用 @tool 定义工具
如果工具需要真正执行 Python 代码,通常推荐使用 @tool 装饰器定义工具。
例如:
实例
@tool
def get_weather(city: str) -> str:
"""查询指定城市的模拟天气。
Args:
city: 城市名称,如杭州、北京。
"""
weather_data = {
"杭州": "晴,25°C",
"北京": "多云,18°C",
"上海": "小雨,22°C",
}
return weather_data.get(
city,
f"没有找到{city}的模拟天气"
)
# 绑定工具
model_with_tools = model.bind_tools([
get_weather
])
response = model_with_tools.invoke(
"查询杭州的模拟天气。"
)
print(response.tool_calls)
使用 @tool 时,LangChain 会利用函数名称、参数类型与文档字符串构建工具定义。
在这个示例中:
- 工具名称为
get_weather。 - 工具需要一个字符串参数
city。 - 文档字符串向模型解释工具用途。
模型可以根据这些信息生成工具调用请求。
但是,此时天气查询函数仍然没有被自动执行。
四、手动执行工具:完整 Tool Calling 流程
仅使用 bind_tools(),模型会告诉程序需要调用哪个工具,但不会自动执行。
如果不使用 Agent,就需要自己编写工具调度代码。
基本流程如下:
用户提问 ↓ 模型生成工具调用请求 ↓ 程序检查 tool_calls ↓ 调用对应 Python 工具 ↓ 将结果封装为 ToolMessage ↓ 再次调用模型 ↓ 生成最终回答
完整实例
下面展示如何手动完成一次工具调用闭环。
实例
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain.messages import (
HumanMessage,
ToolMessage
)
load_dotenv()
# 1. 定义工具
@tool
def get_weather(city: str) -> str:
"""查询指定城市的模拟天气。
Args:
city: 城市名称。
"""
data = {
"杭州": "晴,25°C",
"北京": "多云,18°C"
}
return data.get(city, "没有该城市的模拟数据")
# 2. 初始化模型并绑定工具
model = init_chat_model(
"deepseek:deepseek-flash"
)
model_with_tools = model.bind_tools([
get_weather
])
# 3. 创建消息列表
messages = [
HumanMessage(
content="请调用工具查询杭州的模拟天气。"
)
]
# 4. 第一次调用模型
response = model_with_tools.invoke(messages)
# 保留模型消息,包括工具调用请求
messages.append(response)
# 5. 手动执行模型请求的工具
tool_map = {
"get_weather": get_weather
}
for tool_call in response.tool_calls:
tool_name = tool_call["name"]
if tool_name not in tool_map:
tool_result = f"未知工具:{tool_name}"
else:
try:
# 执行对应工具
tool_result = tool_map[tool_name].invoke(
tool_call["args"]
)
except Exception as error:
tool_result = (
f"工具执行失败:{type(error).__name__}"
)
# 将结果与调用 ID 关联
messages.append(
ToolMessage(
content=str(tool_result),
tool_call_id=tool_call["id"]
)
)
# 6. 将工具结果传回模型
if response.tool_calls:
final_response = model_with_tools.invoke(
messages
)
print("最终回答:")
print(final_response.content)
else:
print("模型直接回答:")
print(response.content)
如果第一次模型调用产生了 get_weather 工具调用,程序会执行对应的 Python 函数。
工具返回模拟天气后,通过 ToolMessage 将结果传回模型。
模型就可以利用这个结果组织最终回答。
例如:
杭州的模拟天气为晴天,气温25°C。
注意:这个例子演示一次工具执行与一次后续模型调用。如果第二次模型响应仍然包含工具调用请求,还需要继续循环处理。
如果需要完整的多轮工具调用、状态管理和执行控制,建议使用上一节介绍的 create_agent(),而不是自己实现复杂的调度逻辑。
另外,工具参数由模型生成,执行数据库修改、文件操作或网络请求等敏感工具前,还应进行参数校验和权限检查。
五、使用 Pydantic 模型描述工具参数
对于参数较多的工具,可以使用 Pydantic 定义参数结构。
Pydantic 可以提供类型声明、字段描述和数据校验,减少手写 JSON Schema 的工作量。
实例
实例
from langchain.chat_models import init_chat_model
class GetWeather(BaseModel):
"""查询指定城市的模拟天气。"""
city: str = Field(
description="城市名称,例如杭州、北京"
)
unit: str = Field(
default="celsius",
description="温度单位:celsius 或 fahrenheit"
)
class Calculate(BaseModel):
"""计算简单的数学表达式。"""
expression: str = Field(
description="数学表达式,例如 123 * 456"
)
model = init_chat_model(
"deepseek:deepseek-flash"
)
model_with_tools = model.bind_tools([
GetWeather,
Calculate
])
response = model_with_tools.invoke(
"查询北京的模拟天气,并计算123乘以456。"
)
for tool_call in response.tool_calls:
print("工具:", tool_call["name"])
print("参数:", tool_call["args"])
如果模型选择调用两个工具,可能返回:
工具: GetWeather
参数: {'city': '北京', 'unit': 'celsius'}
工具: Calculate
参数: {'expression': '123 * 456'}
这里有一个重要区别:工具名称默认来自 Pydantic 类名。
因此:
GetWeather对应工具名称GetWeather。Calculate对应工具名称Calculate。
如果希望工具名称为 get_weather 或 calculate,可以改用对应名称的 @tool 函数。
注意:这里的 Pydantic 类用于描述工具输入结构,不包含天气查询或数学计算的具体执行逻辑。模型返回工具调用请求后,仍需要应用程序提供对应的工具实现。
Pydantic 类的字段描述可以帮助模型生成符合要求的参数,但模型生成的参数并不一定始终有效。实际执行前仍需要进行校验。
六、with_structured_output():结构化输出
with_structured_output() 用于将聊天模型的输出转换为指定的数据结构。
例如,普通模型可能返回以下文字:
张三今年28岁,是一名全栈工程师, 掌握 Python、React 和 Docker。
如果程序需要提取姓名、年龄、职业和技能,就可以通过结构化输出得到:
{
"name": "张三",
"age": 28,
"occupation": "全栈工程师",
"skills": ["Python", "React", "Docker"]
}
这样就不必自己从自然语言中解析字段。
基本语法
structured_model = model.with_structured_output(
schema
)
result = structured_model.invoke(text)
其中,schema 用于定义期望的输出结构。
常用的结构定义方式包括:
- Pydantic 模型。
- JSON Schema 字典。
- TypedDict 类型。
注意:with_structured_output() 并不保证底层一定使用独立的原生结构化输出 API。部分模型集成会使用 Tool Calling 或 JSON Mode 实现。
实例:通过 Pydantic 提取人物信息
实例
from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model
load_dotenv()
# 定义结构化输出格式
class PersonInfo(BaseModel):
"""从文本中提取人物信息。"""
name: str = Field(
description="人物姓名"
)
age: int = Field(
description="年龄"
)
occupation: str = Field(
description="职业"
)
skills: list[str] = Field(
description="技能列表"
)
# 初始化模型
model = init_chat_model(
"deepseek:deepseek-flash"
)
# 使用 Pydantic Schema
structured_model = model.with_structured_output(
PersonInfo
)
text = (
"张三今年28岁,是一名全栈工程师,"
"精通 Python、React 和 Docker。"
)
result = structured_model.invoke(
"请根据文本提取人物信息,不要编造:\n" + text
)
print("姓名:", result.name)
print("年龄:", result.age)
print("职业:", result.occupation)
print("技能:", result.skills)
print("返回类型:", type(result))
如果模型正确提取信息,输出可能为:
姓名: 张三 年龄: 28 职业: 全栈工程师 技能: ['Python', 'React', 'Docker'] 返回类型: <class '__main__.PersonInfo'>
使用 Pydantic Schema 且解析成功时,返回值通常是对应的 Pydantic 模型实例。
因此,可以通过:
result.name result.age result.skills
直接读取字段。
还可以转换为字典:
data = result.model_dump() print(data)
Pydantic 会检查返回字段是否满足相应类型和验证规则。
但需要注意,类型校验不等于事实校验。即使返回结构合法,也需要根据业务需求检查模型提取的信息是否准确。
七、bind_tools() 与 with_structured_output() 的区别
这两个方法都可以利用模型的结构化响应能力,但用途不同。
| 对比项目 | bind_tools() | with_structured_output() |
|---|---|---|
| 主要用途 | 让模型选择并请求调用工具 | 让模型输出符合指定结构的数据 |
| 典型返回值 | AIMessage | Pydantic 实例或 dict |
| 工具执行 | 需要程序或 Agent 执行 | 主要关注数据生成与解析,不负责执行外部业务工具 |
| 典型场景 | Agent、搜索、天气查询、数据库操作 | 信息提取、分类、JSON 数据生成 |
| 底层机制 | 依赖模型的工具调用支持 | 可能使用 Tool Calling、JSON Mode 或原生结构化输出 |
可以这样理解:
- bind_tools():让模型告诉程序下一步需要执行什么操作。
- with_structured_output():让模型按照程序需要的数据格式返回内容。
例如:
用户:查询杭州天气
bind_tools()
↓
模型请求调用 get_weather
↓
程序执行工具
↓
获得天气数据
而结构化输出更像:
输入:张三今年28岁,职业是程序员
with_structured_output()
↓
{
"name": "张三",
"age": 28,
"occupation": "程序员"
}
这两种能力可以在更复杂的 Agent 工作流中组合使用,但不能简单理解为完全相同的操作。
八、嵌套结构化输出
with_structured_output() 不仅可以提取简单字段,还可以描述嵌套对象和列表。
例如,可以从一段菜谱文字中提取:
- 菜名。
- 制作难度。
- 食材名称与用量。
- 烹饪步骤和预计时间。
实例:提取菜谱信息
实例
from langchain.chat_models import init_chat_model
class Ingredient(BaseModel):
"""食材信息。"""
name: str = Field(
description="食材名称"
)
amount: str = Field(
description="食材用量"
)
class CookingStep(BaseModel):
"""烹饪步骤。"""
step_number: int = Field(
description="步骤编号"
)
description: str = Field(
description="步骤说明"
)
duration_minutes: int = Field(
description="步骤所需分钟数"
)
class Recipe(BaseModel):
"""菜谱信息。"""
dish_name: str = Field(
description="菜名"
)
difficulty: str = Field(
description="难度"
)
ingredients: list[Ingredient] = Field(
description="食材列表"
)
steps: list[CookingStep] = Field(
description="烹饪步骤"
)
model = init_chat_model(
"deepseek:deepseek-flash"
)
structured_model = model.with_structured_output(
Recipe
)
recipe_text = """
今天介绍番茄炒蛋。
食材:
番茄2个、鸡蛋3个、葱花少许、
盐适量、糖少许。
制作步骤:
1. 番茄切块,鸡蛋打散,约5分钟。
2. 将鸡蛋炒熟盛出,约3分钟。
3. 炒番茄至出汁,加盐和糖,约5分钟。
4. 加入鸡蛋翻炒,撒上葱花,约2分钟。
"""
result = structured_model.invoke(
"请将下面的菜谱整理为结构化数据。\n"
"难度可根据描述进行合理判断,"
"其他字段不要编造。\n\n"
+ recipe_text
)
print("菜名:", result.dish_name)
print("难度:", result.difficulty)
print("\n食材:")
for ingredient in result.ingredients:
print(
ingredient.name,
ingredient.amount
)
print("\n步骤:")
for step in result.steps:
print(
step.step_number,
step.description,
step.duration_minutes
)
如果解析成功,可以得到类似结果:
菜名: 番茄炒蛋 难度: 简单 食材: 番茄 2个 鸡蛋 3个 葱花 少许 盐 适量 糖 少许 步骤: 1 番茄切块,鸡蛋打散 5 2 将鸡蛋炒熟盛出 3 3 炒番茄至出汁,加盐和糖 5 4 加入鸡蛋翻炒,撒上葱花 2
这里的 Recipe 包含两个嵌套对象列表:
ingredients: list[Ingredient] steps: list[CookingStep]
模型可以根据 Schema 构建层级化的数据结构。
不过,嵌套结构越复杂,越需要关注模型的结构化输出能力、字段完整性和解析失败情况。
九、JSON Schema 结构化输出
如果不希望使用 Pydantic 类,也可以直接通过 JSON Schema 字典描述输出结构。
这对于需要与其他语言、前端应用或外部接口共享数据格式的项目非常有用。
实例:情感分析
实例
model = init_chat_model(
"deepseek:deepseek-flash"
)
# 定义 JSON Schema
json_schema = {
"title": "SentimentAnalysis",
"description": "文本情感分析结果",
"type": "object",
"properties": {
"sentiment": {
"type": "string",
"enum": [
"positive",
"negative",
"neutral"
],
"description": "情感倾向"
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "模型给出的置信度估计"
},
"keywords": {
"type": "array",
"items": {
"type": "string"
},
"description": "关键情感词"
}
},
"required": [
"sentiment",
"confidence",
"keywords"
]
}
structured_model = model.with_structured_output(
json_schema
)
text = (
"菜鸟教程 RUNOOB 真的太棒了,"
"强烈推荐给编程新手!"
)
result = structured_model.invoke(
"请分析下面文本的情感,并返回结构化数据:\n"
+ text
)
print("情感:", result["sentiment"])
print("置信度:", result["confidence"])
print("关键词:", result["keywords"])
如果模型按照要求输出,可以得到类似:
情感: positive 置信度: 0.95 关键词: ['太棒了', '强烈推荐']
这里的置信度只是模型生成的估计值,不代表经过统计校准的真实概率。
与 Pydantic 模式不同,传入 JSON Schema 字典时,LangChain 通常返回普通的 dict。
例如:
result["sentiment"] result["confidence"]
注意:使用 JSON Schema 并不自动保证模型输出完全符合所有规则,也不意味着 LangChain 一定会执行完整的 JSON Schema 验证。
如果业务需要严格验证数据类型、范围和必填字段,建议使用 Pydantic 或单独执行 Schema 校验。
十、include_raw:同时保留原始响应
默认情况下,with_structured_output() 会直接返回解析后的结果。
如果还需要查看原始模型响应、Token 使用情况或解析错误,可以设置:
include_raw=True
实例
实例
from langchain.chat_models import init_chat_model
class PersonInfo(BaseModel):
"""人物信息。"""
name: str = Field(
description="姓名"
)
age: int = Field(
description="年龄"
)
model = init_chat_model(
"deepseek:deepseek-flash"
)
structured_model = model.with_structured_output(
PersonInfo,
include_raw=True
)
result = structured_model.invoke(
"提取人物信息:张三今年28岁。"
)
print("解析结果:", result["parsed"])
print("解析错误:", result["parsing_error"])
print("原始响应:", result["raw"])
设置 include_raw=True 后,返回结果通常包含以下三个字段:
| 字段 | 含义 |
|---|---|
raw |
模型的原始消息响应 |
parsed |
解析完成的结构化结果 |
parsing_error |
解析阶段发生的错误,没有错误时通常为 None |
如果解析成功,parsed 会包含对应的 Pydantic 实例。
如果解析失败,parsing_error 可以帮助定位问题。
注意:include_raw=True 主要帮助观察解析过程,不代表会捕获所有网络错误、认证错误或模型服务异常。
十一、结构化输出的实现方式
LangChain 的 with_structured_output() 是一个统一接口,实际使用的底层机制由模型集成决定。
常见方式包括:
| 方式 | 说明 |
|---|---|
function_calling |
通过工具或函数调用机制产生符合指定结构的参数 |
json_mode |
使用模型服务的 JSON 输出模式 |
json_schema |
使用支持原生 JSON Schema 约束的模型接口 |
并不是每个提供商都支持这三种方式。
另外,不同 LangChain 集成对 method 参数的默认值和实现方式也可能不同。
DeepSeek 的 JSON 输出能力
DeepSeek 官方提供 JSON Output 功能,可在对应接口中设置:
{
"response_format": {
"type": "json_object"
}
}
根据 DeepSeek 官方说明,使用 JSON Output 时,还应该在提示词中明确要求输出 JSON,并提供期望的结构示例。
例如:
请将下列人物信息转换为 JSON。
JSON 字段:
{
"name": "姓名",
"age": 0
}
输入:张三今年28岁。
需要注意:JSON Mode 主要保证输出采用合法 JSON 格式,并不等于所有字段都严格符合指定的 Schema。
如果需要字段类型校验,仍然建议在程序中使用 Pydantic 等机制验证。
此外,DeepSeek 官方还提示 JSON Mode 在部分情况下可能出现空输出或内容截断,因此需要合理设置输出上限并做好异常处理。
十二、结构化输出失败时如何处理?
实际调用中,模型可能返回缺少字段、类型错误或不符合格式的数据。
例如,程序要求:
{
"name": "张三",
"age": 28
}
但模型实际返回:
{
"name": "张三",
"age": "未知"
}
此时 age 不满足预期的整数类型。
如果使用 Pydantic 模型,可能在解析验证阶段发生错误。
实例:捕获结构化输出错误
实例
from langchain.chat_models import init_chat_model
class UserInfo(BaseModel):
"""用户信息。"""
name: str = Field(
description="用户姓名"
)
age: int = Field(
ge=0,
le=150,
description="用户年龄"
)
model = init_chat_model(
"deepseek:deepseek-flash"
)
structured_model = model.with_structured_output(
UserInfo,
include_raw=True
)
try:
result = structured_model.invoke(
"提取用户信息:张三今年28岁。"
)
if result["parsing_error"]:
print(
"解析失败:",
result["parsing_error"]
)
else:
user = result["parsed"]
print("姓名:", user.name)
print("年龄:", user.age)
except Exception as error:
print(
"模型请求失败:",
type(error).__name__,
str(error)
)
这个示例包含两层检查:
- 通过
parsing_error检查解析过程。 - 通过异常捕获处理请求执行过程中发生的其他错误。
对于生产环境,建议进一步增加日志、有限次数的重试和结果业务校验。
注意:不要因为 JSON 能够被解析,就直接信任其中所有数据。模型仍可能生成事实不准确的内容。
十三、ConfigurableModel 中使用 bind_tools()
除了固定模型,LangChain 通过 init_chat_model() 创建的可配置模型也支持工具绑定等声明式操作。
这意味着可以先配置模型和工具,再在运行时选择使用哪个模型。
实例:动态切换 DeepSeek Flash 和 Pro
实例
from langchain.tools import tool
@tool
def get_weather(city: str) -> str:
"""查询指定城市的模拟天气。
Args:
city: 城市名称。
"""
data = {
"杭州": "晴,25°C",
"北京": "多云,18°C"
}
return data.get(
city,
"没有该城市的模拟数据"
)
# 创建可配置模型
configurable_model = init_chat_model(
"deepseek:deepseek-flash",
configurable_fields=[
"model"
]
)
# 绑定工具
model_with_tools = configurable_model.bind_tools([
get_weather
])
# 使用默认 Flash
response = model_with_tools.invoke(
"查询杭州的模拟天气。"
)
print("Flash 工具调用:")
print(response.tool_calls)
# 切换到 Pro
response = model_with_tools.invoke(
"查询北京的模拟天气。",
config={
"configurable": {
"model": "deepseek:deepseek-v4-pro"
}
}
)
print("Pro 工具调用:")
print(response.tool_calls)
上述代码中,configurable_model 可以在运行时选择 Flash 或 Pro。
绑定工具后,仍然可以通过 config 指定当前模型。
但需要注意,目标模型必须支持相应的工具调用功能。
同时,bind_tools() 仍然只让模型生成工具调用请求,不会自动执行工具。
十四、ConfigurableModel 中使用结构化输出
可配置模型同样可以在支持的集成上使用 with_structured_output()。
需要注意,多个模型提供商的结构化输出能力可能不同。因此,动态切换模型时,必须确保目标模型支持所使用的输出 Schema 和解析方式。
实例
实例
from langchain.chat_models import init_chat_model
class SummaryInfo(BaseModel):
"""文本摘要信息。"""
title: str = Field(
description="标题"
)
summary: str = Field(
description="简短摘要"
)
configurable_model = init_chat_model(
"deepseek:deepseek-flash",
configurable_fields=["model"]
)
structured_model = (
configurable_model.with_structured_output(
SummaryInfo
)
)
# 使用默认 Flash
result = structured_model.invoke(
"请提取标题和摘要:"
"Python 是一门简单易学的编程语言,"
"广泛用于 Web 开发和数据分析。"
)
print(result.model_dump())
# 运行时切换为 Pro
result = structured_model.invoke(
"请提取标题和摘要:"
"LangChain 可以帮助开发者构建 AI 应用。",
config={
"configurable": {
"model": "deepseek:deepseek-v4-pro"
}
}
)
print(result.model_dump())
这类声明式操作能够与可配置模型组合,让模型选择和输出结构保持相对独立。
不过,切换提供商时不能假设全部参数与 Schema 都能原样使用。对于不同模型,应分别测试工具调用和结构化输出兼容性。
十五、完整实例:提取结构化文章信息
下面使用 with_structured_output() 创建一个简单的文章信息提取程序。
假设用户输入一段介绍 Python 的文章,程序需要提取标题、摘要和关键词。
文件名:chat_model_advanced.py
完整实例
from dotenv import load_dotenv
from pydantic import BaseModel, Field
from langchain.chat_models import init_chat_model
load_dotenv()
# 定义文章输出结构
class ArticleInfo(BaseModel):
"""文章信息。"""
title: str = Field(
description="文章标题"
)
summary: str = Field(
description="100字以内的文章摘要"
)
keywords: list[str] = Field(
description="文章关键词"
)
# 初始化模型
model = init_chat_model(
"deepseek:deepseek-flash"
)
# 配置结构化输出
structured_model = model.with_structured_output(
ArticleInfo,
include_raw=True
)
# 待分析文章
article = """
Python 是一种通用的高级编程语言。
Python 的语法简洁,适合初学者学习。
它广泛应用于 Web 开发、数据分析、
人工智能和自动化脚本等领域。
Python 拥有丰富的第三方库和活跃的社区。
"""
# 调用模型
try:
response = structured_model.invoke(
"请从下面的文章中提取标题、摘要和关键词。"
"不要编造原文中不存在的事实。\n\n"
+ article
)
# 检查解析是否成功
if response["parsing_error"]:
print(
"解析失败:",
response["parsing_error"]
)
else:
result = response["parsed"]
print("标题:", result.title)
print("摘要:", result.summary)
print("关键词:", result.keywords)
# 转换为 JSON
print("\nJSON:")
print(
result.model_dump_json(indent=2)
)
except Exception as error:
print(
"模型调用失败:",
type(error).__name__,
str(error)
)
如果模型成功解析,程序可以生成类似的 JSON 数据:
{
"title": "Python 编程语言简介",
"summary": "Python 语法简洁,应用广泛,拥有丰富的第三方库和活跃的社区。",
"keywords": [
"Python",
"Web开发",
"数据分析",
"人工智能",
"自动化"
]
}
这个例子展示了结构化输出在实际应用中的用途。
例如,可以将提取结果用于:
- 自动生成文章摘要。
- 提取文章关键词。
- 构建内容分类系统。
- 将非结构化文本转换为数据库记录。
- 为搜索和推荐系统生成结构化元数据。
十六、常见问题
1、为什么 bind_tools() 没有执行 Python 函数?
因为 bind_tools() 只负责向模型声明工具,并不自动执行。
模型通常通过 AIMessage.tool_calls 返回工具调用请求。
要真正执行工具,可以自己编写调度代码,也可以使用 LangChain 的 create_agent()。
2、为什么 tool_calls 为空?
可能有以下原因:
- 模型认为不需要工具。
- 工具描述不清晰。
- 所使用的模型不支持工具调用。
- 模型选择直接生成文本。
如果需要提高工具调用的准确率,可以完善工具描述和提示词,并检查提供商是否支持相关的 Tool Choice 参数。
需要注意,强制工具调用参数并不是所有提供商都支持。
3、Pydantic 工具类为什么没有自动执行?
Pydantic 类主要用于定义工具参数结构,不包含实际执行逻辑。
如果使用 model.bind_tools([GetWeather]),模型可以生成名为 GetWeather 的工具调用,但程序仍需要提供真正的查询逻辑。
4、为什么 with_structured_output() 会报错?
可能是模型不支持当前的结构化输出方式,也可能是模型返回的数据无法通过解析或验证。
建议:
- 确认模型是否支持结构化输出。
- 检查 Schema 定义是否过于复杂。
- 尝试减少字段或嵌套层级。
- 设置
include_raw=True查看原始响应。 - 检查
parsing_error和请求异常。
5、JSON Schema 和 Pydantic 哪个更好?
如果主要在 Python 中使用,Pydantic 通常更方便,可以通过属性访问字段,并提供类型验证。
如果需要在 Python、JavaScript、Java 等不同语言之间共享数据结构,JSON Schema 更容易与多种语言和系统集成。
具体选择取决于项目需求。
6、结构化输出是否保证内容正确?
不能。
结构化输出主要控制返回数据的形式,并不能保证模型提取或生成的事实全部正确。
对于合同、金融、医疗或其他高要求场景,应进行必要的数据校验和人工审核。
7、为什么结构化输出返回 dict,而不是 Pydantic 对象?
返回值类型与传入的 Schema 有关。
一般情况下:
- 使用 Pydantic 模型类:解析成功时返回 Pydantic 实例。
- 使用 JSON Schema 或 TypedDict:通常返回字典。
- 使用
include_raw=True:返回包含raw、parsed和parsing_error的字典。
8、是否可以同时使用工具和结构化输出?
可以在支持相关能力的模型和 Agent 框架中组合使用。
例如,Agent 可以先查询数据库,再把结果整理为结构化数据。
但不同模型集成支持的组合方式可能不同,不应假设可以任意链式调用 bind_tools() 和 with_structured_output() 而始终正常工作。
对于较复杂的应用,可以使用 create_agent() 提供工具,并通过 Agent 的结构化响应配置处理最终输出。
总结
本章节介绍了 LangChain Chat Model 的两个高级功能。
bind_tools() 主要用于工具调用:
model_with_tools = model.bind_tools([
get_weather
])
response = model_with_tools.invoke(
"查询杭州天气"
)
print(response.tool_calls)
with_structured_output() 主要用于结构化数据生成:
structured_model = model.with_structured_output(
PersonInfo
)
result = structured_model.invoke(
"张三今年28岁"
)
print(result.name)
它们的主要区别是:
bind_tools()让模型能够请求执行外部工具,但不负责真正执行。with_structured_output()将模型输出解析为指定数据结构。- Pydantic 适合需要 Python 类型验证的应用。
- JSON Schema 适合需要跨语言共享数据格式的场景。
include_raw=True可以帮助调试结构化输出结果和解析错误。- 可配置模型可以与声明式工具和结构化输出操作组合,但需要检查目标模型的兼容性。
掌握这两个方法后,就具备了进一步学习 LangChain Agent、工具编排、数据提取和 AI 工作流的基础。
