欢迎光临
我们一直在努力

使用 Promptfoo 对 AI 智能体进行自动化评测与轨迹追踪

一份从零开始的完整指南,涵盖安装、接入、断言到进阶追踪的全流程

为什么要评测智能体?

AI 智能体(Agent)与传统 LLM 调用有着本质区别:它不是一个“输入-输出”的简单过程,而是经历“规划→调用工具→观察结果→再规划”的多次循环。这种复杂性使得传统的手工测试难以覆盖全面——两个智能体可能给出相同的最终答案,但一个调用了 3 次工具,另一个却调用了 30 次,光看最终文本完全看不出这种差异。

这就是我们需要自动化评测工具的原因。

Promptfoo 正是这样一款工具:它能帮我们批量运行测试用例,自动打分,甚至能深入智能体内部,追踪每一次工具调用的轨迹。


第一步:安装 Promptfoo

Promptfoo 是一个命令行工具,要求 Node.js ^20.20.0 或 >=22.22.0。三种安装方式选一种即可:

全局安装(推荐,以后直接用 promptfoo 命令):

npm install -g promptfoo

临时运行(不想全局安装时):

npx promptfoo@latest

Homebrew 安装(Mac/Linux 用户):

brew install promptfoo

安装完成后验证一下:

promptfoo –version

能看到类似 0.97.0 的版本号,就说明装好了。


第二步:理解评测的运行原理

Promptfoo 的评测流程可以用一个循环概括:配置 → 评测 → 查看。

首先,你需要创建一个 promptfooconfig.yaml 配置文件,在里面定义三个核心要素:

  • prompts:要测试的提示词模板
  • providers:被测试的对象(模型或你的智能体)
  • tests:测试用例和断言规则

然后,用一条命令启动评测:

promptfoo eval

如果你想在修改配置后自动重新评测,可以加上 –watch 参数:

promptfoo eval –watch

评测完成后,打开网页看结果:

promptfoo view

这个命令会启动本地 Web 服务器,并在浏览器中自动打开可视化报告页面。

记住这个"config → eval → view"循环,它会贯穿你测试任何智能体的全过程。


第三步:把你的智能体接进来(核心)

Promptfoo 本身不直接认识 LangGraph、CrewAI、Google ADK 这些框架的 API,所以你需要写一个"翻译层"文件,把 Promptfoo 的调用请求转发给智能体,再把智能体的结果转发回来。这个翻译层在 Promptfoo 中称为 Python Provider。

3.1 最简单的例子:理解三个参数和一个返回值

官方文档给出的最简示例:

# echo_provider.py
def call_api(prompt, options, context):
"""Simple provider that echoes the prompt with a prefix."""
config = options.get('config', {})
prefix = config.get('prefix', 'Tell me about: ')

return {
"output": f"{prefix}{prompt}"
}

  • def call_api(prompt, options, context): —— Promptfoo 只认这一个函数名,签名固定。

    • prompt:当前测试用例渲染后的最终输入文本。
    • options:字典,options['config'] 对应 YAML 里 provider 下的 config 字段,用来传自定义参数(模型名、超参数、密钥路径等)。
    • context:字典,context['vars'] 是当前测试用例定义的所有变量。
  • config = options.get('config', {}):取出配置字典,没配置就用空字典兜底,避免 KeyError。

  • prefix = config.get('prefix', 'Tell me about: '):取 prefix 配置项,YAML 没写就用默认值。

  • return {"output": f"{prefix}{prompt}"}:最关键的一行——不管智能体内部逻辑多复杂,最终必须返回一个带 output key 的字典。Promptfoo 后续所有断言都基于这个 output 字段。

配套的 YAML 配置:

# promptfooconfig.yaml
providers:
id: 'file://echo_provider.py'

