☰
LangChain 结构化输出:让模型稳定吐出符合 schema 的 JSON
2026/10/16 3:05:14 网站建设 项目流程
个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <

文章目录

  • LangChain 结构化输出:让模型稳定吐出符合 schema 的 JSON
    • 一、先看看不用它会怎样
    • 三、三种 schema 定义方式
      • 3.1 Pydantic(最常用)
      • 3.2 TypedDict(我个人更推荐)
      • 3.3 JSON Schema(跨语言场景)
    • 四、method 参数:三种实现机制
    • 五、include_raw:调试救命参数
    • 六、接进 LCEL 链
      • 6.1 抽取多条:包一层列表
      • 6.2 可选字段:让模型能说"不知道"
    • 七、实战:从合同文本抽结构化字段
    • 八、常见坑
    • 九、小结

LangChain 结构化输出:让模型稳定吐出符合 schema 的 JSON

让模型"返回 JSON"听起来简单,做起来全是坑:
有时候包在 ```json 代码块里,有时候键名对不上,
有时候多一段解释文字,有时候干脆少一个字段。

这篇讲with_structured_output怎么用、三种模式怎么选,
以及为什么我推荐 TypedDict 而不是 Pydantic。

一、先看看不用它会怎样

最常见的"土办法":

prompt="请分析下面这句话的情感,返回 JSON:{'sentiment': ..., 'score': ...}\n文本:xxx"resp=llm.invoke(prompt)print(resp.content)

你会得到各种意外:

好的,分析结果如下: ```json {"sentiment": "positive", "score": 0.9}

希望对你有帮助!

或者:

{“情感”: “正面”, “分数”: 0.9} # 键名被翻译了

或者:

{“sentiment”: “positive”} # 少了一个字段

然后你得写一堆正则、容错、重试。**这些麻烦 `with_structured_output` 全帮你处理了。** ## 二、上手 ```python from pydantic import BaseModel, Field from langchain_openai import ChatOpenAI class Sentiment(BaseModel): """情感分析结果""" sentiment: str = Field(description="情感倾向:positive / negative / neutral") score: float = Field(description="置信度,0~1 之间", ge=0, le=1) keywords: list[str] = Field(description="体现情感倾向的关键词") llm = ChatOpenAI(model="gpt-4o-mini") structured_llm = llm.with_structured_output(Sentiment) result = structured_llm.invoke("这家餐厅的牛排太好吃了,就是上菜有点慢") print(type(result)) # <class 'Sentiment'> print(result.sentiment) # positive print(result.score) # 0.85 print(result.keywords) # ['牛排好吃', '上菜慢']

返回的直接是 Python 对象,不是字符串。类型安全,IDE 能补全,
下游代码可以直接.score用,不用再json.loads。

三、三种 schema 定义方式

3.1 Pydantic(最常用)

frompydanticimportBaseModel,FieldclassProduct(BaseModel):name:str=Field(description="商品名称")price:float=Field(description="单价(元)")tags:list[str]=Field(description="标签列表",default_factory=list)

优点:自带校验。定义了ge=0, le=1,模型传 1.5 会抛校验错误,
你能立刻发现而不是等到线上。

3.2 TypedDict(我个人更推荐)

fromtyping_extensionsimportTypedDict,AnnotatedclassProduct(TypedDict):"""商品信息"""name:Annotated[str,...,"商品名称"]price:Annotated[float,...,"单价(元)"]tags:Annotated[list[str],...,"标签列表"]

为什么不直接用 Pydantic?

因为 Pydantic 的BaseModel有个"顺序陷阱":
想在描述里引用前面定义的字段,在某些写法下会出问题;
而且 Pydantic 的校验错误有时会把模型输出的小瑕疵放大成硬失败。

TypedDict 是纯类型声明,没有运行时校验,也就不会因为一点小偏差直接报错,
配合Annotated的三段式(类型, 默认值/..., 描述)语义非常清晰。

什么时候必须用 Pydantic?需要运行时校验、需要嵌套复杂约束、
或者下游要复用这个 model 做序列化时。其余场景 TypedDict 更轻。

3.3 JSON Schema(跨语言场景)

schema={"title":"Product","type":"object","properties":{"name":{"type":"string","description":"商品名称"},"price":{"type":"number","description":"单价(元)"},"tags":{"type":"array","items":{"type":"string"},"description":"标签"},},"required":["name","price"],}structured_llm=llm.with_structured_output(schema)

适合 schema 存在配置文件里、或由其他系统生成的场景。

四、method 参数:三种实现机制

with_structured_output(schema, method=...)是关键参数。

method机制支持度特点
function_calling用 tool calling 模拟OpenAI、大部分主流模型最通用
json_schema模型原生的结构化输出OpenAI 新模型最严格,保证 100% 符合 schema
json_mode只强制输出合法 JSON部分模型只保证是 JSON,不保证符合 schema
# 默认会自动选,也可以显式指定structured_llm=llm.with_structured_output(Product,method="function_calling")

