【langchain】结构化输出:ToolStrategy与ProviderStrategy

当AI开始"说人话",但你需要的是JSON

你有没有遇到过这种抓狂的场景?

让AI分析一段文本,它给你回了一大段优美的散文——“根据您提供的信息,这位客户的姓名似乎是张三,他的联系方式可能是138xxxx…”

“似乎”、“可能”、“根据您提供的信息”…这些客套话在代码里全是噪音。

你真正想要的是:

{"name": "张三", "phone": "138xxxx", "email": "zhangsan@example.com"}

就像去餐厅点餐,服务员给你念了五分钟的菜品介绍,而你只想知道价格卡路里

今天,我们就来聊聊LangChain 1.0如何让AI"闭嘴",直接给你结构化数据。

两种"驯服"AI的策略

LangChain 1.0提供了两种结构化输出策略,就像两种不同的话术技巧,让AI乖乖按格式输出:

策略 适用场景 可靠性 兼容性
ProviderStrategy OpenAI、Anthropic、Gemini等原生支持 ⭐⭐⭐⭐⭐ 仅限特定厂商
ToolStrategy 所有支持工具调用的模型 ⭐⭐⭐⭐ 通用兼容

ProviderStrategy:原生派的"特权"

想象你走进一家高级餐厅,服务员训练有素,你一说"按这个格式报菜名",他立刻用标准格式回复。

这就是ProviderStrategy——利用模型厂商原生的结构化输出能力。

from pydantic import BaseModel, Field
from langchain.agents import create_agent
from langchain.agents.structured_output import ProviderStrategy

class OrderSummary(BaseModel):
    """订单摘要"""
    customer: str = Field(description="客户名称")
    total_items: int = Field(description="商品总数")
    total_amount: float = Field(description="订单总金额(美元)")

# 使用原生结构化输出
agent = create_agent(
    model="openai:gpt-4o",
    tools=[],
    response_format=ProviderStrategy(OrderSummary),
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "Acme Corp买了10个Widget A($29.99)和5个Widget B($49.99)"}]
})

print(result["structured_response"])
# OrderSummary(customer='Acme Corp', total_items=15, total_amount=549.85)

优势很明显

  • ✅ 模型在生成时就强制遵循Schema,不会"跑偏"
  • ✅ 支持严格模式(strict mode),使用约束解码
  • ✅ 通常性能更好,延迟更低

但有个坑要注意:DeepSeek等部分国内大模型暂时不支持ProviderStrategy,会直接报400错误。

ToolStrategy:万能派的"曲线救国"

如果服务员没受过专业训练怎么办?你可以说:“请把信息填在这个表格里”——本质上是用"填表"这个工具来约束输出格式。

这就是ToolStrategy的工作原理:

from langchain.agents.structured_output import ToolStrategy

agent = create_agent(
    model="deepseek-chat",  # 或其他不支持原生结构化的模型
    tools=[],
    response_format=ToolStrategy(OrderSummary),
)

LangChain会悄悄创建一个"虚拟工具",让模型以为自己在调用工具,实际上是在生成结构化数据。

它的优势

  • ✅ 兼容所有支持工具调用的模型(几乎是全部)
  • ✅ 不挑厂商,本地模型、开源模型都能用

但也有代价

  • ❌ 多了一层转换,可靠性略低于原生方案
  • ❌ 需要模型支持工具调用(虽然这已经普及了)

Pydantic:你的数据"模具"

无论用哪种策略,你都需要先定义输出格式。LangChain推荐使用Pydantic模型——这就像给AI一个精确的模具,告诉它"按这个形状浇筑数据"。

基础定义

from pydantic import BaseModel, Field, EmailStr, validator
from typing import List, Optional
from enum import Enum

