玩转LlamaIndex:结构化输出

一、为什么要结构化输出?

前 4 篇 我们看到的 Agent 输出都是自然语言字符串:"(20 + 2) × 4 = 88"

这种输出人看着舒服,但程序很难处理。如果你想:

你就得自己写一堆正则表达式或者 JSON 解析代码去"扒"答案里的关键信息------这既脆弱又难维护

结构化输出:就是让 Agent 直接返回预定义格式的数据(通常是 JSON)。

二、LlamaIndex 的两种结构化输出方式

2.1 方式一:用 output_cls 指定 Pydantic 模型(推荐)

最简单、最稳的方式 。只要把 Pydantic 类传给 output_cls,框架会:

  1. 内部自动调用一次 LLM,让它把对话结果转成这个 Pydantic 类
  2. 把结果挂在 response.structured_response
  3. 类型完全安全(IDE 能自动补全)

2.2 方式二:用 structured_output_fn 自定义解析函数

如果你想用正则匹配、关键词提取、第三方库等方式自己解析,可以用这个。

99% 的场景用方式一就够了

三、用 output_cls 实现结构化输出

3.1 代码

Python 复制代码
 async def main():

    response = await  agent.run(user_msg = "帮我算一下 (10 + 5) * 3,步骤写清楚")

    # 4.1 完整响应(含自然语言)
    print("=== 完整响应 ===")
    print(response)
    print(type(response))

    # 4.2 结构化响应(dict 形式)
    print("\n=== 结构化响应 (dict) ===")
    print(response.structured_response)

    # 4.3 转成 Pydantic 对象(类型安全)
    print("\n=== Pydantic 对象 ===")
    result_obj:MathResult=response.get_pydantic_model(MathResult)
    print(f"运算: {result_obj.operation}")
    print(f"结果: {result_obj.result}")
    print(f"步骤: {result_obj.steps}")
    print(f"可信度: {result_obj.confidence}")

    # 4.4 直接当 dict 用
    print("\n=== 存到数据库(模拟) ===")
    result_record={
        "expression": result_obj.operation,
        "value": result_obj.result,
        "metadata": {
            "steps": result_obj.steps,
            "confidence": result_obj.confidence,
        }
    }
    print(json.dumps(result_record, ensure_ascii=False, indent=2))

3.2 运行效果

四、代码逐段解析

4.1 Pydantic 模型就是"数据契约"

Python 复制代码
class MathResult(BaseModel):
    """数学运算的结构化结果"""

    operation: str = Field(
        description="执行的运算表达式,如 '2 + 3' 或 '5 * 4'"
    )
    result: float = Field(
        description="最终的运算结果"
    )
    steps: list[str] = Field(
        description="计算步骤列表,每步一行说明"
    )
    confidence: float = Field(
        description="结果的可信度,0~1 之间",
        ge=0.0,    # 最小值
        le=1.0,    # 最大值
    )

三个细节:

  1. 每个字段都有 description:LLM 靠这个 description 来填字段,描述越清楚,填充越准。
  2. 类型注解必须严格 :用 float 不用 int,用 list[str] 不用 list。LLM 输出的格式必须能 cast 到这个类型。
  3. 可以用 Pydantic 的校验器 :geleregex 等,保证数据合法性。如果 LLM 输出的 confidence=1.5,Pydantic 会自动校验失败抛错。

4.2 output_cls 的魔法

Python 复制代码
agent = FunctionAgent(
    ...
    output_cls=MathResult,  # ⭐ 一行搞定
)

底层发生了什么?

关键洞察 :结构化输出 = 框架帮你多调一次 LLM + 一次 Pydantic 验证。所以会比纯 Agent 慢一点、贵一点。

4.3 三种取结果的方式

Python 复制代码
# 方式 1:直接拿 dict(最常用)
data = response.structured_response
print(data["result"])

# 方式 2:转 Pydantic 对象(类型安全)
obj: MathResult = response.get_pydantic_model(MathResult)
print(obj.result)  # IDE 能自动补全

# 方式 3:自己解析
# 不推荐,除非有特殊需求

五、内部工作流

六、用 structured_output_fn 自定义解析

如果你有特殊需求(比如 LLM 输出的是 JSON 之外的格式),可以用自定义函数。

Python 复制代码
async def custom_parser(messages: List[ChatMessage]) -> Dict[str, Any]:
    """自定义解析函数。

    输入是 LLM 收到的所有消息,输出必须是 dict。
    """
    # 提取最后一条 assistant 消息
    last_msg = messages[-1].content

    # 这里可以写你的自定义解析逻辑
    # 例如:正则提取数字、调用第三方 NER 服务等
    import re
    numbers = re.findall(r'\d+\.?\d*', str(last_msg))

    return {
        "operation": "custom",
        "result": float(numbers[0]) if numbers else 0,
        "steps": ["用自定义解析得到"],
        "confidence": 0.5,  # 自定义解析的可信度低一些
    }

