玩转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 调用,慢一点、贵一点
相关推荐
SelectDB技术团队1 小时前
Apache Doris 4.1:面向 AI & Search 的统一数据底座怎么选?向量检索 + 全文搜索 + 100MB JSON 完整能力拆解
人工智能·json·apache doris
宋哥转AI1 小时前
深入理解 AI Agent 03|RAG评估体系:量化检索增强效果,精准定位系统短板
人工智能·后端·agent
zed_231 小时前
一条命令跑通整个后端:Docker Compose 入门
人工智能
十三画者1 小时前
【文献分享】3d-OT:面向空间多组学异质性切片对齐的深度几何感知框架
人工智能·机器学习·数据挖掘·数据分析
八号当铺1 小时前
我做了一个多端基金收益助手:从养基宝数据到 Web、桌面端、浏览器插件和 IDE 插件
前端·人工智能·github
mqiqe1 小时前
构建企业级 AI 原生架构:LLM Gateway、RAG、Agent 与 MCP 的协同关系解析
人工智能·架构·gateway
qq_425516181 小时前
录音转文字工具免费下载:免费额度与功能限制对比
android·人工智能·智能手机·powerpoint
智体工坊1 小时前
从零搭建 AI 模型中转网关:部署、渠道、定价、装修全记录
运维·服务器·人工智能·搜索引擎·自动化
gnsnswa1 小时前
解读6大AI人工智能认证证书
人工智能·信息可视化·职场和发展·数据分析·aigc·学习方法·信息与通信