【LangChain 开发】LangChain 聊天模型——结构化输出


🚀 欢迎来到我的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 的内部流程:

  1. 当传入Joke(继承自 BaseModel)时,with_structured_output 做的第一件事,调用 Pydantic 的 .model_json_schema() 方法,把你的类转换成一个标准的 JSON Schema 字典。
  2. LangChain 会把这个 JSON Schema 塞进 LLM API 的 tools(函数调用)参数(工具调用)或 response_format(JSON 模式)参数中。LLM 看到的只是这个 JSON 结构,它根本不知道 Python 类的存在。
  3. 当 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
📌 收藏本文,下次需要时不迷路!

我们下期再见!💫 持续更新中……


悄悄说:点击主页有更多精彩内容哦~ 😊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Optimistic _ chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值