LangChain 实战:Agent 4类结构化输出方式

0 阅读6分钟

一、Model vs Agent结构化输出

img

二、结构化输出的 4 种策略

LangChain 的 create_agent 会自动处理结构化输出。

用户设置所需的结构化输出模式,当模型生成结构化数据时,系统会将其捕获、验证,并返回到Agent 状态中的 'structured_response'

def create_agent(
    ...
    response_format: Union[
        ToolStrategy[StructuredResponseT],
        ProviderStrategy[StructuredResponseT],
        type[StructuredResponseT],
        None,
    ]
)

三、Response format

img

当直接传入一个 Schema 类型时,LangChain 会自动选择:

  • 如果选用的模型与模型服务商支持原生结构化输出,则使用 ProviderStrategy(例如 OpenAI、Anthropic、xAI,)
  • 其余所有模型,一律使用 ToolStrategy****(推荐)

JSON Schema 字典必须显式包裹在策略对象(****ProviderStrategy **ToolStrategy****)内,**如果直接传给 response_format,不会被自动识别

四、Provider strategy

一些模型提供商通过其API原生支持结构化输出(例如OpenAI、xAI(Grok)、Gemini、Anthropic(Claude))

class ProviderStrategy(Generic[SchemaT]):
    schema: type[SchemaT]
    strict: bool | None = None

The strict param requires langchain>=1.2.

参数schema:必选

schema支持一下四种结构化输出定义:

  • Pydantic models:继承自 BaseModel 的子类,自带字段校验。返回经过校验的 Pydantic 实例对象
  • Dataclasses:带类型注解的 Python 数据类。返回字典(dict)
  • TypedDict:类型化字典类。返回字典(dict)
  • JSON Schema:遵循 JSON Schema 规范的字典。顶层必须包含 title 和 description 两个键。返回字典(dict)

例如:

class ContactInfo(BaseModel):
    """用户的联系方式"""
    name: str = Field(description="用户姓名")
    email: str = Field(description="用户邮箱地址")
    phone: str = Field(description="用户的手机号")

# 3.agent初始化
agent = create_agent(
    model=model,
    #response_format=ContactInfo  # 自动选择 ProviderStrategy
    response_format=ProviderStrategy(ContactInfo)
)
# 4.调用
response = agent.invoke({
    "messages": [HumanMessage("从这段话中抽取结构化信息:吴老狗的邮箱地址为:wu-old-dog@qq.com,手机号:19999999999")]
    })

for msg in response["messages"]:
    msg.pretty_print()

img

五、Tool calling strategy

5.1 定义

对于不支持原生结构化输出的模型,LangChain 通过调用工具来实现相同的效果。

该方法适用于所有支持工具调用的模型(大多数现代模型)。

要使用此策略,配置一个 ToolStrategy即可

class ToolStrategy(Generic[SchemaT]):
    schema: type[SchemaT]
    tool_message_content: str | None
    handle_errors: Union[
        bool,
        str,
        type[Exception],
        tuple[type[Exception], ...],
        Callable[[Exception], str],
    ]

5.2 参数

(1)参数schema:必选

schema支持一下五种结构化输出定义:

  • Pydantic models:继承自 BaseModel 的子类,自带字段校验。返回经过校验的 Pydantic 实例对象。
  • Dataclasses:带有类型注解的 Python 数据类。返回字典(dict)
  • TypedDict:类型化字典类。返回字典(dict)
  • JSON Schema:遵循 JSON Schema 规范的字典。顶层必须包含 title 和 description 键。返回字典(dict)
  • Union types:联合类型,支持多种 Schema 选项。模型会根据上下文选择最合适的 Schema。

(2)参数handle_errors

结构化输出校验失败时的错误处理策略。默认值为 True

  • True:捕获全部异常,使用内置默认错误提示模板
  • str:捕获全部异常,使用该自定义字符串作为错误提示
  • type[Exception]:仅捕获指定这一类异常,使用默认错误提示
  • tuple[type[Exception], ...]:仅捕获元组内列举的多种异常,使用默认错误提示
  • Callable[[Exception], str]:自定义回调函数,入参为异常对象,返回错误提示字符串
  • False:不进行重试,直接向上抛出异常

5.3 案例

5.3.1 Pydantic models

from pydantic import BaseModel, Field
from typing import Literal
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy


class ProductReview(BaseModel):
    """Analysis of a product review."""
    rating: int | None = Field(description="The rating of the product", ge=1, le=5)
    sentiment: Literal["positive", "negative"] = Field(description="The sentiment of the review")
    key_points: list[str] = Field(description="The key points of the review. Lowercase, 1-3 words each.")

