拓冰建站拓冰建站
首页 / 资讯中心 / 正文

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

一份从零开始的完整指南涵盖安装、接入、断言到进阶追踪的全流程为什么要评测智能体AI 智能体Agent与传统 LLM 调用有着本质区别它不是一个“输入-输出”的简单过程而是经历“规划→调用工具→观察结果→再规划”的多次循环。这种复杂性使得传统的手工测试难以覆盖全面——两个智能体可能给出相同的最终答案但一个调用了 3 次工具另一个却调用了 30 次光看最终文本完全看不出这种差异。这就是我们需要自动化评测工具的原因。Promptfoo 正是这样一款工具它能帮我们批量运行测试用例自动打分甚至能深入智能体内部追踪每一次工具调用的轨迹。第一步安装 PromptfooPromptfoo 是一个命令行工具要求 Node.js^20.20.0或22.22.0。三种安装方式选一种即可全局安装推荐以后直接用promptfoo命令npminstall-gpromptfoo临时运行不想全局安装时npx promptfoolatestHomebrew 安装Mac/Linux 用户brewinstallpromptfoo安装完成后验证一下promptfoo--version能看到类似0.97.0的版本号就说明装好了。第二步理解评测的运行原理Promptfoo 的评测流程可以用一个循环概括配置 → 评测 → 查看。首先你需要创建一个promptfooconfig.yaml配置文件在里面定义三个核心要素prompts要测试的提示词模板providers被测试的对象模型或你的智能体tests测试用例和断言规则然后用一条命令启动评测promptfooeval如果你想在修改配置后自动重新评测可以加上--watch参数promptfooeval--watch评测完成后打开网页看结果promptfoo view这个命令会启动本地 Web 服务器并在浏览器中自动打开可视化报告页面。记住这个config → eval → view循环它会贯穿你测试任何智能体的全过程。第三步把你的智能体接进来核心Promptfoo 本身不直接认识 LangGraph、CrewAI、Google ADK 这些框架的 API所以你需要写一个翻译层文件把 Promptfoo 的调用请求转发给智能体再把智能体的结果转发回来。这个翻译层在 Promptfoo 中称为Python Provider。3.1 最简单的例子理解三个参数和一个返回值官方文档给出的最简示例# echo_provider.pydefcall_api(prompt,options,context):Simple provider that echoes the prompt with a prefix.configoptions.get(config,{})prefixconfig.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}}最关键的一行——不管智能体内部逻辑多复杂最终必须返回一个带outputkey 的字典。Promptfoo 后续所有断言都基于这个output字段。配套的 YAML 配置# promptfooconfig.yamlproviders:-id:file://echo_provider.pyprompts:-Tell me a joke-What is 22?providers: - id: file://echo_provider.pyfile://前缀告诉 Promptfoo 这不是内置模型 ID而是本地 Python 文件加载里面的call_api。prompts:列表里的每一条字符串都会作为prompt参数传进call_api跑一次测试。性能提示Promptfoo 对 Python Provider 采用常驻进程的方式执行。脚本只在 worker 启动时加载一次之后每次调用都复用这个进程。所以哪怕你的智能体import很重比如加载模型权重也不会拖慢每次调用的速度。3.2 真实案例CrewAI 多智能体系统接入全过程CrewAI 是一个多智能体协作框架我们用一个完整的招聘智能体案例来演示真实项目怎么接。第一步安装依赖pipinstallcrewainpminstall-gpromptfoo验证都装好了python3-cimport crewai ; print(✅ CrewAI ready)promptfoo--version第二步定义 CrewAI 智能体这部分是 CrewAI 自己的 API跟 Promptfoo 无关# agent.pyimportosimportasynciofromtypingimportDict,AnyfromcrewaiimportAgent,Task,Crew OPENAI_API_KEYos.environ.get(OPENAI_API_KEY)defget_recruitment_crew(model:stropenai:gpt-4o)-Crew:agentAgent(roleSenior Recruiter specializing in technical roles,goalFind the best candidates for a given set of job requirements and return the results in a valid JSON format.,backstory你是一个经验丰富的招聘专家擅长从大量简历中筛选出最匹配的候选人。,verboseFalse,modelmodel,api_keyOPENAI_API_KEY)taskTask(description根据以下招聘需求找出最合适的候选人{requirements},expected_output一个包含 candidates 数组和 summary 字符串的 JSON 对象,agentagent)crewCrew(agents[agent],tasks[task],verboseFalse)returncrewasyncdefrun_recruitment_agent(prompt:str,model:stropenai:gpt-4o)-Dict[str,Any]:try:crewget_recruitment_crew(modelmodel)resultawaitcrew.kickoff_async(inputs{requirements:prompt})return{output:result,raw_output:str(result)}exceptExceptionase:return{error:str(e),raw_output:}第三步写 Promptfoo 适配层真正对接 Promptfoo 的是下面这个函数# agent.py (接续上面的代码)defcall_api(prompt:str,options:Dict[str,Any],context:Dict[str,Any])-Dict[str,Any]:try:# 从配置中获取模型名configoptions.get(config,{})modelconfig.get(model,openai:gpt-4o)# CrewAI 内部是异步的但 Promptfoo 要求同步返回所以用 asyncio.run 包装resultasyncio.run(run_recruitment_agent(prompt,modelmodel))iferrorinresult:return{error:result[error],raw:result.get(raw_output,)}return{output:result[output]}exceptExceptionase:# 兜底捕获避免整个评测进程崩溃return{error:fAn 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, modelmodel))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.yamlproviders:-id:file://agent.pyconfig:model:openai:gpt-4oprompts:-我们正在招聘一名高级 Python 工程师要求有 5 年以上经验熟悉 Django 和微服务架构。tests:-description:招聘需求包含技术栈要求assert:-type:is-jsonvalue:schema:type:objectrequired:[candidates,summary]properties:candidates:type:arrayitems:type:objectrequired:[name,experience_years,skills]properties:name:type:stringexperience_years:type:numberskills:type:arrayitems:type:stringsummary:type:string第五步运行评测exportOPENAI_API_KEYsk-xxx-your-api-key-herepromptfooevalpromptfoo 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:is-jsonvalue:schema:type:objectrequired:[candidates,summary]properties:candidates:type:arrayitems:type:objectrequired:[name,experience_years]properties:name:type:stringexperience_years:type:numbersummary:type:string模型评分断言让另一个 LLM 当裁判断言类型作用llm-rubric用自然语言写评分标准裁判模型给出 pass/score/reason示例assert:-type:llm-rubricvalue:回答是否礼貌、专业且没有事实错误写好断言后跑完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:trueotlp:http:enabled:truetracing.enabled: true表示要发送 OTLP 遥测数据tracing.otlp.http.enabled: true表示启动内置的接收服务器。开启后Promptfoo 会通过一个叫traceparent的字段W3C 标准的追踪上下文格式把当前测试用例的追踪上下文传给 Provider。第二步在 Provider 代码中接入 OpenTelemetry SDK不同框架接入方式不同内置 Provider如openai:agents:*直接在 YAML 里配置一行tracing: true就行不用写代码SDK 内部的工具调用、模型调用、交接事件都会自动转成 span 导出。自定义 Python ProviderCrewAI、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-usedAgent 是否调用了指定工具value可以是字符串、字符串数组或带pattern/min/max的对象trajectory:tool-args-match调用工具时传的参数是否符合预期支持用{{ order_id }}这种模板变量动态匹配参数值trajectory:tool-sequence工具调用的先后顺序默认mode: in_order中间可以有其他步骤也可以设mode: exacttrajectory:goal-success让裁判模型基于完整轨迹判断任务是否真正达成能识别嘴上说做了但实际没调用工具的情况trajectory:step-count统计轨迹里某类步骤的数量比如限制 Agent 最多执行 3 次命令skill-usedAgent 是否路由到了正确的技能目前对 Claude Agent SDK、OpenAI Codex SDK 等生效trace-span-count产生了多少个 span用于验证追踪链路本身是否健康示例验证 Agent 是否按正确顺序调用了搜索和计算工具且搜索次数不超过 5 次assert:-type:trajectory:tool-sequencevalue:tools:[search,calculate]mode:in_order-type:trajectory:step-countvalue:step_type:tooltool_name:searchmax:5速查表从安装到进阶追踪的完整流程步骤命令/操作关键点1. 安装npm install -g promptfoo需要 Node.js^20.20.0或22.22.02. 初始化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:*断言校验中间步骤
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门