
🚀 欢迎来到我的CSDN博客:Optimistic _ chen
✨ 一名热爱技术与分享的全栈开发者,在这里记录成长,专注分享编程技术与实战经验,助力你的技术成长之路,与你共同进步!
🚀我的专栏推荐:
| 专栏 | 内容特色 | 适合人群 |
|---|---|---|
| 🔥C语言从入门到精通 | 系统讲解基础语法、指针、内存管理、项目实战 | 零基础新手、考研党、复习 |
| 🔥Java基础语法 | 系统解释了基础语法、类与对象、继承 | Java初学者 |
| 🔥Java核心技术 | 面向对象、集合框架、多线程、网络编程、新特性解析 | 有一定语法基础的开发者 |
| 🔥Java EE 进阶实战 | Servlet、JSP、SpringBoot、MyBatis、项目案例拆解 | 想快速入门Java Web开发的同学 |
| 🔥Java数据结构与算法 | 图解数据结构、LeetCode刷题解析、大厂面试算法题 | 面试备战、算法爱好者、计算机专业学生 |
| 🔥Redis系列 | 从数据类型到核心特性解析 | 项目必备 |
🚀我的承诺:
✅ 文章配套代码:每篇技术文章都提供完整的可运行代码示例
✅ 持续更新:专栏内容定期更新,紧跟技术趋势
✅ 答疑交流:欢迎在文章评论区留言讨论,我会及时回复(支持互粉)
🚀 关注我,解锁更多技术干货!
⏳ 每天进步一点点,未来惊艳所有人!✍️ 持续更新中,记得⭐收藏关注⭐不迷路 ✨
📌 标签:#技术博客 #编程学习 #Java #C语言 #算法 #程序员
结构化输出(with_structured_output)
在LangChain中有一种使聊天模型以结构化格式进行响应的技术——结构化输出。 指示模型使用特定的输出结构进行响应,比如:将模型输出存储在数据库中,并确保输出符合数据库模式等等。
这样有一个核心优势:在没有结构化输出之前,模型输出一个字符串格式的AIMessage,这种格式对人类阅读非常方便,但是如果后续我们想要使用这段字符串中的某些信息,就需要去编写复杂的解析代码。而聊天模型的结构化输出方法允许我们先定义一个期望的数据结构,可以要求大模型按照我们期望的格式输出信息。
with_structured_output源码:
def with_structured_output(
self,
schema: builtins.dict[str, Any] | type,
*,
include_raw: bool = False,
**kwargs: Any,
) -> Runnable[LanguageModelInput, builtins.dict[str, Any] | BaseModel]:

返回Pydantic对象
定义输出结构为BaseModel,返回输出格式是Pydantic类。 因为 LangChain 的with_structured_output 内部机制,就是利用 Pydantic 的 BaseModel 来做“双重翻译”——既翻译给机器(LLM API)看,又翻译给你(Python 代码)看。
解释一下Pydantic 的内部流程:
- 当传入Joke(继承自 BaseModel)时,
with_structured_output做的第一件事,调用 Pydantic 的.model_json_schema()方法,把你的类转换成一个标准的 JSON Schema 字典。 - LangChain 会把这个 JSON Schema 塞进 LLM API 的 tools(函数调用)参数(工具调用)或
response_format(JSON 模式)参数中。LLM 看到的只是这个 JSON 结构,它根本不知道 Python 类的存在。 - 当 LLM 按照上述 Schema 返回一个合法的 JSON 字符串时,LLM会调用Pydantic 的
YourModel.model_validate(json_dict)方法进行类型强制转化、校验、实例化。
from typing import Optional
from langchain.chat_models import init_chat_model
from pydantic import Field, BaseModel
model = init_chat_model("deepseek-v4-pro",
model_provider="deepseek",
extra_body={"thinking": {"type": "disabled"}}
)
# 定义输出结构pydantic类
class Joke(BaseModel):
"""给用户讲一个的冷笑话"""
setup: str=Field(description="这个笑话背后的开头")
punchline: str=Field(description="这个笑话背后的含义")
rating: Optional[int] = Field(
default=None,description="1-10分,给这个笑话打分"
)
# 绑定schema,生成支持结构化返回的Runnable实例
model_with_structure=model.with_structured_output(Joke)
#执行
structure_output=model_with_structure.invoke("讲一个关于程序员的笑话")
print(structure_output)
注意:extra_body={"thinking": {"type": "disabled"}}强制将“推理模型(DeepSeek-V4)”降级为“普通文本生成模型。
- 因为LangChain 底层使用的是 openai Python 库来发送 HTTP 请求,thinking是DeepSeek独有的自定义参数,extra_body 是 OpenAI 客户端专门预留的“后门”。它的作用是将里面的字典内容,直接塞进 HTTP 请求体(Request Body)的根节点,**而不经过客户端的参数校验 **。这样 DeepSeek 的后端就能正确读取到 thinking 指令。
- DeepSeek-V4 开启“思考模式”下,会进入思维链模式,在这种模式下API禁止用户强制指定模型必须调用哪个工具(tool_choice)。而LangChain 的为了让模型一定按照你要求的格式输出,LangChain 会强制在请求中加上 tool_choice

