2026.8.5 11:03
学习路线
rust
Python -> Python进阶 -> Python数据分析 -> LangChain -> 机器学习 -> 神经网络 -> NLP -> Coze -> Dify -> 大模型应用基础 -> 大模型微调 -> 多模态 -> vibeCoding
复习巩固总结
1. LangChain封装调用大模型
python
from langchain.chat_models import init_chat_model
import os
from dotenv import load_dotenv
load_dotenv(override=True)
CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")
model = init_chat_model(
model="openai:deepseek-v4-flash",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL,
)
print(model.invoke("一句话介绍下你自己"))
2. invoke - 阻塞式
python
# 字典列表(推荐)
conversation = [
{"role": "system", "content": "你是一个非常友好的AI助手"},
{"role": "user", "content": "你好,我叫小明"},
]
response1 = model.invoke(conversation)
print(f"AI的回复1:{response1.content}")
# 如果不传递历史,AI 会"失忆"
conversation.append({"role": "assistant", "content": response1.content})
conversation.append({"role": "user", "content": "我叫什么名字?"})
response2 = model.invoke(messages2)
print(f"AI的回复2:{response2.content}")
# 美化输出
from rich import print as rprint
rprint(response2)
3. stream - 流式输出
python
from langchain.chat_models import init_chat_model
import os
from dotenv import load_dotenv
load_dotenv(override=True)
CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")
model = init_chat_model(
model="openai:gpt-5.4-mini",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL,
)
# model.stream 返回一个迭代器
for chunk in model.stream("帮我解释一下什么是人工智能?"):
print(chunk.text, end="", flush=True)
4. model_kwargs和extra_body
- model_kwargs:传递标准 OpenAI 兼容 API 顶层标准参数,存放官方协议规范内、所有厂商通用的生成参数,最终会平铺展开到请求 JSON 的最外层一级字段
- extra_body:传递厂商私有 / 扩展非标参数,存放不在 OpenAI 官方协议里、仅某一家模型 / 推理后端独有的参数,整个字典会完整嵌套进请求根节点,和 messages、model 同级
5. 4种消息对象中字段的说明
SystemMessage参数列表
- content :消息内容,字段名可以省略
HumanMessage参数列表
- content :消息内容,字段名可以省略
- metadata :元数据字段,可以有很多,自定义
python
# name 和 id 都属于元数据字段,当消息类型相同,对消息进行区分。但不是所有模型(例如GPT)都支持这一功能,是否支持取决于模型供应商(例如CloseAI)
HumanMessage(
content="Hello!",
name="alice", # 可选,用户名
id="msg_123", # 可选,message的ID
)
AIMessage参数列表
- content :模型输出的原始内容,字段名可以省略
- response_metadata :AIMessage特有属性,LLM的响应中附加元数据,根据不同模型会有不同,如可能会包含本次token使用量等信息。
- tool_calls :AIMessage特有属性,表示工具调用信息。当LLM决定调用工具时,在AIMessage 中就会包含这个属性,没有工具调用则为空。
js
tool_calls=[
{
'name': 'get_weather', // 应调用的工具名
'args': {'city': '杭州'}, // 调用工具的参数
'id': 'call_00_gIXYOD1Q1OkEXmdDBqXR1578', // 工具调用的唯一标识ID
'type': 'tool_call'
}
]
ToolMessage参数列表
- content :文件内容
- name :工具名称
- tool_call_id :工具调用唯一ID,ToolMessage必须紧邻匹配的AIMessage,和前者tool_calls中的id一致。
6. 对话历史优化
python
def keep_recent_messages(messages, max_pairs=3):
"""
保留最近的N轮对话
:param max_pairs: 保留对话的轮数(每轮 = user + assistant)
"""
# 分离 system 消息和对话消息
system_messages = [m for m in messages if m.get("role") == "system"]
conversation_messages = [m for m in messages if m.get("role") != "system"]
# 只保留最近的消息对
recent_messages = conversation_messages[-(max_pairs * 2):]
# 返回系统消息和最近的消息对
return system_messages + recent_messages
# 初始化
long_conversation = [
{"role": "system", "content": "你是 Python 导师"}
]
# 多轮对话....
# 优化:只保留最近 2 轮
optimized = keep_recent_messages(long_conversation, max_pairs=2)
# 添加新的用户问题
optimized.append({"role": "user", "content": "我第一个问题问的是什么?"})
# 使用优化后的历史
response = model.invoke(optimized)
print(f"\nAI 回复: {response.content}")
7. content_blocks
支持类型:包括 text (文本)、 image (图片)、 audio (音频)、 video (视频)、 tool_call (工具调用)以及 reasoning (推理/思维链)。
- 输入格式化
python
HumanMessage(
# content_blocks写法参考:https://docs.langchain.com/oss/python/langchain/messages#openai
content_blocks=[
{'type': 'text', 'text': '这张图里有什么?'},
{
'type': 'image',
'base64': base64_image,
'mime_type': 'image/png',
}
]
)
- 输出格式化
不同的模型其输出格式可能不同,仅为提取思考内容,切换模型都可能需要更改代码,非常不方便。content_blocks提供了 统一的输出格式 ,可以将不同格式的响应统一为标准格式。
python
model = init_chat_model(
model="deepseek:deepseek-v4-flash",
extra_body={"thinking": {"type": "enabled"}},
)
response = model.invoke("你好,一句话回答")
print(response.content_blocks)
8. 提示词模板
实例化 - from_messages()
python
from langchain_core.prompts import ChatPromptTemplate
chat_prompt_template = ChatPromptTemplate.from_messages([
("system", "你是一个友好的AI助手,你的名字叫{name}"),
# ("user"),
("human", "你好,最近怎么样?"),
# ("assistant")
("ai", "我很好,谢谢"),
# ("AI", "我很好,谢谢"), # 报错,注意是小写的ai
("human", "{user_input}")
])
result = chat_prompt_template.invoke({"name": "小智", "user_input": "2 + 2 = ?"})
print(result)
更丰富的初始化参数类型
python
# 1 - 字符串列表
chat_prompt_template = ChatPromptTemplate.from_messages([
"你好,我是{name}" # 会理解为是一个用户消息
])
# 2 - 元组列表
chat_prompt_template = ChatPromptTemplate.from_messages([
("system", "你是一个友好的AI助手"),
("human", "你好,我是{name}")
])
# 3 - 字典列表
chat_prompt_template = ChatPromptTemplate.from_messages([
{"role": "system", "content": "你是一个友好的AI助手"},
{"role": "human", "content": "你好,我是{name}"}
])
# 4 - BaseMessagePromptTemplate参数列表
from langchain_core.promts import SystemMessagePromptTemplate, HumanMessagePromptTemplate
system_mess_template = SystemMessagePromptTemplate.from_messages("你是一个友好的AI助手")
human_mess_template = HumanMessagePromptTemplate.from_messages("你好,我是{name}")
chat_prompt_template = ChatPromptTemplate.from_messages([
system_mess_template,
human_mess_template
])
# 5 - BaseChatPromptTemplate参数列表
inner_chat_template1 = ChatPromptTemplate.from_messages([
("system", "你是一个友好的AI助手")
])
inner_chat_template2 = ChatPromptTemplate.from_messages([
("human", "你好,我是{name}")
])
chat_prompt_template = ChatPromptTemplate.from_messages([
inner_chat_template1,
inner_chat_template2
])
模板调用的3种方式
python
# 1 - invoke 传入参数:字典列表;返回值类型:ChatPromptValue
result = chat_prompt_template.invoke({"name": "小智", "user_input": "2 + 2 = ?"})
# 2 - format 参数类型:变量值;返回值类型:字符串
result = chat_prompt_template.format(name="小智", user_input="2 + 2 = ?")
# 3 - format_messages 传入参数:变量值;返回值类型:消息列表
result = chat_prompt_template.format_messages(name="小智", user_input="2 + 2 = ?")
# 结合LLM调用
response = model.invoke(result)
LangChain 学习
24. 高级特性
1. 部分变量预填充:partial()
预填充某些固定不变的变量,创建模板的变体。
使用场景:
- 某些变量在所有调用中都相同
- 需要为不同用户/场景创建定制模板
jupyter
# ChatPromptTemplate的高级特性
## 1. 部分变量预填充:partial()
举例:
from langchain_core.prompts import ChatPromptTemplate
# 原始模板
template = ChatPromptTemplate.from_messages([
("system", "你是{role},目标用户是{audience}"),
("user", "{task}")
])
result1 = template.invoke({"role": "导游", "audience": "游客", "task": "介绍一下北京的故宫"})
result2 = template.invoke({"role": "导游", "audience": "游客", "task": "介绍一下北京的颐和园"})
print(result1)
print(result2)
上述代码,可以使用partial()优化
from langchain_core.prompts import ChatPromptTemplate
# 原始模板
template = ChatPromptTemplate.from_messages([
("system", "你是{role},目标用户是{audience}"),
("user", "{task}")
])
# 部分变量预填充
# template.partial(role="导游", audience="游客")
final_template = template.partial(role="导游", audience="游客")
# result1 = template.invoke({"role": "导游", "audience": "游客", "task": "介绍一下北京的故宫"})
# result2 = template.invoke({"role": "导游", "audience": "游客", "task": "介绍一下北京的颐和园"})
# result1 = template.invoke({"task": "介绍一下北京的故宫"})
# result2 = template.invoke({"task": "介绍一下北京的颐和园"})
result1 = final_template.invoke({"task": "介绍一下北京的故宫"})
result2 = final_template.invoke({"task": "介绍一下北京的颐和园"})
print(result1)
print(result2)
举例:
# 场景:为不同部门创建专用模板
base_template = ChatPromptTemplate.from_messages([
("system", "你是{department}的{role}"),
("user", "{task}")
])
# IT 部门
it_template = base_template.partial(
department="IT 部门",
role="技术支持"
)
# 销售部门
sales_template = base_template.partial(
department="销售部门",
role="销售顾问"
)
sales_template.invoke({"task":"为什么每年年底汽车会促销"})
2026.08.05 13:16
2. 消息占位符
当你不确定消息提示模板使用什么角色,或者希望在格式化过程中 插入消息列表 时,该怎么办? 这就需要使用消息占位符,负责在特定位置添加消息列表。
使用场景:多轮对话系统存储历史消息以及Agent的中间步骤处理此功能非常有用。
jupyter
## 2、消息占位符
### 2.1 使用placeholder
举例
template = ChatPromptTemplate.from_messages([
("system", "你是一个AI助手"),
("placeholder", "{conversation}")
])
result = template.invoke({
"conversation": [
("human", "你好,请问明天的天气如何?"),
("ai", "明天天气晴朗"),
("human", "后天的天气怎么样?")
]
})
print(result)
### 2.2 使用MessagesPlaceholder
举例:
from langchain_core.prompts import MessagesPlaceholder
template = ChatPromptTemplate.from_messages([
("system", "你是一个AI助手"),
# ("placeholder", "{conversation}")
MessagesPlaceholder(variable_name="conversation")
])
result = template.invoke({
"conversation": [
("human", "你好,请问明天的天气如何?"),
("ai", "明天天气晴朗"),
("human", "后天的天气怎么样?")
]
})
print(result)
from langchain_core.messages import HumanMessage, AIMessage
result = template.invoke({
"conversation": [
# ("human", "你好,请问明天的天气如何?"),
# ("ai", "明天天气晴朗"),
# ("human", "后天的天气怎么样?")
HumanMessage("你好,请问明天的天气如何?"),
AIMessage("明天天气晴朗"),
HumanMessage("后天的天气怎么样?"),
]
})
print(result)
举例:存储历史消息
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt_template = ChatPromptTemplate.from_messages(
[
("system", "你是一个非常友好的AI助手"),
MessagesPlaceholder(variable_name="history"),
("human", "{question}")
]
)
prompt_template.invoke(
{
"history": [
("human", "5 + 2 = ?"),
("ai", "5 + 2 = 7")
],
"question": "结果再乘以4呢?"
}
)
3. 可复用模板库
jupyter
## 3、可复用模板库
定义了具体的存放模板的py文件
from langchain_core.prompts import ChatPromptTemplate
class PromptLibrary:
"""可复用的提示词模板库"""
TRANSLATOR = ChatPromptTemplate.from_messages([
("system", "你是专业翻译,精通{source_lang}和{target_lang}"),
("user", "翻译以下文本:\n{text}")
])
CODE_REVIEWER = ChatPromptTemplate.from_messages([
("system", "你是{language}代码审查专家,重点关注{focus}"),
("user", "审查代码:\n```{language}\n{code}\n```")
])
SUMMARIZER = ChatPromptTemplate.from_messages([
("system", "你是内容摘要专家"),
("user", "将以下内容总结为{num}个要点:\n{content}")
])
TUTOR = ChatPromptTemplate.from_messages([
("system", "你是{subject}导师,学生水平:{level}"),
("user", "{question}")
])
其他文件中,进行调用:
# from templates import PromptLibrary
messages = PromptLibrary.TRANSLATOR.format_messages(
source_lang="英语",
target_lang="中文",
text="Hello World"
)
python
# templates/
# ├── __init__.py
# ├── common.py # 通用模板
# ├── translation.py # 翻译相关
# └── coding.py # 编程相关
# common.py
from langchain_core.prompts import ChatPromptTemplate
FRIENDLY_ASSISTANT = ChatPromptTemplate.from_messages([
("system", "你是一个友好的助手"),
("user", "{input}")
])
4. 模板组合(了解)
python
# 定义可复用的部分
role_part = "你是一个{domain}专家。"
style_part = "回答风格:{style}。"
constraint_part = "限制:{constraint}。"
# 组合
full_system = role_part + style_part + constraint_part
template = ChatPromptTemplate.from_messages([
("system", full_system),
("user", "{question}")
])
python
template1 = ChatPromptTemplate.from_messages([
("system", "你是助手")
])
template2 = ChatPromptTemplate.from_messages([
("user", "{input}")
])
# 组合(LangChain 1.0 支持)
combined = template1 + template2
25. Tools概述
1. 工具的重要性
构建更强大的AI工程应用,只有生成文本这样的" 纸上谈兵 "能力自然是不够的。
工具是赋予大语言模型 与外部世界交互能力 的关键组件,从而能让智能体执行搜索、计算、数据库查询、邮件发送或调用第三方API等,进而构建功能强大的AI应用。借助工具,大模型才能从" 认识世界 "走向" 改变世界 "。
大模型和智能体的区别:智能体能直接调用Tools
2. 工具调用的方式
在LangChain中,工具(Tools)实际上是指明确定义了输入和输出的 可调用函数 。因此, 工具调用(Tool Calling) 也被称为 函数调用(Function Calling) 。
jupyter
# Tool使用的概述
## 1、工具的调用方式
### 1.1 方式1:直接调用
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""
获取指定城市的天气信息
参数:
city: 城市名称,如"北京"、"上海"
返回:
天气信息字符串
"""
# 你的实现
return city + "晴天,温度 15°C"
get_weather.invoke({"city": "北京"})
### 1.2 方式2:基于模型进行调用
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
# 从.env文件中加载环境变量
load_dotenv(override=True)
CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL
)
from langchain_core.tools import tool
# 定义工具
@tool
def get_weather(city: str) -> str:
"""获取指定城市的天气"""
# 你的实现
return "晴天,温度 15°C"
# 绑定工具
model_with_tools = model.bind_tools([get_weather])
# AI 可以决定是否调用工具
response = model_with_tools.invoke("北京天气如何?")
# response = model_with_tools.invoke("2 + 3 = ?")
# 检查 AI 是否要调用工具
if response.tool_calls:
print("AI 想调用工具:", response.tool_calls)
else:
print("AI 直接回答:", response.content)
3. 工具调用的整体流程
4. 从Message流转看工具的调用
jupyter
## 2、从Message流转看工具的调用
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
# 从.env文件中加载环境变量
load_dotenv(override=True)
CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL
)
from langchain.messages import HumanMessage, ToolMessage
from rich import print as rprint
@tool
def get_weather(city: str):
"""获取天气的工具"""
return f"{city}天气晴朗~"
# 将模型和工具绑定
model_with_tools = model.bind_tools([get_weather])
# 声明一个消息列表
messages = [
HumanMessage("今天北京天气如何")
]
# 模型生成调用工具请求
response = model_with_tools.invoke(messages)
# 添加AIMessage到消息列表中
messages.append(response)
print(response)
rprint(response) # AIMessage
tool_calls = response.tool_calls
for tool_call in tool_calls:
if tool_call["name"] == "get_weather":
# 大模型和Agent的主要区别在于:大模型不会主动的调用工具,所以这时候我们需要主动让工具调用
# 返回的是ToolMessage类型消息,添加到消息列表中
tool_response = get_weather.invoke(tool_call)
print(type(tool_response))
messages.append(tool_response)
print("=====================> messages <=====================")
for msg in messages:
msg.pretty_print()
print("=====================> messages <=====================")
final_response = model_with_tools.invoke(messages)
print(f"final_response: \n{final_response}")
26. 工具的定义方式1:不使用@tool
1. 模型绑定工具并发送请求
jupyter
# 不使用@tool的方式定义工具
## 1、举例
# 1、模型的初始化
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
from rich import print as rprint
# 从.env文件中加载环境变量
load_dotenv(override=True)
CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL
)
# 2、声明一个函数(工具)
def get_weather(city: str):
return f"{city}天气晴朗~~"
# 3、将函数绑定在模型上
model_with_tools = model.bind_tools([get_weather])
# 4、调用模型
response = model_with_tools.invoke("北京的天气怎么样")
rprint(response)
2. 工具描述的各部分详解
了解:convert_to_openai_tool
执行 model.bind_tools(get_weather) ,底层最终会调用 convert_to_openai_tool 生成工具描述。
jupyter
## 2、工具描述的各部分详解
### 2.1 了解convert_to_openai_tool
执行 model.bind_tools([get_weather]) ,底层最终会调用 convert_to_openai_tool 生成工具描述。所以我们可以直接调用后者查看解析后的工具描述。
from langchain_core.utils.function_calling import convert_to_openai_tool
def get_weather(city: str):
return f"{city}天气晴朗~~"
rprint(convert_to_openai_tool(get_weather))
上面示例输出如下
bash
{
'type': 'function',
'function': {
'name': 'get_weather',
'description': '',
'parameters': {
'properties': {
'city': {
'type': 'string'
}
},
'required': ['city'],
'type': 'object'
}
}
}
问题:为什么不使用@tool装饰器修饰的函数,也可以理解为工具呢?
查看 convert_to_openai_tool 底层源码:
bash
elif isinstance(function, langchain_core.tools.base.BaseTool):
oai_function = cast("dict", _format_tool_to_openai_function(function))
elif callable(function): # 上面示例走这个elif
oai_function = cast(
"dict", _convert_python_function_to_openai_function(function)
)
description说明
jupyter
### 2.2 description说明
from langchain_core.utils.function_calling import convert_to_openai_tool
def get_weather(city: str):
"""
查询城市的天气
"""
return f"{city}天气晴朗~~"
rprint(convert_to_openai_tool(get_weather))
参数说明
这里的 docstring 必须遵循 Google 风格 。
- Google 风格 docstring 说明:google.github.io/styleguide/...
- Google 风格 docstring 示例:www.sphinx-doc.org/en/master/u...
jupyter
### 2.3 参数说明
from langchain_core.utils.function_calling import convert_to_openai_tool
# 注意函数功能描述和入参描述之间必须有一个空行
# 入参描述必须是 Args,不是 args
# 错误示例:city: 具体的城市 中间不能是中文冒号
def get_weather(city: str):
"""
查询城市的天气
Args:
city: 具体的城市
Returns:
返回城市的天气
"""
return f"{city}天气晴朗~~"
rprint(convert_to_openai_tool(get_weather))
AI 依赖 docstring 来理解工具。
python
# ❌ 不好:太模糊
@tool
def tool1(x: str) -> str:
"""做一些事情"""
...
python
# ✅ 好:清晰明确
@tool
def search_products(query: str) -> str:
"""
在产品数据库中搜索产品
Args:
query: 搜索关键词,如"笔记本电脑"、"手机"
Returns:
产品列表的 JSON 字符串
"""
...
参数类型说明
jupyter
### 2.4 参数类型说明
举例1:正确的
from langchain_core.utils.function_calling import convert_to_openai_tool
# def get_weather(city: str):
def get_weather(city):
"""
查询城市的天气
"""
return f"{city}天气晴朗~~"
rprint(convert_to_openai_tool(get_weather))
举例2:如下的代码运行会报错
要求:如果在docstring中声明了参数的描述,则必须在函数声明处指明参数的类型
from langchain_core.utils.function_calling import convert_to_openai_tool
# def get_weather(city: str):
def get_weather(city):
"""
查询城市的天气
Args:
city: 具体的城市
"""
return f"{city}天气晴朗~~"
rprint(convert_to_openai_tool(get_weather))
参数默认值说明
jupyter
### 2.5 参数默认值说明
一旦参数设置了默认值,则打印的结果中的required字段中就不再包含此参数
举例1:
from langchain_core.utils.function_calling import convert_to_openai_tool
# def get_weather(city):
def get_weather(city: str = "beijing"):
"""
查询城市的天气
Args:
city: 具体的城市
"""
return f"{city}天气晴朗~~"
rprint(convert_to_openai_tool(get_weather))
举例2:
from langchain_core.utils.function_calling import convert_to_openai_tool
# def get_weather(city: str = "beijing"):
def get_weather(dt: str, city: str = "beijing"):
"""
查询城市的天气
Args:
city: 具体的城市
dt: 时间
"""
return f"{city}天气晴朗~~"
rprint(convert_to_openai_tool(get_weather))
27. 工具的定义方式2:使用@tool装饰器(推荐)
使用 @tool 装饰器修饰,可以自动将普通 Python 函数转化为智能体可调用的工具。
此方式 最直接 ,代码量极少
1. 自定义工具描述:description
jupyter
# 使用@tool装饰器定义工具
## 1、自定义工具描述:description
举例1:
函数使用@tool装饰器修饰以后,就是一个可以被模型识别的工具了
如下的程序报错了,因为:
在没有提供description参数的情况下,要求函数必须提供docstring
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool
@tool
def get_weather(city: str):
return f"{city}天气晴朗"
rprint(convert_to_openai_tool(get_weather))
修改为:
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool
@tool
def get_weather(city: str):
"""获取城市的天气"""
return f"{city}天气晴朗"
rprint(convert_to_openai_tool(get_weather))
举例2:使用description参数
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool
@tool(description="获取具体城市的天气情况")
def get_weather(city: str):
return f"{city}天气晴朗"
rprint(convert_to_openai_tool(get_weather))
举例3:
在同时声明了description和docstring的情况下,description的优先级更高
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool
@tool(description="获取具体城市的天气情况")
def get_weather(city: str):
"""获取城市的天气"""
return f"{city}天气晴朗"
rprint(convert_to_openai_tool(get_weather))
举例4:
当我们没有向 @tool 传递 description 参数时,默认情况下, tool 会将 docstring 整体视为description
不使用 @tool 装饰器时,docstring不合法会被视为普通文本,作为 description ,但如果使用了 @tool 时 docstring 不合法,将会抛出异常
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool
# @tool(description="获取具体城市的天气情况")
# @tool
@tool(parse_docstring=True)
def get_weather(city: str):
"""
获取城市的天气
Args:
city: 城市
"""
return f"{city}天气晴朗"
rprint(convert_to_openai_tool(get_weather))
举例5:
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool
# @tool(parse_docstring=True)
@tool(parse_docstring=True, description="获取具体城市的天气")
def get_weather(city: str):
"""
获取城市的天气
Args:
city: 城市
"""
return f"{city}天气晴朗"
rprint(convert_to_openai_tool(get_weather))
2. 更改工具名称:name_or_callable
jupyter
## 2、更改工具名称:name_or_callable
开发中,习惯使用函数名作为工具名称,不推荐自定义工具名称。
举例:
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool
# @tool(parse_docstring=True)
# @tool(parse_docstring=True, description="获取具体城市的天气")
@tool(parse_docstring=True, name_or_callable="getWeather")
def get_weather(city: str):
"""
获取城市的天气
Args:
city: 城市
"""
return f"{city}天气晴朗"
rprint(convert_to_openai_tool(get_weather))
说明:不要使用config或runtime作为参数名,这些是LangChain内部保留的。
3. 自定义args_schema
1. 使用Pydantic模型定义
jupyter
## 3、自定义args_schema
### 3.1 使用Pydantic模型定义
举例1:
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool
from pydantic import BaseModel
class WeatherInput(BaseModel):
city: str
# @tool(parse_docstring=True, name_or_callable="getWeather")
# @tool(parse_docstring=True)
@tool(args_schema=WeatherInput)
def get_weather(city: str):
# def get_weather(city: int):
"""
获取城市的天气
"""
return f"{city}天气晴朗"
rprint(convert_to_openai_tool(get_weather))
举例2:
from langchain_core.utils.function_calling import convert_to_openai_tool
from rich import print as rprint
from langchain_core.tools import tool
from pydantic import BaseModel, Field
class WeatherInput(BaseModel):
# city: str
city: str = Field(
description="具体的城市",
default="北京"
)
@tool(args_schema=WeatherInput)
def get_weather(city: str):
"""
获取城市的天气
"""
return f"{city}天气晴朗"
rprint(convert_to_openai_tool(get_weather))
举例3:
from typing import Literal
class WeatherInput(BaseModel):
# city: str
city: str = Field(
description="具体的城市",
default="北京"
)
unit: Literal["celsius", "fahrenheit"]
# include_forecast: bool = False
include_forecast: bool = Field(
default=False,
description="是否包含未来五天的天气预报"
)
@tool(args_schema=WeatherInput)
# def get_weather(city: str):
# def get_weather(city: str, unit: Literal["celsius", "fahrenheit"] = "celsius"):
# def get_weather(city: str, unit: str = "celsius"):
def get_weather(city: str, unit: str = "celsius", include_forecast: bool = True):
"""
获取城市的天气
"""
return f"{city}天气晴朗"
rprint(convert_to_openai_tool(get_weather))
2. 使用Json Schema定义
在 LangChain 中,还可以直接使用 JSON Schema 字典 来定义工具的参数模式。这种方式提供了极大的灵活性。
因为工具参数模式可以基于数据库配置或用户输入在 运行时动态生成 ,所以这种方式特别适合参数结构需要动态生成的场景。
jupyter
### 3.2 使用Json Schema定义
举例:
json_schema = {
'type': 'function',
'function': {
'name': 'get_weather',
'description': '获取城市的天气',
'parameters': {
'properties': {
'city': {'default': '北京', 'description': '具体的城市111', 'type': 'string'},
'unit': {'enum': ['celsius', 'fahrenheit'], 'type': 'string'},
'include_forecast': {
'default': False,
'description': '是否包含未来五天的天气预报111',
'type': 'boolean'
}
},
'required': ['unit'],
'type': 'object'
}
}
}
# @tool(args_schema=WeatherInput)
@tool(args_schema=json_schema)
def get_weather(city: str, unit: str = "celsius", include_forecast: bool = True):
"""
获取城市的天气
"""
return f"{city}天气晴朗"
28. 工具的应用案例
1. 使用args_schema
jupyter
# 工具的应用案例
## 1、使用args_schema
举例
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
# 从.env文件中加载环境变量
load_dotenv(override=True)
CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL
)
from pydantic import BaseModel, Field
from langchain_core.utils.function_calling import convert_to_openai_tool
from langchain_core.tools.convert import tool
# 定义工具
class WeatherSchema(BaseModel):
city: str = Field(
default="北京",
description="具体的城市名称"
)
if_forecast: bool = Field(
default=False,
description="是否包含明日天气"
)
# @tool
@tool("get_weather_and_forecast", description="查询当日的天气,可以包含明天的天气预报", args_schema=WeatherSchema)
def get_weather(city: str, if_forecast: bool):
res = f"{city}今天天气不错"
if if_forecast:
# res += '\n明天天气也不错'
res += '\n明天下雨'
return res
# print(convert_to_openai_tool(get_weather))
from langchain_core.messages import HumanMessage
# 1、将工具绑定到模型上
model_with_tools = model.bind_tools([get_weather])
# 2、维护一个消息列表
messages = [
HumanMessage('今天杭州的天气怎么样?明天呢?')
]
# 3、调用模型,得到响应:AIMessage
# model_with_tools.invoke("")
response = model_with_tools.invoke(messages)
messages.append(response)
# 4、获取响应中的tool_calls字段信息
tool_calls = response.tool_calls
for tool_call in tool_calls:
# if tool_call["name"] == "get_weather":
if tool_call["name"] == "get_weather_and_forecast":
# 5、调用工具(因为大模型不能直接调用工具,所以此时我们主动让工具调用执行)
# 调用完,返回ToolMessage的实例
tool_message = get_weather.invoke(tool_call)
messages.append(tool_message)
# 6、调用模型,得到AIMessage
final_response = model.invoke(messages)
# 7、添加到消息列表中
messages.append(final_response)
# 8、遍历消息列表
for msg in messages:
msg.pretty_print()
2. 撰写docstring
jupyter
## 2、撰写docstring
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
# 从.env文件中加载环境变量
load_dotenv(override=True)
CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL
)
from pydantic import BaseModel, Field
from langchain_core.utils.function_calling import convert_to_openai_tool
from langchain_core.tools.convert import tool
# 定义工具
# class WeatherSchema(BaseModel):
# city: str = Field(
# default="北京",
# description="具体的城市名称"
# )
# if_forecast: bool = Field(
# default=False,
# description="是否包含明日天气"
# )
# @tool("get_weather_and_forecast", description="查询当日的天气,可以包含明天的天气预报", args_schema=WeatherSchema)
# @tool("get_weather_and_forecast", description="")
@tool("get_weather_and_forecast", parse_docstring=True)
# def get_weather(city: str, if_forecast: bool):
# def get_weather(city: str = "北京", if_forecast: bool):
def get_weather(city: str = "北京", if_forecast: bool = False):
"""
查询当日的天气,可以包含明天的天气预报
Args:
city: 城市名称,
if_forecast: 是否包含明天的天气
"""
res = f"{city}今天天气不错"
if if_forecast:
res += '\n明天下雨'
return res
print(convert_to_openai_tool(get_weather))
from langchain_core.messages import HumanMessage
# 1、将工具绑定到模型上
model_with_tools = model.bind_tools([get_weather])
# 2、维护一个消息列表
messages = [
HumanMessage('今天杭州的天气怎么样?明天呢?')
]
# 3、调用模型,得到响应:AIMessage
# model_with_tools.invoke("")
response = model_with_tools.invoke(messages)
messages.append(response)
# 4、获取响应中的tool_calls字段信息
tool_calls = response.tool_calls
for tool_call in tool_calls:
# if tool_call["name"] == "get_weather":
if tool_call["name"] == "get_weather_and_forecast":
# 5、调用工具(因为大模型不能直接调用工具,所以此时我们主动让工具调用执行)
# 调用完,返回ToolMessage的实例
tool_message = get_weather.invoke(tool_call)
messages.append(tool_message)
# 6、调用模型,得到AIMessage
final_response = model.invoke(messages)
# 7、添加到消息列表中
messages.append(final_response)
# 8、遍历消息列表
for msg in messages:
msg.pretty_print()
3. 多工具调用
jupyter
## 3、多工具调用
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from rich import print as rprint
# 1.定义工具
# 定义股票查询工具
@tool(parse_docstring=True)
def get_stock_price(company: str, timeframe: str = "today") -> str:
"""
获取指定公司的股票价格信息
Args:
company: 公司名称(如:苹果公司, 微软公司, 谷歌公司)
timeframe: 时间范围(today-今日, week-本周, month-本月)
"""
# 模拟股票数据
mock_data = {
"苹果公司": {"today": 185.20, "week": 183.50, "month": 180.75},
"微软公司": {"today": 415.86, "week": 412.30, "month": 405.42},
"谷歌公司": {"today": 15.42, "week": 15.20, "month": 14.85}
}
if company in mock_data:
price = mock_data[company].get(timeframe, "未知时间范围")
return f"{company} {timeframe}价格: {price}美元"
else:
return f"未找到股票代码 {company} 的数据"
# 定义新闻搜索工具
@tool(parse_docstring=True)
def search_news(company: str) -> str:
"""
搜索指定公司的财经新闻
Args:
company: 公司名称
Returns:
公司的财经新闻,每个新闻占一行
"""
# 模拟新闻数据
mock_news = {
"苹果公司": [
"苹果发布新款iPhone,股价上涨3%",
"苹果与欧盟达成反垄断和解协议",
"苹果将在印度扩大生产规模"
],
"微软公司": [
"微软Azure云业务季度增长超预期",
"微软完成对Nuance的收购",
"微软推出新一代AI助手Copilot"
],
"谷歌公司": [
"谷歌发布新AI模型,性能提升20%",
"谷歌与OpenAI合作,开发新的AI助手",
"谷歌在欧洲展开AI研究项目"
]
}
news_list = mock_news.get(company, [f"未找到{company}的相关新闻"])
return "\n".join(news_list)
# rprint(convert_to_openai_tool(search_news))
# 2.初始化模型并绑定工具
tools = [get_stock_price, search_news]
model_with_tools = model.bind_tools(tools)
message_list = []
human_message = HumanMessage(content="苹果公司今天的股价是多少?最近有什么新闻?")
# human_message = HumanMessage(content="比较一下微软和苹果的股价")
# human_message = HumanMessage(content="腾讯最近有什么重大新闻?")
# human_message = HumanMessage(content="海水为什么是咸的?")
message_list.append(human_message)
# 3.工具调用
while True:
response = model_with_tools.invoke(message_list)
# rprint(response)
# break
# 将返回的AIMessage添加到消息列表
message_list.append(response)
# 如果模型不需要调用工具,直接退出循环
if not response.tool_calls:
print("没有工具调用,直接返回答案")
break
# 如果有调用工具,处理工具调用响应
# 4.开发者根据模型的响应,调用工具并获取结果
for tool_call in response.tool_calls:
if tool_call["name"] == "get_stock_price":
stock_result = get_stock_price.invoke(tool_call)
print("stock_result", stock_result)
message_list.append(stock_result)
if tool_call["name"] == "search_news":
news_result = search_news.invoke(tool_call)
print("news_result", news_result)
message_list.append(news_result)
# print("response", response)
# print(response.content)
for msg in message_list:
msg.pretty_print()
4. 多工具调用
jupyter
## 4、多工具调用
from langchain.tools import tool
from langchain.messages import HumanMessage
@tool(parse_docstring=True)
def get_weather(city: str) -> str:
"""
获取当日天气
Args:
city: 城市名称
"""
return f'{city}当天晴朗'
@tool(parse_docstring=True)
def get_news() -> str:
"""
获取当日新闻
"""
return "近期,受全球储蓄芯片短缺等多重因素影响,多地回收商称废旧手机回收市场迎来"火热潮",回收价格普遍上涨,旧手机成"香饽饽"。"
model_with_tools = model.bind_tools([get_weather, get_news])
messages = [
HumanMessage("今天杭州天气如何?今天新闻是什么?别瞎编")
]
response = model_with_tools.invoke(messages)
response.pretty_print()
messages.append(response)
for tool_call in response.tool_calls:
if tool_call["name"] == "get_weather":
tool_msg = get_weather.invoke(tool_call)
print(tool_msg)
messages.append(tool_msg)
elif tool_call["name"] == "get_news":
tool_msg = get_news.invoke(tool_call)
print(tool_msg)
messages.append(tool_msg)
else:
raise Exception("不存在的工具")
final_response = model.invoke(messages)
messages.append(final_response)
for msg in messages:
msg.pretty_print()
29. 拓展:强制实用工具(了解)
bind_tools 可以传递参数 tool_choice ,用于控制是否强制使用工具。
该字段最终会作为 payload 的 tool_choice 字段传递给模型,OpenAI和Deepseek的官方API服务对于 tool_choice 的取值做了相同的规定。
- none :模型不会调用任何工具。
- auto : 默认值 ,模型可以自主决定不调用或调用任意数量的工具。
- required :模型必须调用工具,数量不限。
- 此外, tool_choice 还支持传递 any ,等价于 required 。
jupyter
# tool_choice的使用
## 1、tool_choice的取值为none
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
# 从.env文件中加载环境变量
load_dotenv(override=True)
CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL
)
from langchain.tools import tool
from langchain.messages import HumanMessage
from rich import print as rprint
@tool(parse_docstring=True)
def get_weather(city: str) -> str:
"""
获取当日天气
Args:
city: 城市名称
"""
return f'{city}当天晴朗'
model_with_tools = model.bind_tools([get_weather], tool_choice="none")
response = model_with_tools.invoke("北京今天的天气如何?")
rprint(response)
## 2、tool_choice的取值为auto
from langchain.tools import tool
from langchain.messages import HumanMessage
from rich import print as rprint
@tool(parse_docstring=True)
def get_weather(city: str) -> str:
"""
获取当日天气
Args:
city: 城市名称
"""
return f'{city}当天晴朗'
# model_with_tools = model.bind_tools([get_weather], tool_choice="none")
model_with_tools = model.bind_tools([get_weather], tool_choice="auto")
# response = model_with_tools.invoke("北京今天的天气如何?")
response = model_with_tools.invoke("2 + 3 = ?")
rprint(response)
## 3、tool_choice取值为required
from langchain.tools import tool
from langchain.messages import HumanMessage
from rich import print as rprint
@tool(parse_docstring=True)
def get_weather(city: str) -> str:
"""
获取当日天气
Args:
city: 城市名称
"""
return f'{city}当天晴朗'
# model_with_tools = model.bind_tools([get_weather], tool_choice="auto")
model_with_tools = model.bind_tools([get_weather], tool_choice="required")
response = model_with_tools.invoke("北京今天的天气如何?")
rprint(response)
from langchain.tools import tool
from langchain.messages import HumanMessage
from rich import print as rprint
@tool(parse_docstring=True)
def get_weather(city: str) -> str:
"""
获取当日天气
Args:
city: 城市名称
"""
return f'{city}当天晴朗'
model_with_tools = model.bind_tools([get_weather], tool_choice="required")
# response = model_with_tools.invoke("北京今天的天气如何?")
response = model_with_tools.invoke("2 + 3 = ?")
rprint(response)
## 4、强制调用指定的工具
from langchain.tools import tool
from langchain.messages import HumanMessage
@tool(parse_docstring=True)
def get_weather1(city: str) -> str:
"""
获取当日天气
Args:
city: 城市名称
"""
return f'{city}当天晴朗'
@tool(parse_docstring=True)
def get_weather2(city: str) -> str:
"""
获取当日天气
Args:
city: 城市名称
"""
return f'{city}当天晴朗'
# model.bind_tools([get_weather1, get_weather2], tool_choice="get_weather2")
model_with_tools = model.bind_tools([get_weather1, get_weather2], tool_choice="get_weather2")
response = model_with_tools.invoke("2 + 3 = ?")
rprint(response)
from langchain.tools import tool
from langchain.messages import HumanMessage
@tool(parse_docstring=True)
def get_weather1(city: str) -> str:
"""
获取当日天气
Args:
city: 城市名称
"""
return f'{city}当天晴朗'
@tool(parse_docstring=True)
def get_weather2(city: str) -> str:
"""
获取当日天气
Args:
city: 城市名称
"""
return f'{city}当天晴朗'
# model_with_tools = model.bind_tools([get_weather1, get_weather2], tool_choice="get_weather2")
model_with_tools = model.bind_tools([get_weather1, get_weather2], tool_choice="get_weather1")
# response = model_with_tools.invoke("2 + 3 = ?")
response = model_with_tools.invoke("北京今天的天气如何?")
rprint(response)
30. 实践经验总结
1. 清晰的描述
python
# ✅ 好
@tool(parse_docstring=True)
def search_flights(origin: str, destination: str, date: str) -> str:
"""
搜索航班信息
Args:
origin: 出发城市,如"北京"
destination: 目的地城市,如"上海"
date: 出发日期,格式 YYYY-MM-DD
Returns:
可用航班的 JSON 列表
"""
2. 功能单一
python
# ❌ 不好:一个工具做太多事
@tool
def do_everything(action: str, data: str) -> str:
"""做各种事情"""
if action == "weather": ...
elif action == "calculate": ...
elif action == "search": ...
# ✅ 好:每个工具做一件事
@tool
def get_weather(city: str) -> str:
"""获取天气"""
...
@tool
def calculator(operation: str, a: float, b: float) -> str:
"""计算"""
...
3. 如何处理工具失败?
三层防护:
第1层:工具内部处理
python
@tool
def divide(a: float, b: float) -> str:
"""
除法计算
Args:
a: 被除数
b: 除数
"""
try:
if b == 0:
return "错误:除数不能为零"
result = a / b
return f"{a} / {b} = {result}"
except Exception as e:
return f"计算错误:{e}"
第2层:Agent 级重试(使用 prompt)
ini
agent = create_agent(
model=model,
tools=[...],
prompt="如果工具失败,尝试使用其他方法解决问题。"
)
第3层:调用级重试
@retry 就像是一个 容错保险 。
python
from tenacity import retry, stop_after_attempt
# 1. 配置重试规则:如果失败,最多尝试 3 次(即第 1 次正常调用 + 2 次重试)
@retry(stop=stop_after_attempt(3))
def call_agent(question):
# 2. 核心业务逻辑:调用 LangChain 的 Agent
return agent.invoke({"messages": [{"role": "user", "content": question}]})
① 你调用 call_agent("你好") 。
② 程序进入函数,执行 agent.invoke(...) 。
③ 如果执行成功:正常返回结果, @retry 什么都不做。
④ 如果执行失败(报错): @retry 会拦截这个错误,不让程序直接崩溃。它会默默地帮你再次触发 agent.invoke(...) 。
⑤ 如果连续 3 次都报错:它终于放弃了,把第 3 次的报错真正抛出来,程序此时才会报错中止。
4. 返回字符串
python
# ✅ 好:返回字符串
@tool
def get_user_info(user_id: str) -> str:
"""获取用户信息"""
user = {"id": user_id, "name": "张三"}
return json.dumps(user, ensure_ascii=False) # 转成 JSON 字符串
# ❌ 不好:返回字典(某些情况可能有问题)
@tool
def get_user_info(user_id: str) -> dict:
"""获取用户信息"""
return {"id": user_id, "name": "张三"}
强烈建议工具返回字符串(str)。因为:
1)大模型(LLM)的本质只吃"文本"
2)乱码与格式问题
如果你返回一个包含中文的字典 {"name": "张三"} ,LangChain 在强制将其转换为字符串时,默认可能会采用 Unicode 编码,变成 {"name": "\u5f20\u4e09"} 。
大模型虽然能理解 Unicode,但极易受到干扰。直接看到中文 张三 的大模型,和看到 \u5f20\u4e09 的大模型,其输出的稳定性和准确率是有差距的。
5. 选择同步 vs 异步
- 同步工具 :简单场景,CPU 密集型任务
- 异步工具 :IO 密集型(API 调用、数据库、文件操作)
31. 结构化输出概述
1. 什么是结构化输出
要求模型最终返回一个符合预定义结构的数据对象,例如固定字段的JSON、Pydantic 模型、TypedDict,而不再是无格式的自然语言文本。
它的核心目标是把" 自然语言回答 "变成" 程序可以稳定消费的数据 "。
例如,不是让模型输出:
盗梦空间在2010年上映,导演是克里斯托弗·诺兰,评分9.3。
而是让它输出成类似这样的结构:
python
{
"title": "盗梦空间",
"year": 2010,
"director": "克里斯托弗·诺兰",
"rating": 9.3
}
这样做的价值主要有三点:
- 更容易被代码处理:下游系统可以直接读字段,而不是再从自然语言里做解析。
- 结果更稳定:减少"模型说法变了但意思差不多"导致的解析失败。
- 更适合工程化:适用于表单抽取、分类、路由、调用工具参数生成、工作流状态传递等场景。
2. 传统方式 vs 结构化输出
1、传统的几种方式(繁琐、不推荐)
python
# 1. 提示词要求JSON
prompt = "以JSON格式返回:{name, age, occupation}"
response = model.invoke(prompt)
python
# 2. 手动解析
import json
data = json.loads(response.content)
python
# 3. 手动验证类型
if not isinstance(data['age'], int):
raise ValueError("age must be int")
python
# 4. 手动创建对象
person = Person(**data)
2、结构化输出(简洁)
python
# 一步到位
structured_llm = model.with_structured_output(Person)
person = structured_llm.invoke("张三是一名 30 岁的软件工程师")
# ✅ 自动解析、验证、创建对象
为什么第2种结构化输出机制这么受欢迎?
有了 Pydantic 等结构化方案结合 .with_structured_output() 之后:
- Prompt 变干净了: 字段的 description 直接充当了 Prompt 的一部分。
- 类型安全: 编辑器能自动补全,代码运行前就能做类型检查。
- 极其稳定: 依托大模型厂商底层的 JSON 模式,输出错误率降到了极低。
3. 结构化输出模式
- Pydantic(字段校验、描述、嵌套结构,功能最丰富)
- TypedDict(轻量类型约束)
- JSON Schema(与前后端/跨语言接口最通用)
- dataclass
只有 Pydantic 返回的是Schema类实例,其余三种方式返回的都是 字典 ;也只有 Pydantic 在类型不 匹配时会抛出异常。
问题:现在所有模型都支持"本章要讲解的结构化输出方式"吗?
大部分现代模型支持(通过函数调用):
- ✅ OpenAI (gpt-4, gpt-3.5-turbo)
- ✅ Anthropic (claude-3)
- ✅ Groq (llama-3)
- ❌ 某些旧模型不支持
如果不支持,LangChain 会回退到提示词 + JSON 解析。
32. 四种模式的使用
1. Pydantic
它通过在运行时强制执行类型提示,确保数据的正确性和一致性,是 生产场景首选 。
1. 基本使用
- 所有结构化输出的数据模型都必须继承 BaseModel
- 使用 类型提示 。Pydantic 支持丰富的字段类型:str 、int、float、Listxxx、Optionalxxx等
- 使用 Field() 添加字段默认值和描述,帮助 LLM 理解字段含义
jupyter
# Pydantic格式的使用
## 1、基本使用
举例:
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
# 从.env文件中加载环境变量
load_dotenv(override=True)
CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")
model = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL
)
from pydantic import BaseModel, Field
class Person(BaseModel):
"""人物信息"""
name: str = Field(
description="姓名"
)
age: int = Field(
description="年龄"
)
occupation: str = Field(
description="职业"
)
# 创建结构化输出的大语言模型
structured_model = model.with_structured_output(Person)
# result = model.invoke("张三是一名30岁的软件工程师")
result = structured_model.invoke("张三是一名30岁的软件工程师")
print(result)
print(type(result)) # Person
print(f"姓名: {result.name}")
print(f"年龄: {result.age}")
print(f"职业: {result.occupation}")
举例2:
class MovieModel(BaseModel):
"""电影的详细信息"""
title: str = Field(
description="电影标题"
)
year: int= Field(
description="发行年份"
)
actor: str= Field(
description="电影导演"
)
rating: float= Field(
description="电影评分,满分十分"
)
structured_model = model.with_structured_output(MovieModel)
# model.invoke("给出电影盗梦空间的信息")
result = structured_model.invoke("给出电影盗梦空间的信息")
print(result)
举例3:
from pydantic import BaseModel, Field
# 定义输出结构
class SentimentAnalysis(BaseModel):
"""情感分析结果"""
sentiment: str = Field(description="情感倾向:positive/negative/neutral")
confidence: float = Field(description="置信度,0-1之间")
keywords: list[str] = Field(description="关键词列表")
# ✅ v1.x:使用 with_structured_output
structured_model = model.with_structured_output(SentimentAnalysis)
# 调用
text = "这个课程内容很实用,学到了很多知识,强烈推荐!"
result = structured_model.invoke(
f"分析以下文本的情感:\n{text}"
)
print(f"类型: {type(result)}") # <class 'SentimentAnalysis'>
print(f"情感: {result.sentiment}")
print(f"置信度: {result.confidence}")
print(f"关键词: {result.keywords}")
2. 高级特性
情况1:可选字段
jupyter
## 2、高级特性
### 2.1 情况1:可选字段
举例
from pydantic import BaseModel, Field
class Person(BaseModel):
"""人物信息"""
name: str = Field(
description="姓名"
)
age: int = Field(
description="年龄"
)
occupation: str = Field(
description="职业"
)
# 创建结构化输出的大语言模型
structured_model = model.with_structured_output(Person)
# result = structured_model.invoke("张三是一名30岁的软件工程师")
result = structured_model.invoke("张三是一名软件工程师")
print(result) # age: 0
print(type(result))
作为对比
from pydantic import BaseModel, Field
from typing import Optional
class Person(BaseModel):
"""人物信息"""
name: str = Field(
description="姓名"
)
# age: int = Field(
age: Optional[int] = Field(
description="年龄"
)
occupation: str = Field(
description="职业"
)
# 创建结构化输出的大语言模型
structured_model = model.with_structured_output(Person)
result = structured_model.invoke("张三是一名软件工程师")
print(result) # age: None
print(type(result))
情况2:默认值
jupyter
### 2.2 情况2:默认值
不同的模型供应商,对于此字段的支持是不同的。比如:CloseAI平台的gpt-5.4-mini模型就不支持此字段,而OpenRouter平台的gpt-5.4-mini模型就支持此字段
CloseAI平台的模型:
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
# 从.env文件中加载环境变量
load_dotenv(override=True)
CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")
model_with_closeai = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL
)
OpenRouter平台的模型:(1、需要充值 2、需要魔法)
from langchain_openrouter import ChatOpenRouter
from dotenv import load_dotenv
import os
load_dotenv(override=True)
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
OPENROUTER_API_BASE = os.getenv("OPENROUTER_API_BASE")
model_with_openrouter = ChatOpenRouter(
model="openai/gpt-5.4-mini",
api_key=OPENROUTER_API_KEY,
base_url=OPENROUTER_API_BASE,
)
举例1:使用CloseAI平台的模型
from pydantic import BaseModel, Field
# from typing import Optional
class Person(BaseModel):
"""人物信息"""
name: str = Field(
description="姓名"
)
# age: Optional[int] = Field(
age: int = Field(
# 10,
default=10,
description="年龄"
# description="年龄(默认值为20)"
)
occupation: str = Field(
description="职业"
)
# 创建结构化输出的大语言模型
# structured_model = model.with_structured_output(Person)
structured_model = model_with_closeai.with_structured_output(Person)
result = structured_model.invoke("张三是一名软件工程师")
print(result) # age: 0 -> age: 20 -> age: 0
print(type(result))
作为对比:使用OpenRouter平台的模型
from pydantic import BaseModel, Field
class Person(BaseModel):
"""人物信息"""
name: str = Field(
description="姓名"
)
age: int = Field(
default=10,
description="年龄"
)
occupation: str = Field(
description="职业"
)
# 创建结构化输出的大语言模型
# structured_model = model_with_closeai.with_structured_output(Person)
structured_model = model_with_openrouter.with_structured_output(Person)
result = structured_model.invoke("张三是一名软件工程师")
print(result) # age: 10
print(type(result))
举例2:
class Config(BaseModel):
timeout: Optional[int] = Field(30,description="超时时间(单位秒)")
retry: bool = Field(False,description="是否支持重试")
max_attempts: int = Field(6,description="最大重试次数")
# 测试
structured_llm = model_with_closeai.with_structured_output(Config)
structured_llm.invoke("配置要求:支持重试,最多重试5次")
# timeout: None
# 测试
structured_llm = model_with_openrouter.with_structured_output(Config)
structured_llm.invoke("配置要求:支持重试,最多重试5次")
# timeout: 30
举例3:
from typing import Optional
from pydantic import BaseModel, Field
class Product(BaseModel):
"""产品信息"""
name: str = Field(description="产品名称")
price: float = Field(description="价格")
description: Optional[str] = Field(description="产品描述")
stock: int = Field(default=100, description="库存")
# 测试
structured_llm = model_with_openrouter.with_structured_output(Product)
print("\n场景1:完整信息")
result1 = structured_llm.invoke("iPhone 15 售价 5999 元,最新款智能手机,库存 50 台")
print(result1)
print("\n场景2:缺少描述和库存")
result2 = structured_llm.invoke("MacBook Pro 售价 12999 元")
print(result2) # description: None
2026.08.05 20:43
情况3:枚举类型
python
from enum import Enum
class Priority(str, Enum):
LOW = "低"
MEDIUM = "中"
HIGH = "高"
class Task(BaseModel):
title: str
priority: Priority # 只能是 LOW/MEDIUM/HIGH
python
from enum import Enum
class Status(str, Enum):
ACTIVE = "激活"
INACTIVE = "未激活"
class User(BaseModel):
status: Status # 只能是 ACTIVE 或 INACTIVE
jupyter
### 2.3 情况3:枚举类型
举例:
方式1:
from enum import Enum
# 定义枚举类型
class Priority(str, Enum):
LOW = "低"
MEDIUM = "中"
HIGH = "高"
class CustomerInfo(BaseModel):
"""客户信息"""
name: str = Field(description="客户姓名")
phone: str = Field(description="电话号码")
email: Optional[str] = Field(description="邮箱")
issue: str = Field(description="问题描述")
urgency: Priority = Field(description="紧急程度")
# 测试
# structured_llm = model_with_openrouter.with_structured_output(CustomerInfo)
structured_llm = model.with_structured_output(CustomerInfo)
conversation = """
客服: 您好,请问有什么可以帮助您?
客户: 我是王小明,电话 138-1234-5678,我的订单一直没发货,很着急!
客服: 好的,我帮您查一下
"""
result = structured_llm.invoke(f"从以下客服对话中提取客户信息:\n{conversation}")
print(result)
# print("\n提取结果:")
# print(f" 客户: {result.name}")
# print(f" 电话: {result.phone}")
# print(f" 邮箱: {result.email or '未提供'}")
# print(f" 问题: {result.issue}")
# print(f" 紧急程度: {result.urgency.value}")
方式2:
from typing import Literal
# from enum import Enum
# 定义枚举类型
# class Priority(str, Enum):
# LOW = "低"
# MEDIUM = "中"
# HIGH = "高"
class CustomerInfo(BaseModel):
"""客户信息"""
name: str = Field(description="客户姓名")
phone: str = Field(description="电话号码")
email: Optional[str] = Field(description="邮箱")
issue: str = Field(description="问题描述")
# urgency: Priority = Field(description="紧急程度")
urgency: Literal["低", "中", "高"] = Field(description="紧急程度")
# 测试
# structured_llm = model_with_openrouter.with_structured_output(CustomerInfo)
structured_llm = model.with_structured_output(CustomerInfo)
conversation = """
客服: 您好,请问有什么可以帮助您?
客户: 我是王小明,电话 138-1234-5678,我的订单一直没发货,很着急!
客服: 好的,我帮您查一下
"""
result = structured_llm.invoke(f"从以下客服对话中提取客户信息:\n{conversation}")
print(result)
情况4:列表提取
jupyter
### 2.4 情况4:列表提取
举例1:
from typing import List
class Person(BaseModel):
"""任务信息"""
name: str = Field(descriptions="姓名")
age: int = Field(descriptions="年龄")
class PersonList(BaseModel):
"""人物列表"""
people: List[Person] # 多个Person的对象
# model.with_structured_output(Person)
structured_model = model.with_structured_output(PersonList)
result = structured_model.invoke("张三 30岁,李四 40岁")
print(result)
举例2:
class Review(BaseModel):
"""产品评论"""
product: str
rating: int = Field(description="评分 1-5")
pros: List[str] = Field(description="优点列表")
cons: List[str] = Field(description="缺点列表")
structured_llm = model.with_structured_output(Review)
review = structured_llm.invoke("""
iPhone 17 很棒!摄像头强大,手感好。但是价格贵,没有充电器。4分。
""")
print(review)
举例3:
class Invoice(BaseModel):
"""发票信息"""
invoice_number: str = Field(description="发票号")
date: str = Field(description="日期")
total_amount: float = Field(description="总金额")
items: List[str] = Field(description="商品")
# 测试
structured_llm = model.with_structured_output(Invoice)
invoice_text = """
发票号: INV-2024-001
日期: 2024-01-15
总金额: 1299.00
商品: MacBook Pro, AppleCare+
"""
invoice = structured_llm.invoke(f"提取发票信息:{invoice_text}")
print(invoice)
情况5:嵌套结构
jupyter
### 2.5 情况5:嵌套结构
举例1:
class Address(BaseModel):
"""地点描述"""
city: str = Field(description="城市")
district: str = Field(description="区域")
class Company(BaseModel):
"""公司信息"""
name: str = Field(description="公司名称")
address: Address = Field(description="公司所在地")
structured_model = model.with_structured_output(Company)
# model.invoke("阿里巴巴在杭州的滨江区")
result = structured_model.invoke("阿里巴巴在杭州的滨江区")
print(result)
举例2:
from pydantic import BaseModel, Field
from typing import List
# 1. 定义嵌套的 Pydantic 模型
class Actor(BaseModel):
"""演员信息"""
name: str = Field(description="演员姓名")
role: str = Field(description="饰演的角色")
class Movie(BaseModel):
"""电影信息"""
title: str = Field(description="电影标题")
year: int = Field(description="上映年份")
director: str = Field(description="导演")
cast: List[Actor] = Field(description="演员列表") # 定义列表字段
rating: float = Field(description="评分")
# 2. 初始化模型并绑定输出结构
structured_model = model.with_structured_output(Movie)
# 3. 调用模型,直接获取 Movie 实例
response = structured_model.invoke("请介绍电影《盗梦空间》")
# 4. 访问嵌套数据
print(f"电影名: {response.title}")
print(f"上映年份: {response.year}")
print(f"导演: {response.director}")
print(f"演员列表: {response.cast}")
print(f"评分: {response.rating}")
举例3:
from pydantic import BaseModel
from typing import List
class Aspect(BaseModel):
"""评论维度"""
name: str = Field(description="维度名称,如:质量、价格、服务")
score: int = Field(description="评分,1-5")
comment: str = Field(description="具体评价")
class ProductReview(BaseModel):
"""产品评论分析"""
overall_sentiment: str = Field(description="整体情感:positive/negative/neutral")
overall_score: int = Field(description="综合评分,1-5")
aspects: List[Aspect] = Field(description="各维度评价")
summary: str = Field(description="一句话总结")
# 创建结构化模型
structured_model = model.with_structured_output(ProductReview)
# 测试
review_text = """
这款笔记本电脑性能非常强大,运行大型软件毫无压力。
屏幕色彩鲜艳,看视频很舒服。
不过价格有点贵,而且风扇噪音较大。
客服态度很好,物流也快。
总体来说还是值得购买的。
"""
result = structured_model.invoke(
f"分析以下产品评论:\n{review_text}"
)
print(f"整体情感: {result.overall_sentiment}")
print(f"综合评分: {result.overall_score}/5")
print(f"\n各维度评价:")
for aspect in result.aspects:
print(f" - {aspect.name}: {aspect.score}/5 - {aspect.comment}")
print(f"\n总结: {result.summary}")
说明:LLM 能力有限,复杂嵌套结构可能会出错。所以建议:
- 嵌套层级 ≤ 3 层
python
class Bad(BaseModel):
user: User
company: Company
address: Address
country: Country # 4 层嵌套,容易出错
- 使用清晰的 description
- 必要时拆分成多个调用
情况6:限制情况
jupyter
### 2.6 情况6:限制情况
from pydantic import ValidationError
class User(BaseModel):
name: str = Field(description="姓名", min_length=2, max_length=50)
age: int = Field(description="年龄", le=150) # <= 150
email: str = Field(description="邮箱")
# user1 = User(name="tom", age=20, email="tom@126.com")
# print(user1)
try:
# user2 = User(name="tom", age=200, email="tom@126.com")
user1 = User(name="tom", age=20, email="tom@126.com")
print(f"[OK]{user1}")
except ValidationError as e:
print(f"[FAIL]{e}")
from pydantic import ValidationError
try:
user2 = User(name="tom", age=200, email="tom@126.com") # 运行报错 age太大
# print(user2)
print(f"[OK]{user2}")
except ValidationError as e:
print(f"[FAIL]{e}")
举例1:
使用CloseAI平台的模型
class Product(BaseModel):
"""产品信息(严格验证)"""
name: str = Field(description="产品名称(字符串类型)", min_length=2)
price: float = Field(description="价格,数字类型", gt=0) # >0
stock: int = Field(description="库存,整数类型", ge=0) # >=0
structured_model = model_with_closeai.with_structured_output(Product)
# structured_model.invoke("华为mate 80 promax 价格是-7999,当前库存-100")
result = structured_model.invoke("华为mate 80 promax 价格是7999,当前库存100")
print(result)
# result = structured_model.invoke("华为mate 80 promax 价格是7999,当前库存100")
result = structured_model.invoke("华为mate 80 promax 价格是-7999,当前库存-100")
print(result) # price=7999.0 stock=100
使用OpenRouter平台的模型
class Product(BaseModel):
"""产品信息(严格验证)"""
name: str = Field(description="产品名称(字符串类型)", min_length=2)
price: float = Field(description="价格,数字类型", gt=0) # >0
stock: int = Field(description="库存,整数类型", ge=0) # >=0
# structured_model = model_with_closeai.with_structured_output(Product)
structured_model = model_with_openrouter.with_structured_output(Product)
result = structured_model.invoke("华为mate 80 promax 价格是7999,当前库存100")
print(result)
# result = structured_model.invoke("华为mate 80 promax 价格是7999,当前库存100")
result = structured_model.invoke("华为mate 80 promax 价格是-7999,当前库存-100")
print(result) # price=1.0 stock=0
3. 工作流程图解
2. TypedDict
1. 什么是TypedDict
TypedDict 是 Python 3.8+ 引入的一种类型提示工具,即带有类型声明的字典结构。适合需要快速定义字典结构且无需 Pydantic 重量级功能的场景。
1、普通 dict 没有类型信息
2、TypedDict 可以进一步说明:
- 这个字典应该有哪些字段
- 每个字段的类型是什么
- TypedDict 主要是类型声明,不是运行时强校验器。
2. 基本使用
Annotated 用来在"类型"之外,再附加一些额外信息,即元数据。类似于 Pydantic 的 Field 。
基本形式:
Annotated类型, 附加信息1, 附加信息2, ...
jupyter
## 2、基本使用
举例1:
CloseAI平台的模型
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os
# 从.env文件中加载环境变量
load_dotenv(override=True)
CLOSEAI_API_KEY = os.getenv("CLOSEAI_API_KEY")
CLOSEAI_BASE_URL = os.getenv("CLOSEAI_BASE_URL")
model_with_closeai = init_chat_model(
model="gpt-5.4-mini",
model_provider="openai",
api_key=CLOSEAI_API_KEY,
base_url=CLOSEAI_BASE_URL
)
OpenRouter平台的模型:
from langchain_openrouter import ChatOpenRouter
from dotenv import load_dotenv
import os
load_dotenv(override=True)
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
OPENROUTER_API_BASE = os.getenv("OPENROUTER_API_BASE")
model_with_openrouter = ChatOpenRouter(
model="openai/gpt-5.4-mini",
api_key=OPENROUTER_API_KEY,
base_url=OPENROUTER_API_BASE,
)
from typing_extensions import Annotated
class MoveTypedDict(TypedDict):
"""电影的信息"""
title: Annotated[str, "电影的名称"]
year: Annotated[int, "电影的上映时间,四位数"]
director: Annotated[str, "电影的导演"]
rating: Annotated[float, "电影的评分,满分是10分,可以包含一位小数"]
# structured_model = model_with_closeai.with_structured_output(xx)
structured_model = model_with_closeai.with_structured_output(MoveTypedDict)
response = structured_model.invoke("给我介绍一下电影《星际穿越》")
print(response)
print(type(response)) # dict
举例2:嵌套结构的使用
from typing import List
class Actor(TypedDict):
"""演员的信息"""
name: Annotated[str, "演员的名字"]
role: Annotated[str, "扮演的角色"]
class MoveTypedDict(TypedDict):
"""电影的信息"""
title: Annotated[str, "电影的名称"]
year: Annotated[int, "电影的上映时间,四位数"]
director: Annotated[str, "电影的导演"]
rating: Annotated[float, "电影的评分,满分是10分,可以包含一位小数"]
cast: Annotated[List[Actor], "演员的列表"] # 嵌套列表的结构
structured_model = model_with_closeai.with_structured_output(MoveTypedDict)
response = structured_model.invoke("给我介绍一下电影《星际穿越》")
print(response)
print(type(response)) # dict
举例3:... 的使用(意思是必填)
不同的魔心提供商对应的效果不同
以CloseAI平台为例:(失效了)
# class MoveTypedDict(TypedDict):
class MoveDict(TypedDict):
"""电影的信息"""
# title: Annotated[str, "电影的名称"]
# title: Annotated[str, "电影的名称"]
# year: Annotated[int, "电影的上映时间,四位数"]
# director: Annotated[str, "电影的导演"]
# rating: Annotated[float, "电影的评分,满分是10分,可以包含一位小数"]
title: Annotated[str, ..., "电影的名称"]
title: Annotated[str, ..., "电影的名称"]
year: Annotated[int, ..., "电影的上映时间,四位数"]
director: Annotated[str, ..., "电影的导演"]
rating: Annotated[float, ..., "电影的评分,满分是10分,可以包含一位小数"]
# structured_model = model_with_closeai.with_structured_output(MoveTypedDict)
# response = structured_model.invoke("给我介绍一下电影《星际穿越》")
structured_model = model_with_closeai.with_structured_output(MoveDict)
response = structured_model.invoke("根据这段话抽取盗梦空间的信息,不包含的信息可以留空:盗梦空间在2010年上映,导演是克里斯托弗·诺兰。")
print(response)
以OpenRouter平台为例
# class MoveTypedDict(TypedDict):
class MoveDict(TypedDict):
"""电影的信息"""
title: Annotated[str, ..., "电影的名称"]
title: Annotated[str, ..., "电影的名称"]
year: Annotated[int, ..., "电影的上映时间,四位数"]
director: Annotated[str, ..., "电影的导演"]
rating: Annotated[float, ..., "电影的评分,满分是10分,可以包含一位小数"]
# structured_model = model_with_closeai.with_structured_output(MoveDict)
structured_model = model_with_openrouter.with_structured_output(MoveDict)
response = structured_model.invoke("根据这段话抽取盗梦空间的信息,不包含的信息可以留空:盗梦空间在2010年上映,导演是克里斯托弗·诺兰。")
print(response) # rating: 0
说明:上述代码的 ... 是Python的字面量,等价于 Ellipsis ,可以理解为占位符。下游框架(如 LangChain)可以对 ... 作定制化处理,如LangChain中Annotated的 ... 表示当前字段是必须存在的, 不可省略,