prompts:
'Tell me a joke'
'What is 2+2?'

  • providers: – id: 'file://echo_provider.py':file:// 前缀告诉 Promptfoo 这不是内置模型 ID,而是本地 Python 文件,加载里面的 call_api。

  • prompts: 列表里的每一条字符串都会作为 prompt 参数传进 call_api,跑一次测试。

性能提示:Promptfoo 对 Python Provider 采用常驻进程的方式执行。脚本只在 worker 启动时加载一次,之后每次调用都复用这个进程。所以哪怕你的智能体 import 很重(比如加载模型权重),也不会拖慢每次调用的速度。

3.2 真实案例:CrewAI 多智能体系统接入全过程

CrewAI 是一个多智能体协作框架,我们用一个完整的招聘智能体案例来演示真实项目怎么接。

第一步:安装依赖

pip install crewai
npm install -g promptfoo

验证都装好了:

python3 -c "import crewai ; print('✅ CrewAI ready')"
promptfoo –version

第二步:定义 CrewAI 智能体

这部分是 CrewAI 自己的 API,跟 Promptfoo 无关:

# agent.py
import os
import asyncio
from typing import Dict, Any
from crewai import Agent, Task, Crew

OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")

def get_recruitment_crew(model: str = "openai:gpt-4o") > Crew:
agent = Agent(
role="Senior Recruiter specializing in technical roles",
goal="Find the best candidates for a given set of job requirements and return the results in a valid JSON format.",
backstory="你是一个经验丰富的招聘专家,擅长从大量简历中筛选出最匹配的候选人。",
verbose=False,
model=model,
api_key=OPENAI_API_KEY
)

task = Task(
description="根据以下招聘需求,找出最合适的候选人:{requirements}",
expected_output="一个包含 candidates 数组和 summary 字符串的 JSON 对象",
agent=agent
)

crew = Crew(
agents=[agent],
tasks=[task],
verbose=False
)

return crew

async def run_recruitment_agent(prompt: str, model: str = "openai:gpt-4o") > Dict[str, Any]:
try:
crew = get_recruitment_crew(model=model)
result = await crew.kickoff_async(inputs={"requirements": prompt})
return {"output": result, "raw_output": str(result)}
except Exception as e:
return {"error": str(e), "raw_output": ""}

第三步:写 Promptfoo 适配层

真正对接 Promptfoo 的是下面这个函数:

# agent.py (接续上面的代码)

def call_api(prompt: str, options: Dict[str, Any], context: Dict[str, Any]) > Dict[str, Any]:
try:
# 从配置中获取模型名
config = options.get("config", {})
model = config.get("model", "openai:gpt-4o")

# CrewAI 内部是异步的,但 Promptfoo 要求同步返回,所以用 asyncio.run 包装
result = asyncio.run(run_recruitment_agent(prompt, model=model))

if "error" in result:
return {"error": result["error"], "raw": result.get("raw_output", "")}

return {"output": result["output"]}

except Exception as e:
# 兜底捕获,避免整个评测进程崩溃
return {"error": f"An error occurred in call_api: {str(e)}"}

  • config = options.get("config", {}) / model = config.get("model", "openai:gpt-4o"):跟 echo 例子一样,从 YAML 传进来的配置里取模型名,没配就用默认值。这样你可以在 YAML 中为不同测试场景指定不同模型。

  • result = asyncio.run(run_recruitment_agent(prompt, model=model)):CrewAI 内部的执行是异步的(async def),但 Promptfoo 要求 call_api 同步返回,所以用 asyncio.run(…) 把异步调用包成同步。这一步内部会走完 CrewAI 的完整协作流程。

  • if "error" in result: return {"error": …}:如果内部执行出错(比如 LLM 返回格式不对),把错误信息带出来,方便你在 Promptfoo 报告里看到失败原因,而不是让整个评测崩溃。

  • except Exception as e: return {"error": …}:兜底捕获,任何意外异常都转换成 Promptfoo 能理解的 {"error": …} 格式。

第四步:编写 YAML 配置文件

