CopilotKit 语音输入 QA 实战:LangGraph (Python) 集成中 Voice Demo 的验收清单与源码级实现解析

CopilotKit 语音输入 QA 实战:LangGraph (Python) 集成中 Voice Demo 的验收清单与源码级实现解析 CopilotKit 语音输入 QA 实战LangGraph (Python) 集成中 Voice Demo 的验收清单与源码级实现解析【免费下载链接】CopilotKitThe Frontend Stack for Agents Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit本文围绕 CopilotKit 仓库中 LangGraph (Python) 集成的语音输入Voice InputQA 文档展开完整讲解/demos/voice页面的两条输入路径示例音频注入与麦克风转写、V2 Runtime 中transcriptionService的接线方式以及配套的 Playwright E2E 自动化验证与手动 QA 检查清单。读完本文你将掌握如何为 CopilotKit 的语音输入能力建立可重复的验收标准并理解从前端 Composer 麦克风按钮到 OpenAI Whisper 转写端点的完整调用链。演示环境与前置条件该 QA 检查清单showcase/integrations/langgraph-python/qa/voice.md定义了一条可执行的验收流程。执行前需要满足以下前置条件原文档 Prerequisites 的完整继承Demo 已部署并可访问于/demos/voiceRailway 服务showcase-langgraph-python处于健康状态/api/health返回 200Railway 服务上已设置OPENAI_API_KEY与其他 Demo 共享支持MediaRecorder的现代浏览器Chromium、Firefox、Safari 14可用的麦克风硬件仅第 3 节的麦克风路径需要仓库中已捆绑public/demo-audio/sample.wav用于截图/预览生成应用内的示例按钮不再拉取该文件。最后一条与源码实现一致示例按钮已改为同步注入固定文本不再发起音频请求或转写请求。showcase/integrations/langgraph-python/public/demo-audio/README.md进一步说明了该音频资产的规格文件需小于 100KB目标为16kHz 单声道、3–5 秒时长内容为一句What is the weather in Tokyo?——Demo 页面上的说明文字向用户展示该短语QA 检查清单与 E2E 规格则断言转写/注入文本包含 weather 或 Tokyo。该文档还给出了本地生成样音的三种方式macOSsayffmpeg、Linuxespeak-ng、Windows PowerShellSpeechSynthesizer例如# macOS say -o sample.aiff What is the weather in Tokyo? \ ffmpeg -i sample.aiff -ar 16000 -ac 1 sample.wav # Linux espeak-ng -w sample.wav What is the weather in Tokyo?页面结构两条并行的语音输入路径/demos/voice页面由showcase/integrations/langgraph-python/src/app/demos/voice/page.tsx挂载核心是一个指向专用 runtime 端点的CopilotKit容器CopilotKit runtimeUrl/api/copilotkit-voice agentvoice-demo useSingleEndpoint{false} enableInspector{false} VoiceChat / /CopilotKit源码注释解释了enableInspector{false}的原因开发环境下自动启用的cpk-web-inspector浮层会拦截示例音频按钮上方的指针事件导致本地 Playwright 探针无法点击该按钮因此显式禁用让 Demo 在开发环境与生产环境行为一致voice Demo 在生产环境对应 D5 级别、本地为 D4 级别。voice-chat.tsxshowcase/integrations/langgraph-python/src/app/demos/voice/voice-chat.tsx#L31-L73中定义了页面的两条输入路径麦克风路径当 runtime 在/info端点宣告audioFileTranscriptionEnabled: true时CopilotChat /的 Composer 会自动渲染麦克风按钮。点击录音、再次点击停止音频经 runtime 的/transcribe端点转写后自动填充 Composer。这是唯一真正走通 Whisper 转写的路径。示例音频路径页面下方的SampleAudioButton组件同步将一个固定短语注入聊天输入框——不请求麦克风权限、不拉取音频文件、不访问/transcribe端点。它是为 Playwright 和截图流程准备的确定性测试/演示能力。示例按钮同步注入与 React 受控输入的正确姿势sample-audio-button.tsx#L27-L43中的按钮非常薄button typebutton >const nativeSetter Object.getOwnPropertyDescriptor( window.HTMLTextAreaElement.prototype, value, )?.set; if (nativeSetter) { nativeSetter.call(textarea, text); } else { textarea.value text; } textarea.dispatchEvent(new Event(input, { bubbles: true })); textarea.focus();源码注释点明了原理React 对受控输入维护自己最后已知值直接赋值textarea.value不会让 React 观察到变化必须经由原生 setter 写入下一次input事件才会驱动受控状态更新。这就是点击后输入框立即出现What is the weather in Tokyo?无异步往返、无 Transcribing… 中间态这一 QA 断言的实现依据。Voice RuntimetranscriptionService 的接线与鉴权守护语音能力由专用 API 路由showcase/integrations/langgraph-python/src/app/api/copilotkit-voice/[[...slug]]/route.ts提供。该路由直接接线V2CopilotRuntime来自copilotkit/runtime/v2源码注释给出了两个关键设计事实V1 包装器copilotkit/runtime会丢弃transcriptionService选项其构造函数上有对应 TODO因此必须走 V2V2 按 URL 路由分发/info、/agent/:id/run、/transcribe等子路径所以路由文件放在 catch-all 的[[...slug]]/route.ts下捕获/api/copilotkit-voice之下的所有子路径。agent 的接线与缓存route.ts#L38-L119const LANGGRAPH_URL process.env.LANGGRAPH_DEPLOYMENT_URL || http://localhost:8123; const voiceDemoAgent new LangGraphAgent({ deploymentUrl: LANGGRAPH_URL, graphId: sample_agent, });LANGGRAPH_DEPLOYMENT_URL未设置时回落到本地 LangGraph 服务http://localhost:8123前端agentvoice-demo在 runtime 中解析为该 agent同时别名default指向同一sample_agent图保证内部默认 agent 查找也能命中runtime 与 handler 通过模块级cachedHandler跨调用缓存转写服务每个 Node 进程只构造一次。GuardedOpenAITranscriptionService把缺 key变成可预期的 4xx核心类是GuardedOpenAITranscriptionServiceroute.ts#L62-L88它继承自copilotkit/runtime/v2的TranscriptionService做两件事缺失OPENAI_API_KEY时抛出类型化鉴权错误。错误信息刻意包含 api key 子串因为 V2 runtime 的handleTranscribe会把包含 api key 或 unauthorized 的错误消息映射为AUTH_FAILED → HTTP 401。这样未配置 key的场景落入确定性的 4xx 路径而不是不透明的 5xxbaseURL钉死为真实 OpenAI。构造时读取OPENAI_TRANSCRIPTION_BASE_URL未设置则回落https://api.openai.com/v1而不是跟随OPENAI_BASE_URL。源码注释解释了动机本地 docker / Railway 预览环境中OPENAI_BASE_URL指向 aimock 以让 LLM 补全保持确定性但 aimock 存在一个 catchall 的endpoint: transcriptionfixture若不显式钉死 baseURL真实麦克风录音也会被拦截并返回固定短语 What is the weather in Tokyo?。设计上明确分工示例按钮负责确定性文本注入麦克风是唯一应走真实 Whisper 的路径。有 key 时守护类委托给copilotkit/voice包提供的TranscriptionServiceOpenAI上游 Whisper 的错误保留其天然分类。TranscriptionServiceOpenAI 的配置参数packages/voice/src/transcription/transcription-service-openai.ts#L10-L30定义了TranscriptionServiceOpenAIConfig可作为接入语音转写时的参数参考参数类型默认值说明openaiOpenAI必填构造时传入 clientOpenAI 客户端实例demo 中用new OpenAI({ apiKey, baseURL })构造modelstringwhisper-1Whisper 模型languagestring未设置音频语言ISO-639-1 格式如 en提供后可提升准确率与降低延迟promptstring未设置引导模型风格的可选文本应与音频语言一致temperaturenumber未设置0–1 之间越低越确定越高越随机transcribeFile实现直接调用this.openai.audio.transcriptions.create并将可选的language/prompt/temperature按需展开进请求体返回response.texttranscription-service-openai.ts#L48-L57。自动化的 E2E 验证PlaywrightQA 清单并非孤立的文档它由showcase/integrations/langgraph-python/tests/e2e/voice.spec.ts自动化覆盖示例音频路径并明确把麦克风路径划出自动化范围MediaRecorder 在无头浏览器中难以不 mock 地驱动麦克风由人工 QA 清单覆盖。稳定性预期是在 Railway 上连续 3 次运行全部通过。三个测试用例页面加载验证Voice input标题可见、voice-sample-audio-button处于可用状态、Try a sample audio 文案可见、Composercopilot-chat-input渲染、以及关键的麦克风按钮copilot-start-transcribe-button可见。E2E 注释指出麦克风按钮是 runtime 已宣告audioFileTranscriptionEnabled: true即transcriptionService已接线到/api/copilotkit-voice的权威信号它在前端/info往返解析后才渲染冷开发服务器上可能超过 Playwright 默认 5 秒因此把超时放宽到 15 秒。示例按钮同步注入断言点击后 1 秒内输入框匹配/weather|tokyo/i且无瞬态 Transcribing… 状态、无/transcribe往返按钮保持可用。发送后产生天气工具渲染由于 voice-demo 复用的是中性sample_agent图本身不渲染天气卡片断言采用宽松策略——weather-card、custom-catchall-card[data-tool-nameget_weather]、copilot-assistant-message三者之一出现即可关心的是出现了某个 agent 生成的响应面。整条链路在冷 LangGraph 开发服务器上可耗时约 50 秒故测试超时设为 90 秒、定位器自身 45 秒。手动 QA 检查清单原文档 Test Steps 完整继承以下检查项来自 QA 文档的 Test Steps执行时以页面data-testid为准1. 基本功能导航到/demos/voice确认页面标题 Voice input 可见确认示例音频行data-testidvoice-sample-audio可见确认说明文字为Sample: What is the weather in Tokyo?确认 Play sample 按钮data-testidvoice-sample-audio-button处于可用状态确认CopilotChat /渲染了消息 Composerdata-testidcopilot-chat-input确认 Composer 显示麦克风按钮data-testidcopilot-start-transcribe-button——这是transcriptionService已挂载到/api/copilotkit-voice的权威信号。2. 示例音频路径无需麦克风权限点击 Play sample 按钮聊天输入框data-testidcopilot-chat-textarea立即包含固定短语 What is the weather in Tokyo?无异步往返、无 Transcribing… 状态按钮保持可用点击发送data-testidcopilot-send-button10 秒内agent 以天气相关的工具渲染响应WeatherCard、自定义 catch-all 卡片或默认工具卡片取决于该页面当前生效的工具渲染模式。3. 麦克风路径人工执行点击 Composer 中的麦克风按钮data-testidcopilot-start-transcribe-button在浏览器提示中授予麦克风权限清晰说出 Hello然后再次点击麦克风按钮此时 testid 变为copilot-finish-transcribe-button停止录音5 秒内输入框包含匹配 hello不区分大小写的文本点击发送agent 在 10 秒内响应。4. 错误处理拒绝麦克风权限后点击麦克风按钮验证 UI 优雅处理权限拒绝无崩溃、麦克风按钮仍然可见。预期结果与通过标准原文档 Expected Results 定义了该 Demo 的通过标准示例按钮点击同步填充输入框无可感知延迟天气相关的工具响应在发送后 10 秒内渲染成功路径期间控制台无错误麦克风路径的 Whisper 转写返回与所说话语相近的文本前提是部署环境已配置OPENAI_API_KEY。结合源码可以补充两个验收时的判断依据其一如果麦克风按钮始终不出现应检查/api/copilotkit-voice的 runtime 是否正确构造了transcriptionServiceV1 包装器会静默丢弃该选项这是一个典型的接线陷阱其二如果转写请求返回 401这通常是未配置 key被GuardedOpenAITranscriptionService有意映射到AUTH_FAILED的结果而不是代理或网络问题。适用前提与限制该 QA 文档与 Demo 仅对应showcase/integrations/langgraph-python这一套集成部署/demos/voice路由与/api/copilotkit-voice端点均在该集成内部麦克风转写依赖部署环境的OPENAI_API_KEY以及可选的OPENAI_TRANSCRIPTION_BASE_URL示例音频路径则完全不依赖转写端点可在任何环境离线验证本地开发时LANGGRAPH_DEPLOYMENT_URL缺省指向http://localhost:8123的 LangGraph 服务需要先启动本地 agent 后端才能完成发送后的端到端响应。【免费下载链接】CopilotKitThe Frontend Stack for Agents Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考