agent = create_agent(
    model=model,
    response_format=ToolStrategy(ProductReview)
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "Analyze this review: 'Great product: 5 out of 5 stars. Fast shipping, but expensive'"}]
})
rprint(result["structured_response"])

img

5.3.2 dataclass

img

5.3.3 TypedDict

img

5.3.4 JSON Schema

product_review_schema = {
    "title": "ProductReview",
    "type": "object",
    "description": "Analysis of a product review.",
    "properties": {
        "rating": {
            "type": ["integer", "null"],
            "description": "The rating of the product (1-5)",
            "minimum": 1,
            "maximum": 5
        },
        "sentiment": {
            "type": "string",
            "enum": ["positive", "negative"],
            "description": "The sentiment of the review"
        },
        "key_points": {
            "type": "array",
            "items": {"type": "string"},
            "description": "The key points of the review"
        }
    },
    "required": ["sentiment", "key_points"]
}

agent = create_agent(
    model=model,
    response_format=ToolStrategy(product_review_schema)
)

5.3.5 Union Types

class ProductReview(BaseModel):
    """商品评论分析结果。"""
    rating: int | None = Field(description="商品的评分,取值范围1‑5分", ge=1, le=5)
    sentiment: Literal["positive", "negative"] = Field(description="评论的情感倾向")
    key_points: list[str] = Field(description="评论的核心要点。全部小写,每条1‑3个单词。")


class CustomerComplaint(BaseModel):
    """客户针对产品或服务提出的投诉。"""
    issue_type: Literal["product", "service", "shipping", "billing"] = Field(description="问题类型")
    severity: Literal["low", "medium", "high"] = Field(description="投诉严重等级")
    description: str = Field(description="对投诉内容的简短描述")


agent = create_agent(
    model=model,
    response_format=ToolStrategy(Union[ProductReview, CustomerComplaint])
)

img

5.4 自定义工具的返回值

img

5.5 错误处理

5.5.1 多结构化输出错误处理

当model错误地调用多个结构化输出工具时,agent 会通过 ToolMessage 提供错误反馈,并提示模型重试:

class ContactInfo(BaseModel):
    name: str = Field(description="人名")
    email: str = Field(description="邮箱地址")

class EventDetails(BaseModel):
    event_name: str = Field(description="事件名")
    date: str = Field(description="事件时间")

agent = create_agent(
    model=model,
    # Default: handle_errors=True
    response_format=ToolStrategy(Union[ContactInfo, EventDetails])
    # 两种同等效果
    response_format=ToolStrategy(Union[ContactInfo, EventDetails],
                             handle_errors = MultipleStructuredOutputsError
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "提取信息:吴老狗(dog@email.com)将于 3 月 15 日 在杭州举办养狗大会"}]
})

img

handle_errors=False,直接报错

img

5.5.2 处理多种异常类型

如果 handle_errors 是一个异常元组,代理仅在抛出的异常属于指定类型时才会重试(使用默认错误消息)。

在所有其他情况下,异常将被正常抛出。

class ProductRating(BaseModel):
    """商品评论分析结果。"""
    rating: str | None = Field(description="评分范围 1 到 5 分", ge=1, le=5)
    comment: int = Field(description="评审意见")

agent= create_agent(
    model=model,
    # Default: handle_errors=True
    response_format = ToolStrategy(ProductRating,handle_errors = (ValueError, TypeError)
                                   ),
    system_prompt="你是一个用于解析产品评论的实用助手。**不得编造任何字段和数值。**"
)

result= agent.invoke({
    "messages": [{"role": "user", "content": "解析这个:很棒的产品,满分 10 分!"}]
})

img

六、小结

  • 核心机制create_agent 通过 response_format 参数接管结构化输出,最终结果统一存放在状态的 structured_response 字段中。
  • 四种策略
    • 直接传 Schema(懒人包):LangChain 自动根据模型能力在 Provider 和 Tool 之间二选一,日常开发首选。
    • ProviderStrategy(原生效能):调用模型厂商的原生 API 能力(如 OpenAI/Claude),性能和准确率最高。
    • ToolStrategy(万金油):把 Schema 伪装成工具调用,兼容所有支持 Function Call 的模型。
    • None(摆烂模式):不强制要求结构化,模型爱咋输出咋输出。
  • Schema 定义:支持 Pydantic、Dataclass、TypedDict、JSON Schema,甚至 Union 联合类型(让模型自己判断该用哪个)。

搞懂了结构化输出,你的 Agent 才算真正“能干活”。

如果这篇干货帮你少踩了几个坑,欢迎点赞、在看、转发三连!关于结构化输出,你还有哪些想深挖的实战场景?欢迎在评论区留言交流~