玩转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 调用,慢一点、贵一点
相关推荐
码视野33 分钟前
基于 Vue3 + Element Plus 的【基于物联网的智慧居家养老健康关怀与紧急医疗呼叫系统】设计与实现(附完整源码与PRD)
人工智能·物联网·vue3
月亮和九磅十五便士38 分钟前
朝闻 AI|2026-08-25
人工智能
LazzyE1 小时前
初读 Miles:RL 后训练,难在采样和训练之间
人工智能
txg6661 小时前
LLM 驱动漏洞传播验证:TransferFuzz-Pro 如何自动调试“继承“来的安全债
人工智能·深度学习·安全
shujudang1 小时前
从数据闭环到 Agent 协同:企业营销自动化如何演进为智能运营?
大数据·运维·人工智能·数据分析·自动化
吉安特尔雅1 小时前
2026行业实测|主流AI获客系统功能拆解与选型指南(含剪流全矩阵深度分析)
人工智能·剪流geo·剪流·ai获客哪家靠谱
动恰客流统计1 小时前
ReID边缘计算视觉客流统计:无网户外场景的数字化落地路径
大数据·人工智能
FL16238631291 小时前
仙人掌品种类型检测数据集VOC+YOLO格式1991张10类别
人工智能·yolo·机器学习
Sunlly1 小时前
OpenClaw 的 Elevated 到底给了 Agent 什么权限
人工智能
小白说大模型1 小时前
Codex 实战:用 AI 写运维脚本
大数据·运维·网络·人工智能·机器学习·prompt