🚀 快速安装
复制以下命令并运行,立即安装此 Skill:
npx skills add https://skills.sh/github/awesome-copilot/eval-driven-dev
💡 提示:需要 Node.js 和 NPM
面向 Python LLM 应用的评估驱动开发
此技能是关于执行工作,而非描述工作。当用户要求您为他们的应用设置评估时,您应该阅读他们的代码、编辑文件、运行命令并产生一个可工作的测试管道——而不是为他们编写一份日后执行的计划。
启动检查(始终优先)
尝试在用户环境中升级 pixie-qa 包。从项目中检测包管理器(检查是否存在 uv.lock、poetry.lock、requirements.txt 或纯 pip 环境),并运行相应的升级命令:
- uv:
uv add pixie-qa --upgrade(或uv sync --upgrade-package pixie-qa) - poetry:
poetry add pixie-qa@latest - pip:
pip install --upgrade pixie-qa
如果升级失败(例如,无网络连接、版本冲突),记录错误并继续——失败的升级不能阻止技能的其他部分执行。
所有 pixie 生成的文件都存放在项目根目录下一个名为 pixie_qa 的单一目录中:
pixie_qa/
MEMORY.md # 您的理解与评估计划
observations.db # SQLite 跟踪数据库(由 enable_storage 自动创建)
datasets/ # 黄金数据集(JSON 文件)
tests/ # 评估测试文件(test_*.py)
scripts/ # 辅助脚本(run_harness.py、build_dataset.py 等)
设置 vs. 迭代:何时停止
这一点至关重要。 您做什么取决于用户的要求。
“设置 QA” / “设置评估” / “添加测试”(设置意图)
用户想要一个可工作的评估管道。您的工作是完成阶段 0-7:安装、理解、插桩、构建运行测试工具、捕获真实跟踪、编写测试、构建数据集、运行测试。在第一次测试运行后停止,无论测试是通过还是失败。报告:
- 您设置的内容(插桩、运行测试工具、测试文件、数据集)
- 测试结果(通过/失败、分数)
- 如果测试失败:简要总结失败的内容和可能的原因——但不要修复任何东西
然后询问:“QA 设置完成。测试显示 N/M 通过。需要我调查失败原因并开始迭代吗?”
仅在用户确认后才继续执行阶段 8(调查和修复)。
例外:如果测试运行本身出错(导入失败、缺少 API 密钥、配置错误)——这些是设置问题,而非评估失败。修复它们并重新运行,直到获得一个干净的测试执行结果,其中通过/失败反映的是应用程序的实际质量,而不是管道故障。
“修复” / “改进” / “调试” / “为什么 X 失败了”(迭代意图)
用户希望您调查并修复。继续执行所有阶段,包括阶段 8——调查失败、根本原因分析、应用修复、重建数据集、重新运行测试、迭代。
意图模糊的请求
如果意图不明确,默认仅设置,并在迭代前询问。最好及早停止并询问,而不是对用户的应用程序代码做出不希望的更改。
硬性门槛:何时停止并让用户介入
某些障碍无法绕过。当遇到时,停止工作并告诉用户您需要什么——不要猜测、编造数据或跳过后续阶段。
缺少 API 密钥或凭证
如果应用或评估器需要 API 密钥(例如 OPENAI_API_KEY),但该密钥未在环境或 .env 文件中设置,请告诉用户缺少哪个密钥,并等待他们提供。不要:
- 继续运行应用或评估(它们会失败)
- 硬编码占位符密钥
- 跳过后续阶段,希望无关紧要
无法从脚本运行应用
如果在阅读代码(阶段 1)后,您无法弄清楚如何从独立脚本调用应用的核心 LLM 调用函数——因为它需要运行中的服务器、Webhook 触发器、复杂的认证或无法模拟的外部基础设施——停止并询问用户:
“我已确定
<file>中的<function_name>是要评估的核心函数,但它需要<dependency>,我无法轻松模拟。您能否 (a) 告诉我如何独立调用此函数,或 (b) 自己使用几个代表性输入运行应用,以便我捕获跟踪?”
运行测试工具执行期间的应用错误
如果运行测试工具脚本(阶段 4)出错,且两次尝试后仍无法修复,请停止并与用户分享错误。常见障碍包括数据库连接、缺少配置文件、认证/OAuth 流程以及与硬件相关的依赖项。
为何停止很重要
后续的每个阶段都依赖于从实际应用中获取真实跟踪。如果您无法运行应用,就无法捕获跟踪。如果无法捕获跟踪,就无法构建真实的数据集。如果您编造一个数据集,整个评估管道测试的将是虚构,而不是用户的应用。及早停止并寻求用户帮助,总比产生一个测试错误内容的评估管道要好。
评估边界:要评估什么
评估驱动开发关注的是依赖于 LLM 的行为。 其目的是捕获系统中那些非确定性且难以用传统单元测试测试的部分的质量退化——即 LLM 调用及其驱动的决策。
评估范围内
- LLM 响应质量:事实准确性、相关性、格式合规性、安全性
- 智能体路由决策:LLM 是否选择了正确的工具/转接/操作?
- 提示词有效性:提示词是否能产生期望的行为?
- 多轮对话连贯性:智能体是否能在多轮对话中保持上下文?
评估范围外
- 工具实现(数据库查询、API 调用、关键词匹配、业务逻辑)——这些是传统软件;使用单元测试进行测试
- 基础设施(认证、速率限制、缓存、序列化)
- 确定性后处理(格式化、过滤、结果排序)
边界是:LLM 调用下游的所有内容(工具、数据库、API)产生的确定性输出,作为 LLM 驱动系统的输入。评估测试应将这些视为既定事实,并关注 LLM 如何使用它们。
示例:如果一个 FAQ 工具存在关键词匹配错误,返回了错误的数据,这是一个传统的错误——通过常规代码更改修复,而不是调整评估阈值。评估测试的存在是为了验证在给定正确工具输出的情况下,LLM 智能体是否能产生正确的面向用户的响应。
在构建数据集和期望输出时,使用实际的工具/系统输出作为基准事实。一个评估用例的期望输出应反映给定系统实际产生的工具结果时,正确的 LLM 响应应该是什么样子。
阶段 0:确保 pixie-qa 已安装且 API 密钥已设置
在执行任何其他操作之前,检查 pixie-qa 包是否可用:
python -c "import pixie" 2>/dev/null && echo "installed" || echo "not installed"
如果未安装,请安装:
pip install pixie-qa
这提供了 pixie Python 模块、pixie CLI 和 pixie test 运行器——所有这些都依赖于插桩和评估。不要跳过此步骤;本技能的所有其他部分都依赖于它。
验证 API 密钥
被测试的应用程序几乎肯定需要 LLM 提供商的 API 密钥(例如 OPENAI_API_KEY、ANTHROPIC_API_KEY)。像 FactualityEval 这样的 LLM 即评判器也需要 OPENAI_API_KEY。在运行任何东西之前,验证密钥是否已设置:
[ -n "$OPENAI_API_KEY" ] && echo "OPENAI_API_KEY set" || echo "OPENAI_API_KEY missing"
如果密钥未设置:检查项目是否使用了 .env 文件。如果是,请注意 python-dotenv 仅在应用显式调用 load_dotenv() 时才加载 .env——Shell 命令和 pixie CLI 除非已导出,否则不会看到 .env 中的变量。告诉用户缺少哪个密钥以及如何设置。在确认 API 密钥之前,不要继续运行应用或评估——您会遇到浪费时间的错误,这些错误看起来像是应用程序的 bug。
阶段 1:理解应用
在触碰任何代码之前,花时间真正阅读源代码。代码会告诉您比询问用户更多的东西,并使您能够更好地决定评估什么以及如何评估。
需要调查的内容
- 软件如何运行:入口点是什么?如何启动?它是 CLI、服务器还是库函数?所需的参数、配置文件或环境变量是什么?
- LLM 的所有输入:这不仅限于用户的消息。跟踪进入任何 LLM 提示的每一部分数据:
- 用户输入(查询、消息、上传的文件)
- 系统提示(硬编码或模板化)
- 检索到的上下文(RAG 块、搜索结果、数据库记录)
- 工具定义和函数架构
- 对话历史/记忆
- 改变提示行为的配置或特性标志
- 所有中间步骤和输出:遍历从输入到最终输出的代码路径,并记录每个阶段:
- 检索/搜索结果
- 工具调用及其结果
- 智能体路由/转接决策
- 中间的 LLM 调用(例如,最终答案前的摘要)
- 后处理或格式化步骤
- 最终输出:用户看到什么?格式是什么?质量期望是什么?
- 用例和预期行为:应用应该处理的不同场景有哪些?对于每个用例,“良好”的响应是什么样的?什么构成失败?
识别评估边界函数
这是您将做出的最重要的决定,正确与否决定了评估管道是测试真实应用还是虚构。
评估边界函数 是实际生产代码中的函数,它:
- 接收结构化输入(文本、字典、消息列表)——而非原始 HTTP 请求、音频流或 Webhook 负载
- 调用 LLM(直接或通过内部调用链)
- 返回 LLM 的响应(或其处理版本)
此函数上游的所有内容(Webhook 处理器、语音转文本处理、请求解析、认证、会话管理)将在构建运行测试工具时被模拟或绕过。此函数及其下游的所有内容都是您正在评估的真实代码。
示例:在 Twilio 语音 AI 应用中:
- Twilio 发送带音频的 Webhook → 上游,模拟此部分
- 音频处理将语音转为文本 → 上游,模拟此部分
- 从 Redis 加载调用状态 → 上游,模拟或简化此部分
agent.respond(user_text, conversation_history)调用 LLM → 评估边界函数- 响应文本转为语音 → 下游,不在评估范围内
示例:在 FastAPI RAG 聊天机器人中:
- HTTP 端点接收 POST 请求 → 上游,绕过此部分
- 请求验证和认证 → 上游,绕过此部分
chatbot.answer(question, context)检索文档并调用 LLM → 评估边界函数- 响应格式化为 JSON → 下游,不在评估范围内
示例:在简单的 CLI 问答工具中:
main()从标准输入读取用户输入 → 上游,绕过此部分answer_question(question)调用 LLM → 评估边界函数
在识别评估边界函数时,请记录:
- 确切的函数名和文件位置
- 其签名(参数名称和类型)
- 它需要哪些上游依赖(客户端、配置对象、状态)
- 这些依赖中哪些需要真实凭证,哪些可以模拟
如果您无法识别一个清晰的评估边界函数——如果 LLM 调用与无法分离的基础设施代码紧密耦合——停止并询问用户。请参见上面的“硬性门槛”。
编写 MEMORY.md
将您的发现写入 pixie_qa/MEMORY.md。这是评估工作的主要工作文档。它应具有可读性,并且足够详细,使不熟悉项目的人能够理解应用程序和评估策略。
关键:MEMORY.md 记录的是您对现有应用程序代码的理解。它绝不能包含对 pixie 命令、您计划添加的插桩代码或不存在的脚本/函数的引用。 这些内容应放在后续部分,仅在实现后才能添加。
理解部分应包括:
# 评估说明:<项目名称>
## 应用工作原理
### 入口点与执行流程
<描述如何启动/运行应用,逐步流程从输入到输出>
### LLM 调用的输入
<对于 代码库中的 每个 LLM 调用,记录:>
- 代码中的位置(文件 + 函数名)
- 使用的系统提示(引用或总结)
- 哪些用户/动态内容输入其中
- 可用的工具/功能
### 中间处理
<描述 输入和输出之间的任何步骤:>
- 检索、路由、工具执行等。
- 包含每个步骤的代码位置(文件:行号)
### 最终输出
<用户 看到的内容、格式、质量要求是什么>
### 用例
<列出每个不同的场景,包含良好/不良输出的示例>
### 评估边界函数
- **函数**:`<类.方法或函数名>`
- **位置**:`<文件:行号>`
- **签名**:`<参数和返回类型>`
- **需要模拟的上游依赖**:<列出 独立执行时需要模拟的内容>
- **选择此边界的原因**:<解释 为什么这是要评估的正确函数>
## 评估计划
### 要评估什么以及为什么
<质量 维度和理由>
### 评估粒度
<哪个函数/跨度边界构成一个“测试用例”?为什么选择这个边界?>
### 评估器与标准
<对于 每个评估测试,指定:评估器、数据集、阈值、理由>
### 评估所需的数据
<需要 捕获哪些数据点,以及它们在代码中的位置>
如果从代码中确实无法理解某些内容,请询问用户——但大多数问题在仔细阅读代码后都能自己解答。
阶段 2:决定评估什么
既然您理解了应用,就可以对要测量的内容做出明智的选择:
- 最重要的质量维度是什么? 问答应用的事实准确性、结构化提取的输出格式、RAG 的相关性、面向用户文本的安全性。
- 评估哪个跨度: 整个管道(
root)还是仅 LLM 调用(last_llm_call)?如果您正在调试检索,可能在不同于检查最终答案质量的地方进行评估。 - 哪些评估器合适: 参见
references/pixie-api.md→ 评估器。对于事实性问答:FactualityEval。对于结构化输出:ValidJSONEval/JSONDiffEval。对于 RAG 管道:ContextRelevancyEval/FaithfulnessEval。 - 通过标准:
ScoreThreshold(threshold=0.7, pct=0.8)意味着 80% 的用例得分必须 ≥ 0.7。思考这个应用程序“足够好”是什么样的。 - 期望输出:
FactualityEval需要它们。格式评估器通常不需要。
在编写任何代码之前,在 pixie_qa/MEMORY.md 中更新计划。
阶段 3:插桩应用
将 pixie 插桩添加到现有的生产代码中。目标是捕获已经是应用程序正常执行路径一部分的函数的输入和输出。插桩必须在真实的代码路径上——即应用在生产中运行时执行的相同代码——以便在评估运行和实际使用中都能捕获跟踪。
在应用程序启动时添加 enable_storage()
在应用程序启动代码中调用一次 enable_storage()——在 main() 内部,或在服务器的初始化顶部。绝不能在模块级别(任何函数外部的文件顶部),因为那会导致存储设置在导入时触发。
好的位置:
- 在
if __name__ == "__main__":块内 - 在 FastAPI 的
lifespan或on_startup处理器中 - 在
main()/run()函数的顶部 - 在测试文件的
runnable函数内
# ✅ 正确——在应用程序启动时
async def main():
enable_storage()
...
# ✅ 正确——在测试的 runnable 中
def runnable(eval_input):
enable_storage()
my_function(**eval_input)
# ❌ 错误——在模块级别,导入时运行
from pixie import enable_storage
enable_storage() # 当任何文件导入此模块时,这行代码都会运行!
用 @observe 或 start_observation 包装现有函数
关键:对生产代码路径进行插桩。切勿为测试创建单独的函数或备用代码路径。
@observe 装饰器或 start_observation 上下文管理器应放置在应用程序在正常操作期间实际调用的现有函数上。如果应用程序的入口点是交互式 main() 循环,则对 main() 或其每轮调用的核心函数进行插桩——而不是创建一个复制逻辑的新辅助函数。
# ✅ 正确——装饰现有的生产函数
from pixie import observe
@observe(name="answer_question")
def answer_question(question: str, context: str) -> str: # 现有函数
... # 现有代码,保持不变
# ✅ 正确——在现有函数内部使用上下文管理器
from pixie import start_observation
async def main(): # 现有函数
...
with start_observation(input={"user_input": user_input}, name="handle_turn") as obs:
result = await Runner.run(current_agent, input_items, context=context)
# ... 现有的响应处理逻辑 ...
obs.set_output(response_text)
...
# ❌ 错误——创建一个复制 main() 逻辑的新函数
@observe(name="run_for_eval")
async def run_for_eval(user_messages: list[str]) -> str:
# 这复制了 main() 的功能,创建了一个与生产环境不同的代码路径
# 不要这样做。
...
# ❌ 错误——直接调用 LLM 而不是调用应用的函数
@observe(name="agent_answer_question")
def answer_question(question: str) -> str:
# 这绕过了整个应用,直接调用 OpenAI。
# 您在测试刚刚编写的脚本,而不是用户的应用。
response = client.responses.create(
model="gpt-4.1",
input=[{"role": "user", "content": question}],
)
return response.output_text
规则:
- 切勿为评估目的向应用程序代码添加新的包装函数。
- 切勿通过直接调用 LLM 提供商来绕过应用——如果您发现自己在测试或运行测试工具中编写
client.responses.create(...)或openai.ChatCompletion.create(...),您就不是在测试应用。请改为导入并调用应用自己的函数。 - 切勿更改函数的接口(参数、返回类型、行为)。
- 切勿将生产逻辑复制到一个单独的“可测试”函数中。
- 插桩纯粹是附加的——如果移除所有 pixie 导入和装饰器,应用的功能将完全相同。
- 插桩后,在运行结束时调用
flush()以确保所有跨度都被写入。 - 对于交互式应用(CLI 循环、聊天界面),对每轮处理函数进行插桩——即接受用户输入并产生输出的函数。评估
runnable应调用相同的函数。
重要:所有 pixie 符号都可以从顶级 pixie 包导入。切勿告诉用户从子模块导入(pixie.instrumentation、pixie.evals、pixie.storage.evaluable 等)——始终使用 from pixie import ...。
阶段 4:创建运行测试工具并验证跟踪
此阶段是一个硬性门槛。 在您成功通过运行测试工具运行应用程序的真实代码并确认跟踪出现在数据库中之前,不能继续编写测试或构建数据集。
运行测试工具是一个简短的脚本,它调用您在阶段 1 中识别的评估边界函数,绕过与 LLM 评估无关的外部基础设施。
当应用很简单时
如果评估边界函数是一个没有复杂依赖的简单调用(例如,answer_question(question: str) -> str),运行测试工具可以很简单:
# pixie_qa/scripts/run_harness.py
from pixie import enable_storage, flush
from myapp import answer_question
enable_storage()
result = answer_question("What is the capital of France?")
print(f"Result: {result}")
flush()
运行它,验证跟踪出现,然后继续。
当应用有复杂依赖时
大多数真实世界的应用需要更多设置。评估边界函数通常需要配置对象、数据库连接、API 客户端或状态对象才能运行。您的工作是模拟或存根最少数量的必要项,以调用真实的生产函数。
# pixie_qa/scripts/run_harness.py
"""通过评估边界函数执行实际应用程序代码。
模拟上游基础设施(Webhook、语音处理、调用状态等)
并使用代表性的文本输入调用真实的生产函数。
"""
from pixie import enable_storage, flush
# 如果项目使用 .env 文件,则加载它
from dotenv import load_dotenv
load_dotenv()
# 导入 ACTUAL 生产函数——不是副本,不是重新实现
from myapp.agents.llm.openai import OpenAILLM
def run_one_case(question: str) -> str:
"""调用实际的生产函数,使用最少的模拟依赖。"""
enable_storage()
# 构建函数所需的最少上下文。
# 使用真实的 API 客户端(需要真实的密钥),模拟其他所有内容。
llm = OpenAILLM(...)
# 调用 ACTUAL 函数——生产环境使用的同一个函数
result = llm.run_normal_ai_response(
prompt=question,
messages=[{"role": "user", "content": question}],
)
flush()
return result
if __name__ == "__main__":
test_inputs = [
"What are your business hours?",
"I need to update my account information.",
]
for q in test_inputs:
print(f"Q: {q}")
print(f"A: {run_one_case(q)}")
print("---")
运行测试工具的关键规则:
- 调用真实的函数。 生产环境使用的同一个函数。如果您发现在运行测试工具中编写
client.responses.create(...)或openai.ChatCompletion.create(...)而不是调用应用自己的函数,那么您绕过了应用,测试的是完全不同的东西。 - 仅模拟上游基础设施。 数据库连接、Webhook 负载、会话状态、音频处理——这些可以被模拟或存根。LLM 调用本身必须是真实的,因为那是您要评估的内容。
- LLM API 密钥必须是真实的。 如果缺失,停止并询问用户。请参见“硬性门槛”。
- 保持最小化。 这不是完整的集成测试。这是为了执行真实的 LLM 调用代码路径并捕获跟踪的一种方式。
- 如果您尝试两次后仍无法创建可工作的运行测试工具,停止并寻求用户帮助。
验证跟踪是否被捕获
运行测试工具后,验证跟踪是否确实被捕获:
python pixie_qa/scripts/run_harness.py
然后检查数据库:
import asyncio
from pixie import ObservationStore
async def check():
store = ObservationStore()
traces = await store.list_traces(limit=5)
print(f"找到 {len(traces)} 条跟踪")
for t in traces:
print(t)
asyncio.run(check())
要检查的内容:
- 数据库中至少出现一条跟踪
- 跟踪包含评估边界函数的跨度(跨度名称应与您在阶段 3 中添加的
@observe(name=...)匹配) - 跨度捕获了
eval_input和eval_output,且值为合理的
如果没有跟踪出现:
- 在运行被插桩的函数之前是否调用了
enable_storage()? - 函数返回后是否调用了
flush()? @observe装饰器是否在正确的函数上?- 该函数是否实际执行了(而不仅仅是定义/导入)?
在您看到数据库中来自实际应用的真实跟踪之前,不要继续执行阶段 5。 如果跟踪没有出现,现在调试问题或向用户寻求帮助。这是一个设置问题,必须在进行任何其他操作之前解决。
阶段 5:编写评估测试文件
在构建数据集之前编写测试文件。这可能看起来有些反直觉,但它迫使您在开始收集数据之前决定要测量什么——否则数据收集就没有方向。
创建 pixie_qa/tests/test_<feature>.py。模式是:一个调用应用现有生产函数的 runnable 适配器,以及一个调用 assert_dataset_pass 的异步测试函数:
from pixie import enable_storage, assert_dataset_pass, FactualityEval, ScoreThreshold, last_llm_call
from myapp import answer_question
def runnable(eval_input):
"""重放一个数据集项,通过应用程序运行。
调用生产应用程序使用的相同函数。
这里的 enable_storage() 确保在评估运行期间捕获跟踪。
"""
enable_storage()
answer_question(**eval_input)
async def test_factuality():
await assert_dataset_pass(
runnable=runnable,
dataset_name="<dataset-name>",
evaluators=[FactualityEval()],
pass_criteria=ScoreThreshold(threshold=0.7, pct=0.8),
from_trace=last_llm_call,
)
请注意,enable_storage() 位于 runnable 内部,而不是测试文件的模块级别——它需要在每次调用时触发,以便为该次运行捕获跟踪。
runnable 导入并调用生产环境使用的同一个函数——您在阶段 1 中识别并在阶段 4 中验证的评估边界函数。如果 runnable 调用的函数与运行测试工具调用的函数不同,那就有问题。
测试运行器是 pixie test(不是 pytest):
pixie test # 运行当前目录下的所有 test_*.py
pixie test pixie_qa/tests/ # 指定路径
pixie test -k factuality # 按名称过滤
pixie test -v # 详细模式:显示每个用例的分数和推理
pixie test 会自动找到项目根目录(包含 pyproject.toml、setup.py 或 setup.cfg 的目录)并将其添加到 sys.path——就像 pytest 一样。测试文件中不需要 sys.path 技巧。
阶段 6:构建数据集
前提条件:您必须已经成功运行应用并在阶段 4 中验证了跟踪。如果您跳过了阶段 4 或阶段 4 失败,请返回——不要继续。
创建数据集,然后通过实际运行应用并使用代表性输入来填充它。数据集项必须包含从实际执行中捕获的真实应用输出。
pixie dataset create <数据集名称>
pixie dataset list # 验证它已创建
运行应用并将跟踪捕获到数据集中
最简单的方法是将阶段 4 的运行测试工具扩展为数据集构建器。既然您已经有一个调用真实应用代码并产生跟踪的工作脚本,可以调整它以保存结果:
# pixie_qa/scripts/build_dataset.py
import asyncio
from pixie import enable_storage, flush, DatasetStore, Evaluable
from myapp import answer_question
GOLDEN_CASES = [
("What is the capital of France?", "Paris"),
("What is the speed of light?", "299,792,458 meters per second"),
]
async def build_dataset():
enable_storage()
store = DatasetStore()
try:
store.create("qa-golden-set")
except FileExistsError:
pass
for question, expected in GOLDEN_CASES:
result = answer_question(question=question)
flush()
store.append("qa-golden-set", Evaluable(
eval_input={"question": question},
eval_output=result,
expected_output=expected,
))
asyncio.run(build_dataset())
注意 eval_output=result 是从运行应用返回的实际值——不是您手动输入的字符串。
或者,使用 CLI 进行单次捕获:
# 运行应用(enable_storage() 必须处于活动状态)
python -c "from myapp import main; main('What is the capital of France?')"
# 将根跨度保存到数据集
pixie dataset save <数据集名称>
# 或者,特别地保存最后一次 LLM 调用:
pixie dataset save <数据集名称> --select last_llm_call
# 添加上下文:
pixie dataset save <数据集名称> --notes "基础地理问题"
# 为像 FactualityEval 这样的评估器附加期望输出:
echo '"Paris"' | pixie dataset save <数据集名称> --expected-output
数据集构建的大忌
切勿手动编造 eval_output 值。 如果您在数据集 JSON 文件中直接键入 "eval_output": "4",而应用实际上并未产生该输出,那么数据集测试的是虚构。一个编造的数据集比没有数据集更糟糕,因为它会带来虚假的信心——用户认为他们的应用正在被测试,但实际并没有。
如果您发现自己正在直接编写或编辑 JSON 文件中的 eval_output 值,请停止。返回阶段 4,运行应用,并捕获真实的输出。
数据集构建的关键规则
- 每个
eval_output都必须来自评估边界函数的真实执行。 没有例外。 - 包含期望输出,用于像
FactualityEval这样基于比较的评估器。期望输出应反映给定工具/系统实际返回的内容时正确的 LLM 响应——而不是基于修复非 LLM 错误的理想化答案。 - 覆盖您关心的输入范围:正常情况、边缘情况、应用可能出错的场景。
- 使用
pixie dataset save时,可评估对象的eval_metadata将自动包含trace_id和span_id,以便后续调试。
阶段 7:运行测试
pixie test pixie_qa/tests/ -v
-v 标志显示每个用例的分数和推理,使查看哪些通过、哪些失败更加容易。检查通过率是否与您的 ScoreThreshold 设置相符。
在此阶段之后,如果用户的意图是“设置” — 停止。 报告结果并在继续前询问。请参见上面的“设置 vs. 迭代”。
阶段 8:调查失败
仅当用户要求迭代/修复,或在设置后明确确认时,才继续此阶段。
当测试失败时,目标是理解为什么,而不是调整阈值直到通过。调查必须彻底并记录下来——用户需要看到实际数据、您的推理过程和结论。
步骤 1:获取详细的测试输出
pixie test pixie_qa/tests/ -v # 显示每个用例的分数和推理
捕获完整的详细输出。对于每个失败的用例,记下:
eval_input(发送了什么)eval_output(应用产生了什么)expected_output(如果适用,期望是什么)- 评估器的分数和推理
步骤 2:检查跟踪数据
对于每个失败的用例,查找完整的跟踪以查看应用内部发生了什么:
from pixie import DatasetStore
store = DatasetStore()
ds = store.get("<dataset-name>")
for i, item in enumerate(ds.items):
print(i, item.eval_metadata) # trace_id 在这里
然后检查完整的跨度树:
import asyncio
from pixie import ObservationStore
async def inspect(trace_id: str):
store = ObservationStore()
roots = await store.get_trace(trace_id)
for root in roots:
print(root.to_text()) # 完整的跨度树:输入、输出、LLM 消息
asyncio.run(inspect("the-trace-id-here"))
步骤 3:根本原因分析
遍历跟踪并确定失败的确切来源。常见模式:
与 LLM 相关的失败(通过提示/模型/评估更改修复):
| 症状 | 可能原因 |
|---|---|
| 尽管工具结果正确,但输出事实错误 | 提示词未指示 LLM 忠实使用工具输出 |
| 智能体路由到错误的工具/转接 | 路由提示或转接描述含糊不清 |
| 输出格式错误 | 提示词中缺少格式说明 |
| LLM 产生幻觉而非使用工具 | 提示词未强制要求使用工具 |
非 LLM 失败(通过传统代码更改修复,不在评估范围内):
| 症状 | 可能原因 |
|---|---|
| 工具返回错误数据 | 工具实现中的错误——修复工具,而不是评估 |
| 由于关键词不匹配,根本未调用工具 | 工具选择逻辑错误——修复代码 |
| 数据库返回过时/错误记录 | 数据问题——独立修复 |
| API 调用失败并报错 | 基础设施问题 |
对于非 LLM 失败:在调查日志中记录它们,并推荐代码修复,但不要调整评估期望或阈值来适应非 LLM 代码中的错误。评估测试应衡量假设系统其他部分正常工作时的 LLM 质量。
步骤 4:在 MEMORY.md 中记录发现
每次失败调查都必须在 pixie_qa/MEMORY.md 中以结构化格式记录:
### 调查:<test_name> 失败 — <date>
**测试**:`test_faq_factuality` 在 `pixie_qa/tests/test_customer_service.py` 中
**结果**:5 个用例中 3 个通过 (60%),阈值要求 80% ≥ 0.7
#### 失败用例 1:“哪些排有额外的腿部空间?”
- **eval_input**:`{"user_message": "What rows have extra legroom?"}`
- **eval_output**:“抱歉,我没有确切的腿部空间排号...”
- **expected_output**:“5-8 排经济舱 Plus 有额外腿部空间”
- **评估器得分**:0.1 (FactualityEval)
- **评估器推理**:“输出声称不知道答案,而参考明确指出 5-8 排...”
**跟踪分析**:
检查了跟踪 `abc123`。跨度树显示:
1. 分流智能体路由到 FAQ 智能体 ✓
2. FAQ 智能体调用了 `faq_lookup_tool("What rows have extra legroom?")` ✓
3. `faq_lookup_tool` 返回“抱歉,我不知道...” ← **根本原因**
**根本原因**:`faq_lookup_tool` (customer_service.py:112) 使用关键词匹配。
座位 FAQ 条目由关键词 `["seat", "seats", "seating", "plane"]` 触发。
问题“哪些排有额外的腿部空间?”不包含这些关键词中的任何一个,因此
回退到默认的“我不知道”响应。
**分类**:非 LLM 失败——关键词匹配工具坏了。
LLM 智能体正确地路由到 FAQ 智能体并使用了工具;工具
本身返回了错误数据。
**修复**:在 `faq_lookup_tool` (customer_service.py:130) 的座位关键词列表中添加
`"row"`、`"rows"`、`"legroom"`。这是一个传统的代码修复,
不是评估/提示更改。
**验证**:修复后,重新运行:
\`\`\`bash
python pixie_qa/scripts/build_dataset.py # 刷新数据集
pixie test pixie_qa/tests/ -k faq -v # 验证
\`\`\`
步骤 5:修复并重新运行
进行有针对性的更改,如果需要,重建数据集,并重新运行。最后,务必给用户提供精确的命令进行验证:
pixie test pixie_qa/tests/test_<特性>.py -v
记忆模板
# 评估说明:<项目名称>
## 应用工作原理
### 入口点与执行流程
<如何启动/运行应用。从输入到输出的逐步流程。>
### LLM 调用的输入
<对于 每个 LLM 调用,记录:代码中的位置、系统提示、动态内容、可用工具>
### 中间处理
<输入 和输出之间的步骤:检索、路由、工具调用等。每个步骤的代码位置。>
### 最终输出
<用户 看到的内容。格式。质量期望。>
### 用例
<每个场景以及良好/不良输出的示例:>
1. <用例 1>:<描述>
- 输入示例:...
- 良好输出:...
- 不良输出:...
### 评估边界函数
- **函数**:`<完全限定名称>`
- **位置**:`<文件:行号>`
- **签名**:`<参数和返回类型>`
- **需要模拟的上游依赖**:<需要模拟/存根的内容>
- **选择此边界的原因**:<理由>
## 评估计划
### 要评估什么以及为什么
<质量 维度和理由>
### 评估器和标准
| 测试 | 数据集 | 评估器 | 标准 | 理由 |
| ---- | ------- | --------- | -------- | --------- |
| ... | ... | ... | ... | ... |
### 评估所需的数据
<需要 捕获哪些数据,以及代码位置>
## 数据集
| 数据集 | 项数 | 目的 |
| ------- | ----- | ------- |
| ... | ... | ... |
## 调查日志
### <日期> — <测试名称> 失败
<按照 阶段 8 中描述的结构化完整调查>
参考
参见 references/pixie-api.md 获取所有 CLI 命令、评估器签名以及 Python 数据集/存储 API。
📄 原始文档
完整文档(英文):
https://skills.sh/github/awesome-copilot/eval-driven-dev
💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

评论(0)