怎么选?

  • 模型支持原生json_schema(如 gpt-4o 系列)→用它,最稳;
  • 不支持 →function_calling,兼容性最好;
  • 只有在前两者都不可用时才用json_mode,它只保证"是合法 JSON",
    字段对不对它不管,你还得自己校验。

判断方法:直接试试,报错就换。

五、include_raw:调试救命参数

structured_llm=llm.with_structured_output(Product,include_raw=True)result=structured_llm.invoke("iPhone 15 售价 5999 元")print(result)# {# 'raw': AIMessage(content='', tool_calls=[...]),# 'parsed': Product(name='iPhone 15', price=5999.0, tags=[]),# 'parsing_error': None# }

解析失败时parsed是 None、parsing_error有内容,
而raw保留模型原始输出。
排查"为什么又解析失败了"的时候,这个参数能省你半小时。

生产环境建议先开着跑一段时间,收集几次parsing_error,
确认稳定后再关掉。

六、接进 LCEL 链

结构化输出返回的还是 Runnable,可以直接链起来:

fromlangchain_core.promptsimportChatPromptTemplate prompt=ChatPromptTemplate.from_messages([("system","你是信息抽取助手。从文本中抽取商品信息,不要编造。"),("human","{text}"),])chain=prompt|llm.with_structured_output(Product)result=chain.invoke({"text":"这款 MacBook Pro 16G+512G 售价 12999 元"})print(result.name,result.price)

6.1 抽取多条:包一层列表

frompydanticimportBaseModel,FieldclassProductList(BaseModel):"""商品列表"""products:list[Product]=Field(description="抽取到的所有商品")chain=prompt|llm.with_structured_output(ProductList)

不要直接传list[Product]——包一层带描述的容器模型,
模型的抽取完整度会明显更好,也方便你加"总数""备注"等字段。

6.2 可选字段:让模型能说"不知道"

fromtypingimportOptionalclassProduct(BaseModel):name:str=Field(description="商品名称")price:Optional[float]=Field(description="单价,文本中没提到则为 null",default=None)

明确告诉模型"没有就填 null",比让它硬编一个数字好得多。
这是我反复强调的原则——给模型留"不知道"的出口,它才不会编。

七、实战:从合同文本抽结构化字段

fromtypingimportOptionalfromtyping_extensionsimportTypedDict,Annotatedfromlangchain_core.promptsimportChatPromptTemplateclassContractInfo(TypedDict):"""合同关键信息"""party_a:Annotated[str,...,"甲方名称,原文照抄"]party_b:Annotated[str,...,"乙方名称,原文照抄"]amount:Annotated[Optional[float],...,"合同金额(元),未提及则为 None"]sign_date:Annotated[Optional[str],...,"签订日期,YYYY-MM-DD 格式,未提及则为 None"]has_penalty:Annotated[bool,...,"是否包含违约金条款"]prompt=ChatPromptTemplate.from_messages([("system","你是合同信息抽取助手。\n""只抽取文本中明确出现的信息,没有提到的一律填 None,绝对不要推测。\n""金额只填数字,不要带货币单位和千分位。"),("human","合同文本:\n{text}"),])chain=prompt|llm.with_structured_output(ContractInfo)info=chain.invoke({"text":contract_text})print(info["party_a"],info["amount"],info["has_penalty"])

几个 prompt 技巧,效果差别很大:

  1. “原文照抄”——避免模型自作主张规范化公司名;
  2. “没有提到的一律填 None”——给"不知道"的出口;
  3. 格式举例(YYYY-MM-DD、只填数字)——减少格式抖动;
  4. “不要带货币单位”——明确边界。

八、常见坑

现象原因解法
报"不支持 structured output"模型不支持该 method换method="function_calling"
字段值被翻译/改名schema 描述用的中文描述用中文没问题,检查是不是模型幻觉,加"原文照抄"
Pydantic 校验报错模型传了越界值放宽约束,或换 TypedDict
嵌套结构抽不全直接用了list[X]包一层容器模型
偶尔返回 None解析失败开include_raw=True看 raw
长文本抽取质量下降上下文太长先切片,逐片抽取再合并

九、小结

  • with_structured_output直接返回Python 对象,告别正则和容错代码;
  • schema 三种写法:Pydantic(要校验)、TypedDict(推荐,轻量)、JSON Schema(跨语言);
  • method优先级:json_schema(最严格)>function_calling(最通用)>json_mode(兜底);
  • 调试开include_raw=True,能看到 raw / parsed / parsing_error 三部分;
  • 抽多条时包一层容器模型,别直接传list[X];
  • 可选字段用Optional+ “没有就填 None”,给模型留说不知道的出口。

下一篇讲 Agent 入门——把工具调用变成自动的多步循环。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询