新闻详情

新闻详情

首页 / 资讯中心 / 详情

LlamaIndex结构化输出与评估实战:让模型按规矩说话并验证回答质量

发布时间:2026/9/8 6:44:10来源:尧图网络
LlamaIndex结构化输出与评估实战:让模型按规矩说话并验证回答质量
玩LlamaIndex有一段时间的人迟早会撞上两个痛点一是模型输出老是夹带私货好好一个JSON里突然蹦出几句自然语言二是系统上线之后根本说不清回答到底准不准只能靠肉眼抽查。这两个痛点正好对应LlamaIndex里最容易被忽视、却也最能拉开差距的两个模块——结构化输出与评估。这是系列第六篇我把这两个东西放在一起讲因为它们其实是一对配合结构化输出解决“模型怎么按规矩说话”评估解决“模型说得对不对、好不好”。如果你正在搭RAG问答、信息抽取或Agent工作流这篇内容能帮你少踩很多坑。先说结论结构化输出的核心不是“让模型输出JSON”而是“让模型在你定义的框架内做选择”评估的核心也不是“给个分数”而是“用可复现的规则发现失败case”。理解了这两点后面所有代码和配置才有意义。1. 结构化输出到底解决了什么问题1.1 自由文本输出为什么“中看不中用”把大模型当普通文本生成器用的时候体验确实很爽一段提示词下去什么都能给你写出来。但一旦要接入业务系统问题就冒出来了你希望拿到一段JSON模型偏要在一个字段后面补一句“以上是分析结果”你希望分类结果只有“A/B/C”三种模型给你输出“我认为这个属于B类因为……”。这种不确定性在demo阶段还能忍上线后会让下游解析代码变成一场灾难。我习惯把这个问题类比成让实习生填表格你给他一张空表告诉他“姓名、年龄、部门都填上”他大概率会填但偶尔会在备注栏写“这个客户很急”或者“今天心情不好”。你需要的不是批评他而是把表格改成“每个格子只能填什么类型、长度多少、是否必填”的强约束表单。结构化输出就是这个道理不是让模型更聪明而是给模型一个不能越界的“表单”。在LlamaIndex里结构化输出通常指让LLM返回一个符合预先定义Schema的数据结构最终得到一个Pydantic对象或JSON供下游直接使用。常见的应用场景包括从简历里抽取姓名、工作经历、技能标签把用户问题转成查询结构化数据的参数从评论里提取评分、摘要、情感倾向让Agent决定下一步调用哪个工具、传什么参数。这些场景的共同特点是输出格式错了整个链路就断了。1.2 三条实现路径我的选型思考LlamaIndex里做结构化输出主流的做法有三条路我分别说下优缺点和适用场景方便你按自己的情况选。路径实现方式优点痛点适合场景提示词手写解析在Prompt里写清楚JSON格式再用json.loads或正则提取实现简单不依赖额外依赖包模型偶尔输出markdown代码块、解释文字解析极其脆弱快速验证、一次性脚本函数调用/JSON Mode走OpenAI等厂商的function calling或response_format参数格式相对稳定原生支持返回的是dict缺少字段校验和类型约束脏数据要靠自己洗对Schema要求不高、只做一次转换的场景PydanticProgram用Pydantic类定义输出Schema封装prompt与解析逻辑强类型、自动校验、可重试、可嵌套需要多写Pydantic模型理解成本稍高正式项目、复杂业务Schema、要进生产环境我的建议简单粗暴凡是“输出要入库、要传给下游系统、要经过多轮校验”的场景直接上PydanticProgram不要省这点时间。凡是“跑一次看看效果”的场景用提示词手写解析就够了不值得为一次性脚本引入额外复杂度。1.3 写结构化输出前的四个预备动作在动手写代码之前先确认四件事能帮你少走不少弯路。第一确认你的LLM支持function calling或JSON模式。如果用的是OpenAI较新的模型基本没问题如果是自托管开源模型建议选支持工具调用的版本或者干脆走“提示词解析器”的路线。第二把temperature调低。结构化输出最忌讳随机性temperature最好设置在0到0.1之间。我之前遇到过temperature默认1.0时同一个输入来回两次输出字段名都不一致把temperature降到0后问题立刻消失。这个细节很多人忽略但它对稳定性的影响比任何提示词技巧都大。第三设计好输出Schema的“颗粒度”。字段不是越多越好也不是越少越好。字段过少模型会把多个信息塞进一个字符串里字段过多模型容易混淆相近字段。设计原则是每个字段表达一个独立的语义单元字段名一看就懂description写清取值范围和判断标准。第四准备好“失败预案”。结构化输出再稳定也有概率解析失败或校验失败。一定要预留重试逻辑或者把失败记录落盘人工介入修正。很多生产事故不是模型能力不够而是没人处理那1%的解析异常。2. 用PydanticProgram把输出“焊死”成对象2.1 定义Schema模型靠description理解需求PydanticProgram的核心是Pydantic模型。你写一个类每个字段代表输出中的一个键字段的类型、默认值、描述共同构成了给模型的“填写说明”。关键在于Pydantic的Field(description...)不只是给你的代码看的它会被拼进发给LLM的prompt里是模型理解任务需求的主要信息来源。所以description必须写清楚两件事这个字段代表什么取值应该遵循什么规则。比如rating字段你可以写“电影评分0到10之间的浮点数”而不是只写“评分”。字段名也要直白避免让模型去猜“label”到底指标签还是标题。除了description还可以用Field(fi...)或者Field(examples...)给出示例效果非常明显。模型看到示例之后理解成本会大幅下降。如果你的Schema有枚举值更推荐用Literal[正面,负面,中性]这种类型直接从类型层面把取值锁定住比跑了校验再去拒绝要省事得多。2.2 一个能直接抄的完整示例下面用影评抽取做例子展示从定义Schema到调用的完整流程。这个例子的业务逻辑是给模型一段影评文字让它抽取电影名、评分、核心观点摘要、情感标签。from typing import Literal from pydantic import BaseModel, Field, field_validator from llama_index.program import OpenAIPydanticProgram class MovieReview(BaseModel): title: str Field(..., description电影名称保持原文语言) rating: float Field(..., description评分0到10之间的浮点数保留一位小数) summary: str Field(..., description影评核心观点摘要不超过60个汉字) sentiment: Literal[正面, 负面, 中性] Field( description影评整体情感倾向只能从正面、负面、中性中选择一个 ) tags: list[str] Field( default_factorylist, description从影评中提取3到5个关键词标签每个词不超过6个汉字 ) field_validator(rating) classmethod def rating_in_range(cls, v): if not 0 v 10: raise ValueError(rating must be between 0 and 10) return v program OpenAIPydanticProgram.from_defaults( output_clsMovieReview, prompt_template_str( 你是一个专业的影视评论分析助手。\n 请阅读以下影评并严格按照给定格式抽取信息\n {text}\n ), verboseTrue, ) text ( 《流浪地球2》让我重新燃起对国产科幻的信心。 特效场面宏大但最打动我的是人类面对危机时的集体抉择。 豆瓣虽然有一些争议但我认为它至少值8分。 ) result program(texttext) print(result) print(result.rating, result.sentiment, result.tags)这里有几个细节值得说明。OpenAIPydanticProgram.from_defaults会自动把你的pydantic模型转成输出格式要求并在拿到模型回复后做解析和校验。如果校验失败支持自动重试前提是你配置好重试参数不同版本略有差异建议看一眼你当前版本的from_defaults支持哪些参数。field_validator是Pydantic v2的写法如果你的项目还在用Pydantic v1需要换成validator这点很容易踩版本坑。2.3 批量处理与失败重试的工程化写法单个调用好写工程上真正麻烦的是批量场景。假设你有一万条影评要抽取不可能一条条人工盯着需要一套能让程序自己“跑完”的流程。我的做法是三步分批处理、失败隔离、落盘检查。import json from typing import Optional from pydantic import ValidationError def extract_review(text: str, program) - Optional[MovieReview]: for attempt in range(3): try: return program(texttext) except (ValidationError, ValueError) as e: print(f第{attempt 1}次尝试失败: {e}) return None results [] for i, raw_text in enumerate(all_texts): review extract_review(raw_text, program) if review is not None: results.append(review.model_dump()) else: results.append({index: i, error: parse_failed}) with open(output.json, w, encodingutf-8) as f: json.dump(results, f, ensure_asciiFalse, indent2)这段代码的思路是把单条抽取封装成可重试函数超过重试次数就记为失败不让单个坏数据卡死整个批次。输出统一落盘方便事后检查哪几条失败、失败原因是什么。生产环境我还会加一步失败数据单独存一份随后用规则或人工方式补采而不是简单丢弃。实测下来这种“隔离失败”的方式比“遇到一条错就停整个任务”效率高得多。2.4 实战中踩过的坑结构化输出用久了各种翻车方式基本都见过说几个印象比较深的。第一个坑模型返回合法JSON但字段名不对。比如你定义sentiment模型给你返回sentiment_score或者字段名缩写成了sent。Pydantic默认忽略多余字段但缺少必填字段时依然会报错。解决办法是在description里“点名道姓”甚至直接写“必须使用字段名sentiment”。第二个坑中文内容被转义或截断。特别是一些老模型对中文长度敏感摘要字段明明要求60字它可能输出60个token但只有30个汉字。写入数据库时容易被截断。建议在validator里加上长度上限并在description里写“按汉字字数计不超过60个汉字”。第三个坑嵌套结构难解析。比如输出里有子对象、对象列表一旦某个子字段校验失败整体解析就挂了。对策是尽量把嵌套层级控制在两层以内子对象单独定义pydantic类并给每个子字段写清楚description。第四个坑是成本。结构化输出的重试机制会带来额外token消耗尤其在批量抽取场景。我一般会在重试前做一次“检查失败类型”的拦截如果是因为字段缺失重试一次如果是因为内容本身无法分类直接放弃避免无意义重试。这个判断可以靠错误信息里的关键字来做简单分类。3. 评估模块让模型回答变得可丈量3.1 先想清楚你要评估哪个环节RAG系统上线之后最怕的问题是“答案看起来很合理但和文档内容对不上”。传统测试方法基本束手无策因为答案不是确定的键值对而是一段自然语言。LlamaIndex的评估模块思路是用一套评估器对“查询-回答-参考上下文”这三元组打分把主观感受转成可重复的数值。这里必须强调一个概念评估之前先想清楚你要衡量哪一个环节。RAG链路大体分三段每段失败的症状不一样检索阶段该检索到的文档没检索到。症状是回答内容挺好但引用的上下文完全不对题。生成阶段检索到了正确的上下文但LLM没按上下文回答自己编了一段。回答质量上下文和回答都对但是没回答用户的真实意图。LlamaIndex为这三类问题分别设计了评估器。你用错评估器就像用体重秤量身高数据再准也说明不了问题。后面我会给对照表这是评估环节最重要的一份地图。3.2 LlamaIndex内置评估器速查表先看一份我用下来觉得最常用的评估器清单评估器一句话说明核心问题使用场景CorrectnessEvaluator回答与标准答案的匹配程度回答是否和参考答案一致有标准答案的测试集评估整体回答质量FaithfulnessEvaluator回答是否忠于上下文回答是否由提供的上下文支撑有没有编造RAG生成环节检验幻觉RelevancyEvaluator回答与问题的相关程度回答是否在真正回答用户的问题RAG全链路检验答非所问SemanticSimilarityEvaluator回答与标准答案的语义相似度语义是否接近而不只靠字面生成质量评估对措辞变化不敏感GuidelineEvaluator回答是否符合规则是否存在违规定、语气、格式问题有明确业务规范的场景PairwiseEvaluator两个回答谁更好在同查询下比较不同回答优劣模型选型、Prompt调优对比每个评估器内部的打分逻辑都是“让一个LLM扮演裁判”给输出打1到5分部分评估器用布尔值并附上判断理由。所以实际运行时评估也需要消耗token跟生成回答的成本量级相同。记住这一点后面控制成本的部分会用它来说事。3.3 几个评估器背后的“评分逻辑”光知道评估器名字不够还得理解它内部怎么打分才知道结果怎么解读。FaithfulnessEvaluator的做法是拿到回答里提到的若干个关键信息点逐个去上下文里找依据。如果某个信息点找不到依据就认为“不忠实”整个回答可能被判为不通过。所以你看到Faithfulness分数低第一反应应该是“回答里有上下文没有的内容”而不是“回答质量差”。RelevancyEvaluator的做法相反它把“问题”作为核对项去回答里找答案。很多时候回答长篇大论但回答的是另一个问题这种case用Relevancy才能抓出来。实测里最常见的问题是用户问“费用是多少”模型回答了一堆“如何申请”相关度自然很低。CorrectnessEvaluator更像是“阅卷老师”它拿标准答案去比对模型回答根据语义是否一致打1到5分。它对措辞不敏感但对信息完整性敏感。如果你的测试集有标准答案这个评估器是快速验证迭代效果的利器。明白评分逻辑之后你就知道为什么不能只盯着一个评估器看。我一般最少同时跑两个一个管“有没有瞎编“Faithfulness一个管“有没有答非所问”Relevancy。两者都过了再谈回答质量分。4. 实操搭一条评估流水线4.1 评估数据集从哪来评估做得再好没有数据就是空中楼阁。我接触过的项目里评估数据集来源有三种成本递增、质量也递增。第一手写黄金样本。针对业务核心场景人工写几十条查询和参考答案。这个门槛最低适合起步但覆盖度有限很难把边角case暴露出来。第二用生成器构建。LlamaIndex提供了DatasetGenerator可以从你的文档集合里自动生成“问题-上下文”对。它会把文本切分为节点针对每个节点让LLM生成若干问题。这种方式速度快能覆盖全量文档但问题质量波动大有些问题太简单有些问题问得很别扭。建议生成后人工抽检一遍把明显不合理的问题删掉。第三线上日志回流。把真实用户问题记录下来配上人工或半自动的回答标注。这是最接近线上分布的评估集质量最高但需要业务支持不是每个项目都有条件做。我的建议是分步走项目起步用手写黄金样本验证链路能跑通中期用DatasetGenerator扩充规模上线前加上日志回流形成可持续更新的评估集。不要盲目追求数据集大小八十条高质量样本比八百条生成样本更能反映问题。4.2 一键评估脚本下面是我自己项目里用的一个评估脚本模板逻辑是读入一批测试样本每条包含query和reference然后先用你的RAG系统生成response和contexts再调用多个评估器打分最后汇总输出。import asyncio import pandas as pd from llama_index.evaluation import ( CorrectnessEvaluator, FaithfulnessEvaluator, RelevancyEvaluator, ) from llama_index.core import ServiceContext from llama_index.llms.openai import OpenAI eval_llm OpenAI(modelgpt-4o, temperature0) service_context ServiceContext.from_defaults(llmeval_llm) correctness CorrectnessEvaluator(service_contextservice_context) faithfulness FaithfulnessEvaluator(service_contextservice_context) relevancy RelevancyEvaluator(service_contextservice_context) async def evaluate_one(query: str, reference: str, query_engine): response query_engine.query(query) contexts [n.node.get_content() for n in response.source_nodes] c_result await correctness.aevaluate( responsestr(response), referencereference, queryquery ) f_result await faithfulness.aevaluate( responsestr(response), contextscontexts ) r_result await relevancy.aevaluate( responsestr(response), queryquery ) return { query: query, response: str(response), correctness_score: c_result.score, correctness_passing: c_result.passing, faithfulness_score: f_result.score, faithfulness_passing: f_result.passing, relevancy_score: r_result.score, relevancy_passing: r_result.passing, } async def run_evaluation(testset, query_engine): rows [] for item in testset: row await evaluate_one(item[query], item[reference], query_engine) rows.append(row) return pd.DataFrame(rows) # testset [{query: ..., reference: ...}] # df await run_evaluation(testset, query_engine) # df.to_csv(eval_result.csv, indexFalse, encodingutf-8-sig)这套代码有几个值得注意的点。第一LLM用temperature0这非常关键让评估裁判的输出尽可能稳定。第二aevaluate是异步接口批量跑的时候速度会快很多。第三结果我直接存成CSV用Excel就能打开方便给团队其他成员检查。4.3 结果解读与badcase定位评估跑完不是终点解读才是重点。我拿到结果表之后一般按下面三步走。第一步看通过率。比如Faithfulness通过率低于80%意味着每五次回答里就有一次“上下文支撑不足”这个比例在正式环境是不能接受的需要回头查检索、查Prompt。第二步按分数分布看严重程度。同样是通过了分数4和分数5差距很大。我一般把分数低于4的case单独捞出来逐条看评估器的feedback里面通常会写明“哪个信息点在上下文中找不到依据”或“回答没有覆盖参考中的哪个要点”。第三步把失败case按业务模块归类。比如“所有和退款相关的问题都答不好”这往往不是模型问题而是退款文档缺失或检索不到。这个时候就该调整索引结构或补充文档而不是去改Prompt。很多团队把评估做成“跑分机器”跑完就结束这是最浪费的用法。评估真正的产出是badcase清单它是你迭代RAG系统的路线图。5. 常见问题与避坑经验5.1 评估分数忽高忽低最让人头疼的是同一个系统连续评估两次分数波动很大。多半原因有三个。第一评估用的LLM没设temperature0裁判自己都不稳定。第二评估样本量太少几十条数据里一两条异常case就能把平均分拉低很多。第三评估集和线上分布差异大评估集里集中了大量难题导致分数整体偏低。对策也很直接评估LLM固定用低temperature样本量尽量不低于50条评估集要混合简单和困难样本并在报告里分难度统计而不是只给一个总分。我还会在同一批数据上跑两次评估如果两次结果差异超过10%先怀疑评估器稳定性再去怀疑系统改动。5.2 结构化输出解析失败的排查清单解析失败的时候我按下面这个清单排查命中率很高先看原始返回内容。用verboseTrue打印模型原始回复确认是格式问题还是内容问题。再看Schema字段名和description确认模型是否可能产生歧义。然后检查是否用了枚举或正则限制能用Literal就不要让模型自由发挥。最后看校验器本身是不是太严格比如rating要求“保留一位小数”模型输出了整数8validator是否应该兼容整数。这里有个心态问题解析失败不一定是模型不行很可能是你的Schema设计让模型“不知道该填什么”。如果多轮排查后依然频繁失败优先简化Schema比如把5个字段减到3个或者把嵌套结构拍平效果往往立竿见影。5.3 控制成本的三个思路评估和结构化输出都费token钱的问题绕不开。我的经验是三条。一是控制重试次数重试成本可能是成功调用的2到3倍设置合理上限比无限重试更划算。二是评估集分级日常开发用一个小而精的评估集快速跑发布前再跑全量评估集而不是每次改动都全量跑。三是用便宜模型做初步过滤让大模型只处理被筛出来的高风险case比如GuidelineEvaluator可以先跑一遍便宜的过滤模型怀疑违规再让强模型做最终判决。5.4 用评估结果反哺结构化输出最后分享一个可以把两个模块串起来的技巧把评估出的badcase当成结构化输出的“补充示例”。比如某类查询下模型经常在sentiment字段里输出“偏正面”这种不在枚举里的值。你把这个失败case整理成一个few-shot示例塞进PydanticProgram的prompt模板里或者在输出Schema的描述里加一句“只能输出正面、负面、中性三个词不要加修饰语”。我实际测试中这种反哺方式比单纯调temperature更管用因为它直接给模型看了“错误示范”。另外如果你把结构化输出和评估器结合还能做自动化的质量门禁解析成功的记录进入业务逻辑解析失败但评估分数尚可的记录进入人工复核池两边都失败的直接告警。这套机制跑起来之后系统的稳定性会有一个质的提升。我个人在实际操作中的体会是结构化输出和评估都不是“加了就完事”的功能而是需要持续迭代的工程实践。每次拿到新的badcase都值得回头想一想是Schema设计的问题还是评估集覆盖的问题又或者是底层模型能力的问题。把这两个环节维护好你的LlamaIndex应用才真正算得上“能上线”。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

