一、为什么要结构化输出?
前 4 篇 我们看到的 Agent 输出都是自然语言字符串:"(20 + 2) × 4 = 88"
这种输出人看着舒服,但程序很难处理。如果你想:

你就得自己写一堆正则表达式或者 JSON 解析代码去"扒"答案里的关键信息------这既脆弱又难维护。
结构化输出:就是让 Agent 直接返回预定义格式的数据(通常是 JSON)。
二、LlamaIndex 的两种结构化输出方式
2.1 方式一:用 output_cls 指定 Pydantic 模型(推荐)
最简单、最稳的方式 。只要把 Pydantic 类传给 output_cls,框架会:
- 内部自动调用一次 LLM,让它把对话结果转成这个 Pydantic 类
- 把结果挂在
response.structured_response上 - 类型完全安全(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, # 最大值
)
三个细节:
- 每个字段都有
description:LLM 靠这个 description 来填字段,描述越清楚,填充越准。 - 类型注解必须严格 :用
float不用int,用list[str]不用list。LLM 输出的格式必须能 cast 到这个类型。 - 可以用 Pydantic 的校验器 :
ge、le、regex等,保证数据合法性。如果 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_cls 和 structured_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 调用,慢一点、贵一点