class Priority(str, Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"

class TaskItem(BaseModel):
    """单个任务项"""
    title: str = Field(description="任务标题")
    completed: bool = Field(description="是否已完成")
    
class TaskList(BaseModel):
    """任务清单"""
    project_name: str = Field(description="项目名称")
    priority: Priority = Field(description="优先级")
    tasks: List[TaskItem] = Field(description="任务列表")
    deadline: Optional[str] = Field(None, description="截止日期(可选)")
    
    # 自定义验证
    @validator('project_name')
    def validate_name(cls, v):
        if len(v) < 2:
            raise ValueError("项目名称至少需要2个字符")
        return v.strip()

支持的Schema类型

LangChain很灵活,你可以用多种形式定义结构:

类型 示例 返回结果
Pydantic BaseModel class Contact(BaseModel) Pydantic实例
TypedDict class Contact(TypedDict) 字典
dataclass @dataclass class Contact 字典
JSON Schema {"type": "object", ...} 字典

推荐用Pydantic,因为它提供运行时类型验证,还能加自定义校验逻辑。


错误处理:当AI"手滑"时

即使是最先进的模型,偶尔也会"手滑"——返回不符合格式的数据。ToolStrategy提供了错误重试机制

# 方式1:自动处理所有错误(默认)
ToolStrategy(schema=OrderSummary, handle_errors=True)

# 方式2:自定义错误提示
ToolStrategy(
    schema=OrderSummary, 
    handle_errors="请提供完整的订单信息,包括客户名、商品数量和总金额。"
)

# 方式3:自定义错误处理函数
def my_error_handler(error: Exception) -> str:
    return f"格式错误:{str(error)}。请检查数字字段是否为数值类型。"

ToolStrategy(schema=OrderSummary, handle_errors=my_error_handler)

# 方式4:不处理,直接抛出异常(调试时用)
ToolStrategy(schema=OrderSummary, handle_errors=False)

工作原理:当验证失败时,错误信息会被送回给模型,AI会尝试修正并重新输出。这就像一个自动纠错循环,直到输出符合格式或达到重试上限。


实战:智能订单解析器

让我们把学到的知识串起来,做一个实用的例子——从混乱的客户消息中提取结构化订单信息:

from pydantic import BaseModel, Field
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy, ProviderStrategy
from langchain.chat_models import init_chat_model

class LineItem(BaseModel):
    """订单行项目"""
    product_name: str = Field(description="产品名称")
    quantity: int = Field(description="数量", ge=1)
    unit_price: float = Field(description="单价", gt=0)

class CustomerOrder(BaseModel):
    """客户订单"""
    customer_name: str = Field(description="客户姓名")
    items: List[LineItem] = Field(description="订单项目")
    notes: Optional[str] = Field(None, description="特殊备注")
    
    @property
    def total(self) -> float:
        return sum(item.quantity * item.unit_price for item in self.items)

# 初始化模型
model = init_chat_model("openai:gpt-4o-mini")

# 策略选择:优先ProviderStrategy,不支持则回退到ToolStrategy
try:
    response_format = ProviderStrategy(CustomerOrder, strict=True)
except:
    response_format = ToolStrategy(
        CustomerOrder, 
        handle_errors="请确保所有数字字段正确填写。"
    )

agent = create_agent(
    model=model,
    tools=[],
    response_format=response_format,
)

# 模拟一条混乱的客户消息
messy_message = """
嗨,我是李明,想订点东西。
需要3个无线耳机,每个299元,
再加2个充电宝,单价150吧。
对了,希望能尽快发货,急用!
"""

result = agent.invoke({
    "messages": [{"role": "user", "content": messy_message}]
})

order = result["structured_response"]
print(f"客户:{order.customer_name}")
print(f"总计:¥{order.total:.2f}")
print(f"商品数:{len(order.items)}件")
# 输出:
# 客户:李明
# 总计:¥1197.00
# 商品数:2件

关键细节

  • 结果存储在 result["structured_response"]
  • 这是一个已验证的Pydantic实例,可以直接访问属性
  • 如果启用了错误处理,验证失败会自动触发重试

策略选择决策树

面对不同场景,该如何选择?记住这个简单的决策流程:

模型是否支持原生结构化输出?
├─ 是(OpenAI GPT-4o、Anthropic Claude等)
│  └─ 使用 ProviderStrategy(strict=True) 获得最佳可靠性
│
└─ 否(DeepSeek、部分开源模型等)
   └─ 使用 ToolStrategy(handle_errors=True) 保证兼容性

懒人版:直接传Pydantic类,让LangChain自动选择:

# LangChain会自动检测模型能力,选择最优策略
agent = create_agent(
    model=model,
    tools=[],
    response_format=CustomerOrder,  # 直接传Schema
)

但要注意:LangChain 1.0之后,显式使用Strategy是推荐做法,自动选择可能在某些边缘情况下表现不稳定。


生产环境 checklist

在把结构化输出部署到生产环境前,检查这几项:

Schema版本化:业务变动时,保留旧版本Schema以保证兼容性
错误监控:记录验证失败率,如果过高可能需要调整Prompt或Schema
超时设置:重试机制会增加延迟,设置合理的超时时间
敏感信息:不要在Schema中定义包含PII(个人身份信息)的字段描述


小结

今天我们学习了如何让AI"说机器能听懂的话":

  1. ProviderStrategy 是"VIP通道",利用模型原生能力,可靠性最高但兼容性有限
  2. ToolStrategy 是"万能钥匙",通过工具调用实现,兼容所有支持工具调用的模型
  3. Pydantic 是你的"数据模具",定义清晰的结构并自动验证
  4. 错误重试 是安全网,让AI有机会自我修正

结构化输出不只是技术细节,它是AI从"玩具"走向"工具"的关键一步。当你的应用能稳定地从AI输出中提取结构化数据时,自动化流程、数据分析、系统集成才能真正落地。

下一篇,我们将聊聊流式处理——如何让AI的响应像打字机一样实时呈现,而不是让用户盯着"思考中…"的转圈图标发呆。

关注公众号【dev派】,发送 “agent” 获取全部源码和模板
640

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