# promptfooconfig.yaml
providers:
id: 'file://agent.py'
config:
model: 'openai:gpt-4o'

prompts:
'我们正在招聘一名高级 Python 工程师,要求有 5 年以上经验,熟悉 Django 和微服务架构。'

tests:
description: '招聘需求包含技术栈要求'
assert:
type: isjson
value:
schema:
type: object
required: ['candidates', 'summary']
properties:
candidates:
type: array
items:
type: object
required: ['name', 'experience_years', 'skills']
properties:
name:
type: string
experience_years:
type: number
skills:
type: array
items:
type: string
summary:
type: string

第五步:运行评测

export OPENAI_API_KEY="sk-xxx-your-api-key-here"
promptfoo eval
promptfoo view

这一步 Promptfoo 会:用配置调用 CrewAI Provider → 输入招聘需求 → 收集结构化输出 → 用断言检查候选人列表和摘要是否存在 → 生成 pass/fail 报告。网页里能看到测试用例表、每条的 pass/fail、通过率、延迟等统计信息。

3.3 其他框架同理

只要你能用 Python 写出一个 call_api(prompt, options, context) 函数并返回带 output 的字典,任何框架都能接入。官方提供了完整的对照表和可运行示例,覆盖 LangGraph、LangChain、CrewAI、Python 版 OpenAI Agents SDK、PydanticAI、Google ADK、Strands Agents 等主流框架。


第四步:写断言(Assertion),让 Promptfoo 自动打分

跑完 promptfoo eval 后,Promptfoo 拿到 Provider 返回的 output 字段,然后按 tests[].assert 里配置的规则逐条判断对错。

断言分两大类:

确定性断言(不需要模型参与,直接匹配)

断言类型作用
contains 检查输出里是否包含某个字符串
not-contains 检查输出里是否不包含某个字符串
equals 完全匹配
matches 正则表达式匹配
is-json 检查输出是否是合法 JSON,还能配合 JSON Schema 校验结构

示例:CrewAI 招聘案例中,用 is-json 配合 Schema 校验输出必须包含 candidates 数组和 summary 字符串:

assert:
type: isjson
value:
schema:
type: object
required: ['candidates', 'summary']
properties:
candidates:
type: array
items:
type: object
required: ['name', 'experience_years']
properties:
name:
type: string
experience_years:
type: number
summary:
type: string

模型评分断言(让另一个 LLM 当裁判)

断言类型作用
llm-rubric 用自然语言写评分标准,裁判模型给出 pass/score/reason

示例:

assert:
type: llmrubric
value: '回答是否礼貌、专业,且没有事实错误?'

写好断言后,跑完 promptfoo eval,打开 promptfoo view,网页里会显示每条测试用例的输入、Agent 的输出、pass/fail 状态,以及整体通过率、延迟、断言数量统计。

重要:这一整套打分完全不依赖 OpenTelemetry——只要 call_api 能正常返回 output,分数就能算出来。


第五步(进阶):把智能体内部的工具调用变成可断言的证据

如果只看最终答案还不够,想验证"智能体内部到底调用了哪些工具、参数对不对、顺序对不对",就要用到 OpenTelemetry 追踪。

5.1 为什么需要它

智能体不像普通 LLM 一次输出就完事,它会经历"决策→调用工具→观察结果→再决策"的循环。两个智能体给出同样的最终答案,但一个调了 3 次工具、另一个调了 30 次,光看最终文本完全看不出这种差异。

trajectory:* 断言就是为解决这个问题而设计的——它们不只看最终输出,而是分析智能体执行的完整轨迹。

5.2 怎么开启追踪

开启追踪需要两步:配置和代码埋点。

第一步:在 YAML 中启用追踪

在 promptfooconfig.yaml 顶层加一段配置,让 Promptfoo 启动本地 OTLP 接收器:

tracing:
enabled: true
otlp:
http:
enabled: true