返回TypeDict
TypeDict用于字典对像提供精确的、结构化的类型提示,它允许我们指定⼀个字典中应该有哪些键,以及每个键对应的值的类型。
from typing import TypeDict
class User(typeDict):
name:str
age:int
email:str
设置好这样一个类后,我们结构化输出时将输出结果指定为typeDict类,返回一个字典(自定义字典中类型)
from typing import Optional, TypedDict, Annotated
from langchain.chat_models import init_chat_model
from pydantic import Field, BaseModel
model = init_chat_model("deepseek-v4-pro",
model_provider="deepseek",
extra_body={"thinking": {"type": "disabled"}}
)
# 定义输出结构pydantic类
class Joke(TypedDict):
"""给用户讲一个的冷笑话"""
setup: Annotated[str,...,"这个笑话背后的开头"]
punchline: Annotated[str,...,"这个笑话背后的含义"]
rating: Annotated[Optional[int],None,"1-10分,给这个笑话打分"]
# 绑定schema,生成支持结构化返回的Runnable实例
model_with_structure=model.with_structured_output(Joke)
#执行
structure_output=model_with_structure.invoke("讲一个关于程序员的笑话")
print(structure_output)

返回JSON
同样还可以让聊天模型直接返回JSON,只不过为了声明JSON,我们需要定义JSONSchema
from langchain.chat_models import init_chat_model
model = init_chat_model("deepseek-v4-pro",
model_provider="deepseek",
extra_body={"thinking": {"type": "disabled"}}
)
# 定义输出结构JSON Schema
json_schema = {
"title": "joke",
"description": "给⽤⼾讲⼀个笑话。",
"type": "object",
"properties": {
"setup": {
"type": "string",
"description": "这个笑话的开头",
},
"punchline": {
"type": "string",
"description": "这个笑话的妙语",
},
"rating": {
"type": "integer",
"description": "从1到10分,给这个笑话评分",
"default": None,
},
},
"required": ["setup", "punchline"],
}
# 绑定schema,生成支持结构化返回的Runnable实例
model_with_structure=model.with_structured_output(json_schema)
#执行
structure_output=model_with_structure.invoke("讲一个关于程序员的笑话")
print(structure_output)
联合类型父模式
在 Pydantic 中,为联合类型(Union)创建一个带有鉴别器(Discriminator)的父模式(通常是一个包含 Union 字段的模型),其核心好处是:将“手动类型判断”的繁琐工作,转变为由 Pydantic 引擎自动完成的“类型安全路由”
我们可以结合工具来使用联合类型父模式:
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langchain_tavily import TavilySearch
from pydantic import BaseModel, Field
import os
model = init_chat_model("deepseek-v4-pro", model_provider="deepseek",extra_body={"thinking": {"type": "disabled"}})
class Search(BaseModel):
"""结构化搜索结果"""
query: str=Field(description="搜索查询")
findings: str=Field(description="调查结果摘要")
@tool
def web_search(query:str)->str:
"""在网上搜索信息
Args:
query:搜索查询
"""
# 实例化 TavilySearch 工具,可配置参数
api_key = os.getenv("TAVILY_API_KEY")
tavily = TavilySearch(
max_results=3, # 最大结果数[reference:2]
api_key=api_key
)
# 关键改动:使用 invoke 方法,传入包含 query 的字典
result = tavily.invoke({"query": query})
# invoke 返回的是一个字典,包含 'results' 列表[reference:4]
snippets = [res.get("content", "") for res in result.get("results", [])]
return "\n".join(snippets) if snippets else "未找到相关信息"
# 第一步:绑定工具(用于让模型决定是否调用)
model_with_tools = model.bind_tools([web_search])
# 第二步:准备用于结构化输出的模型(单独使用)
structured_model = model.with_structured_output(Search)
messages=[HumanMessage("请搜索今天西安天气怎么样,并使用搜索工具获取实时信息")]
response=model_with_tools.invoke(messages)
# 如果模型决定调用工具,则执行工具并构造结构化结果
if response.tool_calls:
tool_call = response.tool_calls[0]
# 执行工具
search_result = web_search.invoke(tool_call["args"])
# 构造最终的结构化输出(将原始文本包装成 Search 对象)
# 这里我们直接让结构化模型再处理一次,将结果整理成规范格式
final_prompt = [
HumanMessage(content=f"用户问题:{messages[0].content}\n\n搜索结果:\n{search_result}")
]
result = structured_model.invoke(final_prompt) # result 是 Search 实例
else:
# 如果模型没有调用工具,直接尝试结构化输出原始回答(可能不准确)
result = structured_model.invoke(messages)
print(result)
这种方式不仅代码更简洁,也让你的 API 接口在面对多种请求类型时,能实现统一接收、自动分发。处理 LLM 的不确定性:LLM 的输出可能对应多种工具或意图。父模式就像一个“统一入口模型”,能将 LLM 返回的不同类型数据路由到正确的处理逻辑上。这让你的 Agent 能更稳健地处理各种情况。
但是这段代码调用了两次LLM,我们只提出一个问题,为什么会调用两次LLM呢?
- 第一次(model_with_tools.invoke):让模型选择工具并提取参数,这一步模型没有生成最终文本,只是给出函数调用的指令
- 第二次(structured_model.invoke(final_prompt)):把搜索到的原始文本(又臭又长的网页片段)精炼成结构化的摘要,进行格式化输出
完结撒花!🎉

如果这篇博客对你有帮助,不妨点个赞支持一下吧!👍
你的鼓励是我创作的最大动力~
✨ 想获取更多干货? 欢迎关注我的专栏 → optimistic_chen
📌 收藏本文,下次需要时不迷路!
我们下期再见!💫 持续更新中……
悄悄说:点击主页有更多精彩内容哦~ 😊


被折叠的 条评论
为什么被折叠?