DR图像管理系统设计:从DICOM解析到DROC架构实践 2026/9/8 7:23:15

DR图像管理系统设计:从DICOM解析到DROC架构实践

简介:一套用C实现的数字X射线图像管理器(DROC)完整项目源码,面向有志于医疗影像软件开发的学习者、初级工程师及医学信息相关专业学生。资源包共256个文件,其中以C头文件(89个h)和源文件&#x…

阅读更多 →
图像处理四大核心目标:降噪、保真、增强与标准化实战解析 2026/9/8 7:23:15

图像处理四大核心目标:降噪、保真、增强与标准化实战解析

1. 四个核心目标怎么来的?先说我对图像处理的完整理解1.1 从相机按下快门到屏幕展示,图像处理真正在解决什么问题做图像处理这些年,接手的项目越多越发现一个规律:不管你是用 OpenCV 调几个现成函数,还是用 MATLAB 跑实…

阅读更多 →
图像预处理核心四步:降噪、保真、增强与标准化的工程实践 2026/9/8 7:23:15

图像预处理核心四步:降噪、保真、增强与标准化的工程实践

做图像处理这些年,我收到的项目需求翻来覆去其实绕不开四个词:降噪、保真、增强、标准化。这四个目标基本决定了从传感器端ISP到后端视觉算法的每一步该怎么走。今天就把这四件事拆开聊清楚:它们各自要解决什么问题、为什么不能孤立看待&…

阅读更多 →
Element UI v2.15.13 离线文档:内网开发必备,获取、原理与部署全攻略 2026/9/8 7:23:15

Element UI v2.15.13 离线文档:内网开发必备,获取、原理与部署全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
MySQL Connector/NET 6.8.3 Noinstall包使用与排坑指南 2026/9/8 7:23:15

MySQL Connector/NET 6.8.3 Noinstall包使用与排坑指南

简介:MySQL Connector/Net 6.8.3 官方免安装驱动包,面向使用 C#、VB.NET 等语言开发 .NET 应用的开发者,解决 .NET 程序连接 MySQL 时的驱动部署与调用问题,支持查询、事务、存储过程以及实体框架集成。压缩包采用 noinstall 方式…

阅读更多 →
AI动画制作全流程:即梦AI+豆包+LibTV工具链实战指南 2026/9/8 7:20:15

AI动画制作全流程:即梦AI+豆包+LibTV工具链实战指南

这次我们来看一个完整的AI动画电影制作流程,通过即梦AI、豆包和LibTV三个工具的组合,实现从创意到成片的完整生产链路。这个方案的核心优势在于工具链的平民化——不需要专业影视制作经验,用常见的AI工具就能完成动画短剧制作。 从实际效果看…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