Pipecat Release Evals:用 LLM 判卷的自动化场景套件,守护语音智能体发布的每一步 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/pipecatPipecat 仓库自带一套发布前的自动化行为评测系统 Release Evals它把 100 多个官方示例当作真实 bot 逐个拉起由评测框架扮演用户与之对话、用本地 STT 转写 bot 的实际语音、再用本地 LLM 当裁判判卷从而在每次发布前自动验证全部示例是否仍然可用。本文基于仓库中的 scripts/release-evals/README.md 编写并结合 src/pipecat/evals 下的源码实现讲清这套系统的工作原理、环境准备、运行方式、稳定性测量方法以及如何为新的 bot 和新的行为编写评测场景。为什么需要 Release EvalsPipecat 仓库的 examples/ 目录下维护着 100 多个示例语音智能体、realtime 服务、函数调用、Flows 结构化对话、多 worker 协作、视觉与视频头像等。每发布一个版本都需要确认这些示例至少是绝大多数还能正常工作。靠人工逐个跑一遍既慢又痛苦因此仓库提供了这些 release evals用自动化的方式驱动每一个示例。从源码结构看整套系统由三部分组成被评测的 bot每个示例本身就是一个 Pipecat bot用评测专用传输层以-t eval启动评测 harnesspipecat.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 语音的转写器。因此需要以下准备裁判 LLMJudge场景默认使用 Ollamahttp://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: nonegemma4支持推理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 的tiny75MB。其他依赖Node.js仅 MCP botmcp/mcp-stdio.py 用npx拉起内存 MCP serverserver 包在首次使用时下载每个 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_eventabsent:断言看到了被禁止的事件、send_after_timeout、connect_failed、handshake_timeout、judge_no_verdict、error。正因为reason是自由文本常是裁判的散文每次运行都不同跨运行分组失败只能键在kind上。重复扫描始终以 0 退出它只报告通过率什么速率可接受是你的策略而不是 harness 的策略。每次运行无论是否重复还会写results.jsonl每个运行一行 JSON含其kindscript或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_eventsllm_responseLLM 文本输出bot-llm-text两种模态都可用tts_responseTTS 报告要说的话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_EVENTSeval:只对 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 docstringcontext:提供 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 的真实音频管线就加上共享 includeuser: !include user_audio.yaml、judge: !include judge_audio.yaml。写作惯例每轮断言function_call外加一条responseevalresponse事件同时给运行打拍子harness 等 bot 说完才发下一轮终结轮只断言函数调用——end_conversation会在告别语到达 harness 前就把管线拆掉。bot 通过$LLM_PROVIDER选择 LLM默认openai_responseshello_world始终用 GoogleLLM_PROVIDERanthropic ./run.sh -p flows也支持google、aws。llm_switching需要 OpenAI、Google、Anthropic 三个 key 全部设置。warm_transfer.pyDaily 真人坐席不在覆盖范围内。Simulation 场景LLM 扮演来电者Scripted 场景把用户侧写死simulation场景用persona取代剧本一个 LLM 扮演一位有目标的来电者对话需要它说什么它就说什么目标达成或明显无法达成时挂断调用end_call工具。随后裁判阅读整段对话连同 bot 调用过的工具判断来电者是否得到了想要的东西。manifest 中用一个纯语音 bottext 和 audio 各一次好奇来电者校验 simulation 机制本身在两种模态下都工作其余覆盖 Flows 示例因为那批 bot 有真正要完成的任务订桌、收集患者信息、下单、报价。仓库内置的 9 个 simulationSimulationBot来电者capital_curiousvoice/voice-cartesia.py问德国的首都然后挂断文本模式capital_curious_audiovoice/voice-cartesia.py同样的来电者但说话和倾听book_table_availableflows/restaurant_reservation.py订今晚 6 点两位该时段有空位book_table_flexibleflows/restaurant_reservation.py想要 7 点被占四位但接受 6 到 9 点之间任意时间book_table_impossibleflows/restaurant_reservation.py只能 7 点或 8 点均被占成功 得体地拒绝complete_patient_intakeflows/patient_intake.py提供生日、处方、过敏和病情order_pizzaflows/food_ordering.py订一份大 pepperoni 披萨并询问配送时间order_sushiflows/food_ordering_advanced_functionschema.py订三份加州卷get_insurance_quoteflows/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 模块 docstringpersona/goal来电者的人设与目标都进入 persona LLM 的指令successbot完成任务的判据裁判对整段对话裁决。裁判能看到 bot 的工具调用名称和参数但看不到结果。bot 是否发起了某个调用是function_calls指标的职责无需裁判如果回复必须匹配后端数据就把期望值写进success或 criterionthe reply says the appointment is on Tuesday September fifteenth并保持 mock 确定性以便跨运行恒真metrics判卷型质量指标每个含name、criterion、可选min_score0..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 keysimulator.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),仅供参考