# 用法
agent = FunctionAgent(
    tools=[add, multiply],
    llm=llm,
    system_prompt="...",
    structured_output_fn=custom_parser,  # 替换 output_cls
)

注意 :output_clsstructured_output_fn 不能同时用,只能二选一。

七、复杂 Pydantic 模型:嵌套对象

实际业务里数据模型往往更复杂,支持嵌套:

Python 复制代码
from typing import List
from pydantic import BaseModel, Field

class Operation(BaseModel):
    """单个运算步骤"""
    type: str = Field(description="运算类型:add/multiply")
    operands: List[float] = Field(description="操作数")
    result: float = Field(description="这一步的结果")

class MathAnalysis(BaseModel):
    """完整的数学分析结果"""
    original_question: str = Field(description="原始问题")
    operations: List[Operation] = Field(description="所有运算步骤")
    final_result: float = Field(description="最终结果")
    explanation: str = Field(description="通俗易懂的解释")

agent = FunctionAgent(
    tools=[add, multiply],
    llm=llm,
    system_prompt="...",
    output_cls=MathAnalysis,  # 嵌套模型也能工作
)

LLM 会被要求输出这种嵌套 JSON,框架会自动验证整个结构。

这对复杂业务场景非常有用------比如"让 Agent 分析一份销售数据,返回结构化的报告"。

八、踩坑指南

坑 1:LLM 返回的字段类型不对

现象 :confidence 字段返回字符串 "0.99",Pydantic 报 ValidationError。 解决:

  • Pydantic 2.x 默认开启严格模式,需要保证 LLM 输出类型完全匹配
  • 在 prompt 里强调字段类型 (system_prompt 里加一句"所有数字必须是 number 类型,不要加引号")
  • 或者把字段类型改成 Union[float, str],先接受再解析

坑 2:结构化输出偶尔失败

现象 :大部分时候正常,偶尔 ValidationError。 解决:

  • 在 Pydantic 字段里加更多 default 值,降低校验严格度
  • 升级 llama-index-core 到最新版,有 bug 修复
  • 设置重试:output_cls=MathResult 失败时,框架会重试 1 次

坑 3:字段太多,LLM 偷懒漏填

现象 :steps 字段返回空列表 []解决:

  • 在 system_prompt 里明确强调必填字段 :"steps 字段必须包含至少 2 个步骤"
  • 用 Pydantic 的 min_length 约束:steps: list[str] = Field(min_length=1)

坑 4:结构化输出让 Agent 变慢

每次结构化输出都会多调一次 LLM,延迟大概增加 0.5~2 秒。 优化思路:

  • 只在确实需要 JSON 的场景用结构化输出
  • 不需要时让 Agent 直接返回自然语言
  • 考虑用更小的模型专门做"格式转换"(比如用 gpt-4o-mini 转 JSON)

九、小结

这一篇我们学到了:

  • 结构化输出让 Agent 直接返回 JSON/Pydantic 对象,告别手写解析
  • 方式一 (output_cls) 是首选,框架自动多调一次 LLM 转格式
  • 方式二 (structured_output_fn) 留给特殊需求
  • Pydantic 模型 就是"数据契约",description 决定 LLM 怎么填字段
  • 响应结果 可以用 dict 访问、用 get_pydantic_model 转类型安全对象
  • 代价:多一次 LLM 调用,慢一点、贵一点
相关推荐
AI推荐率2 分钟前
品牌的核心能力只写在图片里,公开资料该怎样补充文字解释?
人工智能
能源革命6 分钟前
DeepAR(概率自回归模型)介绍
人工智能·数据挖掘·回归
IvorySQL12 分钟前
PostgreSQL 日报|大模型破解在线校验和(9 月 15 日)
数据库·人工智能·postgresql
人工智能AI技术20 分钟前
模型越强越翻车?GPT-6 旧配置踩坑深度避坑指南
人工智能
小酒星小杜20 分钟前
【AI+Gpt-Image2.5】我偷偷把同事做成了虚拟角色,结果被发现了
人工智能·程序员·产品
ElfBoard24 分钟前
作品展示|基于RK3588的复杂空间下自主导航无人机与多传感器 AI 融合环境监测分析
大数据·人工智能·单片机·嵌入式硬件·团队开发
hoaxxcj26 分钟前
DeepSeek Harness 本地部署与第三方插件排障实录:从 fetch failed 到 preset not found
人工智能·windows·开源·ai agent·deepseek
鲜于言悠90527 分钟前
AgentLoop
人工智能
猫哥随身wifi31 分钟前
AI 手机越智能,随身网络越关键|AI 终端带来的网络新需求
网络·人工智能·智能手机
Mr数据杨33 分钟前
arkav1920多标签文本分类实战解析与建模思路
人工智能·数据分析·kaggle竞赛