tracing.enabled: true 表示"要发送 OTLP 遥测数据",tracing.otlp.http.enabled: true 表示"启动内置的接收服务器"。

开启后,Promptfoo 会通过一个叫 traceparent 的字段(W3C 标准的追踪上下文格式),把当前测试用例的追踪上下文传给 Provider。

第二步:在 Provider 代码中接入 OpenTelemetry SDK

不同框架接入方式不同:

  • 内置 Provider(如 openai:agents:*):直接在 YAML 里配置一行 tracing: true 就行,不用写代码,SDK 内部的工具调用、模型调用、交接事件都会自动转成 span 导出。

  • 自定义 Python Provider(CrewAI、Google ADK、LangGraph 等):需要你在 call_api 里手动接入 Python 的 OpenTelemetry SDK,解析 Promptfoo 传来的 traceparent,起一个 span,再把框架自身产生的 span 作为子 span 导出。

Python 版 OpenAI Agents SDK 的官方示例演示了完整流程:Promptfoo 注入追踪上下文 → 示例代码解析并配置一个自定义的 TracingProcessor → 这个处理器把 SDK 内部产生的 span 转换成 OTLP JSON 格式 → Promptfoo 接收后就能在网页的 Trace Timeline 里看到。

如果跳过这一步导出,Promptfoo 完全看不到 SDK 内部的工具调用和交接过程,trajectory:* 断言就没有数据可用。

第三步:查看追踪结果

开启追踪后跑一遍评测、打开网页,在任意一条测试结果上点击放大镜图标,滚动到"Trace Timeline"区域,就能看到 Agent 内部执行的完整时间线。

5.3 针对轨迹写断言

一旦追踪数据能进来,就可以在 assert 列表里加上以下几类断言:

断言类型校验什么说明
trajectory:tool-used Agent 是否调用了指定工具 value 可以是字符串、字符串数组,或带 pattern/min/max 的对象
trajectory:tool-args-match 调用工具时传的参数是否符合预期 支持用 {{ order_id }} 这种模板变量动态匹配参数值
trajectory:tool-sequence 工具调用的先后顺序 默认 mode: in_order(中间可以有其他步骤),也可以设 mode: exact
trajectory:goal-success 让裁判模型基于完整轨迹判断任务是否真正达成 能识别"嘴上说做了但实际没调用工具"的情况
trajectory:step-count 统计轨迹里某类步骤的数量 比如限制 Agent 最多执行 3 次命令
skill-used Agent 是否路由到了正确的"技能" 目前对 Claude Agent SDK、OpenAI Codex SDK 等生效
trace-span-count 产生了多少个 span 用于验证追踪链路本身是否健康

示例:验证 Agent 是否按正确顺序调用了"搜索"和"计算"工具,且搜索次数不超过 5 次:

assert:
type: trajectory:toolsequence
value:
tools: ['search', 'calculate']
mode: 'in_order'

type: trajectory:stepcount
value:
step_type: 'tool'
tool_name: 'search'
max: 5


速查表:从安装到进阶追踪的完整流程

步骤命令/操作关键点
1. 安装 npm install -g promptfoo 需要 Node.js ^20.20.0 或 >=22.22.0
2. 初始化 promptfoo init 生成示例配置文件,快速上手
3. 接入自己的 Agent 写 call_api(prompt, options, context) 返回 {"output": …} 任意 Python Agent 框架统一接口
4. 配置测试用例 在 YAML 中定义 tests 和 assert 从 contains/is-json 等确定性断言开始
5. 运行评测 promptfoo eval 加上 –watch 可实现自动重跑
6. 查看报告 promptfoo view 浏览器中查看详细结果
7. 进阶:轨迹评估 开启 tracing + 接入 OTel SDK 用 trajectory:* 断言校验中间步骤

赞(0)
未经允许不得转载:171主机测评 » 使用 Promptfoo 对 AI 智能体进行自动化评测与轨迹追踪
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址