Pipecat Release Evals:用 LLM 判卷的自动化场景套件,守护语音智能体发布的每一步
【免费下载链接】pipecatOpen Source framework for voice agents, multimodal apps, and realtime AI. Maintained by Daily and the community.项目地址: https://gitcode.com/GitHub_Trending/pi/pipecat
Pipecat 仓库自带一套发布前的自动化行为评测系统 Release Evals,它把 100 多个官方示例当作真实 bot 逐个拉起,由评测框架扮演"用户"与之对话、用本地 STT 转写 bot 的实际语音、再用本地 LLM 当"裁判"判卷,从而在每次发布前自动验证全部示例是否仍然可用。本文基于仓库中的 scripts/release-evals/README.md 编写,并结合 src/pipecat/evals 下的源码实现,讲清这套系统的工作原理、环境准备、运行方式、稳定性测量方法,以及如何为新的 bot 和新的行为编写评测场景。
为什么需要 Release Evals
Pipecat 仓库的 examples/ 目录下维护着 100 多个示例:语音智能体、realtime 服务、函数调用、Flows 结构化对话、多 worker 协作、视觉与视频头像等。每发布一个版本,都需要确认这些示例(至少是绝大多数)还能正常工作。靠人工逐个跑一遍既慢又痛苦,因此仓库提供了这些 "release evals",用自动化的方式驱动每一个示例。
从源码结构看,整套系统由三部分组成:
- 被评测的 bot:每个示例本身就是一个 Pipecat bot,用评测专用传输层以
-t eval启动; - 评测 harness:pipecat.evals 模块作为 RTVI 客户端连接到 bot,播放对话的用户侧(音频模式下会真实合成语音)、转写 bot 的语音、并用 LLM 判断 bot 的行为是否符合预期;
- 场景(scenario):一个 YAML 文件,描述要与 bot 进行怎样的一段对话、以及如何判定 bot 是否表现正确。场景是可复用的,一个共享场景可以覆盖许多 bot。
两种场景:Scripted 与 Simulation
场景分两类,文件分别放在 scripts/release-evals/scenarios/scripted 和 scripts/release-evals/scenarios/simulated 两个目录中:
- Scripted 场景:把用户侧的每一轮对话写死,每一轮附带对 bot 结果的断言。例如
capital_question会问 "What is the capital of Germany?",并判卷 bot 的回答是否提到 Berlin。 - Simulation 场景(simulation):用户侧交给一个 LLM 扮演一位有目标的来电者,由裁判从整段对话判断 bot 是否完成了任务。
scripts/release-evals/manifest.yaml 把每个 bot 映射到它要运行的场景清单,两类场景在清单中写法一致,靠scripted/或simulated/前缀区分:
bots_dir: ../../examples # bot 路径相对该目录解析 scenarios_dir: scenarios # 场景名解析为 <dir>/<name>.yaml concurrency: 4 runs_dir: test-runs # 日志 + 录音 -> test-runs/<timestamp>/ record: true # 录制对话音频 spawn: "{python} {bot} -t eval --port {port}" suite: - bot: voice/voice-openai.py scenarios: [scripted/capital_question] - bot: flows/yaml/restaurant_reservation/bot.py scenarios: [simulated/book_table_available, simulated/book_table_flexible]manifest 的头部字段(bots_dir、scenarios_dir、runs_dir、concurrency、record等)定义了整个套件的运行参数,spawn模板描述了如何拉起每个 bot 子进程。实际清单中按类别组织了大量条目:Voice(验证各家 TTS/STT 服务栈的完整语音管线)、Realtime(语音到语音服务在自身会话内端到端完成函数调用)、Function Calling、Async Function Calling、MCP、Vision、Turn Management、DTMF、Flows、Multi-worker、Web Search 以及 Simulation 等,每个条目都注明了该类别考察的行为重点。
环境准备:本地判卷、本地语音
harness 默认在本地运行三样东西:裁判 LLM、用户的声音、bot 语音的转写器。因此需要以下准备:
裁判 LLM(Judge)
场景默认使用 Ollama(http://localhost:11434)作为裁判。安装 Ollama、启动服务并拉取场景使用的模型:
ollama pull gemma4:12b对裁判模型的要求可以从 README 中读出:
- 裁判被调用的频率是每个 scripted 场景的每条
eval:断言一次、每个 simulation 的每次运行一次,且所有并发运行共享同一个常驻模型副本,所以裁判延迟决定了整个套件的节奏——裁判必须又快又准,且对相同输入给出相同裁决; gemma4:12b回答在 1 秒以内且重复稳定。更小的模型速度跟得上但会误判简短的中间回复:bot 只说了一半 "Let me check on that." 时,裁判应判continue(等下文),若判了yes就等于放行了一个 bot 什么都没说的轮次;- 旧版裁判还会因转写器的同音字错误而拒收正确回答——"four" 被听成 "for"。
裁判配置通过extra:块传递reasoning_effort: none:gemma4支持推理(thinking),而系统只读取 JSON 裁决结果,开启推理会让每次调用多付出数倍延迟(足以让-c 4的运行卡住),同时挤占裁决本身需要的 token 预算——开着它既更慢也更不准。
每个场景的judge.eval:块也可以通过factory:指向任意其他 LLM:一个点号路径,指向接收该配置块并返回 OpenAI 兼容服务的可调用对象。
本地音频模型(仅音频模式场景)
默认地,用户侧语音用Kokoro TTS合成,bot 的语音用Moonshine转写;场景的user.speech:与judge.transcription:块可以换成其他服务(Whisper,或任意 Pipecat 服务通过factory:)。从 judge_audio.yaml 和 user_audio.yaml 可以看到默认配置:
# judge_audio.yaml modality: audio eval: service: ollama model: gemma4:12b extra: reasoning_effort: none transcription: service: moonshine model: small-streaming # user_audio.yaml modality: audio speech: service: kokoro voice: af_heart sample_rate: 16000默认模型以本地 ONNX 文件运行,首次使用时下载到 Pipecat 模型缓存(~/.cache/pipecat/);合成的用户侧语音缓存在~/.cache/pipecat/evals/tts,重复运行同一场景不会再次合成——无需 API key、无按次成本。注意:非英语转写需要多语言模型,而英语默认模型不具备;例如language_switch_audio场景会拉取 Whisper 的tiny(75MB)。
其他依赖
- Node.js(仅 MCP bot):mcp/mcp-stdio.py 用
npx拉起内存 MCP server,server 包在首次使用时下载; - 每个 bot 自己的凭据:bot 是真实示例,需要它平时就要的服务 API key(
$OPENAI_API_KEY、$CARTESIA_API_KEY、$DEEPGRAM_API_KEY等,放入.env)。缺少 key 的 bot 会直接评测失败。
带 eval 附加项安装框架(Kokoro、Moonshine、Whisper、Ollama 以及各 bot 用到的服务):
uv sync --group dev --all-extras --no-extra gstreamer --no-extra local运行评测套件
scripts/release-evals/run.sh 是pipecat eval suite的薄封装,始终传-d保存完整的分管线调试日志,并透传额外参数:
uv run python -m pipecat.evals suite -d manifest.yaml [-p PATTERN] [-s SCENARIO] [-c N] [-n NAME] [-t SECS] [-a] [--no-cache] [--repeat N]常用调用:
./run.sh # 运行 manifest 中的全部场景 ./run.sh -p voice-openai # 只跑路径包含 "voice-openai" 的 bot ./run.sh -s capital_question # 只跑 capital_question 场景 ./run.sh -c 8 # 8 个并发 ./run.sh -n nightly # 输出到 test-runs/nightly/ 而非时间戳目录每次运行写入test-runs/<name>/(省略-n时是时间戳),产物包括:
logs/<bot>__<scenario>.log— bot 子进程输出;logs/<bot>__<scenario>.eval.log— harness 的决策轨迹(总是写出,诊断偶发失败时价值极高);logs/<bot>__<scenario>.debug.log— harness 的完整分管线日志(用户语音 / bot 语音转写 / 裁判 / harness),每个管线一个 section。run.sh始终传-d/--debug,所以总会写出;recordings/<bot>__<scenario>.wav— 音频模式场景的对话录音。manifest 设置了record: true,所以默认就有;若 manifest 关闭了录制,传-a/--audio可强制开启。
其他实用标志:-c/--concurrency(并发数)、-t/--timeout(默认的单条断言超时秒数,针对未自带within_ms的断言)、--no-cache(每轮重新合成用户语音而不复用缓存)。manifest 头部除suite:列表外的所有字段都可以在命令行覆盖(命令行优先):--bots-dir、--scenarios-dir、--runs-dir、--base-port、--cache-dir、--spawn、--python——因此 manifest 也可以只写一个suite:列表,其余全部以命令行标志提供。
测量偶发性(flakiness)
一次通过回答的是"这个 bot 过了吗?",--repeat N回答的是"它通过的频率是多少?"——后者对存在竞态的行为(打断、异步函数结果、轮次检测)才是关键问题:这类 bot 可能一半时间过场景,任何单次运行都显得"可靠"。
./run.sh -p function-calling -s async_tool_delivery --repeat 50 -c 3重复运行的设计细节:
- 各次尝试在 bot 间交错(
A#1, B#1, C#1, A#2, ...)并从单一队列运行、中间没有屏障,因此所有 bot 在同一时间窗内面对相同的机器状态——一次瞬时降速会表现为横跨所有 bot 的"条带",而不是恰好落在那个 bot 上的回归; - 每次尝试把序号追加到产物文件名(
..._001.log、..._002.log),不会互相覆盖。
汇总变成每个 (bot, scenario) 的通过率,失败按类型分组——timeout、judge_no、missing_function_call等(完整清单见 src/pipecat/evals/results.py 中的FAILURE_KINDS),而不是逐条失败运行各占一行:
Failures (35 of 150): 10x turn 3 response timeout google 4, openai-async 4, anthropic 2 7x turn 3 response judge_no google 4, openai-responses 3 3x turn 1 function_call missing_function_call anthropic 2, openai-async 1源码中FAILURE_KINDS共定义了 15 种机器可读的失败类别:timeout(预算内未出现期望事件)、judge_no(裁判拒绝回复)、judge_continue(裁判始终未接受回复)、no_judge(用了eval:却建不出裁判)、no_content(事件无文本可判)、text_mismatch、missing_function_call、function_args_mismatch、unexpected_event(absent:断言看到了被禁止的事件)、send_after_timeout、connect_failed、handshake_timeout、judge_no_verdict、error。正因为reason是自由文本(常是裁判的散文,每次运行都不同),跨运行分组失败只能键在kind上。
重复扫描始终以 0 退出:它只报告通过率,什么速率可接受是你的策略而不是 harness 的策略。
每次运行(无论是否重复)还会写results.jsonl:每个运行一行 JSON,含其kind(script或simulation)、结果、失败列表(各带kind)、描述每轮状态(passed/failed/not_run,停止的运行未到达的轮次记为not_run,见TURN_STATUSES)以及产物路径——每完成一个运行就追加一行,所以被中断的扫描保留所有已完成内容。它是打印汇总的机器可读对应物,可以按任意问题自行分组统计。未通过的运行还携带events_seen,即 bot 实际做了什么的事件记录,根因通常就在那里。
并发与 GPU
只有裁判 LLM 跑在 GPU 上。Ollama 保持一份裁判模型常驻(gemma4:12b约 8.9GB,其中大部分是它加载的大上下文窗口),因此无论-c/--concurrency多大,GPU 占用大致恒定(峰值约 9GB)。用户声音(Kokoro)与 bot 语音转写器(默认 Moonshine)都通过 ONNX Runtime 跑在 CPU 上,不占 GPU 显存——并发上限由 CPU 和内存决定,而不是 GPU。一张 16GB 的卡(如 RTX A4000)跑默认配置绰绰有余;换上更大的裁判才会给显存压力,显存不足的运行会以 harness error 的形式出现在该次运行的.eval.log里。显存更小的卡上,可以调小裁判extra:块中的num_ctx裁剪上下文——裁判从不需要超过几千 token。
Whisper 可作为替代转写器(transcription: {service: whisper});它默认也在 CPU 上(device: cpu),显存有富余时可用device: cuda放到 GPU。
Scripted 场景:逐轮脚本 + 事件断言
一个 scripted 场景是一串turns。每轮三种驱动方式(user:与dtmf:互斥):
- 发送
user话语; - 用
dtmf:按 DTMF 键; - 都不带——纯观察轮,只做断言,用于 bot 先说话的轮次(如开场问候)。
以仓库中最典型的 scripts/release-evals/scenarios/scripted/capital_question.yaml 为例,它是音频模式端到端测试:用户侧真实合成语音(走 bot 的 STT),裁判评判的是对 bot 实际合成音频的本地转写:
name: capital_question user: !include ../user_audio.yaml judge: !include ../judge_audio.yaml turns: # 先等 bot 的 on-connect 问候结束再开口(避免撞上问候) - expect: - event: response eval: "the bot opens the conversation in some way (a greeting, an introduction, an offer to help, or a question to get the user started)" - user: "What is the capital of Germany?" expect: - event: response eval: "the response says the capital of Germany is Berlin"选"德国的首都是什么"这类问题的原因注释里写得很清楚:独词答案("Berlin")比算术题对转写鲁棒得多——数字在双方都存在同音字问题(four/for、two/to、数字与拼写),一个数字转错就会翻转答案的真假;问题中也没有逗号,合成语音就不会在句中产生停顿被激进的轮次检测切碎。
完整文件格式(事件、断言字段、send_after:、image:等)在 src/pipecat/evals/script.py 模块 docstring 中有权威说明。其中几个关键点值得展开:
事件名。断言用的是 harness 把 RTVI server 消息映射成的友好事件名:user_started_speaking、user_stopped_speaking、vad_user_started_speaking、vad_user_stopped_speaking、user_transcription、bot_started_speaking、bot_stopped_speaking、llm_started、response、llm_response、tts_response、function_call、function_call_stopped。vad_*事件是原始 VAD 信号,当轮次检测策略会门控或延迟轮次级user_stopped_speaking时(如过滤不完整轮次),它们是实用的时序锚点。
bot 回复的三种断言方式:
response:音频模式下是 bot 实际合成音频的本地转写(Moonshine 或 Whisper),文本模式下是 LLM 文本——这是真实的端到端检查,应优先使用。源码中response是模态无关别名,解析后在文本模式下降级为llm_response(见_resolve_response_events);llm_response:LLM 文本输出(bot-llm-text),两种模态都可用;tts_response:TTS 报告要说的话(bot-tts-text,带字级时序),仅音频模式。
断言字段:
within_ms:自最近锚点的延迟预算,省略时默认 60 秒(源码EvalExpectation的 docstring 明确:同一轮的所有断言共享以"用户发送时刻"为锚的单一预算,所以一个卡住的轮次在一个预算内失败,而不是每条断言各烧一个);text_contains:忽略空白差异的子串检查;calls::function_call/function_call_stopped期望的调用集合,按名任意顺序匹配,全部出现才通过;function_call_stopped的args说明调用如何结束(如args: {cancelled: true}区分"被取消的工作"和"自行完成的工作");eval::自然语言判据,由裁判 LLM 评估。注意 script.py 中的JUDGEABLE_EVENTS:eval:只对 bot 生成的文本事件有意义,加在其他事件上会触发解析器警告;absent: true:反转断言——断言在within_ms预算内没有该类型事件到达(默认 60 秒,建议显式设短),只能按事件类型匹配,不能与text_contains/eval:/calls:组合。典型用途是重复输出回归:
- event: response eval: "answers the question" - event: response absent: true within_ms: 30000调度与素材。send_after: {event: llm_started, delay_ms: 500}表示"bot 开始回应后 500ms 打断",用于 barge-in 测试;只给delay_ms不给event则是相对上一次发送的纯时间延迟,方便给 DTMF 按键节奏调速以触发聚合器的空闲超时冲刷。每轮默认在 bot 说完话之后才发送(像真实来电者等对方说完),所以上一轮提前满足的回复不会被新输入覆盖。
音频模式下还可以用audio:指定一个录音文件代替合成(路径相对场景文件,按文件自身采样率发送):user:仍然写明录音内容,因为裁判和text_contains读到的是这个文本,二者无法从音频恢复。场景文件中的assets/目录存放这类录音,例如capital_question_recording场景使用的 capital_question.wav。
写作时的三条注意事项:
- 模态:
judge:与user:块各自选择音频还是文本。音频模式合成用户轮(真实走 bot 的 STT)、裁判评判 bot 实际音频的本地转写;文本模式直接发送/评判文本,更快且无声。 - 录音:如前所述,音频模式下
audio:可以播放文件而不是合成。 - 先问候:大多数 bot 连接时会打招呼,必须等问候结束才能发第一轮用户输入,否则会撞进问候里。所以用户先开口的场景通常以一个"bot 先说话"轮开头,断言期望那条问候。
共享配置片段。judge:/user:的共享配置放在 scripts/release-evals/scenarios 目录下的几个小片段文件中(judge_audio.yaml、judge_text.yaml、user_audio.yaml,以及供 simulation 用的simulator.yaml),场景用!include引入(相对场景文件解析):
user: !include ../user_audio.yaml judge: !include ../judge_audio.yaml顶层还有两个可选字段值得了解(见 script.py docstring):context:提供 bot 上下文应从哪些 LLM 消息开始(给出后会在驱动轮次前替换 bot 的上下文);stop_on_failure:默认true(第一条失败轮次即终止场景),独立计分、需要跑完全部轮次的基准场景应设false并为每轮给显式within_ms,否则一个沉默的 bot 每个剩余轮次都会烧满 60 秒默认预算。
视觉输入(Vision)
有些 bot 需要本来通过/start请求体传入的会话数据,比如视觉 bot 的图片。评测传输层没有这样的端点,所以 bot 条目可以指向一个 JSONrunner_body:文件(相对 manifest 解析),并以--runner-body传给 bot:
- bot: vision/vision-openai.py runner_body: scenarios/vision-cat.json # {"image_path": "../assets/cat.jpg", "question": "..."} scenarios: [vision_describe]bot 以该 body 文件所在目录为工作目录启动,所以 body 内相对image_path会解析到文件旁边,二者一起搬运。vision_describe场景是一个 bot 先说话的轮次(无用户输入):bot 连接时描述图片(一只猫),裁判检查它描述的确实是猫。
对函数调用 + 视频的 bot,另一种方式是在轮次上注册image:,当 bot 在对话中请求用户图片时,评测传输层把这张图递给它(见describe_image场景)。
Flows 场景
examples/flows/ 的 bot 有一组独立场景,断言 Flows 行为:哪些函数被触发、带哪些参数、bot 说了什么回复。每个场景对准其示例的某个标志性特性——动态路由、direct 与 global 函数、FlowsFunctionSchema约束、上下文策略、条件分支、多 worker 交接、LLMSwitcher。
这些场景只跑文本(没有user:/judge:块);要驱动 bot 的真实音频管线,就加上共享 include(user: !include user_audio.yaml、judge: !include judge_audio.yaml)。写作惯例:
- 每轮断言
function_call外加一条responseeval;response事件同时给运行"打拍子":harness 等 bot 说完才发下一轮; - 终结轮只断言函数调用——
end_conversation会在告别语到达 harness 前就把管线拆掉。
bot 通过$LLM_PROVIDER选择 LLM(默认openai_responses;hello_world始终用 Google):LLM_PROVIDER=anthropic ./run.sh -p flows(也支持google、aws)。llm_switching需要 OpenAI、Google、Anthropic 三个 key 全部设置。warm_transfer.py(Daily + 真人坐席)不在覆盖范围内。
Simulation 场景:LLM 扮演来电者
Scripted 场景把用户侧写死;simulation场景用persona取代剧本:一个 LLM 扮演一位有目标的来电者,对话需要它说什么它就说什么,目标达成或明显无法达成时挂断(调用end_call工具)。随后裁判阅读整段对话连同 bot 调用过的工具,判断来电者是否得到了想要的东西。
manifest 中用一个纯语音 bot(text 和 audio 各一次好奇来电者)校验 simulation 机制本身在两种模态下都工作;其余覆盖 Flows 示例,因为那批 bot 有真正要完成的任务:订桌、收集患者信息、下单、报价。仓库内置的 9 个 simulation:
| Simulation | Bot | 来电者 |
|---|---|---|
capital_curious | voice/voice-cartesia.py | 问德国的首都,然后挂断,文本模式 |
capital_curious_audio | voice/voice-cartesia.py | 同样的来电者,但说话和倾听 |
book_table_available | flows/restaurant_reservation.py | 订今晚 6 点两位,该时段有空位 |
book_table_flexible | flows/restaurant_reservation.py | 想要 7 点(被占)四位,但接受 6 到 9 点之间任意时间 |
book_table_impossible | flows/restaurant_reservation.py | 只能 7 点或 8 点,均被占;成功 = 得体地拒绝 |
complete_patient_intake | flows/patient_intake.py | 提供生日、处方、过敏和病情 |
order_pizza | flows/food_ordering.py | 订一份大 pepperoni 披萨并询问配送时间 |
order_sushi | flows/food_ordering_advanced_functionschema.py | 订三份加州卷 |
get_insurance_quote | flows/insurance_quote.py | 先拿一次报价,再拿一次保额更高的报价 |
运行 simulation 的命令:
./run.sh -k simulation # 只跑全部 simulation ./run.sh -p flows # Flows bot:其 scripted 场景 + simulation ./run.sh -s book_table_available # 单个 simulation,按其文件声明的次数运行 ./run.sh -s order_pizza -r 5 # 单个 simulation,跑 5 次以 book_table_available.yaml 为例看文件格式:
name: book_table_available simulator: !include ../simulator.yaml judge: !include ../judge_text.yaml persona: | Jamie, calling to book a table for tonight. Friendly and to the point: gives the party size and the time when asked, one detail per turn, and says goodbye once the booking is confirmed. goal: "Book a table for two at 6 PM tonight, then end the call." success: "the bot confirmed a reservation for a party of two at 6 PM" metrics: - name: efficiency criterion: "the reply does not ask the caller to repeat information they already gave (confirming it back is fine)" min_score: 1 - name: politeness criterion: "the reply is courteous and helpful, never curt or dismissive" min_score: 1 max_turns: 8 max_duration_s: 120 runs: 3字段语义(完整说明在 src/pipecat/evals/simulation.py 模块 docstring):
persona/goal:来电者的人设与目标,都进入 persona LLM 的指令;success:bot"完成任务"的判据,裁判对整段对话裁决。裁判能看到 bot 的工具调用(名称和参数)但看不到结果。bot 是否发起了某个调用是function_calls指标的职责,无需裁判;如果回复必须匹配后端数据,就把期望值写进success或 criterion("the reply says the appointment is on Tuesday September fifteenth"),并保持 mock 确定性以便跨运行恒真;metrics:判卷型质量指标,每个含name、criterion、可选min_score(0..1)。criterion 描述 bot 的每一条回复应该满足什么,裁判在整段 transcript 上一次调用里逐轮判 yes/no(从不给部分分);得分就是拿到 yes 的轮次占比,所以min_score: 1表示"一条都不能错",0.8允许五轮里错一轮。写法上应写成条件句并说明界外回复会做什么("when the reply turns down a time, it offers alternatives"),否则裁判会把 "never" 读成 "always"。bot 只需做一次的事(如读回确认)应写进success而不是 metrics;- 测量型指标:
measure:取turns、duration、words或latency,配min_value/max_value(至少一个),由 harness 从运行中计算,不经裁判。逐回复的度量对每条回复分别约束,所以latency取最慢的回复、words取最长的回复。measure: function_calls则用calls:列表代替区间:列出 bot 应发起的调用(按名,args为子集匹配),缺少任一调用或出现列表外的调用都失败;calls: []表示 bot 不许调用任何工具——用来检查应当被拒绝的来电者; max_turns(默认 20)、max_duration_s(默认 300 秒)、max_silence_s(默认 30 秒):persona 轮次上限、运行墙钟上限、双方都无事件的空窗上限。一个从不问候或停止回答的 bot 会以silence结束运行,而不是耗满时钟。harness 自身管线故障(首先是 persona LLM)会立即以 error 结束运行;runs:套件运行该 simulation 的次数(默认 1)。每一次运行都必须通过:persona 不会两次说同样的话,单次运行只是轶事,三次才算检查。
运行判定的完整规则:一次运行通过,当且仅当裁判判定 bot 完成了任务(success)、没有任何带min_score的判卷指标低于下限、没有任何测量型指标超出区间或调用列表校验失败;失败时会说明是哪一类先垮。results.jsonl携带每个指标的 score、value 和每一轮的裁决。套件为每个 simulation 打印通过率和"是否全部运行通过"的 ✓/✗,任一未通过则以非零码退出;加--repeat后整体变成测量模式:只报告速率,退出码保持 0。出错的运行(bot 始终没起来、persona LLM 失败、裁判对目标无裁决)会被报告但不计入速率。
persona LLM 是simulator:块,默认与裁判同用本地 Ollama 模型,所以 simulation 不需要任何 API key;simulator.yaml 是所有 simulation 统一改换模型的地方(模型必须支持函数调用——persona 通过end_call工具挂断,把"挂断"写成文字输出的模型永远挂不掉)。音频模式下 persona 的轮次同样经 Kokoro 合成、bot 语音同样经 Moonshine 转写,所以capital_curious_audio用一个自主来电者同时压测 bot 的 STT、TTS 与轮次控制。
对已运行的 bot 跑单个场景
如果 bot 已经在以-t eval运行,可以直接对两种场景跑单测(迭代场景或单个 bot 时很顺手):
pipecat eval run scenarios/scripted/capital_question.yaml --bot-url ws://localhost:7860 pipecat eval run scenarios/simulated/capital_curious.yaml --bot-url ws://localhost:7860 -vsimulation 与 scripted 用的是同一条命令,-v会随对话进行实时打印。
添加覆盖
为评测体系补充新覆盖的路径(见 README "Adding coverage"):
- 新 bot:在 manifest.yaml 加一个条目(
bot:+ 它应运行的scenarios:); - 新行为:添加 scenarios/scripted/ 下的
<name>.yaml,并在 manifest 中以scripted/<name>引用; - 新目标:添加 scenarios/simulated/ 下带
persona:的<name>.yaml,并在对应 bot 条目下以simulated/<name>引用。
小结
Pipecat 的 Release Evals 把一个开源框架最容易腐化的部分——"示例库随版本漂移"——变成了可重复执行的工程流程:bot 以统一的 eval 传输层被拉起,场景文件以声明式 YAML 描述行为契约,裁判、TTS 与 STT 全部默认本地化运行以消除 key 与按次成本,--repeat把"单次通过"升级成可度量的通过率,而results.jsonl与按FAILURE_KINDS分组的失败汇总让跨 provider 的回归可以按类型、按轮次定位。对维护一个多 provider 示例库的团队来说,这套机制的价值在于:每个语音/TTS/realtime/Flows provider 的示例共用同一批场景,行为回归在合并前就会被裁判看见,而不是在用户报告里。
【免费下载链接】pipecatOpen Source framework for voice agents, multimodal apps, and realtime AI. Maintained by Daily and the community.项目地址: https://gitcode.com/GitHub_Trending/pi/pipecat
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考