第一节 格式化生成
** 本节核心**:怎么让 LLM 输出"规规矩矩"的 JSON 数据,而不是一段自由散漫的文字。三种方式:① LangChain 的 PydanticOutputParser ② LlamaIndex 的 Pydantic Programs ③ 纯提示词硬写。最后还有终极方案 Function Calling。
从大语言模型(LLM)那里获得一段非结构化的文本在应用中常常不满足实际需求。为了实现更复杂的逻辑、与外部工具交互或以用户友好的方式展示数据,需要模型能够输出具有特定结构的数据,例如 JSON 或 XML。
本节将讨论实现格式化生成的几种主流方法,包括 LangChain、LlamaIndex 等框架内置的解决方案,不依赖框架的实现思路,以及一种更强大的技术------Function Calling。
在生成阶段,提示词工程也是一个重要的部分。但是因为在前面几个章节中已经有了比较多的介绍,所以本章就不再赘述了。
一、为什么需要格式化生成?
先来看几个具体的应用场景:
- RAG 驱动的电商客服:当用户询问"推荐几款适合程序员的键盘"时,我们希望 LLM 返回一个包含产品名称、价格、特性和购买链接的 JSON 列表,而不是一段描述性文字,以便前端直接渲染成商品卡片。
- 自然语言转 API 调用 :用户说"帮我查一下明天从上海到北京的航班",系统需要将这句话解析成一个结构化的 API 请求,如
{"departure": "上海", "destination": "北京", "date": "2025-07-18"}。 - 数据自动提取:从一篇新闻文章中,自动抽取出事件、时间、地点、涉及人物等关键信息,并以结构化形式存入数据库。
在这些场景中,格式化生成是连接 LLM 的自然语言理解能力和下游应用程序的程序化逻辑之间的关键。
共同规律 :上面三个场景都有一个模式:自然语言 → 结构化数据 → 下游程序使用。本质上是在 LLM 的"自由创作"和程序的"严格格式"之间搭一座桥。
二、格式化生成的实现方法
2.1 Output Parsers
LangChain 提供了一个强大的组件------OutputParsers(输出解析器),专门用于处理 LLM 的输出,其主要思想是在发送给 LLM 的提示(Prompt)中自动注入一段关于如何格式化输出的指令,并在得到结果后将 LLM 返回的纯文本字符串解析成预期的结构化数据(如 Python 对象)。
LangChain 提供了多种开箱即用的解析器,例如:
- StrOutputParser:最基础的输出解析器,它简单地将 LLM 的输出作为字符串返回。
- JsonOutputParser:可以解析包含嵌套结构和列表的复杂 JSON 字符串。
- PydanticOutputParser:通过与 Pydantic 模型结合,可以实现对输出格式最严格的定义和验证。
三种Parser怎么选? :
StrOutputParser最省事但拿到的是纯文本;JsonOutputParser适合只要 JSON 不管结构对不对的场景;PydanticOutputParser最严格------它不只要 JSON,还要检查字段名和类型是否完全匹配你定义的模型。
接下来通过一个具体的代码示例,重点分析 PydanticOutputParser 的工作原理。它通过将用户定义的 Pydantic 数据模型转换为详细的格式指令,并注入到提示词中,来引导 LLM 生成严格符合该数据结构的 JSON 输出。最后再将模型返回的 JSON 字符串安全地解析为 Pydantic 对象实例。
python
# (此处省略了导入和 LLM 初始化代码)
# ── 第1步:定义数据模型(相当于画一个"模板",告诉LLM我要什么字段) ──
class PersonInfo(BaseModel):
"""用于存储个人信息的数据结构。"""
name: str = Field(description="人物姓名") # description会被注入提示词,帮助LLM理解
age: int = Field(description="人物年龄") # 类型int表示LLM必须输出数字
skills: List[str] = Field(description="技能列表") # List[str]表示输出字符串列表
# ── 第2步:创建解析器(Parser会自动生成"格式指令"文本) ──
parser = PydanticOutputParser(pydantic_object=PersonInfo)
# parser.get_format_instructions() → 自动生成一段话:
# "请输出一个JSON对象,包含name(string), age(int), skills(array[string])..."
# ── 第3步:创建提示模板,把格式指令混进提示词 ──
prompt = PromptTemplate(
template="请根据以下文本提取信息。\n{format_instructions}\n{text}\n",
input_variables=["text"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
# 实际发给LLM的提示 ≈ "请根据以下文本提取信息。\n请输出JSON...\n张三今年30岁...\n"
# ── 第4步:用 LCEL(LangChain 表达式语言)把三个组件串成一条链 ──
chain = prompt | llm | parser # 数据流:prompt组装消息 → llm生成回复 → parser解析成对象
# ── 第5步:执行 ──
text = "张三今年30岁,他擅长Python和Go语言。"
result = chain.invoke({"text": text})
# 背后发生了什么:
# ① prompt 把 text 和 format_instructions 拼成完整提示 → 发给 llm
# ② llm 返回 JSON 字符串 → parser 收到
# ③ parser 先 JsonOutputParser 把字符串转成 dict
# ④ 再 model_validate() 验证 dict 是否符合 PersonInfo 定义
# ⑤ 验证通过 → 返回 PersonInfo 对象;失败 → 抛异常
# 6. 打印结果
print(result)
# name='张三' age=30 skills=['Python', 'Go语言']
(1)定义数据模型 :使用 Pydantic 的 BaseModel 定义 PersonInfo 类,这不仅是一个 Python 对象,更是一个清晰的数据结构规范(Schema)。Field 中的 description 描述文本将直接作为指令提供给大模型,因此其表述需要清晰准确。
(2)生成格式指令 :当 PydanticOutputParser 实例化后,其 get_format_instructions() 方法会执行以下操作:
- 调用 Pydantic 模型的
.model_json_schema()方法,提取出该数据结构的 JSON Schema 定义。 - 对该 Schema 进行简化,并将其嵌入到一个预设的、指导性的提示模板中。这个模板明确要求 LLM 输出一个符合该 Schema 的 JSON 对象。
(3)构建并执行调用链 :通过 LangChain 表达式语言(LCEL),将 prompt、llm 和 parser 链接起来。当调用链被触发时:
prompt会将用户输入(text)和上一步生成的格式指令(format_instructions)组合成最终的提示,发送给llm。llm根据这个包含严格格式要求的提示,生成一个 JSON 格式的字符串。
(4)解析与验证 :PydanticOutputParser 接收到 LLM 返回的字符串后,会执行一个两步解析过程:
- 首先,它继承自
JsonOutputParser,会将 LLM 输出的文本字符串解析成一个 Python 字典。 - 然后,最关键的一步,它会使用
PersonInfo.model_validate()方法,用定义的数据模型来验证这个字典。如果字典的键和值类型都符合PersonInfo的定义,解析器就会返回一个PersonInfo的实例对象;如果验证失败,则会抛出一个OutputParserException异常。
整个流程 :定模型 → 建解析器 → 拼提示词 → 串成链 → 调一次全自动。关键是 Parser 替你干了"写格式要求"和"检查输出"两件麻烦事。
2.2 LlamaIndex 的输出解析
** 跟 LangChain 的区别**:LangChain 是"先输出文本再解析"(parse after generation),LlamaIndex 是"在生成时就介入"(structured generation),理念上更底层。如果 LLM 支持 Function Calling,LlamaIndex 会直接用,更可靠。
LlamaIndex 的输出解析与生成过程紧密结合,主要体现在两大核心组件中,分别是响应合成(Response Synthesis)和结构化输出(Structured Output)。
在 RAG 流程中,检索器召回一系列相关的文本块 (Nodes)后,并不是简单地将它们拼接起来。响应合成器 (Response Synthesizer)负责接收这些文本块和原始查询,并以一种更智能的方式 将它们呈现给 LLM 以生成最终答案。例如,它可以逐块处理信息并迭代地优化答案(refine 模式),或者将尽可能多的文本块压缩进单次 LLM 调用中(compact 模式)。这个阶段的默认目标是生成一段高质量的文本回答。
将原本的文本块进行进一步处理-再给LLM
当需要 LLM 返回结构化数据(如 JSON)而非纯文本时,LlamaIndex 主要使用 Pydantic 程序(Pydantic Programs) 。这与 LangChain 的 PydanticOutputParser 思想一致:
- 定义 Schema:开发者首先定义一个 Pydantic 模型,明确所需输出的数据结构、字段和类型。
- 引导生成:LlamaIndex 会将这个 Pydantic 模型转换成 LLM 能理解的格式指令。如果底层的 LLM 支持 Function Calling,LlamaIndex 会优先使用该功能以获得更可靠的结构化输出。如果不支持,它会回退到将 JSON Schema 注入到提示词中的方法。
- 解析验证:最后,LLM 返回的输出会被自动解析并用 Pydantic 模型进行验证,确保其类型和结构完全正确,最终返回一个 Pydantic 对象实例。
2.3 不依赖框架的简单实现思路
如果不想依赖特定的框架,也可以通过提示工程的技巧来实现格式化生成。
主要思路是在提示中给出清晰、明确的指令和示例。以下是一些实用技巧:
- 明确要求 JSON 格式:在提示中直接、强硬地要求模型"必须返回一个 JSON 对象"、"不要包含任何解释性文字,只返回 JSON"。
- 提供 JSON Schema:在提示中给出你想要的 JSON 对象的模式(Schema),描述每个键的含义和数据类型。
- 提供 few-shot 示例:给出 1-2 个"用户输入 -> 期望的 JSON 输出"的完整示例,让模型学习输出的格式和风格。
- 使用语法约束 :对于一些本地部署的开源模型(如通过
llama.cpp运行的模型),可以使用 GBNF (GGML BNF) 等语法文件来强制约束模型的输出,确保其生成的每一个 token 都严格符合预定义的 JSON 语法。这是最严格也是最可靠的非 Function Calling 方法。
** 可靠性排序**:语法约束(GBNF) > Function Calling > PydanticOutputParser > 纯提示词。越靠上越可靠但实现也越复杂。
三、Function Calling
普通格式化生成 = 让 LLM "写" JSON;Function Calling = 让 LLM "调用函数"返回参数。后者是模型原生能力,更稳定,还能让 LLM 真正"做事"(比如查天气、调数据库)。
Function Calling(或称 Tool Calling)是近年来 LLM 领域的一个重要进展,提升了模型与外部世界交互和生成结构化数据的能力。
3.1 概念与工作流程
Function Calling 的本质是一个多轮对话流程,让模型、代码和外部工具(如 API)协同工作。其核心工作流如下:
(1)定义工具:首先,在代码中以特定格式(通常是 JSON Schema)定义好可用的工具,包括工具的名称、功能描述、以及需要的参数。
(2)用户提问:用户发起一个需要调用工具才能回答的请求。
(3)模型决策 :模型接收到请求后,分析用户的意图,并匹配最合适的工具。它不会直接回答,而是返回一个包含 tool_calls 的特殊响应。这个响应相当于一个指令:"请调用某某工具,并使用这些参数"。
(4)代码执行 :应用接收到这个指令,解析出工具名称和参数,然后在代码层面实际执行这个工具(例如,调用一个真实的天气 API)。
(5)结果反馈 :将工具的执行结果(例如,从 API 获取的真实天气数据)包装成一个 role 为 tool 的消息,再次发送给模型。
(6)最终生成:模型接收到工具的执行结果后,结合原始问题和工具返回的信息,生成最终的、自然的语言回答。
Function Calling 不是 LLM 真的去调 API,而是 LLM 决定"该调哪个函数、传什么参数" ,实际执行还是你的代码。流程是:User → Model(返回函数名+参数)→ 你的代码执行 → 把结果喂回 Model → Model 给出最终回答。一共两次调用 LLM。
3.2 Function Calling 实践
接下来,直接使用 openai 的例子,来展示上述流程。
python
# ── 第1步:定义工具(用 JSON Schema 描述"有什么函数可用") ──
tools = [...] # 比如 [{name:"get_weather", description:"获取天气", parameters:{...}}]
# ── 第2步:用户提问(第一次调用:User → Model) ──
messages = [{"role": "user", "content": "杭州今天天气怎么样?"}]
message = send_messages(messages, tools=tools)
# ↓ 此时 message.tool_calls 不为空 → 模型决定调用 get_weather(location="杭州")
# ── 第3步:代码执行(解析模型返回的指令,真的去执行函数) ──
if message.tool_calls:
tool_call = message.tool_calls[0] # 取第一个工具调用
messages.append(message) # 把模型的"调用指令"加入历史
tool_output = "24℃,晴朗" # 真实场景:调天气API获取结果
messages.append({
"role": "tool", # role="tool" 表示这是工具返回的结果
"tool_call_id": tool_call.id, # 关联到对应的工具调用
"content": tool_output
}) # ← 把工具结果追加到对话历史
# ── 第4步:第二次调用(Tool → Model):把结果给模型,生成自然语言回答 ──
final_message = send_messages(messages, tools=tools)
print(final_message.content) # 输出:"杭州今天24℃,天气晴朗。"
关键步骤:
(1)定义 tools :用一个列表包含了所有可用的函数定义。每个定义都是一个 JSON 对象,严格描述了函数的名称 (name)、功能 (description) 和参数 (parameters)。这个描述的质量直接决定了模型能否正确选择和使用工具。
(2)第一次调用 (User -> Model) :将用户的原始问题("role": "user")和 tools 列表一同发送给模型。
(3)处理 tool_calls :检查模型的响应中是否包含 tool_calls。如果包含,就说明模型决定使用工具。解析出函数名和参数,并模拟执行(在真实场景中,这里会是真实的 API 调用)。
(4)第二次调用 (Tool -> Model) :将原始的用户问题、模型的工具调用响应,以及模拟执行后得到的工具结果("role": "tool"),一同打包成新的对话历史,再次发送给模型。
(5)获取最终答案:模型在看到工具的执行结果后,就能用自然语言回答用户最初的问题了。
3.3 Function Calling 的优势
相比于单纯通过提示工程"请求"模型输出 JSON,Function Calling 的优势在于:
- 可靠性更高:这是模型原生支持的能力,相比于解析可能格式不稳定的纯文本输出,这种方式得到的结构化数据更稳定、精确。
- 意图识别:它不仅仅是格式化输出,更包含了"意图到函数的映射"。模型能根据用户问题主动选择最合适的工具。
- 与外部世界交互:它是构建能执行实际任务的 AI 代理(Agent)的核心基础,让 LLM 可以查询数据库、调用 API、控制智能家居等。
第六章 RAG 系统评估
怎么判断你的 RAG 系统好不好?从三个维度(检索准不准、回答有没有瞎编、答没答到点上)来打分。本章介绍评估方法和常用工具。
第一节 评估介绍
构建RAG系统后,下一个关键问题是:如何科学地评估其表现?
评估之所以关键,是因为它回答了RAG开发与应用中的一系列核心问题:
- 对于开发者: 如何量化地追踪、迭代并提升RAG应用的性能?当系统出现"幻觉"或答非所问时,如何快速定位问题根源?
- 对于用户或决策者: 面对两个不同的RAG应用,如何客观地评判孰优孰劣?
本节将探讨RAG评估的理念与方法,并围绕 "RAG三元组(RAG Triad)" 展开。
一、RAG评估三元组
该架构包含以下三个维度,并在 TruLens 等工具中有深入的应用:
(1)上下文相关性 (Context Relevance)
- 评估目标: 检索器(Retriever)的性能。
- 核心问题: 检索到的上下文内容,是否与用户的查询(Query)高度相关?
- 重要性: 检索是RAG应用在响应用户查询时的第一步。如果检索回来的上下文充满了噪声或无关信息,那么无论后续的生成模型多么强大,都没法做出正确答案。
(2)忠实度 / 可信度 (Faithfulness / Groundedness)
- 评估目标: 生成器的可靠性。
- 核心问题: 生成的答案是否完全基于所提供的上下文信息?
- 重要性: 这个维度主要在于量化LLM的"幻觉"程度。一个高忠实度的回答意味着模型严格遵守了上下文,没有捏造或歪曲事实。如果忠实度得分低,说明LLM在回答时"自由发挥"过度,引入了外部知识或不实信息。
(3)答案相关性 (Answer Relevance)
- 评估目标: 系统的端到端(End-to-End)表现。
- 核心问题: 最终生成的答案是否直接、完整且有效地回答了用户的原始问题?
- 重要性: 这是用户最直观的感受。一个答案可能完全基于上下文(高忠实度),但如果它答非所问,或者只回答了问题的一部分,那么这个答案的相关性就很低。例如,当用户问"法国在哪里,首都是哪里?",如果答案只是"法国在西欧",那么虽然忠实度高,但答案相关性很低。
"查得准(上下文相关)、不瞎编(忠实度)、答到点(答案相关)"**。三个维度分别对应:检索器好不好、LLM有没有幻觉、整个系统有没有用。
它们的侧重点是不同的。忠实度更关注模型是否严格遵循了上下文,而答案相关性则更关注模型是否直接、完整且有效地回答了问题。
通过对这三个维度进行评估,可以对RAG系统的表现有一个全面而细致的了解,并能准确定位问题所在:是检索出了问题,还是生成环节有待改进。
二、评估工作流
虽然上面把评估分成了三个部分,但实际上可以把评估过程拆解为两个主要环节:检索评估 和响应评估。
2.1 检索评估
检索评估聚焦于RAG三元组中的 上下文相关性 (Context Relevance) ,本质上是一次白盒测试。此阶段的评估需要一个标注数据集,其中包含一系列查询以及每个查询对应的真实相关文档。
这项评估借鉴了信息检索领域的多个经典指标:
-
上下文精确率 (Context Precision): 衡量检索结果的准确性。计算在检索到的前 k 个文档中相关文档所占的比例,其中 k 是一个预设的数字(例如,k=3或k=5),代表评估的范围。高精确率意味着检索结果的噪声较少。
Precision@k=检索到的k个结果中的相关文档数k \text{Precision}@k = \frac{\text{检索到的}k\text{个结果中的相关文档数}}{k} Precision@k=k检索到的k个结果中的相关文档数
-
上下文召回率 (Context Recall): 衡量检索结果的完整性。计算在检索到的前 k 个文档中,找到的相关文档占所有真实相关文档总数的比例。高召回率意味着系统能够成功找回大部分关键信息。
Recall@k=检索到的k个结果中的相关文档数数据集中所有相关的文档总数 \text{Recall}@k = \frac{\text{检索到的}k\text{个结果中的相关文档数}}{\text{数据集中所有相关的文档总数}} Recall@k=数据集中所有相关的文档总数检索到的k个结果中的相关文档数
-
F1分数 (F1-Score): F1分数是精确率和召回率的调和平均数,它同时兼顾了这两个指标,在它们之间寻求平衡。当精确率和召回率都高时,F1分数也高。
F1=2⋅Precision×RecallPrecision+Recall F_1 = 2 \cdot \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}} F1=2⋅Precision+RecallPrecision×Recall
-
平均倒数排名 (MRR - Mean Reciprocal Rank): 评估系统将第一个相关文档排在靠前位置的能力。对于一个查询,倒数排名是第一个相关文档排名的倒数。MRR是所有查询的倒数排名的平均值。该指标适用于用户通常只关心第一个正确答案 的场景。
MRR=1∣Q∣∑q=1∣Q∣1rankq \text{MRR} = \frac{1}{|Q|} \sum_{q=1}^{|Q|} \frac{1}{\text{rank}_q} MRR=∣Q∣1q=1∑∣Q∣rankq1
其中
|Q|是查询总数,rank_q是第q个查询的第一个相关文档的排名。 -
平均准确率均值 (MAP - Mean Average Precision): MAP是一个综合性指标,同时评估了检索结果的精确率和相关文档的排名。它先计算每个查询的平均精确率 (AP),然后对所有查询的AP取平均值。AP本身是基于每个相关文档被检索到时的精确率计算的。
MAP=1∣Q∣∑q=1∣Q∣AP(q) \text{MAP} = \frac{1}{|Q|} \sum_{q=1}^{|Q|} \text{AP}(q) MAP=∣Q∣1q=1∑∣Q∣AP(q)
其中
|Q|是查询总数,AP(q)是第q个查询的平均精确率(Average Precision)。
Precision = "查出来的东西里多少是有用的";Recall = "所有有用的东西里我查出来了多少";F1 = 它俩的平均值;MRR = "第一个正确答案排第几";MAP = "所有正确答案的排名综合得分"。
要计算上述所有指标,前提是拥有一个高质量的标注数据集,其中包含了查询和每个查询对应的"真实"相关文档。
2.2 响应评估
响应评估覆盖了RAG三元组中的 忠实度 和 答案相关性 。此环节通常采用 端到端 的评估范式,因为它直接衡量用户感知的最终输出质量。无论采用何种评估方法,都主要围绕以下两个核心维度展开。
2.2.1 评估维度
(1)忠实度 / 可信度:衡量生成的答案在多大程度上可以由给定的上下文所证实。一个完全忠实的答案,其所有内容都必须能在上下文中找到依据,以此避免模型产生"幻觉"。
(2)答案相关性:衡量生成的答案与用户原始查询的对齐程度。一个高相关性的答案必须是直接的、切题的,并且不包含与问题无关的冗余信息。
2.2.2 主要评估方法
针对上述维度,目前主要有两类评估方法:
(1)基于大语言模型的评估
这是一种强大的评估方法,能够提供更深度的语义评估,正逐渐成为主流选择。利用一个高性能、中立的llm作为"评估者",对上述维度进行深度的语义理解和打分。
- 忠实度评估: 首先,将生成的答案分解为一系列独立的声明或断言(Claims)。然后,对于每一个断言,在提供的上下文中进行验证,判断其真伪。最终的忠实度分数是所有被上下文证实的断言所占的比例。
- 答案相关性评估: 评估者需要同时分析用户查询和生成的答案。评分时会惩罚那些答非所问、信息不完整或包含过多无关细节的答案。
(2)基于词汇重叠的经典指标
这类指标需要在数据集中包含一个或多个"标准答案"。它们通过计算生成答案与标准答案之间 n-gram(连续的n个词)的重叠程度来评估质量。
-
ROUGE (Recall-Oriented Understudy for Gisting Evaluation): ROUGE关注的重点是 召回率 ,即标准答案中的词语有多少被生成答案所覆盖,因此常用于评估内容的 完整性 。其常用变体包括计算n-gram的
ROUGE-N和计算最长公共子序列的ROUGE-L。ROUGE-N=匹配的 n-gram 数量参考答案中 n-gram 的总数 \text{ROUGE-N} = \frac{\text{匹配的 } n\text{-gram 数量}}{\text{参考答案中 } n\text{-gram 的总数}} ROUGE-N=参考答案中 n-gram 的总数匹配的 n-gram 数量
-
BLEU (Bilingual Evalu ation Understudy): BLEU侧重于评估 精确率 ,衡量生成的答案中有多少词是有效的(即在标准答案中出现过)。它还引入了长度惩罚机制,避免模型生成过短的句子,因此更适合评估答案的 流畅度和准确性。
BLEU=BP×exp(∑n=1Nwnlogpn) \text{BLEU} = \text{BP} \times \exp\left(\sum_{n=1}^{N} w_n \log p_n\right) BLEU=BP×exp(n=1∑Nwnlogpn)
其中,
BP是长度惩罚因子,p_n是修正后的n-gram精确率。 -
METEOR (Metric for Evaluation of Translation with Explicit ORdering): 作为BLEU的改进版,METEOR同时考量 精确率和召回率 的调和平均,并通过词干和同义词匹配(如将'boat'和'ship'视为相关)来更好地捕捉语义相似性。其评估结果通常被认为与人类判断的相关性更高。
Fmean=P×RαP+(1−α)R F_{\text{mean}} = \frac{P \times R}{\alpha P + (1-\alpha)R} Fmean=αP+(1−α)RP×R
textMETEOR=Fmean×(1−Penalty) text{METEOR} = F_{\text{mean}} \times (1 - \text{Penalty}) textMETEOR=Fmean×(1−Penalty)
其中
P是精确率,R是召回率,Penalty是基于语序的惩罚项。
为了更直观地理解三者的区别,来看一个简单的例子。
假设:
- 参考答案:
狗 在 床 上面(共5个词)- 生成答案:
狗 在 床 上(共4个词)评估分析:
ROUGE (召回率导向): 从召回率的角度出发:"参考答案里的5个词,生成答案覆盖了多少?"------覆盖了4个。因此,它的召回率很高(ROUGE-1 为 4/5),得分会不错。ROUGE更关心"说全了没"。
BLEU (精确率导向): 从精确率的角度进行评判:"生成答案里的4个词,有多少是有效的(在参考答案里)?"------全部有效,精确率很高。但它会发现生成答案比参考答案短,于是通过 长度惩罚(Brevity Penalty) 进行扣分。BLEU更关心"说对了没,以及长度是否合适"。
METEOR (综合平衡): 同时计算精确率和召回率,并取一个调和平均。在这个例子里,词序是完全正确的,惩罚项为0。METEOR会在"说全"和"说对"之间找到一个最佳平衡点。
2.2.3 方法对比和总结
基于LLM的评估更注重语义和逻辑 ,评估质量高,但成本也更高且存在评估者偏见。基于词汇重叠的指标客观、计算快、成本低,但无法理解语义,可能误判同义词或释义。在实践中,可以将两者结合,使用经典指标进行快速、大规模的初步筛选,再利用LLM进行更精细的评估。
传统指标(ROUGE/BLEU/METEOR)看"字面像不像";LLM评估看"意思对不对"。最好先用传统指标筛一遍,再用LLM仔细审。
第二节 评估常用工具
了解了评估的基本原理之后,来介绍几个RAG评估工具,它们各自代表了不同的设计哲学和应用场景。
三大工具:
- LlamaIndex Evaluation = 如果你用 LlamaIndex 搭 RAG,内置直接评估,最方便
- RAGAS = 轻量独立评估,不管什么框架都能用,支持无标准答案评估
- Phoenix = 生产环境监控,可视化 tracing,适合上线后看真实表现
一、LlamaIndex Evaluation
LlamaIndex Evaluation 是深度集成于LlamaIndex框架内的评估模块 ,专为使用该框架构建的RAG应用提供无缝的评估能力。作为RAG开发框架的原生组件,其核心定位是为开发者在开发、调试和迭代周期中提供快速、灵活的嵌入式评估解决方案。它强调与开发流程的紧密结合,允许开发者在构建过程中即时验证和对比不同RAG策略的性能。
适用场景 :对于深度使用
LlamaIndex框架构建RAG应用的开发者而言,其内置评估模块是无缝集成的首选,提供了一站式的开发与评估体验。
1.1 核心理念与工作流
LlamaIndex 的评估理念是利用LLM作为"裁判",以自动化的方式对RAG系统的各个环节进行打分。这种方法在很多场景下无需预先准备"标准答案",大大降低了评估门槛。其典型工作流如下:
- 准备评估数据集 :通过
DatasetGenerator从文档中自动生成问题-答案对(QueryResponseDataset),或加载一个已有的数据集。为了效率,通常会将生成的数据集保存到本地,避免重复生成。 - 构建查询引擎 :搭建一个或多个需要被评估的RAG查询引擎(
QueryEngine)。这是进行对比实验的基础。 - 初始化评估器 :根据评估维度,选择并初始化一个或多个评估器,如
FaithfulnessEvaluator(忠实度)和RelevancyEvaluator(相关性)。 - 执行批量评估 :使用
BatchEvalRunner来管理整个评估过程。它能够高效地(可并行)将查询引擎应用于数据集中的所有问题,并调用所有评估器进行打分。 - 分析结果:从评估运行器返回的结果中,计算各项指标的平均分,从而量化地对比不同RAG策略的优劣。
1.2 应用实例:对比不同检索策略
下面示例基于我们在第三章学习的"句子窗口检索"技术,通过评估,对比它与"常规分块检索"在响应质量上的差异。
代码示例:
python
# ... (省略数据加载、文档解析、查询引擎构建等步骤)
# 1. 初始化评估器
# 定义需要评估的指标:忠实度和相关性
faithfulness_evaluator = FaithfulnessEvaluator(llm=Settings.llm)
relevancy_evaluator = RelevancyEvaluator(llm=Settings.llm)
evaluators = {"faithfulness": faithfulness_evaluator, "relevancy": relevancy_evaluator}
# 2. 使用BatchEvalRunner执行批量评估
# 从数据集中获取查询列表
queries = response_eval_dataset.queries
# 评估"句子窗口检索"引擎
print("\n=== 评估句子窗口检索 ===")
sentence_runner = BatchEvalRunner(evaluators, workers=2, show_progress=True)
sentence_response_results = await sentence_runner.aevaluate_queries(
queries=queries, query_engine=sentence_query_engine
)
# 评估"常规分块检索"引擎
print("\n=== 评估常规分块检索 ===")
base_runner = BatchEvalRunner(evaluators, workers=2, show_progress=True)
base_response_results = await base_runner.aevaluate_queries(
queries=queries, query_engine=base_query_engine
)
# 3. 分析并打印结果
# ... (省略结果计算与打印的辅助函数)
print(f"句子窗口检索: 忠实度={sentence_faith:.1%}, 相关性={sentence_rel:.1%}")
print(f"常规分块检索: 忠实度={base_faith:.1%}, 相关性={base_rel:.1%}")
输出如下:
bash
============================================================
响应评估结果对比
============================================================
句子窗口检索:
忠实度: 53.3%
相关性: 66.7%
常规分块检索:
忠实度: 0.0%
相关性: 6.7%
通过这个结果可以看出,在本次实验中"句子窗口检索"的忠实度和相关性上均显著优于"常规分块检索"。
1.3 核心评估维度
LlamaIndex提供了丰富的评估器,覆盖了从检索到响应的各个环节。上述示例中主要使用了响应评估维度:
Faithfulness(忠实度): 评估生成的答案是否完全基于检索到的上下文,是检测"幻觉"现象的关键指标。分数越高,说明答案越可靠。Relevancy(相关性): 评估生成的答案与用户提出的原始问题是否直接相关,确保答案切题。
此外,它还支持专门的检索评估维度,如:
Hit Rate(命中率): 评估检索到的上下文中是否包含了正确的答案。MRR(平均倒数排名): 衡量找到正确答案的效率,排名越靠前得分越高。
二、RAGAS
RAGAS(RAG Assessment)是一个独立的、专注于RAG的开源评估框架 。提供了一套全面的指标来量化RAG管道的检索和生成两大核心环节的性能。其最显著的特色是支持无参考评估 ,即在许多场景下无需人工标注的"标准答案"即可进行评估,极大地降低了评估成本。现对RAG管道的持续监控和改进。如果你需要一个轻量级、与具体RAG实现解耦、能够快速对核心指标进行量化评估的工具时,RAGAS 是一个理想的选择。
2.1 设计理念
RAGAS 的核心思想是通过分析问题(question)、生成的答案(answer)和检索到的上下文(context)三者之间的关系,来综合评估RAG系统的性能。它将复杂的评估问题分解为几个简单、可量化的维度。
2.2 工作流程与核心指标
RAGAS的评估流程非常简洁,通常遵循以下步骤:
(1)准备数据集 :根据官方文档,一个标准的评估数据集应包含 question(问题)、answer(RAG系统生成的答案)、contexts(检索到的上下文)以及 ground_truth(标准参考答案)这四列。不过,ground_truth 对于计算 context_recall 等指标是必需的,但对于 faithfulness 等指标则是可选的。
(2)运行评估 :调用 ragas.evaluate() 函数,传入准备好的数据集和需要评估的指标列表。
(3)分析结果:获取一个包含各项指标量化分数的评估报告。
其核心评估指标包括:
faithfulness: 衡量生成的答案中有多少比例的信息是可以由检索到的上下文所支持的。context_recall: 衡量检索到的上下文与标准答案(ground_truth)的对齐程度,即标准答案中的信息是否被上下文完全"召回"。context_precision: 衡量检索到的上下文中,信噪比如何,即有多少是真正与回答问题相关的。answer_relevancy: 评估答案与问题的相关程度。此指标不评估事实准确性,只关注答案是否切题。
三、Phoenix (Arize Phoenix)
Phoenix (现由Arize维护) 是一个开源的LLM可观测性与评估平台 。在RAG评估生态中,它主要扮演生产环境中的可视化分析与故障诊断引擎 的角色。它通过捕获LLM应用的轨迹(Traces),提供强大的可视化、切片和聚类分析能力,帮助开发者理解线上真实数据的表现。Phoenix 的核心价值在于从海量生产数据中发现问题、监控性能漂移并进行深度诊断,是连接线下评估与线上运维的关键桥梁。它不仅提供评估指标,更强调对LLM应用进行追踪(Tracing)和可视化分析,从而快速定位问题。
3.1 核心理念
Phoenix 的核心是"AI可观测性",它通过追踪RAG系统内部的每一步调用(如检索、生成等),将整个流程可视化。这使得开发者可以直观地看到每个环节的输入、输出和耗时,并在此基础上进行深入的评估和调试。
3.2 工作原理
Phoenix 的工作流程是先通过基于开放标准 OpenTelemetry 的代码插桩(Instrumentation) ,在 RAG 应用中集成追踪功能,自动捕获 LLM 调用、函数执行等事件;随后在应用运行过程中持续生成追踪数据(Traces) ,记录完整的执行链路;接着在本地启动 Phoenix 的 Web 界面,加载并可视化这些追踪数据;最后在 UI 中对失败案例或表现不佳的查询进行筛选、钻取,并借助内置的**评估器(Evals)**完成深入的评估与调试。
特色功能:
- 可视化追踪: 将RAG的执行流程、数据和评估结果进行可视化展示,极大地方便了问题定位。
- 根本原因分析: 通过可视化的界面,可以轻松地对表现不佳的查询进行切片和钻取。
- 安全护栏 (
Guardrails): 允许为应用添加保护层,防止恶意或错误的输入输出,保障生产环境安全。 - 数据探索与标注: 提供数据探索、清洗和标注工具,帮助开发者利用生产数据反哺模型和系统优化。
- 与Arize平台集成 :
Phoenix可以与Arize的商业平台无缝对接,实现生产环境中对RAG系统的持续监控。
四、对比建议
| 工具 | 核心机制 | 独特技术 | 典型应用场景 |
|---|---|---|---|
| RAGAS | LLM驱动评估 | 合成数据生成、无参考评估架构 | 对比不同RAG策略、版本迭代后的性能回归测试 |
| LlamaIndex | 嵌入式评估 | 异步评估引擎、模块化BaseEvaluator | 开发过程中快速验证单个组件或完整管道的效果 |
| Phoenix | 追踪分析型 | 分布式追踪、向量聚类分析算法 | 生产环境监控、Bad Case分析、数据漂移检测 |
在实践中,这些工具并非互斥,可以结合使用,以获得对RAG系统更全面、多维度的洞察。