跳到主要内容
LangChain 实战:工具调用与结构化输出 | 极客日志
Python AI 算法
LangChain 实战:工具调用与结构化输出 综述由AI生成 LangChain 工具调用与结构化输出是构建智能应用的关键。了三种工具创建方式及本地/第三方集成流程,对比了 Pydantic、TypedDict 和 JSON Schema 三种结构化输出方案。通过简历解析、意图识别及天气助手等实际案例,展示了如何将非结构化数据转化为规范格式,实现 AI 从对话到执行的跨越。
片刻 发布于 2026/3/22 更新于 2026/5/5 7 浏览LangChain 实战:工具调用与结构化输出
工具调用是 LangChain 的核心功能之一,允许 AI 模型调用外部函数或 API 来完成特定任务。比如获取天气情况时,LLM 无法直接获取实时信息,借助工具通过外部服务搜索就能完成查询。
再如获取数据库表数据,LLM 无法直接读取,此时可借助工具与数据库交互完成查询。
工具创建方式
直接用 @tool 装饰函数
最简单的方式,适合小工具。
from langchain_core.tools import tool
@tool
def add (a: int , b: int ) -> int :
return a + b
用 @tool + 自定义参数结构(Pydantic)
参数更清晰,能写详细说明。
from pydantic import BaseModel
from langchain_core.tools import tool
class AddInput (BaseModel ):
a: int
b: int
@tool(args_schema=AddInput )
def add (a: int , b: int ) -> int :
return a + b
继承 BaseTool 写类
from langchain_core.tools import BaseTool
class AddTool (BaseTool ):
name = "add"
description = "Adds two numbers"
def _run (self, a: int , b: int ) -> int :
return a + b
本地自定义工具实战
定义工具 from langchain_core.tools import tool
@tool
def sum_to_n (n: int ) -> int :
"""计算从 0 累加到 n 的结果"""
total = 0
for i in range (n + 1 ):
total += i
return total
使用 @tool 装饰器标记函数
函数必须有类型注解(n: int 和 -> int)
文档字符串(docstring)会被模型用来理解工具的功能
绑定工具到模型 from langchain_deepseek import ChatDeepSeek
llm = ChatDeepSeek(
model_name="deepseek-chat" ,
api_key="your-api-key" ,
temperature=0.7 ,
max_tokens=8192 ,
)
bound_llm = llm.bind_tools([sum_to_n])
bind_tools() 方法将工具绑定到模型
可以绑定多个工具:bind_tools([tool1, tool2, tool3])
如果需要强制调用工具,可在绑定时设置 tool_choice="any"
工具调用流程 from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
messages = [
SystemMessage(content="你是一个数学助手。当用户给你一个数字 n 时,使用 sum_to_n 工具计算从 0 累加到 n 的结果。" ),
HumanMessage(content="10" ),
]
ai_message = bound_llm.invoke(messages)
messages.append(ai_message)
for tool_call in ai_message.tool_calls:
if tool_call["name" ] == "sum_to_n" :
result = sum_to_n.invoke(tool_call["args" ])
messages.append(
ToolMessage(content=str (result), tool_call_id=tool_call["id" ])
)
final_msg = bound_llm.invoke(messages).content
print (final_msg)
工作流程:
用户输入 → AI 分析 → 决定调用工具 → 执行工具 → 返回结果 → AI 生成最终回答
AI 响应结构解析 当模型决定调用工具时,ai_message 包含以下关键信息:
{
'content' : '我来计算从 0 累加到 10 的结果。' ,
'tool_calls' : [
{
'name' : 'sum_to_n' ,
'args' : {'n' : 10 },
'id' : 'call_00_xxx' ,
'type' : 'tool_call'
}
],
'response_metadata' : {
'token_usage' : {...},
'model_name' : 'deepseek-chat' ,
'finish_reason' : 'tool_calls'
}
}
ai_message 是经过 LangChain 框架整合后的响应,不是模型的原始输出
tool_calls 列表包含模型决定调用的所有工具
必须使用 ToolMessage 包装工具执行结果,并提供 tool_call_id
第三方工具集成(Tavily 搜索)
集成第三方工具 from langchain_tavily import TavilySearch
tavily_tool = TavilySearch(
max_results=4 ,
tavily_api_key="your-tavily-api-key"
)
bound_llm = llm.bind_tools([tavily_tool])
第三方工具通常需要 API Key
参数名必须正确(如 tavily_api_key 而不是 api_key)
多轮工具调用 messages = [
SystemMessage(content="你是一个天气助手。使用 tavily_search 工具搜索天气情况。" ),
HumanMessage(content="2026 年 3 月 2 日北京天气情况" ),
]
max_rounds = 3
for round_num in range (1 , max_rounds + 1 ):
ai_message = bound_llm.invoke(messages)
messages.append(ai_message)
if not ai_message.tool_calls:
print (ai_message.content)
break
for tool_call in ai_message.tool_calls:
result = tavily_tool.invoke(tool_call["args" ])
tool_message = ToolMessage(
content=str (result), tool_call_id=tool_call["id" ]
)
messages.append(tool_message)
工作流程:
第 1 轮:用户提问 → AI 调用搜索工具 → 获取搜索结果
第 2 轮:AI 分析结果 → 生成最终答案(不再调用工具)
实际输出示例 根据搜索结果,2026 年 3 月 2 日北京天气情况:
白天天气:阴天,大部分地区有小雪或雨夹雪,北风转南风 2-3 级,最高气温 5-6℃。
夜间天气:阴天,山区有小雪,南风转北风 1-2 级,最低气温 -1℃到 0℃。
特别预警:北京市气象台发布大雾黄色预警信号,夜间至次日上午能见度小于 1000 米。
结构化输出 结构化输出允许模型返回符合特定格式的数据,而不是纯文本。这对于数据提取、API 响应生成等场景非常有用。例如,可能希望将模型输出存储在数据库中,并确保输出符合数据库模式。
Pydantic BaseModel(推荐)
定义 Pydantic 模型 from pydantic import BaseModel, Field
class TestOutput (BaseModel ):
"""城市信息的 Pydantic 模型"""
test: str = Field(description="这个城市现在的详细天气情况,包括天气状况、温度、风力等" )
test2: str = Field(description="城市名称" )
test3: str = Field(description="在这个城市出生的一个著名名人,包括简短介绍" )
继承 BaseModel
使用 Field() 添加字段描述,帮助模型理解每个字段的含义
支持类型验证和数据校验
绑定结构化输出 from langchain_deepseek import ChatDeepSeek
llm_deepseek = ChatDeepSeek(
model_name="deepseek-chat" ,
api_key="your-api-key" ,
base_url="https://api.deepseek.cn/v1" ,
)
struct_output_model = llm_deepseek.with_structured_output(TestOutput)
result = struct_output_model.invoke("上海" )
print (f"天气:{result.test} " )
print (f"城市:{result.test2} " )
print (f"名人:{result.test3} " )
输出示例
test='上海现在天气晴朗,温度 25°C,东南风 3-4 级,湿度 65%,空气质量良好'
test2='上海'
test3='姚明,中国著名篮球运动员,1980 年出生于上海,曾效力于 NBA 休斯顿火箭队'
类型安全,IDE 有完整的代码提示
自动数据验证
可以使用 Field() 添加详细描述
支持复杂的嵌套结构
TypedDict
定义 TypedDict from typing import TypedDict, Annotated
class TestOutputDict (TypedDict ):
"""城市信息的字典类型"""
weather: Annotated[str , "这个城市现在的详细天气情况,包括天气状况、温度、风力等" ]
city_name: Annotated[str , "城市名称" ]
famous_person: Annotated[str , "在这个城市出生的一个著名名人,包括简短介绍" ]
population: Annotated[int , "城市人口数量(万人)" ]
TypedDict 不支持 Field()
使用 Annotated[类型,"描述"] 添加字段描述
支持不同类型(str, int, float, bool 等)
使用 TypedDict
struct_output_model_dict = llm_deepseek.with_structured_output(TestOutputDict)
result_dict = struct_output_model_dict.invoke("上海" )
print (f"天气:{result_dict['weather' ]} " )
print (f"城市:{result_dict['city_name' ]} " )
print (f"人口:{result_dict['population' ]} 万人" )
输出示例
{'city_name' : '上海' , 'weather' : '上海现在天气晴朗,温度 18-25°C,东南风 3-4 级' , 'population' : 2487 , 'famous_person' : '姚明,中国著名篮球运动员,前 NBA 休斯顿火箭队球员' }
轻量级,返回纯字典
不需要定义复杂的类
可以使用 Annotated 添加描述
JSON Schema
定义 JSON Schema import json
json_schema = {
"title" : "CityInfo" ,
"description" : "城市信息的 JSON 格式" ,
"type" : "object" ,
"properties" : {
"city" : {"type" : "string" , "description" : "城市名称" },
"weather" : {"type" : "string" , "description" : "当前天气情况,包括温度、天气状况、风力" },
"attractions" : {
"type" : "array" ,
"description" : "城市的著名景点列表" ,
"items" : {"type" : "string" }
},
"gdp" : {"type" : "number" , "description" : "城市 GDP(亿元)" }
},
"required" : ["city" , "weather" , "attractions" , "gdp" ]
}
使用标准的 JSON Schema 格式
支持复杂的嵌套结构(数组、对象等)
required 字段指定必填项
使用 JSON Schema
struct_output_model_json = llm_deepseek.with_structured_output(json_schema)
result_json = struct_output_model_json.invoke("北京" )
print (json.dumps(result_json, ensure_ascii=False , indent=2 ))
输出示例 {
"city" : "北京" ,
"weather" : "晴,温度 15°C,风力 2 级" ,
"attractions" : [ "故宫" , "天安门广场" , "长城" , "颐和园" , "天坛" , "圆明园" ] ,
"gdp" : 40269.6
}
标准化,跨语言通用
支持复杂的嵌套结构
灵活,可以定义任意结构
定义较繁琐
没有 IDE 类型提示
返回的是 dict,不是 JSON 字符串
可选结构化输出(动态类型选择) 当你需要模型根据用户输入自动选择 返回不同类型的结构化数据时,可以使用可选结构化输出。这在智能客服系统、信息提取系统或多功能 API 中非常有用。
实现方式 使用 Literal 类型和嵌套的 Pydantic 模型:
from pydantic import BaseModel, Field
from typing import Literal
class PersonInfo (BaseModel ):
"""人物信息"""
name: str = Field(description="姓名" )
age: int = Field(description="年龄" )
occupation: str = Field(description="职业" )
class CityInfo (BaseModel ):
"""城市信息"""
city: str = Field(description="城市名称" )
population: int = Field(description="人口(万人)" )
class NormalAnswer (BaseModel ):
"""普通回答"""
answer: str = Field(description="根据用户的提问正常答复的内容" )
class Response (BaseModel ):
"""响应结构,模型根据问题选择返回人物、城市信息或普通回答"""
type : Literal ["person" , "city" , "normal" ] = Field(
description="响应类型:person 表示人物,city 表示城市,normal 表示普通回答"
)
person: PersonInfo | None = Field(default=None , description="人物信息,仅当 type 为 person 时填充" )
city: CityInfo | None = Field(default=None , description="城市信息,仅当 type 为 city 时填充" )
normal: NormalAnswer | None = Field(default=None , description="普通回答,仅当 type 为 normal 时填充" )
使用示例 from langchain_deepseek import ChatDeepSeek
llm = ChatDeepSeek(
model_name="deepseek-chat" ,
api_key="your-api-key" ,
base_url="https://api.deepseek.cn/v1" ,
)
model = llm.with_structured_output(Response)
result1 = model.invoke("上海" )
if result1.type == "city" :
print (f"城市:{result1.city.city} " )
print (f"人口:{result1.city.population} 万" )
result2 = model.invoke("你是哪个模型" )
if result2.type == "normal" :
print (f"回答:{result2.normal.answer} " )
输出示例 测试 1(城市):
类型:city
城市:上海
人口:2489 万
测试 2(普通问题):
类型:normal
回答:我是 DeepSeek 最新版本的 AI 助手,由深度求索公司开发...
关键特性
Literal 类型 :限定模型只能选择预定义的类型值
可选字段 :使用 | None 和 default=None 表示字段可选
嵌套结构 :不同的数据类型作为子结构嵌套在包装类中
模型自主决策 :AI 根据问题内容自动判断应该返回哪种结构
优势
灵活性高 :一个接口处理多种类型的请求
类型明确 :通过 type 字段清楚知道返回的数据类型
易于扩展 :可以轻松添加更多类型(如 "product", "order" 等)
代码简洁 :不需要多个不同的接口或函数
结构化输出的三大实际应用场景
场景 1:作为信息提取器 核心价值:
结构化输出最直接的应用就是信息提取 :将杂乱无章的文本数据转换为规范的结构化数据,便于存储、查询和分析。
简历解析 :从 PDF/Word 简历中提取姓名、联系方式、教育经历、工作经验
合同分析 :提取合同中的关键条款、金额、日期、甲乙方信息
发票识别 :从发票图片 OCR 文本中提取发票号、金额、税额、日期
新闻摘要 :从新闻文章中提取标题、摘要、关键人物、时间地点
from pydantic import BaseModel, Field
from typing import List
class Education (BaseModel ):
"""教育经历"""
school: str = Field(description="学校名称" )
degree: str = Field(description="学位:本科、硕士、博士等" )
major: str = Field(description="专业" )
start_year: int = Field(description="入学年份" )
end_year: int = Field(description="毕业年份" )
class WorkExperience (BaseModel ):
"""工作经历"""
company: str = Field(description="公司名称" )
position: str = Field(description="职位" )
start_date: str = Field(description="入职日期" )
end_date: str = Field(description="离职日期,如果是当前工作则为'至今'" )
responsibilities: List [str ] = Field(description="主要职责列表" )
class ResumeInfo (BaseModel ):
"""简历信息"""
name: str = Field(description="姓名" )
phone: str = Field(description="电话" )
email: str = Field(description="邮箱" )
education: List [Education] = Field(description="教育经历列表" )
work_experience: List [WorkExperience] = Field(description="工作经历列表" )
skills: List [str ] = Field(description="技能列表" )
model = llm.with_structured_output(ResumeInfo)
resume_text = """
张三,电话:138****1234,邮箱:[email protected]
教育背景:
- 2015-2019 清华大学 计算机科学与技术 本科
- 2019-2021 清华大学 人工智能 硕士
工作经历:
- 2021.07-2023.06 字节跳动 算法工程师 负责推荐系统开发,优化点击率提升 20%
- 2023.07-至今 阿里巴巴 高级算法工程师 负责大模型应用开发
技能:Python, PyTorch, LangChain, 机器学习
"""
result = model.invoke(f"请从以下简历中提取信息:\n{resume_text} " )
print (f"姓名:{result.name} " )
print (f"联系方式:{result.phone} / {result.email} " )
print (f"教育经历:{len (result.education)} 条" )
print (f"工作经历:{len (result.work_experience)} 条" )
print (f"技能:{', ' .join(result.skills)} " )
自动化数据录入 :无需人工逐字段复制粘贴
格式标准化 :统一的数据格式便于后续处理
提高准确性 :AI 理解上下文,减少人工错误
可扩展性强 :轻松添加新字段或修改提取规则
场景 2:作为提示词增强 核心价值:
通过结构化输出,可以让 AI 先理解、再执行 。将用户的模糊需求转换为明确的结构化指令,确保 AI 准确理解用户意图。
搜索意图识别 :理解用户搜索的真实意图(查询、购买、比较、学习)
任务分解 :将复杂任务拆解为多个子任务
参数提取 :从自然语言中提取 API 调用所需的参数
多轮对话管理 :理解对话上下文和用户当前状态
from pydantic import BaseModel, Field
from typing import Literal
class SearchIntent (BaseModel ):
"""搜索意图分析"""
intent_type: Literal ["informational" , "transactional" , "navigational" , "comparison" ] = Field(
description="意图类型:informational(查询信息)、transactional(购买交易)、navigational(导航访问)、comparison(对比比较)"
)
keywords: List [str ] = Field(description="提取的关键词列表" )
filters: dict = Field(description="筛选条件,如价格范围、品牌、地区等" )
urgency: Literal ["low" , "medium" , "high" ] = Field(description="紧急程度" )
clarification_needed: bool = Field(description="是否需要进一步澄清" )
suggested_questions: List [str ] = Field(description="建议的澄清问题列表" )
model = llm.with_structured_output(SearchIntent)
user_query = "我想买个性价比高的笔记本电脑,预算 5000 左右,主要用来写代码"
intent = model.invoke(f"分析以下用户需求:{user_query} " )
print (f"意图类型:{intent.intent_type} " )
print (f"关键词:{', ' .join(intent.keywords)} " )
print (f"筛选条件:{intent.filters} " )
print (f"紧急程度:{intent.urgency} " )
if intent.intent_type == "transactional" :
search_params = {
"category" : "笔记本电脑" ,
"price_range" : intent.filters.get("price_range" , "" ),
"keywords" : intent.keywords,
"sort_by" : "price_performance_ratio"
}
print (f"\n执行搜索:{search_params} " )
elif intent.clarification_needed:
print (f"\n需要澄清的问题:" )
for q in intent.suggested_questions:
print (f" - {q} " )
意图明确化 :将模糊需求转换为明确的结构化指令
减少误解 :AI 先理解再执行,避免答非所问
提高准确性 :基于结构化理解执行后续操作
可追溯性 :清楚知道 AI 是如何理解用户需求的
场景 3:与 Tool 联合使用 核心价值:
结构化输出与工具调用结合,可以实现智能决策 + 精准执行 :AI 先分析需求并输出结构化决策,然后调用相应工具执行。
智能助手 :理解用户指令 → 决定调用哪些工具 → 执行并返回结果
自动化工作流 :分析任务 → 规划步骤 → 依次调用工具完成
数据分析 :理解分析需求 → 调用数据查询工具 → 结构化返回分析结果
多工具协作 :一个任务需要多个工具配合完成
实现示例:智能天气助手(工具调用 + 结构化输出整合)
from langchain.chat_models import init_chat_model
from langchain_tavily import TavilySearch
from pydantic import BaseModel, Field
llm_tavily = init_chat_model(
model="deepseek-chat" ,
model_provider="deepseek" ,
api_key="your-api-key" ,
temperature=0.7 ,
max_tokens=8192 ,
)
tavily_tool = TavilySearch(
max_results=4 ,
tavily_api_key="your-tavily-api-key"
)
class WeatherResult (BaseModel ):
"""天气查询结果"""
location: str = Field(description="地点" )
date: str = Field(description="日期" )
temperature: str = Field(description="温度范围" )
weather: str = Field(description="天气状况" )
wind: str = Field(description="风力风向" )
warning: str = Field(description="预警信息,如果没有则为'无'" )
bound_llm = llm_tavily.bind_tools([tavily_tool])
structured_llm = bound_llm.with_structured_output(WeatherResult)
result = structured_llm.invoke("2026 年 3 月 2 日北京天气情况" )
print (f"地点:{result.location} " )
print (f"日期:{result.date} " )
print (f"温度:{result.temperature} " )
print (f"天气:{result.weather} " )
print (f"风力:{result.wind} " )
print (f"预警:{result.warning} " )
智能决策 :结构化输出帮助 AI 理解需求并做出决策
精准执行 :基于结构化决策调用合适的工具
结果规范 :工具返回的数据再次结构化,便于使用
流程清晰 :理解 → 决策 → 执行 → 返回,每步都可追溯
总结
三大场景对比 场景 核心价值 主要用途 典型应用 技术特点 信息提取器 非结构化 → 结构化 数据提取与转换 简历解析、合同分析、发票识别 单向转换、数据标准化 提示词增强 模糊意图 → 明确指令 意图理解与澄清 搜索优化、任务分解、参数提取 双向交互、意图明确化 Tool 联合使用 智能决策 + 精准执行 复杂任务自动化 智能助手、自动化工作流、数据分析 多步骤、工具协作
选择建议
需要类型灵活切换 → 使用可选结构化输出(choice_struct.py 模式)
固定的数据提取 → 使用单一 Pydantic 模型
复杂嵌套结构 → 使用 JSON Schema 或嵌套 Pydantic 模型
简单键值对 → 使用 TypedDict
四种结构化输出方式对比 方式 优点 缺点 适用场景 返回类型 Pydantic BaseModel 类型安全、字段验证、IDE 支持好、可使用 Field 需要定义类 复杂数据结构、需要验证 Pydantic 对象 TypedDict 轻量级、返回纯字典、使用 Annotated 添加描述 类型检查较弱、不支持 Field 简单字典结构 dict JSON Schema 标准化、跨语言、灵活、支持复杂嵌套 定义繁琐、无 IDE 提示 跨系统对接、复杂嵌套 dict 可选结构化输出 灵活性高、类型明确、易扩展、代码简洁 需要定义包装类 多类型动态响应 Pydantic 对象
参考资源 相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
RSA密钥对生成器 生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
Mermaid 预览与可视化编辑 基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
随机西班牙地址生成器 随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online