Files

1 line
1.8 MiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "boyzhong123/testmcp", "files": {"src/app/[locale]/docs/page.tsx": {"6270b78927175ed620bfbf35547988ca74f83f20": {"t": 1785746567.7356908, "n": 190353, "h": "ecf33defbf4a8dac", "c": "'use client';\n\nimport { useLocale } from 'next-intl';\nimport { Link } from '@/i18n/routing';\nimport { DocsShell, type NavGroup } from './docs-shell';\nimport {\n CodeBlock,\n DocSection,\n SubDoc,\n ParamTable,\n Callout,\n FlowStep,\n ToolTable,\n} from './docs-shared';\nimport { DocsEnglishBody } from './docs-content-en';\nimport {\n BookOpen,\n Code2,\n Zap,\n FileText,\n Terminal,\n Globe,\n Mic,\n MessageSquare,\n BarChart3,\n AlertTriangle,\n Radio,\n FolderOpen,\n Workflow,\n Sparkles,\n Plug,\n Bot,\n Building2,\n Check,\n Sparkle,\n Languages,\n GraduationCap,\n Notebook,\n Gauge,\n ListChecks,\n Network,\n} from 'lucide-react';\n\n/* ─── Navigation Structure ─── */\n\nconst NAV = [\n { id: 'concept', icon: Workflow, label: 'MCP 工作原理', children: [\n { id: 'why-mcp', label: '为什么需要 MCP' },\n { id: 'scenario-walkthrough', label: '场景走查 · 6 步' },\n { id: 'dev-responsibility', label: '开发者只做两件事' },\n { id: 'integration-paths', label: '三种接入姿势' },\n ]},\n { id: 'architecture', icon: Terminal, label: '架构说明', children: [\n { id: 'arch-overview', label: '两种接入模式总览' },\n { id: 'arch-mcp', label: 'MCP 模式架构' },\n { id: 'arch-rest', label: 'functioncalling 模式架构' },\n { id: 'transport', label: '传输协议对比' },\n ]},\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'requirements', label: '系统要求' },\n { id: 'install', label: '安装方式' },\n { id: 'env-vars', label: '环境变量' },\n ]},\n { id: 'config', icon: Plug, label: '接入 · 直接用(零代码)', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: '扣子(Coze)' },\n { id: 'config-coze-workflow', label: 'Coze 工作流' },\n { id: 'config-ai-workspace', label: 'AI 工作台 · 豆包 / 飞书 / WorkBuddy / 钉钉' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: '其他 MCP 客户端' },\n ]},\n { id: 'config-code', icon: Code2, label: '接入 · 编程用 MCP(SDK / 代码)', children: [\n { id: 'code-mcp-sdk', label: '① 官方 MCP 客户端库' },\n { id: 'code-agent-frameworks', label: '② LangChain / Mastra / Agents SDK' },\n { id: 'code-function-calling', label: '③ 直调 chat.completions · 动态 tools' },\n { id: 'code-selfhosted-agent', label: '④ 自研后端 Agent' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM(智谱)' },\n { id: 'llm-kimi', label: 'KIMI(Moonshot)' },\n { id: 'llm-doubao', label: '豆包 Seed 2.0(火山方舟)' },\n { id: 'llm-openai', label: 'OpenAI(GPT-5.1)' },\n { id: 'llm-claude', label: 'Claude(Anthropic)' },\n { id: 'llm-gemini', label: 'Gemini(Google)' },\n { id: 'llm-comparison', label: '模型对比速查' },\n ]},\n { id: 'config-rest', icon: Network, label: '接入 · 非 MCP 原生 REST(cvx_fc)', children: [\n { id: 'rest-overview', label: 'functioncalling 模式总览' },\n { id: 'rest-catalog', label: '函数目录 · GET /v1/functions' },\n { id: 'rest-oneshot', label: '一次性文件评测' },\n { id: 'rest-streaming', label: '流式评测 · 三步骤' },\n { id: 'rest-examples', label: 'JS / Android 完整示例' },\n { id: 'rest-resume', label: '断线恢复 · 兜底拉取' },\n ]},\n { id: 'eval-modes', icon: Radio, label: '评测模式', children: [\n { id: 'stream-eval', label: '实时录音评测' },\n { id: 'stream-flow', label: '流式评测流程' },\n { id: 'file-eval', label: '音频文件评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'tools-en', label: '英文评测工具' },\n { id: 'tools-cn', label: '中文评测工具' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n { id: 'service-info', icon: Globe, label: '服务信息', children: [\n { id: 'endpoints', label: '服务端点' },\n { id: 'limits', label: '服务限制' },\n { id: 'changelog', label: '更新日志' },\n ]},\n];\n\nconst NAV_EN: NavGroup[] = [\n { id: 'concept', icon: Workflow, label: 'How MCP works', children: [\n { id: 'why-mcp', label: 'Why MCP' },\n { id: 'scenario-walkthrough', label: 'Scenario · 6 steps' },\n { id: 'dev-responsibility', label: 'Two developer tasks' },\n { id: 'integration-paths', label: 'Three integration styles' },\n ]},\n { id: 'architecture', icon: Terminal, label: 'Architecture', children: [\n { id: 'arch-overview', label: 'Two integration modes' },\n { id: 'arch-mcp', label: 'MCP mode architecture' },\n { id: 'arch-rest', label: 'Function-calling architecture' },\n { id: 'transport', label: 'Transport comparison' },\n ]},\n { id: 'quick-start', icon: Zap, label: 'Quick start', children: [\n { id: 'overview', label: 'Overview' },\n { id: 'requirements', label: 'Requirements' },\n { id: 'install', label: 'Install' },\n { id: 'env-vars', label: 'Environment' },\n ]},\n { id: 'config', icon: Plug, label: 'Zero-code clients', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: 'Coze' },\n { id: 'config-coze-workflow', label: 'Coze workflows' },\n { id: 'config-ai-workspace', label: 'Enterprise AI workspaces' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: 'Other MCP clients' },\n ]},\n { id: 'config-code', icon: Code2, label: 'MCP · SDK / code integration', children: [\n { id: 'code-mcp-sdk', label: '① Official MCP SDK' },\n { id: 'code-agent-frameworks', label: '② Agent frameworks' },\n { id: 'code-function-calling', label: '③ chat.completions · dynamic tools' },\n { id: 'code-selfhosted-agent', label: '④ Custom backend agent' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM (Zhipu)' },\n { id: 'llm-kimi', label: 'KIMI (Moonshot)' },\n { id: 'llm-doubao', label: 'Doubao Seed 2.0 (Volcano Ark)' },\n { id: 'llm-qwen', label: 'Qwen (Alibaba)' },\n { id: 'llm-openai', label: 'OpenAI (GPT-5.1)' },\n { id: 'llm-claude', label: 'Claude (Anthropic)' },\n { id: 'llm-gemini', label: 'Gemini (Google)' },\n { id: 'llm-comparison', label: 'Model comparison' },\n ]},\n { id: 'config-rest', icon: Network, label: 'Non-MCP REST (cvx_fc)', children: [\n { id: 'rest-overview', label: 'Function-calling overview' },\n { id: 'rest-catalog', label: 'Function catalog · GET /v1/functions' },\n { id: 'rest-oneshot', label: 'One-shot file eval' },\n { id: 'rest-streaming', label: 'Streaming · 3 steps' },\n { id: 'rest-examples', label: 'JS / Android samples' },\n { id: 'rest-resume', label: 'Resume & fallback' },\n ]},\n { id: 'eval-modes', icon: Radio, label: 'Evaluation modes', children: [\n { id: 'stream-eval', label: 'Streaming mic' },\n { id: 'stream-flow', label: 'Streaming flow' },\n { id: 'file-eval', label: 'Audio files' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API reference', children: [\n { id: 'tools-en', label: 'English tools' },\n { id: 'tools-cn', label: 'Chinese tools' },\n { id: 'result-fields', label: 'Result fields' },\n { id: 'error-codes', label: 'Errors' },\n ]},\n { id: 'best-practices', icon: FileText, label: 'Best practices', children: [\n { id: 'prompt-templates', label: 'Prompt templates' },\n { id: 'error-handling', label: 'Errors & retries' },\n { id: 'performance', label: 'Performance' },\n ]},\n { id: 'service-info', icon: Globe, label: 'Service info', children: [\n { id: 'endpoints', label: 'Endpoints' },\n { id: 'limits', label: 'Limits' },\n { id: 'changelog', label: 'Changelog' },\n ]},\n];\n\n/* ─── Page(英文 / 中文拆成两个组件,避免切换语言时 Hooks 数量变化导致白屏)─── */\n\nfunction DocsPageEn() {\n return (\n <DocsShell\n nav={NAV_EN}\n backLabel=\"Back to Home\"\n title=\"Developer documentation\"\n subtitle=\"Chivox Speech Evaluation MCP · chivox-local-mcp\"\n >\n <DocsEnglishBody />\n </DocsShell>\n );\n}\n\nfunction DocsPageZh() {\n return (\n <DocsShell\n nav={NAV}\n backLabel=\"返回首页\"\n title=\"开发者文档\"\n subtitle=\"驰声语音评测 MCP · chivox-local-mcp\"\n >\n {/* ══════ MCP 工作原理 ══════ */}\n <DocSection id=\"concept\" icon={Workflow} title=\"MCP 工作原理\">\n\n <SubDoc id=\"why-mcp\" title=\"为什么需要 MCP\">\n <p>\n 大模型(GPT / Claude / Gemini / 通义千问 等)本质上是<strong>文字大脑</strong>:它擅长读和写,但天生<strong>听不到</strong>音频、<strong>打不开</strong>你的数据库、<strong>调不通</strong>第三方 API。\n </p>\n <p>\n <strong>MCP(Model Context Protocol)</strong>是一套由 Anthropic 主导、业界共同推进的开放协议,它像一根\"对讲机\":让大模型可以在需要时主动去调用外部能力(工具、数据源、服务),然后把结果接回来继续对话。\n </p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <AlertTriangle className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n <span className=\"text-sm font-semibold\">没有 MCP 的做法</span>\n </div>\n <ul className=\"text-xs text-muted-foreground space-y-1.5 leading-relaxed\">\n <li>· 研究评测服务的 REST 文档</li>\n <li>· 自己写鉴权 / 上传 / 轮询代码</li>\n <li>· 把结果序列化后塞进 prompt</li>\n <li>· 每换一个大模型都得重写一遍</li>\n </ul>\n </div>\n <div className=\"rounded-lg border-2 border-foreground/60 p-4 bg-foreground/[0.02]\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Check className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">用驰声 MCP 之后</span>\n </div>\n <ul className=\"text-xs space-y-1.5 leading-relaxed\">\n <li>· 填一行 MCP Server 地址</li>\n <li>· 大模型自动发现&amp;调用评测工具</li>\n <li>· 换任何支持 MCP 的大模型都能用</li>\n <li>· 不写一行调用代码</li>\n </ul>\n </div>\n </div>\n <Callout type=\"info\">\n MCP 让\"接入语音评测\"从一个\"后端工程\"变成了一个\"配置项\"。这也是为什么 Cursor、Claude Desktop、Dify、Coze、字节豆包、飞书智能助手都已经原生支持 MCP。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"scenario-walkthrough\" title=\"场景走查 · 从一段录音到温柔反馈\">\n <p>\n 想象一个具体场景:一位开发者用 Claude 做了一款 <strong>「AI 雅思口语陪练」小程序</strong>,接入驰声语音评测 MCP。当用户发来一段录音,整个链路是这样的:\n </p>\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-5 my-4 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong className=\"text-foreground\">配置阶段</strong>(开发者 · 一次性)</p>\n <p className=\"text-muted-foreground mt-1\">在 AI 后端的配置文件里填一行驰声 MCP Server 地址。大模型启动时自动发现:「我多了一个叫<strong>语音评测</strong>的工具」。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong className=\"text-foreground\">接收用户指令</strong>(终端用户)</p>\n <p className=\"text-muted-foreground mt-1\">小程序用户发来一段自我介绍录音:「帮我听听,发音哪里有问题?」后端把「文字 + 录音链接」打包给大模型。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong className=\"text-foreground\">大模型自主思考</strong>(LLM · 最核心的魔法)</p>\n <div className=\"text-muted-foreground mt-1 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed\">\n 「用户让我听发音。」<br />\n 「但我只是文字大脑,我听不到。」<br />\n 「等等 —— 开发者给了我一个叫 <code className=\"bg-muted px-1 rounded\">chivox_voice_eval</code> 的 MCP 工具!」\n </div>\n </FlowStep>\n <FlowStep title=\"4\">\n <p><strong className=\"text-foreground\">MCP 协议 · 自动调用</strong>(LLM → 驰声)</p>\n <p className=\"text-muted-foreground mt-1\">大模型自动按 MCP 标准构造 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools/call</code> 请求,把录音链接和参考文本发到驰声服务。<span className=\"text-foreground\">开发者不需要写任何调用代码。</span></p>\n </FlowStep>\n <FlowStep title=\"5\">\n <p><strong className=\"text-foreground\">驰声服务处理</strong>(评测引擎)</p>\n <p className=\"text-muted-foreground mt-1\">驰声对音频进行考试级打分,把结构化结果通过 MCP 原路返回:<code className=\"bg-muted px-1 rounded text-xs font-mono\">overall 85</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">/θ/ 音发音错误</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">流利度 88</code>。</p>\n </FlowStep>\n <FlowStep title=\"6\">\n <p><strong className=\"text-foreground\">大模型整合并输出反馈</strong>(LLM)</p>\n <p className=\"text-muted-foreground mt-1\">大模型把冷冰冰的分数翻译成温柔反馈:</p>\n <div className=\"text-foreground/80 mt-2 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed italic\">\n 「你的自我介绍很棒!综合 85 分 🎉 不过 <strong>think</strong> 这个词的 /θ/ 咬舌音不太标准,我们一起来练一下……」\n </div>\n </FlowStep>\n </div>\n <Callout type=\"tip\">\n <strong>整条链路里,MCP 只出现在 ① 和 ④</strong>:① 让开发者一次性告诉大模型「你多了一个工具」,④ 让大模型可以自动调用它。这两步的标准化,就是 MCP 的全部价值。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"dev-responsibility\" title=\"开发者只做两件事\">\n <p>听起来复杂,但对开发者来说只需要两件事:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">1</span>\n <span className=\"text-sm font-semibold\">填一次 MCP 配置</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 在 AI 后端的配置里填入驰声 MCP Server 地址和 API Key,就这一次,以后新加评测能力完全免开发。\n </p>\n <CodeBlock filename=\"mcp-config.json\" lang=\"json\">{`{\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://mcp.cloud.chivox.com\",\n \"apiKey\": \"sk-••••••••••\"\n }\n}`}</CodeBlock>\n </div>\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">2</span>\n <span className=\"text-sm font-semibold\">写一段 System Prompt</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 告诉大模型这个工具是做什么的、什么时候调用、结果怎么转成用户友好的反馈。\n </p>\n <CodeBlock filename=\"system-prompt.md\">{`你是一位英语口语教练。\n当用户发来录音时:\n1. 调用 chivox_voice_eval 评分\n2. 聚焦 <70 分的音素家族\n3. 用高情商话术给出练习建议`}</CodeBlock>\n </div>\n </div>\n <p className=\"text-muted-foreground\">\n 其余所有事情 —— 音频上传、鉴权、工具发现、调用时机、返回格式、结果整合 —— 都由 MCP 协议和大模型自己搞定。\n </p>\n </SubDoc>\n\n <SubDoc id=\"integration-paths\" title=\"三种典型接入姿势\">\n <p>根据你的技术栈,有三种等价的接入方式,底层都走完全相同的 MCP 协议:</p>\n\n {/* ── 姿势一:可视化 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Sparkles className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势一 · 可视化拖拽(扣子 / Coze / 飞书智能助手)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合产品经理、运营、不写后端代码的开发者。整个过程像在手机上装 App。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>找到入口</strong>:在扣子平台的 <strong>「插件 / 工具」</strong> 配置区,点击<strong>「添加 MCP 服务」</strong>。</li>\n <li><strong>填两个字段</strong>(这也是你未来会从驰声这边拿到的):\n <div className=\"mt-2 overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium w-32\">服务器地址</td><td className=\"py-2 px-3 font-mono text-xs\">https://mcp.cloud.chivox.com</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权密钥</td><td className=\"py-2 px-3 font-mono text-xs\">sk-•••••••••• <span className=\"text-muted-foreground ml-2\">(由驰声分配,用于计费与识别)</span></td></tr>\n </tbody>\n </table>\n </div>\n </li>\n <li><strong>平台自动发现能力</strong>:扣子会访问你填的地址,读取 MCP Server 自描述的\"说明书\"。瞬间明白:「这是个语音评测工具,需要传音频,会返发音分」。</li>\n <li><strong>右侧测试</strong>:在平台的聊天框里发一段语音,大模型就会自动调用工具。<strong>整个过程不需要写一行代码。</strong></li>\n </ol>\n </div>\n\n {/* ── 姿势二:代码框架 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Code2 className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势二 · 代码框架配置(Dify / LangChain / 自研后端)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合做独立 APP / 小程序、用 Dify 或 LangChain 写 Agent 的极客开发者。核心就是往配置文件里加一段 JSON。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>打开配置文件</strong>(项目根目录下的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp.json</code> 或框架指定的配置路径)。</li>\n <li><strong>在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code> 下增加一个节点</strong>:\n <CodeBlock filename=\"mcp.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://mcp.cloud.chivox.com\",\n \"env\": {\n \"API_KEY\": \"你的专属密钥\"\n }\n }\n }\n}`}</CodeBlock>\n </li>\n <li><strong>重启后端服务</strong>。大模型的底层调度器会在启动时自动连上驰声 MCP Server,把 16 种评测工具注册成<strong>随时待命</strong>的 tools。</li>\n <li><strong>触发调用</strong>:用户发来录音后,大模型看到 system prompt 的引导,自动选择合适的工具(单词/句子/段落/半开放)调用。</li>\n </ol>\n </div>\n\n {/* ── 姿势三:Function Calling 直连(分 A / B 两种做法) ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Terminal className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势三 · 原生 Function Calling(豆包 / DeepSeek / OpenAI 兼容 API)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合不用任何 Agent 框架、直接调用 LLM API 的后端开发者。豆包 1.5+ / DeepSeek / GPT / Qwen 等模型都走标准 <code className=\"bg-muted px-1 rounded text-xs font-mono\">chat.completions</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> 参数。这里又分 A / B 两种做法,<strong>B 才是生产推荐</strong>。\n </p>\n\n <Callout type=\"warning\">\n 常见疑问:<em>\"要不要手写一个 tool 定义?\"</em><br />\n — <strong>姿势一 / 二不用</strong>(Coze、Dify、火山方舟控制台、Claude Desktop 会自动从 MCP Server 拉工具列表)。<br />\n — <strong>姿势三看你选 A 还是 B</strong>:A 要手写,B 用 MCP 客户端库自动拉。\n </Callout>\n\n {/* ── 3-A:手写 tool schema ── */}\n <div className=\"mt-4 rounded-md border border-border/40 bg-muted/20 p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold\">3-A · 手写 tool schema(最快验证,不推荐生产)</p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20\">快速 Demo</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 自己写一段 JSON Schema 告诉豆包\"有个工具叫 xxx,参数长这样\",豆包返回 tool_call 后你再手动去调驰声 MCP。<strong>缺点</strong>:驰声有 16 个评测工具,你每加一个题型都要改代码。\n </p>\n <CodeBlock filename=\"3a_hardcode_tool.py\" lang=\"python\">{`import os, json\nfrom openai import OpenAI\n\nclient = OpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv('ARK_API_KEY'),\n)\n\n# ─── 手写一段 tool schema(不推荐:16 个工具就要写 16 段)───\ntools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英语单词发音评测。当用户让你评价英语发音并提供录音链接时调用。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"audio_url\": {\"type\": \"string\", \"description\": \"录音 URL\"},\n \"reference_text\": {\"type\": \"string\", \"description\": \"参考文本,如 Apple\"},\n },\n \"required\": [\"audio_url\", \"reference_text\"]\n }\n }\n}]\n\nresp = client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,拿到评测结果后温柔地反馈,不要直接报分数。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n)\n\nmsg = resp.choices[0].message\nif msg.tool_calls:\n args = json.loads(msg.tool_calls[0].function.arguments)\n # ↓↓↓ 这里手动调驰声 MCP,再把结果回填给豆包做第二趟 ↓↓↓\n # result = requests.post(\"https://mcp.cloud.chivox.com\", json={...})`}</CodeBlock>\n </div>\n\n {/* ── 3-B:MCP 自动发现 ── */}\n <div className=\"mt-3 rounded-md border border-foreground/30 bg-foreground/[0.03] dark:bg-white/[0.03] p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold flex items-center gap-1.5\">\n 3-B · 用 MCP 客户端库自动发现 <Sparkles className=\"h-3 w-3 text-foreground\" />\n </p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-foreground text-background\">生产推荐</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 启动时连上驰声 MCP Server,调一次 <code className=\"bg-muted px-1 rounded\">tools/list</code>,把 16 个评测工具自动转成 Function Calling 的 tools 格式。<strong>驰声加新工具,你代码不用改</strong>。这才是 MCP 协议的真正价值。\n </p>\n <CodeBlock filename=\"3b_mcp_autodiscover.py\" lang=\"python\">{`import asyncio, os\nfrom openai import AsyncOpenAI\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\n\nclient = AsyncOpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv(\"ARK_API_KEY\"),\n)\n\nasync def main():\n # ─── 1. 连驰声 MCP Server,自动拉取全部 16 个工具 ───\n async with streamablehttp_client(\n \"https://mcp.cloud.chivox.com\",\n headers={\"Authorization\": f\"Bearer {os.getenv('CHIVOX_KEY')}\"},\n ) as (read, write, _):\n async with ClientSession(read, write) as session:\n await session.initialize()\n mcp_tools = (await session.list_tools()).tools # ← 一行拿到全部工具\n\n # ─── 2. MCP schema → OpenAI Function Calling schema ───\n tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema, # MCP 直接给 JSON Schema\n }\n } for t in mcp_tools]\n\n # ─── 3. 正常调豆包,tools 由 MCP 动态注入 ───\n resp = await client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,收到评测结果后温柔反馈。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n )\n\n # ─── 4. 折返跑:豆包选好工具后,直接走 MCP 调驰声 ───\n msg = resp.choices[0].message\n if msg.tool_calls:\n call = msg.tool_calls[0]\n result = await session.call_tool(\n call.function.name,\n arguments=__import__(\"json\").loads(call.function.arguments),\n )\n # 把 result 回填给豆包做第二趟 chat.completions 即可\n\nasyncio.run(main())`}</CodeBlock>\n <p className=\"text-[11px] text-muted-foreground mt-3 leading-relaxed\">\n 想要更省心?直接用 <a href=\"https://github.com/modelcontextprotocol/python-sdk\" target=\"_blank\" rel=\"noreferrer\" className=\"text-foreground underline underline-offset-2\">官方 mcp python sdk</a>、<code className=\"bg-muted px-1 rounded text-xs\">mcp-use</code>、<code className=\"bg-muted px-1 rounded text-xs\">openai-agents</code>,或火山官方 <code className=\"bg-muted px-1 rounded text-xs\">Arkitect</code> 高代码 SDK,它们都把上面这段\"MCP → Function Calling\"的桥接封装好了。\n </p>\n </div>\n </div>\n\n <Callout type=\"info\">\n 三种姿势走的是完全相同的 MCP 协议,能力完全等价。选型建议:<strong>产品经理 / 原型验证用姿势一</strong>(Coze 填表单);<strong>AI 客户端用户用姿势二</strong>(Cursor / Claude Desktop 填 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">mcp.json</code>);<strong>后端生产服务用姿势三-B</strong>(MCP 客户端库自动发现工具,不要手写 tool schema)。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 架构说明 ══════ */}\n <DocSection id=\"architecture\" icon={Terminal} title=\"架构说明\">\n\n <SubDoc id=\"arch-overview\" title=\"两种接入模式总览\">\n <p>驰声语音评测对外提供<strong>两条并行入口</strong>,底层指向同一个评测引擎,但面向不同的客户端环境,按能力选其一即可:</p>\n\n <div className=\"grid md:grid-cols-2 gap-4 my-5\">\n <div className=\"rounded-xl border-2 border-emerald-500/40 bg-emerald-500/5 p-5\">\n <p className=\"font-semibold mb-2 flex items-center gap-2\">\n <Plug className=\"h-4 w-4 text-emerald-600 dark:text-emerald-400\" />\n MCP 模式(推荐首选)\n </p>\n <ul className=\"text-xs space-y-1.5 text-muted-foreground\">\n <li>• JSON-RPC 2.0 · Streamable HTTP / stdio</li>\n <li>• 所有支持 MCP 的 IDE / 桌面 AI / Agent 框架 <strong className=\"text-foreground\">零代码即用</strong></li>\n <li>• 工具列表自动注入,新增题型<strong className=\"text-foreground\">业务侧不用改</strong></li>\n <li>• 本地代理 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">chivox-local-mcp</code> 支持麦克风实时录音</li>\n </ul>\n <p className=\"text-xs mt-2\"><strong>典型客户端</strong>:Cursor / Claude Desktop / Coze / Dify / LangChain / 自研后端 Agent</p>\n </div>\n <div className=\"rounded-xl border-2 border-sky-500/40 bg-sky-500/5 p-5\">\n <p className=\"font-semibold mb-2 flex items-center gap-2\">\n <Network className=\"h-4 w-4 text-sky-600 dark:text-sky-400\" />\n functioncalling 模式(非 MCP · cvx_fc)\n </p>\n <ul className=\"text-xs space-y-1.5 text-muted-foreground\">\n <li>• OpenAI function-calling 风格 · 纯 REST + WebSocket</li>\n <li>• <strong className=\"text-foreground\">不依赖任何 MCP SDK</strong>,普通 HTTP 客户端即可打</li>\n <li>• 内置 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">resume_token</code> 断线续播、<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">intermediate</code> 中间结果、<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">backpressure</code> 反压</li>\n <li>• 为不能 / 不想引入 MCP 的客户端环境保留兜底通道</li>\n </ul>\n <p className=\"text-xs mt-2\"><strong>典型客户端</strong>:原生 Android / iOS / Flutter、微信 / 支付宝小程序、老版本 Java / PHP 后端</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">怎么选?</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">你的客户端环境</th>\n <th className=\"text-left py-2 px-3 font-medium\">建议</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3\">Cursor / Claude Desktop / 各类 AI IDE 能装 MCP</td><td className=\"py-2 px-3 text-emerald-600 dark:text-emerald-400\">MCP 模式</td></tr>\n <tr><td className=\"py-2 px-3\">Python / Node / Java 后端能引入 MCP SDK</td><td className=\"py-2 px-3 text-emerald-600 dark:text-emerald-400\">MCP 模式</td></tr>\n <tr><td className=\"py-2 px-3\">Coze / Dify / n8n 等可视化平台</td><td className=\"py-2 px-3 text-emerald-600 dark:text-emerald-400\">MCP 模式</td></tr>\n <tr><td className=\"py-2 px-3\">原生 Android / iOS App,想直接打 HTTP + WS</td><td className=\"py-2 px-3 text-sky-600 dark:text-sky-400\">functioncalling 模式</td></tr>\n <tr><td className=\"py-2 px-3\">微信 / 支付宝 / 抖音小程序</td><td className=\"py-2 px-3 text-sky-600 dark:text-sky-400\">functioncalling 模式</td></tr>\n <tr><td className=\"py-2 px-3\">老系统 / 封闭运行时,不想引入 MCP 依赖</td><td className=\"py-2 px-3 text-sky-600 dark:text-sky-400\">functioncalling 模式</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"warning\">\n 同一个会话<strong>二选一、不要混用</strong>:MCP 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">/ws/audio/</code> 和 functioncalling 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">/ws/eval/</code> 属于不同的会话 ID 空间。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"arch-mcp\" title=\"MCP 模式 · 数据流与组件\">\n <p>MCP 模式下有两种部署形态,底层均走 JSON-RPC 2.0,区别只在客户端离服务端有没有一层本地代理:</p>\n\n <p className=\"font-semibold mt-4\">形态 A · 远程直连(零代码)</p>\n <p className=\"text-sm text-muted-foreground\">适合 Cursor / Coze / Dify / n8n 等支持 Streamable HTTP 的客户端,<strong>无本地依赖</strong>。</p>\n <div className=\"flex flex-wrap items-center justify-center gap-4 my-4\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-5 py-3 text-center min-w-[130px]\">\n <p className=\"text-xs font-semibold mb-0.5\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Cursor / Coze /<br/>Dify / n8n …</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">Streamable HTTP<br/>JSON-RPC 2.0</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-5 py-3 text-center min-w-[130px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-0.5\">驰声 MCP Server</p>\n <p className=\"text-[11px] text-muted-foreground\">/mcp · /ws/audio<br/>评测引擎</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">形态 B · 本地代理(支持麦克风实时录音)</p>\n <p className=\"text-sm text-muted-foreground\">适合 Claude Desktop / Claude Code 等以 stdio 挂载 MCP 的客户端,需要本地实时录音时走这条路径。</p>\n <div className=\"flex flex-wrap items-center justify-center gap-3 my-4\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-4 py-3 text-center min-w-[120px]\">\n <p className=\"text-xs font-semibold mb-0.5\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Claude Desktop /<br/>Claude Code</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">stdio</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-4 py-3 text-center min-w-[120px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-0.5\">chivox-local-mcp</p>\n <p className=\"text-[11px] text-muted-foreground\">本地代理 · SoX 录音</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP / WS</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-4 py-3 text-center min-w-[120px]\">\n <p className=\"text-xs font-semibold mb-0.5\">Remote Chivox</p>\n <p className=\"text-[11px] text-muted-foreground\">MCP Server<br/>评测引擎</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">标准数据流</p>\n <ol className=\"list-decimal list-inside space-y-1.5 text-sm ml-1\">\n <li><code className=\"bg-muted px-1 rounded text-xs\">initialize</code> → 协商协议版本与能力</li>\n <li><code className=\"bg-muted px-1 rounded text-xs\">tools/list</code> → 拉取 16 个评测工具的 JSON Schema</li>\n <li><strong>文件模式</strong>:<code className=\"bg-muted px-1 rounded text-xs\">tools/call</code> → 服务端执行评测 → 返回 JSON-RPC 响应</li>\n <li><strong>实时模式</strong>:<code className=\"bg-muted px-1 rounded text-xs\">create_stream_session</code> → 走 <code className=\"bg-muted px-1 rounded text-xs\">/ws/audio/{'{session_id}'}</code> 推流 → <code className=\"bg-muted px-1 rounded text-xs\">stream_eval_result</code> 收结构化评分</li>\n </ol>\n\n <p className=\"font-semibold mt-6\">原始 JSON-RPC 请求示例(自建客户端可直接抄)</p>\n <p className=\"text-xs text-muted-foreground mb-3\">\n 如果你不使用官方 MCP SDK 而是直接通过 <code className=\"bg-muted px-1 rounded\">POST /mcp</code> 自己拼 JSON-RPC 2.0 报文,最小可用的三条请求如下。所有请求均需携带 <code className=\"bg-muted px-1 rounded text-xs\">Authorization: Bearer &lt;your_api_key&gt;</code>。\n </p>\n\n <p className=\"text-sm font-semibold mt-4\">① 初始化 · <code>initialize</code></p>\n <CodeBlock filename=\"initialize.json\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"initialize\",\n \"params\": {\n \"protocolVersion\": \"2024-11-05\",\n \"capabilities\": {},\n \"clientInfo\": { \"name\": \"my-client\", \"version\": \"1.0.0\" }\n }\n}`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">成功后服务端返回协议版本、服务端能力、服务端信息,此后方可调用其他方法。</p>\n\n <p className=\"text-sm font-semibold mt-4\">② 拉取工具清单 · <code>tools/list</code></p>\n <CodeBlock filename=\"tools-list.json\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"id\": 2,\n \"method\": \"tools/list\",\n \"params\": {}\n}`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">返回全部 16 个评测工具(英 10 + 中 6)+ 2 个流式工具,含名字、描述、参数 JSON Schema。客户端应缓存此响应,后续按需展示 / 桥接给 LLM。</p>\n\n <p className=\"text-sm font-semibold mt-4\">③ 调用工具 · <code>tools/call</code></p>\n <CodeBlock filename=\"tools-call.json\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"id\": 3,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"en_word_eval\",\n \"arguments\": {\n \"ref_text\": \"hello\",\n \"audio_base64\": \"<base64 encoded audio>\",\n \"accent\": 2,\n \"rank\": 100\n }\n }\n}`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">\n 成功响应放在 <code className=\"bg-muted px-1 rounded\">result.content[0].text</code>(JSON 字符串形式,内部就是评测结果结构);失败则返回 <code className=\"bg-muted px-1 rounded\">error.code</code> + <code className=\"bg-muted px-1 rounded\">error.message</code>。\n </p>\n\n <Callout type=\"tip\">\n 用 Python / TypeScript 官方 MCP SDK 的用户<strong>不需要关心以上 JSON-RPC 细节</strong>——SDK 已经封装好 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">session.initialize()</code> / <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">session.list_tools()</code> / <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">session.call_tool()</code>。这里仅供裸协议实现或排障时参考。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"arch-rest\" title=\"functioncalling 模式 · 数据流与组件\">\n <p>没有代理、没有 SDK,客户端 ↔ 服务端只有两条链路:一条 REST 管会话 / 文件评测,一条 WebSocket 推音频帧。</p>\n\n <div className=\"flex flex-wrap items-center justify-center gap-3 my-5\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-4 py-3 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-0.5\">客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">原生 App / 小程序 /<br/>老版后端 / 纯前端</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP REST<br/>/v1/call · /v1/functions</p>\n </div>\n <div className=\"rounded-xl border-2 border-sky-500/50 bg-sky-500/5 px-4 py-3 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold text-sky-600 dark:text-sky-400 mb-0.5\">cvx_fc 服务</p>\n <p className=\"text-[11px] text-muted-foreground\">function-calling 网关<br/>评测引擎</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">WebSocket<br/>/ws/eval/&#123;id&#125;</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-4 py-3 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-0.5\">客户端推流</p>\n <p className=\"text-[11px] text-muted-foreground\">麦克风 / 分片缓冲</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">三条典型数据流</p>\n <div className=\"space-y-3 my-3\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-sm font-semibold mb-1.5\">a. 一次性文件评测</p>\n <p className=\"text-xs text-muted-foreground\">POST <code className=\"bg-muted px-1 rounded\">/v1/call</code>(<code className=\"bg-muted px-1 rounded\">audio_url</code> 或 <code className=\"bg-muted px-1 rounded\">audio_base64</code>) → 同步返回评分</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-sm font-semibold mb-1.5\">b. 流式实时评测</p>\n <p className=\"text-xs text-muted-foreground\">POST <code className=\"bg-muted px-1 rounded\">/v1/call</code> → <code className=\"bg-muted px-1 rounded\">start_stream_eval</code> → 拿 <code className=\"bg-muted px-1 rounded\">ws_url</code> → 连 <code className=\"bg-muted px-1 rounded\">/ws/eval/&#123;id&#125;</code> → 推 PCM → 收 <code className=\"bg-muted px-1 rounded\">intermediate</code> / <code className=\"bg-muted px-1 rounded\">backpressure</code> → 发 <code className=\"bg-muted px-1 rounded\">stop</code> → 收 <code className=\"bg-muted px-1 rounded\">result</code></p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-sm font-semibold mb-1.5\">c. 断线恢复 + 兜底拉取</p>\n <p className=\"text-xs text-muted-foreground\">网络断开 60 秒内 → 用 <code className=\"bg-muted px-1 rounded\">resume_token</code> 重连续播;彻底失败 → POST <code className=\"bg-muted px-1 rounded\">/v1/call</code> → <code className=\"bg-muted px-1 rounded\">get_stream_result</code> 兜底</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">特色能力</p>\n <ul className=\"list-disc list-inside space-y-1 text-sm ml-1\">\n <li><strong>resume_token</strong> — 网络抖动 60 秒内可无缝续播,服务端挂起会话状态</li>\n <li><strong>intermediate 帧</strong> — 边推边吐部分结果,前端可做实时波形 / 分数涂色</li>\n <li><strong>backpressure 反馈</strong> — 服务端主动下发 <code className=\"bg-muted px-1 rounded text-xs\">suggested_interval_ms</code>,客户端按建议降速</li>\n <li><strong>结构化错误码</strong> — <code className=\"bg-muted px-1 rounded text-xs\">SESSION_EXPIRED</code> / <code className=\"bg-muted px-1 rounded text-xs\">INVALID_STATE</code> / … 便于客户端写重试逻辑</li>\n </ul>\n <p className=\"text-xs text-muted-foreground mt-3\">详细的接入示例见下文 <a href=\"#config-rest\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">「接入 · 非 MCP 原生 REST」</a> 大节。</p>\n </SubDoc>\n\n <SubDoc id=\"transport\" title=\"传输协议对比\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">维度</th>\n <th className=\"text-left py-2 px-3 font-medium\">MCP 模式</th>\n <th className=\"text-left py-2 px-3 font-medium\">functioncalling(cvx_fc)</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">协议</td><td className=\"py-2 px-3\">JSON-RPC 2.0</td><td className=\"py-2 px-3\">REST + WebSocket</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">入口路径</td><td className=\"py-2 px-3 font-mono text-xs\">POST /mcp</td><td className=\"py-2 px-3 font-mono text-xs\">POST /v1/call</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">列出工具</td><td className=\"py-2 px-3 font-mono text-xs\">tools/list</td><td className=\"py-2 px-3 font-mono text-xs\">GET /v1/functions</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">调用工具</td><td className=\"py-2 px-3 font-mono text-xs\">tools/call</td><td className=\"py-2 px-3 font-mono text-xs\">POST /v1/call + name</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">流式音频 WS</td><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/audio/{session_id}'}</td><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/eval/{session_id}'}</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">本地 stdio 代理</td><td className=\"py-2 px-3\">支持(<code className=\"bg-muted px-1 rounded text-[10px]\">chivox-local-mcp</code>)</td><td className=\"py-2 px-3 text-muted-foreground\">不适用</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">断线重连</td><td className=\"py-2 px-3\">客户端 SDK 自动重连</td><td className=\"py-2 px-3\">resume_token(60s 挂起)</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">中间结果</td><td className=\"py-2 px-3\">stream_eval 回调</td><td className=\"py-2 px-3\">intermediate 帧</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">背压反馈</td><td className=\"py-2 px-3 text-muted-foreground\">—</td><td className=\"py-2 px-3\">backpressure 帧</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">客户端依赖</td><td className=\"py-2 px-3\">MCP SDK(官方 / 框架)</td><td className=\"py-2 px-3\">仅 HTTP + WS 客户端</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-local-mcp</strong> 是驰声语音评测的 MCP 本地代理,通过 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 将远程驰声评测服务桥接到本地,让 AI 助手(Claude Desktop、Claude Code、Cursor 等)直接调用语音评测能力。</p>\n <p>它支持两种评测模式,覆盖从即时交互到批量处理的全部场景:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Mic className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">实时录音评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">通过麦克风边录边评,音频以 WebSocket 流式推送,无需生成中间文件,延迟更低、体验更佳。</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <FolderOpen className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">音频文件评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">支持本地文件路径、Base64 编码、URL 三种输入,适合已有音频的批量评测场景。</p>\n </div>\n </div>\n <Callout type=\"info\">当前支持 <strong>16 种评测工具</strong>:英文 10 种(单词、句子、段落、纠音、自然拼读、口语选择、半开放题等)+ 中文 6 种(汉字、拼音、词句、段落、有限分支、AI Talk)。</Callout>\n </SubDoc>\n\n <SubDoc id=\"requirements\" title=\"系统要求\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">依赖</th>\n <th className=\"text-left py-2 px-3 font-medium\">版本</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-semibold\">Node.js</td><td className=\"py-2 px-3 font-mono text-xs\">{'>'}= 18</td><td className=\"py-2 px-3 text-muted-foreground\">运行时必须</td></tr>\n <tr><td className=\"py-2 px-3 font-semibold\">SoX</td><td className=\"py-2 px-3 font-mono text-xs\">any</td><td className=\"py-2 px-3 text-muted-foreground\">仅实时录音功能需要(处理麦克风音频流)</td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"mt-3\">安装 SoX(仅在使用实时录音时需要):</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# macOS\nbrew install sox\n\n# Ubuntu / Debian\nsudo apt-get install sox`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"install\" title=\"安装方式\">\n <p><strong>方式一:npm 全局安装</strong>(推荐)</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`npm install -g chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式二:npx 免安装运行</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`MCP_REMOTE_URL=https://fc.cloud.chivox.com npx chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式三:从源码构建</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`git clone https://git.chivox.com/CLOUD_DEV/cvx_local_mcp.git\ncd cvx_local_mcp\nbash scripts/build.sh`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"env-vars\" title=\"环境变量\">\n <ParamTable params={[\n { name: 'MCP_REMOTE_URL', type: 'string', required: true, desc: '远程驰声 MCP 服务地址,如 https://fc.cloud.chivox.com' },\n { name: 'MCP_API_KEY', type: 'string', required: false, desc: 'API 认证密钥(如已配置鉴权则必填)' },\n ]} />\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 直接用(零代码) ══════ */}\n <DocSection id=\"config\" icon={Plug} title=\"接入 · 直接用 MCP(零代码)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>不用写代码、不用装 SDK,只在客户端的<strong className=\"text-foreground\">「MCP 配置」</strong>或<strong className=\"text-foreground\">「扩展」</strong>里填一段 JSON / URL</li>\n <li>保存后 LLM 自动发现 16 个评测工具,并在对话 / Agent 中自主调用</li>\n <li>覆盖 <strong className=\"text-foreground\">IDE 编程助手</strong>、<strong className=\"text-foreground\">桌面 AI 客户端</strong>、<strong className=\"text-foreground\">企业 AI 工作台</strong>、<strong className=\"text-foreground\">可视化 Bot / 工作流平台</strong>四大生态</li>\n </ul>\n </div>\n\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置并保存:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://mcp.cloud.chivox.com\"\n}`}</CodeBlock>\n <Callout type=\"tip\">Cursor 使用远程 Streamable HTTP 模式直连,无需安装本地代理。如需实时录音功能,请使用下方 Claude Desktop 的本地代理配置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-desktop\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件(macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"chivox-local-mcp\",\n \"env\": {\n \"MCP_REMOTE_URL\": \"https://fc.cloud.chivox.com\",\n \"MCP_API_KEY\": \"your-api-key\"\n }\n }\n }\n}`}</CodeBlock>\n <p>如果是从源码构建:</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/cvx_local_mcp/dist/index.js\"],\n \"env\": {\n \"MCP_REMOTE_URL\": \"https://fc.cloud.chivox.com\"\n }\n }\n }\n}`}</CodeBlock>\n <Callout type=\"info\">Claude Desktop 使用本地代理模式(stdio),支持实时录音评测。本地代理会自动桥接远程服务。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-code\" title=\"Claude Code 配置\">\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# npm 全局安装后\nclaude mcp add chivox -- env MCP_REMOTE_URL=https://fc.cloud.chivox.com chivox-local-mcp\n\n# 源码构建后\nclaude mcp add chivox -- env MCP_REMOTE_URL=https://fc.cloud.chivox.com node /path/to/cvx_local_mcp/dist/index.js`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-ai-ide\" title=\"Windsurf / Zed / Continue 等 IDE 编程助手\">\n <p>除了 Cursor 和 Claude Code,主流的 AI 编程 IDE 都原生支持 MCP。以下客户端使用<strong>完全一致</strong>的 Streamable HTTP 配置,区别只在配置入口:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">客户端</th>\n <th className=\"text-left py-2 px-3 font-medium\">配置入口</th>\n <th className=\"text-left py-2 px-3 font-medium\">备注</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">Windsurf (Codeium)</td><td className=\"py-2 px-3 text-xs\">Settings → Cascade → MCP Servers</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">直接贴下方 JSON</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Zed</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">~/.config/zed/settings.json</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">context_servers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">Assistant Panel 自动发现</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Continue (VS Code / JetBrains)</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">config.yaml</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">v0.9+ 支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">通义灵码 / CodeGeeX / 文心快码</td><td className=\"py-2 px-3 text-xs\">插件设置 → MCP 服务</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">2026 Q1 起陆续支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Cline (原 Claude Dev)</td><td className=\"py-2 px-3 text-xs\">MCP Servers 面板 → 编辑配置</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">VS Code 扩展</td></tr>\n </tbody>\n </table>\n </div>\n <CodeBlock filename=\"通用 MCP 配置(复制即用)\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox-speech-eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://mcp.cloud.chivox.com\"\n }\n }\n}`}</CodeBlock>\n <Callout type=\"tip\">所有 IDE 类客户端都走远程 Streamable HTTP,零本地依赖。需要实时录音才需切到 Claude Desktop 的本地代理模式。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-coze\" title=\"扣子(Coze)配置\">\n <p><a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子(Coze)</a>是字节跳动推出的 AI 应用开发平台,支持通过 MCP 扩展为 Agent 赋予外部工具能力。接入驰声语音评测后,你可以构建口语练习 Bot、发音诊断助手等应用。</p>\n <p className=\"font-semibold mt-4\">方式一:扣子空间 MCP 扩展(推荐)</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 <a href=\"https://space.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子空间</a>(space.coze.cn)</li>\n <li>在对话框下方点击 <strong>「添加扩展」</strong></li>\n <li>选择 <strong>「自定义」</strong> 标签,点击添加新的 MCP 服务</li>\n <li>填入以下配置信息:</li>\n </ol>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">名称</td><td className=\"py-2 px-3 font-mono text-xs\">chivox-speech-eval</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://mcp.cloud.chivox.com</td></tr>\n </tbody>\n </table>\n </div>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>保存后,在对话中输入 <strong>「帮我评测这段英文发音」</strong> 即可触发 Agent 调用</li>\n </ol>\n <Callout type=\"tip\">添加 MCP 扩展后,扣子空间的 Agent 会自动识别并调用评测工具。你可以在 Agent 的思考过程中看到具体的工具调用细节。</Callout>\n\n <p className=\"font-semibold mt-6\">方式二:在 Coze 开发平台的 Bot 中使用</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>进入 <a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Coze 开发平台</a>,创建或编辑 Bot</li>\n <li>在 Bot 的 <strong>「插件」</strong> 或 <strong>「工具」</strong> 面板中,添加 MCP 工具</li>\n <li>选择 <strong>Streamable HTTP</strong> 类型,填入 URL:<code className=\"bg-muted px-1 rounded text-xs font-mono\">https://mcp.cloud.chivox.com</code></li>\n <li>Bot 将自动发现所有可用的评测工具(如 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_word</code> 等)</li>\n <li>在 Bot 的人设提示词中,添加语音评测相关的引导语</li>\n </ol>\n <CodeBlock filename=\"Bot 人设提示词示例\">{`你是一位专业的英语口语教练。当用户上传音频并请求评测时:\n1. 使用 evaluate_english_word / evaluate_english_sentence 等工具进行评测\n2. 根据评测结果(overall、accuracy、fluency 等分数)给出专业分析\n3. 针对得分较低的维度提供具体改进建议\n4. 鼓励用户持续练习`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze-workflow\" title=\"Coze 工作流接入\">\n <p>如果你需要在 Coze 工作流中集成语音评测能力,可以通过 <strong>HTTP Request 节点</strong>调用评测 API:</p>\n\n <p className=\"font-semibold mt-4\">步骤 1:上传音频</p>\n <p>在工作流中添加一个 HTTP Request 节点,配置如下:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">方法</td><td className=\"py-2 px-3 font-mono text-xs\">POST</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://your-audio-host.com/upload</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Content-Type</td><td className=\"py-2 px-3 font-mono text-xs\">audio/mp3</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Body</td><td className=\"py-2 px-3 text-muted-foreground\">音频文件二进制数据</td></tr>\n </tbody>\n </table>\n </div>\n <p>响应中获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{{audioId}}'}</code>,传递给下一步。</p>\n\n <p className=\"font-semibold mt-4\">步骤 2:调用评测</p>\n <p>添加第二个 HTTP Request 节点,通过 MCP 接口调用评测工具:</p>\n <CodeBlock filename=\"HTTP Request Body\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"evaluate_english_sentence\",\n \"arguments\": {\n \"audioId\": \"{{audioId}}\",\n \"sentence\": \"{{ref_text}}\"\n }\n },\n \"id\": 1\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">步骤 3:处理结果</p>\n <p>评测结果通过 JSON 格式返回,可使用 Coze 的 <strong>代码节点</strong> 或 <strong>LLM 节点</strong> 对结果进行解析和二次处理(如生成学习建议、计算进步趋势等)。</p>\n\n <Callout type=\"info\">工作流方式适合需要固定流程的场景(如批量评测、定时任务、与其他系统联动)。如果是交互式场景,推荐使用上方的 MCP 扩展方式,Agent 会自动选择合适的工具。</Callout>\n\n <div className=\"rounded-lg bg-muted/30 p-5 mt-4\">\n <p className=\"text-xs font-semibold mb-3\">完整工作流示意</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">用户输入音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 上传音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 调用评测</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM: 分析结果</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">输出反馈</span>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"config-ai-workspace\" title=\"AI 工作台 · 豆包 / 飞书智能伙伴 / WorkBuddy / 钉钉 AI\">\n <p>这一类是<strong>面向「上班族」的企业 / 日常 AI 助手</strong>,产品形态是个聊天框 + 插件市场 / 扩展面板。接入后,员工在 App 里打字「帮我评一下这段录音」就能自动触发驰声评测,不用写任何代码。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Bot className=\"h-4 w-4 text-amber-500\" /> 豆包(Doubao)App / 桌面版</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节跳动官方 AI 助手,2026 年起桌面版支持 MCP 扩展。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>桌面版设置 → <strong>「智能体扩展」</strong> → 添加 MCP 服务</li>\n <li>类型选 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">streamable-http</code>,URL 填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://mcp.cloud.chivox.com</code></li>\n <li>对话中直接说「帮我评测」即可自动调用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Building2 className=\"h-4 w-4 text-blue-500\" /> 飞书智能伙伴(My AI)</p>\n <p className=\"text-xs text-muted-foreground mb-2\">飞书企业 AI,支持以 MCP / Function Calling 挂载外部工具给团队 Bot。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>飞书开发者后台 → 创建应用 → <strong>「AI 能力 / 智能伙伴」</strong></li>\n <li>在「工具」中选 <strong>外部 MCP 服务</strong>,填入上方 URL</li>\n <li>把智能伙伴拉进群聊,@ 它提交音频即可评测</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-rose-500\" /> WorkBuddy / 企业智能助手</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节 WorkBuddy、腾讯会议 AI、企业微信智能助手等企业 AI 工作台场景。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>在工作台的<strong>「技能 / 扩展」</strong>后台新增一个 MCP 连接器</li>\n <li>传输协议选 Streamable HTTP,地址填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://mcp.cloud.chivox.com</code></li>\n <li>把技能发布到指定部门 / 群组,HR、培训、销售口语陪练场景即可直接用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Workflow className=\"h-4 w-4 text-cyan-500\" /> 钉钉 AI 助理 / 通义千问 App</p>\n <p className=\"text-xs text-muted-foreground mb-2\">阿里生态:钉钉 AI 助理支持自定义插件,通义 App 可通过「AI 工具箱」挂载 MCP。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>钉钉开发者后台 → AI 助理 → <strong>「插件开发 / MCP 接入」</strong></li>\n <li>选 HTTP Streamable,填入驰声 MCP URL 并开启鉴权(可选)</li>\n <li>在钉钉群 @AI 助理上传音频,自动返回评测卡片</li>\n </ol>\n </div>\n </div>\n\n <Callout type=\"tip\"><strong>典型场景 · 小龙虾们的共同剧本</strong>:销售口语陪练 → 外语培训 → 主持 / 播音岗位考核 → K12 英语作业批改 → 客服话术复盘。这些「上班族 AI 工作台」接入驰声 MCP 后,员工 / 学员只要对着话筒说话,AI 就能自动打分并给出个性化改进建议,运营 / 培训 / 教研团队零开发即可上线。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-workflow-platforms\" title=\"Dify / n8n / Flowise 等可视化编排\">\n <p>如果你需要把语音评测编排进<strong>多步业务流</strong>(例如:学员录音 → 评测 → 生成错题集 → 推送到 CRM),可视化工作流平台比聊天 Bot 更合适。</p>\n\n <div className=\"grid gap-3 md:grid-cols-3 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Dify</p>\n <p className=\"text-xs text-muted-foreground mb-2\">开源 LLMOps,v0.15+ 原生 MCP 工具。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>「工具」→「自定义 MCP」→ 填 URL</li>\n <li>在 Chatflow / Workflow 节点里直接拖用</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">n8n</p>\n <p className=\"text-xs text-muted-foreground mb-2\">工作流自动化,用 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">MCP Client</code> 节点。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>添加 <strong>MCP Client</strong> 节点</li>\n <li>Transport 选 HTTP Stream,URL 贴驰声地址</li>\n <li>Tool 下拉自动列出 16 个评测能力</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Flowise / LangFlow</p>\n <p className=\"text-xs text-muted-foreground mb-2\">可视化 LangChain Agent 编排。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>拖出 <strong>MCP Tool</strong> 节点</li>\n <li>连到 AgentExecutor 即可</li>\n </ul>\n </div>\n </div>\n\n <Callout type=\"info\">可视化平台的好处是<strong>流程可追溯、可回放</strong>,适合教育机构 / 培训部门做批量评测 + 报表导出;缺点是单步延迟略高于直接 MCP 调用,对延迟敏感的交互场景建议用 Cursor / Claude Desktop 直连。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"其他 MCP 客户端(通用配置)\">\n <p>任何遵循 MCP 协议的客户端都可以接入,统一使用以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://mcp.cloud.chivox.com</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权(可选)</td><td className=\"py-2 px-3 font-mono text-xs\">Header:<code>Authorization: Bearer &lt;MCP_API_KEY&gt;</code></td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">还没找到你用的客户端?任何支持 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 的终端都通用。找不到入口?下一小节「编程用 MCP」里用 SDK 直接接即可。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 编程用 MCP ══════ */}\n <DocSection id=\"config-code\" icon={Code2} title=\"接入 · 编程用 MCP(SDK / 代码集成)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Terminal className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>你在写一段<strong className=\"text-foreground\">真正的后端服务</strong>:一个 Agent Worker、一个 API、一个自动化脚本</li>\n <li>业务里的「语音评测」是其中一个步骤,需要由代码 <strong className=\"text-foreground\">决定何时调用</strong>、<strong className=\"text-foreground\">如何解析结果</strong></li>\n <li>四种主流写法:① 官方 MCP 客户端库 → ② Agent 框架自动桥接 → ③ 直调 chat.completions + tools → ④ 把 MCP Client 嵌进业务后端</li>\n <li>如果客户端环境<strong className=\"text-foreground\">不能引入 MCP SDK</strong>(原生 App / 小程序 / 老系统),请跳转下一大节 <a href=\"#config-rest\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">「接入 · 非 MCP 原生 REST(cvx_fc)」</a></li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 强烈推荐①或②:驰声有 16 个评测工具,<strong className=\"text-foreground\">让 MCP 自动注入 tool schema</strong>,每新增一个题型不用改一行业务代码。</p>\n </div>\n\n <SubDoc id=\"code-mcp-sdk\" title=\"① 官方 MCP 客户端库(Python / TypeScript)\">\n <p>最底层、也最推荐的方式。由 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Anthropic 官方维护</a>,一次连接自动拿到全部工具的 JSON Schema,然后交给任意 LLM。</p>\n\n <p className=\"font-semibold mt-4\">Python:<code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp</code></p>\n <CodeBlock filename=\"chivox_client.py\" lang=\"python\">{`# pip install mcp openai\nimport asyncio, os\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nMCP_URL = \"https://mcp.cloud.chivox.com\"\nllm = OpenAI(api_key=os.getenv(\"DOUBAO_KEY\"),\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\") # 豆包 / DeepSeek / OpenAI 同构\n\nasync def main(audio_id: str, ref: str):\n async with streamablehttp_client(MCP_URL) as (reader, writer, _):\n async with ClientSession(reader, writer) as sess:\n await sess.initialize()\n\n tools = await sess.list_tools()\n openai_tools = [{\n \"type\": \"function\",\n \"function\": {\"name\": t.name, \"description\": t.description,\n \"parameters\": t.inputSchema},\n } for t in tools.tools]\n\n resp = llm.chat.completions.create(\n model=\"doubao-1-5-pro-32k\",\n messages=[{\"role\": \"user\",\n \"content\": f\"帮我评测 audioId={audio_id},参考文本:{ref}\"}],\n tools=openai_tools,\n )\n\n for call in resp.choices[0].message.tool_calls or []:\n result = await sess.call_tool(call.function.name,\n arguments=eval(call.function.arguments))\n print(call.function.name, \"→\", result.content[0].text[:200])\n\nasyncio.run(main(\"abc123\", \"I think therefore I am\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">TypeScript / Node.js:<code className=\"bg-muted px-1 rounded text-xs font-mono\">@modelcontextprotocol/sdk</code></p>\n <CodeBlock filename=\"chivox-client.ts\" lang=\"typescript\">{`// npm i @modelcontextprotocol/sdk openai\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport OpenAI from \"openai\";\n\nconst mcp = new Client({ name: \"chivox-biz\", version: \"1.0.0\" });\nawait mcp.connect(new StreamableHTTPClientTransport(new URL(\"https://mcp.cloud.chivox.com\")));\n\nconst { tools } = await mcp.listTools();\nconst openai = new OpenAI({ apiKey: process.env.DEEPSEEK_KEY, baseURL: \"https://api.deepseek.com/v1\" });\n\nconst resp = await openai.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: \"评测 audioId=abc123, 参考 I think therefore I am\" }],\n tools: tools.map(t => ({ type: \"function\", function: {\n name: t.name, description: t.description, parameters: t.inputSchema\n }})),\n});\n\nfor (const call of resp.choices[0].message.tool_calls ?? []) {\n const r = await mcp.callTool({ name: call.function.name,\n arguments: JSON.parse(call.function.arguments) });\n console.log(call.function.name, r.content);\n}`}</CodeBlock>\n <Callout type=\"tip\">这种写法 <strong>LLM 供应商可换</strong>(豆包 / DeepSeek / GPT / Qwen / Moonshot),<strong>工具自动更新</strong>(驰声新增题型无需改业务代码),适合生产后端。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-agent-frameworks\" title=\"② 用 Agent 框架自动桥接(LangChain / Mastra / Agents SDK)\">\n <p>如果项目已经在用 Agent 框架,几行 adapter 就能把驰声 MCP 塞进去,框架本身会接管 tool-calling 循环、重试、日志。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LangChain / LangGraph(Python)</p>\n <CodeBlock filename=\"agent.py\" lang=\"python\">{`# pip install langchain-mcp-adapters langgraph\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom langgraph.prebuilt import create_react_agent\n\nclient = MultiServerMCPClient({\n \"chivox\": {\n \"transport\": \"streamable_http\",\n \"url\": \"https://mcp.cloud.chivox.com\",\n }\n})\ntools = await client.get_tools()\n\nagent = create_react_agent(\"openai:gpt-4o-mini\", tools)\nresult = await agent.ainvoke({\"messages\":\n [(\"user\", \"评测 audioId=abc123 参考 think\")]})\nprint(result[\"messages\"][-1].content)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Mastra(TypeScript)</p>\n <CodeBlock filename=\"mastra-agent.ts\" lang=\"typescript\">{`// npm i @mastra/core @mastra/mcp\nimport { MCPClient } from \"@mastra/mcp\";\nimport { Agent } from \"@mastra/core/agent\";\nimport { openai } from \"@ai-sdk/openai\";\n\nconst mcp = new MCPClient({ servers: {\n chivox: { url: new URL(\"https://mcp.cloud.chivox.com\") }\n}});\n\nexport const speechCoach = new Agent({\n name: \"speech-coach\",\n instructions: \"你是口语教练,用 chivox 工具评测并给改进建议。\",\n model: openai(\"gpt-4o-mini\"),\n tools: await mcp.getTools(),\n});`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">OpenAI Agents SDK(Python)</p>\n <CodeBlock filename=\"agents_sdk.py\" lang=\"python\">{`# pip install openai-agents\nfrom agents import Agent, Runner\nfrom agents.mcp import MCPServerStreamableHttp\n\nchivox = MCPServerStreamableHttp(\n params={\"url\": \"https://mcp.cloud.chivox.com\"},\n name=\"chivox-speech-eval\",\n)\n\nasync with chivox:\n agent = Agent(name=\"coach\",\n instructions=\"专业口语教练\",\n mcp_servers=[chivox])\n r = await Runner.run(agent, \"评测 audioId=abc123\")\n print(r.final_output)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LlamaIndex / AutoGen / CrewAI</p>\n <p className=\"text-xs text-muted-foreground mb-2\">这些框架都有官方或社区 MCP adapter,用法与上述类似:</p>\n <ul className=\"list-disc list-inside space-y-1 text-xs\">\n <li><strong>LlamaIndex</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">llama-index-tools-mcp</code></li>\n <li><strong>AutoGen</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">autogen-ext[mcp]</code></li>\n <li><strong>CrewAI</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">crewai-tools</code> 的 MCPServerAdapter</li>\n <li><strong>Spring AI</strong>(Java):<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">spring-ai-mcp-client</code></li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"code-function-calling\" title=\"③ 直调 chat.completions · 上传与动态 tools(豆包 / DeepSeek / 等)\">\n <p>\n DeepSeek、GLM(智谱)、KIMI(Moonshot)、豆包(火山方舟)、千帆、DashScope 等若提供 <strong>OpenAI 兼容的 HTTP API</strong>,接入驰声 MCP 的套路一致:用 MCP 客户端拉取工具列表 → 转为{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> → 模型决策 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_calls</code> → MCP 执行并回填 → 再让模型生成诊断话术。\n </p>\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">① MCP list_tools()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">② 转 OpenAI tools 格式</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">③ LLM 决策 tool_calls</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">④ MCP call_tool()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">⑤ 评测结果回填 → LLM 诊断</span>\n </div>\n </div>\n <Callout type=\"tip\">各厂商仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 与 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 与 tools 桥接代码可原样复用。</Callout>\n\n <p className=\"font-semibold mt-6\">先上传音频,拿到 audioId</p>\n <p className=\"text-sm text-muted-foreground mb-3\">文件评测工具通常需要 <code className=\"bg-muted px-1 rounded text-xs font-mono\">audioId</code>(或路径参数,视工具而定)。下面是用 HTTP 上传的最小示例:</p>\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://your-audio-host.com/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst res = await fetch('https://your-audio-host.com/upload', {\n method: 'POST',\n headers: { 'Content-Type': 'audio/mp3' },\n body: audioData,\n});\n\nconst result = await res.json();\nconsole.log('audioId:', result.audioId);`}</CodeBlock>\n\n <p className=\"font-semibold mt-6\">再以豆包为例:MCP 动态 tools + chat.completions 折返跑</p>\n <p>当你用的 LLM SDK <strong>还没有原生 MCP 支持</strong>(比如老版本的火山方舟 / 百度千帆 / 通义 DashScope),退化方案是:用 MCP 客户端<strong>一次性拉到 tool schema</strong>,然后按 OpenAI Function Calling 格式喂给 LLM。</p>\n <CodeBlock filename=\"doubao_tools.py\" lang=\"python\">{`# 适用:豆包(火山方舟)/ DeepSeek / Qwen / Moonshot / GLM 等 OpenAI 兼容 API\n# 核心思路:MCP 列工具 → 转 OpenAI tools → LLM 决策 → MCP 执行 → 回填\n\nasync with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n\n # ① 用 MCP 拉到的 schema 自动转 OpenAI 格式\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 喂给豆包\n messages = [{\"role\": \"user\", \"content\": \"评测一下 audioId=abc123\"}]\n resp = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages, tools=oa_tools,\n )\n\n # ③ LLM 回 tool_calls → 折返跑到 MCP 执行\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for c in msg.tool_calls:\n result = await mcp.call_tool(\n c.function.name, arguments=json.loads(c.function.arguments))\n messages.append({\"role\": \"tool\", \"tool_call_id\": c.id,\n \"content\": result.content[0].text})\n # ④ 再喂一轮,让豆包根据评测结果产出自然语言诊断\n final = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages)\n print(final.choices[0].message.content)`}</CodeBlock>\n <Callout type=\"warning\"><strong>千万别手写 tool schema!</strong>驰声共 <strong>16 个评测工具</strong>(英文 10 + 中文 6),每个工具参数较多,手写既容易错也难维护。一定用 MCP <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">list_tools()</code> 动态拿。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-selfhosted-agent\" title=\"④ 自研后端 Agent(FastAPI / NestJS / Spring)\">\n <p>大部分落地场景其实是:<strong>你已有一个业务后端</strong>(教育 SaaS / 培训系统 / 客服平台),只需要新增一个「语音评测」能力。推荐架构:</p>\n\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">前端录音 / 上传</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">业务后端</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">MCP Client(常驻连接)</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">驰声 MCP</span>\n <span className=\"text-muted-foreground\">↘</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM(诊断 / 出题)</span>\n </div>\n </div>\n\n <p className=\"font-semibold mt-2\">FastAPI 示例(生产最小骨架)</p>\n <CodeBlock filename=\"app.py\" lang=\"python\">{`from fastapi import FastAPI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\nfrom contextlib import asynccontextmanager\n\nmcp_session: ClientSession | None = None\n\n@asynccontextmanager\nasync def lifespan(app):\n global mcp_session\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as s:\n await s.initialize()\n mcp_session = s\n yield\n mcp_session = None\n\napp = FastAPI(lifespan=lifespan)\n\n@app.post(\"/api/evaluate\")\nasync def evaluate(audio_id: str, ref_text: str):\n # 直接选一个最合适的工具执行(不经 LLM,延迟最低)\n r = await mcp_session.call_tool(\"evaluate_english_sentence\",\n {\"audioId\": audio_id, \"sentence\": ref_text})\n return {\"result\": r.content[0].text}`}</CodeBlock>\n\n <Callout type=\"tip\"><strong>关键优化</strong>:① MCP 连接在 lifespan 里做成长连接,不要每次请求都重连;② 对延迟敏感的场景可以「跳过 LLM 直接调 tool」,把 LLM 放到「生成诊断报告」那一步。</Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-4 my-5 text-xs\">\n <p className=\"font-semibold text-sm mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-muted-foreground\" /> 下面是 ① ~ ④ 中<strong className=\"text-foreground\">第③种「直调 chat.completions」</strong>按主流 LLM 厂商的细化写法</p>\n <p className=\"text-muted-foreground\">各家 SDK / base_url / 鉴权写法略有差别,挑你当前用的那一家看即可;驰声 MCP 侧代码不用改。</p>\n </div>\n\n <SubDoc id=\"llm-deepseek\" title=\"DeepSeek\">\n <p>\n DeepSeek 提供与 OpenAI 完全兼容的 API,当前最新模型为 <strong>DeepSeek-V3.2</strong>(2025 年 12 月发布,首个把\"思考 + 工具调用\"原生融合的模型)。\n 通过 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-chat</code> 进入<strong>非思考模式</strong>,\n 通过 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-reasoner</code> 进入<strong>思考模式</strong>,\n <strong>两种模式均支持 Function Calling</strong>,128K 上下文。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"deepseek_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# DeepSeek 客户端(OpenAI 兼容)\nclient = OpenAI(\n api_key=os.environ[\"DEEPSEEK_API_KEY\"],\n base_url=\"https://api.deepseek.com/v1\",\n)\n\nasync def evaluate_with_deepseek(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 拉取驰声 MCP 工具列表\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 调用 DeepSeek,让它选择合适的评测工具\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"deepseek-chat\", # 或 deepseek-reasoner\n messages=messages,\n tools=oa_tools,\n )\n\n # ③ 执行 tool_calls\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 让 DeepSeek 根据评测结果生成自然语言诊断\n final = client.chat.completions.create(\n model=\"deepseek-chat\",\n messages=messages,\n )\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_deepseek(\"audio_abc123\", \"Hello, nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议(V3.2):</strong>日常评测诊断用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">deepseek-chat</code>,速度快、token 消耗低;\n 需要深度分析(错误归因 / 个性化练习路径)时用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">deepseek-reasoner</code>,\n V3.2 起<strong>思考模式也能在思维链中调用工具</strong>,可以一次性完成\"调评测 → 思考 → 出诊断\",无需再切回 V3 做工具调用。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-glm\" title=\"GLM(智谱 AI / Z.ai)\">\n <p>\n 智谱 AI 当前最新旗舰为 <strong>GLM-5 / GLM-5.1</strong>(2026 年 2 月发布,744B-A40B MoE,200K 上下文,对标 Claude Opus 4.5),\n 以及上一代 <strong>GLM-4.7</strong>、<strong>GLM-4.6</strong>。\n 此外保留 <code className=\"bg-muted px-1 rounded text-xs font-mono\">glm-4-flash</code>(免费快速版)方便开发测试。\n 全部模型支持 OpenAI 兼容接口与原生 <code className=\"bg-muted px-1 rounded text-xs font-mono\">zhipuai</code> SDK,原生 Function Calling。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`ZHIPU_API_KEY=xxxxxxxx.xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式一:OpenAI 兼容接口(推荐)</p>\n <CodeBlock filename=\"glm_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(\n api_key=os.environ[\"ZHIPU_API_KEY\"],\n base_url=\"https://open.bigmodel.cn/api/paas/v4/\",\n)\n\nasync def evaluate_with_glm(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"glm-4-flash\", # 免费快速版;生产推荐 glm-5 / glm-4.7\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"glm-4-flash\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_glm(\"audio_abc123\", \"How are you today?\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式二:官方 zhipuai SDK</p>\n <CodeBlock filename=\"glm_sdk.py\" lang=\"python\">{`from zhipuai import ZhipuAI\n\nclient = ZhipuAI(api_key=os.environ[\"ZHIPU_API_KEY\"])\n\n# 其余逻辑与方式一相同,仅替换 client 实例\nresp = client.chat.completions.create(\n model=\"glm-5\", # 旗舰;也可用 glm-4.7 / glm-4-flash\n messages=messages,\n tools=oa_tools,\n)`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 开发联调用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-4-flash</code>(免费);\n 在线辅导日常诊断用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-4.7</code> 性价比最高;\n K12 / 复杂 Agent 编排(多步评测 + 个性化练习路径)用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-5</code> / <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-5.1</code>,\n 200K 上下文、Agent 能力对标 Claude Opus 系列。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-kimi\" title=\"KIMI(Moonshot AI)\">\n <p>\n Moonshot AI 当前最新旗舰是 <strong>Kimi K2.5</strong>(2026 年 1 月 27 日发布,MoE 架构、<strong>256K 上下文</strong>、原生多模态、Agent 集群协作)。\n 另外提供 <code className=\"bg-muted px-1 rounded text-xs font-mono\">kimi-k2-thinking</code>(深度思考)、<code className=\"bg-muted px-1 rounded text-xs font-mono\">kimi-k2-turbo-preview</code>(高速版)。\n 上一代 <code className=\"bg-muted px-1 rounded text-xs font-mono\">moonshot-v1-8k/32k/128k</code> 仍然兼容,但<strong>推荐迁移到 kimi-k2.5</strong>。\n 全部模型 OpenAI 兼容,原生 Function Calling。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`MOONSHOT_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"kimi_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# 中国大陆用 api.moonshot.cn;海外/国际版用 api.moonshot.ai\nclient = OpenAI(\n api_key=os.environ[\"MOONSHOT_API_KEY\"],\n base_url=\"https://api.moonshot.cn/v1\",\n)\n\nasync def evaluate_with_kimi(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"kimi-k2.5\", # 旗舰;也可选 kimi-k2-turbo-preview / moonshot-v1-128k\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"kimi-k2.5\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_kimi(\"audio_abc123\", \"Nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\">\n Kimi K2.5 原生 <strong>256K 上下文 + Agent 集群协作</strong>,特别适合「多轮口语练习 + 历史分析 + 多步评测编排」场景:\n 把用户过去 30 次评测的完整 JSON 全部塞进 messages,让 K2.5 一次性生成跨多次练习的进步报告与个性化练习路径。\n 需要更深推理时使用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">kimi-k2-thinking</code>,\n 追求实时反馈用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">kimi-k2-turbo-preview</code>。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-doubao\" title=\"豆包 Seed 2.0(火山方舟)\">\n <p>\n 字节跳动 <strong>豆包 Seed 2.0</strong>(2026 年 2 月发布)是 ByteDance Seed 团队推出的新一代通用大模型,覆盖 Pro / Lite / Mini / Code 多种规格,\n 通过 <strong>火山方舟(Volcano Ark)</strong> 提供 OpenAI 兼容的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">/api/v3/chat/completions</code> 接口,\n 原生支持 <strong>Function Calling、JSON Schema 结构化输出、深度思考(Chain-of-Thought)模式</strong>,\n 接驰声 MCP 几乎无须改动业务代码。\n </p>\n\n <Callout type=\"tip\">\n 豆包 Seed 2.0 的优势在 <strong>深度思考 + 工具编排</strong>:让模型一次性规划\"先调 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code>,看完弱项再调 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.word.score</code> 进一步定位音素\",\n 适合做\"多步评测 + 自动纠音\"这类 Agent 场景。\n 默认开启思考模式,可通过 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'thinking: { type: \"disabled\" }'}</code> 关闭以节省时延。\n </Callout>\n\n <p className=\"font-semibold mt-4 mb-2\">推荐版本</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">model</th>\n <th className=\"text-left py-2 px-3 font-medium\">规格</th>\n <th className=\"text-left py-2 px-3 font-medium\">推荐用途</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-pro-260215</td><td className=\"py-2 px-3\">Pro · 旗舰</td><td className=\"py-2 px-3\">多步评测 + 深度诊断 + 复杂 Agent 编排</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-lite-260215</td><td className=\"py-2 px-3\">Lite · 平衡</td><td className=\"py-2 px-3\">在线辅导单轮评测 + 自然语言反馈,性价比首选</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-mini-260215</td><td className=\"py-2 px-3\">Mini · 极小</td><td className=\"py-2 px-3\">高并发 / 边缘部署 / 移动端实时反馈</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-code-preview-260215</td><td className=\"py-2 px-3\">Code · 预览</td><td className=\"py-2 px-3\">代码生成 / 工具编排 DSL,对评测场景非主推</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-1-8-251228</td><td className=\"py-2 px-3\">1.8 · 深度思考</td><td className=\"py-2 px-3\">支持 reasoning_effort 调档(minimal/low/medium/high),上一代深度思考强化版仍可用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-1-6-251015</td><td className=\"py-2 px-3\">1.6 · 经典</td><td className=\"py-2 px-3\">256K 上下文经典版,已上线项目可继续沿用</td></tr>\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`# 火山方舟控制台 → API Key 管理\nARK_API_KEY=ak-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · 动态 tools + 折返跑)</p>\n <CodeBlock filename=\"doubao_seed20_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# 火山方舟 OpenAI 兼容端点\nclient = OpenAI(\n api_key=os.environ[\"ARK_API_KEY\"],\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n)\n\nMODEL = \"doubao-seed-2-0-pro-260215\" # 也可换成 lite / mini\n\nasync def evaluate_with_doubao(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 把驰声 16 个评测工具的 schema 动态注入豆包\n tools = (await mcp.list_tools()).tools\n ark_tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n },\n } for t in tools]\n\n messages = [\n {\"role\": \"system\",\n \"content\": \"你是一位专业的英语口语教练,会主动调用驰声评测工具获取真实分数。\"},\n {\"role\": \"user\",\n \"content\": f\"请评测这段录音,audio_id={audio_id},参考文本:{ref_text}。\"\n f\"先做句子级评测,弱项再追加单词级定位。\"},\n ]\n\n # ② 第一轮 —— 让豆包根据 prompt 自主选工具\n resp = client.chat.completions.create(\n model=MODEL,\n messages=messages,\n tools=ark_tools,\n tool_choice=\"auto\",\n # extra_body={\"thinking\": {\"type\": \"disabled\"}}, # 需要时可关思考\n )\n\n msg = resp.choices[0].message\n messages.append(msg)\n\n # ③ 折返跑:豆包可能一次发起多个 tool_calls,全部回填\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 第二轮 —— 让豆包基于真实评测 JSON 输出诊断 / 练习建议\n final = client.chat.completions.create(\n model=MODEL,\n messages=messages,\n )\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_doubao(\n audio_id=\"audio_abc123\",\n ref_text=\"The quick brown fox jumps over the lazy dog.\",\n)))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">深度思考模式(可选)</p>\n <p className=\"text-sm text-muted-foreground\">\n 豆包 Seed 2.0 默认开启 <strong>thinking</strong> 模式,会在内部生成思维链再产出答案,\n 对\"多步评测编排 / 复杂诊断\"质量明显更好;但会增加 30%~80% 的端到端延迟。\n 在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">extra_body</code> 里可显式控制:\n </p>\n <CodeBlock filename=\"thinking_mode.py\" lang=\"python\">{`# 关闭思考(更快、适合实时反馈)\nclient.chat.completions.create(\n model=\"doubao-seed-2-0-lite-260215\",\n messages=messages,\n tools=ark_tools,\n extra_body={\"thinking\": {\"type\": \"disabled\"}},\n)\n\n# 开启思考(默认,适合 Pro 模型 + 多步 Agent)\nclient.chat.completions.create(\n model=\"doubao-seed-2-0-pro-260215\",\n messages=messages,\n tools=ark_tools,\n extra_body={\"thinking\": {\"type\": \"enabled\"}},\n)`}</CodeBlock>\n\n <Callout type=\"info\">\n <strong>选型建议:</strong>\n 课堂 / 培训类一对一辅导用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-lite</code> + 关思考;\n K12 / 考试场景需要\"多题型联动 + 升级建议\"时用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-pro</code> + 开思考;\n 移动端实时口语对话用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-mini</code> 控制成本与时延。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-openai\" title=\"OpenAI(GPT-5.1)\">\n <p>\n OpenAI 当前旗舰为 <strong>GPT-5.1</strong>(2025-11-13 发布),<strong>400K 上下文</strong>、128K 最大输出,\n 支持可调推理强度 <code className=\"bg-muted px-1 rounded text-xs font-mono\">reasoning_effort</code>(none / low / medium / high)。\n 另有 <code className=\"bg-muted px-1 rounded text-xs font-mono\">gpt-5.1-chat-latest</code>(ChatGPT 同款,128K 上下文,对话场景更优)和 <code className=\"bg-muted px-1 rounded text-xs font-mono\">gpt-5-mini</code>(性价比版)。\n 全部模型原生支持 Function Calling 与 Structured Outputs。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · GPT-5.1 + 驰声 MCP)</p>\n <CodeBlock filename=\"openai_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(api_key=os.environ[\"OPENAI_API_KEY\"])\n\nasync def evaluate_with_gpt(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [\n {\"role\": \"system\",\n \"content\": \"你是英语口语教练,必须调用驰声评测工具拿真实分数后再回答。\"},\n {\"role\": \"user\",\n \"content\": f\"请评测:audioId={audio_id},参考文本:{ref_text}\"},\n ]\n\n resp = client.chat.completions.create(\n model=\"gpt-5.1\",\n messages=messages,\n tools=oa_tools,\n tool_choice=\"auto\",\n reasoning_effort=\"medium\", # GPT-5.1 新参数:none / low / medium / high\n )\n\n msg = resp.choices[0].message\n messages.append(msg)\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n final = client.chat.completions.create(\n model=\"gpt-5.1\", messages=messages)\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_gpt(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 评测调用 + 诊断默认用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5.1</code>(reasoning_effort=&quot;medium&quot;,质量与延迟平衡);\n 纯对话/课堂讲解用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5.1-chat-latest</code>;\n 高并发批量评测降本用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5-mini</code>。\n 深度推理 / 多步 Agent 编排把 reasoning_effort 调到 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">high</code>。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-claude\" title=\"Claude(Anthropic)\">\n <p>\n Anthropic 当前最新为 <strong>Claude Opus 4.7</strong>(旗舰)、<strong>Claude Sonnet 4.6</strong>(平衡)、<strong>Claude Haiku 4.5</strong>(高速)。\n Claude 用 <strong>Messages API</strong>(与 OpenAI Chat Completions 不同),工具协议是 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_use</code> / <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_result</code> 块结构。\n 支持文本 + 视觉输入,工具 schema 用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">input_schema</code> 字段(与 OpenAI 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">parameters</code> 等价)。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · 原生 Anthropic SDK + MCP 工具桥接)</p>\n <CodeBlock filename=\"claude_chivox.py\" lang=\"python\">{`# pip install anthropic mcp\nimport os, json, asyncio\nfrom anthropic import Anthropic\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = Anthropic(api_key=os.environ[\"ANTHROPIC_API_KEY\"])\n\nasync def evaluate_with_claude(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # 把驰声 MCP 工具转成 Anthropic 风格(用 input_schema 而非 parameters)\n tools = (await mcp.list_tools()).tools\n anth_tools = [{\n \"name\": t.name,\n \"description\": t.description,\n \"input_schema\": t.inputSchema,\n } for t in tools]\n\n messages = [{\"role\": \"user\",\n \"content\": f\"请评测这段录音 audioId={audio_id},参考文本:{ref_text}\"}]\n\n # 工具循环:Claude 可能多轮 tool_use,需逐轮回填\n while True:\n resp = client.messages.create(\n model=\"claude-opus-4-7\", # 或 claude-sonnet-4-6 / claude-haiku-4-5\n max_tokens=2048,\n tools=anth_tools,\n messages=messages,\n system=\"你是专业英语口语教练,必须调用驰声评测工具获取真实分数。\",\n )\n\n # 把整段助手回复(含 tool_use 块)追加进 messages\n messages.append({\"role\": \"assistant\", \"content\": resp.content})\n\n if resp.stop_reason != \"tool_use\":\n # 没有更多工具调用,输出最终诊断\n return \"\".join(b.text for b in resp.content if b.type == \"text\")\n\n # 提取所有 tool_use 块、并行执行、把 tool_result 回填给 Claude\n tool_results = []\n for block in resp.content:\n if block.type == \"tool_use\":\n result = await mcp.call_tool(block.name, arguments=block.input)\n tool_results.append({\n \"type\": \"tool_result\",\n \"tool_use_id\": block.id,\n \"content\": result.content[0].text,\n })\n messages.append({\"role\": \"user\", \"content\": tool_results})\n\nprint(asyncio.run(evaluate_with_claude(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 深度评测诊断 + 复杂 Agent 编排用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-opus-4-7</code>(旗舰,编程/Agent 提升明显);\n 日常评测 + 性价比首选 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-sonnet-4-6</code>;\n 高并发实时反馈用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-haiku-4-5</code>。\n Opus 4.5 起新增 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">effort</code> 参数(low/medium/high),medium 即可达到 Sonnet 4.5 同等质量但 token 减少 ~76%。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-gemini\" title=\"Gemini(Google)\">\n <p>\n Google 最新 <strong>Gemini 3 Flash</strong>(<code className=\"bg-muted px-1 rounded text-xs font-mono\">gemini-3-flash-preview</code>,2025-12-17)以 Flash 价位提供 Pro 级推理;\n 另有 <strong>Gemini 3.1 Pro Preview</strong>(旗舰)、<strong>Gemini 3.1 Flash-Lite Preview</strong>。\n 全部支持 Function Calling、并行调用、组合调用,原生多模态,<code className=\"bg-muted px-1 rounded text-xs font-mono\">thinking_level</code> 控制推理深度。\n 推荐用 <strong>OpenAI 兼容端点</strong>,可直接复用 OpenAI SDK 与已有的 MCP 工具桥接代码。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`GEMINI_API_KEY=AIzaSyxxxxxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式一:OpenAI 兼容端点(推荐,零迁移成本)</p>\n <CodeBlock filename=\"gemini_openai_compat.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# Gemini OpenAI 兼容端点 —— 完全复用 OpenAI SDK\nclient = OpenAI(\n api_key=os.environ[\"GEMINI_API_KEY\"],\n base_url=\"https://generativelanguage.googleapis.com/v1beta/openai/\",\n)\n\nasync def evaluate_with_gemini(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\",\n \"content\": f\"请评测:audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"gemini-3-flash-preview\", # 或 gemini-3.1-pro-preview\n messages=messages,\n tools=oa_tools,\n tool_choice=\"auto\",\n )\n msg = resp.choices[0].message\n messages.append(msg)\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n final = client.chat.completions.create(\n model=\"gemini-3-flash-preview\", messages=messages)\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_gemini(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式二:原生 google-genai SDK(thinking_level / 流式工具调用)</p>\n <CodeBlock filename=\"gemini_native.py\" lang=\"python\">{`# pip install google-genai\nfrom google import genai\nfrom google.genai import types\n\nclient = genai.Client(api_key=os.environ[\"GEMINI_API_KEY\"])\n\n# 把 MCP 工具转成 Gemini function_declarations\nfn_decls = [{\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n} for t in tools]\n\nresp = client.models.generate_content(\n model=\"gemini-3-flash-preview\",\n contents=f\"评测 audioId={audio_id},参考文本:{ref_text}\",\n config=types.GenerateContentConfig(\n tools=[types.Tool(function_declarations=fn_decls)],\n # Gemini 3 起:thinking_level 替代旧的 thinking_budget\n # thinking_level=\"high\",\n ),\n)\n\n# tool_call 在 response.candidates[0].content.parts[0].function_call\nfc = resp.candidates[0].content.parts[0].function_call\nprint(f\"call: {fc.name}({dict(fc.args)}) id={fc.id}\")`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 日常评测用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3-flash-preview</code>(速度比 2.5 Pro 快 3 倍、价格 ~$0.50/M 输入),已是 Gemini App 默认;\n 复杂多步 Agent / 编程任务用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3.1-pro-preview</code>;\n 超大上下文 + 多模态批处理用同系列的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3.1-flash-lite-preview</code>。\n </Callout>\n <Callout type=\"info\">\n <strong>注意:</strong>Gemini 3 起每次 function_call 自动带 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">id</code>,多轮工具调用必须把 id 透传回 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">function_response</code>,否则会被拒绝(OpenAI 兼容端点已自动处理)。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-comparison\" title=\"模型对比速查\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead>\n <tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 pr-4 font-medium\">模型</th>\n <th className=\"text-left py-2 pr-4 font-medium\">推荐版本</th>\n <th className=\"text-left py-2 pr-4 font-medium\">base_url</th>\n <th className=\"text-left py-2 pr-4 font-medium\">Function Calling</th>\n <th className=\"text-left py-2 font-medium\">适合场景</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek V3.2 · 非思考</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-chat</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">评测调用 + 诊断,性价比最高</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek V3.2 · 思考</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-reasoner</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(思考中可调工具)</td>\n <td className=\"py-2\">深度分析 + 错因归因 + 个性化练习路径</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-5 · 旗舰</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-5</code> / <code className=\"bg-muted px-1 rounded font-mono\">glm-5.1</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">200K 长上下文 + Agent 编排,复杂诊断</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4.7</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4.7</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">在线辅导日常诊断,性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4-Flash</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4-flash</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">免费额度,开发联调</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2.5(旗舰)</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2.5</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">256K + 多模态 + Agent 集群协作</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2 Thinking</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2-thinking</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">多步推理 + 多轮历史进度报告</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2 Turbo</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2-turbo-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高速响应版,实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Pro</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-pro-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(含深度思考)</td>\n <td className=\"py-2\">多步评测编排 + 深度诊断 Agent</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Lite</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-lite-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">在线辅导 + 单轮诊断,性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Mini</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-mini-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">移动端 / 边缘部署 / 低延迟实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5.1(旗舰)</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5.1</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(reasoning_effort 可调)</td>\n <td className=\"py-2\">400K 上下文 + 深度推理 + 复杂 Agent 编排</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5.1 Chat</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5.1-chat-latest</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">ChatGPT 同款,对话 / 课堂讲解</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5 mini</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5-mini</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高并发 / 批量评测降本</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Opus 4.7</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-opus-4-7</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(tool_use 协议)</td>\n <td className=\"py-2\">旗舰,编程 / Agent / 深度诊断</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Sonnet 4.6</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-sonnet-4-6</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">日常评测 + 性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Haiku 4.5</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-haiku-4-5</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高并发实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Gemini 3 Flash</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gemini-3-flash-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">…/v1beta/openai/</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(并行 / 组合)</td>\n <td className=\"py-2\">Pro 级推理 + Flash 价位,Gemini App 默认</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Gemini 3.1 Pro Preview</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gemini-3.1-pro-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">…/v1beta/openai/</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(thinking_level 可调)</td>\n <td className=\"py-2\">复杂多步 Agent + 编程任务</td>\n </tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"info\">\n 除 <strong>Claude</strong> 用 Anthropic 原生 SDK(<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">anthropic</code> 包,<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">tool_use</code> / <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">tool_result</code> 协议)外,其余所有模型均可通过 <strong>OpenAI Python SDK</strong>(<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">openai</code> 包)调用,仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 与 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 侧代码<strong>无需改动</strong>。\n </Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mt-6 text-sm\">\n <p className=\"font-semibold mb-2\">方案选型速查</p>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 font-medium\">场景</th>\n <th className=\"text-left py-2 font-medium\">推荐方案</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2\">AI 对话 / Copilot 类产品,LLM 供应商可能会换</td><td className=\"py-2\">① 官方 MCP 客户端库</td></tr>\n <tr><td className=\"py-2\">已有 LangChain / Mastra / Agents SDK 技术栈</td><td className=\"py-2\">② Agent 框架 + adapter</td></tr>\n <tr><td className=\"py-2\">火山方舟 / 千帆控制台直连,最小侵入</td><td className=\"py-2\">③ chat.completions + tools</td></tr>\n <tr><td className=\"py-2\">教育 SaaS / 培训 / 客服等业务后端集成</td><td className=\"py-2\">④ 自研后端 Agent</td></tr>\n <tr><td className=\"py-2\">原生 Android / iOS / 小程序 / 不装 MCP SDK 的老系统</td><td className=\"py-2\"><a href=\"#config-rest\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">→ 走下一大节「非 MCP REST」</a></td></tr>\n </tbody>\n </table>\n </div>\n </div>\n </DocSection>\n\n {/* ══════ 接入 · 非 MCP 原生 REST(cvx_fc) ══════ */}\n <DocSection id=\"config-rest\" icon={Network} title=\"接入 · 非 MCP 原生 REST(cvx_fc)\">\n\n <div className=\"rounded-lg border border-sky-500/30 bg-sky-500/5 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Network className=\"h-4 w-4 text-sky-600\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>客户端环境<strong className=\"text-foreground\">不能 / 不想引入 MCP SDK</strong>:原生 Android / iOS / Flutter、微信 / 支付宝小程序、老版本 Java / PHP / Go 后端</li>\n <li>希望把驰声当作<strong className=\"text-foreground\">普通 REST + WebSocket</strong>调,不经过 JSON-RPC、不走 stdio 代理</li>\n <li>内部代号 <code className=\"bg-muted px-1 rounded text-xs font-mono\">cvx_fc</code>(OpenAI function-calling 风格),与 MCP 模式<strong className=\"text-foreground\">指向同一个评测引擎</strong>,两者为并行入口</li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 能走 MCP 的一律优先 MCP;仅当客户端限制无法装 SDK 时走本大节。</p>\n </div>\n\n <SubDoc id=\"rest-overview\" title=\"functioncalling 模式总览 · 端点与认证\">\n <p>\n 驰声在 MCP 之外提供一套 <strong>OpenAI 兼容的 Function Calling 风格 REST API</strong>(内部代号 <code className=\"bg-muted px-1 rounded text-xs\">cvx_fc</code>)。它和 MCP 模式指向<strong>同一个评测引擎</strong>,但协议层走纯 HTTP / WebSocket,<strong>不依赖任何 MCP SDK</strong>。\n </p>\n\n <Callout type=\"info\">\n <strong>与 MCP 模式的关系</strong>:MCP 模式(<code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">POST /mcp</code>,JSON-RPC 2.0)和 functioncalling 模式(<code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">POST /v1/call</code>,REST)是<strong>两个并行入口</strong>,功能等价、工具/函数列表基本一致。\n </Callout>\n\n <p className=\"font-semibold mt-6\">API 端点</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">路径</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">GET</td><td className=\"py-2 px-3 font-mono text-xs\">/v1/functions</td><td className=\"py-2 px-3\">列出全部可用评测函数(等价 MCP <code className=\"bg-muted px-1 rounded text-xs\">tools/list</code>)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">POST</td><td className=\"py-2 px-3 font-mono text-xs\">/v1/call</td><td className=\"py-2 px-3\">调用一个评测函数(等价 MCP <code className=\"bg-muted px-1 rounded text-xs\">tools/call</code>)· 同时用于<strong>创建流式会话</strong>和<strong>拉取流式结果</strong></td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">WS</td><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/eval/{session_id}'}</td><td className=\"py-2 px-3\">流式评测音频传输</td></tr>\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6\">认证</p>\n <p>所有请求在 HTTP Header 中携带 API Key:</p>\n <CodeBlock filename=\"Header\" lang=\"http\">{`Authorization: Bearer <your_api_key>`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">认证失败返回 <code className=\"bg-muted px-1 rounded text-xs\">401</code>(缺少 Authorization)或 <code className=\"bg-muted px-1 rounded text-xs\">403</code>(Key 无效 / 无权使用该评测类型)。结构化错误码见 <a href=\"#error-codes\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">API 参考 · 错误码</a>。</p>\n </SubDoc>\n\n <SubDoc id=\"rest-catalog\" title=\"函数目录 · GET /v1/functions\">\n <p>\n 调用 <code className=\"bg-muted px-1 rounded text-xs\">GET /v1/functions</code> 拿到<strong>当前 API Key 已授权</strong>的全部评测函数列表,响应格式与 OpenAI function-calling 的工具清单一致,可以直接喂给大模型作为 <code className=\"bg-muted px-1 rounded text-xs\">tools</code>:\n </p>\n <CodeBlock filename=\"request\" lang=\"http\">{`GET /v1/functions\nAuthorization: Bearer <api_key>`}</CodeBlock>\n <CodeBlock filename=\"response.json\" lang=\"json\">{`{\n \"object\": \"list\",\n \"data\": [\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英文单词发音评测,返回总分和每个音标得分。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"ref_text\": { \"type\": \"string\", \"description\": \"参考文本\" },\n \"audio_base64\": { \"type\": \"string\", \"description\": \"Base64 编码音频(与 audio_url 二选一)\" },\n \"audio_url\": { \"type\": \"string\", \"description\": \"音频 HTTP(S) URL(与 audio_base64 二选一)\" },\n \"accent\": { \"type\": \"number\", \"description\": \"1=英式 / 2=美式 / 3=不区分(默认)\" },\n \"rank\": { \"type\": \"number\", \"description\": \"评分制:4 或 100(默认)\" }\n },\n \"required\": [\"ref_text\"]\n }\n }\n }\n /* … 其它函数依此类推 … */\n ]\n}`}</CodeBlock>\n <Callout type=\"tip\">\n 这个端点语义等价于 MCP 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">tools/list</code>,<strong>每个 API Key 可见的函数集合由后台权限决定</strong>——客户端应以实际返回为准,不要在本地硬编码全量 19 个。\n </Callout>\n\n <p className=\"font-semibold mt-6\">英文评测函数(10 个)</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">函数名</th>\n <th className=\"text-left py-2 px-3 font-medium\">core_type</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['en_word_eval', 'en.word.score', '单词发音评测'],\n ['en_word_correction', 'en.word.pron', '单词发音纠错'],\n ['en_phonics_eval', 'en.nsp.score', '自然拼读评测'],\n ['en_sentence_eval', 'en.sent.score', '句子朗读评测'],\n ['en_sentence_correction', 'en.sent.pron', '句子发音纠错'],\n ['en_vocab_eval', 'en.vocabs.pron', '词汇批量评测'],\n ['en_paragraph_eval', 'en.pred.score', '段落朗读评测'],\n ['en_realtime_eval', 'en.rltm.score', '实时朗读评测'],\n ['en_choice_eval', 'en.choc.score', '口语选择题'],\n ['en_semi_open_eval', 'en.scne.exam', '半开放题(情景对话等)'],\n ].map(([fn, ct, desc]) => (\n <tr key={fn}>\n <td className=\"py-2 px-3 font-mono text-xs\">{fn}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{ct}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6\">中文评测函数(6 个)</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">函数名</th>\n <th className=\"text-left py-2 px-3 font-medium\">core_type</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['cn_word_pinyin_eval', 'cn.word.score', '拼音 / 汉字发音评测'],\n ['cn_word_raw_eval', 'cn.word.raw', '汉字发音评测'],\n ['cn_sentence_eval', 'cn.sent.raw', '句子朗读评测'],\n ['cn_paragraph_eval', 'cn.pred.raw', '段落朗读评测'],\n ['cn_rec_eval', 'cn.rec.raw', '有限分支识别'],\n ['cn_aitalk_eval', 'cn.recscore.raw', '口语表达评测(AI Talk)'],\n ].map(([fn, ct, desc]) => (\n <tr key={fn}>\n <td className=\"py-2 px-3 font-mono text-xs\">{fn}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{ct}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6\">流式评测函数(3 个)</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">函数名</th>\n <th className=\"text-left py-2 px-3 font-medium\">作用</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr>\n <td className=\"py-2 px-3 font-mono text-xs\">start_stream_eval</td>\n <td className=\"py-2 px-3\">创建流式评测会话,返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code> / <code className=\"bg-muted px-1 rounded text-xs\">ws_url</code> / <code className=\"bg-muted px-1 rounded text-xs\">resume_token</code></td>\n </tr>\n <tr>\n <td className=\"py-2 px-3 font-mono text-xs\">get_stream_result</td>\n <td className=\"py-2 px-3\">兜底拉取:WS 没拿到 <code className=\"bg-muted px-1 rounded text-xs\">result</code> 时,通过 HTTP 把最终评分捞回来</td>\n </tr>\n <tr>\n <td className=\"py-2 px-3 font-mono text-xs\">stream_eval_result</td>\n <td className=\"py-2 px-3 text-muted-foreground\">(MCP 模式的别名;cvx_fc 模式统一用 <code className=\"bg-muted px-1 rounded text-xs\">get_stream_result</code>)</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <p className=\"text-xs text-muted-foreground mt-4\">\n 📘 每个函数的<strong>业务语义 / 参数 / 返回字段</strong>与 MCP 模式下的同名工具完全一致,可直接参考下文 <a href=\"#tools-en\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">API 参考 · 英文评测工具</a> 与 <a href=\"#tools-cn\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">中文评测工具</a>。\n </p>\n </SubDoc>\n\n <SubDoc id=\"rest-oneshot\" title=\"一次性文件评测\">\n <p>把完整音频通过 <code className=\"bg-muted px-1 rounded text-xs\">audio_base64</code> 或 <code className=\"bg-muted px-1 rounded text-xs\">audio_url</code> 二选一传给 <code className=\"bg-muted px-1 rounded text-xs\">/v1/call</code>,同步返回评分(与 MCP 模式的 <code className=\"bg-muted px-1 rounded text-xs\">evaluate_english_word</code> 等工具语义等价):</p>\n <CodeBlock filename=\"curl · 英文单词\" lang=\"bash\">{`curl -X POST https://fc.cloud.chivox.com/v1/call \\\\\n -H \"Authorization: Bearer your_api_key\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\n \"name\": \"en_word_eval\",\n \"arguments\": {\n \"ref_text\": \"hello\",\n \"audio_url\": \"https://example.com/hello.mp3\",\n \"accent\": 2,\n \"rank\": 100\n }\n }'`}</CodeBlock>\n <p><strong>成功响应:</strong></p>\n <CodeBlock filename=\"response.json\" lang=\"json\">{`{\n \"name\": \"en_word_eval\",\n \"result\": {\n \"overall\": 85.5,\n \"details\": { /* 音素 / 韵律 / 流利度等结构与 MCP 模式完全一致 */ }\n }\n}`}</CodeBlock>\n <p><strong>失败响应:</strong></p>\n <CodeBlock filename=\"error.json\" lang=\"json\">{`{\n \"name\": \"en_word_eval\",\n \"error\": { \"message\": \"错误描述\" }\n}`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">单次音频 ≤ 50MB,推荐 16 kHz / 单声道 / 16bit。</p>\n </SubDoc>\n\n <SubDoc id=\"rest-streaming\" title=\"流式评测 · 三步骤\">\n <div className=\"rounded-lg border border-border/60 bg-muted/10 p-5 space-y-5 my-3\">\n <FlowStep title=\"1\">\n <p><strong>创建会话</strong>:<code className=\"bg-muted px-1 rounded text-xs\">POST /v1/call</code> 调用 <code className=\"bg-muted px-1 rounded text-xs\">start_stream_eval</code></p>\n <p className=\"text-muted-foreground mt-1\">响应中拿到 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code> / <code className=\"bg-muted px-1 rounded text-xs\">ws_url</code> / <code className=\"bg-muted px-1 rounded text-xs\">resume_token</code>。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong>WebSocket 推送</strong>:连接 <code className=\"bg-muted px-1 rounded text-xs\">ws_url</code>,收到 <code className=\"bg-muted px-1 rounded text-xs\">{`{\"type\":\"ready\"}`}</code> 即可开始发<strong>二进制音频帧</strong>。</p>\n <p className=\"text-muted-foreground mt-1\">边推边收 <code className=\"bg-muted px-1 rounded text-xs\">intermediate</code> 中间结果;结束时发 <code className=\"bg-muted px-1 rounded text-xs\">{`{\"type\":\"stop\"}`}</code>,收到 <code className=\"bg-muted px-1 rounded text-xs\">result</code> 即为最终评分。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>兜底拉取(可选)</strong>:如 WS 断开未收到最终 <code className=\"bg-muted px-1 rounded text-xs\">result</code>,可再次 <code className=\"bg-muted px-1 rounded text-xs\">POST /v1/call</code> 调用 <code className=\"bg-muted px-1 rounded text-xs\">get_stream_result</code> 兜底。</p>\n </FlowStep>\n </div>\n\n <p className=\"font-semibold mt-4\"><code>start_stream_eval</code> 参数</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.sent.score / cn.sent.raw(参考上方中英评测工具表)' },\n { name: 'ref_text', type: 'string', required: true, desc: '参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式:pcm / wav / mp3(默认 mp3;流式推荐 pcm 省编解码)' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'channel', type: 'number', required: false, desc: '声道:1=单声道(默认)/ 2=立体声' },\n { name: 'sample_bytes', type: 'number', required: false, desc: '采样位深,默认 2(16bit)' },\n { name: 'accent', type: 'number', required: false, desc: '英文口音:1=英式 / 2=美式 / 3=不区分(默认)' },\n { name: 'age_group', type: 'string', required: false, desc: '中文人群:adult(默认)/ child' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100(默认)' },\n ]} />\n\n <p className=\"font-semibold mt-4\">WebSocket 帧协议</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-3\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-xs font-semibold mb-2\">客户端 → 服务端</p>\n <ul className=\"text-xs space-y-1.5 text-muted-foreground leading-relaxed list-none pl-0\">\n <li>• <code className=\"bg-muted px-1 rounded\">Binary</code> — 原始音频帧,连上即可直接发送,无需握手</li>\n <li>• <code className=\"bg-muted px-1 rounded\">{`{\"type\":\"stop\"}`}</code> — 结束录音</li>\n <li>• <code className=\"bg-muted px-1 rounded\">{`{\"type\":\"ping\"}`}</code> — 心跳</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-xs font-semibold mb-2\">服务端 → 客户端</p>\n <ul className=\"text-xs space-y-1.5 text-muted-foreground leading-relaxed list-none pl-0\">\n <li>• <code className=\"bg-muted px-1 rounded\">ready</code> — 连接就绪,可开始发送音频</li>\n <li>• <code className=\"bg-muted px-1 rounded\">intermediate</code> — 实时中间结果</li>\n <li>• <code className=\"bg-muted px-1 rounded\">result</code> — 最终评测结果</li>\n <li>• <code className=\"bg-muted px-1 rounded\">backpressure</code> — 背压,含 <code className=\"bg-muted px-1 rounded\">suggested_interval_ms</code>,建议降低发送频率</li>\n <li>• <code className=\"bg-muted px-1 rounded\">error</code> — 错误(含结构化 <code className=\"bg-muted px-1 rounded\">code</code> + <code className=\"bg-muted px-1 rounded\">message</code>,见<a href=\"#error-codes\" className=\"underline underline-offset-2\">错误码</a>一节)</li>\n <li>• <code className=\"bg-muted px-1 rounded\">pong</code> — 心跳响应</li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"rest-examples\" title=\"JS / Android 完整示例\">\n <p className=\"font-semibold\">JavaScript(浏览器 / Node.js)</p>\n <CodeBlock filename=\"browser-or-node.js\" lang=\"javascript\">{`// ① 创建会话\nconst resp = await fetch('https://fc.cloud.chivox.com/v1/call', {\n method: 'POST',\n headers: {\n 'Authorization': 'Bearer your_api_key',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n name: 'start_stream_eval',\n arguments: {\n core_type: 'en.sent.score',\n ref_text: 'Hello world',\n audio_type: 'pcm',\n sample_rate: 16000,\n },\n }),\n});\nconst { result } = await resp.json();\n\n// ② WebSocket 推送音频\nconst ws = new WebSocket(result.ws_url);\nws.onmessage = (ev) => {\n const msg = JSON.parse(ev.data);\n switch (msg.type) {\n case 'ready': startRecording(); break;\n case 'intermediate': console.log('部分结果', msg.data); break;\n case 'result': console.log('最终结果', msg.data); ws.close(); break;\n case 'backpressure': console.warn('降速 ~', msg.suggested_interval_ms, 'ms'); break;\n case 'error': console.error(msg.code, msg.message); break;\n }\n};\n\nfunction onAudioChunk(pcm) {\n if (ws.readyState === WebSocket.OPEN) ws.send(pcm); // 直接发二进制\n}\n\nfunction stopRecording() {\n ws.send(JSON.stringify({ type: 'stop' }));\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-6\">Android(OkHttp · Kotlin)</p>\n <CodeBlock filename=\"AudioEvalClient.kt\" lang=\"kotlin\">{`val wsUrl = \"ws://your-host:8080/ws/eval/$sessionId\"\nval client = OkHttpClient()\nval req = Request.Builder()\n .url(wsUrl)\n .addHeader(\"Authorization\", \"Bearer \\${apiKey}\")\n .build()\n\nval ws = client.newWebSocket(req, object : WebSocketListener() {\n override fun onMessage(ws: WebSocket, text: String) {\n val msg = JSONObject(text)\n when (msg.getString(\"type\")) {\n \"ready\" -> startRecording()\n \"intermediate\" -> updateUI(msg.getJSONObject(\"data\"))\n \"result\" -> handleFinalResult(msg.getJSONObject(\"data\"))\n \"error\" -> handleError(msg.getString(\"code\"), msg.getString(\"message\"))\n }\n }\n})\n\n// 录音回调中发送 PCM 字节\nfun onAudioData(pcm: ByteArray) {\n ws.send(ByteString.of(*pcm))\n}\n\nfun stop() {\n ws.send(\"\"\"{\"type\":\"stop\"}\"\"\")\n}`}</CodeBlock>\n <Callout type=\"tip\">iOS / Flutter / 小程序同理,只要平台提供 WebSocket 客户端即可:连接 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">ws_url</code>、把 PCM bytes 发出去、按 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">type</code> 字段解析服务端消息就行。</Callout>\n </SubDoc>\n\n <SubDoc id=\"rest-resume\" title=\"断线恢复 · 兜底拉取\">\n <p className=\"font-semibold\">resume_token · 60 秒续播</p>\n <p>\n 客户端意外断开时,会话在服务端<strong>挂起 60 秒</strong>;用创建会话拿到的 <code className=\"bg-muted px-1 rounded text-xs\">resume_token</code> 续播即可:\n </p>\n <CodeBlock filename=\"reconnect\" lang=\"http\">{`GET ws://your-host:8080/ws/eval/{session_id}?resume={resume_token}\nAuthorization: Bearer <your_api_key>`}</CodeBlock>\n <Callout type=\"tip\">\n 重连成功后会收到新的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">ready</code>,继续推送音频即可;服务端会颁发<strong>新的 resume_token</strong>,原 token 立即作废。\n </Callout>\n\n <p className=\"font-semibold mt-6\">兜底拉取 · get_stream_result</p>\n <p>如果 WS 没能收到最终 <code className=\"bg-muted px-1 rounded text-xs\">result</code>,可主动通过 HTTP 拉取:</p>\n <CodeBlock filename=\"fallback-result.sh\" lang=\"bash\">{`curl -X POST https://fc.cloud.chivox.com/v1/call \\\\\n -H \"Authorization: Bearer your_api_key\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\n \"name\": \"get_stream_result\",\n \"arguments\": {\n \"session_id\": \"stream-1713340800-a1b2c3\",\n \"auto_stop\": true,\n \"timeout\": 30\n }\n }'`}</CodeBlock>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '由 start_stream_eval 返回的会话 ID' },\n { name: 'auto_stop', type: 'bool', required: false, desc: '是否自动发送 stop 并等待结果,默认 true' },\n { name: 'timeout', type: 'number', required: false, desc: '等待秒数,默认 30' },\n ]} />\n\n <Callout type=\"warning\">\n functioncalling 模式与 MCP 模式<strong>二选一、不要混用</strong>:同一个会话不要同时走 MCP 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">/ws/audio/</code> 和 cvx_fc 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">/ws/eval/</code>,两条链路的会话 ID 空间是分开的。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 评测模式 ══════ */}\n <DocSection id=\"eval-modes\" icon={Radio} title=\"评测模式\">\n\n <SubDoc id=\"stream-eval\" title=\"实时录音评测(边录边评)\">\n <p>通过本地麦克风实时录音,音频以 WebSocket 流式推送到评测服务,<strong>无需生成中间文件</strong>。这是最灵活、延迟最低的评测方式,特别适合交互式口语练习场景。</p>\n <Callout type=\"tip\">实时录音评测需要安装 <strong>SoX</strong> 并使用本地代理模式(Claude Desktop / Claude Code)。Cursor 等远程直连模式暂不支持实时录音。</Callout>\n\n <p className=\"font-semibold mt-4\">需要按顺序调用三个工具:</p>\n\n <div className=\"mt-3 space-y-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">create_stream_session</code> — 创建流式评测会话</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.word.score、en.sent.score、cn.sent.raw' },\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式,默认 mp3' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100,默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code>,后续步骤需使用。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">start_recording</code> — 开始麦克风录音</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '来自 create_stream_session 的会话 ID' },\n { name: 'chunk_size', type: 'number', required: false, desc: '音频块大小(bytes),默认 3200' },\n { name: 'chunk_interval_ms', type: 'number', required: false, desc: '推送间隔(ms),默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">调用后自动启动麦克风,音频通过 WebSocket 实时推送到评测服务。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">stop_recording</code> — 停止录音并获取结果</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '会话 ID' },\n { name: 'timeout', type: 'number', required: false, desc: '等待评测结果超时秒数,默认 30' },\n ]} />\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"stream-flow\" title=\"流式评测完整流程\">\n <div className=\"rounded-lg border border-border/60 p-5 bg-muted/10 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong>用户:</strong>&quot;请评测我的英文发音,文本是 Hello&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">create_stream_session(core_type=&quot;en.word.score&quot;, ref_text=&quot;Hello&quot;)</code></p>\n <p className=\"text-muted-foreground\">→ 获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">session_id</code></p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p>AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">start_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground mt-1\">麦克风开始录音,音频通过 WebSocket 实时推送到评测服务</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>用户</strong>说完后告知 AI &quot;结束&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">stop_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground\">→ 返回评测结果(总分、准确度、流利度、音素得分等)</p>\n </FlowStep>\n </div>\n </SubDoc>\n\n <SubDoc id=\"file-eval\" title=\"音频文件评测\">\n <p>适用于已有音频文件的评测场景,支持三种输入方式:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">适用场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_file_path</td><td className=\"py-2 px-3\">本地文件路径</td><td className=\"py-2 px-3 text-muted-foreground\">代理自动读取并转为 Base64,最便捷</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_base64</td><td className=\"py-2 px-3\">Base64 编码数据</td><td className=\"py-2 px-3 text-muted-foreground\">已有编码数据时使用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_url</td><td className=\"py-2 px-3\">音频文件 URL</td><td className=\"py-2 px-3 text-muted-foreground\">音频托管在云存储时使用</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"tip\">推荐使用 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">audio_file_path</code>,只需传入本地文件路径,代理会自动处理编码和上传。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"tools-en\" title=\"英文评测工具(10 种)\">\n <ToolTable tools={[\n ['en_word_eval', '单词评测', '评测 \"Hello\" 的发音准确度', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.word.score'],\n ['en_word_correction', '单词纠音', '检测多读/漏读/错读并给出纠正建议', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.word.pron'],\n ['en_vocab_eval', '词语评测', '同时评测多个单词的发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.vocabs.pron'],\n ['en_sentence_eval', '句子评测', '评测整句朗读的准确度与流利度', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.sent.score'],\n ['en_sentence_correction', '句子纠音', '逐词检测发音问题并给出修正建议', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.sent.pron'],\n ['en_paragraph_eval', '段落朗读评测', '评测整段英文课文的朗读质量', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.pred.score'],\n ['en_phonics_eval', '自然拼读评测', '评测 Phonics 拼读规则掌握程度', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.nsp.score'],\n ['en_choice_eval', '口语选择题', '从预设选项中识别口语回答', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.choc.score'],\n ['en_semi_open_eval', '半开放题评测', '场景对话等半开放口语题型', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.scne.exam'],\n ['en_realtime_eval', '实时朗读评测', '实时反馈朗读质量,逐句评分', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.rltm.score'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong>所有英文评测工具均支持以下参数。</p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tools-cn\" title=\"中文评测工具(6 种)\">\n <ToolTable tools={[\n ['cn_word_raw_eval', '单字评测(汉字)', '评测 \"中\" 的发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.pred.raw'],\n ['cn_word_pinyin_eval', '单字评测(拼音)', '评测 \"zhōng\" 的发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.pred.pny'],\n ['cn_sentence_eval', '词句评测', '评测 \"你好世界\" 的朗读', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.sent.score'],\n ['cn_paragraph_eval', '段落朗读评测', '评测一段中文课文的朗读质量', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.para.score'],\n ['cn_rec_eval', '有限分支识别评测', '从预设选项中识别用户发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.asr.rec'],\n ['cn_aitalk_eval', 'AI Talk 口语评测', '中文口语对话能力评测', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.aitalk'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong></p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本(汉字或拼音)' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结构化结果:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"integrity\": 95,\n \"details\": [\n {\n \"char\": \"hello\",\n \"score\": 85,\n \"phone\": [\n { \"phoneme\": \"h\", \"score\": 90 },\n { \"phoneme\": \"ɛ\", \"score\": 82 },\n { \"phoneme\": \"l\", \"score\": 88 },\n { \"phoneme\": \"oʊ\", \"score\": 80 }\n ]\n }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall', '0–100', '综合总分,反映整体语音质量'],\n ['accuracy', '0–100', '准确度,音素级别的发音准确率'],\n ['pron', '0–100', '发音得分,整体发音质量评价'],\n ['fluency.overall', '0–100', '流利度,语流的自然度与平滑度'],\n ['fluency.speed', '0–100', '语速得分,每分钟词/音节计量'],\n ['fluency.pause', 'number', '不自然停顿次数'],\n ['integrity', '0–100', '完整度,是否完整朗读了参考文本'],\n ['details[].phone[]', 'array', '每个音素的独立评分,可定位具体弱项'],\n ['details[].stress[]', 'array', '重音得分,检测重弱读是否正确'],\n ].map(([field, range, desc]) => (\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <p className=\"font-semibold mb-2\">常见业务场景</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期', '音频上传后 5 分钟内有效,评测完成后自动删除', '重新上传音频'],\n ['服务器繁忙', '当前请求过多,排队已满', '稍后重试,使用指数退避策略'],\n ['存储空间已满', '服务器临时音频存储已满', '稍后重试,系统会自动清理'],\n ['音频格式不支持', '上传了不支持的文件类型', '使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限', '超过该题型最大时长限制', '裁剪音频后重试'],\n ['WebSocket 连接断开', '网络不稳定导致流式连接中断', '代理会自动重连,如持续失败请检查网络'],\n ].map(([scene, reason, fix]) => (\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6 mb-2\">HTTP 状态码(functioncalling 模式)</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">状态码</th>\n <th className=\"text-left py-2 px-3 font-medium\">含义</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['400', '请求格式错误(JSON 缺字段 / 类型错)'],\n ['401', '未认证(缺少 Authorization 头)'],\n ['403', '权限不足(API Key 无效或无权使用该评测类型)'],\n ['404', '会话不存在(session_id 错误或已回收)'],\n ['409', '会话状态不允许当前操作(如已 stop 后又发音频)'],\n ].map(([code, desc]) => (\n <tr key={code}>\n <td className=\"py-2 px-3 font-mono text-xs\">{code}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6 mb-2\">流式评测结构化错误码</p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n WebSocket 的 <code className=\"bg-muted px-1 rounded\">error</code> 帧与 <code className=\"bg-muted px-1 rounded\">get_stream_result</code> 的 HTTP 错误体统一使用以下错误码,便于客户端差错分流:\n </p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">错误码</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">建议处理</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['SESSION_NOT_FOUND', '会话不存在', '重新调用 start_stream_eval / create_stream_session 创建'],\n ['SESSION_EXPIRED', '会话已过期(60s 未续流)', '重新创建会话'],\n ['INVALID_STATE', '当前状态不允许该操作', '检查调用顺序(例如已 stop 后又发音频)'],\n ['INVALID_PARAMS', '参数无效', '核对 core_type / ref_text / 采样率等'],\n ['RESUME_INVALID', 'resume_token 无效或已过期', '重新创建会话(每次重连会颁发新 token)'],\n ['AUDIO_TOO_LARGE', '单次音频超过 50MB', '压缩或切片后重传'],\n ['UPSTREAM_CONNECT', '评测引擎连接失败', '稍后重试;持续发生请联系驰声'],\n ['UPSTREAM_TIMEOUT', '评测超时', '缩短音频 / 检查网络'],\n ['UPSTREAM_EVAL_ERROR', '评测引擎返回错误', '查看 message 字段细节'],\n ['CAPACITY_FULL', '并发会话已满', '退避重试或升级配额'],\n ].map(([code, desc, fix]) => (\n <tr key={code}>\n <td className=\"py-2 px-3 font-mono text-xs\">{code}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{desc}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p className=\"text-sm text-muted-foreground\">\n 下面这些模板与{' '}\n <Link href=\"/demo\" className=\"underline underline-offset-2\">免费体验</Link>{' '}\n Demo 中实际使用的「教练 Prompt」一脉相承,全部围绕 <strong>角色 / 任务 / 方法 / 输出格式 / 语气</strong> 五段式结构,并使用占位符{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code> 直接拼接 MCP 工具返回的 JSON。\n 推荐在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">system</code> 消息里挂载,再把工具返回原文塞进\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">user</code> 消息,由 LLM 基于真实分数和音素生成可读诊断 / 练习材料。\n </p>\n\n <Callout type=\"tip\">\n <strong>使用建议:</strong>\n <ul className=\"list-disc list-inside mt-1 space-y-1\">\n <li>把 MCP 工具调用的完整 JSON(含 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">overall / dp_type / phonemes</code> 等字段)原样塞入 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,不要预先做汇总——细节越完整诊断越具体。</li>\n <li>「诊断」类模板让 LLM 输出 Markdown 给最终用户阅读;「练习」类模板要求 LLM 输出严格 JSON 给前端渲染。两步分开调用,效果远好于一次性混合。</li>\n <li>题型决定 prompt:单词题用音素级、句子题加韵律、段落题加 chunk / 整体性,半开放题套四维评级。下面按这 6 类分别给出。</li>\n </ul>\n </Callout>\n\n {/* ─── A. 诊断类 · 输出 Markdown 给学习者阅读 ─── */}\n <h4 className=\"text-sm font-semibold mt-6 mb-3 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> A. 二次诊断 · 输出 Markdown\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Sparkle className=\"h-4 w-4\" /> 1. 单词级 · 音素诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.word.score</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code>。围绕 <code className=\"bg-muted px-1 rounded font-mono\">phonemes[].dp_type</code> 找出错读音。\n </p>\n <CodeBlock filename=\"prompt-word-diagnosis.txt\">{`你是一位专业的英语发音教练。下面是驰声 MCP(en.word.score)返回的单词级评测数据:\n一个目标词、总分与发音分、以及带有 dp_type 的音素数组。\n\n## 任务\n为学习者写一份 **可直接阅读** 的发音诊断,每一条都要引用音素分数(写出 IPA 符号)。\n\n## 方法\n- 按 dp_type 分组:先看 mispron,再补充 normal 但分数 < 70 的\"边缘音\"。\n- 每个问题音素:① 典型母语干扰 → ② 你听到的声音 → ③ 一条具体的发音修正动作(下颌 / 舌位 / 气流 / 唇形)。\n- 重音符号 ˈ 标记的音节,重音错位也要单独列出(即便音素本身分数尚可)。\n- 最多列 4 条,按 **影响力从弱到强** 排序。\n\n## 输出\n精简 Markdown:可选一句总结 → 编号列表 → 单行 **🎯 优先级** 排序。\n\n## 语气\n友善、具体、可执行,不要空喊\"加油\"。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> 2. 句子级 · 节奏 + 重音诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.sent.score</code>。开头先给一行\"计分牌\",再从 phones → words → prosody。\n </p>\n <CodeBlock filename=\"prompt-sentence-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.sent.score)返回的句子级数据:refText、总分、发音子分(accuracy / integrity / fluency / rhythm)、可选 speed(WPM),以及 details[] 中的逐词得分、dp_type 与可选 phonemes[]。\n\n## 任务\n开头给一行紧凑的 **计分牌**(总分 + 各子分 + 速度),再按 **音素 → 词 → 韵律** 顺序展开诊断。\n\n## 检查清单(每项都要覆盖;不适用则写\"无明显问题\")\n1. **音素级**:每个 score < 70 或 dp_type ≠ normal 的词,展开 phonemes,命名典型错误(如 /ð/ → /d/)并给一条修正动作。\n2. **漏读 / 多读**:标注 dp_type=omit / insert,解释如何拉低 integrity。\n3. **韵律**:基于 rhythm + 错读词的重音,指出重音错位、陈述句调型平直、断句过多等。\n4. **流利度 vs 准确度**:若 fluency 高但 accuracy 低,明确指出,让学习者知道优先改哪一项。\n\n## 输出格式\nMarkdown 子标题:**Pronunciation / Prosody / Study priority**(最后一行简短排序,按 ROI 从高到低)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <FileText className=\"h-4 w-4\" /> 3. 段落级 · 音素族归并 + 完整度\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code>。把同类错音合并为\"音素族\",避免逐词复述。\n </p>\n <CodeBlock filename=\"prompt-paragraph-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.pred.score)返回的段落级数据:长 refText、总分、发音子分、可选 speed / integrity,以及大量 details[](同一个词可能在不同子句中重复出现;空 char + dp_type=omit 表示漏读)。\n\n## 任务\n对整段做 **整体诊断**:\n(a) 完整度 / 漏读,\n(b) 反复出现的 **音素族**,\n(c) 节奏与断句,\n(d) 各子分之间是否相互印证。\n\n## 检查清单\n1. **integrity / dp_type**:列出每个 omit / insert,并给出所在子句;解释长读时为什么虚词容易被吞。\n2. **音素聚类**:将弱词按共同音素问题归族(/θ/ 链、/e/ 链、/ɒ/ 等),引用 details 中的实例。\n3. **韵律**:结合 rhythm 与标点 —— 短语内停顿过多?子句边界缺失?\n4. **整体一致性**:若 fluency 正常但 accuracy 差(或反之),点明并指出对练习设计的影响。\n\n## 输出格式\nMarkdown 章节:**Summary / Completeness / Pronunciation (families) / Rhythm / Study priorities**(最后一项最多 4 条,按影响力排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <GraduationCap className=\"h-4 w-4\" /> 4. 半开放题 · IELTS 风格四维评级\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pqan.score</code>。结合学习者 transcript + 四维分数,给出可教学的\"升档建议\"。\n </p>\n <CodeBlock filename=\"prompt-semi-open.txt\">{`你是一位 **IELTS / 课堂口语风格** 的口语教练。下面是驰声 MCP(en.pqan.score)返回的半开放题数据:题目 refText、学习者 transcript、overall、四个维度分(grammar / content / fluency / pron)、speed(WPM)、以及结构化的 issues[] 标记。\n\n## 任务\n给出 **基于 rubric 的诊断**:先用一句话给出 0–100 的整体段位估计,再 **逐维度** 解释,并 **引用 transcript 中的短句**(不要复述全文)。最后给 3–5 条 **可执行的\"升档建议\"**(句型 / 模板,而不是\"多练一点\")。\n\n## 权重(要明确告诉学习者)\n- **Content ~30%** — 想法深度、举例、具体程度\n- **Grammar ~25%** — 句型多样性、主谓一致、从句\n- **Fluency ~25%** — 语速、停顿、口头禅、衔接\n- **Pronunciation ~20%** — 音素 + 可懂度\n\n## 跨维度检查\n- 若 content 最低,先给\"展开模板\",不要先抠音素。\n- 若 fluency 低 + WPM 低,区分\"思考型停顿\"与\"发音卡顿\"。\n- 若 pron 标出特定音,绑定到 transcript 里 **真实出现过的词**。\n\n## 输出格式\nMarkdown:**段位估计** → 紧凑的 **得分表格** → **\"To reach the next band\"** 编号列表(每条给出可仿写的句型)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Languages className=\"h-4 w-4\" /> 5. 中文专项 · 声调 / 儿化 / 变调\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.sent.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.pred.raw</code>。中文评测最大权重在 **声调**,必须单独检查。\n </p>\n <CodeBlock filename=\"prompt-mandarin-coach.txt\">{`你是一位专业的普通话口语教练。下面是驰声 MCP(cn.sent.raw)返回的中文句子评测数据:整句分数 + 逐字 details + 可选 phonemes + 流利 / 节奏分。\n\n## 中文评测的权重\n- **声调错误权重最高** —— 声调读错可能直接改变词义\n- dp_type=mispron 多来自:声调错、平翘舌混用、前后鼻音混用\n- 拼音对、声调错,听感仍是\"洋腔洋调\"\n\n## 检查清单(缺则写\"无明显问题\")\n1. **儿化**:phonemes 出现 r(儿化) 或 dp_type 提示时,确认是否被读成\"主音节 + 单独儿\"两拍。\n2. **轻声**:叠词、词缀、句末语气词 —— 第二字应短促轻读,无完整声调曲线。\n3. **变调**:三声连读、不 + 后字、一 + 后字,是否符合普通话规则。\n4. **节奏**:用 rhythm + 逗号位置,标记停顿过长或过碎,建议 chunk 边界。\n\n## 输出\nMarkdown 子标题:**总览 / 儿化 / 轻声 / 变调 / 节奏 / 学习优先级**(最后一项 1–3 条,按\"听感自然度提升\"排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── B. 练习类 · 输出 JSON 给前端渲染 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <Mic className=\"h-4 w-4\" /> B. 微练习生成 · 输出严格 JSON\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 6. 单词 / 句子 · 微练习\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 建议在 <strong>诊断完成后</strong> 第二轮调用:让 LLM 严格按弱项音素生成,避免\"通用绕口令包\"。\n </p>\n <CodeBlock filename=\"prompt-micro-drills.txt\">{`你仍是这位学习者的发音教练。**第二轮诊断已经完成** —— 现在严格基于诊断里点名的弱音素,生成 **1:1 对应** 的微练习(不要给通用题)。\n\n## 输出(仅返回一段合法 JSON 数组,外层不要任何文字)\n根节点必须是数组,每个元素含 category(字符串)、icon(单个 emoji)、items({ \"label\", \"content\" } 数组)。\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme drills\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Awareness\", \"icon\": \"🗣️\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Whole word\", \"icon\": \"📝\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme drills**:诊断里每个 mispron 音素至少给一对最小对立对 + 一句短绕口令 / 重复句。\n- **Awareness**:当重音偏弱时,给一条 stress / 舌位 / 下颌姿态提示。\n- **Whole word**:2–3 句自然句,目标词分别出现在句首 / 句中 / 句末。\n- 所有 content 必须可念出声;字符串内不要再嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n原始 MCP:\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Notebook className=\"h-4 w-4\" /> 7. 段落 · Chunk Map + 元认知\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 段落级练习要从\"音素\"上升到\"短语 / 整段 / 元认知录回放\"。直接对应 <code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code> 的诊断结果。\n </p>\n <CodeBlock filename=\"prompt-paragraph-drills.txt\">{`你仍是这位学习者的口语教练。**段落级二次诊断** 已经列出了漏读位置和音素族 —— 现在请生成 **整段练习**:把弱音素串成\"族\"练习、用 chunk map 把虚词粘回短语,并附一条录音 → 回放 → 对照 MCP details 的元认知步骤。\n\n## 输出(仅返回一段合法 JSON 数组)\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme families\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Chunking\", \"icon\": \"🎵\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Metacognition\", \"icon\": \"🧠\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme families**:至少 3 条,按诊断里的音素族(/θ/、/e/、/ɒ/ 等)组织最小对立对 / 词链。\n- **Chunking**:① 给一行用 [ ] 和 / 标注的 chunk map(可加大致停顿秒数);② 给一句\"把漏掉的虚词嵌入短语\"的练习(不要孤立练)。\n- **Metacognition**:一条\"录音 → 回放 → 对照 MCP details\"的 N 遍练习(N 写明,2–3 之间)。\n- 字符串只能是纯展示文本,禁止嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── C. 跨题 / 运营 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" /> C. 跨题 / 运营\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Gauge className=\"h-4 w-4\" /> 8. 多日学习报告\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 把过去 N 天的 MCP 评测结果聚合后丢给 LLM,输出适合发给学生 / 家长的周报。\n </p>\n <CodeBlock filename=\"prompt-weekly-report.txt\">{`你是一位英语口语学习顾问。下面是这位学习者过去 7 天的驰声 MCP 评测历史(按时间顺序),每条包含题型、coreType、overall、子分、weak_phonemes 摘要。\n\n## 任务\n为学习者生成一份 **周报**,目标读者:本人 + 家长 / 老师。\n强调 **趋势** 与 **下一步计划**,不要复述每一次具体分数。\n\n## 必须包含\n1. **整体趋势**:进步 / 持平 / 退步(一句话 + 简短依据)\n2. **维度变化**:accuracy / fluency / rhythm / integrity / speed 任意有数据的维度,给出\"本周均分 vs 上周\"\n3. **本周高光**:分数提升最大的题型 / 音素 / 句型\n4. **顽固问题**:连续 3 次以上未达 70 的音素或维度\n5. **下周计划**:3 条具体动作(每条对应一个 MCP 工具或一个练习类型)\n6. **结尾**:1–2 句鼓励性总结,避免空话\n\n## 风格\n- 像一位认真但不啰嗦的私教\n- 出现学生姓名时使用占位 {student_name}\n- 数字一律保留整数\n\n## 评测历史\n\\`\\`\\`json\n{history}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 9. 错误归因 · JSON 摘要\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 用于后端聚合 / 数据看板:让 LLM 把一次评测压缩成结构化摘要,方便入库统计。\n </p>\n <CodeBlock filename=\"prompt-error-summary.txt\">{`你是一个评测结果归类器。下面是驰声 MCP 一次评测的完整返回。请抽取关键信息并 **仅返回合法 JSON**(不要任何解释文字)。\n\n## Schema\n\\`\\`\\`json\n{\n \"core_type\": \"string,例如 en.sent.score\",\n \"overall\": 0,\n \"sub_scores\": { \"accuracy\": 0, \"integrity\": 0, \"fluency\": 0, \"rhythm\": 0 },\n \"weak_phonemes\": [\n { \"ipa\": \"string\", \"avg_score\": 0, \"occur\": 0, \"example_word\": \"string\" }\n ],\n \"omissions\": [{ \"after\": \"string\", \"missing\": \"string\" }],\n \"stress_issues\": [{ \"word\": \"string\", \"expected\": \"string\", \"actual\": \"string\" }],\n \"primary_issue_tag\": \"phoneme|stress|fluency|integrity|content|none\",\n \"next_action_hint\": \"string,10 字以内,例如 \\\"练 /θ/ 对立对\\\"\"\n}\n\\`\\`\\`\n\n## 规则\n- weak_phonemes 只收 avg_score < 70 的项,最多 5 项,按 occur 降序。\n- 不要复述 refText,不要 markdown。\n- 字段缺失时使用 null 或空数组,不要省略 key。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n <Callout type=\"info\">\n <strong>怎么接到 MCP 工具调用?</strong>\n <br />\n 以「直调 chat.completions + 动态 tools」为例:第一轮 LLM 调出 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code> 工具,把工具 result(即 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">tool</code> 消息内容)原样填入上面任意「诊断」模板的{' '}\n <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,作为第二轮 user 消息发回;拿到诊断结论再走第三轮,套「微练习」模板得到 JSON。三轮调用即可串起 <strong>评测 → 诊断 → 练习</strong> 全链路。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频过期:</strong>上传后 5 分钟内有效。建议在上传后 3 分钟内完成评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>服务繁忙:</strong>使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>录音中断:</strong>WebSocket 断线后代理会自动重连。如持续失败,检查网络连接和 SoX 安装。</span></li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>优先使用流式评测:</strong>实时录音模式跳过了文件上传环节,端到端延迟比文件评测低 30-50%。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频压缩:</strong>文件评测时,上传前将音频转为 16kHz 单声道 mp3,可减少 60-80% 的上传体积。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>选择合适的工具:</strong>单词评测比段落评测快 3-5 倍。如只需单词级分数,不要用句子评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>本地文件路径最便捷:</strong>使用 <code className=\"bg-muted px-1 rounded text-xs\">audio_file_path</code> 参数传入本地路径,代理自动处理编码,无需手动转 Base64。</span></li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Globe} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传(MCP 模式文件评测使用)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP 模式 · JSON-RPC(Streamable HTTP)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/audio/{session_id}'}</td><td className=\"py-2 px-3\">WS</td><td className=\"py-2 px-3\">MCP 模式 · 流式音频 WebSocket</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/v1/functions</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">functioncalling 模式 · 列出全部评测函数</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/v1/call</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">functioncalling 模式 · 一次性评测 / 创建流式会话 / 拉取流式结果</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/eval/{session_id}'}</td><td className=\"py-2 px-3\">WS</td><td className=\"py-2 px-3\">functioncalling 模式 · 流式音频 WebSocket</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制', '500MB', '最大音频存储空间'],\n ['最大排队', '10', '等待处理的请求数'],\n ['最大并发', '3', '同时处理的评测数'],\n ['音频有效期', '5 分钟', '上传后未评测自动过期'],\n ['文件大小限制', '50MB', '单个音频文件最大'],\n ].map(([k, v, d]) => (\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">\n 上表为托管侧<strong>技术默认</strong>;计费与试用/商用<strong>调用配额、并发档位</strong>以{' '}\n <Link href=\"/dashboard/plans\" className=\"text-foreground underline underline-offset-2\">\n 会员套餐\n </Link>\n 说明为准,与上表独立。\n </p>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <Callout type=\"info\">\n <strong>当前线上版本:v1.0.0</strong>(网站未正式上线)。以下变更项为开发阶段规划记录,以正式发布公告为准。\n </Callout>\n <div className=\"space-y-3 mt-4\">\n {[\n { ver: 'v1.0.0', date: 'TBD', desc: '当前线上版本(网站未正式上线)' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </DocsShell>\n );\n}\n\nexport default function DocsPage() {\n const locale = useLocale();\n return locale.startsWith('zh') ? <DocsPageZh /> : <DocsPageEn />;\n}\n"}, "bcc3fdfc23e8e34802364c99952e7d8836e9d43e": {"t": 1785765787.197597, "n": 190353, "h": "ecf33defbf4a8dac", "c": "'use client';\n\nimport { useLocale } from 'next-intl';\nimport { Link } from '@/i18n/routing';\nimport { DocsShell, type NavGroup } from './docs-shell';\nimport {\n CodeBlock,\n DocSection,\n SubDoc,\n ParamTable,\n Callout,\n FlowStep,\n ToolTable,\n} from './docs-shared';\nimport { DocsEnglishBody } from './docs-content-en';\nimport {\n BookOpen,\n Code2,\n Zap,\n FileText,\n Terminal,\n Globe,\n Mic,\n MessageSquare,\n BarChart3,\n AlertTriangle,\n Radio,\n FolderOpen,\n Workflow,\n Sparkles,\n Plug,\n Bot,\n Building2,\n Check,\n Sparkle,\n Languages,\n GraduationCap,\n Notebook,\n Gauge,\n ListChecks,\n Network,\n} from 'lucide-react';\n\n/* ─── Navigation Structure ─── */\n\nconst NAV = [\n { id: 'concept', icon: Workflow, label: 'MCP 工作原理', children: [\n { id: 'why-mcp', label: '为什么需要 MCP' },\n { id: 'scenario-walkthrough', label: '场景走查 · 6 步' },\n { id: 'dev-responsibility', label: '开发者只做两件事' },\n { id: 'integration-paths', label: '三种接入姿势' },\n ]},\n { id: 'architecture', icon: Terminal, label: '架构说明', children: [\n { id: 'arch-overview', label: '两种接入模式总览' },\n { id: 'arch-mcp', label: 'MCP 模式架构' },\n { id: 'arch-rest', label: 'functioncalling 模式架构' },\n { id: 'transport', label: '传输协议对比' },\n ]},\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'requirements', label: '系统要求' },\n { id: 'install', label: '安装方式' },\n { id: 'env-vars', label: '环境变量' },\n ]},\n { id: 'config', icon: Plug, label: '接入 · 直接用(零代码)', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: '扣子(Coze)' },\n { id: 'config-coze-workflow', label: 'Coze 工作流' },\n { id: 'config-ai-workspace', label: 'AI 工作台 · 豆包 / 飞书 / WorkBuddy / 钉钉' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: '其他 MCP 客户端' },\n ]},\n { id: 'config-code', icon: Code2, label: '接入 · 编程用 MCP(SDK / 代码)', children: [\n { id: 'code-mcp-sdk', label: '① 官方 MCP 客户端库' },\n { id: 'code-agent-frameworks', label: '② LangChain / Mastra / Agents SDK' },\n { id: 'code-function-calling', label: '③ 直调 chat.completions · 动态 tools' },\n { id: 'code-selfhosted-agent', label: '④ 自研后端 Agent' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM(智谱)' },\n { id: 'llm-kimi', label: 'KIMI(Moonshot)' },\n { id: 'llm-doubao', label: '豆包 Seed 2.0(火山方舟)' },\n { id: 'llm-openai', label: 'OpenAI(GPT-5.1)' },\n { id: 'llm-claude', label: 'Claude(Anthropic)' },\n { id: 'llm-gemini', label: 'Gemini(Google)' },\n { id: 'llm-comparison', label: '模型对比速查' },\n ]},\n { id: 'config-rest', icon: Network, label: '接入 · 非 MCP 原生 REST(cvx_fc)', children: [\n { id: 'rest-overview', label: 'functioncalling 模式总览' },\n { id: 'rest-catalog', label: '函数目录 · GET /v1/functions' },\n { id: 'rest-oneshot', label: '一次性文件评测' },\n { id: 'rest-streaming', label: '流式评测 · 三步骤' },\n { id: 'rest-examples', label: 'JS / Android 完整示例' },\n { id: 'rest-resume', label: '断线恢复 · 兜底拉取' },\n ]},\n { id: 'eval-modes', icon: Radio, label: '评测模式', children: [\n { id: 'stream-eval', label: '实时录音评测' },\n { id: 'stream-flow', label: '流式评测流程' },\n { id: 'file-eval', label: '音频文件评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'tools-en', label: '英文评测工具' },\n { id: 'tools-cn', label: '中文评测工具' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n { id: 'service-info', icon: Globe, label: '服务信息', children: [\n { id: 'endpoints', label: '服务端点' },\n { id: 'limits', label: '服务限制' },\n { id: 'changelog', label: '更新日志' },\n ]},\n];\n\nconst NAV_EN: NavGroup[] = [\n { id: 'concept', icon: Workflow, label: 'How MCP works', children: [\n { id: 'why-mcp', label: 'Why MCP' },\n { id: 'scenario-walkthrough', label: 'Scenario · 6 steps' },\n { id: 'dev-responsibility', label: 'Two developer tasks' },\n { id: 'integration-paths', label: 'Three integration styles' },\n ]},\n { id: 'architecture', icon: Terminal, label: 'Architecture', children: [\n { id: 'arch-overview', label: 'Two integration modes' },\n { id: 'arch-mcp', label: 'MCP mode architecture' },\n { id: 'arch-rest', label: 'Function-calling architecture' },\n { id: 'transport', label: 'Transport comparison' },\n ]},\n { id: 'quick-start', icon: Zap, label: 'Quick start', children: [\n { id: 'overview', label: 'Overview' },\n { id: 'requirements', label: 'Requirements' },\n { id: 'install', label: 'Install' },\n { id: 'env-vars', label: 'Environment' },\n ]},\n { id: 'config', icon: Plug, label: 'Zero-code clients', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: 'Coze' },\n { id: 'config-coze-workflow', label: 'Coze workflows' },\n { id: 'config-ai-workspace', label: 'Enterprise AI workspaces' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: 'Other MCP clients' },\n ]},\n { id: 'config-code', icon: Code2, label: 'MCP · SDK / code integration', children: [\n { id: 'code-mcp-sdk', label: '① Official MCP SDK' },\n { id: 'code-agent-frameworks', label: '② Agent frameworks' },\n { id: 'code-function-calling', label: '③ chat.completions · dynamic tools' },\n { id: 'code-selfhosted-agent', label: '④ Custom backend agent' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM (Zhipu)' },\n { id: 'llm-kimi', label: 'KIMI (Moonshot)' },\n { id: 'llm-doubao', label: 'Doubao Seed 2.0 (Volcano Ark)' },\n { id: 'llm-qwen', label: 'Qwen (Alibaba)' },\n { id: 'llm-openai', label: 'OpenAI (GPT-5.1)' },\n { id: 'llm-claude', label: 'Claude (Anthropic)' },\n { id: 'llm-gemini', label: 'Gemini (Google)' },\n { id: 'llm-comparison', label: 'Model comparison' },\n ]},\n { id: 'config-rest', icon: Network, label: 'Non-MCP REST (cvx_fc)', children: [\n { id: 'rest-overview', label: 'Function-calling overview' },\n { id: 'rest-catalog', label: 'Function catalog · GET /v1/functions' },\n { id: 'rest-oneshot', label: 'One-shot file eval' },\n { id: 'rest-streaming', label: 'Streaming · 3 steps' },\n { id: 'rest-examples', label: 'JS / Android samples' },\n { id: 'rest-resume', label: 'Resume & fallback' },\n ]},\n { id: 'eval-modes', icon: Radio, label: 'Evaluation modes', children: [\n { id: 'stream-eval', label: 'Streaming mic' },\n { id: 'stream-flow', label: 'Streaming flow' },\n { id: 'file-eval', label: 'Audio files' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API reference', children: [\n { id: 'tools-en', label: 'English tools' },\n { id: 'tools-cn', label: 'Chinese tools' },\n { id: 'result-fields', label: 'Result fields' },\n { id: 'error-codes', label: 'Errors' },\n ]},\n { id: 'best-practices', icon: FileText, label: 'Best practices', children: [\n { id: 'prompt-templates', label: 'Prompt templates' },\n { id: 'error-handling', label: 'Errors & retries' },\n { id: 'performance', label: 'Performance' },\n ]},\n { id: 'service-info', icon: Globe, label: 'Service info', children: [\n { id: 'endpoints', label: 'Endpoints' },\n { id: 'limits', label: 'Limits' },\n { id: 'changelog', label: 'Changelog' },\n ]},\n];\n\n/* ─── Page(英文 / 中文拆成两个组件,避免切换语言时 Hooks 数量变化导致白屏)─── */\n\nfunction DocsPageEn() {\n return (\n <DocsShell\n nav={NAV_EN}\n backLabel=\"Back to Home\"\n title=\"Developer documentation\"\n subtitle=\"Chivox Speech Evaluation MCP · chivox-local-mcp\"\n >\n <DocsEnglishBody />\n </DocsShell>\n );\n}\n\nfunction DocsPageZh() {\n return (\n <DocsShell\n nav={NAV}\n backLabel=\"返回首页\"\n title=\"开发者文档\"\n subtitle=\"驰声语音评测 MCP · chivox-local-mcp\"\n >\n {/* ══════ MCP 工作原理 ══════ */}\n <DocSection id=\"concept\" icon={Workflow} title=\"MCP 工作原理\">\n\n <SubDoc id=\"why-mcp\" title=\"为什么需要 MCP\">\n <p>\n 大模型(GPT / Claude / Gemini / 通义千问 等)本质上是<strong>文字大脑</strong>:它擅长读和写,但天生<strong>听不到</strong>音频、<strong>打不开</strong>你的数据库、<strong>调不通</strong>第三方 API。\n </p>\n <p>\n <strong>MCP(Model Context Protocol)</strong>是一套由 Anthropic 主导、业界共同推进的开放协议,它像一根\"对讲机\":让大模型可以在需要时主动去调用外部能力(工具、数据源、服务),然后把结果接回来继续对话。\n </p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <AlertTriangle className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n <span className=\"text-sm font-semibold\">没有 MCP 的做法</span>\n </div>\n <ul className=\"text-xs text-muted-foreground space-y-1.5 leading-relaxed\">\n <li>· 研究评测服务的 REST 文档</li>\n <li>· 自己写鉴权 / 上传 / 轮询代码</li>\n <li>· 把结果序列化后塞进 prompt</li>\n <li>· 每换一个大模型都得重写一遍</li>\n </ul>\n </div>\n <div className=\"rounded-lg border-2 border-foreground/60 p-4 bg-foreground/[0.02]\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Check className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">用驰声 MCP 之后</span>\n </div>\n <ul className=\"text-xs space-y-1.5 leading-relaxed\">\n <li>· 填一行 MCP Server 地址</li>\n <li>· 大模型自动发现&amp;调用评测工具</li>\n <li>· 换任何支持 MCP 的大模型都能用</li>\n <li>· 不写一行调用代码</li>\n </ul>\n </div>\n </div>\n <Callout type=\"info\">\n MCP 让\"接入语音评测\"从一个\"后端工程\"变成了一个\"配置项\"。这也是为什么 Cursor、Claude Desktop、Dify、Coze、字节豆包、飞书智能助手都已经原生支持 MCP。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"scenario-walkthrough\" title=\"场景走查 · 从一段录音到温柔反馈\">\n <p>\n 想象一个具体场景:一位开发者用 Claude 做了一款 <strong>「AI 雅思口语陪练」小程序</strong>,接入驰声语音评测 MCP。当用户发来一段录音,整个链路是这样的:\n </p>\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-5 my-4 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong className=\"text-foreground\">配置阶段</strong>(开发者 · 一次性)</p>\n <p className=\"text-muted-foreground mt-1\">在 AI 后端的配置文件里填一行驰声 MCP Server 地址。大模型启动时自动发现:「我多了一个叫<strong>语音评测</strong>的工具」。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong className=\"text-foreground\">接收用户指令</strong>(终端用户)</p>\n <p className=\"text-muted-foreground mt-1\">小程序用户发来一段自我介绍录音:「帮我听听,发音哪里有问题?」后端把「文字 + 录音链接」打包给大模型。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong className=\"text-foreground\">大模型自主思考</strong>(LLM · 最核心的魔法)</p>\n <div className=\"text-muted-foreground mt-1 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed\">\n 「用户让我听发音。」<br />\n 「但我只是文字大脑,我听不到。」<br />\n 「等等 —— 开发者给了我一个叫 <code className=\"bg-muted px-1 rounded\">chivox_voice_eval</code> 的 MCP 工具!」\n </div>\n </FlowStep>\n <FlowStep title=\"4\">\n <p><strong className=\"text-foreground\">MCP 协议 · 自动调用</strong>(LLM → 驰声)</p>\n <p className=\"text-muted-foreground mt-1\">大模型自动按 MCP 标准构造 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools/call</code> 请求,把录音链接和参考文本发到驰声服务。<span className=\"text-foreground\">开发者不需要写任何调用代码。</span></p>\n </FlowStep>\n <FlowStep title=\"5\">\n <p><strong className=\"text-foreground\">驰声服务处理</strong>(评测引擎)</p>\n <p className=\"text-muted-foreground mt-1\">驰声对音频进行考试级打分,把结构化结果通过 MCP 原路返回:<code className=\"bg-muted px-1 rounded text-xs font-mono\">overall 85</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">/θ/ 音发音错误</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">流利度 88</code>。</p>\n </FlowStep>\n <FlowStep title=\"6\">\n <p><strong className=\"text-foreground\">大模型整合并输出反馈</strong>(LLM)</p>\n <p className=\"text-muted-foreground mt-1\">大模型把冷冰冰的分数翻译成温柔反馈:</p>\n <div className=\"text-foreground/80 mt-2 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed italic\">\n 「你的自我介绍很棒!综合 85 分 🎉 不过 <strong>think</strong> 这个词的 /θ/ 咬舌音不太标准,我们一起来练一下……」\n </div>\n </FlowStep>\n </div>\n <Callout type=\"tip\">\n <strong>整条链路里,MCP 只出现在 ① 和 ④</strong>:① 让开发者一次性告诉大模型「你多了一个工具」,④ 让大模型可以自动调用它。这两步的标准化,就是 MCP 的全部价值。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"dev-responsibility\" title=\"开发者只做两件事\">\n <p>听起来复杂,但对开发者来说只需要两件事:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">1</span>\n <span className=\"text-sm font-semibold\">填一次 MCP 配置</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 在 AI 后端的配置里填入驰声 MCP Server 地址和 API Key,就这一次,以后新加评测能力完全免开发。\n </p>\n <CodeBlock filename=\"mcp-config.json\" lang=\"json\">{`{\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://mcp.cloud.chivox.com\",\n \"apiKey\": \"sk-••••••••••\"\n }\n}`}</CodeBlock>\n </div>\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">2</span>\n <span className=\"text-sm font-semibold\">写一段 System Prompt</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 告诉大模型这个工具是做什么的、什么时候调用、结果怎么转成用户友好的反馈。\n </p>\n <CodeBlock filename=\"system-prompt.md\">{`你是一位英语口语教练。\n当用户发来录音时:\n1. 调用 chivox_voice_eval 评分\n2. 聚焦 <70 分的音素家族\n3. 用高情商话术给出练习建议`}</CodeBlock>\n </div>\n </div>\n <p className=\"text-muted-foreground\">\n 其余所有事情 —— 音频上传、鉴权、工具发现、调用时机、返回格式、结果整合 —— 都由 MCP 协议和大模型自己搞定。\n </p>\n </SubDoc>\n\n <SubDoc id=\"integration-paths\" title=\"三种典型接入姿势\">\n <p>根据你的技术栈,有三种等价的接入方式,底层都走完全相同的 MCP 协议:</p>\n\n {/* ── 姿势一:可视化 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Sparkles className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势一 · 可视化拖拽(扣子 / Coze / 飞书智能助手)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合产品经理、运营、不写后端代码的开发者。整个过程像在手机上装 App。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>找到入口</strong>:在扣子平台的 <strong>「插件 / 工具」</strong> 配置区,点击<strong>「添加 MCP 服务」</strong>。</li>\n <li><strong>填两个字段</strong>(这也是你未来会从驰声这边拿到的):\n <div className=\"mt-2 overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium w-32\">服务器地址</td><td className=\"py-2 px-3 font-mono text-xs\">https://mcp.cloud.chivox.com</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权密钥</td><td className=\"py-2 px-3 font-mono text-xs\">sk-•••••••••• <span className=\"text-muted-foreground ml-2\">(由驰声分配,用于计费与识别)</span></td></tr>\n </tbody>\n </table>\n </div>\n </li>\n <li><strong>平台自动发现能力</strong>:扣子会访问你填的地址,读取 MCP Server 自描述的\"说明书\"。瞬间明白:「这是个语音评测工具,需要传音频,会返发音分」。</li>\n <li><strong>右侧测试</strong>:在平台的聊天框里发一段语音,大模型就会自动调用工具。<strong>整个过程不需要写一行代码。</strong></li>\n </ol>\n </div>\n\n {/* ── 姿势二:代码框架 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Code2 className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势二 · 代码框架配置(Dify / LangChain / 自研后端)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合做独立 APP / 小程序、用 Dify 或 LangChain 写 Agent 的极客开发者。核心就是往配置文件里加一段 JSON。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>打开配置文件</strong>(项目根目录下的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp.json</code> 或框架指定的配置路径)。</li>\n <li><strong>在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code> 下增加一个节点</strong>:\n <CodeBlock filename=\"mcp.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://mcp.cloud.chivox.com\",\n \"env\": {\n \"API_KEY\": \"你的专属密钥\"\n }\n }\n }\n}`}</CodeBlock>\n </li>\n <li><strong>重启后端服务</strong>。大模型的底层调度器会在启动时自动连上驰声 MCP Server,把 16 种评测工具注册成<strong>随时待命</strong>的 tools。</li>\n <li><strong>触发调用</strong>:用户发来录音后,大模型看到 system prompt 的引导,自动选择合适的工具(单词/句子/段落/半开放)调用。</li>\n </ol>\n </div>\n\n {/* ── 姿势三:Function Calling 直连(分 A / B 两种做法) ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Terminal className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势三 · 原生 Function Calling(豆包 / DeepSeek / OpenAI 兼容 API)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合不用任何 Agent 框架、直接调用 LLM API 的后端开发者。豆包 1.5+ / DeepSeek / GPT / Qwen 等模型都走标准 <code className=\"bg-muted px-1 rounded text-xs font-mono\">chat.completions</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> 参数。这里又分 A / B 两种做法,<strong>B 才是生产推荐</strong>。\n </p>\n\n <Callout type=\"warning\">\n 常见疑问:<em>\"要不要手写一个 tool 定义?\"</em><br />\n — <strong>姿势一 / 二不用</strong>(Coze、Dify、火山方舟控制台、Claude Desktop 会自动从 MCP Server 拉工具列表)。<br />\n — <strong>姿势三看你选 A 还是 B</strong>:A 要手写,B 用 MCP 客户端库自动拉。\n </Callout>\n\n {/* ── 3-A:手写 tool schema ── */}\n <div className=\"mt-4 rounded-md border border-border/40 bg-muted/20 p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold\">3-A · 手写 tool schema(最快验证,不推荐生产)</p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20\">快速 Demo</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 自己写一段 JSON Schema 告诉豆包\"有个工具叫 xxx,参数长这样\",豆包返回 tool_call 后你再手动去调驰声 MCP。<strong>缺点</strong>:驰声有 16 个评测工具,你每加一个题型都要改代码。\n </p>\n <CodeBlock filename=\"3a_hardcode_tool.py\" lang=\"python\">{`import os, json\nfrom openai import OpenAI\n\nclient = OpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv('ARK_API_KEY'),\n)\n\n# ─── 手写一段 tool schema(不推荐:16 个工具就要写 16 段)───\ntools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英语单词发音评测。当用户让你评价英语发音并提供录音链接时调用。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"audio_url\": {\"type\": \"string\", \"description\": \"录音 URL\"},\n \"reference_text\": {\"type\": \"string\", \"description\": \"参考文本,如 Apple\"},\n },\n \"required\": [\"audio_url\", \"reference_text\"]\n }\n }\n}]\n\nresp = client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,拿到评测结果后温柔地反馈,不要直接报分数。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n)\n\nmsg = resp.choices[0].message\nif msg.tool_calls:\n args = json.loads(msg.tool_calls[0].function.arguments)\n # ↓↓↓ 这里手动调驰声 MCP,再把结果回填给豆包做第二趟 ↓↓↓\n # result = requests.post(\"https://mcp.cloud.chivox.com\", json={...})`}</CodeBlock>\n </div>\n\n {/* ── 3-B:MCP 自动发现 ── */}\n <div className=\"mt-3 rounded-md border border-foreground/30 bg-foreground/[0.03] dark:bg-white/[0.03] p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold flex items-center gap-1.5\">\n 3-B · 用 MCP 客户端库自动发现 <Sparkles className=\"h-3 w-3 text-foreground\" />\n </p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-foreground text-background\">生产推荐</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 启动时连上驰声 MCP Server,调一次 <code className=\"bg-muted px-1 rounded\">tools/list</code>,把 16 个评测工具自动转成 Function Calling 的 tools 格式。<strong>驰声加新工具,你代码不用改</strong>。这才是 MCP 协议的真正价值。\n </p>\n <CodeBlock filename=\"3b_mcp_autodiscover.py\" lang=\"python\">{`import asyncio, os\nfrom openai import AsyncOpenAI\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\n\nclient = AsyncOpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv(\"ARK_API_KEY\"),\n)\n\nasync def main():\n # ─── 1. 连驰声 MCP Server,自动拉取全部 16 个工具 ───\n async with streamablehttp_client(\n \"https://mcp.cloud.chivox.com\",\n headers={\"Authorization\": f\"Bearer {os.getenv('CHIVOX_KEY')}\"},\n ) as (read, write, _):\n async with ClientSession(read, write) as session:\n await session.initialize()\n mcp_tools = (await session.list_tools()).tools # ← 一行拿到全部工具\n\n # ─── 2. MCP schema → OpenAI Function Calling schema ───\n tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema, # MCP 直接给 JSON Schema\n }\n } for t in mcp_tools]\n\n # ─── 3. 正常调豆包,tools 由 MCP 动态注入 ───\n resp = await client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,收到评测结果后温柔反馈。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n )\n\n # ─── 4. 折返跑:豆包选好工具后,直接走 MCP 调驰声 ───\n msg = resp.choices[0].message\n if msg.tool_calls:\n call = msg.tool_calls[0]\n result = await session.call_tool(\n call.function.name,\n arguments=__import__(\"json\").loads(call.function.arguments),\n )\n # 把 result 回填给豆包做第二趟 chat.completions 即可\n\nasyncio.run(main())`}</CodeBlock>\n <p className=\"text-[11px] text-muted-foreground mt-3 leading-relaxed\">\n 想要更省心?直接用 <a href=\"https://github.com/modelcontextprotocol/python-sdk\" target=\"_blank\" rel=\"noreferrer\" className=\"text-foreground underline underline-offset-2\">官方 mcp python sdk</a>、<code className=\"bg-muted px-1 rounded text-xs\">mcp-use</code>、<code className=\"bg-muted px-1 rounded text-xs\">openai-agents</code>,或火山官方 <code className=\"bg-muted px-1 rounded text-xs\">Arkitect</code> 高代码 SDK,它们都把上面这段\"MCP → Function Calling\"的桥接封装好了。\n </p>\n </div>\n </div>\n\n <Callout type=\"info\">\n 三种姿势走的是完全相同的 MCP 协议,能力完全等价。选型建议:<strong>产品经理 / 原型验证用姿势一</strong>(Coze 填表单);<strong>AI 客户端用户用姿势二</strong>(Cursor / Claude Desktop 填 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">mcp.json</code>);<strong>后端生产服务用姿势三-B</strong>(MCP 客户端库自动发现工具,不要手写 tool schema)。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 架构说明 ══════ */}\n <DocSection id=\"architecture\" icon={Terminal} title=\"架构说明\">\n\n <SubDoc id=\"arch-overview\" title=\"两种接入模式总览\">\n <p>驰声语音评测对外提供<strong>两条并行入口</strong>,底层指向同一个评测引擎,但面向不同的客户端环境,按能力选其一即可:</p>\n\n <div className=\"grid md:grid-cols-2 gap-4 my-5\">\n <div className=\"rounded-xl border-2 border-emerald-500/40 bg-emerald-500/5 p-5\">\n <p className=\"font-semibold mb-2 flex items-center gap-2\">\n <Plug className=\"h-4 w-4 text-emerald-600 dark:text-emerald-400\" />\n MCP 模式(推荐首选)\n </p>\n <ul className=\"text-xs space-y-1.5 text-muted-foreground\">\n <li>• JSON-RPC 2.0 · Streamable HTTP / stdio</li>\n <li>• 所有支持 MCP 的 IDE / 桌面 AI / Agent 框架 <strong className=\"text-foreground\">零代码即用</strong></li>\n <li>• 工具列表自动注入,新增题型<strong className=\"text-foreground\">业务侧不用改</strong></li>\n <li>• 本地代理 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">chivox-local-mcp</code> 支持麦克风实时录音</li>\n </ul>\n <p className=\"text-xs mt-2\"><strong>典型客户端</strong>:Cursor / Claude Desktop / Coze / Dify / LangChain / 自研后端 Agent</p>\n </div>\n <div className=\"rounded-xl border-2 border-sky-500/40 bg-sky-500/5 p-5\">\n <p className=\"font-semibold mb-2 flex items-center gap-2\">\n <Network className=\"h-4 w-4 text-sky-600 dark:text-sky-400\" />\n functioncalling 模式(非 MCP · cvx_fc)\n </p>\n <ul className=\"text-xs space-y-1.5 text-muted-foreground\">\n <li>• OpenAI function-calling 风格 · 纯 REST + WebSocket</li>\n <li>• <strong className=\"text-foreground\">不依赖任何 MCP SDK</strong>,普通 HTTP 客户端即可打</li>\n <li>• 内置 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">resume_token</code> 断线续播、<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">intermediate</code> 中间结果、<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">backpressure</code> 反压</li>\n <li>• 为不能 / 不想引入 MCP 的客户端环境保留兜底通道</li>\n </ul>\n <p className=\"text-xs mt-2\"><strong>典型客户端</strong>:原生 Android / iOS / Flutter、微信 / 支付宝小程序、老版本 Java / PHP 后端</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">怎么选?</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">你的客户端环境</th>\n <th className=\"text-left py-2 px-3 font-medium\">建议</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3\">Cursor / Claude Desktop / 各类 AI IDE 能装 MCP</td><td className=\"py-2 px-3 text-emerald-600 dark:text-emerald-400\">MCP 模式</td></tr>\n <tr><td className=\"py-2 px-3\">Python / Node / Java 后端能引入 MCP SDK</td><td className=\"py-2 px-3 text-emerald-600 dark:text-emerald-400\">MCP 模式</td></tr>\n <tr><td className=\"py-2 px-3\">Coze / Dify / n8n 等可视化平台</td><td className=\"py-2 px-3 text-emerald-600 dark:text-emerald-400\">MCP 模式</td></tr>\n <tr><td className=\"py-2 px-3\">原生 Android / iOS App,想直接打 HTTP + WS</td><td className=\"py-2 px-3 text-sky-600 dark:text-sky-400\">functioncalling 模式</td></tr>\n <tr><td className=\"py-2 px-3\">微信 / 支付宝 / 抖音小程序</td><td className=\"py-2 px-3 text-sky-600 dark:text-sky-400\">functioncalling 模式</td></tr>\n <tr><td className=\"py-2 px-3\">老系统 / 封闭运行时,不想引入 MCP 依赖</td><td className=\"py-2 px-3 text-sky-600 dark:text-sky-400\">functioncalling 模式</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"warning\">\n 同一个会话<strong>二选一、不要混用</strong>:MCP 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">/ws/audio/</code> 和 functioncalling 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">/ws/eval/</code> 属于不同的会话 ID 空间。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"arch-mcp\" title=\"MCP 模式 · 数据流与组件\">\n <p>MCP 模式下有两种部署形态,底层均走 JSON-RPC 2.0,区别只在客户端离服务端有没有一层本地代理:</p>\n\n <p className=\"font-semibold mt-4\">形态 A · 远程直连(零代码)</p>\n <p className=\"text-sm text-muted-foreground\">适合 Cursor / Coze / Dify / n8n 等支持 Streamable HTTP 的客户端,<strong>无本地依赖</strong>。</p>\n <div className=\"flex flex-wrap items-center justify-center gap-4 my-4\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-5 py-3 text-center min-w-[130px]\">\n <p className=\"text-xs font-semibold mb-0.5\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Cursor / Coze /<br/>Dify / n8n …</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">Streamable HTTP<br/>JSON-RPC 2.0</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-5 py-3 text-center min-w-[130px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-0.5\">驰声 MCP Server</p>\n <p className=\"text-[11px] text-muted-foreground\">/mcp · /ws/audio<br/>评测引擎</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">形态 B · 本地代理(支持麦克风实时录音)</p>\n <p className=\"text-sm text-muted-foreground\">适合 Claude Desktop / Claude Code 等以 stdio 挂载 MCP 的客户端,需要本地实时录音时走这条路径。</p>\n <div className=\"flex flex-wrap items-center justify-center gap-3 my-4\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-4 py-3 text-center min-w-[120px]\">\n <p className=\"text-xs font-semibold mb-0.5\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Claude Desktop /<br/>Claude Code</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">stdio</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-4 py-3 text-center min-w-[120px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-0.5\">chivox-local-mcp</p>\n <p className=\"text-[11px] text-muted-foreground\">本地代理 · SoX 录音</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP / WS</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-4 py-3 text-center min-w-[120px]\">\n <p className=\"text-xs font-semibold mb-0.5\">Remote Chivox</p>\n <p className=\"text-[11px] text-muted-foreground\">MCP Server<br/>评测引擎</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">标准数据流</p>\n <ol className=\"list-decimal list-inside space-y-1.5 text-sm ml-1\">\n <li><code className=\"bg-muted px-1 rounded text-xs\">initialize</code> → 协商协议版本与能力</li>\n <li><code className=\"bg-muted px-1 rounded text-xs\">tools/list</code> → 拉取 16 个评测工具的 JSON Schema</li>\n <li><strong>文件模式</strong>:<code className=\"bg-muted px-1 rounded text-xs\">tools/call</code> → 服务端执行评测 → 返回 JSON-RPC 响应</li>\n <li><strong>实时模式</strong>:<code className=\"bg-muted px-1 rounded text-xs\">create_stream_session</code> → 走 <code className=\"bg-muted px-1 rounded text-xs\">/ws/audio/{'{session_id}'}</code> 推流 → <code className=\"bg-muted px-1 rounded text-xs\">stream_eval_result</code> 收结构化评分</li>\n </ol>\n\n <p className=\"font-semibold mt-6\">原始 JSON-RPC 请求示例(自建客户端可直接抄)</p>\n <p className=\"text-xs text-muted-foreground mb-3\">\n 如果你不使用官方 MCP SDK 而是直接通过 <code className=\"bg-muted px-1 rounded\">POST /mcp</code> 自己拼 JSON-RPC 2.0 报文,最小可用的三条请求如下。所有请求均需携带 <code className=\"bg-muted px-1 rounded text-xs\">Authorization: Bearer &lt;your_api_key&gt;</code>。\n </p>\n\n <p className=\"text-sm font-semibold mt-4\">① 初始化 · <code>initialize</code></p>\n <CodeBlock filename=\"initialize.json\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"initialize\",\n \"params\": {\n \"protocolVersion\": \"2024-11-05\",\n \"capabilities\": {},\n \"clientInfo\": { \"name\": \"my-client\", \"version\": \"1.0.0\" }\n }\n}`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">成功后服务端返回协议版本、服务端能力、服务端信息,此后方可调用其他方法。</p>\n\n <p className=\"text-sm font-semibold mt-4\">② 拉取工具清单 · <code>tools/list</code></p>\n <CodeBlock filename=\"tools-list.json\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"id\": 2,\n \"method\": \"tools/list\",\n \"params\": {}\n}`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">返回全部 16 个评测工具(英 10 + 中 6)+ 2 个流式工具,含名字、描述、参数 JSON Schema。客户端应缓存此响应,后续按需展示 / 桥接给 LLM。</p>\n\n <p className=\"text-sm font-semibold mt-4\">③ 调用工具 · <code>tools/call</code></p>\n <CodeBlock filename=\"tools-call.json\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"id\": 3,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"en_word_eval\",\n \"arguments\": {\n \"ref_text\": \"hello\",\n \"audio_base64\": \"<base64 encoded audio>\",\n \"accent\": 2,\n \"rank\": 100\n }\n }\n}`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">\n 成功响应放在 <code className=\"bg-muted px-1 rounded\">result.content[0].text</code>(JSON 字符串形式,内部就是评测结果结构);失败则返回 <code className=\"bg-muted px-1 rounded\">error.code</code> + <code className=\"bg-muted px-1 rounded\">error.message</code>。\n </p>\n\n <Callout type=\"tip\">\n 用 Python / TypeScript 官方 MCP SDK 的用户<strong>不需要关心以上 JSON-RPC 细节</strong>——SDK 已经封装好 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">session.initialize()</code> / <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">session.list_tools()</code> / <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">session.call_tool()</code>。这里仅供裸协议实现或排障时参考。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"arch-rest\" title=\"functioncalling 模式 · 数据流与组件\">\n <p>没有代理、没有 SDK,客户端 ↔ 服务端只有两条链路:一条 REST 管会话 / 文件评测,一条 WebSocket 推音频帧。</p>\n\n <div className=\"flex flex-wrap items-center justify-center gap-3 my-5\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-4 py-3 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-0.5\">客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">原生 App / 小程序 /<br/>老版后端 / 纯前端</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP REST<br/>/v1/call · /v1/functions</p>\n </div>\n <div className=\"rounded-xl border-2 border-sky-500/50 bg-sky-500/5 px-4 py-3 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold text-sky-600 dark:text-sky-400 mb-0.5\">cvx_fc 服务</p>\n <p className=\"text-[11px] text-muted-foreground\">function-calling 网关<br/>评测引擎</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">WebSocket<br/>/ws/eval/&#123;id&#125;</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-4 py-3 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-0.5\">客户端推流</p>\n <p className=\"text-[11px] text-muted-foreground\">麦克风 / 分片缓冲</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">三条典型数据流</p>\n <div className=\"space-y-3 my-3\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-sm font-semibold mb-1.5\">a. 一次性文件评测</p>\n <p className=\"text-xs text-muted-foreground\">POST <code className=\"bg-muted px-1 rounded\">/v1/call</code>(<code className=\"bg-muted px-1 rounded\">audio_url</code> 或 <code className=\"bg-muted px-1 rounded\">audio_base64</code>) → 同步返回评分</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-sm font-semibold mb-1.5\">b. 流式实时评测</p>\n <p className=\"text-xs text-muted-foreground\">POST <code className=\"bg-muted px-1 rounded\">/v1/call</code> → <code className=\"bg-muted px-1 rounded\">start_stream_eval</code> → 拿 <code className=\"bg-muted px-1 rounded\">ws_url</code> → 连 <code className=\"bg-muted px-1 rounded\">/ws/eval/&#123;id&#125;</code> → 推 PCM → 收 <code className=\"bg-muted px-1 rounded\">intermediate</code> / <code className=\"bg-muted px-1 rounded\">backpressure</code> → 发 <code className=\"bg-muted px-1 rounded\">stop</code> → 收 <code className=\"bg-muted px-1 rounded\">result</code></p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-sm font-semibold mb-1.5\">c. 断线恢复 + 兜底拉取</p>\n <p className=\"text-xs text-muted-foreground\">网络断开 60 秒内 → 用 <code className=\"bg-muted px-1 rounded\">resume_token</code> 重连续播;彻底失败 → POST <code className=\"bg-muted px-1 rounded\">/v1/call</code> → <code className=\"bg-muted px-1 rounded\">get_stream_result</code> 兜底</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">特色能力</p>\n <ul className=\"list-disc list-inside space-y-1 text-sm ml-1\">\n <li><strong>resume_token</strong> — 网络抖动 60 秒内可无缝续播,服务端挂起会话状态</li>\n <li><strong>intermediate 帧</strong> — 边推边吐部分结果,前端可做实时波形 / 分数涂色</li>\n <li><strong>backpressure 反馈</strong> — 服务端主动下发 <code className=\"bg-muted px-1 rounded text-xs\">suggested_interval_ms</code>,客户端按建议降速</li>\n <li><strong>结构化错误码</strong> — <code className=\"bg-muted px-1 rounded text-xs\">SESSION_EXPIRED</code> / <code className=\"bg-muted px-1 rounded text-xs\">INVALID_STATE</code> / … 便于客户端写重试逻辑</li>\n </ul>\n <p className=\"text-xs text-muted-foreground mt-3\">详细的接入示例见下文 <a href=\"#config-rest\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">「接入 · 非 MCP 原生 REST」</a> 大节。</p>\n </SubDoc>\n\n <SubDoc id=\"transport\" title=\"传输协议对比\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">维度</th>\n <th className=\"text-left py-2 px-3 font-medium\">MCP 模式</th>\n <th className=\"text-left py-2 px-3 font-medium\">functioncalling(cvx_fc)</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">协议</td><td className=\"py-2 px-3\">JSON-RPC 2.0</td><td className=\"py-2 px-3\">REST + WebSocket</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">入口路径</td><td className=\"py-2 px-3 font-mono text-xs\">POST /mcp</td><td className=\"py-2 px-3 font-mono text-xs\">POST /v1/call</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">列出工具</td><td className=\"py-2 px-3 font-mono text-xs\">tools/list</td><td className=\"py-2 px-3 font-mono text-xs\">GET /v1/functions</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">调用工具</td><td className=\"py-2 px-3 font-mono text-xs\">tools/call</td><td className=\"py-2 px-3 font-mono text-xs\">POST /v1/call + name</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">流式音频 WS</td><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/audio/{session_id}'}</td><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/eval/{session_id}'}</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">本地 stdio 代理</td><td className=\"py-2 px-3\">支持(<code className=\"bg-muted px-1 rounded text-[10px]\">chivox-local-mcp</code>)</td><td className=\"py-2 px-3 text-muted-foreground\">不适用</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">断线重连</td><td className=\"py-2 px-3\">客户端 SDK 自动重连</td><td className=\"py-2 px-3\">resume_token(60s 挂起)</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">中间结果</td><td className=\"py-2 px-3\">stream_eval 回调</td><td className=\"py-2 px-3\">intermediate 帧</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">背压反馈</td><td className=\"py-2 px-3 text-muted-foreground\">—</td><td className=\"py-2 px-3\">backpressure 帧</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">客户端依赖</td><td className=\"py-2 px-3\">MCP SDK(官方 / 框架)</td><td className=\"py-2 px-3\">仅 HTTP + WS 客户端</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-local-mcp</strong> 是驰声语音评测的 MCP 本地代理,通过 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 将远程驰声评测服务桥接到本地,让 AI 助手(Claude Desktop、Claude Code、Cursor 等)直接调用语音评测能力。</p>\n <p>它支持两种评测模式,覆盖从即时交互到批量处理的全部场景:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Mic className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">实时录音评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">通过麦克风边录边评,音频以 WebSocket 流式推送,无需生成中间文件,延迟更低、体验更佳。</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <FolderOpen className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">音频文件评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">支持本地文件路径、Base64 编码、URL 三种输入,适合已有音频的批量评测场景。</p>\n </div>\n </div>\n <Callout type=\"info\">当前支持 <strong>16 种评测工具</strong>:英文 10 种(单词、句子、段落、纠音、自然拼读、口语选择、半开放题等)+ 中文 6 种(汉字、拼音、词句、段落、有限分支、AI Talk)。</Callout>\n </SubDoc>\n\n <SubDoc id=\"requirements\" title=\"系统要求\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">依赖</th>\n <th className=\"text-left py-2 px-3 font-medium\">版本</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-semibold\">Node.js</td><td className=\"py-2 px-3 font-mono text-xs\">{'>'}= 18</td><td className=\"py-2 px-3 text-muted-foreground\">运行时必须</td></tr>\n <tr><td className=\"py-2 px-3 font-semibold\">SoX</td><td className=\"py-2 px-3 font-mono text-xs\">any</td><td className=\"py-2 px-3 text-muted-foreground\">仅实时录音功能需要(处理麦克风音频流)</td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"mt-3\">安装 SoX(仅在使用实时录音时需要):</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# macOS\nbrew install sox\n\n# Ubuntu / Debian\nsudo apt-get install sox`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"install\" title=\"安装方式\">\n <p><strong>方式一:npm 全局安装</strong>(推荐)</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`npm install -g chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式二:npx 免安装运行</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`MCP_REMOTE_URL=https://fc.cloud.chivox.com npx chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式三:从源码构建</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`git clone https://git.chivox.com/CLOUD_DEV/cvx_local_mcp.git\ncd cvx_local_mcp\nbash scripts/build.sh`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"env-vars\" title=\"环境变量\">\n <ParamTable params={[\n { name: 'MCP_REMOTE_URL', type: 'string', required: true, desc: '远程驰声 MCP 服务地址,如 https://fc.cloud.chivox.com' },\n { name: 'MCP_API_KEY', type: 'string', required: false, desc: 'API 认证密钥(如已配置鉴权则必填)' },\n ]} />\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 直接用(零代码) ══════ */}\n <DocSection id=\"config\" icon={Plug} title=\"接入 · 直接用 MCP(零代码)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>不用写代码、不用装 SDK,只在客户端的<strong className=\"text-foreground\">「MCP 配置」</strong>或<strong className=\"text-foreground\">「扩展」</strong>里填一段 JSON / URL</li>\n <li>保存后 LLM 自动发现 16 个评测工具,并在对话 / Agent 中自主调用</li>\n <li>覆盖 <strong className=\"text-foreground\">IDE 编程助手</strong>、<strong className=\"text-foreground\">桌面 AI 客户端</strong>、<strong className=\"text-foreground\">企业 AI 工作台</strong>、<strong className=\"text-foreground\">可视化 Bot / 工作流平台</strong>四大生态</li>\n </ul>\n </div>\n\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置并保存:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://mcp.cloud.chivox.com\"\n}`}</CodeBlock>\n <Callout type=\"tip\">Cursor 使用远程 Streamable HTTP 模式直连,无需安装本地代理。如需实时录音功能,请使用下方 Claude Desktop 的本地代理配置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-desktop\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件(macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"chivox-local-mcp\",\n \"env\": {\n \"MCP_REMOTE_URL\": \"https://fc.cloud.chivox.com\",\n \"MCP_API_KEY\": \"your-api-key\"\n }\n }\n }\n}`}</CodeBlock>\n <p>如果是从源码构建:</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/cvx_local_mcp/dist/index.js\"],\n \"env\": {\n \"MCP_REMOTE_URL\": \"https://fc.cloud.chivox.com\"\n }\n }\n }\n}`}</CodeBlock>\n <Callout type=\"info\">Claude Desktop 使用本地代理模式(stdio),支持实时录音评测。本地代理会自动桥接远程服务。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-code\" title=\"Claude Code 配置\">\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# npm 全局安装后\nclaude mcp add chivox -- env MCP_REMOTE_URL=https://fc.cloud.chivox.com chivox-local-mcp\n\n# 源码构建后\nclaude mcp add chivox -- env MCP_REMOTE_URL=https://fc.cloud.chivox.com node /path/to/cvx_local_mcp/dist/index.js`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-ai-ide\" title=\"Windsurf / Zed / Continue 等 IDE 编程助手\">\n <p>除了 Cursor 和 Claude Code,主流的 AI 编程 IDE 都原生支持 MCP。以下客户端使用<strong>完全一致</strong>的 Streamable HTTP 配置,区别只在配置入口:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">客户端</th>\n <th className=\"text-left py-2 px-3 font-medium\">配置入口</th>\n <th className=\"text-left py-2 px-3 font-medium\">备注</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">Windsurf (Codeium)</td><td className=\"py-2 px-3 text-xs\">Settings → Cascade → MCP Servers</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">直接贴下方 JSON</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Zed</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">~/.config/zed/settings.json</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">context_servers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">Assistant Panel 自动发现</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Continue (VS Code / JetBrains)</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">config.yaml</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">v0.9+ 支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">通义灵码 / CodeGeeX / 文心快码</td><td className=\"py-2 px-3 text-xs\">插件设置 → MCP 服务</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">2026 Q1 起陆续支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Cline (原 Claude Dev)</td><td className=\"py-2 px-3 text-xs\">MCP Servers 面板 → 编辑配置</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">VS Code 扩展</td></tr>\n </tbody>\n </table>\n </div>\n <CodeBlock filename=\"通用 MCP 配置(复制即用)\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox-speech-eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://mcp.cloud.chivox.com\"\n }\n }\n}`}</CodeBlock>\n <Callout type=\"tip\">所有 IDE 类客户端都走远程 Streamable HTTP,零本地依赖。需要实时录音才需切到 Claude Desktop 的本地代理模式。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-coze\" title=\"扣子(Coze)配置\">\n <p><a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子(Coze)</a>是字节跳动推出的 AI 应用开发平台,支持通过 MCP 扩展为 Agent 赋予外部工具能力。接入驰声语音评测后,你可以构建口语练习 Bot、发音诊断助手等应用。</p>\n <p className=\"font-semibold mt-4\">方式一:扣子空间 MCP 扩展(推荐)</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 <a href=\"https://space.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子空间</a>(space.coze.cn)</li>\n <li>在对话框下方点击 <strong>「添加扩展」</strong></li>\n <li>选择 <strong>「自定义」</strong> 标签,点击添加新的 MCP 服务</li>\n <li>填入以下配置信息:</li>\n </ol>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">名称</td><td className=\"py-2 px-3 font-mono text-xs\">chivox-speech-eval</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://mcp.cloud.chivox.com</td></tr>\n </tbody>\n </table>\n </div>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>保存后,在对话中输入 <strong>「帮我评测这段英文发音」</strong> 即可触发 Agent 调用</li>\n </ol>\n <Callout type=\"tip\">添加 MCP 扩展后,扣子空间的 Agent 会自动识别并调用评测工具。你可以在 Agent 的思考过程中看到具体的工具调用细节。</Callout>\n\n <p className=\"font-semibold mt-6\">方式二:在 Coze 开发平台的 Bot 中使用</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>进入 <a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Coze 开发平台</a>,创建或编辑 Bot</li>\n <li>在 Bot 的 <strong>「插件」</strong> 或 <strong>「工具」</strong> 面板中,添加 MCP 工具</li>\n <li>选择 <strong>Streamable HTTP</strong> 类型,填入 URL:<code className=\"bg-muted px-1 rounded text-xs font-mono\">https://mcp.cloud.chivox.com</code></li>\n <li>Bot 将自动发现所有可用的评测工具(如 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_word</code> 等)</li>\n <li>在 Bot 的人设提示词中,添加语音评测相关的引导语</li>\n </ol>\n <CodeBlock filename=\"Bot 人设提示词示例\">{`你是一位专业的英语口语教练。当用户上传音频并请求评测时:\n1. 使用 evaluate_english_word / evaluate_english_sentence 等工具进行评测\n2. 根据评测结果(overall、accuracy、fluency 等分数)给出专业分析\n3. 针对得分较低的维度提供具体改进建议\n4. 鼓励用户持续练习`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze-workflow\" title=\"Coze 工作流接入\">\n <p>如果你需要在 Coze 工作流中集成语音评测能力,可以通过 <strong>HTTP Request 节点</strong>调用评测 API:</p>\n\n <p className=\"font-semibold mt-4\">步骤 1:上传音频</p>\n <p>在工作流中添加一个 HTTP Request 节点,配置如下:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">方法</td><td className=\"py-2 px-3 font-mono text-xs\">POST</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://your-audio-host.com/upload</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Content-Type</td><td className=\"py-2 px-3 font-mono text-xs\">audio/mp3</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Body</td><td className=\"py-2 px-3 text-muted-foreground\">音频文件二进制数据</td></tr>\n </tbody>\n </table>\n </div>\n <p>响应中获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{{audioId}}'}</code>,传递给下一步。</p>\n\n <p className=\"font-semibold mt-4\">步骤 2:调用评测</p>\n <p>添加第二个 HTTP Request 节点,通过 MCP 接口调用评测工具:</p>\n <CodeBlock filename=\"HTTP Request Body\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"evaluate_english_sentence\",\n \"arguments\": {\n \"audioId\": \"{{audioId}}\",\n \"sentence\": \"{{ref_text}}\"\n }\n },\n \"id\": 1\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">步骤 3:处理结果</p>\n <p>评测结果通过 JSON 格式返回,可使用 Coze 的 <strong>代码节点</strong> 或 <strong>LLM 节点</strong> 对结果进行解析和二次处理(如生成学习建议、计算进步趋势等)。</p>\n\n <Callout type=\"info\">工作流方式适合需要固定流程的场景(如批量评测、定时任务、与其他系统联动)。如果是交互式场景,推荐使用上方的 MCP 扩展方式,Agent 会自动选择合适的工具。</Callout>\n\n <div className=\"rounded-lg bg-muted/30 p-5 mt-4\">\n <p className=\"text-xs font-semibold mb-3\">完整工作流示意</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">用户输入音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 上传音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 调用评测</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM: 分析结果</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">输出反馈</span>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"config-ai-workspace\" title=\"AI 工作台 · 豆包 / 飞书智能伙伴 / WorkBuddy / 钉钉 AI\">\n <p>这一类是<strong>面向「上班族」的企业 / 日常 AI 助手</strong>,产品形态是个聊天框 + 插件市场 / 扩展面板。接入后,员工在 App 里打字「帮我评一下这段录音」就能自动触发驰声评测,不用写任何代码。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Bot className=\"h-4 w-4 text-amber-500\" /> 豆包(Doubao)App / 桌面版</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节跳动官方 AI 助手,2026 年起桌面版支持 MCP 扩展。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>桌面版设置 → <strong>「智能体扩展」</strong> → 添加 MCP 服务</li>\n <li>类型选 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">streamable-http</code>,URL 填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://mcp.cloud.chivox.com</code></li>\n <li>对话中直接说「帮我评测」即可自动调用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Building2 className=\"h-4 w-4 text-blue-500\" /> 飞书智能伙伴(My AI)</p>\n <p className=\"text-xs text-muted-foreground mb-2\">飞书企业 AI,支持以 MCP / Function Calling 挂载外部工具给团队 Bot。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>飞书开发者后台 → 创建应用 → <strong>「AI 能力 / 智能伙伴」</strong></li>\n <li>在「工具」中选 <strong>外部 MCP 服务</strong>,填入上方 URL</li>\n <li>把智能伙伴拉进群聊,@ 它提交音频即可评测</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-rose-500\" /> WorkBuddy / 企业智能助手</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节 WorkBuddy、腾讯会议 AI、企业微信智能助手等企业 AI 工作台场景。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>在工作台的<strong>「技能 / 扩展」</strong>后台新增一个 MCP 连接器</li>\n <li>传输协议选 Streamable HTTP,地址填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://mcp.cloud.chivox.com</code></li>\n <li>把技能发布到指定部门 / 群组,HR、培训、销售口语陪练场景即可直接用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Workflow className=\"h-4 w-4 text-cyan-500\" /> 钉钉 AI 助理 / 通义千问 App</p>\n <p className=\"text-xs text-muted-foreground mb-2\">阿里生态:钉钉 AI 助理支持自定义插件,通义 App 可通过「AI 工具箱」挂载 MCP。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>钉钉开发者后台 → AI 助理 → <strong>「插件开发 / MCP 接入」</strong></li>\n <li>选 HTTP Streamable,填入驰声 MCP URL 并开启鉴权(可选)</li>\n <li>在钉钉群 @AI 助理上传音频,自动返回评测卡片</li>\n </ol>\n </div>\n </div>\n\n <Callout type=\"tip\"><strong>典型场景 · 小龙虾们的共同剧本</strong>:销售口语陪练 → 外语培训 → 主持 / 播音岗位考核 → K12 英语作业批改 → 客服话术复盘。这些「上班族 AI 工作台」接入驰声 MCP 后,员工 / 学员只要对着话筒说话,AI 就能自动打分并给出个性化改进建议,运营 / 培训 / 教研团队零开发即可上线。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-workflow-platforms\" title=\"Dify / n8n / Flowise 等可视化编排\">\n <p>如果你需要把语音评测编排进<strong>多步业务流</strong>(例如:学员录音 → 评测 → 生成错题集 → 推送到 CRM),可视化工作流平台比聊天 Bot 更合适。</p>\n\n <div className=\"grid gap-3 md:grid-cols-3 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Dify</p>\n <p className=\"text-xs text-muted-foreground mb-2\">开源 LLMOps,v0.15+ 原生 MCP 工具。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>「工具」→「自定义 MCP」→ 填 URL</li>\n <li>在 Chatflow / Workflow 节点里直接拖用</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">n8n</p>\n <p className=\"text-xs text-muted-foreground mb-2\">工作流自动化,用 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">MCP Client</code> 节点。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>添加 <strong>MCP Client</strong> 节点</li>\n <li>Transport 选 HTTP Stream,URL 贴驰声地址</li>\n <li>Tool 下拉自动列出 16 个评测能力</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Flowise / LangFlow</p>\n <p className=\"text-xs text-muted-foreground mb-2\">可视化 LangChain Agent 编排。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>拖出 <strong>MCP Tool</strong> 节点</li>\n <li>连到 AgentExecutor 即可</li>\n </ul>\n </div>\n </div>\n\n <Callout type=\"info\">可视化平台的好处是<strong>流程可追溯、可回放</strong>,适合教育机构 / 培训部门做批量评测 + 报表导出;缺点是单步延迟略高于直接 MCP 调用,对延迟敏感的交互场景建议用 Cursor / Claude Desktop 直连。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"其他 MCP 客户端(通用配置)\">\n <p>任何遵循 MCP 协议的客户端都可以接入,统一使用以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://mcp.cloud.chivox.com</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权(可选)</td><td className=\"py-2 px-3 font-mono text-xs\">Header:<code>Authorization: Bearer &lt;MCP_API_KEY&gt;</code></td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">还没找到你用的客户端?任何支持 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 的终端都通用。找不到入口?下一小节「编程用 MCP」里用 SDK 直接接即可。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 编程用 MCP ══════ */}\n <DocSection id=\"config-code\" icon={Code2} title=\"接入 · 编程用 MCP(SDK / 代码集成)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Terminal className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>你在写一段<strong className=\"text-foreground\">真正的后端服务</strong>:一个 Agent Worker、一个 API、一个自动化脚本</li>\n <li>业务里的「语音评测」是其中一个步骤,需要由代码 <strong className=\"text-foreground\">决定何时调用</strong>、<strong className=\"text-foreground\">如何解析结果</strong></li>\n <li>四种主流写法:① 官方 MCP 客户端库 → ② Agent 框架自动桥接 → ③ 直调 chat.completions + tools → ④ 把 MCP Client 嵌进业务后端</li>\n <li>如果客户端环境<strong className=\"text-foreground\">不能引入 MCP SDK</strong>(原生 App / 小程序 / 老系统),请跳转下一大节 <a href=\"#config-rest\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">「接入 · 非 MCP 原生 REST(cvx_fc)」</a></li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 强烈推荐①或②:驰声有 16 个评测工具,<strong className=\"text-foreground\">让 MCP 自动注入 tool schema</strong>,每新增一个题型不用改一行业务代码。</p>\n </div>\n\n <SubDoc id=\"code-mcp-sdk\" title=\"① 官方 MCP 客户端库(Python / TypeScript)\">\n <p>最底层、也最推荐的方式。由 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Anthropic 官方维护</a>,一次连接自动拿到全部工具的 JSON Schema,然后交给任意 LLM。</p>\n\n <p className=\"font-semibold mt-4\">Python:<code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp</code></p>\n <CodeBlock filename=\"chivox_client.py\" lang=\"python\">{`# pip install mcp openai\nimport asyncio, os\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nMCP_URL = \"https://mcp.cloud.chivox.com\"\nllm = OpenAI(api_key=os.getenv(\"DOUBAO_KEY\"),\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\") # 豆包 / DeepSeek / OpenAI 同构\n\nasync def main(audio_id: str, ref: str):\n async with streamablehttp_client(MCP_URL) as (reader, writer, _):\n async with ClientSession(reader, writer) as sess:\n await sess.initialize()\n\n tools = await sess.list_tools()\n openai_tools = [{\n \"type\": \"function\",\n \"function\": {\"name\": t.name, \"description\": t.description,\n \"parameters\": t.inputSchema},\n } for t in tools.tools]\n\n resp = llm.chat.completions.create(\n model=\"doubao-1-5-pro-32k\",\n messages=[{\"role\": \"user\",\n \"content\": f\"帮我评测 audioId={audio_id},参考文本:{ref}\"}],\n tools=openai_tools,\n )\n\n for call in resp.choices[0].message.tool_calls or []:\n result = await sess.call_tool(call.function.name,\n arguments=eval(call.function.arguments))\n print(call.function.name, \"→\", result.content[0].text[:200])\n\nasyncio.run(main(\"abc123\", \"I think therefore I am\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">TypeScript / Node.js:<code className=\"bg-muted px-1 rounded text-xs font-mono\">@modelcontextprotocol/sdk</code></p>\n <CodeBlock filename=\"chivox-client.ts\" lang=\"typescript\">{`// npm i @modelcontextprotocol/sdk openai\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport OpenAI from \"openai\";\n\nconst mcp = new Client({ name: \"chivox-biz\", version: \"1.0.0\" });\nawait mcp.connect(new StreamableHTTPClientTransport(new URL(\"https://mcp.cloud.chivox.com\")));\n\nconst { tools } = await mcp.listTools();\nconst openai = new OpenAI({ apiKey: process.env.DEEPSEEK_KEY, baseURL: \"https://api.deepseek.com/v1\" });\n\nconst resp = await openai.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: \"评测 audioId=abc123, 参考 I think therefore I am\" }],\n tools: tools.map(t => ({ type: \"function\", function: {\n name: t.name, description: t.description, parameters: t.inputSchema\n }})),\n});\n\nfor (const call of resp.choices[0].message.tool_calls ?? []) {\n const r = await mcp.callTool({ name: call.function.name,\n arguments: JSON.parse(call.function.arguments) });\n console.log(call.function.name, r.content);\n}`}</CodeBlock>\n <Callout type=\"tip\">这种写法 <strong>LLM 供应商可换</strong>(豆包 / DeepSeek / GPT / Qwen / Moonshot),<strong>工具自动更新</strong>(驰声新增题型无需改业务代码),适合生产后端。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-agent-frameworks\" title=\"② 用 Agent 框架自动桥接(LangChain / Mastra / Agents SDK)\">\n <p>如果项目已经在用 Agent 框架,几行 adapter 就能把驰声 MCP 塞进去,框架本身会接管 tool-calling 循环、重试、日志。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LangChain / LangGraph(Python)</p>\n <CodeBlock filename=\"agent.py\" lang=\"python\">{`# pip install langchain-mcp-adapters langgraph\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom langgraph.prebuilt import create_react_agent\n\nclient = MultiServerMCPClient({\n \"chivox\": {\n \"transport\": \"streamable_http\",\n \"url\": \"https://mcp.cloud.chivox.com\",\n }\n})\ntools = await client.get_tools()\n\nagent = create_react_agent(\"openai:gpt-4o-mini\", tools)\nresult = await agent.ainvoke({\"messages\":\n [(\"user\", \"评测 audioId=abc123 参考 think\")]})\nprint(result[\"messages\"][-1].content)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Mastra(TypeScript)</p>\n <CodeBlock filename=\"mastra-agent.ts\" lang=\"typescript\">{`// npm i @mastra/core @mastra/mcp\nimport { MCPClient } from \"@mastra/mcp\";\nimport { Agent } from \"@mastra/core/agent\";\nimport { openai } from \"@ai-sdk/openai\";\n\nconst mcp = new MCPClient({ servers: {\n chivox: { url: new URL(\"https://mcp.cloud.chivox.com\") }\n}});\n\nexport const speechCoach = new Agent({\n name: \"speech-coach\",\n instructions: \"你是口语教练,用 chivox 工具评测并给改进建议。\",\n model: openai(\"gpt-4o-mini\"),\n tools: await mcp.getTools(),\n});`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">OpenAI Agents SDK(Python)</p>\n <CodeBlock filename=\"agents_sdk.py\" lang=\"python\">{`# pip install openai-agents\nfrom agents import Agent, Runner\nfrom agents.mcp import MCPServerStreamableHttp\n\nchivox = MCPServerStreamableHttp(\n params={\"url\": \"https://mcp.cloud.chivox.com\"},\n name=\"chivox-speech-eval\",\n)\n\nasync with chivox:\n agent = Agent(name=\"coach\",\n instructions=\"专业口语教练\",\n mcp_servers=[chivox])\n r = await Runner.run(agent, \"评测 audioId=abc123\")\n print(r.final_output)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LlamaIndex / AutoGen / CrewAI</p>\n <p className=\"text-xs text-muted-foreground mb-2\">这些框架都有官方或社区 MCP adapter,用法与上述类似:</p>\n <ul className=\"list-disc list-inside space-y-1 text-xs\">\n <li><strong>LlamaIndex</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">llama-index-tools-mcp</code></li>\n <li><strong>AutoGen</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">autogen-ext[mcp]</code></li>\n <li><strong>CrewAI</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">crewai-tools</code> 的 MCPServerAdapter</li>\n <li><strong>Spring AI</strong>(Java):<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">spring-ai-mcp-client</code></li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"code-function-calling\" title=\"③ 直调 chat.completions · 上传与动态 tools(豆包 / DeepSeek / 等)\">\n <p>\n DeepSeek、GLM(智谱)、KIMI(Moonshot)、豆包(火山方舟)、千帆、DashScope 等若提供 <strong>OpenAI 兼容的 HTTP API</strong>,接入驰声 MCP 的套路一致:用 MCP 客户端拉取工具列表 → 转为{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> → 模型决策 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_calls</code> → MCP 执行并回填 → 再让模型生成诊断话术。\n </p>\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">① MCP list_tools()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">② 转 OpenAI tools 格式</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">③ LLM 决策 tool_calls</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">④ MCP call_tool()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">⑤ 评测结果回填 → LLM 诊断</span>\n </div>\n </div>\n <Callout type=\"tip\">各厂商仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 与 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 与 tools 桥接代码可原样复用。</Callout>\n\n <p className=\"font-semibold mt-6\">先上传音频,拿到 audioId</p>\n <p className=\"text-sm text-muted-foreground mb-3\">文件评测工具通常需要 <code className=\"bg-muted px-1 rounded text-xs font-mono\">audioId</code>(或路径参数,视工具而定)。下面是用 HTTP 上传的最小示例:</p>\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://your-audio-host.com/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst res = await fetch('https://your-audio-host.com/upload', {\n method: 'POST',\n headers: { 'Content-Type': 'audio/mp3' },\n body: audioData,\n});\n\nconst result = await res.json();\nconsole.log('audioId:', result.audioId);`}</CodeBlock>\n\n <p className=\"font-semibold mt-6\">再以豆包为例:MCP 动态 tools + chat.completions 折返跑</p>\n <p>当你用的 LLM SDK <strong>还没有原生 MCP 支持</strong>(比如老版本的火山方舟 / 百度千帆 / 通义 DashScope),退化方案是:用 MCP 客户端<strong>一次性拉到 tool schema</strong>,然后按 OpenAI Function Calling 格式喂给 LLM。</p>\n <CodeBlock filename=\"doubao_tools.py\" lang=\"python\">{`# 适用:豆包(火山方舟)/ DeepSeek / Qwen / Moonshot / GLM 等 OpenAI 兼容 API\n# 核心思路:MCP 列工具 → 转 OpenAI tools → LLM 决策 → MCP 执行 → 回填\n\nasync with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n\n # ① 用 MCP 拉到的 schema 自动转 OpenAI 格式\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 喂给豆包\n messages = [{\"role\": \"user\", \"content\": \"评测一下 audioId=abc123\"}]\n resp = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages, tools=oa_tools,\n )\n\n # ③ LLM 回 tool_calls → 折返跑到 MCP 执行\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for c in msg.tool_calls:\n result = await mcp.call_tool(\n c.function.name, arguments=json.loads(c.function.arguments))\n messages.append({\"role\": \"tool\", \"tool_call_id\": c.id,\n \"content\": result.content[0].text})\n # ④ 再喂一轮,让豆包根据评测结果产出自然语言诊断\n final = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages)\n print(final.choices[0].message.content)`}</CodeBlock>\n <Callout type=\"warning\"><strong>千万别手写 tool schema!</strong>驰声共 <strong>16 个评测工具</strong>(英文 10 + 中文 6),每个工具参数较多,手写既容易错也难维护。一定用 MCP <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">list_tools()</code> 动态拿。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-selfhosted-agent\" title=\"④ 自研后端 Agent(FastAPI / NestJS / Spring)\">\n <p>大部分落地场景其实是:<strong>你已有一个业务后端</strong>(教育 SaaS / 培训系统 / 客服平台),只需要新增一个「语音评测」能力。推荐架构:</p>\n\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">前端录音 / 上传</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">业务后端</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">MCP Client(常驻连接)</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">驰声 MCP</span>\n <span className=\"text-muted-foreground\">↘</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM(诊断 / 出题)</span>\n </div>\n </div>\n\n <p className=\"font-semibold mt-2\">FastAPI 示例(生产最小骨架)</p>\n <CodeBlock filename=\"app.py\" lang=\"python\">{`from fastapi import FastAPI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\nfrom contextlib import asynccontextmanager\n\nmcp_session: ClientSession | None = None\n\n@asynccontextmanager\nasync def lifespan(app):\n global mcp_session\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as s:\n await s.initialize()\n mcp_session = s\n yield\n mcp_session = None\n\napp = FastAPI(lifespan=lifespan)\n\n@app.post(\"/api/evaluate\")\nasync def evaluate(audio_id: str, ref_text: str):\n # 直接选一个最合适的工具执行(不经 LLM,延迟最低)\n r = await mcp_session.call_tool(\"evaluate_english_sentence\",\n {\"audioId\": audio_id, \"sentence\": ref_text})\n return {\"result\": r.content[0].text}`}</CodeBlock>\n\n <Callout type=\"tip\"><strong>关键优化</strong>:① MCP 连接在 lifespan 里做成长连接,不要每次请求都重连;② 对延迟敏感的场景可以「跳过 LLM 直接调 tool」,把 LLM 放到「生成诊断报告」那一步。</Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-4 my-5 text-xs\">\n <p className=\"font-semibold text-sm mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-muted-foreground\" /> 下面是 ① ~ ④ 中<strong className=\"text-foreground\">第③种「直调 chat.completions」</strong>按主流 LLM 厂商的细化写法</p>\n <p className=\"text-muted-foreground\">各家 SDK / base_url / 鉴权写法略有差别,挑你当前用的那一家看即可;驰声 MCP 侧代码不用改。</p>\n </div>\n\n <SubDoc id=\"llm-deepseek\" title=\"DeepSeek\">\n <p>\n DeepSeek 提供与 OpenAI 完全兼容的 API,当前最新模型为 <strong>DeepSeek-V3.2</strong>(2025 年 12 月发布,首个把\"思考 + 工具调用\"原生融合的模型)。\n 通过 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-chat</code> 进入<strong>非思考模式</strong>,\n 通过 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-reasoner</code> 进入<strong>思考模式</strong>,\n <strong>两种模式均支持 Function Calling</strong>,128K 上下文。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"deepseek_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# DeepSeek 客户端(OpenAI 兼容)\nclient = OpenAI(\n api_key=os.environ[\"DEEPSEEK_API_KEY\"],\n base_url=\"https://api.deepseek.com/v1\",\n)\n\nasync def evaluate_with_deepseek(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 拉取驰声 MCP 工具列表\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 调用 DeepSeek,让它选择合适的评测工具\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"deepseek-chat\", # 或 deepseek-reasoner\n messages=messages,\n tools=oa_tools,\n )\n\n # ③ 执行 tool_calls\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 让 DeepSeek 根据评测结果生成自然语言诊断\n final = client.chat.completions.create(\n model=\"deepseek-chat\",\n messages=messages,\n )\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_deepseek(\"audio_abc123\", \"Hello, nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议(V3.2):</strong>日常评测诊断用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">deepseek-chat</code>,速度快、token 消耗低;\n 需要深度分析(错误归因 / 个性化练习路径)时用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">deepseek-reasoner</code>,\n V3.2 起<strong>思考模式也能在思维链中调用工具</strong>,可以一次性完成\"调评测 → 思考 → 出诊断\",无需再切回 V3 做工具调用。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-glm\" title=\"GLM(智谱 AI / Z.ai)\">\n <p>\n 智谱 AI 当前最新旗舰为 <strong>GLM-5 / GLM-5.1</strong>(2026 年 2 月发布,744B-A40B MoE,200K 上下文,对标 Claude Opus 4.5),\n 以及上一代 <strong>GLM-4.7</strong>、<strong>GLM-4.6</strong>。\n 此外保留 <code className=\"bg-muted px-1 rounded text-xs font-mono\">glm-4-flash</code>(免费快速版)方便开发测试。\n 全部模型支持 OpenAI 兼容接口与原生 <code className=\"bg-muted px-1 rounded text-xs font-mono\">zhipuai</code> SDK,原生 Function Calling。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`ZHIPU_API_KEY=xxxxxxxx.xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式一:OpenAI 兼容接口(推荐)</p>\n <CodeBlock filename=\"glm_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(\n api_key=os.environ[\"ZHIPU_API_KEY\"],\n base_url=\"https://open.bigmodel.cn/api/paas/v4/\",\n)\n\nasync def evaluate_with_glm(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"glm-4-flash\", # 免费快速版;生产推荐 glm-5 / glm-4.7\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"glm-4-flash\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_glm(\"audio_abc123\", \"How are you today?\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式二:官方 zhipuai SDK</p>\n <CodeBlock filename=\"glm_sdk.py\" lang=\"python\">{`from zhipuai import ZhipuAI\n\nclient = ZhipuAI(api_key=os.environ[\"ZHIPU_API_KEY\"])\n\n# 其余逻辑与方式一相同,仅替换 client 实例\nresp = client.chat.completions.create(\n model=\"glm-5\", # 旗舰;也可用 glm-4.7 / glm-4-flash\n messages=messages,\n tools=oa_tools,\n)`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 开发联调用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-4-flash</code>(免费);\n 在线辅导日常诊断用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-4.7</code> 性价比最高;\n K12 / 复杂 Agent 编排(多步评测 + 个性化练习路径)用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-5</code> / <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-5.1</code>,\n 200K 上下文、Agent 能力对标 Claude Opus 系列。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-kimi\" title=\"KIMI(Moonshot AI)\">\n <p>\n Moonshot AI 当前最新旗舰是 <strong>Kimi K2.5</strong>(2026 年 1 月 27 日发布,MoE 架构、<strong>256K 上下文</strong>、原生多模态、Agent 集群协作)。\n 另外提供 <code className=\"bg-muted px-1 rounded text-xs font-mono\">kimi-k2-thinking</code>(深度思考)、<code className=\"bg-muted px-1 rounded text-xs font-mono\">kimi-k2-turbo-preview</code>(高速版)。\n 上一代 <code className=\"bg-muted px-1 rounded text-xs font-mono\">moonshot-v1-8k/32k/128k</code> 仍然兼容,但<strong>推荐迁移到 kimi-k2.5</strong>。\n 全部模型 OpenAI 兼容,原生 Function Calling。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`MOONSHOT_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"kimi_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# 中国大陆用 api.moonshot.cn;海外/国际版用 api.moonshot.ai\nclient = OpenAI(\n api_key=os.environ[\"MOONSHOT_API_KEY\"],\n base_url=\"https://api.moonshot.cn/v1\",\n)\n\nasync def evaluate_with_kimi(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"kimi-k2.5\", # 旗舰;也可选 kimi-k2-turbo-preview / moonshot-v1-128k\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"kimi-k2.5\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_kimi(\"audio_abc123\", \"Nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\">\n Kimi K2.5 原生 <strong>256K 上下文 + Agent 集群协作</strong>,特别适合「多轮口语练习 + 历史分析 + 多步评测编排」场景:\n 把用户过去 30 次评测的完整 JSON 全部塞进 messages,让 K2.5 一次性生成跨多次练习的进步报告与个性化练习路径。\n 需要更深推理时使用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">kimi-k2-thinking</code>,\n 追求实时反馈用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">kimi-k2-turbo-preview</code>。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-doubao\" title=\"豆包 Seed 2.0(火山方舟)\">\n <p>\n 字节跳动 <strong>豆包 Seed 2.0</strong>(2026 年 2 月发布)是 ByteDance Seed 团队推出的新一代通用大模型,覆盖 Pro / Lite / Mini / Code 多种规格,\n 通过 <strong>火山方舟(Volcano Ark)</strong> 提供 OpenAI 兼容的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">/api/v3/chat/completions</code> 接口,\n 原生支持 <strong>Function Calling、JSON Schema 结构化输出、深度思考(Chain-of-Thought)模式</strong>,\n 接驰声 MCP 几乎无须改动业务代码。\n </p>\n\n <Callout type=\"tip\">\n 豆包 Seed 2.0 的优势在 <strong>深度思考 + 工具编排</strong>:让模型一次性规划\"先调 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code>,看完弱项再调 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.word.score</code> 进一步定位音素\",\n 适合做\"多步评测 + 自动纠音\"这类 Agent 场景。\n 默认开启思考模式,可通过 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'thinking: { type: \"disabled\" }'}</code> 关闭以节省时延。\n </Callout>\n\n <p className=\"font-semibold mt-4 mb-2\">推荐版本</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">model</th>\n <th className=\"text-left py-2 px-3 font-medium\">规格</th>\n <th className=\"text-left py-2 px-3 font-medium\">推荐用途</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-pro-260215</td><td className=\"py-2 px-3\">Pro · 旗舰</td><td className=\"py-2 px-3\">多步评测 + 深度诊断 + 复杂 Agent 编排</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-lite-260215</td><td className=\"py-2 px-3\">Lite · 平衡</td><td className=\"py-2 px-3\">在线辅导单轮评测 + 自然语言反馈,性价比首选</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-mini-260215</td><td className=\"py-2 px-3\">Mini · 极小</td><td className=\"py-2 px-3\">高并发 / 边缘部署 / 移动端实时反馈</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-code-preview-260215</td><td className=\"py-2 px-3\">Code · 预览</td><td className=\"py-2 px-3\">代码生成 / 工具编排 DSL,对评测场景非主推</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-1-8-251228</td><td className=\"py-2 px-3\">1.8 · 深度思考</td><td className=\"py-2 px-3\">支持 reasoning_effort 调档(minimal/low/medium/high),上一代深度思考强化版仍可用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-1-6-251015</td><td className=\"py-2 px-3\">1.6 · 经典</td><td className=\"py-2 px-3\">256K 上下文经典版,已上线项目可继续沿用</td></tr>\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`# 火山方舟控制台 → API Key 管理\nARK_API_KEY=ak-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · 动态 tools + 折返跑)</p>\n <CodeBlock filename=\"doubao_seed20_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# 火山方舟 OpenAI 兼容端点\nclient = OpenAI(\n api_key=os.environ[\"ARK_API_KEY\"],\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n)\n\nMODEL = \"doubao-seed-2-0-pro-260215\" # 也可换成 lite / mini\n\nasync def evaluate_with_doubao(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 把驰声 16 个评测工具的 schema 动态注入豆包\n tools = (await mcp.list_tools()).tools\n ark_tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n },\n } for t in tools]\n\n messages = [\n {\"role\": \"system\",\n \"content\": \"你是一位专业的英语口语教练,会主动调用驰声评测工具获取真实分数。\"},\n {\"role\": \"user\",\n \"content\": f\"请评测这段录音,audio_id={audio_id},参考文本:{ref_text}。\"\n f\"先做句子级评测,弱项再追加单词级定位。\"},\n ]\n\n # ② 第一轮 —— 让豆包根据 prompt 自主选工具\n resp = client.chat.completions.create(\n model=MODEL,\n messages=messages,\n tools=ark_tools,\n tool_choice=\"auto\",\n # extra_body={\"thinking\": {\"type\": \"disabled\"}}, # 需要时可关思考\n )\n\n msg = resp.choices[0].message\n messages.append(msg)\n\n # ③ 折返跑:豆包可能一次发起多个 tool_calls,全部回填\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 第二轮 —— 让豆包基于真实评测 JSON 输出诊断 / 练习建议\n final = client.chat.completions.create(\n model=MODEL,\n messages=messages,\n )\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_doubao(\n audio_id=\"audio_abc123\",\n ref_text=\"The quick brown fox jumps over the lazy dog.\",\n)))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">深度思考模式(可选)</p>\n <p className=\"text-sm text-muted-foreground\">\n 豆包 Seed 2.0 默认开启 <strong>thinking</strong> 模式,会在内部生成思维链再产出答案,\n 对\"多步评测编排 / 复杂诊断\"质量明显更好;但会增加 30%~80% 的端到端延迟。\n 在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">extra_body</code> 里可显式控制:\n </p>\n <CodeBlock filename=\"thinking_mode.py\" lang=\"python\">{`# 关闭思考(更快、适合实时反馈)\nclient.chat.completions.create(\n model=\"doubao-seed-2-0-lite-260215\",\n messages=messages,\n tools=ark_tools,\n extra_body={\"thinking\": {\"type\": \"disabled\"}},\n)\n\n# 开启思考(默认,适合 Pro 模型 + 多步 Agent)\nclient.chat.completions.create(\n model=\"doubao-seed-2-0-pro-260215\",\n messages=messages,\n tools=ark_tools,\n extra_body={\"thinking\": {\"type\": \"enabled\"}},\n)`}</CodeBlock>\n\n <Callout type=\"info\">\n <strong>选型建议:</strong>\n 课堂 / 培训类一对一辅导用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-lite</code> + 关思考;\n K12 / 考试场景需要\"多题型联动 + 升级建议\"时用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-pro</code> + 开思考;\n 移动端实时口语对话用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-mini</code> 控制成本与时延。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-openai\" title=\"OpenAI(GPT-5.1)\">\n <p>\n OpenAI 当前旗舰为 <strong>GPT-5.1</strong>(2025-11-13 发布),<strong>400K 上下文</strong>、128K 最大输出,\n 支持可调推理强度 <code className=\"bg-muted px-1 rounded text-xs font-mono\">reasoning_effort</code>(none / low / medium / high)。\n 另有 <code className=\"bg-muted px-1 rounded text-xs font-mono\">gpt-5.1-chat-latest</code>(ChatGPT 同款,128K 上下文,对话场景更优)和 <code className=\"bg-muted px-1 rounded text-xs font-mono\">gpt-5-mini</code>(性价比版)。\n 全部模型原生支持 Function Calling 与 Structured Outputs。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · GPT-5.1 + 驰声 MCP)</p>\n <CodeBlock filename=\"openai_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(api_key=os.environ[\"OPENAI_API_KEY\"])\n\nasync def evaluate_with_gpt(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [\n {\"role\": \"system\",\n \"content\": \"你是英语口语教练,必须调用驰声评测工具拿真实分数后再回答。\"},\n {\"role\": \"user\",\n \"content\": f\"请评测:audioId={audio_id},参考文本:{ref_text}\"},\n ]\n\n resp = client.chat.completions.create(\n model=\"gpt-5.1\",\n messages=messages,\n tools=oa_tools,\n tool_choice=\"auto\",\n reasoning_effort=\"medium\", # GPT-5.1 新参数:none / low / medium / high\n )\n\n msg = resp.choices[0].message\n messages.append(msg)\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n final = client.chat.completions.create(\n model=\"gpt-5.1\", messages=messages)\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_gpt(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 评测调用 + 诊断默认用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5.1</code>(reasoning_effort=&quot;medium&quot;,质量与延迟平衡);\n 纯对话/课堂讲解用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5.1-chat-latest</code>;\n 高并发批量评测降本用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5-mini</code>。\n 深度推理 / 多步 Agent 编排把 reasoning_effort 调到 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">high</code>。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-claude\" title=\"Claude(Anthropic)\">\n <p>\n Anthropic 当前最新为 <strong>Claude Opus 4.7</strong>(旗舰)、<strong>Claude Sonnet 4.6</strong>(平衡)、<strong>Claude Haiku 4.5</strong>(高速)。\n Claude 用 <strong>Messages API</strong>(与 OpenAI Chat Completions 不同),工具协议是 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_use</code> / <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_result</code> 块结构。\n 支持文本 + 视觉输入,工具 schema 用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">input_schema</code> 字段(与 OpenAI 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">parameters</code> 等价)。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · 原生 Anthropic SDK + MCP 工具桥接)</p>\n <CodeBlock filename=\"claude_chivox.py\" lang=\"python\">{`# pip install anthropic mcp\nimport os, json, asyncio\nfrom anthropic import Anthropic\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = Anthropic(api_key=os.environ[\"ANTHROPIC_API_KEY\"])\n\nasync def evaluate_with_claude(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # 把驰声 MCP 工具转成 Anthropic 风格(用 input_schema 而非 parameters)\n tools = (await mcp.list_tools()).tools\n anth_tools = [{\n \"name\": t.name,\n \"description\": t.description,\n \"input_schema\": t.inputSchema,\n } for t in tools]\n\n messages = [{\"role\": \"user\",\n \"content\": f\"请评测这段录音 audioId={audio_id},参考文本:{ref_text}\"}]\n\n # 工具循环:Claude 可能多轮 tool_use,需逐轮回填\n while True:\n resp = client.messages.create(\n model=\"claude-opus-4-7\", # 或 claude-sonnet-4-6 / claude-haiku-4-5\n max_tokens=2048,\n tools=anth_tools,\n messages=messages,\n system=\"你是专业英语口语教练,必须调用驰声评测工具获取真实分数。\",\n )\n\n # 把整段助手回复(含 tool_use 块)追加进 messages\n messages.append({\"role\": \"assistant\", \"content\": resp.content})\n\n if resp.stop_reason != \"tool_use\":\n # 没有更多工具调用,输出最终诊断\n return \"\".join(b.text for b in resp.content if b.type == \"text\")\n\n # 提取所有 tool_use 块、并行执行、把 tool_result 回填给 Claude\n tool_results = []\n for block in resp.content:\n if block.type == \"tool_use\":\n result = await mcp.call_tool(block.name, arguments=block.input)\n tool_results.append({\n \"type\": \"tool_result\",\n \"tool_use_id\": block.id,\n \"content\": result.content[0].text,\n })\n messages.append({\"role\": \"user\", \"content\": tool_results})\n\nprint(asyncio.run(evaluate_with_claude(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 深度评测诊断 + 复杂 Agent 编排用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-opus-4-7</code>(旗舰,编程/Agent 提升明显);\n 日常评测 + 性价比首选 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-sonnet-4-6</code>;\n 高并发实时反馈用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-haiku-4-5</code>。\n Opus 4.5 起新增 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">effort</code> 参数(low/medium/high),medium 即可达到 Sonnet 4.5 同等质量但 token 减少 ~76%。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-gemini\" title=\"Gemini(Google)\">\n <p>\n Google 最新 <strong>Gemini 3 Flash</strong>(<code className=\"bg-muted px-1 rounded text-xs font-mono\">gemini-3-flash-preview</code>,2025-12-17)以 Flash 价位提供 Pro 级推理;\n 另有 <strong>Gemini 3.1 Pro Preview</strong>(旗舰)、<strong>Gemini 3.1 Flash-Lite Preview</strong>。\n 全部支持 Function Calling、并行调用、组合调用,原生多模态,<code className=\"bg-muted px-1 rounded text-xs font-mono\">thinking_level</code> 控制推理深度。\n 推荐用 <strong>OpenAI 兼容端点</strong>,可直接复用 OpenAI SDK 与已有的 MCP 工具桥接代码。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`GEMINI_API_KEY=AIzaSyxxxxxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式一:OpenAI 兼容端点(推荐,零迁移成本)</p>\n <CodeBlock filename=\"gemini_openai_compat.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# Gemini OpenAI 兼容端点 —— 完全复用 OpenAI SDK\nclient = OpenAI(\n api_key=os.environ[\"GEMINI_API_KEY\"],\n base_url=\"https://generativelanguage.googleapis.com/v1beta/openai/\",\n)\n\nasync def evaluate_with_gemini(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://mcp.cloud.chivox.com\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\",\n \"content\": f\"请评测:audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"gemini-3-flash-preview\", # 或 gemini-3.1-pro-preview\n messages=messages,\n tools=oa_tools,\n tool_choice=\"auto\",\n )\n msg = resp.choices[0].message\n messages.append(msg)\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n final = client.chat.completions.create(\n model=\"gemini-3-flash-preview\", messages=messages)\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_gemini(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式二:原生 google-genai SDK(thinking_level / 流式工具调用)</p>\n <CodeBlock filename=\"gemini_native.py\" lang=\"python\">{`# pip install google-genai\nfrom google import genai\nfrom google.genai import types\n\nclient = genai.Client(api_key=os.environ[\"GEMINI_API_KEY\"])\n\n# 把 MCP 工具转成 Gemini function_declarations\nfn_decls = [{\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n} for t in tools]\n\nresp = client.models.generate_content(\n model=\"gemini-3-flash-preview\",\n contents=f\"评测 audioId={audio_id},参考文本:{ref_text}\",\n config=types.GenerateContentConfig(\n tools=[types.Tool(function_declarations=fn_decls)],\n # Gemini 3 起:thinking_level 替代旧的 thinking_budget\n # thinking_level=\"high\",\n ),\n)\n\n# tool_call 在 response.candidates[0].content.parts[0].function_call\nfc = resp.candidates[0].content.parts[0].function_call\nprint(f\"call: {fc.name}({dict(fc.args)}) id={fc.id}\")`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 日常评测用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3-flash-preview</code>(速度比 2.5 Pro 快 3 倍、价格 ~$0.50/M 输入),已是 Gemini App 默认;\n 复杂多步 Agent / 编程任务用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3.1-pro-preview</code>;\n 超大上下文 + 多模态批处理用同系列的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3.1-flash-lite-preview</code>。\n </Callout>\n <Callout type=\"info\">\n <strong>注意:</strong>Gemini 3 起每次 function_call 自动带 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">id</code>,多轮工具调用必须把 id 透传回 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">function_response</code>,否则会被拒绝(OpenAI 兼容端点已自动处理)。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-comparison\" title=\"模型对比速查\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead>\n <tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 pr-4 font-medium\">模型</th>\n <th className=\"text-left py-2 pr-4 font-medium\">推荐版本</th>\n <th className=\"text-left py-2 pr-4 font-medium\">base_url</th>\n <th className=\"text-left py-2 pr-4 font-medium\">Function Calling</th>\n <th className=\"text-left py-2 font-medium\">适合场景</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek V3.2 · 非思考</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-chat</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">评测调用 + 诊断,性价比最高</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek V3.2 · 思考</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-reasoner</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(思考中可调工具)</td>\n <td className=\"py-2\">深度分析 + 错因归因 + 个性化练习路径</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-5 · 旗舰</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-5</code> / <code className=\"bg-muted px-1 rounded font-mono\">glm-5.1</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">200K 长上下文 + Agent 编排,复杂诊断</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4.7</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4.7</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">在线辅导日常诊断,性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4-Flash</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4-flash</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">免费额度,开发联调</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2.5(旗舰)</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2.5</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">256K + 多模态 + Agent 集群协作</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2 Thinking</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2-thinking</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">多步推理 + 多轮历史进度报告</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2 Turbo</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2-turbo-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高速响应版,实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Pro</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-pro-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(含深度思考)</td>\n <td className=\"py-2\">多步评测编排 + 深度诊断 Agent</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Lite</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-lite-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">在线辅导 + 单轮诊断,性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Mini</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-mini-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">移动端 / 边缘部署 / 低延迟实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5.1(旗舰)</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5.1</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(reasoning_effort 可调)</td>\n <td className=\"py-2\">400K 上下文 + 深度推理 + 复杂 Agent 编排</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5.1 Chat</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5.1-chat-latest</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">ChatGPT 同款,对话 / 课堂讲解</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5 mini</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5-mini</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高并发 / 批量评测降本</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Opus 4.7</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-opus-4-7</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(tool_use 协议)</td>\n <td className=\"py-2\">旗舰,编程 / Agent / 深度诊断</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Sonnet 4.6</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-sonnet-4-6</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">日常评测 + 性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Haiku 4.5</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-haiku-4-5</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高并发实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Gemini 3 Flash</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gemini-3-flash-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">…/v1beta/openai/</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(并行 / 组合)</td>\n <td className=\"py-2\">Pro 级推理 + Flash 价位,Gemini App 默认</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Gemini 3.1 Pro Preview</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gemini-3.1-pro-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">…/v1beta/openai/</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(thinking_level 可调)</td>\n <td className=\"py-2\">复杂多步 Agent + 编程任务</td>\n </tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"info\">\n 除 <strong>Claude</strong> 用 Anthropic 原生 SDK(<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">anthropic</code> 包,<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">tool_use</code> / <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">tool_result</code> 协议)外,其余所有模型均可通过 <strong>OpenAI Python SDK</strong>(<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">openai</code> 包)调用,仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 与 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 侧代码<strong>无需改动</strong>。\n </Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mt-6 text-sm\">\n <p className=\"font-semibold mb-2\">方案选型速查</p>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 font-medium\">场景</th>\n <th className=\"text-left py-2 font-medium\">推荐方案</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2\">AI 对话 / Copilot 类产品,LLM 供应商可能会换</td><td className=\"py-2\">① 官方 MCP 客户端库</td></tr>\n <tr><td className=\"py-2\">已有 LangChain / Mastra / Agents SDK 技术栈</td><td className=\"py-2\">② Agent 框架 + adapter</td></tr>\n <tr><td className=\"py-2\">火山方舟 / 千帆控制台直连,最小侵入</td><td className=\"py-2\">③ chat.completions + tools</td></tr>\n <tr><td className=\"py-2\">教育 SaaS / 培训 / 客服等业务后端集成</td><td className=\"py-2\">④ 自研后端 Agent</td></tr>\n <tr><td className=\"py-2\">原生 Android / iOS / 小程序 / 不装 MCP SDK 的老系统</td><td className=\"py-2\"><a href=\"#config-rest\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">→ 走下一大节「非 MCP REST」</a></td></tr>\n </tbody>\n </table>\n </div>\n </div>\n </DocSection>\n\n {/* ══════ 接入 · 非 MCP 原生 REST(cvx_fc) ══════ */}\n <DocSection id=\"config-rest\" icon={Network} title=\"接入 · 非 MCP 原生 REST(cvx_fc)\">\n\n <div className=\"rounded-lg border border-sky-500/30 bg-sky-500/5 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Network className=\"h-4 w-4 text-sky-600\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>客户端环境<strong className=\"text-foreground\">不能 / 不想引入 MCP SDK</strong>:原生 Android / iOS / Flutter、微信 / 支付宝小程序、老版本 Java / PHP / Go 后端</li>\n <li>希望把驰声当作<strong className=\"text-foreground\">普通 REST + WebSocket</strong>调,不经过 JSON-RPC、不走 stdio 代理</li>\n <li>内部代号 <code className=\"bg-muted px-1 rounded text-xs font-mono\">cvx_fc</code>(OpenAI function-calling 风格),与 MCP 模式<strong className=\"text-foreground\">指向同一个评测引擎</strong>,两者为并行入口</li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 能走 MCP 的一律优先 MCP;仅当客户端限制无法装 SDK 时走本大节。</p>\n </div>\n\n <SubDoc id=\"rest-overview\" title=\"functioncalling 模式总览 · 端点与认证\">\n <p>\n 驰声在 MCP 之外提供一套 <strong>OpenAI 兼容的 Function Calling 风格 REST API</strong>(内部代号 <code className=\"bg-muted px-1 rounded text-xs\">cvx_fc</code>)。它和 MCP 模式指向<strong>同一个评测引擎</strong>,但协议层走纯 HTTP / WebSocket,<strong>不依赖任何 MCP SDK</strong>。\n </p>\n\n <Callout type=\"info\">\n <strong>与 MCP 模式的关系</strong>:MCP 模式(<code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">POST /mcp</code>,JSON-RPC 2.0)和 functioncalling 模式(<code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">POST /v1/call</code>,REST)是<strong>两个并行入口</strong>,功能等价、工具/函数列表基本一致。\n </Callout>\n\n <p className=\"font-semibold mt-6\">API 端点</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">路径</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">GET</td><td className=\"py-2 px-3 font-mono text-xs\">/v1/functions</td><td className=\"py-2 px-3\">列出全部可用评测函数(等价 MCP <code className=\"bg-muted px-1 rounded text-xs\">tools/list</code>)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">POST</td><td className=\"py-2 px-3 font-mono text-xs\">/v1/call</td><td className=\"py-2 px-3\">调用一个评测函数(等价 MCP <code className=\"bg-muted px-1 rounded text-xs\">tools/call</code>)· 同时用于<strong>创建流式会话</strong>和<strong>拉取流式结果</strong></td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">WS</td><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/eval/{session_id}'}</td><td className=\"py-2 px-3\">流式评测音频传输</td></tr>\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6\">认证</p>\n <p>所有请求在 HTTP Header 中携带 API Key:</p>\n <CodeBlock filename=\"Header\" lang=\"http\">{`Authorization: Bearer <your_api_key>`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">认证失败返回 <code className=\"bg-muted px-1 rounded text-xs\">401</code>(缺少 Authorization)或 <code className=\"bg-muted px-1 rounded text-xs\">403</code>(Key 无效 / 无权使用该评测类型)。结构化错误码见 <a href=\"#error-codes\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">API 参考 · 错误码</a>。</p>\n </SubDoc>\n\n <SubDoc id=\"rest-catalog\" title=\"函数目录 · GET /v1/functions\">\n <p>\n 调用 <code className=\"bg-muted px-1 rounded text-xs\">GET /v1/functions</code> 拿到<strong>当前 API Key 已授权</strong>的全部评测函数列表,响应格式与 OpenAI function-calling 的工具清单一致,可以直接喂给大模型作为 <code className=\"bg-muted px-1 rounded text-xs\">tools</code>:\n </p>\n <CodeBlock filename=\"request\" lang=\"http\">{`GET /v1/functions\nAuthorization: Bearer <api_key>`}</CodeBlock>\n <CodeBlock filename=\"response.json\" lang=\"json\">{`{\n \"object\": \"list\",\n \"data\": [\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英文单词发音评测,返回总分和每个音标得分。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"ref_text\": { \"type\": \"string\", \"description\": \"参考文本\" },\n \"audio_base64\": { \"type\": \"string\", \"description\": \"Base64 编码音频(与 audio_url 二选一)\" },\n \"audio_url\": { \"type\": \"string\", \"description\": \"音频 HTTP(S) URL(与 audio_base64 二选一)\" },\n \"accent\": { \"type\": \"number\", \"description\": \"1=英式 / 2=美式 / 3=不区分(默认)\" },\n \"rank\": { \"type\": \"number\", \"description\": \"评分制:4 或 100(默认)\" }\n },\n \"required\": [\"ref_text\"]\n }\n }\n }\n /* … 其它函数依此类推 … */\n ]\n}`}</CodeBlock>\n <Callout type=\"tip\">\n 这个端点语义等价于 MCP 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">tools/list</code>,<strong>每个 API Key 可见的函数集合由后台权限决定</strong>——客户端应以实际返回为准,不要在本地硬编码全量 19 个。\n </Callout>\n\n <p className=\"font-semibold mt-6\">英文评测函数(10 个)</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">函数名</th>\n <th className=\"text-left py-2 px-3 font-medium\">core_type</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['en_word_eval', 'en.word.score', '单词发音评测'],\n ['en_word_correction', 'en.word.pron', '单词发音纠错'],\n ['en_phonics_eval', 'en.nsp.score', '自然拼读评测'],\n ['en_sentence_eval', 'en.sent.score', '句子朗读评测'],\n ['en_sentence_correction', 'en.sent.pron', '句子发音纠错'],\n ['en_vocab_eval', 'en.vocabs.pron', '词汇批量评测'],\n ['en_paragraph_eval', 'en.pred.score', '段落朗读评测'],\n ['en_realtime_eval', 'en.rltm.score', '实时朗读评测'],\n ['en_choice_eval', 'en.choc.score', '口语选择题'],\n ['en_semi_open_eval', 'en.scne.exam', '半开放题(情景对话等)'],\n ].map(([fn, ct, desc]) => (\n <tr key={fn}>\n <td className=\"py-2 px-3 font-mono text-xs\">{fn}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{ct}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6\">中文评测函数(6 个)</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">函数名</th>\n <th className=\"text-left py-2 px-3 font-medium\">core_type</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['cn_word_pinyin_eval', 'cn.word.score', '拼音 / 汉字发音评测'],\n ['cn_word_raw_eval', 'cn.word.raw', '汉字发音评测'],\n ['cn_sentence_eval', 'cn.sent.raw', '句子朗读评测'],\n ['cn_paragraph_eval', 'cn.pred.raw', '段落朗读评测'],\n ['cn_rec_eval', 'cn.rec.raw', '有限分支识别'],\n ['cn_aitalk_eval', 'cn.recscore.raw', '口语表达评测(AI Talk)'],\n ].map(([fn, ct, desc]) => (\n <tr key={fn}>\n <td className=\"py-2 px-3 font-mono text-xs\">{fn}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{ct}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6\">流式评测函数(3 个)</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">函数名</th>\n <th className=\"text-left py-2 px-3 font-medium\">作用</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr>\n <td className=\"py-2 px-3 font-mono text-xs\">start_stream_eval</td>\n <td className=\"py-2 px-3\">创建流式评测会话,返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code> / <code className=\"bg-muted px-1 rounded text-xs\">ws_url</code> / <code className=\"bg-muted px-1 rounded text-xs\">resume_token</code></td>\n </tr>\n <tr>\n <td className=\"py-2 px-3 font-mono text-xs\">get_stream_result</td>\n <td className=\"py-2 px-3\">兜底拉取:WS 没拿到 <code className=\"bg-muted px-1 rounded text-xs\">result</code> 时,通过 HTTP 把最终评分捞回来</td>\n </tr>\n <tr>\n <td className=\"py-2 px-3 font-mono text-xs\">stream_eval_result</td>\n <td className=\"py-2 px-3 text-muted-foreground\">(MCP 模式的别名;cvx_fc 模式统一用 <code className=\"bg-muted px-1 rounded text-xs\">get_stream_result</code>)</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <p className=\"text-xs text-muted-foreground mt-4\">\n 📘 每个函数的<strong>业务语义 / 参数 / 返回字段</strong>与 MCP 模式下的同名工具完全一致,可直接参考下文 <a href=\"#tools-en\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">API 参考 · 英文评测工具</a> 与 <a href=\"#tools-cn\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">中文评测工具</a>。\n </p>\n </SubDoc>\n\n <SubDoc id=\"rest-oneshot\" title=\"一次性文件评测\">\n <p>把完整音频通过 <code className=\"bg-muted px-1 rounded text-xs\">audio_base64</code> 或 <code className=\"bg-muted px-1 rounded text-xs\">audio_url</code> 二选一传给 <code className=\"bg-muted px-1 rounded text-xs\">/v1/call</code>,同步返回评分(与 MCP 模式的 <code className=\"bg-muted px-1 rounded text-xs\">evaluate_english_word</code> 等工具语义等价):</p>\n <CodeBlock filename=\"curl · 英文单词\" lang=\"bash\">{`curl -X POST https://fc.cloud.chivox.com/v1/call \\\\\n -H \"Authorization: Bearer your_api_key\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\n \"name\": \"en_word_eval\",\n \"arguments\": {\n \"ref_text\": \"hello\",\n \"audio_url\": \"https://example.com/hello.mp3\",\n \"accent\": 2,\n \"rank\": 100\n }\n }'`}</CodeBlock>\n <p><strong>成功响应:</strong></p>\n <CodeBlock filename=\"response.json\" lang=\"json\">{`{\n \"name\": \"en_word_eval\",\n \"result\": {\n \"overall\": 85.5,\n \"details\": { /* 音素 / 韵律 / 流利度等结构与 MCP 模式完全一致 */ }\n }\n}`}</CodeBlock>\n <p><strong>失败响应:</strong></p>\n <CodeBlock filename=\"error.json\" lang=\"json\">{`{\n \"name\": \"en_word_eval\",\n \"error\": { \"message\": \"错误描述\" }\n}`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">单次音频 ≤ 50MB,推荐 16 kHz / 单声道 / 16bit。</p>\n </SubDoc>\n\n <SubDoc id=\"rest-streaming\" title=\"流式评测 · 三步骤\">\n <div className=\"rounded-lg border border-border/60 bg-muted/10 p-5 space-y-5 my-3\">\n <FlowStep title=\"1\">\n <p><strong>创建会话</strong>:<code className=\"bg-muted px-1 rounded text-xs\">POST /v1/call</code> 调用 <code className=\"bg-muted px-1 rounded text-xs\">start_stream_eval</code></p>\n <p className=\"text-muted-foreground mt-1\">响应中拿到 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code> / <code className=\"bg-muted px-1 rounded text-xs\">ws_url</code> / <code className=\"bg-muted px-1 rounded text-xs\">resume_token</code>。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong>WebSocket 推送</strong>:连接 <code className=\"bg-muted px-1 rounded text-xs\">ws_url</code>,收到 <code className=\"bg-muted px-1 rounded text-xs\">{`{\"type\":\"ready\"}`}</code> 即可开始发<strong>二进制音频帧</strong>。</p>\n <p className=\"text-muted-foreground mt-1\">边推边收 <code className=\"bg-muted px-1 rounded text-xs\">intermediate</code> 中间结果;结束时发 <code className=\"bg-muted px-1 rounded text-xs\">{`{\"type\":\"stop\"}`}</code>,收到 <code className=\"bg-muted px-1 rounded text-xs\">result</code> 即为最终评分。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>兜底拉取(可选)</strong>:如 WS 断开未收到最终 <code className=\"bg-muted px-1 rounded text-xs\">result</code>,可再次 <code className=\"bg-muted px-1 rounded text-xs\">POST /v1/call</code> 调用 <code className=\"bg-muted px-1 rounded text-xs\">get_stream_result</code> 兜底。</p>\n </FlowStep>\n </div>\n\n <p className=\"font-semibold mt-4\"><code>start_stream_eval</code> 参数</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.sent.score / cn.sent.raw(参考上方中英评测工具表)' },\n { name: 'ref_text', type: 'string', required: true, desc: '参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式:pcm / wav / mp3(默认 mp3;流式推荐 pcm 省编解码)' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'channel', type: 'number', required: false, desc: '声道:1=单声道(默认)/ 2=立体声' },\n { name: 'sample_bytes', type: 'number', required: false, desc: '采样位深,默认 2(16bit)' },\n { name: 'accent', type: 'number', required: false, desc: '英文口音:1=英式 / 2=美式 / 3=不区分(默认)' },\n { name: 'age_group', type: 'string', required: false, desc: '中文人群:adult(默认)/ child' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100(默认)' },\n ]} />\n\n <p className=\"font-semibold mt-4\">WebSocket 帧协议</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-3\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-xs font-semibold mb-2\">客户端 → 服务端</p>\n <ul className=\"text-xs space-y-1.5 text-muted-foreground leading-relaxed list-none pl-0\">\n <li>• <code className=\"bg-muted px-1 rounded\">Binary</code> — 原始音频帧,连上即可直接发送,无需握手</li>\n <li>• <code className=\"bg-muted px-1 rounded\">{`{\"type\":\"stop\"}`}</code> — 结束录音</li>\n <li>• <code className=\"bg-muted px-1 rounded\">{`{\"type\":\"ping\"}`}</code> — 心跳</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-xs font-semibold mb-2\">服务端 → 客户端</p>\n <ul className=\"text-xs space-y-1.5 text-muted-foreground leading-relaxed list-none pl-0\">\n <li>• <code className=\"bg-muted px-1 rounded\">ready</code> — 连接就绪,可开始发送音频</li>\n <li>• <code className=\"bg-muted px-1 rounded\">intermediate</code> — 实时中间结果</li>\n <li>• <code className=\"bg-muted px-1 rounded\">result</code> — 最终评测结果</li>\n <li>• <code className=\"bg-muted px-1 rounded\">backpressure</code> — 背压,含 <code className=\"bg-muted px-1 rounded\">suggested_interval_ms</code>,建议降低发送频率</li>\n <li>• <code className=\"bg-muted px-1 rounded\">error</code> — 错误(含结构化 <code className=\"bg-muted px-1 rounded\">code</code> + <code className=\"bg-muted px-1 rounded\">message</code>,见<a href=\"#error-codes\" className=\"underline underline-offset-2\">错误码</a>一节)</li>\n <li>• <code className=\"bg-muted px-1 rounded\">pong</code> — 心跳响应</li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"rest-examples\" title=\"JS / Android 完整示例\">\n <p className=\"font-semibold\">JavaScript(浏览器 / Node.js)</p>\n <CodeBlock filename=\"browser-or-node.js\" lang=\"javascript\">{`// ① 创建会话\nconst resp = await fetch('https://fc.cloud.chivox.com/v1/call', {\n method: 'POST',\n headers: {\n 'Authorization': 'Bearer your_api_key',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n name: 'start_stream_eval',\n arguments: {\n core_type: 'en.sent.score',\n ref_text: 'Hello world',\n audio_type: 'pcm',\n sample_rate: 16000,\n },\n }),\n});\nconst { result } = await resp.json();\n\n// ② WebSocket 推送音频\nconst ws = new WebSocket(result.ws_url);\nws.onmessage = (ev) => {\n const msg = JSON.parse(ev.data);\n switch (msg.type) {\n case 'ready': startRecording(); break;\n case 'intermediate': console.log('部分结果', msg.data); break;\n case 'result': console.log('最终结果', msg.data); ws.close(); break;\n case 'backpressure': console.warn('降速 ~', msg.suggested_interval_ms, 'ms'); break;\n case 'error': console.error(msg.code, msg.message); break;\n }\n};\n\nfunction onAudioChunk(pcm) {\n if (ws.readyState === WebSocket.OPEN) ws.send(pcm); // 直接发二进制\n}\n\nfunction stopRecording() {\n ws.send(JSON.stringify({ type: 'stop' }));\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-6\">Android(OkHttp · Kotlin)</p>\n <CodeBlock filename=\"AudioEvalClient.kt\" lang=\"kotlin\">{`val wsUrl = \"ws://your-host:8080/ws/eval/$sessionId\"\nval client = OkHttpClient()\nval req = Request.Builder()\n .url(wsUrl)\n .addHeader(\"Authorization\", \"Bearer \\${apiKey}\")\n .build()\n\nval ws = client.newWebSocket(req, object : WebSocketListener() {\n override fun onMessage(ws: WebSocket, text: String) {\n val msg = JSONObject(text)\n when (msg.getString(\"type\")) {\n \"ready\" -> startRecording()\n \"intermediate\" -> updateUI(msg.getJSONObject(\"data\"))\n \"result\" -> handleFinalResult(msg.getJSONObject(\"data\"))\n \"error\" -> handleError(msg.getString(\"code\"), msg.getString(\"message\"))\n }\n }\n})\n\n// 录音回调中发送 PCM 字节\nfun onAudioData(pcm: ByteArray) {\n ws.send(ByteString.of(*pcm))\n}\n\nfun stop() {\n ws.send(\"\"\"{\"type\":\"stop\"}\"\"\")\n}`}</CodeBlock>\n <Callout type=\"tip\">iOS / Flutter / 小程序同理,只要平台提供 WebSocket 客户端即可:连接 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">ws_url</code>、把 PCM bytes 发出去、按 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">type</code> 字段解析服务端消息就行。</Callout>\n </SubDoc>\n\n <SubDoc id=\"rest-resume\" title=\"断线恢复 · 兜底拉取\">\n <p className=\"font-semibold\">resume_token · 60 秒续播</p>\n <p>\n 客户端意外断开时,会话在服务端<strong>挂起 60 秒</strong>;用创建会话拿到的 <code className=\"bg-muted px-1 rounded text-xs\">resume_token</code> 续播即可:\n </p>\n <CodeBlock filename=\"reconnect\" lang=\"http\">{`GET ws://your-host:8080/ws/eval/{session_id}?resume={resume_token}\nAuthorization: Bearer <your_api_key>`}</CodeBlock>\n <Callout type=\"tip\">\n 重连成功后会收到新的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">ready</code>,继续推送音频即可;服务端会颁发<strong>新的 resume_token</strong>,原 token 立即作废。\n </Callout>\n\n <p className=\"font-semibold mt-6\">兜底拉取 · get_stream_result</p>\n <p>如果 WS 没能收到最终 <code className=\"bg-muted px-1 rounded text-xs\">result</code>,可主动通过 HTTP 拉取:</p>\n <CodeBlock filename=\"fallback-result.sh\" lang=\"bash\">{`curl -X POST https://fc.cloud.chivox.com/v1/call \\\\\n -H \"Authorization: Bearer your_api_key\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\n \"name\": \"get_stream_result\",\n \"arguments\": {\n \"session_id\": \"stream-1713340800-a1b2c3\",\n \"auto_stop\": true,\n \"timeout\": 30\n }\n }'`}</CodeBlock>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '由 start_stream_eval 返回的会话 ID' },\n { name: 'auto_stop', type: 'bool', required: false, desc: '是否自动发送 stop 并等待结果,默认 true' },\n { name: 'timeout', type: 'number', required: false, desc: '等待秒数,默认 30' },\n ]} />\n\n <Callout type=\"warning\">\n functioncalling 模式与 MCP 模式<strong>二选一、不要混用</strong>:同一个会话不要同时走 MCP 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">/ws/audio/</code> 和 cvx_fc 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">/ws/eval/</code>,两条链路的会话 ID 空间是分开的。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 评测模式 ══════ */}\n <DocSection id=\"eval-modes\" icon={Radio} title=\"评测模式\">\n\n <SubDoc id=\"stream-eval\" title=\"实时录音评测(边录边评)\">\n <p>通过本地麦克风实时录音,音频以 WebSocket 流式推送到评测服务,<strong>无需生成中间文件</strong>。这是最灵活、延迟最低的评测方式,特别适合交互式口语练习场景。</p>\n <Callout type=\"tip\">实时录音评测需要安装 <strong>SoX</strong> 并使用本地代理模式(Claude Desktop / Claude Code)。Cursor 等远程直连模式暂不支持实时录音。</Callout>\n\n <p className=\"font-semibold mt-4\">需要按顺序调用三个工具:</p>\n\n <div className=\"mt-3 space-y-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">create_stream_session</code> — 创建流式评测会话</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.word.score、en.sent.score、cn.sent.raw' },\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式,默认 mp3' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100,默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code>,后续步骤需使用。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">start_recording</code> — 开始麦克风录音</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '来自 create_stream_session 的会话 ID' },\n { name: 'chunk_size', type: 'number', required: false, desc: '音频块大小(bytes),默认 3200' },\n { name: 'chunk_interval_ms', type: 'number', required: false, desc: '推送间隔(ms),默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">调用后自动启动麦克风,音频通过 WebSocket 实时推送到评测服务。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">stop_recording</code> — 停止录音并获取结果</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '会话 ID' },\n { name: 'timeout', type: 'number', required: false, desc: '等待评测结果超时秒数,默认 30' },\n ]} />\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"stream-flow\" title=\"流式评测完整流程\">\n <div className=\"rounded-lg border border-border/60 p-5 bg-muted/10 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong>用户:</strong>&quot;请评测我的英文发音,文本是 Hello&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">create_stream_session(core_type=&quot;en.word.score&quot;, ref_text=&quot;Hello&quot;)</code></p>\n <p className=\"text-muted-foreground\">→ 获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">session_id</code></p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p>AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">start_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground mt-1\">麦克风开始录音,音频通过 WebSocket 实时推送到评测服务</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>用户</strong>说完后告知 AI &quot;结束&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">stop_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground\">→ 返回评测结果(总分、准确度、流利度、音素得分等)</p>\n </FlowStep>\n </div>\n </SubDoc>\n\n <SubDoc id=\"file-eval\" title=\"音频文件评测\">\n <p>适用于已有音频文件的评测场景,支持三种输入方式:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">适用场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_file_path</td><td className=\"py-2 px-3\">本地文件路径</td><td className=\"py-2 px-3 text-muted-foreground\">代理自动读取并转为 Base64,最便捷</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_base64</td><td className=\"py-2 px-3\">Base64 编码数据</td><td className=\"py-2 px-3 text-muted-foreground\">已有编码数据时使用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_url</td><td className=\"py-2 px-3\">音频文件 URL</td><td className=\"py-2 px-3 text-muted-foreground\">音频托管在云存储时使用</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"tip\">推荐使用 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">audio_file_path</code>,只需传入本地文件路径,代理会自动处理编码和上传。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"tools-en\" title=\"英文评测工具(10 种)\">\n <ToolTable tools={[\n ['en_word_eval', '单词评测', '评测 \"Hello\" 的发音准确度', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.word.score'],\n ['en_word_correction', '单词纠音', '检测多读/漏读/错读并给出纠正建议', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.word.pron'],\n ['en_vocab_eval', '词语评测', '同时评测多个单词的发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.vocabs.pron'],\n ['en_sentence_eval', '句子评测', '评测整句朗读的准确度与流利度', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.sent.score'],\n ['en_sentence_correction', '句子纠音', '逐词检测发音问题并给出修正建议', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.sent.pron'],\n ['en_paragraph_eval', '段落朗读评测', '评测整段英文课文的朗读质量', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.pred.score'],\n ['en_phonics_eval', '自然拼读评测', '评测 Phonics 拼读规则掌握程度', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.nsp.score'],\n ['en_choice_eval', '口语选择题', '从预设选项中识别口语回答', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.choc.score'],\n ['en_semi_open_eval', '半开放题评测', '场景对话等半开放口语题型', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.scne.exam'],\n ['en_realtime_eval', '实时朗读评测', '实时反馈朗读质量,逐句评分', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.rltm.score'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong>所有英文评测工具均支持以下参数。</p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tools-cn\" title=\"中文评测工具(6 种)\">\n <ToolTable tools={[\n ['cn_word_raw_eval', '单字评测(汉字)', '评测 \"中\" 的发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.pred.raw'],\n ['cn_word_pinyin_eval', '单字评测(拼音)', '评测 \"zhōng\" 的发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.pred.pny'],\n ['cn_sentence_eval', '词句评测', '评测 \"你好世界\" 的朗读', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.sent.score'],\n ['cn_paragraph_eval', '段落朗读评测', '评测一段中文课文的朗读质量', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.para.score'],\n ['cn_rec_eval', '有限分支识别评测', '从预设选项中识别用户发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.asr.rec'],\n ['cn_aitalk_eval', 'AI Talk 口语评测', '中文口语对话能力评测', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.aitalk'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong></p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本(汉字或拼音)' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结构化结果:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"integrity\": 95,\n \"details\": [\n {\n \"char\": \"hello\",\n \"score\": 85,\n \"phone\": [\n { \"phoneme\": \"h\", \"score\": 90 },\n { \"phoneme\": \"ɛ\", \"score\": 82 },\n { \"phoneme\": \"l\", \"score\": 88 },\n { \"phoneme\": \"oʊ\", \"score\": 80 }\n ]\n }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall', '0–100', '综合总分,反映整体语音质量'],\n ['accuracy', '0–100', '准确度,音素级别的发音准确率'],\n ['pron', '0–100', '发音得分,整体发音质量评价'],\n ['fluency.overall', '0–100', '流利度,语流的自然度与平滑度'],\n ['fluency.speed', '0–100', '语速得分,每分钟词/音节计量'],\n ['fluency.pause', 'number', '不自然停顿次数'],\n ['integrity', '0–100', '完整度,是否完整朗读了参考文本'],\n ['details[].phone[]', 'array', '每个音素的独立评分,可定位具体弱项'],\n ['details[].stress[]', 'array', '重音得分,检测重弱读是否正确'],\n ].map(([field, range, desc]) => (\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <p className=\"font-semibold mb-2\">常见业务场景</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期', '音频上传后 5 分钟内有效,评测完成后自动删除', '重新上传音频'],\n ['服务器繁忙', '当前请求过多,排队已满', '稍后重试,使用指数退避策略'],\n ['存储空间已满', '服务器临时音频存储已满', '稍后重试,系统会自动清理'],\n ['音频格式不支持', '上传了不支持的文件类型', '使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限', '超过该题型最大时长限制', '裁剪音频后重试'],\n ['WebSocket 连接断开', '网络不稳定导致流式连接中断', '代理会自动重连,如持续失败请检查网络'],\n ].map(([scene, reason, fix]) => (\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6 mb-2\">HTTP 状态码(functioncalling 模式)</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">状态码</th>\n <th className=\"text-left py-2 px-3 font-medium\">含义</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['400', '请求格式错误(JSON 缺字段 / 类型错)'],\n ['401', '未认证(缺少 Authorization 头)'],\n ['403', '权限不足(API Key 无效或无权使用该评测类型)'],\n ['404', '会话不存在(session_id 错误或已回收)'],\n ['409', '会话状态不允许当前操作(如已 stop 后又发音频)'],\n ].map(([code, desc]) => (\n <tr key={code}>\n <td className=\"py-2 px-3 font-mono text-xs\">{code}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6 mb-2\">流式评测结构化错误码</p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n WebSocket 的 <code className=\"bg-muted px-1 rounded\">error</code> 帧与 <code className=\"bg-muted px-1 rounded\">get_stream_result</code> 的 HTTP 错误体统一使用以下错误码,便于客户端差错分流:\n </p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">错误码</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">建议处理</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['SESSION_NOT_FOUND', '会话不存在', '重新调用 start_stream_eval / create_stream_session 创建'],\n ['SESSION_EXPIRED', '会话已过期(60s 未续流)', '重新创建会话'],\n ['INVALID_STATE', '当前状态不允许该操作', '检查调用顺序(例如已 stop 后又发音频)'],\n ['INVALID_PARAMS', '参数无效', '核对 core_type / ref_text / 采样率等'],\n ['RESUME_INVALID', 'resume_token 无效或已过期', '重新创建会话(每次重连会颁发新 token)'],\n ['AUDIO_TOO_LARGE', '单次音频超过 50MB', '压缩或切片后重传'],\n ['UPSTREAM_CONNECT', '评测引擎连接失败', '稍后重试;持续发生请联系驰声'],\n ['UPSTREAM_TIMEOUT', '评测超时', '缩短音频 / 检查网络'],\n ['UPSTREAM_EVAL_ERROR', '评测引擎返回错误', '查看 message 字段细节'],\n ['CAPACITY_FULL', '并发会话已满', '退避重试或升级配额'],\n ].map(([code, desc, fix]) => (\n <tr key={code}>\n <td className=\"py-2 px-3 font-mono text-xs\">{code}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{desc}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p className=\"text-sm text-muted-foreground\">\n 下面这些模板与{' '}\n <Link href=\"/demo\" className=\"underline underline-offset-2\">免费体验</Link>{' '}\n Demo 中实际使用的「教练 Prompt」一脉相承,全部围绕 <strong>角色 / 任务 / 方法 / 输出格式 / 语气</strong> 五段式结构,并使用占位符{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code> 直接拼接 MCP 工具返回的 JSON。\n 推荐在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">system</code> 消息里挂载,再把工具返回原文塞进\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">user</code> 消息,由 LLM 基于真实分数和音素生成可读诊断 / 练习材料。\n </p>\n\n <Callout type=\"tip\">\n <strong>使用建议:</strong>\n <ul className=\"list-disc list-inside mt-1 space-y-1\">\n <li>把 MCP 工具调用的完整 JSON(含 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">overall / dp_type / phonemes</code> 等字段)原样塞入 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,不要预先做汇总——细节越完整诊断越具体。</li>\n <li>「诊断」类模板让 LLM 输出 Markdown 给最终用户阅读;「练习」类模板要求 LLM 输出严格 JSON 给前端渲染。两步分开调用,效果远好于一次性混合。</li>\n <li>题型决定 prompt:单词题用音素级、句子题加韵律、段落题加 chunk / 整体性,半开放题套四维评级。下面按这 6 类分别给出。</li>\n </ul>\n </Callout>\n\n {/* ─── A. 诊断类 · 输出 Markdown 给学习者阅读 ─── */}\n <h4 className=\"text-sm font-semibold mt-6 mb-3 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> A. 二次诊断 · 输出 Markdown\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Sparkle className=\"h-4 w-4\" /> 1. 单词级 · 音素诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.word.score</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code>。围绕 <code className=\"bg-muted px-1 rounded font-mono\">phonemes[].dp_type</code> 找出错读音。\n </p>\n <CodeBlock filename=\"prompt-word-diagnosis.txt\">{`你是一位专业的英语发音教练。下面是驰声 MCP(en.word.score)返回的单词级评测数据:\n一个目标词、总分与发音分、以及带有 dp_type 的音素数组。\n\n## 任务\n为学习者写一份 **可直接阅读** 的发音诊断,每一条都要引用音素分数(写出 IPA 符号)。\n\n## 方法\n- 按 dp_type 分组:先看 mispron,再补充 normal 但分数 < 70 的\"边缘音\"。\n- 每个问题音素:① 典型母语干扰 → ② 你听到的声音 → ③ 一条具体的发音修正动作(下颌 / 舌位 / 气流 / 唇形)。\n- 重音符号 ˈ 标记的音节,重音错位也要单独列出(即便音素本身分数尚可)。\n- 最多列 4 条,按 **影响力从弱到强** 排序。\n\n## 输出\n精简 Markdown:可选一句总结 → 编号列表 → 单行 **🎯 优先级** 排序。\n\n## 语气\n友善、具体、可执行,不要空喊\"加油\"。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> 2. 句子级 · 节奏 + 重音诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.sent.score</code>。开头先给一行\"计分牌\",再从 phones → words → prosody。\n </p>\n <CodeBlock filename=\"prompt-sentence-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.sent.score)返回的句子级数据:refText、总分、发音子分(accuracy / integrity / fluency / rhythm)、可选 speed(WPM),以及 details[] 中的逐词得分、dp_type 与可选 phonemes[]。\n\n## 任务\n开头给一行紧凑的 **计分牌**(总分 + 各子分 + 速度),再按 **音素 → 词 → 韵律** 顺序展开诊断。\n\n## 检查清单(每项都要覆盖;不适用则写\"无明显问题\")\n1. **音素级**:每个 score < 70 或 dp_type ≠ normal 的词,展开 phonemes,命名典型错误(如 /ð/ → /d/)并给一条修正动作。\n2. **漏读 / 多读**:标注 dp_type=omit / insert,解释如何拉低 integrity。\n3. **韵律**:基于 rhythm + 错读词的重音,指出重音错位、陈述句调型平直、断句过多等。\n4. **流利度 vs 准确度**:若 fluency 高但 accuracy 低,明确指出,让学习者知道优先改哪一项。\n\n## 输出格式\nMarkdown 子标题:**Pronunciation / Prosody / Study priority**(最后一行简短排序,按 ROI 从高到低)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <FileText className=\"h-4 w-4\" /> 3. 段落级 · 音素族归并 + 完整度\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code>。把同类错音合并为\"音素族\",避免逐词复述。\n </p>\n <CodeBlock filename=\"prompt-paragraph-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.pred.score)返回的段落级数据:长 refText、总分、发音子分、可选 speed / integrity,以及大量 details[](同一个词可能在不同子句中重复出现;空 char + dp_type=omit 表示漏读)。\n\n## 任务\n对整段做 **整体诊断**:\n(a) 完整度 / 漏读,\n(b) 反复出现的 **音素族**,\n(c) 节奏与断句,\n(d) 各子分之间是否相互印证。\n\n## 检查清单\n1. **integrity / dp_type**:列出每个 omit / insert,并给出所在子句;解释长读时为什么虚词容易被吞。\n2. **音素聚类**:将弱词按共同音素问题归族(/θ/ 链、/e/ 链、/ɒ/ 等),引用 details 中的实例。\n3. **韵律**:结合 rhythm 与标点 —— 短语内停顿过多?子句边界缺失?\n4. **整体一致性**:若 fluency 正常但 accuracy 差(或反之),点明并指出对练习设计的影响。\n\n## 输出格式\nMarkdown 章节:**Summary / Completeness / Pronunciation (families) / Rhythm / Study priorities**(最后一项最多 4 条,按影响力排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <GraduationCap className=\"h-4 w-4\" /> 4. 半开放题 · IELTS 风格四维评级\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pqan.score</code>。结合学习者 transcript + 四维分数,给出可教学的\"升档建议\"。\n </p>\n <CodeBlock filename=\"prompt-semi-open.txt\">{`你是一位 **IELTS / 课堂口语风格** 的口语教练。下面是驰声 MCP(en.pqan.score)返回的半开放题数据:题目 refText、学习者 transcript、overall、四个维度分(grammar / content / fluency / pron)、speed(WPM)、以及结构化的 issues[] 标记。\n\n## 任务\n给出 **基于 rubric 的诊断**:先用一句话给出 0–100 的整体段位估计,再 **逐维度** 解释,并 **引用 transcript 中的短句**(不要复述全文)。最后给 3–5 条 **可执行的\"升档建议\"**(句型 / 模板,而不是\"多练一点\")。\n\n## 权重(要明确告诉学习者)\n- **Content ~30%** — 想法深度、举例、具体程度\n- **Grammar ~25%** — 句型多样性、主谓一致、从句\n- **Fluency ~25%** — 语速、停顿、口头禅、衔接\n- **Pronunciation ~20%** — 音素 + 可懂度\n\n## 跨维度检查\n- 若 content 最低,先给\"展开模板\",不要先抠音素。\n- 若 fluency 低 + WPM 低,区分\"思考型停顿\"与\"发音卡顿\"。\n- 若 pron 标出特定音,绑定到 transcript 里 **真实出现过的词**。\n\n## 输出格式\nMarkdown:**段位估计** → 紧凑的 **得分表格** → **\"To reach the next band\"** 编号列表(每条给出可仿写的句型)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Languages className=\"h-4 w-4\" /> 5. 中文专项 · 声调 / 儿化 / 变调\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.sent.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.pred.raw</code>。中文评测最大权重在 **声调**,必须单独检查。\n </p>\n <CodeBlock filename=\"prompt-mandarin-coach.txt\">{`你是一位专业的普通话口语教练。下面是驰声 MCP(cn.sent.raw)返回的中文句子评测数据:整句分数 + 逐字 details + 可选 phonemes + 流利 / 节奏分。\n\n## 中文评测的权重\n- **声调错误权重最高** —— 声调读错可能直接改变词义\n- dp_type=mispron 多来自:声调错、平翘舌混用、前后鼻音混用\n- 拼音对、声调错,听感仍是\"洋腔洋调\"\n\n## 检查清单(缺则写\"无明显问题\")\n1. **儿化**:phonemes 出现 r(儿化) 或 dp_type 提示时,确认是否被读成\"主音节 + 单独儿\"两拍。\n2. **轻声**:叠词、词缀、句末语气词 —— 第二字应短促轻读,无完整声调曲线。\n3. **变调**:三声连读、不 + 后字、一 + 后字,是否符合普通话规则。\n4. **节奏**:用 rhythm + 逗号位置,标记停顿过长或过碎,建议 chunk 边界。\n\n## 输出\nMarkdown 子标题:**总览 / 儿化 / 轻声 / 变调 / 节奏 / 学习优先级**(最后一项 1–3 条,按\"听感自然度提升\"排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── B. 练习类 · 输出 JSON 给前端渲染 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <Mic className=\"h-4 w-4\" /> B. 微练习生成 · 输出严格 JSON\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 6. 单词 / 句子 · 微练习\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 建议在 <strong>诊断完成后</strong> 第二轮调用:让 LLM 严格按弱项音素生成,避免\"通用绕口令包\"。\n </p>\n <CodeBlock filename=\"prompt-micro-drills.txt\">{`你仍是这位学习者的发音教练。**第二轮诊断已经完成** —— 现在严格基于诊断里点名的弱音素,生成 **1:1 对应** 的微练习(不要给通用题)。\n\n## 输出(仅返回一段合法 JSON 数组,外层不要任何文字)\n根节点必须是数组,每个元素含 category(字符串)、icon(单个 emoji)、items({ \"label\", \"content\" } 数组)。\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme drills\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Awareness\", \"icon\": \"🗣️\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Whole word\", \"icon\": \"📝\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme drills**:诊断里每个 mispron 音素至少给一对最小对立对 + 一句短绕口令 / 重复句。\n- **Awareness**:当重音偏弱时,给一条 stress / 舌位 / 下颌姿态提示。\n- **Whole word**:2–3 句自然句,目标词分别出现在句首 / 句中 / 句末。\n- 所有 content 必须可念出声;字符串内不要再嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n原始 MCP:\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Notebook className=\"h-4 w-4\" /> 7. 段落 · Chunk Map + 元认知\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 段落级练习要从\"音素\"上升到\"短语 / 整段 / 元认知录回放\"。直接对应 <code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code> 的诊断结果。\n </p>\n <CodeBlock filename=\"prompt-paragraph-drills.txt\">{`你仍是这位学习者的口语教练。**段落级二次诊断** 已经列出了漏读位置和音素族 —— 现在请生成 **整段练习**:把弱音素串成\"族\"练习、用 chunk map 把虚词粘回短语,并附一条录音 → 回放 → 对照 MCP details 的元认知步骤。\n\n## 输出(仅返回一段合法 JSON 数组)\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme families\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Chunking\", \"icon\": \"🎵\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Metacognition\", \"icon\": \"🧠\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme families**:至少 3 条,按诊断里的音素族(/θ/、/e/、/ɒ/ 等)组织最小对立对 / 词链。\n- **Chunking**:① 给一行用 [ ] 和 / 标注的 chunk map(可加大致停顿秒数);② 给一句\"把漏掉的虚词嵌入短语\"的练习(不要孤立练)。\n- **Metacognition**:一条\"录音 → 回放 → 对照 MCP details\"的 N 遍练习(N 写明,2–3 之间)。\n- 字符串只能是纯展示文本,禁止嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── C. 跨题 / 运营 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" /> C. 跨题 / 运营\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Gauge className=\"h-4 w-4\" /> 8. 多日学习报告\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 把过去 N 天的 MCP 评测结果聚合后丢给 LLM,输出适合发给学生 / 家长的周报。\n </p>\n <CodeBlock filename=\"prompt-weekly-report.txt\">{`你是一位英语口语学习顾问。下面是这位学习者过去 7 天的驰声 MCP 评测历史(按时间顺序),每条包含题型、coreType、overall、子分、weak_phonemes 摘要。\n\n## 任务\n为学习者生成一份 **周报**,目标读者:本人 + 家长 / 老师。\n强调 **趋势** 与 **下一步计划**,不要复述每一次具体分数。\n\n## 必须包含\n1. **整体趋势**:进步 / 持平 / 退步(一句话 + 简短依据)\n2. **维度变化**:accuracy / fluency / rhythm / integrity / speed 任意有数据的维度,给出\"本周均分 vs 上周\"\n3. **本周高光**:分数提升最大的题型 / 音素 / 句型\n4. **顽固问题**:连续 3 次以上未达 70 的音素或维度\n5. **下周计划**:3 条具体动作(每条对应一个 MCP 工具或一个练习类型)\n6. **结尾**:1–2 句鼓励性总结,避免空话\n\n## 风格\n- 像一位认真但不啰嗦的私教\n- 出现学生姓名时使用占位 {student_name}\n- 数字一律保留整数\n\n## 评测历史\n\\`\\`\\`json\n{history}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 9. 错误归因 · JSON 摘要\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 用于后端聚合 / 数据看板:让 LLM 把一次评测压缩成结构化摘要,方便入库统计。\n </p>\n <CodeBlock filename=\"prompt-error-summary.txt\">{`你是一个评测结果归类器。下面是驰声 MCP 一次评测的完整返回。请抽取关键信息并 **仅返回合法 JSON**(不要任何解释文字)。\n\n## Schema\n\\`\\`\\`json\n{\n \"core_type\": \"string,例如 en.sent.score\",\n \"overall\": 0,\n \"sub_scores\": { \"accuracy\": 0, \"integrity\": 0, \"fluency\": 0, \"rhythm\": 0 },\n \"weak_phonemes\": [\n { \"ipa\": \"string\", \"avg_score\": 0, \"occur\": 0, \"example_word\": \"string\" }\n ],\n \"omissions\": [{ \"after\": \"string\", \"missing\": \"string\" }],\n \"stress_issues\": [{ \"word\": \"string\", \"expected\": \"string\", \"actual\": \"string\" }],\n \"primary_issue_tag\": \"phoneme|stress|fluency|integrity|content|none\",\n \"next_action_hint\": \"string,10 字以内,例如 \\\"练 /θ/ 对立对\\\"\"\n}\n\\`\\`\\`\n\n## 规则\n- weak_phonemes 只收 avg_score < 70 的项,最多 5 项,按 occur 降序。\n- 不要复述 refText,不要 markdown。\n- 字段缺失时使用 null 或空数组,不要省略 key。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n <Callout type=\"info\">\n <strong>怎么接到 MCP 工具调用?</strong>\n <br />\n 以「直调 chat.completions + 动态 tools」为例:第一轮 LLM 调出 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code> 工具,把工具 result(即 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">tool</code> 消息内容)原样填入上面任意「诊断」模板的{' '}\n <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,作为第二轮 user 消息发回;拿到诊断结论再走第三轮,套「微练习」模板得到 JSON。三轮调用即可串起 <strong>评测 → 诊断 → 练习</strong> 全链路。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频过期:</strong>上传后 5 分钟内有效。建议在上传后 3 分钟内完成评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>服务繁忙:</strong>使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>录音中断:</strong>WebSocket 断线后代理会自动重连。如持续失败,检查网络连接和 SoX 安装。</span></li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>优先使用流式评测:</strong>实时录音模式跳过了文件上传环节,端到端延迟比文件评测低 30-50%。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频压缩:</strong>文件评测时,上传前将音频转为 16kHz 单声道 mp3,可减少 60-80% 的上传体积。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>选择合适的工具:</strong>单词评测比段落评测快 3-5 倍。如只需单词级分数,不要用句子评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>本地文件路径最便捷:</strong>使用 <code className=\"bg-muted px-1 rounded text-xs\">audio_file_path</code> 参数传入本地路径,代理自动处理编码,无需手动转 Base64。</span></li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Globe} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传(MCP 模式文件评测使用)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP 模式 · JSON-RPC(Streamable HTTP)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/audio/{session_id}'}</td><td className=\"py-2 px-3\">WS</td><td className=\"py-2 px-3\">MCP 模式 · 流式音频 WebSocket</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/v1/functions</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">functioncalling 模式 · 列出全部评测函数</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/v1/call</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">functioncalling 模式 · 一次性评测 / 创建流式会话 / 拉取流式结果</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/eval/{session_id}'}</td><td className=\"py-2 px-3\">WS</td><td className=\"py-2 px-3\">functioncalling 模式 · 流式音频 WebSocket</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制', '500MB', '最大音频存储空间'],\n ['最大排队', '10', '等待处理的请求数'],\n ['最大并发', '3', '同时处理的评测数'],\n ['音频有效期', '5 分钟', '上传后未评测自动过期'],\n ['文件大小限制', '50MB', '单个音频文件最大'],\n ].map(([k, v, d]) => (\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">\n 上表为托管侧<strong>技术默认</strong>;计费与试用/商用<strong>调用配额、并发档位</strong>以{' '}\n <Link href=\"/dashboard/plans\" className=\"text-foreground underline underline-offset-2\">\n 会员套餐\n </Link>\n 说明为准,与上表独立。\n </p>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <Callout type=\"info\">\n <strong>当前线上版本:v1.0.0</strong>(网站未正式上线)。以下变更项为开发阶段规划记录,以正式发布公告为准。\n </Callout>\n <div className=\"space-y-3 mt-4\">\n {[\n { ver: 'v1.0.0', date: 'TBD', desc: '当前线上版本(网站未正式上线)' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </DocsShell>\n );\n}\n\nexport default function DocsPage() {\n const locale = useLocale();\n return locale.startsWith('zh') ? <DocsPageZh /> : <DocsPageEn />;\n}\n"}, "d5b192666f386dbc7610989f2c6eb696d348b5f9": {"t": 1785765789.6910784, "n": 190302, "h": "4c05f5a612d811d1", "c": "'use client';\n\nimport { useLocale } from 'next-intl';\nimport { Link } from '@/i18n/routing';\nimport { DocsShell, type NavGroup } from './docs-shell';\nimport {\n CodeBlock,\n DocSection,\n SubDoc,\n ParamTable,\n Callout,\n FlowStep,\n ToolTable,\n} from './docs-shared';\nimport { DocsEnglishBody } from './docs-content-en';\nimport {\n BookOpen,\n Code2,\n Zap,\n FileText,\n Terminal,\n Globe,\n Mic,\n MessageSquare,\n BarChart3,\n AlertTriangle,\n Radio,\n FolderOpen,\n Workflow,\n Sparkles,\n Plug,\n Bot,\n Building2,\n Check,\n Sparkle,\n Languages,\n GraduationCap,\n Notebook,\n Gauge,\n ListChecks,\n Network,\n} from 'lucide-react';\n\n/* ─── Navigation Structure ─── */\n\nconst NAV = [\n { id: 'concept', icon: Workflow, label: 'MCP 工作原理', children: [\n { id: 'why-mcp', label: '为什么需要 MCP' },\n { id: 'scenario-walkthrough', label: '场景走查 · 6 步' },\n { id: 'dev-responsibility', label: '开发者只做两件事' },\n { id: 'integration-paths', label: '三种接入姿势' },\n ]},\n { id: 'architecture', icon: Terminal, label: '架构说明', children: [\n { id: 'arch-overview', label: '两种接入模式总览' },\n { id: 'arch-mcp', label: 'MCP 模式架构' },\n { id: 'arch-rest', label: 'functioncalling 模式架构' },\n { id: 'transport', label: '传输协议对比' },\n ]},\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'requirements', label: '系统要求' },\n { id: 'install', label: '安装方式' },\n { id: 'env-vars', label: '环境变量' },\n ]},\n { id: 'config', icon: Plug, label: '接入 · 直接用(零代码)', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: '扣子(Coze)' },\n { id: 'config-coze-workflow', label: 'Coze 工作流' },\n { id: 'config-ai-workspace', label: 'AI 工作台 · 豆包 / 飞书 / WorkBuddy / 钉钉' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: '其他 MCP 客户端' },\n ]},\n { id: 'config-code', icon: Code2, label: '接入 · 编程用 MCP(SDK / 代码)', children: [\n { id: 'code-mcp-sdk', label: '① 官方 MCP 客户端库' },\n { id: 'code-agent-frameworks', label: '② LangChain / Mastra / Agents SDK' },\n { id: 'code-function-calling', label: '③ 直调 chat.completions · 动态 tools' },\n { id: 'code-selfhosted-agent', label: '④ 自研后端 Agent' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM(智谱)' },\n { id: 'llm-kimi', label: 'KIMI(Moonshot)' },\n { id: 'llm-doubao', label: '豆包 Seed 2.0(火山方舟)' },\n { id: 'llm-openai', label: 'OpenAI(GPT-5.1)' },\n { id: 'llm-claude', label: 'Claude(Anthropic)' },\n { id: 'llm-gemini', label: 'Gemini(Google)' },\n { id: 'llm-comparison', label: '模型对比速查' },\n ]},\n { id: 'config-rest', icon: Network, label: '接入 · 非 MCP 原生 REST(cvx_fc)', children: [\n { id: 'rest-overview', label: 'functioncalling 模式总览' },\n { id: 'rest-catalog', label: '函数目录 · GET /v1/functions' },\n { id: 'rest-oneshot', label: '一次性文件评测' },\n { id: 'rest-streaming', label: '流式评测 · 三步骤' },\n { id: 'rest-examples', label: 'JS / Android 完整示例' },\n { id: 'rest-resume', label: '断线恢复 · 兜底拉取' },\n ]},\n { id: 'eval-modes', icon: Radio, label: '评测模式', children: [\n { id: 'stream-eval', label: '实时录音评测' },\n { id: 'stream-flow', label: '流式评测流程' },\n { id: 'file-eval', label: '音频文件评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'tools-en', label: '英文评测工具' },\n { id: 'tools-cn', label: '中文评测工具' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n { id: 'service-info', icon: Globe, label: '服务信息', children: [\n { id: 'endpoints', label: '服务端点' },\n { id: 'limits', label: '服务限制' },\n { id: 'changelog', label: '更新日志' },\n ]},\n];\n\nconst NAV_EN: NavGroup[] = [\n { id: 'concept', icon: Workflow, label: 'How MCP works', children: [\n { id: 'why-mcp', label: 'Why MCP' },\n { id: 'scenario-walkthrough', label: 'Scenario · 6 steps' },\n { id: 'dev-responsibility', label: 'Two developer tasks' },\n { id: 'integration-paths', label: 'Three integration styles' },\n ]},\n { id: 'architecture', icon: Terminal, label: 'Architecture', children: [\n { id: 'arch-overview', label: 'Two integration modes' },\n { id: 'arch-mcp', label: 'MCP mode architecture' },\n { id: 'arch-rest', label: 'Function-calling architecture' },\n { id: 'transport', label: 'Transport comparison' },\n ]},\n { id: 'quick-start', icon: Zap, label: 'Quick start', children: [\n { id: 'overview', label: 'Overview' },\n { id: 'requirements', label: 'Requirements' },\n { id: 'install', label: 'Install' },\n { id: 'env-vars', label: 'Environment' },\n ]},\n { id: 'config', icon: Plug, label: 'Zero-code clients', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: 'Coze' },\n { id: 'config-coze-workflow', label: 'Coze workflows' },\n { id: 'config-ai-workspace', label: 'Enterprise AI workspaces' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: 'Other MCP clients' },\n ]},\n { id: 'config-code', icon: Code2, label: 'MCP · SDK / code integration', children: [\n { id: 'code-mcp-sdk', label: '① Official MCP SDK' },\n { id: 'code-agent-frameworks', label: '② Agent frameworks' },\n { id: 'code-function-calling', label: '③ chat.completions · dynamic tools' },\n { id: 'code-selfhosted-agent', label: '④ Custom backend agent' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM (Zhipu)' },\n { id: 'llm-kimi', label: 'KIMI (Moonshot)' },\n { id: 'llm-doubao', label: 'Doubao Seed 2.0 (Volcano Ark)' },\n { id: 'llm-qwen', label: 'Qwen (Alibaba)' },\n { id: 'llm-openai', label: 'OpenAI (GPT-5.1)' },\n { id: 'llm-claude', label: 'Claude (Anthropic)' },\n { id: 'llm-gemini', label: 'Gemini (Google)' },\n { id: 'llm-comparison', label: 'Model comparison' },\n ]},\n { id: 'config-rest', icon: Network, label: 'Non-MCP REST (cvx_fc)', children: [\n { id: 'rest-overview', label: 'Function-calling overview' },\n { id: 'rest-catalog', label: 'Function catalog · GET /v1/functions' },\n { id: 'rest-oneshot', label: 'One-shot file eval' },\n { id: 'rest-streaming', label: 'Streaming · 3 steps' },\n { id: 'rest-examples', label: 'JS / Android samples' },\n { id: 'rest-resume', label: 'Resume & fallback' },\n ]},\n { id: 'eval-modes', icon: Radio, label: 'Evaluation modes', children: [\n { id: 'stream-eval', label: 'Streaming mic' },\n { id: 'stream-flow', label: 'Streaming flow' },\n { id: 'file-eval', label: 'Audio files' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API reference', children: [\n { id: 'tools-en', label: 'English tools' },\n { id: 'tools-cn', label: 'Chinese tools' },\n { id: 'result-fields', label: 'Result fields' },\n { id: 'error-codes', label: 'Errors' },\n ]},\n { id: 'best-practices', icon: FileText, label: 'Best practices', children: [\n { id: 'prompt-templates', label: 'Prompt templates' },\n { id: 'error-handling', label: 'Errors & retries' },\n { id: 'performance', label: 'Performance' },\n ]},\n { id: 'service-info', icon: Globe, label: 'Service info', children: [\n { id: 'endpoints', label: 'Endpoints' },\n { id: 'limits', label: 'Limits' },\n { id: 'changelog', label: 'Changelog' },\n ]},\n];\n\n/* ─── Page(英文 / 中文拆成两个组件,避免切换语言时 Hooks 数量变化导致白屏)─── */\n\nfunction DocsPageEn() {\n return (\n <DocsShell\n nav={NAV_EN}\n backLabel=\"Back to Home\"\n title=\"Developer documentation\"\n subtitle=\"Chivox Speech Evaluation MCP · chivox-local-mcp\"\n >\n <DocsEnglishBody />\n </DocsShell>\n );\n}\n\nfunction DocsPageZh() {\n return (\n <DocsShell\n nav={NAV}\n backLabel=\"返回首页\"\n title=\"开发者文档\"\n subtitle=\"驰声语音评测 MCP · chivox-local-mcp\"\n >\n {/* ══════ MCP 工作原理 ══════ */}\n <DocSection id=\"concept\" icon={Workflow} title=\"MCP 工作原理\">\n\n <SubDoc id=\"why-mcp\" title=\"为什么需要 MCP\">\n <p>\n 大模型(GPT / Claude / Gemini / 通义千问 等)本质上是<strong>文字大脑</strong>:它擅长读和写,但天生<strong>听不到</strong>音频、<strong>打不开</strong>你的数据库、<strong>调不通</strong>第三方 API。\n </p>\n <p>\n <strong>MCP(Model Context Protocol)</strong>是一套由 Anthropic 主导、业界共同推进的开放协议,它像一根\"对讲机\":让大模型可以在需要时主动去调用外部能力(工具、数据源、服务),然后把结果接回来继续对话。\n </p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <AlertTriangle className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n <span className=\"text-sm font-semibold\">没有 MCP 的做法</span>\n </div>\n <ul className=\"text-xs text-muted-foreground space-y-1.5 leading-relaxed\">\n <li>· 研究评测服务的 REST 文档</li>\n <li>· 自己写鉴权 / 上传 / 轮询代码</li>\n <li>· 把结果序列化后塞进 prompt</li>\n <li>· 每换一个大模型都得重写一遍</li>\n </ul>\n </div>\n <div className=\"rounded-lg border-2 border-foreground/60 p-4 bg-foreground/[0.02]\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Check className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">用驰声 MCP 之后</span>\n </div>\n <ul className=\"text-xs space-y-1.5 leading-relaxed\">\n <li>· 填一行 MCP Server 地址</li>\n <li>· 大模型自动发现&amp;调用评测工具</li>\n <li>· 换任何支持 MCP 的大模型都能用</li>\n <li>· 不写一行调用代码</li>\n </ul>\n </div>\n </div>\n <Callout type=\"info\">\n MCP 让\"接入语音评测\"从一个\"后端工程\"变成了一个\"配置项\"。这也是为什么 Cursor、Claude Desktop、Dify、Coze、字节豆包、飞书智能助手都已经原生支持 MCP。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"scenario-walkthrough\" title=\"场景走查 · 从一段录音到温柔反馈\">\n <p>\n 想象一个具体场景:一位开发者用 Claude 做了一款 <strong>「AI 雅思口语陪练」小程序</strong>,接入驰声语音评测 MCP。当用户发来一段录音,整个链路是这样的:\n </p>\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-5 my-4 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong className=\"text-foreground\">配置阶段</strong>(开发者 · 一次性)</p>\n <p className=\"text-muted-foreground mt-1\">在 AI 后端的配置文件里填一行驰声 MCP Server 地址。大模型启动时自动发现:「我多了一个叫<strong>语音评测</strong>的工具」。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong className=\"text-foreground\">接收用户指令</strong>(终端用户)</p>\n <p className=\"text-muted-foreground mt-1\">小程序用户发来一段自我介绍录音:「帮我听听,发音哪里有问题?」后端把「文字 + 录音链接」打包给大模型。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong className=\"text-foreground\">大模型自主思考</strong>(LLM · 最核心的魔法)</p>\n <div className=\"text-muted-foreground mt-1 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed\">\n 「用户让我听发音。」<br />\n 「但我只是文字大脑,我听不到。」<br />\n 「等等 —— 开发者给了我一个叫 <code className=\"bg-muted px-1 rounded\">chivox_voice_eval</code> 的 MCP 工具!」\n </div>\n </FlowStep>\n <FlowStep title=\"4\">\n <p><strong className=\"text-foreground\">MCP 协议 · 自动调用</strong>(LLM → 驰声)</p>\n <p className=\"text-muted-foreground mt-1\">大模型自动按 MCP 标准构造 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools/call</code> 请求,把录音链接和参考文本发到驰声服务。<span className=\"text-foreground\">开发者不需要写任何调用代码。</span></p>\n </FlowStep>\n <FlowStep title=\"5\">\n <p><strong className=\"text-foreground\">驰声服务处理</strong>(评测引擎)</p>\n <p className=\"text-muted-foreground mt-1\">驰声对音频进行考试级打分,把结构化结果通过 MCP 原路返回:<code className=\"bg-muted px-1 rounded text-xs font-mono\">overall 85</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">/θ/ 音发音错误</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">流利度 88</code>。</p>\n </FlowStep>\n <FlowStep title=\"6\">\n <p><strong className=\"text-foreground\">大模型整合并输出反馈</strong>(LLM)</p>\n <p className=\"text-muted-foreground mt-1\">大模型把冷冰冰的分数翻译成温柔反馈:</p>\n <div className=\"text-foreground/80 mt-2 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed italic\">\n 「你的自我介绍很棒!综合 85 分 🎉 不过 <strong>think</strong> 这个词的 /θ/ 咬舌音不太标准,我们一起来练一下……」\n </div>\n </FlowStep>\n </div>\n <Callout type=\"tip\">\n <strong>整条链路里,MCP 只出现在 ① 和 ④</strong>:① 让开发者一次性告诉大模型「你多了一个工具」,④ 让大模型可以自动调用它。这两步的标准化,就是 MCP 的全部价值。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"dev-responsibility\" title=\"开发者只做两件事\">\n <p>听起来复杂,但对开发者来说只需要两件事:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">1</span>\n <span className=\"text-sm font-semibold\">填一次 MCP 配置</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 在 AI 后端的配置里填入驰声 MCP Server 地址和 API Key,就这一次,以后新加评测能力完全免开发。\n </p>\n <CodeBlock filename=\"mcp-config.json\" lang=\"json\">{`{\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"apiKey\": \"sk-••••••••••\"\n }\n}`}</CodeBlock>\n </div>\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">2</span>\n <span className=\"text-sm font-semibold\">写一段 System Prompt</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 告诉大模型这个工具是做什么的、什么时候调用、结果怎么转成用户友好的反馈。\n </p>\n <CodeBlock filename=\"system-prompt.md\">{`你是一位英语口语教练。\n当用户发来录音时:\n1. 调用 chivox_voice_eval 评分\n2. 聚焦 <70 分的音素家族\n3. 用高情商话术给出练习建议`}</CodeBlock>\n </div>\n </div>\n <p className=\"text-muted-foreground\">\n 其余所有事情 —— 音频上传、鉴权、工具发现、调用时机、返回格式、结果整合 —— 都由 MCP 协议和大模型自己搞定。\n </p>\n </SubDoc>\n\n <SubDoc id=\"integration-paths\" title=\"三种典型接入姿势\">\n <p>根据你的技术栈,有三种等价的接入方式,底层都走完全相同的 MCP 协议:</p>\n\n {/* ── 姿势一:可视化 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Sparkles className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势一 · 可视化拖拽(扣子 / Coze / 飞书智能助手)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合产品经理、运营、不写后端代码的开发者。整个过程像在手机上装 App。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>找到入口</strong>:在扣子平台的 <strong>「插件 / 工具」</strong> 配置区,点击<strong>「添加 MCP 服务」</strong>。</li>\n <li><strong>填两个字段</strong>(这也是你未来会从驰声这边拿到的):\n <div className=\"mt-2 overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium w-32\">服务器地址</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权密钥</td><td className=\"py-2 px-3 font-mono text-xs\">sk-•••••••••• <span className=\"text-muted-foreground ml-2\">(由驰声分配,用于计费与识别)</span></td></tr>\n </tbody>\n </table>\n </div>\n </li>\n <li><strong>平台自动发现能力</strong>:扣子会访问你填的地址,读取 MCP Server 自描述的\"说明书\"。瞬间明白:「这是个语音评测工具,需要传音频,会返发音分」。</li>\n <li><strong>右侧测试</strong>:在平台的聊天框里发一段语音,大模型就会自动调用工具。<strong>整个过程不需要写一行代码。</strong></li>\n </ol>\n </div>\n\n {/* ── 姿势二:代码框架 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Code2 className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势二 · 代码框架配置(Dify / LangChain / 自研后端)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合做独立 APP / 小程序、用 Dify 或 LangChain 写 Agent 的极客开发者。核心就是往配置文件里加一段 JSON。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>打开配置文件</strong>(项目根目录下的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp.json</code> 或框架指定的配置路径)。</li>\n <li><strong>在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code> 下增加一个节点</strong>:\n <CodeBlock filename=\"mcp.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"env\": {\n \"API_KEY\": \"你的专属密钥\"\n }\n }\n }\n}`}</CodeBlock>\n </li>\n <li><strong>重启后端服务</strong>。大模型的底层调度器会在启动时自动连上驰声 MCP Server,把 16 种评测工具注册成<strong>随时待命</strong>的 tools。</li>\n <li><strong>触发调用</strong>:用户发来录音后,大模型看到 system prompt 的引导,自动选择合适的工具(单词/句子/段落/半开放)调用。</li>\n </ol>\n </div>\n\n {/* ── 姿势三:Function Calling 直连(分 A / B 两种做法) ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Terminal className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势三 · 原生 Function Calling(豆包 / DeepSeek / OpenAI 兼容 API)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合不用任何 Agent 框架、直接调用 LLM API 的后端开发者。豆包 1.5+ / DeepSeek / GPT / Qwen 等模型都走标准 <code className=\"bg-muted px-1 rounded text-xs font-mono\">chat.completions</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> 参数。这里又分 A / B 两种做法,<strong>B 才是生产推荐</strong>。\n </p>\n\n <Callout type=\"warning\">\n 常见疑问:<em>\"要不要手写一个 tool 定义?\"</em><br />\n — <strong>姿势一 / 二不用</strong>(Coze、Dify、火山方舟控制台、Claude Desktop 会自动从 MCP Server 拉工具列表)。<br />\n — <strong>姿势三看你选 A 还是 B</strong>:A 要手写,B 用 MCP 客户端库自动拉。\n </Callout>\n\n {/* ── 3-A:手写 tool schema ── */}\n <div className=\"mt-4 rounded-md border border-border/40 bg-muted/20 p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold\">3-A · 手写 tool schema(最快验证,不推荐生产)</p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20\">快速 Demo</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 自己写一段 JSON Schema 告诉豆包\"有个工具叫 xxx,参数长这样\",豆包返回 tool_call 后你再手动去调驰声 MCP。<strong>缺点</strong>:驰声有 16 个评测工具,你每加一个题型都要改代码。\n </p>\n <CodeBlock filename=\"3a_hardcode_tool.py\" lang=\"python\">{`import os, json\nfrom openai import OpenAI\n\nclient = OpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv('ARK_API_KEY'),\n)\n\n# ─── 手写一段 tool schema(不推荐:16 个工具就要写 16 段)───\ntools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英语单词发音评测。当用户让你评价英语发音并提供录音链接时调用。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"audio_url\": {\"type\": \"string\", \"description\": \"录音 URL\"},\n \"reference_text\": {\"type\": \"string\", \"description\": \"参考文本,如 Apple\"},\n },\n \"required\": [\"audio_url\", \"reference_text\"]\n }\n }\n}]\n\nresp = client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,拿到评测结果后温柔地反馈,不要直接报分数。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n)\n\nmsg = resp.choices[0].message\nif msg.tool_calls:\n args = json.loads(msg.tool_calls[0].function.arguments)\n # ↓↓↓ 这里手动调驰声 MCP,再把结果回填给豆包做第二趟 ↓↓↓\n # result = requests.post(\"https://speech-eval.site/mcp\", json={...})`}</CodeBlock>\n </div>\n\n {/* ── 3-B:MCP 自动发现 ── */}\n <div className=\"mt-3 rounded-md border border-foreground/30 bg-foreground/[0.03] dark:bg-white/[0.03] p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold flex items-center gap-1.5\">\n 3-B · 用 MCP 客户端库自动发现 <Sparkles className=\"h-3 w-3 text-foreground\" />\n </p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-foreground text-background\">生产推荐</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 启动时连上驰声 MCP Server,调一次 <code className=\"bg-muted px-1 rounded\">tools/list</code>,把 16 个评测工具自动转成 Function Calling 的 tools 格式。<strong>驰声加新工具,你代码不用改</strong>。这才是 MCP 协议的真正价值。\n </p>\n <CodeBlock filename=\"3b_mcp_autodiscover.py\" lang=\"python\">{`import asyncio, os\nfrom openai import AsyncOpenAI\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\n\nclient = AsyncOpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv(\"ARK_API_KEY\"),\n)\n\nasync def main():\n # ─── 1. 连驰声 MCP Server,自动拉取全部 16 个工具 ───\n async with streamablehttp_client(\n \"https://speech-eval.site/mcp\",\n headers={\"Authorization\": f\"Bearer {os.getenv('CHIVOX_KEY')}\"},\n ) as (read, write, _):\n async with ClientSession(read, write) as session:\n await session.initialize()\n mcp_tools = (await session.list_tools()).tools # ← 一行拿到全部工具\n\n # ─── 2. MCP schema → OpenAI Function Calling schema ───\n tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema, # MCP 直接给 JSON Schema\n }\n } for t in mcp_tools]\n\n # ─── 3. 正常调豆包,tools 由 MCP 动态注入 ───\n resp = await client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,收到评测结果后温柔反馈。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n )\n\n # ─── 4. 折返跑:豆包选好工具后,直接走 MCP 调驰声 ───\n msg = resp.choices[0].message\n if msg.tool_calls:\n call = msg.tool_calls[0]\n result = await session.call_tool(\n call.function.name,\n arguments=__import__(\"json\").loads(call.function.arguments),\n )\n # 把 result 回填给豆包做第二趟 chat.completions 即可\n\nasyncio.run(main())`}</CodeBlock>\n <p className=\"text-[11px] text-muted-foreground mt-3 leading-relaxed\">\n 想要更省心?直接用 <a href=\"https://github.com/modelcontextprotocol/python-sdk\" target=\"_blank\" rel=\"noreferrer\" className=\"text-foreground underline underline-offset-2\">官方 mcp python sdk</a>、<code className=\"bg-muted px-1 rounded text-xs\">mcp-use</code>、<code className=\"bg-muted px-1 rounded text-xs\">openai-agents</code>,或火山官方 <code className=\"bg-muted px-1 rounded text-xs\">Arkitect</code> 高代码 SDK,它们都把上面这段\"MCP → Function Calling\"的桥接封装好了。\n </p>\n </div>\n </div>\n\n <Callout type=\"info\">\n 三种姿势走的是完全相同的 MCP 协议,能力完全等价。选型建议:<strong>产品经理 / 原型验证用姿势一</strong>(Coze 填表单);<strong>AI 客户端用户用姿势二</strong>(Cursor / Claude Desktop 填 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">mcp.json</code>);<strong>后端生产服务用姿势三-B</strong>(MCP 客户端库自动发现工具,不要手写 tool schema)。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 架构说明 ══════ */}\n <DocSection id=\"architecture\" icon={Terminal} title=\"架构说明\">\n\n <SubDoc id=\"arch-overview\" title=\"两种接入模式总览\">\n <p>驰声语音评测对外提供<strong>两条并行入口</strong>,底层指向同一个评测引擎,但面向不同的客户端环境,按能力选其一即可:</p>\n\n <div className=\"grid md:grid-cols-2 gap-4 my-5\">\n <div className=\"rounded-xl border-2 border-emerald-500/40 bg-emerald-500/5 p-5\">\n <p className=\"font-semibold mb-2 flex items-center gap-2\">\n <Plug className=\"h-4 w-4 text-emerald-600 dark:text-emerald-400\" />\n MCP 模式(推荐首选)\n </p>\n <ul className=\"text-xs space-y-1.5 text-muted-foreground\">\n <li>• JSON-RPC 2.0 · Streamable HTTP / stdio</li>\n <li>• 所有支持 MCP 的 IDE / 桌面 AI / Agent 框架 <strong className=\"text-foreground\">零代码即用</strong></li>\n <li>• 工具列表自动注入,新增题型<strong className=\"text-foreground\">业务侧不用改</strong></li>\n <li>• 本地代理 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">chivox-local-mcp</code> 支持麦克风实时录音</li>\n </ul>\n <p className=\"text-xs mt-2\"><strong>典型客户端</strong>:Cursor / Claude Desktop / Coze / Dify / LangChain / 自研后端 Agent</p>\n </div>\n <div className=\"rounded-xl border-2 border-sky-500/40 bg-sky-500/5 p-5\">\n <p className=\"font-semibold mb-2 flex items-center gap-2\">\n <Network className=\"h-4 w-4 text-sky-600 dark:text-sky-400\" />\n functioncalling 模式(非 MCP · cvx_fc)\n </p>\n <ul className=\"text-xs space-y-1.5 text-muted-foreground\">\n <li>• OpenAI function-calling 风格 · 纯 REST + WebSocket</li>\n <li>• <strong className=\"text-foreground\">不依赖任何 MCP SDK</strong>,普通 HTTP 客户端即可打</li>\n <li>• 内置 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">resume_token</code> 断线续播、<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">intermediate</code> 中间结果、<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">backpressure</code> 反压</li>\n <li>• 为不能 / 不想引入 MCP 的客户端环境保留兜底通道</li>\n </ul>\n <p className=\"text-xs mt-2\"><strong>典型客户端</strong>:原生 Android / iOS / Flutter、微信 / 支付宝小程序、老版本 Java / PHP 后端</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">怎么选?</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">你的客户端环境</th>\n <th className=\"text-left py-2 px-3 font-medium\">建议</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3\">Cursor / Claude Desktop / 各类 AI IDE 能装 MCP</td><td className=\"py-2 px-3 text-emerald-600 dark:text-emerald-400\">MCP 模式</td></tr>\n <tr><td className=\"py-2 px-3\">Python / Node / Java 后端能引入 MCP SDK</td><td className=\"py-2 px-3 text-emerald-600 dark:text-emerald-400\">MCP 模式</td></tr>\n <tr><td className=\"py-2 px-3\">Coze / Dify / n8n 等可视化平台</td><td className=\"py-2 px-3 text-emerald-600 dark:text-emerald-400\">MCP 模式</td></tr>\n <tr><td className=\"py-2 px-3\">原生 Android / iOS App,想直接打 HTTP + WS</td><td className=\"py-2 px-3 text-sky-600 dark:text-sky-400\">functioncalling 模式</td></tr>\n <tr><td className=\"py-2 px-3\">微信 / 支付宝 / 抖音小程序</td><td className=\"py-2 px-3 text-sky-600 dark:text-sky-400\">functioncalling 模式</td></tr>\n <tr><td className=\"py-2 px-3\">老系统 / 封闭运行时,不想引入 MCP 依赖</td><td className=\"py-2 px-3 text-sky-600 dark:text-sky-400\">functioncalling 模式</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"warning\">\n 同一个会话<strong>二选一、不要混用</strong>:MCP 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">/ws/audio/</code> 和 functioncalling 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">/ws/eval/</code> 属于不同的会话 ID 空间。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"arch-mcp\" title=\"MCP 模式 · 数据流与组件\">\n <p>MCP 模式下有两种部署形态,底层均走 JSON-RPC 2.0,区别只在客户端离服务端有没有一层本地代理:</p>\n\n <p className=\"font-semibold mt-4\">形态 A · 远程直连(零代码)</p>\n <p className=\"text-sm text-muted-foreground\">适合 Cursor / Coze / Dify / n8n 等支持 Streamable HTTP 的客户端,<strong>无本地依赖</strong>。</p>\n <div className=\"flex flex-wrap items-center justify-center gap-4 my-4\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-5 py-3 text-center min-w-[130px]\">\n <p className=\"text-xs font-semibold mb-0.5\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Cursor / Coze /<br/>Dify / n8n …</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">Streamable HTTP<br/>JSON-RPC 2.0</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-5 py-3 text-center min-w-[130px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-0.5\">驰声 MCP Server</p>\n <p className=\"text-[11px] text-muted-foreground\">/mcp · /ws/audio<br/>评测引擎</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">形态 B · 本地代理(支持麦克风实时录音)</p>\n <p className=\"text-sm text-muted-foreground\">适合 Claude Desktop / Claude Code 等以 stdio 挂载 MCP 的客户端,需要本地实时录音时走这条路径。</p>\n <div className=\"flex flex-wrap items-center justify-center gap-3 my-4\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-4 py-3 text-center min-w-[120px]\">\n <p className=\"text-xs font-semibold mb-0.5\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Claude Desktop /<br/>Claude Code</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">stdio</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-4 py-3 text-center min-w-[120px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-0.5\">chivox-local-mcp</p>\n <p className=\"text-[11px] text-muted-foreground\">本地代理 · SoX 录音</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP / WS</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-4 py-3 text-center min-w-[120px]\">\n <p className=\"text-xs font-semibold mb-0.5\">Remote Chivox</p>\n <p className=\"text-[11px] text-muted-foreground\">MCP Server<br/>评测引擎</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">标准数据流</p>\n <ol className=\"list-decimal list-inside space-y-1.5 text-sm ml-1\">\n <li><code className=\"bg-muted px-1 rounded text-xs\">initialize</code> → 协商协议版本与能力</li>\n <li><code className=\"bg-muted px-1 rounded text-xs\">tools/list</code> → 拉取 16 个评测工具的 JSON Schema</li>\n <li><strong>文件模式</strong>:<code className=\"bg-muted px-1 rounded text-xs\">tools/call</code> → 服务端执行评测 → 返回 JSON-RPC 响应</li>\n <li><strong>实时模式</strong>:<code className=\"bg-muted px-1 rounded text-xs\">create_stream_session</code> → 走 <code className=\"bg-muted px-1 rounded text-xs\">/ws/audio/{'{session_id}'}</code> 推流 → <code className=\"bg-muted px-1 rounded text-xs\">stream_eval_result</code> 收结构化评分</li>\n </ol>\n\n <p className=\"font-semibold mt-6\">原始 JSON-RPC 请求示例(自建客户端可直接抄)</p>\n <p className=\"text-xs text-muted-foreground mb-3\">\n 如果你不使用官方 MCP SDK 而是直接通过 <code className=\"bg-muted px-1 rounded\">POST /mcp</code> 自己拼 JSON-RPC 2.0 报文,最小可用的三条请求如下。所有请求均需携带 <code className=\"bg-muted px-1 rounded text-xs\">Authorization: Bearer &lt;your_api_key&gt;</code>。\n </p>\n\n <p className=\"text-sm font-semibold mt-4\">① 初始化 · <code>initialize</code></p>\n <CodeBlock filename=\"initialize.json\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"method\": \"initialize\",\n \"params\": {\n \"protocolVersion\": \"2024-11-05\",\n \"capabilities\": {},\n \"clientInfo\": { \"name\": \"my-client\", \"version\": \"1.0.0\" }\n }\n}`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">成功后服务端返回协议版本、服务端能力、服务端信息,此后方可调用其他方法。</p>\n\n <p className=\"text-sm font-semibold mt-4\">② 拉取工具清单 · <code>tools/list</code></p>\n <CodeBlock filename=\"tools-list.json\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"id\": 2,\n \"method\": \"tools/list\",\n \"params\": {}\n}`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">返回全部 16 个评测工具(英 10 + 中 6)+ 2 个流式工具,含名字、描述、参数 JSON Schema。客户端应缓存此响应,后续按需展示 / 桥接给 LLM。</p>\n\n <p className=\"text-sm font-semibold mt-4\">③ 调用工具 · <code>tools/call</code></p>\n <CodeBlock filename=\"tools-call.json\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"id\": 3,\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"en_word_eval\",\n \"arguments\": {\n \"ref_text\": \"hello\",\n \"audio_base64\": \"<base64 encoded audio>\",\n \"accent\": 2,\n \"rank\": 100\n }\n }\n}`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">\n 成功响应放在 <code className=\"bg-muted px-1 rounded\">result.content[0].text</code>(JSON 字符串形式,内部就是评测结果结构);失败则返回 <code className=\"bg-muted px-1 rounded\">error.code</code> + <code className=\"bg-muted px-1 rounded\">error.message</code>。\n </p>\n\n <Callout type=\"tip\">\n 用 Python / TypeScript 官方 MCP SDK 的用户<strong>不需要关心以上 JSON-RPC 细节</strong>——SDK 已经封装好 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">session.initialize()</code> / <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">session.list_tools()</code> / <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">session.call_tool()</code>。这里仅供裸协议实现或排障时参考。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"arch-rest\" title=\"functioncalling 模式 · 数据流与组件\">\n <p>没有代理、没有 SDK,客户端 ↔ 服务端只有两条链路:一条 REST 管会话 / 文件评测,一条 WebSocket 推音频帧。</p>\n\n <div className=\"flex flex-wrap items-center justify-center gap-3 my-5\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-4 py-3 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-0.5\">客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">原生 App / 小程序 /<br/>老版后端 / 纯前端</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP REST<br/>/v1/call · /v1/functions</p>\n </div>\n <div className=\"rounded-xl border-2 border-sky-500/50 bg-sky-500/5 px-4 py-3 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold text-sky-600 dark:text-sky-400 mb-0.5\">cvx_fc 服务</p>\n <p className=\"text-[11px] text-muted-foreground\">function-calling 网关<br/>评测引擎</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">WebSocket<br/>/ws/eval/&#123;id&#125;</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-4 py-3 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-0.5\">客户端推流</p>\n <p className=\"text-[11px] text-muted-foreground\">麦克风 / 分片缓冲</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">三条典型数据流</p>\n <div className=\"space-y-3 my-3\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-sm font-semibold mb-1.5\">a. 一次性文件评测</p>\n <p className=\"text-xs text-muted-foreground\">POST <code className=\"bg-muted px-1 rounded\">/v1/call</code>(<code className=\"bg-muted px-1 rounded\">audio_url</code> 或 <code className=\"bg-muted px-1 rounded\">audio_base64</code>) → 同步返回评分</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-sm font-semibold mb-1.5\">b. 流式实时评测</p>\n <p className=\"text-xs text-muted-foreground\">POST <code className=\"bg-muted px-1 rounded\">/v1/call</code> → <code className=\"bg-muted px-1 rounded\">start_stream_eval</code> → 拿 <code className=\"bg-muted px-1 rounded\">ws_url</code> → 连 <code className=\"bg-muted px-1 rounded\">/ws/eval/&#123;id&#125;</code> → 推 PCM → 收 <code className=\"bg-muted px-1 rounded\">intermediate</code> / <code className=\"bg-muted px-1 rounded\">backpressure</code> → 发 <code className=\"bg-muted px-1 rounded\">stop</code> → 收 <code className=\"bg-muted px-1 rounded\">result</code></p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-sm font-semibold mb-1.5\">c. 断线恢复 + 兜底拉取</p>\n <p className=\"text-xs text-muted-foreground\">网络断开 60 秒内 → 用 <code className=\"bg-muted px-1 rounded\">resume_token</code> 重连续播;彻底失败 → POST <code className=\"bg-muted px-1 rounded\">/v1/call</code> → <code className=\"bg-muted px-1 rounded\">get_stream_result</code> 兜底</p>\n </div>\n </div>\n\n <p className=\"font-semibold mt-5\">特色能力</p>\n <ul className=\"list-disc list-inside space-y-1 text-sm ml-1\">\n <li><strong>resume_token</strong> — 网络抖动 60 秒内可无缝续播,服务端挂起会话状态</li>\n <li><strong>intermediate 帧</strong> — 边推边吐部分结果,前端可做实时波形 / 分数涂色</li>\n <li><strong>backpressure 反馈</strong> — 服务端主动下发 <code className=\"bg-muted px-1 rounded text-xs\">suggested_interval_ms</code>,客户端按建议降速</li>\n <li><strong>结构化错误码</strong> — <code className=\"bg-muted px-1 rounded text-xs\">SESSION_EXPIRED</code> / <code className=\"bg-muted px-1 rounded text-xs\">INVALID_STATE</code> / … 便于客户端写重试逻辑</li>\n </ul>\n <p className=\"text-xs text-muted-foreground mt-3\">详细的接入示例见下文 <a href=\"#config-rest\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">「接入 · 非 MCP 原生 REST」</a> 大节。</p>\n </SubDoc>\n\n <SubDoc id=\"transport\" title=\"传输协议对比\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">维度</th>\n <th className=\"text-left py-2 px-3 font-medium\">MCP 模式</th>\n <th className=\"text-left py-2 px-3 font-medium\">functioncalling(cvx_fc)</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">协议</td><td className=\"py-2 px-3\">JSON-RPC 2.0</td><td className=\"py-2 px-3\">REST + WebSocket</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">入口路径</td><td className=\"py-2 px-3 font-mono text-xs\">POST /mcp</td><td className=\"py-2 px-3 font-mono text-xs\">POST /v1/call</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">列出工具</td><td className=\"py-2 px-3 font-mono text-xs\">tools/list</td><td className=\"py-2 px-3 font-mono text-xs\">GET /v1/functions</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">调用工具</td><td className=\"py-2 px-3 font-mono text-xs\">tools/call</td><td className=\"py-2 px-3 font-mono text-xs\">POST /v1/call + name</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">流式音频 WS</td><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/audio/{session_id}'}</td><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/eval/{session_id}'}</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">本地 stdio 代理</td><td className=\"py-2 px-3\">支持(<code className=\"bg-muted px-1 rounded text-[10px]\">chivox-local-mcp</code>)</td><td className=\"py-2 px-3 text-muted-foreground\">不适用</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">断线重连</td><td className=\"py-2 px-3\">客户端 SDK 自动重连</td><td className=\"py-2 px-3\">resume_token(60s 挂起)</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">中间结果</td><td className=\"py-2 px-3\">stream_eval 回调</td><td className=\"py-2 px-3\">intermediate 帧</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">背压反馈</td><td className=\"py-2 px-3 text-muted-foreground\">—</td><td className=\"py-2 px-3\">backpressure 帧</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">客户端依赖</td><td className=\"py-2 px-3\">MCP SDK(官方 / 框架)</td><td className=\"py-2 px-3\">仅 HTTP + WS 客户端</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-local-mcp</strong> 是驰声语音评测的 MCP 本地代理,通过 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 将远程驰声评测服务桥接到本地,让 AI 助手(Claude Desktop、Claude Code、Cursor 等)直接调用语音评测能力。</p>\n <p>它支持两种评测模式,覆盖从即时交互到批量处理的全部场景:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Mic className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">实时录音评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">通过麦克风边录边评,音频以 WebSocket 流式推送,无需生成中间文件,延迟更低、体验更佳。</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <FolderOpen className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">音频文件评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">支持本地文件路径、Base64 编码、URL 三种输入,适合已有音频的批量评测场景。</p>\n </div>\n </div>\n <Callout type=\"info\">当前支持 <strong>16 种评测工具</strong>:英文 10 种(单词、句子、段落、纠音、自然拼读、口语选择、半开放题等)+ 中文 6 种(汉字、拼音、词句、段落、有限分支、AI Talk)。</Callout>\n </SubDoc>\n\n <SubDoc id=\"requirements\" title=\"系统要求\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">依赖</th>\n <th className=\"text-left py-2 px-3 font-medium\">版本</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-semibold\">Node.js</td><td className=\"py-2 px-3 font-mono text-xs\">{'>'}= 18</td><td className=\"py-2 px-3 text-muted-foreground\">运行时必须</td></tr>\n <tr><td className=\"py-2 px-3 font-semibold\">SoX</td><td className=\"py-2 px-3 font-mono text-xs\">any</td><td className=\"py-2 px-3 text-muted-foreground\">仅实时录音功能需要(处理麦克风音频流)</td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"mt-3\">安装 SoX(仅在使用实时录音时需要):</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# macOS\nbrew install sox\n\n# Ubuntu / Debian\nsudo apt-get install sox`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"install\" title=\"安装方式\">\n <p><strong>方式一:npm 全局安装</strong>(推荐)</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`npm install -g chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式二:npx 免安装运行</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`MCP_REMOTE_URL=http://your-server:8080 npx chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式三:从源码构建</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`git clone https://git.chivox.com/CLOUD_DEV/cvx_local_mcp.git\ncd cvx_local_mcp\nbash scripts/build.sh`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"env-vars\" title=\"环境变量\">\n <ParamTable params={[\n { name: 'MCP_REMOTE_URL', type: 'string', required: true, desc: '远程驰声 MCP 服务地址,如 http://your-server:8080' },\n { name: 'MCP_API_KEY', type: 'string', required: false, desc: 'API 认证密钥(如已配置鉴权则必填)' },\n ]} />\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 直接用(零代码) ══════ */}\n <DocSection id=\"config\" icon={Plug} title=\"接入 · 直接用 MCP(零代码)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>不用写代码、不用装 SDK,只在客户端的<strong className=\"text-foreground\">「MCP 配置」</strong>或<strong className=\"text-foreground\">「扩展」</strong>里填一段 JSON / URL</li>\n <li>保存后 LLM 自动发现 16 个评测工具,并在对话 / Agent 中自主调用</li>\n <li>覆盖 <strong className=\"text-foreground\">IDE 编程助手</strong>、<strong className=\"text-foreground\">桌面 AI 客户端</strong>、<strong className=\"text-foreground\">企业 AI 工作台</strong>、<strong className=\"text-foreground\">可视化 Bot / 工作流平台</strong>四大生态</li>\n </ul>\n </div>\n\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置并保存:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <Callout type=\"tip\">Cursor 使用远程 Streamable HTTP 模式直连,无需安装本地代理。如需实时录音功能,请使用下方 Claude Desktop 的本地代理配置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-desktop\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件(macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"chivox-local-mcp\",\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\",\n \"MCP_API_KEY\": \"your-api-key\"\n }\n }\n }\n}`}</CodeBlock>\n <p>如果是从源码构建:</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/cvx_local_mcp/dist/index.js\"],\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\"\n }\n }\n }\n}`}</CodeBlock>\n <Callout type=\"info\">Claude Desktop 使用本地代理模式(stdio),支持实时录音评测。本地代理会自动桥接远程服务。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-code\" title=\"Claude Code 配置\">\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# npm 全局安装后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 chivox-local-mcp\n\n# 源码构建后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 node /path/to/cvx_local_mcp/dist/index.js`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-ai-ide\" title=\"Windsurf / Zed / Continue 等 IDE 编程助手\">\n <p>除了 Cursor 和 Claude Code,主流的 AI 编程 IDE 都原生支持 MCP。以下客户端使用<strong>完全一致</strong>的 Streamable HTTP 配置,区别只在配置入口:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">客户端</th>\n <th className=\"text-left py-2 px-3 font-medium\">配置入口</th>\n <th className=\"text-left py-2 px-3 font-medium\">备注</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">Windsurf (Codeium)</td><td className=\"py-2 px-3 text-xs\">Settings → Cascade → MCP Servers</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">直接贴下方 JSON</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Zed</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">~/.config/zed/settings.json</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">context_servers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">Assistant Panel 自动发现</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Continue (VS Code / JetBrains)</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">config.yaml</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">v0.9+ 支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">通义灵码 / CodeGeeX / 文心快码</td><td className=\"py-2 px-3 text-xs\">插件设置 → MCP 服务</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">2026 Q1 起陆续支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Cline (原 Claude Dev)</td><td className=\"py-2 px-3 text-xs\">MCP Servers 面板 → 编辑配置</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">VS Code 扩展</td></tr>\n </tbody>\n </table>\n </div>\n <CodeBlock filename=\"通用 MCP 配置(复制即用)\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox-speech-eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n }\n }\n}`}</CodeBlock>\n <Callout type=\"tip\">所有 IDE 类客户端都走远程 Streamable HTTP,零本地依赖。需要实时录音才需切到 Claude Desktop 的本地代理模式。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-coze\" title=\"扣子(Coze)配置\">\n <p><a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子(Coze)</a>是字节跳动推出的 AI 应用开发平台,支持通过 MCP 扩展为 Agent 赋予外部工具能力。接入驰声语音评测后,你可以构建口语练习 Bot、发音诊断助手等应用。</p>\n <p className=\"font-semibold mt-4\">方式一:扣子空间 MCP 扩展(推荐)</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 <a href=\"https://space.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子空间</a>(space.coze.cn)</li>\n <li>在对话框下方点击 <strong>「添加扩展」</strong></li>\n <li>选择 <strong>「自定义」</strong> 标签,点击添加新的 MCP 服务</li>\n <li>填入以下配置信息:</li>\n </ol>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">名称</td><td className=\"py-2 px-3 font-mono text-xs\">chivox-speech-eval</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>保存后,在对话中输入 <strong>「帮我评测这段英文发音」</strong> 即可触发 Agent 调用</li>\n </ol>\n <Callout type=\"tip\">添加 MCP 扩展后,扣子空间的 Agent 会自动识别并调用评测工具。你可以在 Agent 的思考过程中看到具体的工具调用细节。</Callout>\n\n <p className=\"font-semibold mt-6\">方式二:在 Coze 开发平台的 Bot 中使用</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>进入 <a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Coze 开发平台</a>,创建或编辑 Bot</li>\n <li>在 Bot 的 <strong>「插件」</strong> 或 <strong>「工具」</strong> 面板中,添加 MCP 工具</li>\n <li>选择 <strong>Streamable HTTP</strong> 类型,填入 URL:<code className=\"bg-muted px-1 rounded text-xs font-mono\">https://speech-eval.site/mcp</code></li>\n <li>Bot 将自动发现所有可用的评测工具(如 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_word</code> 等)</li>\n <li>在 Bot 的人设提示词中,添加语音评测相关的引导语</li>\n </ol>\n <CodeBlock filename=\"Bot 人设提示词示例\">{`你是一位专业的英语口语教练。当用户上传音频并请求评测时:\n1. 使用 evaluate_english_word / evaluate_english_sentence 等工具进行评测\n2. 根据评测结果(overall、accuracy、fluency 等分数)给出专业分析\n3. 针对得分较低的维度提供具体改进建议\n4. 鼓励用户持续练习`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze-workflow\" title=\"Coze 工作流接入\">\n <p>如果你需要在 Coze 工作流中集成语音评测能力,可以通过 <strong>HTTP Request 节点</strong>调用评测 API:</p>\n\n <p className=\"font-semibold mt-4\">步骤 1:上传音频</p>\n <p>在工作流中添加一个 HTTP Request 节点,配置如下:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">方法</td><td className=\"py-2 px-3 font-mono text-xs\">POST</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/upload</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Content-Type</td><td className=\"py-2 px-3 font-mono text-xs\">audio/mp3</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Body</td><td className=\"py-2 px-3 text-muted-foreground\">音频文件二进制数据</td></tr>\n </tbody>\n </table>\n </div>\n <p>响应中获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{{audioId}}'}</code>,传递给下一步。</p>\n\n <p className=\"font-semibold mt-4\">步骤 2:调用评测</p>\n <p>添加第二个 HTTP Request 节点,通过 MCP 接口调用评测工具:</p>\n <CodeBlock filename=\"HTTP Request Body\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"evaluate_english_sentence\",\n \"arguments\": {\n \"audioId\": \"{{audioId}}\",\n \"sentence\": \"{{ref_text}}\"\n }\n },\n \"id\": 1\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">步骤 3:处理结果</p>\n <p>评测结果通过 JSON 格式返回,可使用 Coze 的 <strong>代码节点</strong> 或 <strong>LLM 节点</strong> 对结果进行解析和二次处理(如生成学习建议、计算进步趋势等)。</p>\n\n <Callout type=\"info\">工作流方式适合需要固定流程的场景(如批量评测、定时任务、与其他系统联动)。如果是交互式场景,推荐使用上方的 MCP 扩展方式,Agent 会自动选择合适的工具。</Callout>\n\n <div className=\"rounded-lg bg-muted/30 p-5 mt-4\">\n <p className=\"text-xs font-semibold mb-3\">完整工作流示意</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">用户输入音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 上传音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 调用评测</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM: 分析结果</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">输出反馈</span>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"config-ai-workspace\" title=\"AI 工作台 · 豆包 / 飞书智能伙伴 / WorkBuddy / 钉钉 AI\">\n <p>这一类是<strong>面向「上班族」的企业 / 日常 AI 助手</strong>,产品形态是个聊天框 + 插件市场 / 扩展面板。接入后,员工在 App 里打字「帮我评一下这段录音」就能自动触发驰声评测,不用写任何代码。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Bot className=\"h-4 w-4 text-amber-500\" /> 豆包(Doubao)App / 桌面版</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节跳动官方 AI 助手,2026 年起桌面版支持 MCP 扩展。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>桌面版设置 → <strong>「智能体扩展」</strong> → 添加 MCP 服务</li>\n <li>类型选 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">streamable-http</code>,URL 填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>对话中直接说「帮我评测」即可自动调用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Building2 className=\"h-4 w-4 text-blue-500\" /> 飞书智能伙伴(My AI)</p>\n <p className=\"text-xs text-muted-foreground mb-2\">飞书企业 AI,支持以 MCP / Function Calling 挂载外部工具给团队 Bot。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>飞书开发者后台 → 创建应用 → <strong>「AI 能力 / 智能伙伴」</strong></li>\n <li>在「工具」中选 <strong>外部 MCP 服务</strong>,填入上方 URL</li>\n <li>把智能伙伴拉进群聊,@ 它提交音频即可评测</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-rose-500\" /> WorkBuddy / 企业智能助手</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节 WorkBuddy、腾讯会议 AI、企业微信智能助手等企业 AI 工作台场景。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>在工作台的<strong>「技能 / 扩展」</strong>后台新增一个 MCP 连接器</li>\n <li>传输协议选 Streamable HTTP,地址填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>把技能发布到指定部门 / 群组,HR、培训、销售口语陪练场景即可直接用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Workflow className=\"h-4 w-4 text-cyan-500\" /> 钉钉 AI 助理 / 通义千问 App</p>\n <p className=\"text-xs text-muted-foreground mb-2\">阿里生态:钉钉 AI 助理支持自定义插件,通义 App 可通过「AI 工具箱」挂载 MCP。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>钉钉开发者后台 → AI 助理 → <strong>「插件开发 / MCP 接入」</strong></li>\n <li>选 HTTP Streamable,填入驰声 MCP URL 并开启鉴权(可选)</li>\n <li>在钉钉群 @AI 助理上传音频,自动返回评测卡片</li>\n </ol>\n </div>\n </div>\n\n <Callout type=\"tip\"><strong>典型场景 · 小龙虾们的共同剧本</strong>:销售口语陪练 → 外语培训 → 主持 / 播音岗位考核 → K12 英语作业批改 → 客服话术复盘。这些「上班族 AI 工作台」接入驰声 MCP 后,员工 / 学员只要对着话筒说话,AI 就能自动打分并给出个性化改进建议,运营 / 培训 / 教研团队零开发即可上线。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-workflow-platforms\" title=\"Dify / n8n / Flowise 等可视化编排\">\n <p>如果你需要把语音评测编排进<strong>多步业务流</strong>(例如:学员录音 → 评测 → 生成错题集 → 推送到 CRM),可视化工作流平台比聊天 Bot 更合适。</p>\n\n <div className=\"grid gap-3 md:grid-cols-3 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Dify</p>\n <p className=\"text-xs text-muted-foreground mb-2\">开源 LLMOps,v0.15+ 原生 MCP 工具。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>「工具」→「自定义 MCP」→ 填 URL</li>\n <li>在 Chatflow / Workflow 节点里直接拖用</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">n8n</p>\n <p className=\"text-xs text-muted-foreground mb-2\">工作流自动化,用 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">MCP Client</code> 节点。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>添加 <strong>MCP Client</strong> 节点</li>\n <li>Transport 选 HTTP Stream,URL 贴驰声地址</li>\n <li>Tool 下拉自动列出 16 个评测能力</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Flowise / LangFlow</p>\n <p className=\"text-xs text-muted-foreground mb-2\">可视化 LangChain Agent 编排。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>拖出 <strong>MCP Tool</strong> 节点</li>\n <li>连到 AgentExecutor 即可</li>\n </ul>\n </div>\n </div>\n\n <Callout type=\"info\">可视化平台的好处是<strong>流程可追溯、可回放</strong>,适合教育机构 / 培训部门做批量评测 + 报表导出;缺点是单步延迟略高于直接 MCP 调用,对延迟敏感的交互场景建议用 Cursor / Claude Desktop 直连。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"其他 MCP 客户端(通用配置)\">\n <p>任何遵循 MCP 协议的客户端都可以接入,统一使用以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权(可选)</td><td className=\"py-2 px-3 font-mono text-xs\">Header:<code>Authorization: Bearer &lt;MCP_API_KEY&gt;</code></td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">还没找到你用的客户端?任何支持 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 的终端都通用。找不到入口?下一小节「编程用 MCP」里用 SDK 直接接即可。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 编程用 MCP ══════ */}\n <DocSection id=\"config-code\" icon={Code2} title=\"接入 · 编程用 MCP(SDK / 代码集成)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Terminal className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>你在写一段<strong className=\"text-foreground\">真正的后端服务</strong>:一个 Agent Worker、一个 API、一个自动化脚本</li>\n <li>业务里的「语音评测」是其中一个步骤,需要由代码 <strong className=\"text-foreground\">决定何时调用</strong>、<strong className=\"text-foreground\">如何解析结果</strong></li>\n <li>四种主流写法:① 官方 MCP 客户端库 → ② Agent 框架自动桥接 → ③ 直调 chat.completions + tools → ④ 把 MCP Client 嵌进业务后端</li>\n <li>如果客户端环境<strong className=\"text-foreground\">不能引入 MCP SDK</strong>(原生 App / 小程序 / 老系统),请跳转下一大节 <a href=\"#config-rest\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">「接入 · 非 MCP 原生 REST(cvx_fc)」</a></li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 强烈推荐①或②:驰声有 16 个评测工具,<strong className=\"text-foreground\">让 MCP 自动注入 tool schema</strong>,每新增一个题型不用改一行业务代码。</p>\n </div>\n\n <SubDoc id=\"code-mcp-sdk\" title=\"① 官方 MCP 客户端库(Python / TypeScript)\">\n <p>最底层、也最推荐的方式。由 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Anthropic 官方维护</a>,一次连接自动拿到全部工具的 JSON Schema,然后交给任意 LLM。</p>\n\n <p className=\"font-semibold mt-4\">Python:<code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp</code></p>\n <CodeBlock filename=\"chivox_client.py\" lang=\"python\">{`# pip install mcp openai\nimport asyncio, os\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nMCP_URL = \"https://speech-eval.site/mcp\"\nllm = OpenAI(api_key=os.getenv(\"DOUBAO_KEY\"),\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\") # 豆包 / DeepSeek / OpenAI 同构\n\nasync def main(audio_id: str, ref: str):\n async with streamablehttp_client(MCP_URL) as (reader, writer, _):\n async with ClientSession(reader, writer) as sess:\n await sess.initialize()\n\n tools = await sess.list_tools()\n openai_tools = [{\n \"type\": \"function\",\n \"function\": {\"name\": t.name, \"description\": t.description,\n \"parameters\": t.inputSchema},\n } for t in tools.tools]\n\n resp = llm.chat.completions.create(\n model=\"doubao-1-5-pro-32k\",\n messages=[{\"role\": \"user\",\n \"content\": f\"帮我评测 audioId={audio_id},参考文本:{ref}\"}],\n tools=openai_tools,\n )\n\n for call in resp.choices[0].message.tool_calls or []:\n result = await sess.call_tool(call.function.name,\n arguments=eval(call.function.arguments))\n print(call.function.name, \"→\", result.content[0].text[:200])\n\nasyncio.run(main(\"abc123\", \"I think therefore I am\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">TypeScript / Node.js:<code className=\"bg-muted px-1 rounded text-xs font-mono\">@modelcontextprotocol/sdk</code></p>\n <CodeBlock filename=\"chivox-client.ts\" lang=\"typescript\">{`// npm i @modelcontextprotocol/sdk openai\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport OpenAI from \"openai\";\n\nconst mcp = new Client({ name: \"chivox-biz\", version: \"1.0.0\" });\nawait mcp.connect(new StreamableHTTPClientTransport(new URL(\"https://speech-eval.site/mcp\")));\n\nconst { tools } = await mcp.listTools();\nconst openai = new OpenAI({ apiKey: process.env.DEEPSEEK_KEY, baseURL: \"https://api.deepseek.com/v1\" });\n\nconst resp = await openai.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: \"评测 audioId=abc123, 参考 I think therefore I am\" }],\n tools: tools.map(t => ({ type: \"function\", function: {\n name: t.name, description: t.description, parameters: t.inputSchema\n }})),\n});\n\nfor (const call of resp.choices[0].message.tool_calls ?? []) {\n const r = await mcp.callTool({ name: call.function.name,\n arguments: JSON.parse(call.function.arguments) });\n console.log(call.function.name, r.content);\n}`}</CodeBlock>\n <Callout type=\"tip\">这种写法 <strong>LLM 供应商可换</strong>(豆包 / DeepSeek / GPT / Qwen / Moonshot),<strong>工具自动更新</strong>(驰声新增题型无需改业务代码),适合生产后端。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-agent-frameworks\" title=\"② 用 Agent 框架自动桥接(LangChain / Mastra / Agents SDK)\">\n <p>如果项目已经在用 Agent 框架,几行 adapter 就能把驰声 MCP 塞进去,框架本身会接管 tool-calling 循环、重试、日志。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LangChain / LangGraph(Python)</p>\n <CodeBlock filename=\"agent.py\" lang=\"python\">{`# pip install langchain-mcp-adapters langgraph\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom langgraph.prebuilt import create_react_agent\n\nclient = MultiServerMCPClient({\n \"chivox\": {\n \"transport\": \"streamable_http\",\n \"url\": \"https://speech-eval.site/mcp\",\n }\n})\ntools = await client.get_tools()\n\nagent = create_react_agent(\"openai:gpt-4o-mini\", tools)\nresult = await agent.ainvoke({\"messages\":\n [(\"user\", \"评测 audioId=abc123 参考 think\")]})\nprint(result[\"messages\"][-1].content)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Mastra(TypeScript)</p>\n <CodeBlock filename=\"mastra-agent.ts\" lang=\"typescript\">{`// npm i @mastra/core @mastra/mcp\nimport { MCPClient } from \"@mastra/mcp\";\nimport { Agent } from \"@mastra/core/agent\";\nimport { openai } from \"@ai-sdk/openai\";\n\nconst mcp = new MCPClient({ servers: {\n chivox: { url: new URL(\"https://speech-eval.site/mcp\") }\n}});\n\nexport const speechCoach = new Agent({\n name: \"speech-coach\",\n instructions: \"你是口语教练,用 chivox 工具评测并给改进建议。\",\n model: openai(\"gpt-4o-mini\"),\n tools: await mcp.getTools(),\n});`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">OpenAI Agents SDK(Python)</p>\n <CodeBlock filename=\"agents_sdk.py\" lang=\"python\">{`# pip install openai-agents\nfrom agents import Agent, Runner\nfrom agents.mcp import MCPServerStreamableHttp\n\nchivox = MCPServerStreamableHttp(\n params={\"url\": \"https://speech-eval.site/mcp\"},\n name=\"chivox-speech-eval\",\n)\n\nasync with chivox:\n agent = Agent(name=\"coach\",\n instructions=\"专业口语教练\",\n mcp_servers=[chivox])\n r = await Runner.run(agent, \"评测 audioId=abc123\")\n print(r.final_output)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LlamaIndex / AutoGen / CrewAI</p>\n <p className=\"text-xs text-muted-foreground mb-2\">这些框架都有官方或社区 MCP adapter,用法与上述类似:</p>\n <ul className=\"list-disc list-inside space-y-1 text-xs\">\n <li><strong>LlamaIndex</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">llama-index-tools-mcp</code></li>\n <li><strong>AutoGen</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">autogen-ext[mcp]</code></li>\n <li><strong>CrewAI</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">crewai-tools</code> 的 MCPServerAdapter</li>\n <li><strong>Spring AI</strong>(Java):<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">spring-ai-mcp-client</code></li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"code-function-calling\" title=\"③ 直调 chat.completions · 上传与动态 tools(豆包 / DeepSeek / 等)\">\n <p>\n DeepSeek、GLM(智谱)、KIMI(Moonshot)、豆包(火山方舟)、千帆、DashScope 等若提供 <strong>OpenAI 兼容的 HTTP API</strong>,接入驰声 MCP 的套路一致:用 MCP 客户端拉取工具列表 → 转为{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> → 模型决策 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_calls</code> → MCP 执行并回填 → 再让模型生成诊断话术。\n </p>\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">① MCP list_tools()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">② 转 OpenAI tools 格式</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">③ LLM 决策 tool_calls</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">④ MCP call_tool()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">⑤ 评测结果回填 → LLM 诊断</span>\n </div>\n </div>\n <Callout type=\"tip\">各厂商仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 与 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 与 tools 桥接代码可原样复用。</Callout>\n\n <p className=\"font-semibold mt-6\">先上传音频,拿到 audioId</p>\n <p className=\"text-sm text-muted-foreground mb-3\">文件评测工具通常需要 <code className=\"bg-muted px-1 rounded text-xs font-mono\">audioId</code>(或路径参数,视工具而定)。下面是用 HTTP 上传的最小示例:</p>\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://speech-eval.site/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst res = await fetch('https://speech-eval.site/upload', {\n method: 'POST',\n headers: { 'Content-Type': 'audio/mp3' },\n body: audioData,\n});\n\nconst result = await res.json();\nconsole.log('audioId:', result.audioId);`}</CodeBlock>\n\n <p className=\"font-semibold mt-6\">再以豆包为例:MCP 动态 tools + chat.completions 折返跑</p>\n <p>当你用的 LLM SDK <strong>还没有原生 MCP 支持</strong>(比如老版本的火山方舟 / 百度千帆 / 通义 DashScope),退化方案是:用 MCP 客户端<strong>一次性拉到 tool schema</strong>,然后按 OpenAI Function Calling 格式喂给 LLM。</p>\n <CodeBlock filename=\"doubao_tools.py\" lang=\"python\">{`# 适用:豆包(火山方舟)/ DeepSeek / Qwen / Moonshot / GLM 等 OpenAI 兼容 API\n# 核心思路:MCP 列工具 → 转 OpenAI tools → LLM 决策 → MCP 执行 → 回填\n\nasync with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n\n # ① 用 MCP 拉到的 schema 自动转 OpenAI 格式\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 喂给豆包\n messages = [{\"role\": \"user\", \"content\": \"评测一下 audioId=abc123\"}]\n resp = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages, tools=oa_tools,\n )\n\n # ③ LLM 回 tool_calls → 折返跑到 MCP 执行\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for c in msg.tool_calls:\n result = await mcp.call_tool(\n c.function.name, arguments=json.loads(c.function.arguments))\n messages.append({\"role\": \"tool\", \"tool_call_id\": c.id,\n \"content\": result.content[0].text})\n # ④ 再喂一轮,让豆包根据评测结果产出自然语言诊断\n final = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages)\n print(final.choices[0].message.content)`}</CodeBlock>\n <Callout type=\"warning\"><strong>千万别手写 tool schema!</strong>驰声共 <strong>16 个评测工具</strong>(英文 10 + 中文 6),每个工具参数较多,手写既容易错也难维护。一定用 MCP <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">list_tools()</code> 动态拿。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-selfhosted-agent\" title=\"④ 自研后端 Agent(FastAPI / NestJS / Spring)\">\n <p>大部分落地场景其实是:<strong>你已有一个业务后端</strong>(教育 SaaS / 培训系统 / 客服平台),只需要新增一个「语音评测」能力。推荐架构:</p>\n\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">前端录音 / 上传</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">业务后端</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">MCP Client(常驻连接)</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">驰声 MCP</span>\n <span className=\"text-muted-foreground\">↘</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM(诊断 / 出题)</span>\n </div>\n </div>\n\n <p className=\"font-semibold mt-2\">FastAPI 示例(生产最小骨架)</p>\n <CodeBlock filename=\"app.py\" lang=\"python\">{`from fastapi import FastAPI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\nfrom contextlib import asynccontextmanager\n\nmcp_session: ClientSession | None = None\n\n@asynccontextmanager\nasync def lifespan(app):\n global mcp_session\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as s:\n await s.initialize()\n mcp_session = s\n yield\n mcp_session = None\n\napp = FastAPI(lifespan=lifespan)\n\n@app.post(\"/api/evaluate\")\nasync def evaluate(audio_id: str, ref_text: str):\n # 直接选一个最合适的工具执行(不经 LLM,延迟最低)\n r = await mcp_session.call_tool(\"evaluate_english_sentence\",\n {\"audioId\": audio_id, \"sentence\": ref_text})\n return {\"result\": r.content[0].text}`}</CodeBlock>\n\n <Callout type=\"tip\"><strong>关键优化</strong>:① MCP 连接在 lifespan 里做成长连接,不要每次请求都重连;② 对延迟敏感的场景可以「跳过 LLM 直接调 tool」,把 LLM 放到「生成诊断报告」那一步。</Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-4 my-5 text-xs\">\n <p className=\"font-semibold text-sm mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-muted-foreground\" /> 下面是 ① ~ ④ 中<strong className=\"text-foreground\">第③种「直调 chat.completions」</strong>按主流 LLM 厂商的细化写法</p>\n <p className=\"text-muted-foreground\">各家 SDK / base_url / 鉴权写法略有差别,挑你当前用的那一家看即可;驰声 MCP 侧代码不用改。</p>\n </div>\n\n <SubDoc id=\"llm-deepseek\" title=\"DeepSeek\">\n <p>\n DeepSeek 提供与 OpenAI 完全兼容的 API,当前最新模型为 <strong>DeepSeek-V3.2</strong>(2025 年 12 月发布,首个把\"思考 + 工具调用\"原生融合的模型)。\n 通过 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-chat</code> 进入<strong>非思考模式</strong>,\n 通过 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-reasoner</code> 进入<strong>思考模式</strong>,\n <strong>两种模式均支持 Function Calling</strong>,128K 上下文。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"deepseek_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# DeepSeek 客户端(OpenAI 兼容)\nclient = OpenAI(\n api_key=os.environ[\"DEEPSEEK_API_KEY\"],\n base_url=\"https://api.deepseek.com/v1\",\n)\n\nasync def evaluate_with_deepseek(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 拉取驰声 MCP 工具列表\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 调用 DeepSeek,让它选择合适的评测工具\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"deepseek-chat\", # 或 deepseek-reasoner\n messages=messages,\n tools=oa_tools,\n )\n\n # ③ 执行 tool_calls\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 让 DeepSeek 根据评测结果生成自然语言诊断\n final = client.chat.completions.create(\n model=\"deepseek-chat\",\n messages=messages,\n )\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_deepseek(\"audio_abc123\", \"Hello, nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议(V3.2):</strong>日常评测诊断用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">deepseek-chat</code>,速度快、token 消耗低;\n 需要深度分析(错误归因 / 个性化练习路径)时用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">deepseek-reasoner</code>,\n V3.2 起<strong>思考模式也能在思维链中调用工具</strong>,可以一次性完成\"调评测 → 思考 → 出诊断\",无需再切回 V3 做工具调用。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-glm\" title=\"GLM(智谱 AI / Z.ai)\">\n <p>\n 智谱 AI 当前最新旗舰为 <strong>GLM-5 / GLM-5.1</strong>(2026 年 2 月发布,744B-A40B MoE,200K 上下文,对标 Claude Opus 4.5),\n 以及上一代 <strong>GLM-4.7</strong>、<strong>GLM-4.6</strong>。\n 此外保留 <code className=\"bg-muted px-1 rounded text-xs font-mono\">glm-4-flash</code>(免费快速版)方便开发测试。\n 全部模型支持 OpenAI 兼容接口与原生 <code className=\"bg-muted px-1 rounded text-xs font-mono\">zhipuai</code> SDK,原生 Function Calling。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`ZHIPU_API_KEY=xxxxxxxx.xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式一:OpenAI 兼容接口(推荐)</p>\n <CodeBlock filename=\"glm_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(\n api_key=os.environ[\"ZHIPU_API_KEY\"],\n base_url=\"https://open.bigmodel.cn/api/paas/v4/\",\n)\n\nasync def evaluate_with_glm(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"glm-4-flash\", # 免费快速版;生产推荐 glm-5 / glm-4.7\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"glm-4-flash\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_glm(\"audio_abc123\", \"How are you today?\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式二:官方 zhipuai SDK</p>\n <CodeBlock filename=\"glm_sdk.py\" lang=\"python\">{`from zhipuai import ZhipuAI\n\nclient = ZhipuAI(api_key=os.environ[\"ZHIPU_API_KEY\"])\n\n# 其余逻辑与方式一相同,仅替换 client 实例\nresp = client.chat.completions.create(\n model=\"glm-5\", # 旗舰;也可用 glm-4.7 / glm-4-flash\n messages=messages,\n tools=oa_tools,\n)`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 开发联调用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-4-flash</code>(免费);\n 在线辅导日常诊断用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-4.7</code> 性价比最高;\n K12 / 复杂 Agent 编排(多步评测 + 个性化练习路径)用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-5</code> / <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-5.1</code>,\n 200K 上下文、Agent 能力对标 Claude Opus 系列。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-kimi\" title=\"KIMI(Moonshot AI)\">\n <p>\n Moonshot AI 当前最新旗舰是 <strong>Kimi K2.5</strong>(2026 年 1 月 27 日发布,MoE 架构、<strong>256K 上下文</strong>、原生多模态、Agent 集群协作)。\n 另外提供 <code className=\"bg-muted px-1 rounded text-xs font-mono\">kimi-k2-thinking</code>(深度思考)、<code className=\"bg-muted px-1 rounded text-xs font-mono\">kimi-k2-turbo-preview</code>(高速版)。\n 上一代 <code className=\"bg-muted px-1 rounded text-xs font-mono\">moonshot-v1-8k/32k/128k</code> 仍然兼容,但<strong>推荐迁移到 kimi-k2.5</strong>。\n 全部模型 OpenAI 兼容,原生 Function Calling。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`MOONSHOT_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"kimi_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# 中国大陆用 api.moonshot.cn;海外/国际版用 api.moonshot.ai\nclient = OpenAI(\n api_key=os.environ[\"MOONSHOT_API_KEY\"],\n base_url=\"https://api.moonshot.cn/v1\",\n)\n\nasync def evaluate_with_kimi(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"kimi-k2.5\", # 旗舰;也可选 kimi-k2-turbo-preview / moonshot-v1-128k\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"kimi-k2.5\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_kimi(\"audio_abc123\", \"Nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\">\n Kimi K2.5 原生 <strong>256K 上下文 + Agent 集群协作</strong>,特别适合「多轮口语练习 + 历史分析 + 多步评测编排」场景:\n 把用户过去 30 次评测的完整 JSON 全部塞进 messages,让 K2.5 一次性生成跨多次练习的进步报告与个性化练习路径。\n 需要更深推理时使用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">kimi-k2-thinking</code>,\n 追求实时反馈用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">kimi-k2-turbo-preview</code>。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-doubao\" title=\"豆包 Seed 2.0(火山方舟)\">\n <p>\n 字节跳动 <strong>豆包 Seed 2.0</strong>(2026 年 2 月发布)是 ByteDance Seed 团队推出的新一代通用大模型,覆盖 Pro / Lite / Mini / Code 多种规格,\n 通过 <strong>火山方舟(Volcano Ark)</strong> 提供 OpenAI 兼容的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">/api/v3/chat/completions</code> 接口,\n 原生支持 <strong>Function Calling、JSON Schema 结构化输出、深度思考(Chain-of-Thought)模式</strong>,\n 接驰声 MCP 几乎无须改动业务代码。\n </p>\n\n <Callout type=\"tip\">\n 豆包 Seed 2.0 的优势在 <strong>深度思考 + 工具编排</strong>:让模型一次性规划\"先调 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code>,看完弱项再调 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.word.score</code> 进一步定位音素\",\n 适合做\"多步评测 + 自动纠音\"这类 Agent 场景。\n 默认开启思考模式,可通过 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'thinking: { type: \"disabled\" }'}</code> 关闭以节省时延。\n </Callout>\n\n <p className=\"font-semibold mt-4 mb-2\">推荐版本</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">model</th>\n <th className=\"text-left py-2 px-3 font-medium\">规格</th>\n <th className=\"text-left py-2 px-3 font-medium\">推荐用途</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-pro-260215</td><td className=\"py-2 px-3\">Pro · 旗舰</td><td className=\"py-2 px-3\">多步评测 + 深度诊断 + 复杂 Agent 编排</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-lite-260215</td><td className=\"py-2 px-3\">Lite · 平衡</td><td className=\"py-2 px-3\">在线辅导单轮评测 + 自然语言反馈,性价比首选</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-mini-260215</td><td className=\"py-2 px-3\">Mini · 极小</td><td className=\"py-2 px-3\">高并发 / 边缘部署 / 移动端实时反馈</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-code-preview-260215</td><td className=\"py-2 px-3\">Code · 预览</td><td className=\"py-2 px-3\">代码生成 / 工具编排 DSL,对评测场景非主推</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-1-8-251228</td><td className=\"py-2 px-3\">1.8 · 深度思考</td><td className=\"py-2 px-3\">支持 reasoning_effort 调档(minimal/low/medium/high),上一代深度思考强化版仍可用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-1-6-251015</td><td className=\"py-2 px-3\">1.6 · 经典</td><td className=\"py-2 px-3\">256K 上下文经典版,已上线项目可继续沿用</td></tr>\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`# 火山方舟控制台 → API Key 管理\nARK_API_KEY=ak-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · 动态 tools + 折返跑)</p>\n <CodeBlock filename=\"doubao_seed20_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# 火山方舟 OpenAI 兼容端点\nclient = OpenAI(\n api_key=os.environ[\"ARK_API_KEY\"],\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n)\n\nMODEL = \"doubao-seed-2-0-pro-260215\" # 也可换成 lite / mini\n\nasync def evaluate_with_doubao(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 把驰声 16 个评测工具的 schema 动态注入豆包\n tools = (await mcp.list_tools()).tools\n ark_tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n },\n } for t in tools]\n\n messages = [\n {\"role\": \"system\",\n \"content\": \"你是一位专业的英语口语教练,会主动调用驰声评测工具获取真实分数。\"},\n {\"role\": \"user\",\n \"content\": f\"请评测这段录音,audio_id={audio_id},参考文本:{ref_text}。\"\n f\"先做句子级评测,弱项再追加单词级定位。\"},\n ]\n\n # ② 第一轮 —— 让豆包根据 prompt 自主选工具\n resp = client.chat.completions.create(\n model=MODEL,\n messages=messages,\n tools=ark_tools,\n tool_choice=\"auto\",\n # extra_body={\"thinking\": {\"type\": \"disabled\"}}, # 需要时可关思考\n )\n\n msg = resp.choices[0].message\n messages.append(msg)\n\n # ③ 折返跑:豆包可能一次发起多个 tool_calls,全部回填\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 第二轮 —— 让豆包基于真实评测 JSON 输出诊断 / 练习建议\n final = client.chat.completions.create(\n model=MODEL,\n messages=messages,\n )\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_doubao(\n audio_id=\"audio_abc123\",\n ref_text=\"The quick brown fox jumps over the lazy dog.\",\n)))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">深度思考模式(可选)</p>\n <p className=\"text-sm text-muted-foreground\">\n 豆包 Seed 2.0 默认开启 <strong>thinking</strong> 模式,会在内部生成思维链再产出答案,\n 对\"多步评测编排 / 复杂诊断\"质量明显更好;但会增加 30%~80% 的端到端延迟。\n 在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">extra_body</code> 里可显式控制:\n </p>\n <CodeBlock filename=\"thinking_mode.py\" lang=\"python\">{`# 关闭思考(更快、适合实时反馈)\nclient.chat.completions.create(\n model=\"doubao-seed-2-0-lite-260215\",\n messages=messages,\n tools=ark_tools,\n extra_body={\"thinking\": {\"type\": \"disabled\"}},\n)\n\n# 开启思考(默认,适合 Pro 模型 + 多步 Agent)\nclient.chat.completions.create(\n model=\"doubao-seed-2-0-pro-260215\",\n messages=messages,\n tools=ark_tools,\n extra_body={\"thinking\": {\"type\": \"enabled\"}},\n)`}</CodeBlock>\n\n <Callout type=\"info\">\n <strong>选型建议:</strong>\n 课堂 / 培训类一对一辅导用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-lite</code> + 关思考;\n K12 / 考试场景需要\"多题型联动 + 升级建议\"时用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-pro</code> + 开思考;\n 移动端实时口语对话用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-mini</code> 控制成本与时延。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-openai\" title=\"OpenAI(GPT-5.1)\">\n <p>\n OpenAI 当前旗舰为 <strong>GPT-5.1</strong>(2025-11-13 发布),<strong>400K 上下文</strong>、128K 最大输出,\n 支持可调推理强度 <code className=\"bg-muted px-1 rounded text-xs font-mono\">reasoning_effort</code>(none / low / medium / high)。\n 另有 <code className=\"bg-muted px-1 rounded text-xs font-mono\">gpt-5.1-chat-latest</code>(ChatGPT 同款,128K 上下文,对话场景更优)和 <code className=\"bg-muted px-1 rounded text-xs font-mono\">gpt-5-mini</code>(性价比版)。\n 全部模型原生支持 Function Calling 与 Structured Outputs。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · GPT-5.1 + 驰声 MCP)</p>\n <CodeBlock filename=\"openai_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(api_key=os.environ[\"OPENAI_API_KEY\"])\n\nasync def evaluate_with_gpt(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [\n {\"role\": \"system\",\n \"content\": \"你是英语口语教练,必须调用驰声评测工具拿真实分数后再回答。\"},\n {\"role\": \"user\",\n \"content\": f\"请评测:audioId={audio_id},参考文本:{ref_text}\"},\n ]\n\n resp = client.chat.completions.create(\n model=\"gpt-5.1\",\n messages=messages,\n tools=oa_tools,\n tool_choice=\"auto\",\n reasoning_effort=\"medium\", # GPT-5.1 新参数:none / low / medium / high\n )\n\n msg = resp.choices[0].message\n messages.append(msg)\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n final = client.chat.completions.create(\n model=\"gpt-5.1\", messages=messages)\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_gpt(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 评测调用 + 诊断默认用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5.1</code>(reasoning_effort=&quot;medium&quot;,质量与延迟平衡);\n 纯对话/课堂讲解用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5.1-chat-latest</code>;\n 高并发批量评测降本用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5-mini</code>。\n 深度推理 / 多步 Agent 编排把 reasoning_effort 调到 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">high</code>。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-claude\" title=\"Claude(Anthropic)\">\n <p>\n Anthropic 当前最新为 <strong>Claude Opus 4.7</strong>(旗舰)、<strong>Claude Sonnet 4.6</strong>(平衡)、<strong>Claude Haiku 4.5</strong>(高速)。\n Claude 用 <strong>Messages API</strong>(与 OpenAI Chat Completions 不同),工具协议是 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_use</code> / <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_result</code> 块结构。\n 支持文本 + 视觉输入,工具 schema 用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">input_schema</code> 字段(与 OpenAI 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">parameters</code> 等价)。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · 原生 Anthropic SDK + MCP 工具桥接)</p>\n <CodeBlock filename=\"claude_chivox.py\" lang=\"python\">{`# pip install anthropic mcp\nimport os, json, asyncio\nfrom anthropic import Anthropic\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = Anthropic(api_key=os.environ[\"ANTHROPIC_API_KEY\"])\n\nasync def evaluate_with_claude(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # 把驰声 MCP 工具转成 Anthropic 风格(用 input_schema 而非 parameters)\n tools = (await mcp.list_tools()).tools\n anth_tools = [{\n \"name\": t.name,\n \"description\": t.description,\n \"input_schema\": t.inputSchema,\n } for t in tools]\n\n messages = [{\"role\": \"user\",\n \"content\": f\"请评测这段录音 audioId={audio_id},参考文本:{ref_text}\"}]\n\n # 工具循环:Claude 可能多轮 tool_use,需逐轮回填\n while True:\n resp = client.messages.create(\n model=\"claude-opus-4-7\", # 或 claude-sonnet-4-6 / claude-haiku-4-5\n max_tokens=2048,\n tools=anth_tools,\n messages=messages,\n system=\"你是专业英语口语教练,必须调用驰声评测工具获取真实分数。\",\n )\n\n # 把整段助手回复(含 tool_use 块)追加进 messages\n messages.append({\"role\": \"assistant\", \"content\": resp.content})\n\n if resp.stop_reason != \"tool_use\":\n # 没有更多工具调用,输出最终诊断\n return \"\".join(b.text for b in resp.content if b.type == \"text\")\n\n # 提取所有 tool_use 块、并行执行、把 tool_result 回填给 Claude\n tool_results = []\n for block in resp.content:\n if block.type == \"tool_use\":\n result = await mcp.call_tool(block.name, arguments=block.input)\n tool_results.append({\n \"type\": \"tool_result\",\n \"tool_use_id\": block.id,\n \"content\": result.content[0].text,\n })\n messages.append({\"role\": \"user\", \"content\": tool_results})\n\nprint(asyncio.run(evaluate_with_claude(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 深度评测诊断 + 复杂 Agent 编排用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-opus-4-7</code>(旗舰,编程/Agent 提升明显);\n 日常评测 + 性价比首选 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-sonnet-4-6</code>;\n 高并发实时反馈用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-haiku-4-5</code>。\n Opus 4.5 起新增 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">effort</code> 参数(low/medium/high),medium 即可达到 Sonnet 4.5 同等质量但 token 减少 ~76%。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-gemini\" title=\"Gemini(Google)\">\n <p>\n Google 最新 <strong>Gemini 3 Flash</strong>(<code className=\"bg-muted px-1 rounded text-xs font-mono\">gemini-3-flash-preview</code>,2025-12-17)以 Flash 价位提供 Pro 级推理;\n 另有 <strong>Gemini 3.1 Pro Preview</strong>(旗舰)、<strong>Gemini 3.1 Flash-Lite Preview</strong>。\n 全部支持 Function Calling、并行调用、组合调用,原生多模态,<code className=\"bg-muted px-1 rounded text-xs font-mono\">thinking_level</code> 控制推理深度。\n 推荐用 <strong>OpenAI 兼容端点</strong>,可直接复用 OpenAI SDK 与已有的 MCP 工具桥接代码。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`GEMINI_API_KEY=AIzaSyxxxxxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式一:OpenAI 兼容端点(推荐,零迁移成本)</p>\n <CodeBlock filename=\"gemini_openai_compat.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# Gemini OpenAI 兼容端点 —— 完全复用 OpenAI SDK\nclient = OpenAI(\n api_key=os.environ[\"GEMINI_API_KEY\"],\n base_url=\"https://generativelanguage.googleapis.com/v1beta/openai/\",\n)\n\nasync def evaluate_with_gemini(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\",\n \"content\": f\"请评测:audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"gemini-3-flash-preview\", # 或 gemini-3.1-pro-preview\n messages=messages,\n tools=oa_tools,\n tool_choice=\"auto\",\n )\n msg = resp.choices[0].message\n messages.append(msg)\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n final = client.chat.completions.create(\n model=\"gemini-3-flash-preview\", messages=messages)\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_gemini(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式二:原生 google-genai SDK(thinking_level / 流式工具调用)</p>\n <CodeBlock filename=\"gemini_native.py\" lang=\"python\">{`# pip install google-genai\nfrom google import genai\nfrom google.genai import types\n\nclient = genai.Client(api_key=os.environ[\"GEMINI_API_KEY\"])\n\n# 把 MCP 工具转成 Gemini function_declarations\nfn_decls = [{\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n} for t in tools]\n\nresp = client.models.generate_content(\n model=\"gemini-3-flash-preview\",\n contents=f\"评测 audioId={audio_id},参考文本:{ref_text}\",\n config=types.GenerateContentConfig(\n tools=[types.Tool(function_declarations=fn_decls)],\n # Gemini 3 起:thinking_level 替代旧的 thinking_budget\n # thinking_level=\"high\",\n ),\n)\n\n# tool_call 在 response.candidates[0].content.parts[0].function_call\nfc = resp.candidates[0].content.parts[0].function_call\nprint(f\"call: {fc.name}({dict(fc.args)}) id={fc.id}\")`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 日常评测用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3-flash-preview</code>(速度比 2.5 Pro 快 3 倍、价格 ~$0.50/M 输入),已是 Gemini App 默认;\n 复杂多步 Agent / 编程任务用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3.1-pro-preview</code>;\n 超大上下文 + 多模态批处理用同系列的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3.1-flash-lite-preview</code>。\n </Callout>\n <Callout type=\"info\">\n <strong>注意:</strong>Gemini 3 起每次 function_call 自动带 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">id</code>,多轮工具调用必须把 id 透传回 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">function_response</code>,否则会被拒绝(OpenAI 兼容端点已自动处理)。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-comparison\" title=\"模型对比速查\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead>\n <tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 pr-4 font-medium\">模型</th>\n <th className=\"text-left py-2 pr-4 font-medium\">推荐版本</th>\n <th className=\"text-left py-2 pr-4 font-medium\">base_url</th>\n <th className=\"text-left py-2 pr-4 font-medium\">Function Calling</th>\n <th className=\"text-left py-2 font-medium\">适合场景</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek V3.2 · 非思考</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-chat</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">评测调用 + 诊断,性价比最高</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek V3.2 · 思考</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-reasoner</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(思考中可调工具)</td>\n <td className=\"py-2\">深度分析 + 错因归因 + 个性化练习路径</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-5 · 旗舰</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-5</code> / <code className=\"bg-muted px-1 rounded font-mono\">glm-5.1</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">200K 长上下文 + Agent 编排,复杂诊断</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4.7</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4.7</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">在线辅导日常诊断,性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4-Flash</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4-flash</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">免费额度,开发联调</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2.5(旗舰)</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2.5</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">256K + 多模态 + Agent 集群协作</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2 Thinking</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2-thinking</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">多步推理 + 多轮历史进度报告</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2 Turbo</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2-turbo-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高速响应版,实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Pro</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-pro-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(含深度思考)</td>\n <td className=\"py-2\">多步评测编排 + 深度诊断 Agent</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Lite</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-lite-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">在线辅导 + 单轮诊断,性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Mini</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-mini-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">移动端 / 边缘部署 / 低延迟实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5.1(旗舰)</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5.1</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(reasoning_effort 可调)</td>\n <td className=\"py-2\">400K 上下文 + 深度推理 + 复杂 Agent 编排</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5.1 Chat</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5.1-chat-latest</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">ChatGPT 同款,对话 / 课堂讲解</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5 mini</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5-mini</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高并发 / 批量评测降本</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Opus 4.7</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-opus-4-7</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(tool_use 协议)</td>\n <td className=\"py-2\">旗舰,编程 / Agent / 深度诊断</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Sonnet 4.6</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-sonnet-4-6</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">日常评测 + 性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Haiku 4.5</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-haiku-4-5</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高并发实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Gemini 3 Flash</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gemini-3-flash-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">…/v1beta/openai/</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(并行 / 组合)</td>\n <td className=\"py-2\">Pro 级推理 + Flash 价位,Gemini App 默认</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Gemini 3.1 Pro Preview</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gemini-3.1-pro-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">…/v1beta/openai/</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(thinking_level 可调)</td>\n <td className=\"py-2\">复杂多步 Agent + 编程任务</td>\n </tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"info\">\n 除 <strong>Claude</strong> 用 Anthropic 原生 SDK(<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">anthropic</code> 包,<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">tool_use</code> / <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">tool_result</code> 协议)外,其余所有模型均可通过 <strong>OpenAI Python SDK</strong>(<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">openai</code> 包)调用,仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 与 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 侧代码<strong>无需改动</strong>。\n </Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mt-6 text-sm\">\n <p className=\"font-semibold mb-2\">方案选型速查</p>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 font-medium\">场景</th>\n <th className=\"text-left py-2 font-medium\">推荐方案</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2\">AI 对话 / Copilot 类产品,LLM 供应商可能会换</td><td className=\"py-2\">① 官方 MCP 客户端库</td></tr>\n <tr><td className=\"py-2\">已有 LangChain / Mastra / Agents SDK 技术栈</td><td className=\"py-2\">② Agent 框架 + adapter</td></tr>\n <tr><td className=\"py-2\">火山方舟 / 千帆控制台直连,最小侵入</td><td className=\"py-2\">③ chat.completions + tools</td></tr>\n <tr><td className=\"py-2\">教育 SaaS / 培训 / 客服等业务后端集成</td><td className=\"py-2\">④ 自研后端 Agent</td></tr>\n <tr><td className=\"py-2\">原生 Android / iOS / 小程序 / 不装 MCP SDK 的老系统</td><td className=\"py-2\"><a href=\"#config-rest\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">→ 走下一大节「非 MCP REST」</a></td></tr>\n </tbody>\n </table>\n </div>\n </div>\n </DocSection>\n\n {/* ══════ 接入 · 非 MCP 原生 REST(cvx_fc) ══════ */}\n <DocSection id=\"config-rest\" icon={Network} title=\"接入 · 非 MCP 原生 REST(cvx_fc)\">\n\n <div className=\"rounded-lg border border-sky-500/30 bg-sky-500/5 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Network className=\"h-4 w-4 text-sky-600\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>客户端环境<strong className=\"text-foreground\">不能 / 不想引入 MCP SDK</strong>:原生 Android / iOS / Flutter、微信 / 支付宝小程序、老版本 Java / PHP / Go 后端</li>\n <li>希望把驰声当作<strong className=\"text-foreground\">普通 REST + WebSocket</strong>调,不经过 JSON-RPC、不走 stdio 代理</li>\n <li>内部代号 <code className=\"bg-muted px-1 rounded text-xs font-mono\">cvx_fc</code>(OpenAI function-calling 风格),与 MCP 模式<strong className=\"text-foreground\">指向同一个评测引擎</strong>,两者为并行入口</li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 能走 MCP 的一律优先 MCP;仅当客户端限制无法装 SDK 时走本大节。</p>\n </div>\n\n <SubDoc id=\"rest-overview\" title=\"functioncalling 模式总览 · 端点与认证\">\n <p>\n 驰声在 MCP 之外提供一套 <strong>OpenAI 兼容的 Function Calling 风格 REST API</strong>(内部代号 <code className=\"bg-muted px-1 rounded text-xs\">cvx_fc</code>)。它和 MCP 模式指向<strong>同一个评测引擎</strong>,但协议层走纯 HTTP / WebSocket,<strong>不依赖任何 MCP SDK</strong>。\n </p>\n\n <Callout type=\"info\">\n <strong>与 MCP 模式的关系</strong>:MCP 模式(<code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">POST /mcp</code>,JSON-RPC 2.0)和 functioncalling 模式(<code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">POST /v1/call</code>,REST)是<strong>两个并行入口</strong>,功能等价、工具/函数列表基本一致。\n </Callout>\n\n <p className=\"font-semibold mt-6\">API 端点</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">路径</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">GET</td><td className=\"py-2 px-3 font-mono text-xs\">/v1/functions</td><td className=\"py-2 px-3\">列出全部可用评测函数(等价 MCP <code className=\"bg-muted px-1 rounded text-xs\">tools/list</code>)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">POST</td><td className=\"py-2 px-3 font-mono text-xs\">/v1/call</td><td className=\"py-2 px-3\">调用一个评测函数(等价 MCP <code className=\"bg-muted px-1 rounded text-xs\">tools/call</code>)· 同时用于<strong>创建流式会话</strong>和<strong>拉取流式结果</strong></td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">WS</td><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/eval/{session_id}'}</td><td className=\"py-2 px-3\">流式评测音频传输</td></tr>\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6\">认证</p>\n <p>所有请求在 HTTP Header 中携带 API Key:</p>\n <CodeBlock filename=\"Header\" lang=\"http\">{`Authorization: Bearer <your_api_key>`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">认证失败返回 <code className=\"bg-muted px-1 rounded text-xs\">401</code>(缺少 Authorization)或 <code className=\"bg-muted px-1 rounded text-xs\">403</code>(Key 无效 / 无权使用该评测类型)。结构化错误码见 <a href=\"#error-codes\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">API 参考 · 错误码</a>。</p>\n </SubDoc>\n\n <SubDoc id=\"rest-catalog\" title=\"函数目录 · GET /v1/functions\">\n <p>\n 调用 <code className=\"bg-muted px-1 rounded text-xs\">GET /v1/functions</code> 拿到<strong>当前 API Key 已授权</strong>的全部评测函数列表,响应格式与 OpenAI function-calling 的工具清单一致,可以直接喂给大模型作为 <code className=\"bg-muted px-1 rounded text-xs\">tools</code>:\n </p>\n <CodeBlock filename=\"request\" lang=\"http\">{`GET /v1/functions\nAuthorization: Bearer <api_key>`}</CodeBlock>\n <CodeBlock filename=\"response.json\" lang=\"json\">{`{\n \"object\": \"list\",\n \"data\": [\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英文单词发音评测,返回总分和每个音标得分。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"ref_text\": { \"type\": \"string\", \"description\": \"参考文本\" },\n \"audio_base64\": { \"type\": \"string\", \"description\": \"Base64 编码音频(与 audio_url 二选一)\" },\n \"audio_url\": { \"type\": \"string\", \"description\": \"音频 HTTP(S) URL(与 audio_base64 二选一)\" },\n \"accent\": { \"type\": \"number\", \"description\": \"1=英式 / 2=美式 / 3=不区分(默认)\" },\n \"rank\": { \"type\": \"number\", \"description\": \"评分制:4 或 100(默认)\" }\n },\n \"required\": [\"ref_text\"]\n }\n }\n }\n /* … 其它函数依此类推 … */\n ]\n}`}</CodeBlock>\n <Callout type=\"tip\">\n 这个端点语义等价于 MCP 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">tools/list</code>,<strong>每个 API Key 可见的函数集合由后台权限决定</strong>——客户端应以实际返回为准,不要在本地硬编码全量 19 个。\n </Callout>\n\n <p className=\"font-semibold mt-6\">英文评测函数(10 个)</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">函数名</th>\n <th className=\"text-left py-2 px-3 font-medium\">core_type</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['en_word_eval', 'en.word.score', '单词发音评测'],\n ['en_word_correction', 'en.word.pron', '单词发音纠错'],\n ['en_phonics_eval', 'en.nsp.score', '自然拼读评测'],\n ['en_sentence_eval', 'en.sent.score', '句子朗读评测'],\n ['en_sentence_correction', 'en.sent.pron', '句子发音纠错'],\n ['en_vocab_eval', 'en.vocabs.pron', '词汇批量评测'],\n ['en_paragraph_eval', 'en.pred.score', '段落朗读评测'],\n ['en_realtime_eval', 'en.rltm.score', '实时朗读评测'],\n ['en_choice_eval', 'en.choc.score', '口语选择题'],\n ['en_semi_open_eval', 'en.scne.exam', '半开放题(情景对话等)'],\n ].map(([fn, ct, desc]) => (\n <tr key={fn}>\n <td className=\"py-2 px-3 font-mono text-xs\">{fn}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{ct}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6\">中文评测函数(6 个)</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">函数名</th>\n <th className=\"text-left py-2 px-3 font-medium\">core_type</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['cn_word_pinyin_eval', 'cn.word.score', '拼音 / 汉字发音评测'],\n ['cn_word_raw_eval', 'cn.word.raw', '汉字发音评测'],\n ['cn_sentence_eval', 'cn.sent.raw', '句子朗读评测'],\n ['cn_paragraph_eval', 'cn.pred.raw', '段落朗读评测'],\n ['cn_rec_eval', 'cn.rec.raw', '有限分支识别'],\n ['cn_aitalk_eval', 'cn.recscore.raw', '口语表达评测(AI Talk)'],\n ].map(([fn, ct, desc]) => (\n <tr key={fn}>\n <td className=\"py-2 px-3 font-mono text-xs\">{fn}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{ct}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6\">流式评测函数(3 个)</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">函数名</th>\n <th className=\"text-left py-2 px-3 font-medium\">作用</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr>\n <td className=\"py-2 px-3 font-mono text-xs\">start_stream_eval</td>\n <td className=\"py-2 px-3\">创建流式评测会话,返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code> / <code className=\"bg-muted px-1 rounded text-xs\">ws_url</code> / <code className=\"bg-muted px-1 rounded text-xs\">resume_token</code></td>\n </tr>\n <tr>\n <td className=\"py-2 px-3 font-mono text-xs\">get_stream_result</td>\n <td className=\"py-2 px-3\">兜底拉取:WS 没拿到 <code className=\"bg-muted px-1 rounded text-xs\">result</code> 时,通过 HTTP 把最终评分捞回来</td>\n </tr>\n <tr>\n <td className=\"py-2 px-3 font-mono text-xs\">stream_eval_result</td>\n <td className=\"py-2 px-3 text-muted-foreground\">(MCP 模式的别名;cvx_fc 模式统一用 <code className=\"bg-muted px-1 rounded text-xs\">get_stream_result</code>)</td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <p className=\"text-xs text-muted-foreground mt-4\">\n 📘 每个函数的<strong>业务语义 / 参数 / 返回字段</strong>与 MCP 模式下的同名工具完全一致,可直接参考下文 <a href=\"#tools-en\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">API 参考 · 英文评测工具</a> 与 <a href=\"#tools-cn\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">中文评测工具</a>。\n </p>\n </SubDoc>\n\n <SubDoc id=\"rest-oneshot\" title=\"一次性文件评测\">\n <p>把完整音频通过 <code className=\"bg-muted px-1 rounded text-xs\">audio_base64</code> 或 <code className=\"bg-muted px-1 rounded text-xs\">audio_url</code> 二选一传给 <code className=\"bg-muted px-1 rounded text-xs\">/v1/call</code>,同步返回评分(与 MCP 模式的 <code className=\"bg-muted px-1 rounded text-xs\">evaluate_english_word</code> 等工具语义等价):</p>\n <CodeBlock filename=\"curl · 英文单词\" lang=\"bash\">{`curl -X POST http://your-host:8080/v1/call \\\\\n -H \"Authorization: Bearer your_api_key\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\n \"name\": \"en_word_eval\",\n \"arguments\": {\n \"ref_text\": \"hello\",\n \"audio_url\": \"https://example.com/hello.mp3\",\n \"accent\": 2,\n \"rank\": 100\n }\n }'`}</CodeBlock>\n <p><strong>成功响应:</strong></p>\n <CodeBlock filename=\"response.json\" lang=\"json\">{`{\n \"name\": \"en_word_eval\",\n \"result\": {\n \"overall\": 85.5,\n \"details\": { /* 音素 / 韵律 / 流利度等结构与 MCP 模式完全一致 */ }\n }\n}`}</CodeBlock>\n <p><strong>失败响应:</strong></p>\n <CodeBlock filename=\"error.json\" lang=\"json\">{`{\n \"name\": \"en_word_eval\",\n \"error\": { \"message\": \"错误描述\" }\n}`}</CodeBlock>\n <p className=\"text-xs text-muted-foreground\">单次音频 ≤ 50MB,推荐 16 kHz / 单声道 / 16bit。</p>\n </SubDoc>\n\n <SubDoc id=\"rest-streaming\" title=\"流式评测 · 三步骤\">\n <div className=\"rounded-lg border border-border/60 bg-muted/10 p-5 space-y-5 my-3\">\n <FlowStep title=\"1\">\n <p><strong>创建会话</strong>:<code className=\"bg-muted px-1 rounded text-xs\">POST /v1/call</code> 调用 <code className=\"bg-muted px-1 rounded text-xs\">start_stream_eval</code></p>\n <p className=\"text-muted-foreground mt-1\">响应中拿到 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code> / <code className=\"bg-muted px-1 rounded text-xs\">ws_url</code> / <code className=\"bg-muted px-1 rounded text-xs\">resume_token</code>。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong>WebSocket 推送</strong>:连接 <code className=\"bg-muted px-1 rounded text-xs\">ws_url</code>,收到 <code className=\"bg-muted px-1 rounded text-xs\">{`{\"type\":\"ready\"}`}</code> 即可开始发<strong>二进制音频帧</strong>。</p>\n <p className=\"text-muted-foreground mt-1\">边推边收 <code className=\"bg-muted px-1 rounded text-xs\">intermediate</code> 中间结果;结束时发 <code className=\"bg-muted px-1 rounded text-xs\">{`{\"type\":\"stop\"}`}</code>,收到 <code className=\"bg-muted px-1 rounded text-xs\">result</code> 即为最终评分。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>兜底拉取(可选)</strong>:如 WS 断开未收到最终 <code className=\"bg-muted px-1 rounded text-xs\">result</code>,可再次 <code className=\"bg-muted px-1 rounded text-xs\">POST /v1/call</code> 调用 <code className=\"bg-muted px-1 rounded text-xs\">get_stream_result</code> 兜底。</p>\n </FlowStep>\n </div>\n\n <p className=\"font-semibold mt-4\"><code>start_stream_eval</code> 参数</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.sent.score / cn.sent.raw(参考上方中英评测工具表)' },\n { name: 'ref_text', type: 'string', required: true, desc: '参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式:pcm / wav / mp3(默认 mp3;流式推荐 pcm 省编解码)' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'channel', type: 'number', required: false, desc: '声道:1=单声道(默认)/ 2=立体声' },\n { name: 'sample_bytes', type: 'number', required: false, desc: '采样位深,默认 2(16bit)' },\n { name: 'accent', type: 'number', required: false, desc: '英文口音:1=英式 / 2=美式 / 3=不区分(默认)' },\n { name: 'age_group', type: 'string', required: false, desc: '中文人群:adult(默认)/ child' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100(默认)' },\n ]} />\n\n <p className=\"font-semibold mt-4\">WebSocket 帧协议</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-3\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-xs font-semibold mb-2\">客户端 → 服务端</p>\n <ul className=\"text-xs space-y-1.5 text-muted-foreground leading-relaxed list-none pl-0\">\n <li>• <code className=\"bg-muted px-1 rounded\">Binary</code> — 原始音频帧,连上即可直接发送,无需握手</li>\n <li>• <code className=\"bg-muted px-1 rounded\">{`{\"type\":\"stop\"}`}</code> — 结束录音</li>\n <li>• <code className=\"bg-muted px-1 rounded\">{`{\"type\":\"ping\"}`}</code> — 心跳</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"text-xs font-semibold mb-2\">服务端 → 客户端</p>\n <ul className=\"text-xs space-y-1.5 text-muted-foreground leading-relaxed list-none pl-0\">\n <li>• <code className=\"bg-muted px-1 rounded\">ready</code> — 连接就绪,可开始发送音频</li>\n <li>• <code className=\"bg-muted px-1 rounded\">intermediate</code> — 实时中间结果</li>\n <li>• <code className=\"bg-muted px-1 rounded\">result</code> — 最终评测结果</li>\n <li>• <code className=\"bg-muted px-1 rounded\">backpressure</code> — 背压,含 <code className=\"bg-muted px-1 rounded\">suggested_interval_ms</code>,建议降低发送频率</li>\n <li>• <code className=\"bg-muted px-1 rounded\">error</code> — 错误(含结构化 <code className=\"bg-muted px-1 rounded\">code</code> + <code className=\"bg-muted px-1 rounded\">message</code>,见<a href=\"#error-codes\" className=\"underline underline-offset-2\">错误码</a>一节)</li>\n <li>• <code className=\"bg-muted px-1 rounded\">pong</code> — 心跳响应</li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"rest-examples\" title=\"JS / Android 完整示例\">\n <p className=\"font-semibold\">JavaScript(浏览器 / Node.js)</p>\n <CodeBlock filename=\"browser-or-node.js\" lang=\"javascript\">{`// ① 创建会话\nconst resp = await fetch('http://your-host:8080/v1/call', {\n method: 'POST',\n headers: {\n 'Authorization': 'Bearer your_api_key',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n name: 'start_stream_eval',\n arguments: {\n core_type: 'en.sent.score',\n ref_text: 'Hello world',\n audio_type: 'pcm',\n sample_rate: 16000,\n },\n }),\n});\nconst { result } = await resp.json();\n\n// ② WebSocket 推送音频\nconst ws = new WebSocket(result.ws_url);\nws.onmessage = (ev) => {\n const msg = JSON.parse(ev.data);\n switch (msg.type) {\n case 'ready': startRecording(); break;\n case 'intermediate': console.log('部分结果', msg.data); break;\n case 'result': console.log('最终结果', msg.data); ws.close(); break;\n case 'backpressure': console.warn('降速 ~', msg.suggested_interval_ms, 'ms'); break;\n case 'error': console.error(msg.code, msg.message); break;\n }\n};\n\nfunction onAudioChunk(pcm) {\n if (ws.readyState === WebSocket.OPEN) ws.send(pcm); // 直接发二进制\n}\n\nfunction stopRecording() {\n ws.send(JSON.stringify({ type: 'stop' }));\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-6\">Android(OkHttp · Kotlin)</p>\n <CodeBlock filename=\"AudioEvalClient.kt\" lang=\"kotlin\">{`val wsUrl = \"ws://your-host:8080/ws/eval/$sessionId\"\nval client = OkHttpClient()\nval req = Request.Builder()\n .url(wsUrl)\n .addHeader(\"Authorization\", \"Bearer \\${apiKey}\")\n .build()\n\nval ws = client.newWebSocket(req, object : WebSocketListener() {\n override fun onMessage(ws: WebSocket, text: String) {\n val msg = JSONObject(text)\n when (msg.getString(\"type\")) {\n \"ready\" -> startRecording()\n \"intermediate\" -> updateUI(msg.getJSONObject(\"data\"))\n \"result\" -> handleFinalResult(msg.getJSONObject(\"data\"))\n \"error\" -> handleError(msg.getString(\"code\"), msg.getString(\"message\"))\n }\n }\n})\n\n// 录音回调中发送 PCM 字节\nfun onAudioData(pcm: ByteArray) {\n ws.send(ByteString.of(*pcm))\n}\n\nfun stop() {\n ws.send(\"\"\"{\"type\":\"stop\"}\"\"\")\n}`}</CodeBlock>\n <Callout type=\"tip\">iOS / Flutter / 小程序同理,只要平台提供 WebSocket 客户端即可:连接 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">ws_url</code>、把 PCM bytes 发出去、按 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">type</code> 字段解析服务端消息就行。</Callout>\n </SubDoc>\n\n <SubDoc id=\"rest-resume\" title=\"断线恢复 · 兜底拉取\">\n <p className=\"font-semibold\">resume_token · 60 秒续播</p>\n <p>\n 客户端意外断开时,会话在服务端<strong>挂起 60 秒</strong>;用创建会话拿到的 <code className=\"bg-muted px-1 rounded text-xs\">resume_token</code> 续播即可:\n </p>\n <CodeBlock filename=\"reconnect\" lang=\"http\">{`GET ws://your-host:8080/ws/eval/{session_id}?resume={resume_token}\nAuthorization: Bearer <your_api_key>`}</CodeBlock>\n <Callout type=\"tip\">\n 重连成功后会收到新的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">ready</code>,继续推送音频即可;服务端会颁发<strong>新的 resume_token</strong>,原 token 立即作废。\n </Callout>\n\n <p className=\"font-semibold mt-6\">兜底拉取 · get_stream_result</p>\n <p>如果 WS 没能收到最终 <code className=\"bg-muted px-1 rounded text-xs\">result</code>,可主动通过 HTTP 拉取:</p>\n <CodeBlock filename=\"fallback-result.sh\" lang=\"bash\">{`curl -X POST http://your-host:8080/v1/call \\\\\n -H \"Authorization: Bearer your_api_key\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\n \"name\": \"get_stream_result\",\n \"arguments\": {\n \"session_id\": \"stream-1713340800-a1b2c3\",\n \"auto_stop\": true,\n \"timeout\": 30\n }\n }'`}</CodeBlock>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '由 start_stream_eval 返回的会话 ID' },\n { name: 'auto_stop', type: 'bool', required: false, desc: '是否自动发送 stop 并等待结果,默认 true' },\n { name: 'timeout', type: 'number', required: false, desc: '等待秒数,默认 30' },\n ]} />\n\n <Callout type=\"warning\">\n functioncalling 模式与 MCP 模式<strong>二选一、不要混用</strong>:同一个会话不要同时走 MCP 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">/ws/audio/</code> 和 cvx_fc 的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs\">/ws/eval/</code>,两条链路的会话 ID 空间是分开的。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 评测模式 ══════ */}\n <DocSection id=\"eval-modes\" icon={Radio} title=\"评测模式\">\n\n <SubDoc id=\"stream-eval\" title=\"实时录音评测(边录边评)\">\n <p>通过本地麦克风实时录音,音频以 WebSocket 流式推送到评测服务,<strong>无需生成中间文件</strong>。这是最灵活、延迟最低的评测方式,特别适合交互式口语练习场景。</p>\n <Callout type=\"tip\">实时录音评测需要安装 <strong>SoX</strong> 并使用本地代理模式(Claude Desktop / Claude Code)。Cursor 等远程直连模式暂不支持实时录音。</Callout>\n\n <p className=\"font-semibold mt-4\">需要按顺序调用三个工具:</p>\n\n <div className=\"mt-3 space-y-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">create_stream_session</code> — 创建流式评测会话</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.word.score、en.sent.score、cn.sent.raw' },\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式,默认 mp3' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100,默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code>,后续步骤需使用。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">start_recording</code> — 开始麦克风录音</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '来自 create_stream_session 的会话 ID' },\n { name: 'chunk_size', type: 'number', required: false, desc: '音频块大小(bytes),默认 3200' },\n { name: 'chunk_interval_ms', type: 'number', required: false, desc: '推送间隔(ms),默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">调用后自动启动麦克风,音频通过 WebSocket 实时推送到评测服务。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">stop_recording</code> — 停止录音并获取结果</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '会话 ID' },\n { name: 'timeout', type: 'number', required: false, desc: '等待评测结果超时秒数,默认 30' },\n ]} />\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"stream-flow\" title=\"流式评测完整流程\">\n <div className=\"rounded-lg border border-border/60 p-5 bg-muted/10 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong>用户:</strong>&quot;请评测我的英文发音,文本是 Hello&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">create_stream_session(core_type=&quot;en.word.score&quot;, ref_text=&quot;Hello&quot;)</code></p>\n <p className=\"text-muted-foreground\">→ 获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">session_id</code></p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p>AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">start_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground mt-1\">麦克风开始录音,音频通过 WebSocket 实时推送到评测服务</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>用户</strong>说完后告知 AI &quot;结束&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">stop_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground\">→ 返回评测结果(总分、准确度、流利度、音素得分等)</p>\n </FlowStep>\n </div>\n </SubDoc>\n\n <SubDoc id=\"file-eval\" title=\"音频文件评测\">\n <p>适用于已有音频文件的评测场景,支持三种输入方式:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">适用场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_file_path</td><td className=\"py-2 px-3\">本地文件路径</td><td className=\"py-2 px-3 text-muted-foreground\">代理自动读取并转为 Base64,最便捷</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_base64</td><td className=\"py-2 px-3\">Base64 编码数据</td><td className=\"py-2 px-3 text-muted-foreground\">已有编码数据时使用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_url</td><td className=\"py-2 px-3\">音频文件 URL</td><td className=\"py-2 px-3 text-muted-foreground\">音频托管在云存储时使用</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"tip\">推荐使用 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">audio_file_path</code>,只需传入本地文件路径,代理会自动处理编码和上传。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"tools-en\" title=\"英文评测工具(10 种)\">\n <ToolTable tools={[\n ['en_word_eval', '单词评测', '评测 \"Hello\" 的发音准确度', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.word.score'],\n ['en_word_correction', '单词纠音', '检测多读/漏读/错读并给出纠正建议', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.word.pron'],\n ['en_vocab_eval', '词语评测', '同时评测多个单词的发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.vocabs.pron'],\n ['en_sentence_eval', '句子评测', '评测整句朗读的准确度与流利度', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.sent.score'],\n ['en_sentence_correction', '句子纠音', '逐词检测发音问题并给出修正建议', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.sent.pron'],\n ['en_paragraph_eval', '段落朗读评测', '评测整段英文课文的朗读质量', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.pred.score'],\n ['en_phonics_eval', '自然拼读评测', '评测 Phonics 拼读规则掌握程度', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.nsp.score'],\n ['en_choice_eval', '口语选择题', '从预设选项中识别口语回答', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.choc.score'],\n ['en_semi_open_eval', '半开放题评测', '场景对话等半开放口语题型', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.scne.exam'],\n ['en_realtime_eval', '实时朗读评测', '实时反馈朗读质量,逐句评分', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.rltm.score'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong>所有英文评测工具均支持以下参数。</p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tools-cn\" title=\"中文评测工具(6 种)\">\n <ToolTable tools={[\n ['cn_word_raw_eval', '单字评测(汉字)', '评测 \"中\" 的发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.pred.raw'],\n ['cn_word_pinyin_eval', '单字评测(拼音)', '评测 \"zhōng\" 的发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.pred.pny'],\n ['cn_sentence_eval', '词句评测', '评测 \"你好世界\" 的朗读', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.sent.score'],\n ['cn_paragraph_eval', '段落朗读评测', '评测一段中文课文的朗读质量', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.para.score'],\n ['cn_rec_eval', '有限分支识别评测', '从预设选项中识别用户发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.asr.rec'],\n ['cn_aitalk_eval', 'AI Talk 口语评测', '中文口语对话能力评测', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.aitalk'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong></p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本(汉字或拼音)' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结构化结果:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"integrity\": 95,\n \"details\": [\n {\n \"char\": \"hello\",\n \"score\": 85,\n \"phone\": [\n { \"phoneme\": \"h\", \"score\": 90 },\n { \"phoneme\": \"ɛ\", \"score\": 82 },\n { \"phoneme\": \"l\", \"score\": 88 },\n { \"phoneme\": \"oʊ\", \"score\": 80 }\n ]\n }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall', '0–100', '综合总分,反映整体语音质量'],\n ['accuracy', '0–100', '准确度,音素级别的发音准确率'],\n ['pron', '0–100', '发音得分,整体发音质量评价'],\n ['fluency.overall', '0–100', '流利度,语流的自然度与平滑度'],\n ['fluency.speed', '0–100', '语速得分,每分钟词/音节计量'],\n ['fluency.pause', 'number', '不自然停顿次数'],\n ['integrity', '0–100', '完整度,是否完整朗读了参考文本'],\n ['details[].phone[]', 'array', '每个音素的独立评分,可定位具体弱项'],\n ['details[].stress[]', 'array', '重音得分,检测重弱读是否正确'],\n ].map(([field, range, desc]) => (\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <p className=\"font-semibold mb-2\">常见业务场景</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期', '音频上传后 5 分钟内有效,评测完成后自动删除', '重新上传音频'],\n ['服务器繁忙', '当前请求过多,排队已满', '稍后重试,使用指数退避策略'],\n ['存储空间已满', '服务器临时音频存储已满', '稍后重试,系统会自动清理'],\n ['音频格式不支持', '上传了不支持的文件类型', '使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限', '超过该题型最大时长限制', '裁剪音频后重试'],\n ['WebSocket 连接断开', '网络不稳定导致流式连接中断', '代理会自动重连,如持续失败请检查网络'],\n ].map(([scene, reason, fix]) => (\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6 mb-2\">HTTP 状态码(functioncalling 模式)</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">状态码</th>\n <th className=\"text-left py-2 px-3 font-medium\">含义</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['400', '请求格式错误(JSON 缺字段 / 类型错)'],\n ['401', '未认证(缺少 Authorization 头)'],\n ['403', '权限不足(API Key 无效或无权使用该评测类型)'],\n ['404', '会话不存在(session_id 错误或已回收)'],\n ['409', '会话状态不允许当前操作(如已 stop 后又发音频)'],\n ].map(([code, desc]) => (\n <tr key={code}>\n <td className=\"py-2 px-3 font-mono text-xs\">{code}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-6 mb-2\">流式评测结构化错误码</p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n WebSocket 的 <code className=\"bg-muted px-1 rounded\">error</code> 帧与 <code className=\"bg-muted px-1 rounded\">get_stream_result</code> 的 HTTP 错误体统一使用以下错误码,便于客户端差错分流:\n </p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">错误码</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">建议处理</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['SESSION_NOT_FOUND', '会话不存在', '重新调用 start_stream_eval / create_stream_session 创建'],\n ['SESSION_EXPIRED', '会话已过期(60s 未续流)', '重新创建会话'],\n ['INVALID_STATE', '当前状态不允许该操作', '检查调用顺序(例如已 stop 后又发音频)'],\n ['INVALID_PARAMS', '参数无效', '核对 core_type / ref_text / 采样率等'],\n ['RESUME_INVALID', 'resume_token 无效或已过期', '重新创建会话(每次重连会颁发新 token)'],\n ['AUDIO_TOO_LARGE', '单次音频超过 50MB', '压缩或切片后重传'],\n ['UPSTREAM_CONNECT', '评测引擎连接失败', '稍后重试;持续发生请联系驰声'],\n ['UPSTREAM_TIMEOUT', '评测超时', '缩短音频 / 检查网络'],\n ['UPSTREAM_EVAL_ERROR', '评测引擎返回错误', '查看 message 字段细节'],\n ['CAPACITY_FULL', '并发会话已满', '退避重试或升级配额'],\n ].map(([code, desc, fix]) => (\n <tr key={code}>\n <td className=\"py-2 px-3 font-mono text-xs\">{code}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{desc}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p className=\"text-sm text-muted-foreground\">\n 下面这些模板与{' '}\n <Link href=\"/demo\" className=\"underline underline-offset-2\">免费体验</Link>{' '}\n Demo 中实际使用的「教练 Prompt」一脉相承,全部围绕 <strong>角色 / 任务 / 方法 / 输出格式 / 语气</strong> 五段式结构,并使用占位符{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code> 直接拼接 MCP 工具返回的 JSON。\n 推荐在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">system</code> 消息里挂载,再把工具返回原文塞进\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">user</code> 消息,由 LLM 基于真实分数和音素生成可读诊断 / 练习材料。\n </p>\n\n <Callout type=\"tip\">\n <strong>使用建议:</strong>\n <ul className=\"list-disc list-inside mt-1 space-y-1\">\n <li>把 MCP 工具调用的完整 JSON(含 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">overall / dp_type / phonemes</code> 等字段)原样塞入 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,不要预先做汇总——细节越完整诊断越具体。</li>\n <li>「诊断」类模板让 LLM 输出 Markdown 给最终用户阅读;「练习」类模板要求 LLM 输出严格 JSON 给前端渲染。两步分开调用,效果远好于一次性混合。</li>\n <li>题型决定 prompt:单词题用音素级、句子题加韵律、段落题加 chunk / 整体性,半开放题套四维评级。下面按这 6 类分别给出。</li>\n </ul>\n </Callout>\n\n {/* ─── A. 诊断类 · 输出 Markdown 给学习者阅读 ─── */}\n <h4 className=\"text-sm font-semibold mt-6 mb-3 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> A. 二次诊断 · 输出 Markdown\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Sparkle className=\"h-4 w-4\" /> 1. 单词级 · 音素诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.word.score</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code>。围绕 <code className=\"bg-muted px-1 rounded font-mono\">phonemes[].dp_type</code> 找出错读音。\n </p>\n <CodeBlock filename=\"prompt-word-diagnosis.txt\">{`你是一位专业的英语发音教练。下面是驰声 MCP(en.word.score)返回的单词级评测数据:\n一个目标词、总分与发音分、以及带有 dp_type 的音素数组。\n\n## 任务\n为学习者写一份 **可直接阅读** 的发音诊断,每一条都要引用音素分数(写出 IPA 符号)。\n\n## 方法\n- 按 dp_type 分组:先看 mispron,再补充 normal 但分数 < 70 的\"边缘音\"。\n- 每个问题音素:① 典型母语干扰 → ② 你听到的声音 → ③ 一条具体的发音修正动作(下颌 / 舌位 / 气流 / 唇形)。\n- 重音符号 ˈ 标记的音节,重音错位也要单独列出(即便音素本身分数尚可)。\n- 最多列 4 条,按 **影响力从弱到强** 排序。\n\n## 输出\n精简 Markdown:可选一句总结 → 编号列表 → 单行 **🎯 优先级** 排序。\n\n## 语气\n友善、具体、可执行,不要空喊\"加油\"。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> 2. 句子级 · 节奏 + 重音诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.sent.score</code>。开头先给一行\"计分牌\",再从 phones → words → prosody。\n </p>\n <CodeBlock filename=\"prompt-sentence-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.sent.score)返回的句子级数据:refText、总分、发音子分(accuracy / integrity / fluency / rhythm)、可选 speed(WPM),以及 details[] 中的逐词得分、dp_type 与可选 phonemes[]。\n\n## 任务\n开头给一行紧凑的 **计分牌**(总分 + 各子分 + 速度),再按 **音素 → 词 → 韵律** 顺序展开诊断。\n\n## 检查清单(每项都要覆盖;不适用则写\"无明显问题\")\n1. **音素级**:每个 score < 70 或 dp_type ≠ normal 的词,展开 phonemes,命名典型错误(如 /ð/ → /d/)并给一条修正动作。\n2. **漏读 / 多读**:标注 dp_type=omit / insert,解释如何拉低 integrity。\n3. **韵律**:基于 rhythm + 错读词的重音,指出重音错位、陈述句调型平直、断句过多等。\n4. **流利度 vs 准确度**:若 fluency 高但 accuracy 低,明确指出,让学习者知道优先改哪一项。\n\n## 输出格式\nMarkdown 子标题:**Pronunciation / Prosody / Study priority**(最后一行简短排序,按 ROI 从高到低)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <FileText className=\"h-4 w-4\" /> 3. 段落级 · 音素族归并 + 完整度\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code>。把同类错音合并为\"音素族\",避免逐词复述。\n </p>\n <CodeBlock filename=\"prompt-paragraph-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.pred.score)返回的段落级数据:长 refText、总分、发音子分、可选 speed / integrity,以及大量 details[](同一个词可能在不同子句中重复出现;空 char + dp_type=omit 表示漏读)。\n\n## 任务\n对整段做 **整体诊断**:\n(a) 完整度 / 漏读,\n(b) 反复出现的 **音素族**,\n(c) 节奏与断句,\n(d) 各子分之间是否相互印证。\n\n## 检查清单\n1. **integrity / dp_type**:列出每个 omit / insert,并给出所在子句;解释长读时为什么虚词容易被吞。\n2. **音素聚类**:将弱词按共同音素问题归族(/θ/ 链、/e/ 链、/ɒ/ 等),引用 details 中的实例。\n3. **韵律**:结合 rhythm 与标点 —— 短语内停顿过多?子句边界缺失?\n4. **整体一致性**:若 fluency 正常但 accuracy 差(或反之),点明并指出对练习设计的影响。\n\n## 输出格式\nMarkdown 章节:**Summary / Completeness / Pronunciation (families) / Rhythm / Study priorities**(最后一项最多 4 条,按影响力排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <GraduationCap className=\"h-4 w-4\" /> 4. 半开放题 · IELTS 风格四维评级\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pqan.score</code>。结合学习者 transcript + 四维分数,给出可教学的\"升档建议\"。\n </p>\n <CodeBlock filename=\"prompt-semi-open.txt\">{`你是一位 **IELTS / 课堂口语风格** 的口语教练。下面是驰声 MCP(en.pqan.score)返回的半开放题数据:题目 refText、学习者 transcript、overall、四个维度分(grammar / content / fluency / pron)、speed(WPM)、以及结构化的 issues[] 标记。\n\n## 任务\n给出 **基于 rubric 的诊断**:先用一句话给出 0–100 的整体段位估计,再 **逐维度** 解释,并 **引用 transcript 中的短句**(不要复述全文)。最后给 3–5 条 **可执行的\"升档建议\"**(句型 / 模板,而不是\"多练一点\")。\n\n## 权重(要明确告诉学习者)\n- **Content ~30%** — 想法深度、举例、具体程度\n- **Grammar ~25%** — 句型多样性、主谓一致、从句\n- **Fluency ~25%** — 语速、停顿、口头禅、衔接\n- **Pronunciation ~20%** — 音素 + 可懂度\n\n## 跨维度检查\n- 若 content 最低,先给\"展开模板\",不要先抠音素。\n- 若 fluency 低 + WPM 低,区分\"思考型停顿\"与\"发音卡顿\"。\n- 若 pron 标出特定音,绑定到 transcript 里 **真实出现过的词**。\n\n## 输出格式\nMarkdown:**段位估计** → 紧凑的 **得分表格** → **\"To reach the next band\"** 编号列表(每条给出可仿写的句型)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Languages className=\"h-4 w-4\" /> 5. 中文专项 · 声调 / 儿化 / 变调\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.sent.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.pred.raw</code>。中文评测最大权重在 **声调**,必须单独检查。\n </p>\n <CodeBlock filename=\"prompt-mandarin-coach.txt\">{`你是一位专业的普通话口语教练。下面是驰声 MCP(cn.sent.raw)返回的中文句子评测数据:整句分数 + 逐字 details + 可选 phonemes + 流利 / 节奏分。\n\n## 中文评测的权重\n- **声调错误权重最高** —— 声调读错可能直接改变词义\n- dp_type=mispron 多来自:声调错、平翘舌混用、前后鼻音混用\n- 拼音对、声调错,听感仍是\"洋腔洋调\"\n\n## 检查清单(缺则写\"无明显问题\")\n1. **儿化**:phonemes 出现 r(儿化) 或 dp_type 提示时,确认是否被读成\"主音节 + 单独儿\"两拍。\n2. **轻声**:叠词、词缀、句末语气词 —— 第二字应短促轻读,无完整声调曲线。\n3. **变调**:三声连读、不 + 后字、一 + 后字,是否符合普通话规则。\n4. **节奏**:用 rhythm + 逗号位置,标记停顿过长或过碎,建议 chunk 边界。\n\n## 输出\nMarkdown 子标题:**总览 / 儿化 / 轻声 / 变调 / 节奏 / 学习优先级**(最后一项 1–3 条,按\"听感自然度提升\"排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── B. 练习类 · 输出 JSON 给前端渲染 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <Mic className=\"h-4 w-4\" /> B. 微练习生成 · 输出严格 JSON\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 6. 单词 / 句子 · 微练习\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 建议在 <strong>诊断完成后</strong> 第二轮调用:让 LLM 严格按弱项音素生成,避免\"通用绕口令包\"。\n </p>\n <CodeBlock filename=\"prompt-micro-drills.txt\">{`你仍是这位学习者的发音教练。**第二轮诊断已经完成** —— 现在严格基于诊断里点名的弱音素,生成 **1:1 对应** 的微练习(不要给通用题)。\n\n## 输出(仅返回一段合法 JSON 数组,外层不要任何文字)\n根节点必须是数组,每个元素含 category(字符串)、icon(单个 emoji)、items({ \"label\", \"content\" } 数组)。\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme drills\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Awareness\", \"icon\": \"🗣️\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Whole word\", \"icon\": \"📝\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme drills**:诊断里每个 mispron 音素至少给一对最小对立对 + 一句短绕口令 / 重复句。\n- **Awareness**:当重音偏弱时,给一条 stress / 舌位 / 下颌姿态提示。\n- **Whole word**:2–3 句自然句,目标词分别出现在句首 / 句中 / 句末。\n- 所有 content 必须可念出声;字符串内不要再嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n原始 MCP:\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Notebook className=\"h-4 w-4\" /> 7. 段落 · Chunk Map + 元认知\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 段落级练习要从\"音素\"上升到\"短语 / 整段 / 元认知录回放\"。直接对应 <code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code> 的诊断结果。\n </p>\n <CodeBlock filename=\"prompt-paragraph-drills.txt\">{`你仍是这位学习者的口语教练。**段落级二次诊断** 已经列出了漏读位置和音素族 —— 现在请生成 **整段练习**:把弱音素串成\"族\"练习、用 chunk map 把虚词粘回短语,并附一条录音 → 回放 → 对照 MCP details 的元认知步骤。\n\n## 输出(仅返回一段合法 JSON 数组)\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme families\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Chunking\", \"icon\": \"🎵\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Metacognition\", \"icon\": \"🧠\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme families**:至少 3 条,按诊断里的音素族(/θ/、/e/、/ɒ/ 等)组织最小对立对 / 词链。\n- **Chunking**:① 给一行用 [ ] 和 / 标注的 chunk map(可加大致停顿秒数);② 给一句\"把漏掉的虚词嵌入短语\"的练习(不要孤立练)。\n- **Metacognition**:一条\"录音 → 回放 → 对照 MCP details\"的 N 遍练习(N 写明,2–3 之间)。\n- 字符串只能是纯展示文本,禁止嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── C. 跨题 / 运营 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" /> C. 跨题 / 运营\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Gauge className=\"h-4 w-4\" /> 8. 多日学习报告\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 把过去 N 天的 MCP 评测结果聚合后丢给 LLM,输出适合发给学生 / 家长的周报。\n </p>\n <CodeBlock filename=\"prompt-weekly-report.txt\">{`你是一位英语口语学习顾问。下面是这位学习者过去 7 天的驰声 MCP 评测历史(按时间顺序),每条包含题型、coreType、overall、子分、weak_phonemes 摘要。\n\n## 任务\n为学习者生成一份 **周报**,目标读者:本人 + 家长 / 老师。\n强调 **趋势** 与 **下一步计划**,不要复述每一次具体分数。\n\n## 必须包含\n1. **整体趋势**:进步 / 持平 / 退步(一句话 + 简短依据)\n2. **维度变化**:accuracy / fluency / rhythm / integrity / speed 任意有数据的维度,给出\"本周均分 vs 上周\"\n3. **本周高光**:分数提升最大的题型 / 音素 / 句型\n4. **顽固问题**:连续 3 次以上未达 70 的音素或维度\n5. **下周计划**:3 条具体动作(每条对应一个 MCP 工具或一个练习类型)\n6. **结尾**:1–2 句鼓励性总结,避免空话\n\n## 风格\n- 像一位认真但不啰嗦的私教\n- 出现学生姓名时使用占位 {student_name}\n- 数字一律保留整数\n\n## 评测历史\n\\`\\`\\`json\n{history}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 9. 错误归因 · JSON 摘要\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 用于后端聚合 / 数据看板:让 LLM 把一次评测压缩成结构化摘要,方便入库统计。\n </p>\n <CodeBlock filename=\"prompt-error-summary.txt\">{`你是一个评测结果归类器。下面是驰声 MCP 一次评测的完整返回。请抽取关键信息并 **仅返回合法 JSON**(不要任何解释文字)。\n\n## Schema\n\\`\\`\\`json\n{\n \"core_type\": \"string,例如 en.sent.score\",\n \"overall\": 0,\n \"sub_scores\": { \"accuracy\": 0, \"integrity\": 0, \"fluency\": 0, \"rhythm\": 0 },\n \"weak_phonemes\": [\n { \"ipa\": \"string\", \"avg_score\": 0, \"occur\": 0, \"example_word\": \"string\" }\n ],\n \"omissions\": [{ \"after\": \"string\", \"missing\": \"string\" }],\n \"stress_issues\": [{ \"word\": \"string\", \"expected\": \"string\", \"actual\": \"string\" }],\n \"primary_issue_tag\": \"phoneme|stress|fluency|integrity|content|none\",\n \"next_action_hint\": \"string,10 字以内,例如 \\\"练 /θ/ 对立对\\\"\"\n}\n\\`\\`\\`\n\n## 规则\n- weak_phonemes 只收 avg_score < 70 的项,最多 5 项,按 occur 降序。\n- 不要复述 refText,不要 markdown。\n- 字段缺失时使用 null 或空数组,不要省略 key。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n <Callout type=\"info\">\n <strong>怎么接到 MCP 工具调用?</strong>\n <br />\n 以「直调 chat.completions + 动态 tools」为例:第一轮 LLM 调出 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code> 工具,把工具 result(即 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">tool</code> 消息内容)原样填入上面任意「诊断」模板的{' '}\n <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,作为第二轮 user 消息发回;拿到诊断结论再走第三轮,套「微练习」模板得到 JSON。三轮调用即可串起 <strong>评测 → 诊断 → 练习</strong> 全链路。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频过期:</strong>上传后 5 分钟内有效。建议在上传后 3 分钟内完成评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>服务繁忙:</strong>使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>录音中断:</strong>WebSocket 断线后代理会自动重连。如持续失败,检查网络连接和 SoX 安装。</span></li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>优先使用流式评测:</strong>实时录音模式跳过了文件上传环节,端到端延迟比文件评测低 30-50%。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频压缩:</strong>文件评测时,上传前将音频转为 16kHz 单声道 mp3,可减少 60-80% 的上传体积。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>选择合适的工具:</strong>单词评测比段落评测快 3-5 倍。如只需单词级分数,不要用句子评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>本地文件路径最便捷:</strong>使用 <code className=\"bg-muted px-1 rounded text-xs\">audio_file_path</code> 参数传入本地路径,代理自动处理编码,无需手动转 Base64。</span></li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Globe} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传(MCP 模式文件评测使用)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP 模式 · JSON-RPC(Streamable HTTP)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/audio/{session_id}'}</td><td className=\"py-2 px-3\">WS</td><td className=\"py-2 px-3\">MCP 模式 · 流式音频 WebSocket</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/v1/functions</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">functioncalling 模式 · 列出全部评测函数</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/v1/call</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">functioncalling 模式 · 一次性评测 / 创建流式会话 / 拉取流式结果</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">{'/ws/eval/{session_id}'}</td><td className=\"py-2 px-3\">WS</td><td className=\"py-2 px-3\">functioncalling 模式 · 流式音频 WebSocket</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制', '500MB', '最大音频存储空间'],\n ['最大排队', '10', '等待处理的请求数'],\n ['最大并发', '3', '同时处理的评测数'],\n ['音频有效期', '5 分钟', '上传后未评测自动过期'],\n ['文件大小限制', '50MB', '单个音频文件最大'],\n ].map(([k, v, d]) => (\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">\n 上表为托管侧<strong>技术默认</strong>;计费与试用/商用<strong>调用配额、并发档位</strong>以{' '}\n <Link href=\"/dashboard/plans\" className=\"text-foreground underline underline-offset-2\">\n 会员套餐\n </Link>\n 说明为准,与上表独立。\n </p>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <Callout type=\"info\">\n <strong>当前线上版本:v1.0.0</strong>(网站未正式上线)。以下变更项为开发阶段规划记录,以正式发布公告为准。\n </Callout>\n <div className=\"space-y-3 mt-4\">\n {[\n { ver: 'v1.0.0', date: 'TBD', desc: '当前线上版本(网站未正式上线)' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </DocsShell>\n );\n}\n\nexport default function DocsPage() {\n const locale = useLocale();\n return locale.startsWith('zh') ? <DocsPageZh /> : <DocsPageEn />;\n}\n"}, "ce93676d08f8657f4a01a76b1f6a83340d025909": {"t": 1785765791.3998358, "n": 146308, "h": "46965a277b762163", "c": "'use client';\n\nimport { useLocale } from 'next-intl';\nimport { Link } from '@/i18n/routing';\nimport { DocsShell, type NavGroup } from './docs-shell';\nimport {\n CodeBlock,\n DocSection,\n SubDoc,\n ParamTable,\n Callout,\n FlowStep,\n ToolTable,\n} from './docs-shared';\nimport { DocsEnglishBody } from './docs-content-en';\nimport {\n BookOpen,\n Code2,\n Zap,\n FileText,\n Terminal,\n Globe,\n Mic,\n MessageSquare,\n BarChart3,\n AlertTriangle,\n Radio,\n FolderOpen,\n Workflow,\n Sparkles,\n Plug,\n Bot,\n Building2,\n Check,\n Sparkle,\n Languages,\n GraduationCap,\n Notebook,\n Gauge,\n ListChecks,\n} from 'lucide-react';\n\n/* ─── Navigation Structure ─── */\n\nconst NAV = [\n { id: 'concept', icon: Workflow, label: 'MCP 工作原理', children: [\n { id: 'why-mcp', label: '为什么需要 MCP' },\n { id: 'scenario-walkthrough', label: '场景走查 · 6 步' },\n { id: 'dev-responsibility', label: '开发者只做两件事' },\n { id: 'integration-paths', label: '三种接入姿势' },\n ]},\n { id: 'architecture', icon: Terminal, label: '架构说明', children: [\n { id: 'arch-diagram', label: '架构图' },\n { id: 'transport', label: '传输协议' },\n ]},\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'requirements', label: '系统要求' },\n { id: 'install', label: '安装方式' },\n { id: 'env-vars', label: '环境变量' },\n ]},\n { id: 'config', icon: Plug, label: '接入 · 直接用(零代码)', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: '扣子(Coze)' },\n { id: 'config-coze-workflow', label: 'Coze 工作流' },\n { id: 'config-ai-workspace', label: 'AI 工作台 · 豆包 / 飞书 / WorkBuddy / 钉钉' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: '其他 MCP 客户端' },\n ]},\n { id: 'config-code', icon: Code2, label: '接入 · 编程用(SDK / 代码)', children: [\n { id: 'code-mcp-sdk', label: '官方 MCP 客户端库' },\n { id: 'code-agent-frameworks', label: 'LangChain / Mastra / Agents SDK' },\n { id: 'code-function-calling', label: '直调 chat.completions · 上传与动态 tools' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM(智谱)' },\n { id: 'llm-kimi', label: 'KIMI(Moonshot)' },\n { id: 'llm-doubao', label: '豆包 Seed 2.0(火山方舟)' },\n { id: 'llm-openai', label: 'OpenAI(GPT-5.1)' },\n { id: 'llm-claude', label: 'Claude(Anthropic)' },\n { id: 'llm-gemini', label: 'Gemini(Google)' },\n { id: 'llm-comparison', label: '模型对比速查' },\n { id: 'code-selfhosted-agent', label: '自研后端 Agent' },\n ]},\n { id: 'eval-modes', icon: Radio, label: '评测模式', children: [\n { id: 'stream-eval', label: '实时录音评测' },\n { id: 'stream-flow', label: '流式评测流程' },\n { id: 'file-eval', label: '音频文件评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'tools-en', label: '英文评测工具' },\n { id: 'tools-cn', label: '中文评测工具' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n { id: 'service-info', icon: Globe, label: '服务信息', children: [\n { id: 'endpoints', label: '服务端点' },\n { id: 'limits', label: '服务限制' },\n { id: 'changelog', label: '更新日志' },\n ]},\n];\n\nconst NAV_EN: NavGroup[] = [\n { id: 'concept', icon: Workflow, label: 'How MCP works', children: [\n { id: 'why-mcp', label: 'Why MCP' },\n { id: 'scenario-walkthrough', label: 'Scenario · 6 steps' },\n { id: 'dev-responsibility', label: 'Two developer tasks' },\n { id: 'integration-paths', label: 'Three integration styles' },\n ]},\n { id: 'architecture', icon: Terminal, label: 'Architecture', children: [\n { id: 'arch-diagram', label: 'Diagram' },\n { id: 'transport', label: 'Transports' },\n ]},\n { id: 'quick-start', icon: Zap, label: 'Quick start', children: [\n { id: 'overview', label: 'Overview' },\n { id: 'requirements', label: 'Requirements' },\n { id: 'install', label: 'Install' },\n { id: 'env-vars', label: 'Environment' },\n ]},\n { id: 'config', icon: Plug, label: 'Zero-code clients', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: 'Coze' },\n { id: 'config-coze-workflow', label: 'Coze workflows' },\n { id: 'config-ai-workspace', label: 'Enterprise AI workspaces' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: 'Other MCP clients' },\n ]},\n { id: 'config-code', icon: Code2, label: 'SDK / code integration', children: [\n { id: 'code-mcp-sdk', label: 'Official MCP SDK' },\n { id: 'code-agent-frameworks', label: 'Agent frameworks' },\n { id: 'code-function-calling', label: 'chat.completions · upload & tools' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM (Zhipu)' },\n { id: 'llm-kimi', label: 'KIMI (Moonshot)' },\n { id: 'llm-doubao', label: 'Doubao Seed 2.0 (Volcano Ark)' },\n { id: 'llm-qwen', label: 'Qwen (Alibaba)' },\n { id: 'llm-openai', label: 'OpenAI (GPT-5.1)' },\n { id: 'llm-claude', label: 'Claude (Anthropic)' },\n { id: 'llm-gemini', label: 'Gemini (Google)' },\n { id: 'llm-comparison', label: 'Model comparison' },\n { id: 'code-selfhosted-agent', label: 'Custom backend' },\n ]},\n { id: 'eval-modes', icon: Radio, label: 'Evaluation modes', children: [\n { id: 'stream-eval', label: 'Streaming mic' },\n { id: 'stream-flow', label: 'Streaming flow' },\n { id: 'file-eval', label: 'Audio files' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API reference', children: [\n { id: 'tools-en', label: 'English tools' },\n { id: 'tools-cn', label: 'Chinese tools' },\n { id: 'result-fields', label: 'Result fields' },\n { id: 'error-codes', label: 'Errors' },\n ]},\n { id: 'best-practices', icon: FileText, label: 'Best practices', children: [\n { id: 'prompt-templates', label: 'Prompt templates' },\n { id: 'error-handling', label: 'Errors & retries' },\n { id: 'performance', label: 'Performance' },\n ]},\n { id: 'service-info', icon: Globe, label: 'Service info', children: [\n { id: 'endpoints', label: 'Endpoints' },\n { id: 'limits', label: 'Limits' },\n { id: 'changelog', label: 'Changelog' },\n ]},\n];\n\n/* ─── Page(英文 / 中文拆成两个组件,避免切换语言时 Hooks 数量变化导致白屏)─── */\n\nfunction DocsPageEn() {\n return (\n <DocsShell\n nav={NAV_EN}\n backLabel=\"Back to Home\"\n title=\"Developer documentation\"\n subtitle=\"Chivox Speech Evaluation MCP · chivox-local-mcp\"\n >\n <DocsEnglishBody />\n </DocsShell>\n );\n}\n\nfunction DocsPageZh() {\n return (\n <DocsShell\n nav={NAV}\n backLabel=\"返回首页\"\n title=\"开发者文档\"\n subtitle=\"驰声语音评测 MCP · chivox-local-mcp\"\n >\n {/* ══════ MCP 工作原理 ══════ */}\n <DocSection id=\"concept\" icon={Workflow} title=\"MCP 工作原理\">\n\n <SubDoc id=\"why-mcp\" title=\"为什么需要 MCP\">\n <p>\n 大模型(GPT / Claude / Gemini / 通义千问 等)本质上是<strong>文字大脑</strong>:它擅长读和写,但天生<strong>听不到</strong>音频、<strong>打不开</strong>你的数据库、<strong>调不通</strong>第三方 API。\n </p>\n <p>\n <strong>MCP(Model Context Protocol)</strong>是一套由 Anthropic 主导、业界共同推进的开放协议,它像一根\"对讲机\":让大模型可以在需要时主动去调用外部能力(工具、数据源、服务),然后把结果接回来继续对话。\n </p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <AlertTriangle className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n <span className=\"text-sm font-semibold\">没有 MCP 的做法</span>\n </div>\n <ul className=\"text-xs text-muted-foreground space-y-1.5 leading-relaxed\">\n <li>· 研究评测服务的 REST 文档</li>\n <li>· 自己写鉴权 / 上传 / 轮询代码</li>\n <li>· 把结果序列化后塞进 prompt</li>\n <li>· 每换一个大模型都得重写一遍</li>\n </ul>\n </div>\n <div className=\"rounded-lg border-2 border-foreground/60 p-4 bg-foreground/[0.02]\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Check className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">用驰声 MCP 之后</span>\n </div>\n <ul className=\"text-xs space-y-1.5 leading-relaxed\">\n <li>· 填一行 MCP Server 地址</li>\n <li>· 大模型自动发现&amp;调用评测工具</li>\n <li>· 换任何支持 MCP 的大模型都能用</li>\n <li>· 不写一行调用代码</li>\n </ul>\n </div>\n </div>\n <Callout type=\"info\">\n MCP 让\"接入语音评测\"从一个\"后端工程\"变成了一个\"配置项\"。这也是为什么 Cursor、Claude Desktop、Dify、Coze、字节豆包、飞书智能助手都已经原生支持 MCP。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"scenario-walkthrough\" title=\"场景走查 · 从一段录音到温柔反馈\">\n <p>\n 想象一个具体场景:一位开发者用 Claude 做了一款 <strong>「AI 雅思口语陪练」小程序</strong>,接入驰声语音评测 MCP。当用户发来一段录音,整个链路是这样的:\n </p>\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-5 my-4 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong className=\"text-foreground\">配置阶段</strong>(开发者 · 一次性)</p>\n <p className=\"text-muted-foreground mt-1\">在 AI 后端的配置文件里填一行驰声 MCP Server 地址。大模型启动时自动发现:「我多了一个叫<strong>语音评测</strong>的工具」。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong className=\"text-foreground\">接收用户指令</strong>(终端用户)</p>\n <p className=\"text-muted-foreground mt-1\">小程序用户发来一段自我介绍录音:「帮我听听,发音哪里有问题?」后端把「文字 + 录音链接」打包给大模型。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong className=\"text-foreground\">大模型自主思考</strong>(LLM · 最核心的魔法)</p>\n <div className=\"text-muted-foreground mt-1 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed\">\n 「用户让我听发音。」<br />\n 「但我只是文字大脑,我听不到。」<br />\n 「等等 —— 开发者给了我一个叫 <code className=\"bg-muted px-1 rounded\">chivox_voice_eval</code> 的 MCP 工具!」\n </div>\n </FlowStep>\n <FlowStep title=\"4\">\n <p><strong className=\"text-foreground\">MCP 协议 · 自动调用</strong>(LLM → 驰声)</p>\n <p className=\"text-muted-foreground mt-1\">大模型自动按 MCP 标准构造 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools/call</code> 请求,把录音链接和参考文本发到驰声服务。<span className=\"text-foreground\">开发者不需要写任何调用代码。</span></p>\n </FlowStep>\n <FlowStep title=\"5\">\n <p><strong className=\"text-foreground\">驰声服务处理</strong>(评测引擎)</p>\n <p className=\"text-muted-foreground mt-1\">驰声对音频进行考试级打分,把结构化结果通过 MCP 原路返回:<code className=\"bg-muted px-1 rounded text-xs font-mono\">overall 85</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">/θ/ 音发音错误</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">流利度 88</code>。</p>\n </FlowStep>\n <FlowStep title=\"6\">\n <p><strong className=\"text-foreground\">大模型整合并输出反馈</strong>(LLM)</p>\n <p className=\"text-muted-foreground mt-1\">大模型把冷冰冰的分数翻译成温柔反馈:</p>\n <div className=\"text-foreground/80 mt-2 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed italic\">\n 「你的自我介绍很棒!综合 85 分 🎉 不过 <strong>think</strong> 这个词的 /θ/ 咬舌音不太标准,我们一起来练一下……」\n </div>\n </FlowStep>\n </div>\n <Callout type=\"tip\">\n <strong>整条链路里,MCP 只出现在 ① 和 ④</strong>:① 让开发者一次性告诉大模型「你多了一个工具」,④ 让大模型可以自动调用它。这两步的标准化,就是 MCP 的全部价值。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"dev-responsibility\" title=\"开发者只做两件事\">\n <p>听起来复杂,但对开发者来说只需要两件事:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">1</span>\n <span className=\"text-sm font-semibold\">填一次 MCP 配置</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 在 AI 后端的配置里填入驰声 MCP Server 地址和 API Key,就这一次,以后新加评测能力完全免开发。\n </p>\n <CodeBlock filename=\"mcp-config.json\" lang=\"json\">{`{\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"apiKey\": \"sk-••••••••••\"\n }\n}`}</CodeBlock>\n </div>\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">2</span>\n <span className=\"text-sm font-semibold\">写一段 System Prompt</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 告诉大模型这个工具是做什么的、什么时候调用、结果怎么转成用户友好的反馈。\n </p>\n <CodeBlock filename=\"system-prompt.md\">{`你是一位英语口语教练。\n当用户发来录音时:\n1. 调用 chivox_voice_eval 评分\n2. 聚焦 <70 分的音素家族\n3. 用高情商话术给出练习建议`}</CodeBlock>\n </div>\n </div>\n <p className=\"text-muted-foreground\">\n 其余所有事情 —— 音频上传、鉴权、工具发现、调用时机、返回格式、结果整合 —— 都由 MCP 协议和大模型自己搞定。\n </p>\n </SubDoc>\n\n <SubDoc id=\"integration-paths\" title=\"三种典型接入姿势\">\n <p>根据你的技术栈,有三种等价的接入方式,底层都走完全相同的 MCP 协议:</p>\n\n {/* ── 姿势一:可视化 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Sparkles className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势一 · 可视化拖拽(扣子 / Coze / 飞书智能助手)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合产品经理、运营、不写后端代码的开发者。整个过程像在手机上装 App。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>找到入口</strong>:在扣子平台的 <strong>「插件 / 工具」</strong> 配置区,点击<strong>「添加 MCP 服务」</strong>。</li>\n <li><strong>填两个字段</strong>(这也是你未来会从驰声这边拿到的):\n <div className=\"mt-2 overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium w-32\">服务器地址</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权密钥</td><td className=\"py-2 px-3 font-mono text-xs\">sk-•••••••••• <span className=\"text-muted-foreground ml-2\">(由驰声分配,用于计费与识别)</span></td></tr>\n </tbody>\n </table>\n </div>\n </li>\n <li><strong>平台自动发现能力</strong>:扣子会访问你填的地址,读取 MCP Server 自描述的\"说明书\"。瞬间明白:「这是个语音评测工具,需要传音频,会返发音分」。</li>\n <li><strong>右侧测试</strong>:在平台的聊天框里发一段语音,大模型就会自动调用工具。<strong>整个过程不需要写一行代码。</strong></li>\n </ol>\n </div>\n\n {/* ── 姿势二:代码框架 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Code2 className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势二 · 代码框架配置(Dify / LangChain / 自研后端)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合做独立 APP / 小程序、用 Dify 或 LangChain 写 Agent 的极客开发者。核心就是往配置文件里加一段 JSON。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>打开配置文件</strong>(项目根目录下的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp.json</code> 或框架指定的配置路径)。</li>\n <li><strong>在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code> 下增加一个节点</strong>:\n <CodeBlock filename=\"mcp.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"env\": {\n \"API_KEY\": \"你的专属密钥\"\n }\n }\n }\n}`}</CodeBlock>\n </li>\n <li><strong>重启后端服务</strong>。大模型的底层调度器会在启动时自动连上驰声 MCP Server,把 16 种评测工具注册成<strong>随时待命</strong>的 tools。</li>\n <li><strong>触发调用</strong>:用户发来录音后,大模型看到 system prompt 的引导,自动选择合适的工具(单词/句子/段落/半开放)调用。</li>\n </ol>\n </div>\n\n {/* ── 姿势三:Function Calling 直连(分 A / B 两种做法) ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Terminal className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势三 · 原生 Function Calling(豆包 / DeepSeek / OpenAI 兼容 API)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合不用任何 Agent 框架、直接调用 LLM API 的后端开发者。豆包 1.5+ / DeepSeek / GPT / Qwen 等模型都走标准 <code className=\"bg-muted px-1 rounded text-xs font-mono\">chat.completions</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> 参数。这里又分 A / B 两种做法,<strong>B 才是生产推荐</strong>。\n </p>\n\n <Callout type=\"warning\">\n 常见疑问:<em>\"要不要手写一个 tool 定义?\"</em><br />\n — <strong>姿势一 / 二不用</strong>(Coze、Dify、火山方舟控制台、Claude Desktop 会自动从 MCP Server 拉工具列表)。<br />\n — <strong>姿势三看你选 A 还是 B</strong>:A 要手写,B 用 MCP 客户端库自动拉。\n </Callout>\n\n {/* ── 3-A:手写 tool schema ── */}\n <div className=\"mt-4 rounded-md border border-border/40 bg-muted/20 p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold\">3-A · 手写 tool schema(最快验证,不推荐生产)</p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20\">快速 Demo</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 自己写一段 JSON Schema 告诉豆包\"有个工具叫 xxx,参数长这样\",豆包返回 tool_call 后你再手动去调驰声 MCP。<strong>缺点</strong>:驰声有 16 个评测工具,你每加一个题型都要改代码。\n </p>\n <CodeBlock filename=\"3a_hardcode_tool.py\" lang=\"python\">{`import os, json\nfrom openai import OpenAI\n\nclient = OpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv('ARK_API_KEY'),\n)\n\n# ─── 手写一段 tool schema(不推荐:16 个工具就要写 16 段)───\ntools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英语单词发音评测。当用户让你评价英语发音并提供录音链接时调用。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"audio_url\": {\"type\": \"string\", \"description\": \"录音 URL\"},\n \"reference_text\": {\"type\": \"string\", \"description\": \"参考文本,如 Apple\"},\n },\n \"required\": [\"audio_url\", \"reference_text\"]\n }\n }\n}]\n\nresp = client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,拿到评测结果后温柔地反馈,不要直接报分数。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n)\n\nmsg = resp.choices[0].message\nif msg.tool_calls:\n args = json.loads(msg.tool_calls[0].function.arguments)\n # ↓↓↓ 这里手动调驰声 MCP,再把结果回填给豆包做第二趟 ↓↓↓\n # result = requests.post(\"https://speech-eval.site/mcp\", json={...})`}</CodeBlock>\n </div>\n\n {/* ── 3-B:MCP 自动发现 ── */}\n <div className=\"mt-3 rounded-md border border-foreground/30 bg-foreground/[0.03] dark:bg-white/[0.03] p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold flex items-center gap-1.5\">\n 3-B · 用 MCP 客户端库自动发现 <Sparkles className=\"h-3 w-3 text-foreground\" />\n </p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-foreground text-background\">生产推荐</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 启动时连上驰声 MCP Server,调一次 <code className=\"bg-muted px-1 rounded\">tools/list</code>,把 16 个评测工具自动转成 Function Calling 的 tools 格式。<strong>驰声加新工具,你代码不用改</strong>。这才是 MCP 协议的真正价值。\n </p>\n <CodeBlock filename=\"3b_mcp_autodiscover.py\" lang=\"python\">{`import asyncio, os\nfrom openai import AsyncOpenAI\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\n\nclient = AsyncOpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv(\"ARK_API_KEY\"),\n)\n\nasync def main():\n # ─── 1. 连驰声 MCP Server,自动拉取全部 16 个工具 ───\n async with streamablehttp_client(\n \"https://speech-eval.site/mcp\",\n headers={\"Authorization\": f\"Bearer {os.getenv('CHIVOX_KEY')}\"},\n ) as (read, write, _):\n async with ClientSession(read, write) as session:\n await session.initialize()\n mcp_tools = (await session.list_tools()).tools # ← 一行拿到全部工具\n\n # ─── 2. MCP schema → OpenAI Function Calling schema ───\n tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema, # MCP 直接给 JSON Schema\n }\n } for t in mcp_tools]\n\n # ─── 3. 正常调豆包,tools 由 MCP 动态注入 ───\n resp = await client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,收到评测结果后温柔反馈。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n )\n\n # ─── 4. 折返跑:豆包选好工具后,直接走 MCP 调驰声 ───\n msg = resp.choices[0].message\n if msg.tool_calls:\n call = msg.tool_calls[0]\n result = await session.call_tool(\n call.function.name,\n arguments=__import__(\"json\").loads(call.function.arguments),\n )\n # 把 result 回填给豆包做第二趟 chat.completions 即可\n\nasyncio.run(main())`}</CodeBlock>\n <p className=\"text-[11px] text-muted-foreground mt-3 leading-relaxed\">\n 想要更省心?直接用 <a href=\"https://github.com/modelcontextprotocol/python-sdk\" target=\"_blank\" rel=\"noreferrer\" className=\"text-foreground underline underline-offset-2\">官方 mcp python sdk</a>、<code className=\"bg-muted px-1 rounded text-xs\">mcp-use</code>、<code className=\"bg-muted px-1 rounded text-xs\">openai-agents</code>,或火山官方 <code className=\"bg-muted px-1 rounded text-xs\">Arkitect</code> 高代码 SDK,它们都把上面这段\"MCP → Function Calling\"的桥接封装好了。\n </p>\n </div>\n </div>\n\n <Callout type=\"info\">\n 三种姿势走的是完全相同的 MCP 协议,能力完全等价。选型建议:<strong>产品经理 / 原型验证用姿势一</strong>(Coze 填表单);<strong>AI 客户端用户用姿势二</strong>(Cursor / Claude Desktop 填 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">mcp.json</code>);<strong>后端生产服务用姿势三-B</strong>(MCP 客户端库自动发现工具,不要手写 tool schema)。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 架构说明 ══════ */}\n <DocSection id=\"architecture\" icon={Terminal} title=\"架构说明\">\n\n <SubDoc id=\"arch-diagram\" title=\"系统架构\">\n <div className=\"flex flex-wrap items-center justify-center gap-4 my-6\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Claude Desktop<br/>Claude Code<br/>Cursor</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">stdio</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-1\">chivox-local-mcp</p>\n <p className=\"text-[11px] text-muted-foreground\">本地代理<br/>SoX 录音</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP / WS</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">Remote Chivox</p>\n <p className=\"text-[11px] text-muted-foreground\">MCP Server<br/>评测引擎</p>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"transport\" title=\"传输协议\">\n <ul className=\"space-y-3\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>stdio</strong> — AI 客户端通过标准输入输出与本地代理通信,这是 MCP 的标准传输方式</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>HTTP</strong> — 音频文件评测工具通过 HTTP 代理到远程服务,适用于非流式场景</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>WebSocket</strong> — 实时录音音频通过 WebSocket 流式推送,支持断线自动重连</span>\n </li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-local-mcp</strong> 是驰声语音评测的 MCP 本地代理,通过 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 将远程驰声评测服务桥接到本地,让 AI 助手(Claude Desktop、Claude Code、Cursor 等)直接调用语音评测能力。</p>\n <p>它支持两种评测模式,覆盖从即时交互到批量处理的全部场景:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Mic className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">实时录音评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">通过麦克风边录边评,音频以 WebSocket 流式推送,无需生成中间文件,延迟更低、体验更佳。</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <FolderOpen className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">音频文件评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">支持本地文件路径、Base64 编码、URL 三种输入,适合已有音频的批量评测场景。</p>\n </div>\n </div>\n <Callout type=\"info\">当前支持 <strong>16 种评测工具</strong>:英文 10 种(单词、句子、段落、纠音、自然拼读、口语选择、半开放题等)+ 中文 6 种(汉字、拼音、词句、段落、有限分支、AI Talk)。</Callout>\n </SubDoc>\n\n <SubDoc id=\"requirements\" title=\"系统要求\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">依赖</th>\n <th className=\"text-left py-2 px-3 font-medium\">版本</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-semibold\">Node.js</td><td className=\"py-2 px-3 font-mono text-xs\">{'>'}= 18</td><td className=\"py-2 px-3 text-muted-foreground\">运行时必须</td></tr>\n <tr><td className=\"py-2 px-3 font-semibold\">SoX</td><td className=\"py-2 px-3 font-mono text-xs\">any</td><td className=\"py-2 px-3 text-muted-foreground\">仅实时录音功能需要(处理麦克风音频流)</td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"mt-3\">安装 SoX(仅在使用实时录音时需要):</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# macOS\nbrew install sox\n\n# Ubuntu / Debian\nsudo apt-get install sox`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"install\" title=\"安装方式\">\n <p><strong>方式一:npm 全局安装</strong>(推荐)</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`npm install -g chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式二:npx 免安装运行</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`MCP_REMOTE_URL=http://your-server:8080 npx chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式三:从源码构建</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`git clone https://git.chivox.com/CLOUD_DEV/cvx_local_mcp.git\ncd cvx_local_mcp\nbash scripts/build.sh`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"env-vars\" title=\"环境变量\">\n <ParamTable params={[\n { name: 'MCP_REMOTE_URL', type: 'string', required: true, desc: '远程驰声 MCP 服务地址,如 http://your-server:8080' },\n { name: 'MCP_API_KEY', type: 'string', required: false, desc: 'API 认证密钥(如已配置鉴权则必填)' },\n ]} />\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 直接用(零代码) ══════ */}\n <DocSection id=\"config\" icon={Plug} title=\"接入 · 直接用 MCP(零代码)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>不用写代码、不用装 SDK,只在客户端的<strong className=\"text-foreground\">「MCP 配置」</strong>或<strong className=\"text-foreground\">「扩展」</strong>里填一段 JSON / URL</li>\n <li>保存后 LLM 自动发现 16 个评测工具,并在对话 / Agent 中自主调用</li>\n <li>覆盖 <strong className=\"text-foreground\">IDE 编程助手</strong>、<strong className=\"text-foreground\">桌面 AI 客户端</strong>、<strong className=\"text-foreground\">企业 AI 工作台</strong>、<strong className=\"text-foreground\">可视化 Bot / 工作流平台</strong>四大生态</li>\n </ul>\n </div>\n\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置并保存:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <Callout type=\"tip\">Cursor 使用远程 Streamable HTTP 模式直连,无需安装本地代理。如需实时录音功能,请使用下方 Claude Desktop 的本地代理配置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-desktop\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件(macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"chivox-local-mcp\",\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\",\n \"MCP_API_KEY\": \"your-api-key\"\n }\n }\n }\n}`}</CodeBlock>\n <p>如果是从源码构建:</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/cvx_local_mcp/dist/index.js\"],\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\"\n }\n }\n }\n}`}</CodeBlock>\n <Callout type=\"info\">Claude Desktop 使用本地代理模式(stdio),支持实时录音评测。本地代理会自动桥接远程服务。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-code\" title=\"Claude Code 配置\">\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# npm 全局安装后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 chivox-local-mcp\n\n# 源码构建后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 node /path/to/cvx_local_mcp/dist/index.js`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-ai-ide\" title=\"Windsurf / Zed / Continue 等 IDE 编程助手\">\n <p>除了 Cursor 和 Claude Code,主流的 AI 编程 IDE 都原生支持 MCP。以下客户端使用<strong>完全一致</strong>的 Streamable HTTP 配置,区别只在配置入口:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">客户端</th>\n <th className=\"text-left py-2 px-3 font-medium\">配置入口</th>\n <th className=\"text-left py-2 px-3 font-medium\">备注</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">Windsurf (Codeium)</td><td className=\"py-2 px-3 text-xs\">Settings → Cascade → MCP Servers</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">直接贴下方 JSON</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Zed</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">~/.config/zed/settings.json</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">context_servers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">Assistant Panel 自动发现</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Continue (VS Code / JetBrains)</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">config.yaml</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">v0.9+ 支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">通义灵码 / CodeGeeX / 文心快码</td><td className=\"py-2 px-3 text-xs\">插件设置 → MCP 服务</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">2026 Q1 起陆续支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Cline (原 Claude Dev)</td><td className=\"py-2 px-3 text-xs\">MCP Servers 面板 → 编辑配置</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">VS Code 扩展</td></tr>\n </tbody>\n </table>\n </div>\n <CodeBlock filename=\"通用 MCP 配置(复制即用)\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox-speech-eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n }\n }\n}`}</CodeBlock>\n <Callout type=\"tip\">所有 IDE 类客户端都走远程 Streamable HTTP,零本地依赖。需要实时录音才需切到 Claude Desktop 的本地代理模式。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-coze\" title=\"扣子(Coze)配置\">\n <p><a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子(Coze)</a>是字节跳动推出的 AI 应用开发平台,支持通过 MCP 扩展为 Agent 赋予外部工具能力。接入驰声语音评测后,你可以构建口语练习 Bot、发音诊断助手等应用。</p>\n <p className=\"font-semibold mt-4\">方式一:扣子空间 MCP 扩展(推荐)</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 <a href=\"https://space.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子空间</a>(space.coze.cn)</li>\n <li>在对话框下方点击 <strong>「添加扩展」</strong></li>\n <li>选择 <strong>「自定义」</strong> 标签,点击添加新的 MCP 服务</li>\n <li>填入以下配置信息:</li>\n </ol>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">名称</td><td className=\"py-2 px-3 font-mono text-xs\">chivox-speech-eval</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>保存后,在对话中输入 <strong>「帮我评测这段英文发音」</strong> 即可触发 Agent 调用</li>\n </ol>\n <Callout type=\"tip\">添加 MCP 扩展后,扣子空间的 Agent 会自动识别并调用评测工具。你可以在 Agent 的思考过程中看到具体的工具调用细节。</Callout>\n\n <p className=\"font-semibold mt-6\">方式二:在 Coze 开发平台的 Bot 中使用</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>进入 <a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Coze 开发平台</a>,创建或编辑 Bot</li>\n <li>在 Bot 的 <strong>「插件」</strong> 或 <strong>「工具」</strong> 面板中,添加 MCP 工具</li>\n <li>选择 <strong>Streamable HTTP</strong> 类型,填入 URL:<code className=\"bg-muted px-1 rounded text-xs font-mono\">https://speech-eval.site/mcp</code></li>\n <li>Bot 将自动发现所有可用的评测工具(如 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_word</code> 等)</li>\n <li>在 Bot 的人设提示词中,添加语音评测相关的引导语</li>\n </ol>\n <CodeBlock filename=\"Bot 人设提示词示例\">{`你是一位专业的英语口语教练。当用户上传音频并请求评测时:\n1. 使用 evaluate_english_word / evaluate_english_sentence 等工具进行评测\n2. 根据评测结果(overall、accuracy、fluency 等分数)给出专业分析\n3. 针对得分较低的维度提供具体改进建议\n4. 鼓励用户持续练习`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze-workflow\" title=\"Coze 工作流接入\">\n <p>如果你需要在 Coze 工作流中集成语音评测能力,可以通过 <strong>HTTP Request 节点</strong>调用评测 API:</p>\n\n <p className=\"font-semibold mt-4\">步骤 1:上传音频</p>\n <p>在工作流中添加一个 HTTP Request 节点,配置如下:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">方法</td><td className=\"py-2 px-3 font-mono text-xs\">POST</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/upload</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Content-Type</td><td className=\"py-2 px-3 font-mono text-xs\">audio/mp3</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Body</td><td className=\"py-2 px-3 text-muted-foreground\">音频文件二进制数据</td></tr>\n </tbody>\n </table>\n </div>\n <p>响应中获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{{audioId}}'}</code>,传递给下一步。</p>\n\n <p className=\"font-semibold mt-4\">步骤 2:调用评测</p>\n <p>添加第二个 HTTP Request 节点,通过 MCP 接口调用评测工具:</p>\n <CodeBlock filename=\"HTTP Request Body\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"evaluate_english_sentence\",\n \"arguments\": {\n \"audioId\": \"{{audioId}}\",\n \"sentence\": \"{{ref_text}}\"\n }\n },\n \"id\": 1\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">步骤 3:处理结果</p>\n <p>评测结果通过 JSON 格式返回,可使用 Coze 的 <strong>代码节点</strong> 或 <strong>LLM 节点</strong> 对结果进行解析和二次处理(如生成学习建议、计算进步趋势等)。</p>\n\n <Callout type=\"info\">工作流方式适合需要固定流程的场景(如批量评测、定时任务、与其他系统联动)。如果是交互式场景,推荐使用上方的 MCP 扩展方式,Agent 会自动选择合适的工具。</Callout>\n\n <div className=\"rounded-lg bg-muted/30 p-5 mt-4\">\n <p className=\"text-xs font-semibold mb-3\">完整工作流示意</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">用户输入音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 上传音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 调用评测</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM: 分析结果</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">输出反馈</span>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"config-ai-workspace\" title=\"AI 工作台 · 豆包 / 飞书智能伙伴 / WorkBuddy / 钉钉 AI\">\n <p>这一类是<strong>面向「上班族」的企业 / 日常 AI 助手</strong>,产品形态是个聊天框 + 插件市场 / 扩展面板。接入后,员工在 App 里打字「帮我评一下这段录音」就能自动触发驰声评测,不用写任何代码。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Bot className=\"h-4 w-4 text-amber-500\" /> 豆包(Doubao)App / 桌面版</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节跳动官方 AI 助手,2026 年起桌面版支持 MCP 扩展。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>桌面版设置 → <strong>「智能体扩展」</strong> → 添加 MCP 服务</li>\n <li>类型选 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">streamable-http</code>,URL 填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>对话中直接说「帮我评测」即可自动调用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Building2 className=\"h-4 w-4 text-blue-500\" /> 飞书智能伙伴(My AI)</p>\n <p className=\"text-xs text-muted-foreground mb-2\">飞书企业 AI,支持以 MCP / Function Calling 挂载外部工具给团队 Bot。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>飞书开发者后台 → 创建应用 → <strong>「AI 能力 / 智能伙伴」</strong></li>\n <li>在「工具」中选 <strong>外部 MCP 服务</strong>,填入上方 URL</li>\n <li>把智能伙伴拉进群聊,@ 它提交音频即可评测</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-rose-500\" /> WorkBuddy / 企业智能助手</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节 WorkBuddy、腾讯会议 AI、企业微信智能助手等企业 AI 工作台场景。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>在工作台的<strong>「技能 / 扩展」</strong>后台新增一个 MCP 连接器</li>\n <li>传输协议选 Streamable HTTP,地址填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>把技能发布到指定部门 / 群组,HR、培训、销售口语陪练场景即可直接用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Workflow className=\"h-4 w-4 text-cyan-500\" /> 钉钉 AI 助理 / 通义千问 App</p>\n <p className=\"text-xs text-muted-foreground mb-2\">阿里生态:钉钉 AI 助理支持自定义插件,通义 App 可通过「AI 工具箱」挂载 MCP。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>钉钉开发者后台 → AI 助理 → <strong>「插件开发 / MCP 接入」</strong></li>\n <li>选 HTTP Streamable,填入驰声 MCP URL 并开启鉴权(可选)</li>\n <li>在钉钉群 @AI 助理上传音频,自动返回评测卡片</li>\n </ol>\n </div>\n </div>\n\n <Callout type=\"tip\"><strong>典型场景 · 小龙虾们的共同剧本</strong>:销售口语陪练 → 外语培训 → 主持 / 播音岗位考核 → K12 英语作业批改 → 客服话术复盘。这些「上班族 AI 工作台」接入驰声 MCP 后,员工 / 学员只要对着话筒说话,AI 就能自动打分并给出个性化改进建议,运营 / 培训 / 教研团队零开发即可上线。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-workflow-platforms\" title=\"Dify / n8n / Flowise 等可视化编排\">\n <p>如果你需要把语音评测编排进<strong>多步业务流</strong>(例如:学员录音 → 评测 → 生成错题集 → 推送到 CRM),可视化工作流平台比聊天 Bot 更合适。</p>\n\n <div className=\"grid gap-3 md:grid-cols-3 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Dify</p>\n <p className=\"text-xs text-muted-foreground mb-2\">开源 LLMOps,v0.15+ 原生 MCP 工具。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>「工具」→「自定义 MCP」→ 填 URL</li>\n <li>在 Chatflow / Workflow 节点里直接拖用</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">n8n</p>\n <p className=\"text-xs text-muted-foreground mb-2\">工作流自动化,用 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">MCP Client</code> 节点。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>添加 <strong>MCP Client</strong> 节点</li>\n <li>Transport 选 HTTP Stream,URL 贴驰声地址</li>\n <li>Tool 下拉自动列出 16 个评测能力</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Flowise / LangFlow</p>\n <p className=\"text-xs text-muted-foreground mb-2\">可视化 LangChain Agent 编排。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>拖出 <strong>MCP Tool</strong> 节点</li>\n <li>连到 AgentExecutor 即可</li>\n </ul>\n </div>\n </div>\n\n <Callout type=\"info\">可视化平台的好处是<strong>流程可追溯、可回放</strong>,适合教育机构 / 培训部门做批量评测 + 报表导出;缺点是单步延迟略高于直接 MCP 调用,对延迟敏感的交互场景建议用 Cursor / Claude Desktop 直连。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"其他 MCP 客户端(通用配置)\">\n <p>任何遵循 MCP 协议的客户端都可以接入,统一使用以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权(可选)</td><td className=\"py-2 px-3 font-mono text-xs\">Header:<code>Authorization: Bearer &lt;MCP_API_KEY&gt;</code></td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">还没找到你用的客户端?任何支持 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 的终端都通用。找不到入口?下一小节「编程用 MCP」里用 SDK 直接接即可。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 编程用 MCP ══════ */}\n <DocSection id=\"config-code\" icon={Code2} title=\"接入 · 编程用 MCP(SDK / 代码集成)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Terminal className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>你在写一段<strong className=\"text-foreground\">真正的后端服务</strong>:一个 Agent Worker、一个 API、一个自动化脚本</li>\n <li>业务里的「语音评测」是其中一个步骤,需要由代码 <strong className=\"text-foreground\">决定何时调用</strong>、<strong className=\"text-foreground\">如何解析结果</strong></li>\n <li>三种主流写法:① 官方 MCP 客户端库 → ② Agent 框架自动桥接 → ③ 退化到 chat.completions + tools 参数</li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 强烈推荐①或②:驰声有 16 个评测工具,<strong className=\"text-foreground\">让 MCP 自动注入 tool schema</strong>,每新增一个题型不用改一行业务代码。</p>\n </div>\n\n <SubDoc id=\"code-mcp-sdk\" title=\"① 官方 MCP 客户端库(Python / TypeScript)\">\n <p>最底层、也最推荐的方式。由 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Anthropic 官方维护</a>,一次连接自动拿到全部工具的 JSON Schema,然后交给任意 LLM。</p>\n\n <p className=\"font-semibold mt-4\">Python:<code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp</code></p>\n <CodeBlock filename=\"chivox_client.py\" lang=\"python\">{`# pip install mcp openai\nimport asyncio, os\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nMCP_URL = \"https://speech-eval.site/mcp\"\nllm = OpenAI(api_key=os.getenv(\"DOUBAO_KEY\"),\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\") # 豆包 / DeepSeek / OpenAI 同构\n\nasync def main(audio_id: str, ref: str):\n async with streamablehttp_client(MCP_URL) as (reader, writer, _):\n async with ClientSession(reader, writer) as sess:\n await sess.initialize()\n\n tools = await sess.list_tools()\n openai_tools = [{\n \"type\": \"function\",\n \"function\": {\"name\": t.name, \"description\": t.description,\n \"parameters\": t.inputSchema},\n } for t in tools.tools]\n\n resp = llm.chat.completions.create(\n model=\"doubao-1-5-pro-32k\",\n messages=[{\"role\": \"user\",\n \"content\": f\"帮我评测 audioId={audio_id},参考文本:{ref}\"}],\n tools=openai_tools,\n )\n\n for call in resp.choices[0].message.tool_calls or []:\n result = await sess.call_tool(call.function.name,\n arguments=eval(call.function.arguments))\n print(call.function.name, \"→\", result.content[0].text[:200])\n\nasyncio.run(main(\"abc123\", \"I think therefore I am\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">TypeScript / Node.js:<code className=\"bg-muted px-1 rounded text-xs font-mono\">@modelcontextprotocol/sdk</code></p>\n <CodeBlock filename=\"chivox-client.ts\" lang=\"typescript\">{`// npm i @modelcontextprotocol/sdk openai\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport OpenAI from \"openai\";\n\nconst mcp = new Client({ name: \"chivox-biz\", version: \"1.0.0\" });\nawait mcp.connect(new StreamableHTTPClientTransport(new URL(\"https://speech-eval.site/mcp\")));\n\nconst { tools } = await mcp.listTools();\nconst openai = new OpenAI({ apiKey: process.env.DEEPSEEK_KEY, baseURL: \"https://api.deepseek.com/v1\" });\n\nconst resp = await openai.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: \"评测 audioId=abc123, 参考 I think therefore I am\" }],\n tools: tools.map(t => ({ type: \"function\", function: {\n name: t.name, description: t.description, parameters: t.inputSchema\n }})),\n});\n\nfor (const call of resp.choices[0].message.tool_calls ?? []) {\n const r = await mcp.callTool({ name: call.function.name,\n arguments: JSON.parse(call.function.arguments) });\n console.log(call.function.name, r.content);\n}`}</CodeBlock>\n <Callout type=\"tip\">这种写法 <strong>LLM 供应商可换</strong>(豆包 / DeepSeek / GPT / Qwen / Moonshot),<strong>工具自动更新</strong>(驰声新增题型无需改业务代码),适合生产后端。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-agent-frameworks\" title=\"② 用 Agent 框架自动桥接(LangChain / Mastra / Agents SDK)\">\n <p>如果项目已经在用 Agent 框架,几行 adapter 就能把驰声 MCP 塞进去,框架本身会接管 tool-calling 循环、重试、日志。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LangChain / LangGraph(Python)</p>\n <CodeBlock filename=\"agent.py\" lang=\"python\">{`# pip install langchain-mcp-adapters langgraph\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom langgraph.prebuilt import create_react_agent\n\nclient = MultiServerMCPClient({\n \"chivox\": {\n \"transport\": \"streamable_http\",\n \"url\": \"https://speech-eval.site/mcp\",\n }\n})\ntools = await client.get_tools()\n\nagent = create_react_agent(\"openai:gpt-4o-mini\", tools)\nresult = await agent.ainvoke({\"messages\":\n [(\"user\", \"评测 audioId=abc123 参考 think\")]})\nprint(result[\"messages\"][-1].content)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Mastra(TypeScript)</p>\n <CodeBlock filename=\"mastra-agent.ts\" lang=\"typescript\">{`// npm i @mastra/core @mastra/mcp\nimport { MCPClient } from \"@mastra/mcp\";\nimport { Agent } from \"@mastra/core/agent\";\nimport { openai } from \"@ai-sdk/openai\";\n\nconst mcp = new MCPClient({ servers: {\n chivox: { url: new URL(\"https://speech-eval.site/mcp\") }\n}});\n\nexport const speechCoach = new Agent({\n name: \"speech-coach\",\n instructions: \"你是口语教练,用 chivox 工具评测并给改进建议。\",\n model: openai(\"gpt-4o-mini\"),\n tools: await mcp.getTools(),\n});`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">OpenAI Agents SDK(Python)</p>\n <CodeBlock filename=\"agents_sdk.py\" lang=\"python\">{`# pip install openai-agents\nfrom agents import Agent, Runner\nfrom agents.mcp import MCPServerStreamableHttp\n\nchivox = MCPServerStreamableHttp(\n params={\"url\": \"https://speech-eval.site/mcp\"},\n name=\"chivox-speech-eval\",\n)\n\nasync with chivox:\n agent = Agent(name=\"coach\",\n instructions=\"专业口语教练\",\n mcp_servers=[chivox])\n r = await Runner.run(agent, \"评测 audioId=abc123\")\n print(r.final_output)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LlamaIndex / AutoGen / CrewAI</p>\n <p className=\"text-xs text-muted-foreground mb-2\">这些框架都有官方或社区 MCP adapter,用法与上述类似:</p>\n <ul className=\"list-disc list-inside space-y-1 text-xs\">\n <li><strong>LlamaIndex</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">llama-index-tools-mcp</code></li>\n <li><strong>AutoGen</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">autogen-ext[mcp]</code></li>\n <li><strong>CrewAI</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">crewai-tools</code> 的 MCPServerAdapter</li>\n <li><strong>Spring AI</strong>(Java):<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">spring-ai-mcp-client</code></li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"code-function-calling\" title=\"③ 直调 chat.completions · 上传与动态 tools(豆包 / DeepSeek / 等)\">\n <p>\n DeepSeek、GLM(智谱)、KIMI(Moonshot)、豆包(火山方舟)、千帆、DashScope 等若提供 <strong>OpenAI 兼容的 HTTP API</strong>,接入驰声 MCP 的套路一致:用 MCP 客户端拉取工具列表 → 转为{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> → 模型决策 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_calls</code> → MCP 执行并回填 → 再让模型生成诊断话术。\n </p>\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">① MCP list_tools()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">② 转 OpenAI tools 格式</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">③ LLM 决策 tool_calls</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">④ MCP call_tool()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">⑤ 评测结果回填 → LLM 诊断</span>\n </div>\n </div>\n <Callout type=\"tip\">各厂商仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 与 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 与 tools 桥接代码可原样复用。</Callout>\n\n <p className=\"font-semibold mt-6\">先上传音频,拿到 audioId</p>\n <p className=\"text-sm text-muted-foreground mb-3\">文件评测工具通常需要 <code className=\"bg-muted px-1 rounded text-xs font-mono\">audioId</code>(或路径参数,视工具而定)。下面是用 HTTP 上传的最小示例:</p>\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://speech-eval.site/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst res = await fetch('https://speech-eval.site/upload', {\n method: 'POST',\n headers: { 'Content-Type': 'audio/mp3' },\n body: audioData,\n});\n\nconst result = await res.json();\nconsole.log('audioId:', result.audioId);`}</CodeBlock>\n\n <p className=\"font-semibold mt-6\">再以豆包为例:MCP 动态 tools + chat.completions 折返跑</p>\n <p>当你用的 LLM SDK <strong>还没有原生 MCP 支持</strong>(比如老版本的火山方舟 / 百度千帆 / 通义 DashScope),退化方案是:用 MCP 客户端<strong>一次性拉到 tool schema</strong>,然后按 OpenAI Function Calling 格式喂给 LLM。</p>\n <CodeBlock filename=\"doubao_tools.py\" lang=\"python\">{`# 适用:豆包(火山方舟)/ DeepSeek / Qwen / Moonshot / GLM 等 OpenAI 兼容 API\n# 核心思路:MCP 列工具 → 转 OpenAI tools → LLM 决策 → MCP 执行 → 回填\n\nasync with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n\n # ① 用 MCP 拉到的 schema 自动转 OpenAI 格式\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 喂给豆包\n messages = [{\"role\": \"user\", \"content\": \"评测一下 audioId=abc123\"}]\n resp = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages, tools=oa_tools,\n )\n\n # ③ LLM 回 tool_calls → 折返跑到 MCP 执行\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for c in msg.tool_calls:\n result = await mcp.call_tool(\n c.function.name, arguments=json.loads(c.function.arguments))\n messages.append({\"role\": \"tool\", \"tool_call_id\": c.id,\n \"content\": result.content[0].text})\n # ④ 再喂一轮,让豆包根据评测结果产出自然语言诊断\n final = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages)\n print(final.choices[0].message.content)`}</CodeBlock>\n <Callout type=\"warning\"><strong>千万别手写 tool schema!</strong>驰声共 <strong>16 个评测工具</strong>(英文 10 + 中文 6),每个工具参数较多,手写既容易错也难维护。一定用 MCP <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">list_tools()</code> 动态拿。</Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-deepseek\" title=\"DeepSeek\">\n <p>\n DeepSeek 提供与 OpenAI 完全兼容的 API,当前最新模型为 <strong>DeepSeek-V3.2</strong>(2025 年 12 月发布,首个把\"思考 + 工具调用\"原生融合的模型)。\n 通过 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-chat</code> 进入<strong>非思考模式</strong>,\n 通过 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-reasoner</code> 进入<strong>思考模式</strong>,\n <strong>两种模式均支持 Function Calling</strong>,128K 上下文。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"deepseek_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# DeepSeek 客户端(OpenAI 兼容)\nclient = OpenAI(\n api_key=os.environ[\"DEEPSEEK_API_KEY\"],\n base_url=\"https://api.deepseek.com/v1\",\n)\n\nasync def evaluate_with_deepseek(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 拉取驰声 MCP 工具列表\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 调用 DeepSeek,让它选择合适的评测工具\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"deepseek-chat\", # 或 deepseek-reasoner\n messages=messages,\n tools=oa_tools,\n )\n\n # ③ 执行 tool_calls\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 让 DeepSeek 根据评测结果生成自然语言诊断\n final = client.chat.completions.create(\n model=\"deepseek-chat\",\n messages=messages,\n )\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_deepseek(\"audio_abc123\", \"Hello, nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议(V3.2):</strong>日常评测诊断用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">deepseek-chat</code>,速度快、token 消耗低;\n 需要深度分析(错误归因 / 个性化练习路径)时用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">deepseek-reasoner</code>,\n V3.2 起<strong>思考模式也能在思维链中调用工具</strong>,可以一次性完成\"调评测 → 思考 → 出诊断\",无需再切回 V3 做工具调用。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-glm\" title=\"GLM(智谱 AI / Z.ai)\">\n <p>\n 智谱 AI 当前最新旗舰为 <strong>GLM-5 / GLM-5.1</strong>(2026 年 2 月发布,744B-A40B MoE,200K 上下文,对标 Claude Opus 4.5),\n 以及上一代 <strong>GLM-4.7</strong>、<strong>GLM-4.6</strong>。\n 此外保留 <code className=\"bg-muted px-1 rounded text-xs font-mono\">glm-4-flash</code>(免费快速版)方便开发测试。\n 全部模型支持 OpenAI 兼容接口与原生 <code className=\"bg-muted px-1 rounded text-xs font-mono\">zhipuai</code> SDK,原生 Function Calling。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`ZHIPU_API_KEY=xxxxxxxx.xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式一:OpenAI 兼容接口(推荐)</p>\n <CodeBlock filename=\"glm_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(\n api_key=os.environ[\"ZHIPU_API_KEY\"],\n base_url=\"https://open.bigmodel.cn/api/paas/v4/\",\n)\n\nasync def evaluate_with_glm(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"glm-4-flash\", # 免费快速版;生产推荐 glm-5 / glm-4.7\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"glm-4-flash\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_glm(\"audio_abc123\", \"How are you today?\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式二:官方 zhipuai SDK</p>\n <CodeBlock filename=\"glm_sdk.py\" lang=\"python\">{`from zhipuai import ZhipuAI\n\nclient = ZhipuAI(api_key=os.environ[\"ZHIPU_API_KEY\"])\n\n# 其余逻辑与方式一相同,仅替换 client 实例\nresp = client.chat.completions.create(\n model=\"glm-5\", # 旗舰;也可用 glm-4.7 / glm-4-flash\n messages=messages,\n tools=oa_tools,\n)`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 开发联调用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-4-flash</code>(免费);\n 在线辅导日常诊断用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-4.7</code> 性价比最高;\n K12 / 复杂 Agent 编排(多步评测 + 个性化练习路径)用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-5</code> / <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-5.1</code>,\n 200K 上下文、Agent 能力对标 Claude Opus 系列。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-kimi\" title=\"KIMI(Moonshot AI)\">\n <p>\n Moonshot AI 当前最新旗舰是 <strong>Kimi K2.5</strong>(2026 年 1 月 27 日发布,MoE 架构、<strong>256K 上下文</strong>、原生多模态、Agent 集群协作)。\n 另外提供 <code className=\"bg-muted px-1 rounded text-xs font-mono\">kimi-k2-thinking</code>(深度思考)、<code className=\"bg-muted px-1 rounded text-xs font-mono\">kimi-k2-turbo-preview</code>(高速版)。\n 上一代 <code className=\"bg-muted px-1 rounded text-xs font-mono\">moonshot-v1-8k/32k/128k</code> 仍然兼容,但<strong>推荐迁移到 kimi-k2.5</strong>。\n 全部模型 OpenAI 兼容,原生 Function Calling。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`MOONSHOT_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"kimi_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# 中国大陆用 api.moonshot.cn;海外/国际版用 api.moonshot.ai\nclient = OpenAI(\n api_key=os.environ[\"MOONSHOT_API_KEY\"],\n base_url=\"https://api.moonshot.cn/v1\",\n)\n\nasync def evaluate_with_kimi(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"kimi-k2.5\", # 旗舰;也可选 kimi-k2-turbo-preview / moonshot-v1-128k\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"kimi-k2.5\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_kimi(\"audio_abc123\", \"Nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\">\n Kimi K2.5 原生 <strong>256K 上下文 + Agent 集群协作</strong>,特别适合「多轮口语练习 + 历史分析 + 多步评测编排」场景:\n 把用户过去 30 次评测的完整 JSON 全部塞进 messages,让 K2.5 一次性生成跨多次练习的进步报告与个性化练习路径。\n 需要更深推理时使用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">kimi-k2-thinking</code>,\n 追求实时反馈用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">kimi-k2-turbo-preview</code>。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-doubao\" title=\"豆包 Seed 2.0(火山方舟)\">\n <p>\n 字节跳动 <strong>豆包 Seed 2.0</strong>(2026 年 2 月发布)是 ByteDance Seed 团队推出的新一代通用大模型,覆盖 Pro / Lite / Mini / Code 多种规格,\n 通过 <strong>火山方舟(Volcano Ark)</strong> 提供 OpenAI 兼容的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">/api/v3/chat/completions</code> 接口,\n 原生支持 <strong>Function Calling、JSON Schema 结构化输出、深度思考(Chain-of-Thought)模式</strong>,\n 接驰声 MCP 几乎无须改动业务代码。\n </p>\n\n <Callout type=\"tip\">\n 豆包 Seed 2.0 的优势在 <strong>深度思考 + 工具编排</strong>:让模型一次性规划\"先调 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code>,看完弱项再调 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.word.score</code> 进一步定位音素\",\n 适合做\"多步评测 + 自动纠音\"这类 Agent 场景。\n 默认开启思考模式,可通过 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'thinking: { type: \"disabled\" }'}</code> 关闭以节省时延。\n </Callout>\n\n <p className=\"font-semibold mt-4 mb-2\">推荐版本</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">model</th>\n <th className=\"text-left py-2 px-3 font-medium\">规格</th>\n <th className=\"text-left py-2 px-3 font-medium\">推荐用途</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-pro-260215</td><td className=\"py-2 px-3\">Pro · 旗舰</td><td className=\"py-2 px-3\">多步评测 + 深度诊断 + 复杂 Agent 编排</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-lite-260215</td><td className=\"py-2 px-3\">Lite · 平衡</td><td className=\"py-2 px-3\">在线辅导单轮评测 + 自然语言反馈,性价比首选</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-mini-260215</td><td className=\"py-2 px-3\">Mini · 极小</td><td className=\"py-2 px-3\">高并发 / 边缘部署 / 移动端实时反馈</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-code-preview-260215</td><td className=\"py-2 px-3\">Code · 预览</td><td className=\"py-2 px-3\">代码生成 / 工具编排 DSL,对评测场景非主推</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-1-8-251228</td><td className=\"py-2 px-3\">1.8 · 深度思考</td><td className=\"py-2 px-3\">支持 reasoning_effort 调档(minimal/low/medium/high),上一代深度思考强化版仍可用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-1-6-251015</td><td className=\"py-2 px-3\">1.6 · 经典</td><td className=\"py-2 px-3\">256K 上下文经典版,已上线项目可继续沿用</td></tr>\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`# 火山方舟控制台 → API Key 管理\nARK_API_KEY=ak-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · 动态 tools + 折返跑)</p>\n <CodeBlock filename=\"doubao_seed20_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# 火山方舟 OpenAI 兼容端点\nclient = OpenAI(\n api_key=os.environ[\"ARK_API_KEY\"],\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n)\n\nMODEL = \"doubao-seed-2-0-pro-260215\" # 也可换成 lite / mini\n\nasync def evaluate_with_doubao(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 把驰声 16 个评测工具的 schema 动态注入豆包\n tools = (await mcp.list_tools()).tools\n ark_tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n },\n } for t in tools]\n\n messages = [\n {\"role\": \"system\",\n \"content\": \"你是一位专业的英语口语教练,会主动调用驰声评测工具获取真实分数。\"},\n {\"role\": \"user\",\n \"content\": f\"请评测这段录音,audio_id={audio_id},参考文本:{ref_text}。\"\n f\"先做句子级评测,弱项再追加单词级定位。\"},\n ]\n\n # ② 第一轮 —— 让豆包根据 prompt 自主选工具\n resp = client.chat.completions.create(\n model=MODEL,\n messages=messages,\n tools=ark_tools,\n tool_choice=\"auto\",\n # extra_body={\"thinking\": {\"type\": \"disabled\"}}, # 需要时可关思考\n )\n\n msg = resp.choices[0].message\n messages.append(msg)\n\n # ③ 折返跑:豆包可能一次发起多个 tool_calls,全部回填\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 第二轮 —— 让豆包基于真实评测 JSON 输出诊断 / 练习建议\n final = client.chat.completions.create(\n model=MODEL,\n messages=messages,\n )\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_doubao(\n audio_id=\"audio_abc123\",\n ref_text=\"The quick brown fox jumps over the lazy dog.\",\n)))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">深度思考模式(可选)</p>\n <p className=\"text-sm text-muted-foreground\">\n 豆包 Seed 2.0 默认开启 <strong>thinking</strong> 模式,会在内部生成思维链再产出答案,\n 对\"多步评测编排 / 复杂诊断\"质量明显更好;但会增加 30%~80% 的端到端延迟。\n 在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">extra_body</code> 里可显式控制:\n </p>\n <CodeBlock filename=\"thinking_mode.py\" lang=\"python\">{`# 关闭思考(更快、适合实时反馈)\nclient.chat.completions.create(\n model=\"doubao-seed-2-0-lite-260215\",\n messages=messages,\n tools=ark_tools,\n extra_body={\"thinking\": {\"type\": \"disabled\"}},\n)\n\n# 开启思考(默认,适合 Pro 模型 + 多步 Agent)\nclient.chat.completions.create(\n model=\"doubao-seed-2-0-pro-260215\",\n messages=messages,\n tools=ark_tools,\n extra_body={\"thinking\": {\"type\": \"enabled\"}},\n)`}</CodeBlock>\n\n <Callout type=\"info\">\n <strong>选型建议:</strong>\n 课堂 / 培训类一对一辅导用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-lite</code> + 关思考;\n K12 / 考试场景需要\"多题型联动 + 升级建议\"时用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-pro</code> + 开思考;\n 移动端实时口语对话用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-mini</code> 控制成本与时延。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-openai\" title=\"OpenAI(GPT-5.1)\">\n <p>\n OpenAI 当前旗舰为 <strong>GPT-5.1</strong>(2025-11-13 发布),<strong>400K 上下文</strong>、128K 最大输出,\n 支持可调推理强度 <code className=\"bg-muted px-1 rounded text-xs font-mono\">reasoning_effort</code>(none / low / medium / high)。\n 另有 <code className=\"bg-muted px-1 rounded text-xs font-mono\">gpt-5.1-chat-latest</code>(ChatGPT 同款,128K 上下文,对话场景更优)和 <code className=\"bg-muted px-1 rounded text-xs font-mono\">gpt-5-mini</code>(性价比版)。\n 全部模型原生支持 Function Calling 与 Structured Outputs。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · GPT-5.1 + 驰声 MCP)</p>\n <CodeBlock filename=\"openai_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(api_key=os.environ[\"OPENAI_API_KEY\"])\n\nasync def evaluate_with_gpt(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [\n {\"role\": \"system\",\n \"content\": \"你是英语口语教练,必须调用驰声评测工具拿真实分数后再回答。\"},\n {\"role\": \"user\",\n \"content\": f\"请评测:audioId={audio_id},参考文本:{ref_text}\"},\n ]\n\n resp = client.chat.completions.create(\n model=\"gpt-5.1\",\n messages=messages,\n tools=oa_tools,\n tool_choice=\"auto\",\n reasoning_effort=\"medium\", # GPT-5.1 新参数:none / low / medium / high\n )\n\n msg = resp.choices[0].message\n messages.append(msg)\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n final = client.chat.completions.create(\n model=\"gpt-5.1\", messages=messages)\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_gpt(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 评测调用 + 诊断默认用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5.1</code>(reasoning_effort=&quot;medium&quot;,质量与延迟平衡);\n 纯对话/课堂讲解用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5.1-chat-latest</code>;\n 高并发批量评测降本用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5-mini</code>。\n 深度推理 / 多步 Agent 编排把 reasoning_effort 调到 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">high</code>。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-claude\" title=\"Claude(Anthropic)\">\n <p>\n Anthropic 当前最新为 <strong>Claude Opus 4.7</strong>(旗舰)、<strong>Claude Sonnet 4.6</strong>(平衡)、<strong>Claude Haiku 4.5</strong>(高速)。\n Claude 用 <strong>Messages API</strong>(与 OpenAI Chat Completions 不同),工具协议是 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_use</code> / <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_result</code> 块结构。\n 支持文本 + 视觉输入,工具 schema 用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">input_schema</code> 字段(与 OpenAI 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">parameters</code> 等价)。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · 原生 Anthropic SDK + MCP 工具桥接)</p>\n <CodeBlock filename=\"claude_chivox.py\" lang=\"python\">{`# pip install anthropic mcp\nimport os, json, asyncio\nfrom anthropic import Anthropic\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = Anthropic(api_key=os.environ[\"ANTHROPIC_API_KEY\"])\n\nasync def evaluate_with_claude(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # 把驰声 MCP 工具转成 Anthropic 风格(用 input_schema 而非 parameters)\n tools = (await mcp.list_tools()).tools\n anth_tools = [{\n \"name\": t.name,\n \"description\": t.description,\n \"input_schema\": t.inputSchema,\n } for t in tools]\n\n messages = [{\"role\": \"user\",\n \"content\": f\"请评测这段录音 audioId={audio_id},参考文本:{ref_text}\"}]\n\n # 工具循环:Claude 可能多轮 tool_use,需逐轮回填\n while True:\n resp = client.messages.create(\n model=\"claude-opus-4-7\", # 或 claude-sonnet-4-6 / claude-haiku-4-5\n max_tokens=2048,\n tools=anth_tools,\n messages=messages,\n system=\"你是专业英语口语教练,必须调用驰声评测工具获取真实分数。\",\n )\n\n # 把整段助手回复(含 tool_use 块)追加进 messages\n messages.append({\"role\": \"assistant\", \"content\": resp.content})\n\n if resp.stop_reason != \"tool_use\":\n # 没有更多工具调用,输出最终诊断\n return \"\".join(b.text for b in resp.content if b.type == \"text\")\n\n # 提取所有 tool_use 块、并行执行、把 tool_result 回填给 Claude\n tool_results = []\n for block in resp.content:\n if block.type == \"tool_use\":\n result = await mcp.call_tool(block.name, arguments=block.input)\n tool_results.append({\n \"type\": \"tool_result\",\n \"tool_use_id\": block.id,\n \"content\": result.content[0].text,\n })\n messages.append({\"role\": \"user\", \"content\": tool_results})\n\nprint(asyncio.run(evaluate_with_claude(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 深度评测诊断 + 复杂 Agent 编排用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-opus-4-7</code>(旗舰,编程/Agent 提升明显);\n 日常评测 + 性价比首选 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-sonnet-4-6</code>;\n 高并发实时反馈用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-haiku-4-5</code>。\n Opus 4.5 起新增 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">effort</code> 参数(low/medium/high),medium 即可达到 Sonnet 4.5 同等质量但 token 减少 ~76%。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-gemini\" title=\"Gemini(Google)\">\n <p>\n Google 最新 <strong>Gemini 3 Flash</strong>(<code className=\"bg-muted px-1 rounded text-xs font-mono\">gemini-3-flash-preview</code>,2025-12-17)以 Flash 价位提供 Pro 级推理;\n 另有 <strong>Gemini 3.1 Pro Preview</strong>(旗舰)、<strong>Gemini 3.1 Flash-Lite Preview</strong>。\n 全部支持 Function Calling、并行调用、组合调用,原生多模态,<code className=\"bg-muted px-1 rounded text-xs font-mono\">thinking_level</code> 控制推理深度。\n 推荐用 <strong>OpenAI 兼容端点</strong>,可直接复用 OpenAI SDK 与已有的 MCP 工具桥接代码。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`GEMINI_API_KEY=AIzaSyxxxxxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式一:OpenAI 兼容端点(推荐,零迁移成本)</p>\n <CodeBlock filename=\"gemini_openai_compat.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# Gemini OpenAI 兼容端点 —— 完全复用 OpenAI SDK\nclient = OpenAI(\n api_key=os.environ[\"GEMINI_API_KEY\"],\n base_url=\"https://generativelanguage.googleapis.com/v1beta/openai/\",\n)\n\nasync def evaluate_with_gemini(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\",\n \"content\": f\"请评测:audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"gemini-3-flash-preview\", # 或 gemini-3.1-pro-preview\n messages=messages,\n tools=oa_tools,\n tool_choice=\"auto\",\n )\n msg = resp.choices[0].message\n messages.append(msg)\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n final = client.chat.completions.create(\n model=\"gemini-3-flash-preview\", messages=messages)\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_gemini(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式二:原生 google-genai SDK(thinking_level / 流式工具调用)</p>\n <CodeBlock filename=\"gemini_native.py\" lang=\"python\">{`# pip install google-genai\nfrom google import genai\nfrom google.genai import types\n\nclient = genai.Client(api_key=os.environ[\"GEMINI_API_KEY\"])\n\n# 把 MCP 工具转成 Gemini function_declarations\nfn_decls = [{\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n} for t in tools]\n\nresp = client.models.generate_content(\n model=\"gemini-3-flash-preview\",\n contents=f\"评测 audioId={audio_id},参考文本:{ref_text}\",\n config=types.GenerateContentConfig(\n tools=[types.Tool(function_declarations=fn_decls)],\n # Gemini 3 起:thinking_level 替代旧的 thinking_budget\n # thinking_level=\"high\",\n ),\n)\n\n# tool_call 在 response.candidates[0].content.parts[0].function_call\nfc = resp.candidates[0].content.parts[0].function_call\nprint(f\"call: {fc.name}({dict(fc.args)}) id={fc.id}\")`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 日常评测用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3-flash-preview</code>(速度比 2.5 Pro 快 3 倍、价格 ~$0.50/M 输入),已是 Gemini App 默认;\n 复杂多步 Agent / 编程任务用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3.1-pro-preview</code>;\n 超大上下文 + 多模态批处理用同系列的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3.1-flash-lite-preview</code>。\n </Callout>\n <Callout type=\"info\">\n <strong>注意:</strong>Gemini 3 起每次 function_call 自动带 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">id</code>,多轮工具调用必须把 id 透传回 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">function_response</code>,否则会被拒绝(OpenAI 兼容端点已自动处理)。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-comparison\" title=\"模型对比速查\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead>\n <tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 pr-4 font-medium\">模型</th>\n <th className=\"text-left py-2 pr-4 font-medium\">推荐版本</th>\n <th className=\"text-left py-2 pr-4 font-medium\">base_url</th>\n <th className=\"text-left py-2 pr-4 font-medium\">Function Calling</th>\n <th className=\"text-left py-2 font-medium\">适合场景</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek V3.2 · 非思考</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-chat</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">评测调用 + 诊断,性价比最高</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek V3.2 · 思考</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-reasoner</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(思考中可调工具)</td>\n <td className=\"py-2\">深度分析 + 错因归因 + 个性化练习路径</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-5 · 旗舰</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-5</code> / <code className=\"bg-muted px-1 rounded font-mono\">glm-5.1</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">200K 长上下文 + Agent 编排,复杂诊断</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4.7</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4.7</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">在线辅导日常诊断,性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4-Flash</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4-flash</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">免费额度,开发联调</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2.5(旗舰)</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2.5</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">256K + 多模态 + Agent 集群协作</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2 Thinking</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2-thinking</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">多步推理 + 多轮历史进度报告</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2 Turbo</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2-turbo-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高速响应版,实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Pro</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-pro-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(含深度思考)</td>\n <td className=\"py-2\">多步评测编排 + 深度诊断 Agent</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Lite</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-lite-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">在线辅导 + 单轮诊断,性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Mini</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-mini-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">移动端 / 边缘部署 / 低延迟实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5.1(旗舰)</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5.1</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(reasoning_effort 可调)</td>\n <td className=\"py-2\">400K 上下文 + 深度推理 + 复杂 Agent 编排</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5.1 Chat</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5.1-chat-latest</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">ChatGPT 同款,对话 / 课堂讲解</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5 mini</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5-mini</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高并发 / 批量评测降本</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Opus 4.7</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-opus-4-7</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(tool_use 协议)</td>\n <td className=\"py-2\">旗舰,编程 / Agent / 深度诊断</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Sonnet 4.6</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-sonnet-4-6</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">日常评测 + 性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Haiku 4.5</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-haiku-4-5</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高并发实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Gemini 3 Flash</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gemini-3-flash-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">…/v1beta/openai/</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(并行 / 组合)</td>\n <td className=\"py-2\">Pro 级推理 + Flash 价位,Gemini App 默认</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Gemini 3.1 Pro Preview</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gemini-3.1-pro-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">…/v1beta/openai/</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(thinking_level 可调)</td>\n <td className=\"py-2\">复杂多步 Agent + 编程任务</td>\n </tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"info\">\n 除 <strong>Claude</strong> 用 Anthropic 原生 SDK(<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">anthropic</code> 包,<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">tool_use</code> / <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">tool_result</code> 协议)外,其余所有模型均可通过 <strong>OpenAI Python SDK</strong>(<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">openai</code> 包)调用,仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 与 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 侧代码<strong>无需改动</strong>。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"code-selfhosted-agent\" title=\"④ 自研后端 Agent(FastAPI / NestJS / Spring)\">\n <p>大部分落地场景其实是:<strong>你已有一个业务后端</strong>(教育 SaaS / 培训系统 / 客服平台),只需要新增一个「语音评测」能力。推荐架构:</p>\n\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">前端录音 / 上传</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">业务后端</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">MCP Client(常驻连接)</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">驰声 MCP</span>\n <span className=\"text-muted-foreground\">↘</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM(诊断 / 出题)</span>\n </div>\n </div>\n\n <p className=\"font-semibold mt-2\">FastAPI 示例(生产最小骨架)</p>\n <CodeBlock filename=\"app.py\" lang=\"python\">{`from fastapi import FastAPI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\nfrom contextlib import asynccontextmanager\n\nmcp_session: ClientSession | None = None\n\n@asynccontextmanager\nasync def lifespan(app):\n global mcp_session\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as s:\n await s.initialize()\n mcp_session = s\n yield\n mcp_session = None\n\napp = FastAPI(lifespan=lifespan)\n\n@app.post(\"/api/evaluate\")\nasync def evaluate(audio_id: str, ref_text: str):\n # 直接选一个最合适的工具执行(不经 LLM,延迟最低)\n r = await mcp_session.call_tool(\"evaluate_english_sentence\",\n {\"audioId\": audio_id, \"sentence\": ref_text})\n return {\"result\": r.content[0].text}`}</CodeBlock>\n\n <Callout type=\"tip\"><strong>关键优化</strong>:① MCP 连接在 lifespan 里做成长连接,不要每次请求都重连;② 对延迟敏感的场景可以「跳过 LLM 直接调 tool」,把 LLM 放到「生成诊断报告」那一步。</Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mt-6 text-sm\">\n <p className=\"font-semibold mb-2\">方案选型速查</p>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 font-medium\">场景</th>\n <th className=\"text-left py-2 font-medium\">推荐方案</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2\">AI 对话 / Copilot 类产品,LLM 供应商可能会换</td><td className=\"py-2\">① 官方 MCP 客户端库</td></tr>\n <tr><td className=\"py-2\">已有 LangChain / Mastra / Agents SDK 技术栈</td><td className=\"py-2\">② Agent 框架 + adapter</td></tr>\n <tr><td className=\"py-2\">火山方舟 / 千帆控制台直连,最小侵入</td><td className=\"py-2\">③ chat.completions + tools</td></tr>\n <tr><td className=\"py-2\">教育 SaaS / 培训 / 客服等业务后端集成</td><td className=\"py-2\">④ 自研后端 Agent</td></tr>\n </tbody>\n </table>\n </div>\n </div>\n </DocSection>\n\n {/* ══════ 评测模式 ══════ */}\n <DocSection id=\"eval-modes\" icon={Radio} title=\"评测模式\">\n\n <SubDoc id=\"stream-eval\" title=\"实时录音评测(边录边评)\">\n <p>通过本地麦克风实时录音,音频以 WebSocket 流式推送到评测服务,<strong>无需生成中间文件</strong>。这是最灵活、延迟最低的评测方式,特别适合交互式口语练习场景。</p>\n <Callout type=\"tip\">实时录音评测需要安装 <strong>SoX</strong> 并使用本地代理模式(Claude Desktop / Claude Code)。Cursor 等远程直连模式暂不支持实时录音。</Callout>\n\n <p className=\"font-semibold mt-4\">需要按顺序调用三个工具:</p>\n\n <div className=\"mt-3 space-y-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">create_stream_session</code> — 创建流式评测会话</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.word.score、en.sent.score、cn.sent.raw' },\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式,默认 mp3' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100,默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code>,后续步骤需使用。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">start_recording</code> — 开始麦克风录音</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '来自 create_stream_session 的会话 ID' },\n { name: 'chunk_size', type: 'number', required: false, desc: '音频块大小(bytes),默认 3200' },\n { name: 'chunk_interval_ms', type: 'number', required: false, desc: '推送间隔(ms),默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">调用后自动启动麦克风,音频通过 WebSocket 实时推送到评测服务。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">stop_recording</code> — 停止录音并获取结果</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '会话 ID' },\n { name: 'timeout', type: 'number', required: false, desc: '等待评测结果超时秒数,默认 30' },\n ]} />\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"stream-flow\" title=\"流式评测完整流程\">\n <div className=\"rounded-lg border border-border/60 p-5 bg-muted/10 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong>用户:</strong>&quot;请评测我的英文发音,文本是 Hello&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">create_stream_session(core_type=&quot;en.word.score&quot;, ref_text=&quot;Hello&quot;)</code></p>\n <p className=\"text-muted-foreground\">→ 获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">session_id</code></p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p>AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">start_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground mt-1\">麦克风开始录音,音频通过 WebSocket 实时推送到评测服务</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>用户</strong>说完后告知 AI &quot;结束&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">stop_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground\">→ 返回评测结果(总分、准确度、流利度、音素得分等)</p>\n </FlowStep>\n </div>\n </SubDoc>\n\n <SubDoc id=\"file-eval\" title=\"音频文件评测\">\n <p>适用于已有音频文件的评测场景,支持三种输入方式:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">适用场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_file_path</td><td className=\"py-2 px-3\">本地文件路径</td><td className=\"py-2 px-3 text-muted-foreground\">代理自动读取并转为 Base64,最便捷</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_base64</td><td className=\"py-2 px-3\">Base64 编码数据</td><td className=\"py-2 px-3 text-muted-foreground\">已有编码数据时使用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_url</td><td className=\"py-2 px-3\">音频文件 URL</td><td className=\"py-2 px-3 text-muted-foreground\">音频托管在云存储时使用</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"tip\">推荐使用 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">audio_file_path</code>,只需传入本地文件路径,代理会自动处理编码和上传。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"tools-en\" title=\"英文评测工具(10 种)\">\n <ToolTable tools={[\n ['en_word_eval', '单词评测', '评测 \"Hello\" 的发音准确度', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.word.score'],\n ['en_word_correction', '单词纠音', '检测多读/漏读/错读并给出纠正建议', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.word.pron'],\n ['en_vocab_eval', '词语评测', '同时评测多个单词的发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.vocabs.pron'],\n ['en_sentence_eval', '句子评测', '评测整句朗读的准确度与流利度', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.sent.score'],\n ['en_sentence_correction', '句子纠音', '逐词检测发音问题并给出修正建议', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.sent.pron'],\n ['en_paragraph_eval', '段落朗读评测', '评测整段英文课文的朗读质量', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.pred.score'],\n ['en_phonics_eval', '自然拼读评测', '评测 Phonics 拼读规则掌握程度', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.nsp.score'],\n ['en_choice_eval', '口语选择题', '从预设选项中识别口语回答', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.choc.score'],\n ['en_semi_open_eval', '半开放题评测', '场景对话等半开放口语题型', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.scne.exam'],\n ['en_realtime_eval', '实时朗读评测', '实时反馈朗读质量,逐句评分', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreEn/en.rltm.score'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong>所有英文评测工具均支持以下参数。</p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tools-cn\" title=\"中文评测工具(6 种)\">\n <ToolTable tools={[\n ['cn_word_raw_eval', '单字评测(汉字)', '评测 \"中\" 的发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.pred.raw'],\n ['cn_word_pinyin_eval', '单字评测(拼音)', '评测 \"zhōng\" 的发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.pred.pny'],\n ['cn_sentence_eval', '词句评测', '评测 \"你好世界\" 的朗读', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.sent.score'],\n ['cn_paragraph_eval', '段落朗读评测', '评测一段中文课文的朗读质量', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.para.score'],\n ['cn_rec_eval', '有限分支识别评测', '从预设选项中识别用户发音', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.asr.rec'],\n ['cn_aitalk_eval', 'AI Talk 口语评测', '中文口语对话能力评测', 'https://www.chivox.com/opendoc/#/ChineseDoc/coreCn/Chinese/cn.aitalk'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong></p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本(汉字或拼音)' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结构化结果:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"integrity\": 95,\n \"details\": [\n {\n \"char\": \"hello\",\n \"score\": 85,\n \"phone\": [\n { \"phoneme\": \"h\", \"score\": 90 },\n { \"phoneme\": \"ɛ\", \"score\": 82 },\n { \"phoneme\": \"l\", \"score\": 88 },\n { \"phoneme\": \"oʊ\", \"score\": 80 }\n ]\n }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall', '0–100', '综合总分,反映整体语音质量'],\n ['accuracy', '0–100', '准确度,音素级别的发音准确率'],\n ['pron', '0–100', '发音得分,整体发音质量评价'],\n ['fluency.overall', '0–100', '流利度,语流的自然度与平滑度'],\n ['fluency.speed', '0–100', '语速得分,每分钟词/音节计量'],\n ['fluency.pause', 'number', '不自然停顿次数'],\n ['integrity', '0–100', '完整度,是否完整朗读了参考文本'],\n ['details[].phone[]', 'array', '每个音素的独立评分,可定位具体弱项'],\n ['details[].stress[]', 'array', '重音得分,检测重弱读是否正确'],\n ].map(([field, range, desc]) => (\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期', '音频上传后 5 分钟内有效,评测完成后自动删除', '重新上传音频'],\n ['服务器繁忙', '当前请求过多,排队已满', '稍后重试,使用指数退避策略'],\n ['存储空间已满', '服务器临时音频存储已满', '稍后重试,系统会自动清理'],\n ['音频格式不支持', '上传了不支持的文件类型', '使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限', '超过该题型最大时长限制', '裁剪音频后重试'],\n ['WebSocket 连接断开', '网络不稳定导致流式连接中断', '代理会自动重连,如持续失败请检查网络'],\n ].map(([scene, reason, fix]) => (\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p className=\"text-sm text-muted-foreground\">\n 下面这些模板与{' '}\n <Link href=\"/demo\" className=\"underline underline-offset-2\">免费体验</Link>{' '}\n Demo 中实际使用的「教练 Prompt」一脉相承,全部围绕 <strong>角色 / 任务 / 方法 / 输出格式 / 语气</strong> 五段式结构,并使用占位符{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code> 直接拼接 MCP 工具返回的 JSON。\n 推荐在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">system</code> 消息里挂载,再把工具返回原文塞进\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">user</code> 消息,由 LLM 基于真实分数和音素生成可读诊断 / 练习材料。\n </p>\n\n <Callout type=\"tip\">\n <strong>使用建议:</strong>\n <ul className=\"list-disc list-inside mt-1 space-y-1\">\n <li>把 MCP 工具调用的完整 JSON(含 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">overall / dp_type / phonemes</code> 等字段)原样塞入 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,不要预先做汇总——细节越完整诊断越具体。</li>\n <li>「诊断」类模板让 LLM 输出 Markdown 给最终用户阅读;「练习」类模板要求 LLM 输出严格 JSON 给前端渲染。两步分开调用,效果远好于一次性混合。</li>\n <li>题型决定 prompt:单词题用音素级、句子题加韵律、段落题加 chunk / 整体性,半开放题套四维评级。下面按这 6 类分别给出。</li>\n </ul>\n </Callout>\n\n {/* ─── A. 诊断类 · 输出 Markdown 给学习者阅读 ─── */}\n <h4 className=\"text-sm font-semibold mt-6 mb-3 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> A. 二次诊断 · 输出 Markdown\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Sparkle className=\"h-4 w-4\" /> 1. 单词级 · 音素诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.word.score</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code>。围绕 <code className=\"bg-muted px-1 rounded font-mono\">phonemes[].dp_type</code> 找出错读音。\n </p>\n <CodeBlock filename=\"prompt-word-diagnosis.txt\">{`你是一位专业的英语发音教练。下面是驰声 MCP(en.word.score)返回的单词级评测数据:\n一个目标词、总分与发音分、以及带有 dp_type 的音素数组。\n\n## 任务\n为学习者写一份 **可直接阅读** 的发音诊断,每一条都要引用音素分数(写出 IPA 符号)。\n\n## 方法\n- 按 dp_type 分组:先看 mispron,再补充 normal 但分数 < 70 的\"边缘音\"。\n- 每个问题音素:① 典型母语干扰 → ② 你听到的声音 → ③ 一条具体的发音修正动作(下颌 / 舌位 / 气流 / 唇形)。\n- 重音符号 ˈ 标记的音节,重音错位也要单独列出(即便音素本身分数尚可)。\n- 最多列 4 条,按 **影响力从弱到强** 排序。\n\n## 输出\n精简 Markdown:可选一句总结 → 编号列表 → 单行 **🎯 优先级** 排序。\n\n## 语气\n友善、具体、可执行,不要空喊\"加油\"。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> 2. 句子级 · 节奏 + 重音诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.sent.score</code>。开头先给一行\"计分牌\",再从 phones → words → prosody。\n </p>\n <CodeBlock filename=\"prompt-sentence-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.sent.score)返回的句子级数据:refText、总分、发音子分(accuracy / integrity / fluency / rhythm)、可选 speed(WPM),以及 details[] 中的逐词得分、dp_type 与可选 phonemes[]。\n\n## 任务\n开头给一行紧凑的 **计分牌**(总分 + 各子分 + 速度),再按 **音素 → 词 → 韵律** 顺序展开诊断。\n\n## 检查清单(每项都要覆盖;不适用则写\"无明显问题\")\n1. **音素级**:每个 score < 70 或 dp_type ≠ normal 的词,展开 phonemes,命名典型错误(如 /ð/ → /d/)并给一条修正动作。\n2. **漏读 / 多读**:标注 dp_type=omit / insert,解释如何拉低 integrity。\n3. **韵律**:基于 rhythm + 错读词的重音,指出重音错位、陈述句调型平直、断句过多等。\n4. **流利度 vs 准确度**:若 fluency 高但 accuracy 低,明确指出,让学习者知道优先改哪一项。\n\n## 输出格式\nMarkdown 子标题:**Pronunciation / Prosody / Study priority**(最后一行简短排序,按 ROI 从高到低)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <FileText className=\"h-4 w-4\" /> 3. 段落级 · 音素族归并 + 完整度\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code>。把同类错音合并为\"音素族\",避免逐词复述。\n </p>\n <CodeBlock filename=\"prompt-paragraph-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.pred.score)返回的段落级数据:长 refText、总分、发音子分、可选 speed / integrity,以及大量 details[](同一个词可能在不同子句中重复出现;空 char + dp_type=omit 表示漏读)。\n\n## 任务\n对整段做 **整体诊断**:\n(a) 完整度 / 漏读,\n(b) 反复出现的 **音素族**,\n(c) 节奏与断句,\n(d) 各子分之间是否相互印证。\n\n## 检查清单\n1. **integrity / dp_type**:列出每个 omit / insert,并给出所在子句;解释长读时为什么虚词容易被吞。\n2. **音素聚类**:将弱词按共同音素问题归族(/θ/ 链、/e/ 链、/ɒ/ 等),引用 details 中的实例。\n3. **韵律**:结合 rhythm 与标点 —— 短语内停顿过多?子句边界缺失?\n4. **整体一致性**:若 fluency 正常但 accuracy 差(或反之),点明并指出对练习设计的影响。\n\n## 输出格式\nMarkdown 章节:**Summary / Completeness / Pronunciation (families) / Rhythm / Study priorities**(最后一项最多 4 条,按影响力排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <GraduationCap className=\"h-4 w-4\" /> 4. 半开放题 · IELTS 风格四维评级\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pqan.score</code>。结合学习者 transcript + 四维分数,给出可教学的\"升档建议\"。\n </p>\n <CodeBlock filename=\"prompt-semi-open.txt\">{`你是一位 **IELTS / 课堂口语风格** 的口语教练。下面是驰声 MCP(en.pqan.score)返回的半开放题数据:题目 refText、学习者 transcript、overall、四个维度分(grammar / content / fluency / pron)、speed(WPM)、以及结构化的 issues[] 标记。\n\n## 任务\n给出 **基于 rubric 的诊断**:先用一句话给出 0–100 的整体段位估计,再 **逐维度** 解释,并 **引用 transcript 中的短句**(不要复述全文)。最后给 3–5 条 **可执行的\"升档建议\"**(句型 / 模板,而不是\"多练一点\")。\n\n## 权重(要明确告诉学习者)\n- **Content ~30%** — 想法深度、举例、具体程度\n- **Grammar ~25%** — 句型多样性、主谓一致、从句\n- **Fluency ~25%** — 语速、停顿、口头禅、衔接\n- **Pronunciation ~20%** — 音素 + 可懂度\n\n## 跨维度检查\n- 若 content 最低,先给\"展开模板\",不要先抠音素。\n- 若 fluency 低 + WPM 低,区分\"思考型停顿\"与\"发音卡顿\"。\n- 若 pron 标出特定音,绑定到 transcript 里 **真实出现过的词**。\n\n## 输出格式\nMarkdown:**段位估计** → 紧凑的 **得分表格** → **\"To reach the next band\"** 编号列表(每条给出可仿写的句型)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Languages className=\"h-4 w-4\" /> 5. 中文专项 · 声调 / 儿化 / 变调\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.sent.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.pred.raw</code>。中文评测最大权重在 **声调**,必须单独检查。\n </p>\n <CodeBlock filename=\"prompt-mandarin-coach.txt\">{`你是一位专业的普通话口语教练。下面是驰声 MCP(cn.sent.raw)返回的中文句子评测数据:整句分数 + 逐字 details + 可选 phonemes + 流利 / 节奏分。\n\n## 中文评测的权重\n- **声调错误权重最高** —— 声调读错可能直接改变词义\n- dp_type=mispron 多来自:声调错、平翘舌混用、前后鼻音混用\n- 拼音对、声调错,听感仍是\"洋腔洋调\"\n\n## 检查清单(缺则写\"无明显问题\")\n1. **儿化**:phonemes 出现 r(儿化) 或 dp_type 提示时,确认是否被读成\"主音节 + 单独儿\"两拍。\n2. **轻声**:叠词、词缀、句末语气词 —— 第二字应短促轻读,无完整声调曲线。\n3. **变调**:三声连读、不 + 后字、一 + 后字,是否符合普通话规则。\n4. **节奏**:用 rhythm + 逗号位置,标记停顿过长或过碎,建议 chunk 边界。\n\n## 输出\nMarkdown 子标题:**总览 / 儿化 / 轻声 / 变调 / 节奏 / 学习优先级**(最后一项 1–3 条,按\"听感自然度提升\"排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── B. 练习类 · 输出 JSON 给前端渲染 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <Mic className=\"h-4 w-4\" /> B. 微练习生成 · 输出严格 JSON\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 6. 单词 / 句子 · 微练习\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 建议在 <strong>诊断完成后</strong> 第二轮调用:让 LLM 严格按弱项音素生成,避免\"通用绕口令包\"。\n </p>\n <CodeBlock filename=\"prompt-micro-drills.txt\">{`你仍是这位学习者的发音教练。**第二轮诊断已经完成** —— 现在严格基于诊断里点名的弱音素,生成 **1:1 对应** 的微练习(不要给通用题)。\n\n## 输出(仅返回一段合法 JSON 数组,外层不要任何文字)\n根节点必须是数组,每个元素含 category(字符串)、icon(单个 emoji)、items({ \"label\", \"content\" } 数组)。\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme drills\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Awareness\", \"icon\": \"🗣️\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Whole word\", \"icon\": \"📝\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme drills**:诊断里每个 mispron 音素至少给一对最小对立对 + 一句短绕口令 / 重复句。\n- **Awareness**:当重音偏弱时,给一条 stress / 舌位 / 下颌姿态提示。\n- **Whole word**:2–3 句自然句,目标词分别出现在句首 / 句中 / 句末。\n- 所有 content 必须可念出声;字符串内不要再嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n原始 MCP:\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Notebook className=\"h-4 w-4\" /> 7. 段落 · Chunk Map + 元认知\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 段落级练习要从\"音素\"上升到\"短语 / 整段 / 元认知录回放\"。直接对应 <code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code> 的诊断结果。\n </p>\n <CodeBlock filename=\"prompt-paragraph-drills.txt\">{`你仍是这位学习者的口语教练。**段落级二次诊断** 已经列出了漏读位置和音素族 —— 现在请生成 **整段练习**:把弱音素串成\"族\"练习、用 chunk map 把虚词粘回短语,并附一条录音 → 回放 → 对照 MCP details 的元认知步骤。\n\n## 输出(仅返回一段合法 JSON 数组)\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme families\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Chunking\", \"icon\": \"🎵\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Metacognition\", \"icon\": \"🧠\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme families**:至少 3 条,按诊断里的音素族(/θ/、/e/、/ɒ/ 等)组织最小对立对 / 词链。\n- **Chunking**:① 给一行用 [ ] 和 / 标注的 chunk map(可加大致停顿秒数);② 给一句\"把漏掉的虚词嵌入短语\"的练习(不要孤立练)。\n- **Metacognition**:一条\"录音 → 回放 → 对照 MCP details\"的 N 遍练习(N 写明,2–3 之间)。\n- 字符串只能是纯展示文本,禁止嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── C. 跨题 / 运营 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" /> C. 跨题 / 运营\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Gauge className=\"h-4 w-4\" /> 8. 多日学习报告\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 把过去 N 天的 MCP 评测结果聚合后丢给 LLM,输出适合发给学生 / 家长的周报。\n </p>\n <CodeBlock filename=\"prompt-weekly-report.txt\">{`你是一位英语口语学习顾问。下面是这位学习者过去 7 天的驰声 MCP 评测历史(按时间顺序),每条包含题型、coreType、overall、子分、weak_phonemes 摘要。\n\n## 任务\n为学习者生成一份 **周报**,目标读者:本人 + 家长 / 老师。\n强调 **趋势** 与 **下一步计划**,不要复述每一次具体分数。\n\n## 必须包含\n1. **整体趋势**:进步 / 持平 / 退步(一句话 + 简短依据)\n2. **维度变化**:accuracy / fluency / rhythm / integrity / speed 任意有数据的维度,给出\"本周均分 vs 上周\"\n3. **本周高光**:分数提升最大的题型 / 音素 / 句型\n4. **顽固问题**:连续 3 次以上未达 70 的音素或维度\n5. **下周计划**:3 条具体动作(每条对应一个 MCP 工具或一个练习类型)\n6. **结尾**:1–2 句鼓励性总结,避免空话\n\n## 风格\n- 像一位认真但不啰嗦的私教\n- 出现学生姓名时使用占位 {student_name}\n- 数字一律保留整数\n\n## 评测历史\n\\`\\`\\`json\n{history}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 9. 错误归因 · JSON 摘要\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 用于后端聚合 / 数据看板:让 LLM 把一次评测压缩成结构化摘要,方便入库统计。\n </p>\n <CodeBlock filename=\"prompt-error-summary.txt\">{`你是一个评测结果归类器。下面是驰声 MCP 一次评测的完整返回。请抽取关键信息并 **仅返回合法 JSON**(不要任何解释文字)。\n\n## Schema\n\\`\\`\\`json\n{\n \"core_type\": \"string,例如 en.sent.score\",\n \"overall\": 0,\n \"sub_scores\": { \"accuracy\": 0, \"integrity\": 0, \"fluency\": 0, \"rhythm\": 0 },\n \"weak_phonemes\": [\n { \"ipa\": \"string\", \"avg_score\": 0, \"occur\": 0, \"example_word\": \"string\" }\n ],\n \"omissions\": [{ \"after\": \"string\", \"missing\": \"string\" }],\n \"stress_issues\": [{ \"word\": \"string\", \"expected\": \"string\", \"actual\": \"string\" }],\n \"primary_issue_tag\": \"phoneme|stress|fluency|integrity|content|none\",\n \"next_action_hint\": \"string,10 字以内,例如 \\\"练 /θ/ 对立对\\\"\"\n}\n\\`\\`\\`\n\n## 规则\n- weak_phonemes 只收 avg_score < 70 的项,最多 5 项,按 occur 降序。\n- 不要复述 refText,不要 markdown。\n- 字段缺失时使用 null 或空数组,不要省略 key。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n <Callout type=\"info\">\n <strong>怎么接到 MCP 工具调用?</strong>\n <br />\n 以「直调 chat.completions + 动态 tools」为例:第一轮 LLM 调出 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code> 工具,把工具 result(即 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">tool</code> 消息内容)原样填入上面任意「诊断」模板的{' '}\n <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,作为第二轮 user 消息发回;拿到诊断结论再走第三轮,套「微练习」模板得到 JSON。三轮调用即可串起 <strong>评测 → 诊断 → 练习</strong> 全链路。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频过期:</strong>上传后 5 分钟内有效。建议在上传后 3 分钟内完成评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>服务繁忙:</strong>使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>录音中断:</strong>WebSocket 断线后代理会自动重连。如持续失败,检查网络连接和 SoX 安装。</span></li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>优先使用流式评测:</strong>实时录音模式跳过了文件上传环节,端到端延迟比文件评测低 30-50%。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频压缩:</strong>文件评测时,上传前将音频转为 16kHz 单声道 mp3,可减少 60-80% 的上传体积。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>选择合适的工具:</strong>单词评测比段落评测快 3-5 倍。如只需单词级分数,不要用句子评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>本地文件路径最便捷:</strong>使用 <code className=\"bg-muted px-1 rounded text-xs\">audio_file_path</code> 参数传入本地路径,代理自动处理编码,无需手动转 Base64。</span></li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Globe} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP JSON-RPC 接口(Streamable HTTP)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制', '500MB', '最大音频存储空间'],\n ['最大排队', '10', '等待处理的请求数'],\n ['最大并发', '3', '同时处理的评测数'],\n ['音频有效期', '5 分钟', '上传后未评测自动过期'],\n ['文件大小限制', '50MB', '单个音频文件最大'],\n ].map(([k, v, d]) => (\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">\n 上表为托管侧<strong>技术默认</strong>;计费与试用/商用<strong>调用配额、并发档位</strong>以{' '}\n <Link href=\"/dashboard/plans\" className=\"text-foreground underline underline-offset-2\">\n 会员套餐\n </Link>\n 说明为准,与上表独立。\n </p>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <Callout type=\"info\">\n <strong>当前线上版本:v1.0.0</strong>(网站未正式上线)。以下变更项为开发阶段规划记录,以正式发布公告为准。\n </Callout>\n <div className=\"space-y-3 mt-4\">\n {[\n { ver: 'v1.0.0', date: 'TBD', desc: '当前线上版本(网站未正式上线)' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </DocsShell>\n );\n}\n\nexport default function DocsPage() {\n const locale = useLocale();\n return locale.startsWith('zh') ? <DocsPageZh /> : <DocsPageEn />;\n}\n"}, "fdbdbf4fba51ea8d4a904cd233d4d6d47fd62592": {"t": 1785765793.677472, "n": 145186, "h": "698501e54defff53", "c": "'use client';\n\nimport { useLocale } from 'next-intl';\nimport { Link } from '@/i18n/routing';\nimport { DocsShell, type NavGroup } from './docs-shell';\nimport {\n CodeBlock,\n DocSection,\n SubDoc,\n ParamTable,\n Callout,\n FlowStep,\n ToolTable,\n} from './docs-shared';\nimport { DocsEnglishBody } from './docs-content-en';\nimport {\n BookOpen,\n Code2,\n Zap,\n FileText,\n Terminal,\n Globe,\n Mic,\n MessageSquare,\n BarChart3,\n AlertTriangle,\n Radio,\n FolderOpen,\n Workflow,\n Sparkles,\n Plug,\n Bot,\n Building2,\n Check,\n Sparkle,\n Languages,\n GraduationCap,\n Notebook,\n Gauge,\n ListChecks,\n} from 'lucide-react';\n\n/* ─── Navigation Structure ─── */\n\nconst NAV = [\n { id: 'concept', icon: Workflow, label: 'MCP 工作原理', children: [\n { id: 'why-mcp', label: '为什么需要 MCP' },\n { id: 'scenario-walkthrough', label: '场景走查 · 6 步' },\n { id: 'dev-responsibility', label: '开发者只做两件事' },\n { id: 'integration-paths', label: '三种接入姿势' },\n ]},\n { id: 'architecture', icon: Terminal, label: '架构说明', children: [\n { id: 'arch-diagram', label: '架构图' },\n { id: 'transport', label: '传输协议' },\n ]},\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'requirements', label: '系统要求' },\n { id: 'install', label: '安装方式' },\n { id: 'env-vars', label: '环境变量' },\n ]},\n { id: 'config', icon: Plug, label: '接入 · 直接用(零代码)', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: '扣子(Coze)' },\n { id: 'config-coze-workflow', label: 'Coze 工作流' },\n { id: 'config-ai-workspace', label: 'AI 工作台 · 豆包 / 飞书 / WorkBuddy / 钉钉' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: '其他 MCP 客户端' },\n ]},\n { id: 'config-code', icon: Code2, label: '接入 · 编程用(SDK / 代码)', children: [\n { id: 'code-mcp-sdk', label: '官方 MCP 客户端库' },\n { id: 'code-agent-frameworks', label: 'LangChain / Mastra / Agents SDK' },\n { id: 'code-function-calling', label: '直调 chat.completions · 上传与动态 tools' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM(智谱)' },\n { id: 'llm-kimi', label: 'KIMI(Moonshot)' },\n { id: 'llm-doubao', label: '豆包 Seed 2.0(火山方舟)' },\n { id: 'llm-openai', label: 'OpenAI(GPT-5.1)' },\n { id: 'llm-claude', label: 'Claude(Anthropic)' },\n { id: 'llm-gemini', label: 'Gemini(Google)' },\n { id: 'llm-comparison', label: '模型对比速查' },\n { id: 'code-selfhosted-agent', label: '自研后端 Agent' },\n ]},\n { id: 'eval-modes', icon: Radio, label: '评测模式', children: [\n { id: 'stream-eval', label: '实时录音评测' },\n { id: 'stream-flow', label: '流式评测流程' },\n { id: 'file-eval', label: '音频文件评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'tools-en', label: '英文评测工具' },\n { id: 'tools-cn', label: '中文评测工具' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n { id: 'service-info', icon: Globe, label: '服务信息', children: [\n { id: 'endpoints', label: '服务端点' },\n { id: 'limits', label: '服务限制' },\n { id: 'changelog', label: '更新日志' },\n ]},\n];\n\nconst NAV_EN: NavGroup[] = [\n { id: 'concept', icon: Workflow, label: 'How MCP works', children: [\n { id: 'why-mcp', label: 'Why MCP' },\n { id: 'scenario-walkthrough', label: 'Scenario · 6 steps' },\n { id: 'dev-responsibility', label: 'Two developer tasks' },\n { id: 'integration-paths', label: 'Three integration styles' },\n ]},\n { id: 'architecture', icon: Terminal, label: 'Architecture', children: [\n { id: 'arch-diagram', label: 'Diagram' },\n { id: 'transport', label: 'Transports' },\n ]},\n { id: 'quick-start', icon: Zap, label: 'Quick start', children: [\n { id: 'overview', label: 'Overview' },\n { id: 'requirements', label: 'Requirements' },\n { id: 'install', label: 'Install' },\n { id: 'env-vars', label: 'Environment' },\n ]},\n { id: 'config', icon: Plug, label: 'Zero-code clients', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: 'Coze' },\n { id: 'config-coze-workflow', label: 'Coze workflows' },\n { id: 'config-ai-workspace', label: 'Enterprise AI workspaces' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: 'Other MCP clients' },\n ]},\n { id: 'config-code', icon: Code2, label: 'SDK / code integration', children: [\n { id: 'code-mcp-sdk', label: 'Official MCP SDK' },\n { id: 'code-agent-frameworks', label: 'Agent frameworks' },\n { id: 'code-function-calling', label: 'chat.completions · upload & tools' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM (Zhipu)' },\n { id: 'llm-kimi', label: 'KIMI (Moonshot)' },\n { id: 'llm-doubao', label: 'Doubao Seed 2.0 (Volcano Ark)' },\n { id: 'llm-qwen', label: 'Qwen (Alibaba)' },\n { id: 'llm-openai', label: 'OpenAI (GPT-5.1)' },\n { id: 'llm-claude', label: 'Claude (Anthropic)' },\n { id: 'llm-gemini', label: 'Gemini (Google)' },\n { id: 'llm-comparison', label: 'Model comparison' },\n { id: 'code-selfhosted-agent', label: 'Custom backend' },\n ]},\n { id: 'eval-modes', icon: Radio, label: 'Evaluation modes', children: [\n { id: 'stream-eval', label: 'Streaming mic' },\n { id: 'stream-flow', label: 'Streaming flow' },\n { id: 'file-eval', label: 'Audio files' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API reference', children: [\n { id: 'tools-en', label: 'English tools' },\n { id: 'tools-cn', label: 'Chinese tools' },\n { id: 'result-fields', label: 'Result fields' },\n { id: 'error-codes', label: 'Errors' },\n ]},\n { id: 'best-practices', icon: FileText, label: 'Best practices', children: [\n { id: 'prompt-templates', label: 'Prompt templates' },\n { id: 'error-handling', label: 'Errors & retries' },\n { id: 'performance', label: 'Performance' },\n ]},\n { id: 'service-info', icon: Globe, label: 'Service info', children: [\n { id: 'endpoints', label: 'Endpoints' },\n { id: 'limits', label: 'Limits' },\n { id: 'changelog', label: 'Changelog' },\n ]},\n];\n\n/* ─── Page(英文 / 中文拆成两个组件,避免切换语言时 Hooks 数量变化导致白屏)─── */\n\nfunction DocsPageEn() {\n return (\n <DocsShell\n nav={NAV_EN}\n backLabel=\"Back to Home\"\n title=\"Developer documentation\"\n subtitle=\"Chivox Speech Evaluation MCP · chivox-local-mcp\"\n >\n <DocsEnglishBody />\n </DocsShell>\n );\n}\n\nfunction DocsPageZh() {\n return (\n <DocsShell\n nav={NAV}\n backLabel=\"返回首页\"\n title=\"开发者文档\"\n subtitle=\"驰声语音评测 MCP · chivox-local-mcp\"\n >\n {/* ══════ MCP 工作原理 ══════ */}\n <DocSection id=\"concept\" icon={Workflow} title=\"MCP 工作原理\">\n\n <SubDoc id=\"why-mcp\" title=\"为什么需要 MCP\">\n <p>\n 大模型(GPT / Claude / Gemini / 通义千问 等)本质上是<strong>文字大脑</strong>:它擅长读和写,但天生<strong>听不到</strong>音频、<strong>打不开</strong>你的数据库、<strong>调不通</strong>第三方 API。\n </p>\n <p>\n <strong>MCP(Model Context Protocol)</strong>是一套由 Anthropic 主导、业界共同推进的开放协议,它像一根\"对讲机\":让大模型可以在需要时主动去调用外部能力(工具、数据源、服务),然后把结果接回来继续对话。\n </p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <AlertTriangle className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n <span className=\"text-sm font-semibold\">没有 MCP 的做法</span>\n </div>\n <ul className=\"text-xs text-muted-foreground space-y-1.5 leading-relaxed\">\n <li>· 研究评测服务的 REST 文档</li>\n <li>· 自己写鉴权 / 上传 / 轮询代码</li>\n <li>· 把结果序列化后塞进 prompt</li>\n <li>· 每换一个大模型都得重写一遍</li>\n </ul>\n </div>\n <div className=\"rounded-lg border-2 border-foreground/60 p-4 bg-foreground/[0.02]\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Check className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">用驰声 MCP 之后</span>\n </div>\n <ul className=\"text-xs space-y-1.5 leading-relaxed\">\n <li>· 填一行 MCP Server 地址</li>\n <li>· 大模型自动发现&amp;调用评测工具</li>\n <li>· 换任何支持 MCP 的大模型都能用</li>\n <li>· 不写一行调用代码</li>\n </ul>\n </div>\n </div>\n <Callout type=\"info\">\n MCP 让\"接入语音评测\"从一个\"后端工程\"变成了一个\"配置项\"。这也是为什么 Cursor、Claude Desktop、Dify、Coze、字节豆包、飞书智能助手都已经原生支持 MCP。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"scenario-walkthrough\" title=\"场景走查 · 从一段录音到温柔反馈\">\n <p>\n 想象一个具体场景:一位开发者用 Claude 做了一款 <strong>「AI 雅思口语陪练」小程序</strong>,接入驰声语音评测 MCP。当用户发来一段录音,整个链路是这样的:\n </p>\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-5 my-4 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong className=\"text-foreground\">配置阶段</strong>(开发者 · 一次性)</p>\n <p className=\"text-muted-foreground mt-1\">在 AI 后端的配置文件里填一行驰声 MCP Server 地址。大模型启动时自动发现:「我多了一个叫<strong>语音评测</strong>的工具」。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong className=\"text-foreground\">接收用户指令</strong>(终端用户)</p>\n <p className=\"text-muted-foreground mt-1\">小程序用户发来一段自我介绍录音:「帮我听听,发音哪里有问题?」后端把「文字 + 录音链接」打包给大模型。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong className=\"text-foreground\">大模型自主思考</strong>(LLM · 最核心的魔法)</p>\n <div className=\"text-muted-foreground mt-1 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed\">\n 「用户让我听发音。」<br />\n 「但我只是文字大脑,我听不到。」<br />\n 「等等 —— 开发者给了我一个叫 <code className=\"bg-muted px-1 rounded\">chivox_voice_eval</code> 的 MCP 工具!」\n </div>\n </FlowStep>\n <FlowStep title=\"4\">\n <p><strong className=\"text-foreground\">MCP 协议 · 自动调用</strong>(LLM → 驰声)</p>\n <p className=\"text-muted-foreground mt-1\">大模型自动按 MCP 标准构造 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools/call</code> 请求,把录音链接和参考文本发到驰声服务。<span className=\"text-foreground\">开发者不需要写任何调用代码。</span></p>\n </FlowStep>\n <FlowStep title=\"5\">\n <p><strong className=\"text-foreground\">驰声服务处理</strong>(评测引擎)</p>\n <p className=\"text-muted-foreground mt-1\">驰声对音频进行考试级打分,把结构化结果通过 MCP 原路返回:<code className=\"bg-muted px-1 rounded text-xs font-mono\">overall 85</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">/θ/ 音发音错误</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">流利度 88</code>。</p>\n </FlowStep>\n <FlowStep title=\"6\">\n <p><strong className=\"text-foreground\">大模型整合并输出反馈</strong>(LLM)</p>\n <p className=\"text-muted-foreground mt-1\">大模型把冷冰冰的分数翻译成温柔反馈:</p>\n <div className=\"text-foreground/80 mt-2 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed italic\">\n 「你的自我介绍很棒!综合 85 分 🎉 不过 <strong>think</strong> 这个词的 /θ/ 咬舌音不太标准,我们一起来练一下……」\n </div>\n </FlowStep>\n </div>\n <Callout type=\"tip\">\n <strong>整条链路里,MCP 只出现在 ① 和 ④</strong>:① 让开发者一次性告诉大模型「你多了一个工具」,④ 让大模型可以自动调用它。这两步的标准化,就是 MCP 的全部价值。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"dev-responsibility\" title=\"开发者只做两件事\">\n <p>听起来复杂,但对开发者来说只需要两件事:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">1</span>\n <span className=\"text-sm font-semibold\">填一次 MCP 配置</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 在 AI 后端的配置里填入驰声 MCP Server 地址和 API Key,就这一次,以后新加评测能力完全免开发。\n </p>\n <CodeBlock filename=\"mcp-config.json\" lang=\"json\">{`{\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"apiKey\": \"sk-••••••••••\"\n }\n}`}</CodeBlock>\n </div>\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">2</span>\n <span className=\"text-sm font-semibold\">写一段 System Prompt</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 告诉大模型这个工具是做什么的、什么时候调用、结果怎么转成用户友好的反馈。\n </p>\n <CodeBlock filename=\"system-prompt.md\">{`你是一位英语口语教练。\n当用户发来录音时:\n1. 调用 chivox_voice_eval 评分\n2. 聚焦 <70 分的音素家族\n3. 用高情商话术给出练习建议`}</CodeBlock>\n </div>\n </div>\n <p className=\"text-muted-foreground\">\n 其余所有事情 —— 音频上传、鉴权、工具发现、调用时机、返回格式、结果整合 —— 都由 MCP 协议和大模型自己搞定。\n </p>\n </SubDoc>\n\n <SubDoc id=\"integration-paths\" title=\"三种典型接入姿势\">\n <p>根据你的技术栈,有三种等价的接入方式,底层都走完全相同的 MCP 协议:</p>\n\n {/* ── 姿势一:可视化 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Sparkles className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势一 · 可视化拖拽(扣子 / Coze / 飞书智能助手)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合产品经理、运营、不写后端代码的开发者。整个过程像在手机上装 App。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>找到入口</strong>:在扣子平台的 <strong>「插件 / 工具」</strong> 配置区,点击<strong>「添加 MCP 服务」</strong>。</li>\n <li><strong>填两个字段</strong>(这也是你未来会从驰声这边拿到的):\n <div className=\"mt-2 overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium w-32\">服务器地址</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权密钥</td><td className=\"py-2 px-3 font-mono text-xs\">sk-•••••••••• <span className=\"text-muted-foreground ml-2\">(由驰声分配,用于计费与识别)</span></td></tr>\n </tbody>\n </table>\n </div>\n </li>\n <li><strong>平台自动发现能力</strong>:扣子会访问你填的地址,读取 MCP Server 自描述的\"说明书\"。瞬间明白:「这是个语音评测工具,需要传音频,会返发音分」。</li>\n <li><strong>右侧测试</strong>:在平台的聊天框里发一段语音,大模型就会自动调用工具。<strong>整个过程不需要写一行代码。</strong></li>\n </ol>\n </div>\n\n {/* ── 姿势二:代码框架 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Code2 className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势二 · 代码框架配置(Dify / LangChain / 自研后端)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合做独立 APP / 小程序、用 Dify 或 LangChain 写 Agent 的极客开发者。核心就是往配置文件里加一段 JSON。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>打开配置文件</strong>(项目根目录下的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp.json</code> 或框架指定的配置路径)。</li>\n <li><strong>在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code> 下增加一个节点</strong>:\n <CodeBlock filename=\"mcp.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"env\": {\n \"API_KEY\": \"你的专属密钥\"\n }\n }\n }\n}`}</CodeBlock>\n </li>\n <li><strong>重启后端服务</strong>。大模型的底层调度器会在启动时自动连上驰声 MCP Server,把 16 种评测工具注册成<strong>随时待命</strong>的 tools。</li>\n <li><strong>触发调用</strong>:用户发来录音后,大模型看到 system prompt 的引导,自动选择合适的工具(单词/句子/段落/半开放)调用。</li>\n </ol>\n </div>\n\n {/* ── 姿势三:Function Calling 直连(分 A / B 两种做法) ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Terminal className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势三 · 原生 Function Calling(豆包 / DeepSeek / OpenAI 兼容 API)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合不用任何 Agent 框架、直接调用 LLM API 的后端开发者。豆包 1.5+ / DeepSeek / GPT / Qwen 等模型都走标准 <code className=\"bg-muted px-1 rounded text-xs font-mono\">chat.completions</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> 参数。这里又分 A / B 两种做法,<strong>B 才是生产推荐</strong>。\n </p>\n\n <Callout type=\"warning\">\n 常见疑问:<em>\"要不要手写一个 tool 定义?\"</em><br />\n — <strong>姿势一 / 二不用</strong>(Coze、Dify、火山方舟控制台、Claude Desktop 会自动从 MCP Server 拉工具列表)。<br />\n — <strong>姿势三看你选 A 还是 B</strong>:A 要手写,B 用 MCP 客户端库自动拉。\n </Callout>\n\n {/* ── 3-A:手写 tool schema ── */}\n <div className=\"mt-4 rounded-md border border-border/40 bg-muted/20 p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold\">3-A · 手写 tool schema(最快验证,不推荐生产)</p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20\">快速 Demo</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 自己写一段 JSON Schema 告诉豆包\"有个工具叫 xxx,参数长这样\",豆包返回 tool_call 后你再手动去调驰声 MCP。<strong>缺点</strong>:驰声有 16 个评测工具,你每加一个题型都要改代码。\n </p>\n <CodeBlock filename=\"3a_hardcode_tool.py\" lang=\"python\">{`import os, json\nfrom openai import OpenAI\n\nclient = OpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv('ARK_API_KEY'),\n)\n\n# ─── 手写一段 tool schema(不推荐:16 个工具就要写 16 段)───\ntools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英语单词发音评测。当用户让你评价英语发音并提供录音链接时调用。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"audio_url\": {\"type\": \"string\", \"description\": \"录音 URL\"},\n \"reference_text\": {\"type\": \"string\", \"description\": \"参考文本,如 Apple\"},\n },\n \"required\": [\"audio_url\", \"reference_text\"]\n }\n }\n}]\n\nresp = client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,拿到评测结果后温柔地反馈,不要直接报分数。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n)\n\nmsg = resp.choices[0].message\nif msg.tool_calls:\n args = json.loads(msg.tool_calls[0].function.arguments)\n # ↓↓↓ 这里手动调驰声 MCP,再把结果回填给豆包做第二趟 ↓↓↓\n # result = requests.post(\"https://speech-eval.site/mcp\", json={...})`}</CodeBlock>\n </div>\n\n {/* ── 3-B:MCP 自动发现 ── */}\n <div className=\"mt-3 rounded-md border border-foreground/30 bg-foreground/[0.03] dark:bg-white/[0.03] p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold flex items-center gap-1.5\">\n 3-B · 用 MCP 客户端库自动发现 <Sparkles className=\"h-3 w-3 text-foreground\" />\n </p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-foreground text-background\">生产推荐</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 启动时连上驰声 MCP Server,调一次 <code className=\"bg-muted px-1 rounded\">tools/list</code>,把 16 个评测工具自动转成 Function Calling 的 tools 格式。<strong>驰声加新工具,你代码不用改</strong>。这才是 MCP 协议的真正价值。\n </p>\n <CodeBlock filename=\"3b_mcp_autodiscover.py\" lang=\"python\">{`import asyncio, os\nfrom openai import AsyncOpenAI\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\n\nclient = AsyncOpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv(\"ARK_API_KEY\"),\n)\n\nasync def main():\n # ─── 1. 连驰声 MCP Server,自动拉取全部 16 个工具 ───\n async with streamablehttp_client(\n \"https://speech-eval.site/mcp\",\n headers={\"Authorization\": f\"Bearer {os.getenv('CHIVOX_KEY')}\"},\n ) as (read, write, _):\n async with ClientSession(read, write) as session:\n await session.initialize()\n mcp_tools = (await session.list_tools()).tools # ← 一行拿到全部工具\n\n # ─── 2. MCP schema → OpenAI Function Calling schema ───\n tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema, # MCP 直接给 JSON Schema\n }\n } for t in mcp_tools]\n\n # ─── 3. 正常调豆包,tools 由 MCP 动态注入 ───\n resp = await client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,收到评测结果后温柔反馈。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n )\n\n # ─── 4. 折返跑:豆包选好工具后,直接走 MCP 调驰声 ───\n msg = resp.choices[0].message\n if msg.tool_calls:\n call = msg.tool_calls[0]\n result = await session.call_tool(\n call.function.name,\n arguments=__import__(\"json\").loads(call.function.arguments),\n )\n # 把 result 回填给豆包做第二趟 chat.completions 即可\n\nasyncio.run(main())`}</CodeBlock>\n <p className=\"text-[11px] text-muted-foreground mt-3 leading-relaxed\">\n 想要更省心?直接用 <a href=\"https://github.com/modelcontextprotocol/python-sdk\" target=\"_blank\" rel=\"noreferrer\" className=\"text-foreground underline underline-offset-2\">官方 mcp python sdk</a>、<code className=\"bg-muted px-1 rounded text-xs\">mcp-use</code>、<code className=\"bg-muted px-1 rounded text-xs\">openai-agents</code>,或火山官方 <code className=\"bg-muted px-1 rounded text-xs\">Arkitect</code> 高代码 SDK,它们都把上面这段\"MCP → Function Calling\"的桥接封装好了。\n </p>\n </div>\n </div>\n\n <Callout type=\"info\">\n 三种姿势走的是完全相同的 MCP 协议,能力完全等价。选型建议:<strong>产品经理 / 原型验证用姿势一</strong>(Coze 填表单);<strong>AI 客户端用户用姿势二</strong>(Cursor / Claude Desktop 填 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">mcp.json</code>);<strong>后端生产服务用姿势三-B</strong>(MCP 客户端库自动发现工具,不要手写 tool schema)。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 架构说明 ══════ */}\n <DocSection id=\"architecture\" icon={Terminal} title=\"架构说明\">\n\n <SubDoc id=\"arch-diagram\" title=\"系统架构\">\n <div className=\"flex flex-wrap items-center justify-center gap-4 my-6\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Claude Desktop<br/>Claude Code<br/>Cursor</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">stdio</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-1\">chivox-local-mcp</p>\n <p className=\"text-[11px] text-muted-foreground\">本地代理<br/>SoX 录音</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-semibold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP / WS</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">Remote Chivox</p>\n <p className=\"text-[11px] text-muted-foreground\">MCP Server<br/>评测引擎</p>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"transport\" title=\"传输协议\">\n <ul className=\"space-y-3\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>stdio</strong> — AI 客户端通过标准输入输出与本地代理通信,这是 MCP 的标准传输方式</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>HTTP</strong> — 音频文件评测工具通过 HTTP 代理到远程服务,适用于非流式场景</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>WebSocket</strong> — 实时录音音频通过 WebSocket 流式推送,支持断线自动重连</span>\n </li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-local-mcp</strong> 是驰声语音评测的 MCP 本地代理,通过 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 将远程驰声评测服务桥接到本地,让 AI 助手(Claude Desktop、Claude Code、Cursor 等)直接调用语音评测能力。</p>\n <p>它支持两种评测模式,覆盖从即时交互到批量处理的全部场景:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Mic className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">实时录音评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">通过麦克风边录边评,音频以 WebSocket 流式推送,无需生成中间文件,延迟更低、体验更佳。</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <FolderOpen className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">音频文件评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">支持本地文件路径、Base64 编码、URL 三种输入,适合已有音频的批量评测场景。</p>\n </div>\n </div>\n <Callout type=\"info\">当前支持 <strong>16 种评测工具</strong>:英文 10 种(单词、句子、段落、纠音、自然拼读、口语选择、半开放题等)+ 中文 6 种(汉字、拼音、词句、段落、有限分支、AI Talk)。</Callout>\n </SubDoc>\n\n <SubDoc id=\"requirements\" title=\"系统要求\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">依赖</th>\n <th className=\"text-left py-2 px-3 font-medium\">版本</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-semibold\">Node.js</td><td className=\"py-2 px-3 font-mono text-xs\">{'>'}= 18</td><td className=\"py-2 px-3 text-muted-foreground\">运行时必须</td></tr>\n <tr><td className=\"py-2 px-3 font-semibold\">SoX</td><td className=\"py-2 px-3 font-mono text-xs\">any</td><td className=\"py-2 px-3 text-muted-foreground\">仅实时录音功能需要(处理麦克风音频流)</td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"mt-3\">安装 SoX(仅在使用实时录音时需要):</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# macOS\nbrew install sox\n\n# Ubuntu / Debian\nsudo apt-get install sox`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"install\" title=\"安装方式\">\n <p><strong>方式一:npm 全局安装</strong>(推荐)</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`npm install -g chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式二:npx 免安装运行</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`MCP_REMOTE_URL=http://your-server:8080 npx chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式三:从源码构建</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`git clone https://git.chivox.com/CLOUD_DEV/cvx_local_mcp.git\ncd cvx_local_mcp\nbash scripts/build.sh`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"env-vars\" title=\"环境变量\">\n <ParamTable params={[\n { name: 'MCP_REMOTE_URL', type: 'string', required: true, desc: '远程驰声 MCP 服务地址,如 http://your-server:8080' },\n { name: 'MCP_API_KEY', type: 'string', required: false, desc: 'API 认证密钥(如已配置鉴权则必填)' },\n ]} />\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 直接用(零代码) ══════ */}\n <DocSection id=\"config\" icon={Plug} title=\"接入 · 直接用 MCP(零代码)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>不用写代码、不用装 SDK,只在客户端的<strong className=\"text-foreground\">「MCP 配置」</strong>或<strong className=\"text-foreground\">「扩展」</strong>里填一段 JSON / URL</li>\n <li>保存后 LLM 自动发现 16 个评测工具,并在对话 / Agent 中自主调用</li>\n <li>覆盖 <strong className=\"text-foreground\">IDE 编程助手</strong>、<strong className=\"text-foreground\">桌面 AI 客户端</strong>、<strong className=\"text-foreground\">企业 AI 工作台</strong>、<strong className=\"text-foreground\">可视化 Bot / 工作流平台</strong>四大生态</li>\n </ul>\n </div>\n\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置并保存:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <Callout type=\"tip\">Cursor 使用远程 Streamable HTTP 模式直连,无需安装本地代理。如需实时录音功能,请使用下方 Claude Desktop 的本地代理配置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-desktop\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件(macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"chivox-local-mcp\",\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\",\n \"MCP_API_KEY\": \"your-api-key\"\n }\n }\n }\n}`}</CodeBlock>\n <p>如果是从源码构建:</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/cvx_local_mcp/dist/index.js\"],\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\"\n }\n }\n }\n}`}</CodeBlock>\n <Callout type=\"info\">Claude Desktop 使用本地代理模式(stdio),支持实时录音评测。本地代理会自动桥接远程服务。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-code\" title=\"Claude Code 配置\">\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# npm 全局安装后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 chivox-local-mcp\n\n# 源码构建后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 node /path/to/cvx_local_mcp/dist/index.js`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-ai-ide\" title=\"Windsurf / Zed / Continue 等 IDE 编程助手\">\n <p>除了 Cursor 和 Claude Code,主流的 AI 编程 IDE 都原生支持 MCP。以下客户端使用<strong>完全一致</strong>的 Streamable HTTP 配置,区别只在配置入口:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">客户端</th>\n <th className=\"text-left py-2 px-3 font-medium\">配置入口</th>\n <th className=\"text-left py-2 px-3 font-medium\">备注</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">Windsurf (Codeium)</td><td className=\"py-2 px-3 text-xs\">Settings → Cascade → MCP Servers</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">直接贴下方 JSON</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Zed</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">~/.config/zed/settings.json</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">context_servers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">Assistant Panel 自动发现</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Continue (VS Code / JetBrains)</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">config.yaml</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">v0.9+ 支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">通义灵码 / CodeGeeX / 文心快码</td><td className=\"py-2 px-3 text-xs\">插件设置 → MCP 服务</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">2026 Q1 起陆续支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Cline (原 Claude Dev)</td><td className=\"py-2 px-3 text-xs\">MCP Servers 面板 → 编辑配置</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">VS Code 扩展</td></tr>\n </tbody>\n </table>\n </div>\n <CodeBlock filename=\"通用 MCP 配置(复制即用)\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox-speech-eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n }\n }\n}`}</CodeBlock>\n <Callout type=\"tip\">所有 IDE 类客户端都走远程 Streamable HTTP,零本地依赖。需要实时录音才需切到 Claude Desktop 的本地代理模式。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-coze\" title=\"扣子(Coze)配置\">\n <p><a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子(Coze)</a>是字节跳动推出的 AI 应用开发平台,支持通过 MCP 扩展为 Agent 赋予外部工具能力。接入驰声语音评测后,你可以构建口语练习 Bot、发音诊断助手等应用。</p>\n <p className=\"font-semibold mt-4\">方式一:扣子空间 MCP 扩展(推荐)</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 <a href=\"https://space.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子空间</a>(space.coze.cn)</li>\n <li>在对话框下方点击 <strong>「添加扩展」</strong></li>\n <li>选择 <strong>「自定义」</strong> 标签,点击添加新的 MCP 服务</li>\n <li>填入以下配置信息:</li>\n </ol>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">名称</td><td className=\"py-2 px-3 font-mono text-xs\">chivox-speech-eval</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>保存后,在对话中输入 <strong>「帮我评测这段英文发音」</strong> 即可触发 Agent 调用</li>\n </ol>\n <Callout type=\"tip\">添加 MCP 扩展后,扣子空间的 Agent 会自动识别并调用评测工具。你可以在 Agent 的思考过程中看到具体的工具调用细节。</Callout>\n\n <p className=\"font-semibold mt-6\">方式二:在 Coze 开发平台的 Bot 中使用</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>进入 <a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Coze 开发平台</a>,创建或编辑 Bot</li>\n <li>在 Bot 的 <strong>「插件」</strong> 或 <strong>「工具」</strong> 面板中,添加 MCP 工具</li>\n <li>选择 <strong>Streamable HTTP</strong> 类型,填入 URL:<code className=\"bg-muted px-1 rounded text-xs font-mono\">https://speech-eval.site/mcp</code></li>\n <li>Bot 将自动发现所有可用的评测工具(如 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_word</code> 等)</li>\n <li>在 Bot 的人设提示词中,添加语音评测相关的引导语</li>\n </ol>\n <CodeBlock filename=\"Bot 人设提示词示例\">{`你是一位专业的英语口语教练。当用户上传音频并请求评测时:\n1. 使用 evaluate_english_word / evaluate_english_sentence 等工具进行评测\n2. 根据评测结果(overall、accuracy、fluency 等分数)给出专业分析\n3. 针对得分较低的维度提供具体改进建议\n4. 鼓励用户持续练习`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze-workflow\" title=\"Coze 工作流接入\">\n <p>如果你需要在 Coze 工作流中集成语音评测能力,可以通过 <strong>HTTP Request 节点</strong>调用评测 API:</p>\n\n <p className=\"font-semibold mt-4\">步骤 1:上传音频</p>\n <p>在工作流中添加一个 HTTP Request 节点,配置如下:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">方法</td><td className=\"py-2 px-3 font-mono text-xs\">POST</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/upload</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Content-Type</td><td className=\"py-2 px-3 font-mono text-xs\">audio/mp3</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Body</td><td className=\"py-2 px-3 text-muted-foreground\">音频文件二进制数据</td></tr>\n </tbody>\n </table>\n </div>\n <p>响应中获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{{audioId}}'}</code>,传递给下一步。</p>\n\n <p className=\"font-semibold mt-4\">步骤 2:调用评测</p>\n <p>添加第二个 HTTP Request 节点,通过 MCP 接口调用评测工具:</p>\n <CodeBlock filename=\"HTTP Request Body\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"evaluate_english_sentence\",\n \"arguments\": {\n \"audioId\": \"{{audioId}}\",\n \"sentence\": \"{{ref_text}}\"\n }\n },\n \"id\": 1\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">步骤 3:处理结果</p>\n <p>评测结果通过 JSON 格式返回,可使用 Coze 的 <strong>代码节点</strong> 或 <strong>LLM 节点</strong> 对结果进行解析和二次处理(如生成学习建议、计算进步趋势等)。</p>\n\n <Callout type=\"info\">工作流方式适合需要固定流程的场景(如批量评测、定时任务、与其他系统联动)。如果是交互式场景,推荐使用上方的 MCP 扩展方式,Agent 会自动选择合适的工具。</Callout>\n\n <div className=\"rounded-lg bg-muted/30 p-5 mt-4\">\n <p className=\"text-xs font-semibold mb-3\">完整工作流示意</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">用户输入音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 上传音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 调用评测</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM: 分析结果</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">输出反馈</span>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"config-ai-workspace\" title=\"AI 工作台 · 豆包 / 飞书智能伙伴 / WorkBuddy / 钉钉 AI\">\n <p>这一类是<strong>面向「上班族」的企业 / 日常 AI 助手</strong>,产品形态是个聊天框 + 插件市场 / 扩展面板。接入后,员工在 App 里打字「帮我评一下这段录音」就能自动触发驰声评测,不用写任何代码。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Bot className=\"h-4 w-4 text-amber-500\" /> 豆包(Doubao)App / 桌面版</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节跳动官方 AI 助手,2026 年起桌面版支持 MCP 扩展。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>桌面版设置 → <strong>「智能体扩展」</strong> → 添加 MCP 服务</li>\n <li>类型选 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">streamable-http</code>,URL 填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>对话中直接说「帮我评测」即可自动调用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Building2 className=\"h-4 w-4 text-blue-500\" /> 飞书智能伙伴(My AI)</p>\n <p className=\"text-xs text-muted-foreground mb-2\">飞书企业 AI,支持以 MCP / Function Calling 挂载外部工具给团队 Bot。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>飞书开发者后台 → 创建应用 → <strong>「AI 能力 / 智能伙伴」</strong></li>\n <li>在「工具」中选 <strong>外部 MCP 服务</strong>,填入上方 URL</li>\n <li>把智能伙伴拉进群聊,@ 它提交音频即可评测</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-rose-500\" /> WorkBuddy / 企业智能助手</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节 WorkBuddy、腾讯会议 AI、企业微信智能助手等企业 AI 工作台场景。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>在工作台的<strong>「技能 / 扩展」</strong>后台新增一个 MCP 连接器</li>\n <li>传输协议选 Streamable HTTP,地址填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>把技能发布到指定部门 / 群组,HR、培训、销售口语陪练场景即可直接用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Workflow className=\"h-4 w-4 text-cyan-500\" /> 钉钉 AI 助理 / 通义千问 App</p>\n <p className=\"text-xs text-muted-foreground mb-2\">阿里生态:钉钉 AI 助理支持自定义插件,通义 App 可通过「AI 工具箱」挂载 MCP。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>钉钉开发者后台 → AI 助理 → <strong>「插件开发 / MCP 接入」</strong></li>\n <li>选 HTTP Streamable,填入驰声 MCP URL 并开启鉴权(可选)</li>\n <li>在钉钉群 @AI 助理上传音频,自动返回评测卡片</li>\n </ol>\n </div>\n </div>\n\n <Callout type=\"tip\"><strong>典型场景 · 小龙虾们的共同剧本</strong>:销售口语陪练 → 外语培训 → 主持 / 播音岗位考核 → K12 英语作业批改 → 客服话术复盘。这些「上班族 AI 工作台」接入驰声 MCP 后,员工 / 学员只要对着话筒说话,AI 就能自动打分并给出个性化改进建议,运营 / 培训 / 教研团队零开发即可上线。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-workflow-platforms\" title=\"Dify / n8n / Flowise 等可视化编排\">\n <p>如果你需要把语音评测编排进<strong>多步业务流</strong>(例如:学员录音 → 评测 → 生成错题集 → 推送到 CRM),可视化工作流平台比聊天 Bot 更合适。</p>\n\n <div className=\"grid gap-3 md:grid-cols-3 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Dify</p>\n <p className=\"text-xs text-muted-foreground mb-2\">开源 LLMOps,v0.15+ 原生 MCP 工具。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>「工具」→「自定义 MCP」→ 填 URL</li>\n <li>在 Chatflow / Workflow 节点里直接拖用</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">n8n</p>\n <p className=\"text-xs text-muted-foreground mb-2\">工作流自动化,用 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">MCP Client</code> 节点。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>添加 <strong>MCP Client</strong> 节点</li>\n <li>Transport 选 HTTP Stream,URL 贴驰声地址</li>\n <li>Tool 下拉自动列出 16 个评测能力</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Flowise / LangFlow</p>\n <p className=\"text-xs text-muted-foreground mb-2\">可视化 LangChain Agent 编排。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>拖出 <strong>MCP Tool</strong> 节点</li>\n <li>连到 AgentExecutor 即可</li>\n </ul>\n </div>\n </div>\n\n <Callout type=\"info\">可视化平台的好处是<strong>流程可追溯、可回放</strong>,适合教育机构 / 培训部门做批量评测 + 报表导出;缺点是单步延迟略高于直接 MCP 调用,对延迟敏感的交互场景建议用 Cursor / Claude Desktop 直连。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"其他 MCP 客户端(通用配置)\">\n <p>任何遵循 MCP 协议的客户端都可以接入,统一使用以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权(可选)</td><td className=\"py-2 px-3 font-mono text-xs\">Header:<code>Authorization: Bearer &lt;MCP_API_KEY&gt;</code></td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">还没找到你用的客户端?任何支持 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 的终端都通用。找不到入口?下一小节「编程用 MCP」里用 SDK 直接接即可。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 编程用 MCP ══════ */}\n <DocSection id=\"config-code\" icon={Code2} title=\"接入 · 编程用 MCP(SDK / 代码集成)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Terminal className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>你在写一段<strong className=\"text-foreground\">真正的后端服务</strong>:一个 Agent Worker、一个 API、一个自动化脚本</li>\n <li>业务里的「语音评测」是其中一个步骤,需要由代码 <strong className=\"text-foreground\">决定何时调用</strong>、<strong className=\"text-foreground\">如何解析结果</strong></li>\n <li>三种主流写法:① 官方 MCP 客户端库 → ② Agent 框架自动桥接 → ③ 退化到 chat.completions + tools 参数</li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 强烈推荐①或②:驰声有 16 个评测工具,<strong className=\"text-foreground\">让 MCP 自动注入 tool schema</strong>,每新增一个题型不用改一行业务代码。</p>\n </div>\n\n <SubDoc id=\"code-mcp-sdk\" title=\"① 官方 MCP 客户端库(Python / TypeScript)\">\n <p>最底层、也最推荐的方式。由 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Anthropic 官方维护</a>,一次连接自动拿到全部工具的 JSON Schema,然后交给任意 LLM。</p>\n\n <p className=\"font-semibold mt-4\">Python:<code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp</code></p>\n <CodeBlock filename=\"chivox_client.py\" lang=\"python\">{`# pip install mcp openai\nimport asyncio, os\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nMCP_URL = \"https://speech-eval.site/mcp\"\nllm = OpenAI(api_key=os.getenv(\"DOUBAO_KEY\"),\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\") # 豆包 / DeepSeek / OpenAI 同构\n\nasync def main(audio_id: str, ref: str):\n async with streamablehttp_client(MCP_URL) as (reader, writer, _):\n async with ClientSession(reader, writer) as sess:\n await sess.initialize()\n\n tools = await sess.list_tools()\n openai_tools = [{\n \"type\": \"function\",\n \"function\": {\"name\": t.name, \"description\": t.description,\n \"parameters\": t.inputSchema},\n } for t in tools.tools]\n\n resp = llm.chat.completions.create(\n model=\"doubao-1-5-pro-32k\",\n messages=[{\"role\": \"user\",\n \"content\": f\"帮我评测 audioId={audio_id},参考文本:{ref}\"}],\n tools=openai_tools,\n )\n\n for call in resp.choices[0].message.tool_calls or []:\n result = await sess.call_tool(call.function.name,\n arguments=eval(call.function.arguments))\n print(call.function.name, \"→\", result.content[0].text[:200])\n\nasyncio.run(main(\"abc123\", \"I think therefore I am\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">TypeScript / Node.js:<code className=\"bg-muted px-1 rounded text-xs font-mono\">@modelcontextprotocol/sdk</code></p>\n <CodeBlock filename=\"chivox-client.ts\" lang=\"typescript\">{`// npm i @modelcontextprotocol/sdk openai\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport OpenAI from \"openai\";\n\nconst mcp = new Client({ name: \"chivox-biz\", version: \"1.0.0\" });\nawait mcp.connect(new StreamableHTTPClientTransport(new URL(\"https://speech-eval.site/mcp\")));\n\nconst { tools } = await mcp.listTools();\nconst openai = new OpenAI({ apiKey: process.env.DEEPSEEK_KEY, baseURL: \"https://api.deepseek.com/v1\" });\n\nconst resp = await openai.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: \"评测 audioId=abc123, 参考 I think therefore I am\" }],\n tools: tools.map(t => ({ type: \"function\", function: {\n name: t.name, description: t.description, parameters: t.inputSchema\n }})),\n});\n\nfor (const call of resp.choices[0].message.tool_calls ?? []) {\n const r = await mcp.callTool({ name: call.function.name,\n arguments: JSON.parse(call.function.arguments) });\n console.log(call.function.name, r.content);\n}`}</CodeBlock>\n <Callout type=\"tip\">这种写法 <strong>LLM 供应商可换</strong>(豆包 / DeepSeek / GPT / Qwen / Moonshot),<strong>工具自动更新</strong>(驰声新增题型无需改业务代码),适合生产后端。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-agent-frameworks\" title=\"② 用 Agent 框架自动桥接(LangChain / Mastra / Agents SDK)\">\n <p>如果项目已经在用 Agent 框架,几行 adapter 就能把驰声 MCP 塞进去,框架本身会接管 tool-calling 循环、重试、日志。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LangChain / LangGraph(Python)</p>\n <CodeBlock filename=\"agent.py\" lang=\"python\">{`# pip install langchain-mcp-adapters langgraph\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom langgraph.prebuilt import create_react_agent\n\nclient = MultiServerMCPClient({\n \"chivox\": {\n \"transport\": \"streamable_http\",\n \"url\": \"https://speech-eval.site/mcp\",\n }\n})\ntools = await client.get_tools()\n\nagent = create_react_agent(\"openai:gpt-4o-mini\", tools)\nresult = await agent.ainvoke({\"messages\":\n [(\"user\", \"评测 audioId=abc123 参考 think\")]})\nprint(result[\"messages\"][-1].content)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Mastra(TypeScript)</p>\n <CodeBlock filename=\"mastra-agent.ts\" lang=\"typescript\">{`// npm i @mastra/core @mastra/mcp\nimport { MCPClient } from \"@mastra/mcp\";\nimport { Agent } from \"@mastra/core/agent\";\nimport { openai } from \"@ai-sdk/openai\";\n\nconst mcp = new MCPClient({ servers: {\n chivox: { url: new URL(\"https://speech-eval.site/mcp\") }\n}});\n\nexport const speechCoach = new Agent({\n name: \"speech-coach\",\n instructions: \"你是口语教练,用 chivox 工具评测并给改进建议。\",\n model: openai(\"gpt-4o-mini\"),\n tools: await mcp.getTools(),\n});`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">OpenAI Agents SDK(Python)</p>\n <CodeBlock filename=\"agents_sdk.py\" lang=\"python\">{`# pip install openai-agents\nfrom agents import Agent, Runner\nfrom agents.mcp import MCPServerStreamableHttp\n\nchivox = MCPServerStreamableHttp(\n params={\"url\": \"https://speech-eval.site/mcp\"},\n name=\"chivox-speech-eval\",\n)\n\nasync with chivox:\n agent = Agent(name=\"coach\",\n instructions=\"专业口语教练\",\n mcp_servers=[chivox])\n r = await Runner.run(agent, \"评测 audioId=abc123\")\n print(r.final_output)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LlamaIndex / AutoGen / CrewAI</p>\n <p className=\"text-xs text-muted-foreground mb-2\">这些框架都有官方或社区 MCP adapter,用法与上述类似:</p>\n <ul className=\"list-disc list-inside space-y-1 text-xs\">\n <li><strong>LlamaIndex</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">llama-index-tools-mcp</code></li>\n <li><strong>AutoGen</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">autogen-ext[mcp]</code></li>\n <li><strong>CrewAI</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">crewai-tools</code> 的 MCPServerAdapter</li>\n <li><strong>Spring AI</strong>(Java):<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">spring-ai-mcp-client</code></li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"code-function-calling\" title=\"③ 直调 chat.completions · 上传与动态 tools(豆包 / DeepSeek / 等)\">\n <p>\n DeepSeek、GLM(智谱)、KIMI(Moonshot)、豆包(火山方舟)、千帆、DashScope 等若提供 <strong>OpenAI 兼容的 HTTP API</strong>,接入驰声 MCP 的套路一致:用 MCP 客户端拉取工具列表 → 转为{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> → 模型决策 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_calls</code> → MCP 执行并回填 → 再让模型生成诊断话术。\n </p>\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">① MCP list_tools()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">② 转 OpenAI tools 格式</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">③ LLM 决策 tool_calls</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">④ MCP call_tool()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">⑤ 评测结果回填 → LLM 诊断</span>\n </div>\n </div>\n <Callout type=\"tip\">各厂商仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 与 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 与 tools 桥接代码可原样复用。</Callout>\n\n <p className=\"font-semibold mt-6\">先上传音频,拿到 audioId</p>\n <p className=\"text-sm text-muted-foreground mb-3\">文件评测工具通常需要 <code className=\"bg-muted px-1 rounded text-xs font-mono\">audioId</code>(或路径参数,视工具而定)。下面是用 HTTP 上传的最小示例:</p>\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://speech-eval.site/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst res = await fetch('https://speech-eval.site/upload', {\n method: 'POST',\n headers: { 'Content-Type': 'audio/mp3' },\n body: audioData,\n});\n\nconst result = await res.json();\nconsole.log('audioId:', result.audioId);`}</CodeBlock>\n\n <p className=\"font-semibold mt-6\">再以豆包为例:MCP 动态 tools + chat.completions 折返跑</p>\n <p>当你用的 LLM SDK <strong>还没有原生 MCP 支持</strong>(比如老版本的火山方舟 / 百度千帆 / 通义 DashScope),退化方案是:用 MCP 客户端<strong>一次性拉到 tool schema</strong>,然后按 OpenAI Function Calling 格式喂给 LLM。</p>\n <CodeBlock filename=\"doubao_tools.py\" lang=\"python\">{`# 适用:豆包(火山方舟)/ DeepSeek / Qwen / Moonshot / GLM 等 OpenAI 兼容 API\n# 核心思路:MCP 列工具 → 转 OpenAI tools → LLM 决策 → MCP 执行 → 回填\n\nasync with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n\n # ① 用 MCP 拉到的 schema 自动转 OpenAI 格式\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 喂给豆包\n messages = [{\"role\": \"user\", \"content\": \"评测一下 audioId=abc123\"}]\n resp = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages, tools=oa_tools,\n )\n\n # ③ LLM 回 tool_calls → 折返跑到 MCP 执行\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for c in msg.tool_calls:\n result = await mcp.call_tool(\n c.function.name, arguments=json.loads(c.function.arguments))\n messages.append({\"role\": \"tool\", \"tool_call_id\": c.id,\n \"content\": result.content[0].text})\n # ④ 再喂一轮,让豆包根据评测结果产出自然语言诊断\n final = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages)\n print(final.choices[0].message.content)`}</CodeBlock>\n <Callout type=\"warning\"><strong>千万别手写 tool schema!</strong>驰声共 <strong>16 个评测工具</strong>(英文 10 + 中文 6),每个工具参数较多,手写既容易错也难维护。一定用 MCP <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">list_tools()</code> 动态拿。</Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-deepseek\" title=\"DeepSeek\">\n <p>\n DeepSeek 提供与 OpenAI 完全兼容的 API,当前最新模型为 <strong>DeepSeek-V3.2</strong>(2025 年 12 月发布,首个把\"思考 + 工具调用\"原生融合的模型)。\n 通过 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-chat</code> 进入<strong>非思考模式</strong>,\n 通过 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-reasoner</code> 进入<strong>思考模式</strong>,\n <strong>两种模式均支持 Function Calling</strong>,128K 上下文。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"deepseek_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# DeepSeek 客户端(OpenAI 兼容)\nclient = OpenAI(\n api_key=os.environ[\"DEEPSEEK_API_KEY\"],\n base_url=\"https://api.deepseek.com/v1\",\n)\n\nasync def evaluate_with_deepseek(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 拉取驰声 MCP 工具列表\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 调用 DeepSeek,让它选择合适的评测工具\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"deepseek-chat\", # 或 deepseek-reasoner\n messages=messages,\n tools=oa_tools,\n )\n\n # ③ 执行 tool_calls\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 让 DeepSeek 根据评测结果生成自然语言诊断\n final = client.chat.completions.create(\n model=\"deepseek-chat\",\n messages=messages,\n )\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_deepseek(\"audio_abc123\", \"Hello, nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议(V3.2):</strong>日常评测诊断用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">deepseek-chat</code>,速度快、token 消耗低;\n 需要深度分析(错误归因 / 个性化练习路径)时用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">deepseek-reasoner</code>,\n V3.2 起<strong>思考模式也能在思维链中调用工具</strong>,可以一次性完成\"调评测 → 思考 → 出诊断\",无需再切回 V3 做工具调用。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-glm\" title=\"GLM(智谱 AI / Z.ai)\">\n <p>\n 智谱 AI 当前最新旗舰为 <strong>GLM-5 / GLM-5.1</strong>(2026 年 2 月发布,744B-A40B MoE,200K 上下文,对标 Claude Opus 4.5),\n 以及上一代 <strong>GLM-4.7</strong>、<strong>GLM-4.6</strong>。\n 此外保留 <code className=\"bg-muted px-1 rounded text-xs font-mono\">glm-4-flash</code>(免费快速版)方便开发测试。\n 全部模型支持 OpenAI 兼容接口与原生 <code className=\"bg-muted px-1 rounded text-xs font-mono\">zhipuai</code> SDK,原生 Function Calling。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`ZHIPU_API_KEY=xxxxxxxx.xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式一:OpenAI 兼容接口(推荐)</p>\n <CodeBlock filename=\"glm_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(\n api_key=os.environ[\"ZHIPU_API_KEY\"],\n base_url=\"https://open.bigmodel.cn/api/paas/v4/\",\n)\n\nasync def evaluate_with_glm(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"glm-4-flash\", # 免费快速版;生产推荐 glm-5 / glm-4.7\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"glm-4-flash\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_glm(\"audio_abc123\", \"How are you today?\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式二:官方 zhipuai SDK</p>\n <CodeBlock filename=\"glm_sdk.py\" lang=\"python\">{`from zhipuai import ZhipuAI\n\nclient = ZhipuAI(api_key=os.environ[\"ZHIPU_API_KEY\"])\n\n# 其余逻辑与方式一相同,仅替换 client 实例\nresp = client.chat.completions.create(\n model=\"glm-5\", # 旗舰;也可用 glm-4.7 / glm-4-flash\n messages=messages,\n tools=oa_tools,\n)`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 开发联调用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-4-flash</code>(免费);\n 在线辅导日常诊断用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-4.7</code> 性价比最高;\n K12 / 复杂 Agent 编排(多步评测 + 个性化练习路径)用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-5</code> / <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">glm-5.1</code>,\n 200K 上下文、Agent 能力对标 Claude Opus 系列。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-kimi\" title=\"KIMI(Moonshot AI)\">\n <p>\n Moonshot AI 当前最新旗舰是 <strong>Kimi K2.5</strong>(2026 年 1 月 27 日发布,MoE 架构、<strong>256K 上下文</strong>、原生多模态、Agent 集群协作)。\n 另外提供 <code className=\"bg-muted px-1 rounded text-xs font-mono\">kimi-k2-thinking</code>(深度思考)、<code className=\"bg-muted px-1 rounded text-xs font-mono\">kimi-k2-turbo-preview</code>(高速版)。\n 上一代 <code className=\"bg-muted px-1 rounded text-xs font-mono\">moonshot-v1-8k/32k/128k</code> 仍然兼容,但<strong>推荐迁移到 kimi-k2.5</strong>。\n 全部模型 OpenAI 兼容,原生 Function Calling。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`MOONSHOT_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"kimi_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# 中国大陆用 api.moonshot.cn;海外/国际版用 api.moonshot.ai\nclient = OpenAI(\n api_key=os.environ[\"MOONSHOT_API_KEY\"],\n base_url=\"https://api.moonshot.cn/v1\",\n)\n\nasync def evaluate_with_kimi(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"kimi-k2.5\", # 旗舰;也可选 kimi-k2-turbo-preview / moonshot-v1-128k\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"kimi-k2.5\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_kimi(\"audio_abc123\", \"Nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\">\n Kimi K2.5 原生 <strong>256K 上下文 + Agent 集群协作</strong>,特别适合「多轮口语练习 + 历史分析 + 多步评测编排」场景:\n 把用户过去 30 次评测的完整 JSON 全部塞进 messages,让 K2.5 一次性生成跨多次练习的进步报告与个性化练习路径。\n 需要更深推理时使用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">kimi-k2-thinking</code>,\n 追求实时反馈用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">kimi-k2-turbo-preview</code>。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-doubao\" title=\"豆包 Seed 2.0(火山方舟)\">\n <p>\n 字节跳动 <strong>豆包 Seed 2.0</strong>(2026 年 2 月发布)是 ByteDance Seed 团队推出的新一代通用大模型,覆盖 Pro / Lite / Mini / Code 多种规格,\n 通过 <strong>火山方舟(Volcano Ark)</strong> 提供 OpenAI 兼容的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">/api/v3/chat/completions</code> 接口,\n 原生支持 <strong>Function Calling、JSON Schema 结构化输出、深度思考(Chain-of-Thought)模式</strong>,\n 接驰声 MCP 几乎无须改动业务代码。\n </p>\n\n <Callout type=\"tip\">\n 豆包 Seed 2.0 的优势在 <strong>深度思考 + 工具编排</strong>:让模型一次性规划\"先调 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code>,看完弱项再调 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.word.score</code> 进一步定位音素\",\n 适合做\"多步评测 + 自动纠音\"这类 Agent 场景。\n 默认开启思考模式,可通过 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'thinking: { type: \"disabled\" }'}</code> 关闭以节省时延。\n </Callout>\n\n <p className=\"font-semibold mt-4 mb-2\">推荐版本</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">model</th>\n <th className=\"text-left py-2 px-3 font-medium\">规格</th>\n <th className=\"text-left py-2 px-3 font-medium\">推荐用途</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-pro-260215</td><td className=\"py-2 px-3\">Pro · 旗舰</td><td className=\"py-2 px-3\">多步评测 + 深度诊断 + 复杂 Agent 编排</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-lite-260215</td><td className=\"py-2 px-3\">Lite · 平衡</td><td className=\"py-2 px-3\">在线辅导单轮评测 + 自然语言反馈,性价比首选</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-mini-260215</td><td className=\"py-2 px-3\">Mini · 极小</td><td className=\"py-2 px-3\">高并发 / 边缘部署 / 移动端实时反馈</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-code-preview-260215</td><td className=\"py-2 px-3\">Code · 预览</td><td className=\"py-2 px-3\">代码生成 / 工具编排 DSL,对评测场景非主推</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-1-8-251228</td><td className=\"py-2 px-3\">1.8 · 深度思考</td><td className=\"py-2 px-3\">支持 reasoning_effort 调档(minimal/low/medium/high),上一代深度思考强化版仍可用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-1-6-251015</td><td className=\"py-2 px-3\">1.6 · 经典</td><td className=\"py-2 px-3\">256K 上下文经典版,已上线项目可继续沿用</td></tr>\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`# 火山方舟控制台 → API Key 管理\nARK_API_KEY=ak-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · 动态 tools + 折返跑)</p>\n <CodeBlock filename=\"doubao_seed20_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# 火山方舟 OpenAI 兼容端点\nclient = OpenAI(\n api_key=os.environ[\"ARK_API_KEY\"],\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n)\n\nMODEL = \"doubao-seed-2-0-pro-260215\" # 也可换成 lite / mini\n\nasync def evaluate_with_doubao(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 把驰声 16 个评测工具的 schema 动态注入豆包\n tools = (await mcp.list_tools()).tools\n ark_tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n },\n } for t in tools]\n\n messages = [\n {\"role\": \"system\",\n \"content\": \"你是一位专业的英语口语教练,会主动调用驰声评测工具获取真实分数。\"},\n {\"role\": \"user\",\n \"content\": f\"请评测这段录音,audio_id={audio_id},参考文本:{ref_text}。\"\n f\"先做句子级评测,弱项再追加单词级定位。\"},\n ]\n\n # ② 第一轮 —— 让豆包根据 prompt 自主选工具\n resp = client.chat.completions.create(\n model=MODEL,\n messages=messages,\n tools=ark_tools,\n tool_choice=\"auto\",\n # extra_body={\"thinking\": {\"type\": \"disabled\"}}, # 需要时可关思考\n )\n\n msg = resp.choices[0].message\n messages.append(msg)\n\n # ③ 折返跑:豆包可能一次发起多个 tool_calls,全部回填\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 第二轮 —— 让豆包基于真实评测 JSON 输出诊断 / 练习建议\n final = client.chat.completions.create(\n model=MODEL,\n messages=messages,\n )\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_doubao(\n audio_id=\"audio_abc123\",\n ref_text=\"The quick brown fox jumps over the lazy dog.\",\n)))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">深度思考模式(可选)</p>\n <p className=\"text-sm text-muted-foreground\">\n 豆包 Seed 2.0 默认开启 <strong>thinking</strong> 模式,会在内部生成思维链再产出答案,\n 对\"多步评测编排 / 复杂诊断\"质量明显更好;但会增加 30%~80% 的端到端延迟。\n 在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">extra_body</code> 里可显式控制:\n </p>\n <CodeBlock filename=\"thinking_mode.py\" lang=\"python\">{`# 关闭思考(更快、适合实时反馈)\nclient.chat.completions.create(\n model=\"doubao-seed-2-0-lite-260215\",\n messages=messages,\n tools=ark_tools,\n extra_body={\"thinking\": {\"type\": \"disabled\"}},\n)\n\n# 开启思考(默认,适合 Pro 模型 + 多步 Agent)\nclient.chat.completions.create(\n model=\"doubao-seed-2-0-pro-260215\",\n messages=messages,\n tools=ark_tools,\n extra_body={\"thinking\": {\"type\": \"enabled\"}},\n)`}</CodeBlock>\n\n <Callout type=\"info\">\n <strong>选型建议:</strong>\n 课堂 / 培训类一对一辅导用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-lite</code> + 关思考;\n K12 / 考试场景需要\"多题型联动 + 升级建议\"时用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-pro</code> + 开思考;\n 移动端实时口语对话用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-mini</code> 控制成本与时延。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-openai\" title=\"OpenAI(GPT-5.1)\">\n <p>\n OpenAI 当前旗舰为 <strong>GPT-5.1</strong>(2025-11-13 发布),<strong>400K 上下文</strong>、128K 最大输出,\n 支持可调推理强度 <code className=\"bg-muted px-1 rounded text-xs font-mono\">reasoning_effort</code>(none / low / medium / high)。\n 另有 <code className=\"bg-muted px-1 rounded text-xs font-mono\">gpt-5.1-chat-latest</code>(ChatGPT 同款,128K 上下文,对话场景更优)和 <code className=\"bg-muted px-1 rounded text-xs font-mono\">gpt-5-mini</code>(性价比版)。\n 全部模型原生支持 Function Calling 与 Structured Outputs。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · GPT-5.1 + 驰声 MCP)</p>\n <CodeBlock filename=\"openai_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(api_key=os.environ[\"OPENAI_API_KEY\"])\n\nasync def evaluate_with_gpt(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [\n {\"role\": \"system\",\n \"content\": \"你是英语口语教练,必须调用驰声评测工具拿真实分数后再回答。\"},\n {\"role\": \"user\",\n \"content\": f\"请评测:audioId={audio_id},参考文本:{ref_text}\"},\n ]\n\n resp = client.chat.completions.create(\n model=\"gpt-5.1\",\n messages=messages,\n tools=oa_tools,\n tool_choice=\"auto\",\n reasoning_effort=\"medium\", # GPT-5.1 新参数:none / low / medium / high\n )\n\n msg = resp.choices[0].message\n messages.append(msg)\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n final = client.chat.completions.create(\n model=\"gpt-5.1\", messages=messages)\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_gpt(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 评测调用 + 诊断默认用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5.1</code>(reasoning_effort=&quot;medium&quot;,质量与延迟平衡);\n 纯对话/课堂讲解用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5.1-chat-latest</code>;\n 高并发批量评测降本用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gpt-5-mini</code>。\n 深度推理 / 多步 Agent 编排把 reasoning_effort 调到 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">high</code>。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-claude\" title=\"Claude(Anthropic)\">\n <p>\n Anthropic 当前最新为 <strong>Claude Opus 4.7</strong>(旗舰)、<strong>Claude Sonnet 4.6</strong>(平衡)、<strong>Claude Haiku 4.5</strong>(高速)。\n Claude 用 <strong>Messages API</strong>(与 OpenAI Chat Completions 不同),工具协议是 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_use</code> / <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_result</code> 块结构。\n 支持文本 + 视觉输入,工具 schema 用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">input_schema</code> 字段(与 OpenAI 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">parameters</code> 等价)。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · 原生 Anthropic SDK + MCP 工具桥接)</p>\n <CodeBlock filename=\"claude_chivox.py\" lang=\"python\">{`# pip install anthropic mcp\nimport os, json, asyncio\nfrom anthropic import Anthropic\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = Anthropic(api_key=os.environ[\"ANTHROPIC_API_KEY\"])\n\nasync def evaluate_with_claude(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # 把驰声 MCP 工具转成 Anthropic 风格(用 input_schema 而非 parameters)\n tools = (await mcp.list_tools()).tools\n anth_tools = [{\n \"name\": t.name,\n \"description\": t.description,\n \"input_schema\": t.inputSchema,\n } for t in tools]\n\n messages = [{\"role\": \"user\",\n \"content\": f\"请评测这段录音 audioId={audio_id},参考文本:{ref_text}\"}]\n\n # 工具循环:Claude 可能多轮 tool_use,需逐轮回填\n while True:\n resp = client.messages.create(\n model=\"claude-opus-4-7\", # 或 claude-sonnet-4-6 / claude-haiku-4-5\n max_tokens=2048,\n tools=anth_tools,\n messages=messages,\n system=\"你是专业英语口语教练,必须调用驰声评测工具获取真实分数。\",\n )\n\n # 把整段助手回复(含 tool_use 块)追加进 messages\n messages.append({\"role\": \"assistant\", \"content\": resp.content})\n\n if resp.stop_reason != \"tool_use\":\n # 没有更多工具调用,输出最终诊断\n return \"\".join(b.text for b in resp.content if b.type == \"text\")\n\n # 提取所有 tool_use 块、并行执行、把 tool_result 回填给 Claude\n tool_results = []\n for block in resp.content:\n if block.type == \"tool_use\":\n result = await mcp.call_tool(block.name, arguments=block.input)\n tool_results.append({\n \"type\": \"tool_result\",\n \"tool_use_id\": block.id,\n \"content\": result.content[0].text,\n })\n messages.append({\"role\": \"user\", \"content\": tool_results})\n\nprint(asyncio.run(evaluate_with_claude(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 深度评测诊断 + 复杂 Agent 编排用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-opus-4-7</code>(旗舰,编程/Agent 提升明显);\n 日常评测 + 性价比首选 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-sonnet-4-6</code>;\n 高并发实时反馈用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">claude-haiku-4-5</code>。\n Opus 4.5 起新增 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">effort</code> 参数(low/medium/high),medium 即可达到 Sonnet 4.5 同等质量但 token 减少 ~76%。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-gemini\" title=\"Gemini(Google)\">\n <p>\n Google 最新 <strong>Gemini 3 Flash</strong>(<code className=\"bg-muted px-1 rounded text-xs font-mono\">gemini-3-flash-preview</code>,2025-12-17)以 Flash 价位提供 Pro 级推理;\n 另有 <strong>Gemini 3.1 Pro Preview</strong>(旗舰)、<strong>Gemini 3.1 Flash-Lite Preview</strong>。\n 全部支持 Function Calling、并行调用、组合调用,原生多模态,<code className=\"bg-muted px-1 rounded text-xs font-mono\">thinking_level</code> 控制推理深度。\n 推荐用 <strong>OpenAI 兼容端点</strong>,可直接复用 OpenAI SDK 与已有的 MCP 工具桥接代码。\n </p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`GEMINI_API_KEY=AIzaSyxxxxxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式一:OpenAI 兼容端点(推荐,零迁移成本)</p>\n <CodeBlock filename=\"gemini_openai_compat.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# Gemini OpenAI 兼容端点 —— 完全复用 OpenAI SDK\nclient = OpenAI(\n api_key=os.environ[\"GEMINI_API_KEY\"],\n base_url=\"https://generativelanguage.googleapis.com/v1beta/openai/\",\n)\n\nasync def evaluate_with_gemini(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\",\n \"content\": f\"请评测:audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"gemini-3-flash-preview\", # 或 gemini-3.1-pro-preview\n messages=messages,\n tools=oa_tools,\n tool_choice=\"auto\",\n )\n msg = resp.choices[0].message\n messages.append(msg)\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n final = client.chat.completions.create(\n model=\"gemini-3-flash-preview\", messages=messages)\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_gemini(\n \"audio_abc123\", \"The quick brown fox jumps over the lazy dog.\")))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式二:原生 google-genai SDK(thinking_level / 流式工具调用)</p>\n <CodeBlock filename=\"gemini_native.py\" lang=\"python\">{`# pip install google-genai\nfrom google import genai\nfrom google.genai import types\n\nclient = genai.Client(api_key=os.environ[\"GEMINI_API_KEY\"])\n\n# 把 MCP 工具转成 Gemini function_declarations\nfn_decls = [{\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n} for t in tools]\n\nresp = client.models.generate_content(\n model=\"gemini-3-flash-preview\",\n contents=f\"评测 audioId={audio_id},参考文本:{ref_text}\",\n config=types.GenerateContentConfig(\n tools=[types.Tool(function_declarations=fn_decls)],\n # Gemini 3 起:thinking_level 替代旧的 thinking_budget\n # thinking_level=\"high\",\n ),\n)\n\n# tool_call 在 response.candidates[0].content.parts[0].function_call\nfc = resp.candidates[0].content.parts[0].function_call\nprint(f\"call: {fc.name}({dict(fc.args)}) id={fc.id}\")`}</CodeBlock>\n <Callout type=\"tip\">\n <strong>选型建议:</strong>\n 日常评测用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3-flash-preview</code>(速度比 2.5 Pro 快 3 倍、价格 ~$0.50/M 输入),已是 Gemini App 默认;\n 复杂多步 Agent / 编程任务用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3.1-pro-preview</code>;\n 超大上下文 + 多模态批处理用同系列的 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">gemini-3.1-flash-lite-preview</code>。\n </Callout>\n <Callout type=\"info\">\n <strong>注意:</strong>Gemini 3 起每次 function_call 自动带 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">id</code>,多轮工具调用必须把 id 透传回 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">function_response</code>,否则会被拒绝(OpenAI 兼容端点已自动处理)。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-comparison\" title=\"模型对比速查\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead>\n <tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 pr-4 font-medium\">模型</th>\n <th className=\"text-left py-2 pr-4 font-medium\">推荐版本</th>\n <th className=\"text-left py-2 pr-4 font-medium\">base_url</th>\n <th className=\"text-left py-2 pr-4 font-medium\">Function Calling</th>\n <th className=\"text-left py-2 font-medium\">适合场景</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek V3.2 · 非思考</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-chat</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">评测调用 + 诊断,性价比最高</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek V3.2 · 思考</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-reasoner</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(思考中可调工具)</td>\n <td className=\"py-2\">深度分析 + 错因归因 + 个性化练习路径</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-5 · 旗舰</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-5</code> / <code className=\"bg-muted px-1 rounded font-mono\">glm-5.1</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">200K 长上下文 + Agent 编排,复杂诊断</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4.7</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4.7</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">在线辅导日常诊断,性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4-Flash</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4-flash</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">免费额度,开发联调</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2.5(旗舰)</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2.5</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">256K + 多模态 + Agent 集群协作</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2 Thinking</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2-thinking</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">多步推理 + 多轮历史进度报告</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Kimi K2 Turbo</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">kimi-k2-turbo-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高速响应版,实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Pro</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-pro-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(含深度思考)</td>\n <td className=\"py-2\">多步评测编排 + 深度诊断 Agent</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Lite</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-lite-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">在线辅导 + 单轮诊断,性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Mini</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-mini-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">移动端 / 边缘部署 / 低延迟实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5.1(旗舰)</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5.1</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(reasoning_effort 可调)</td>\n <td className=\"py-2\">400K 上下文 + 深度推理 + 复杂 Agent 编排</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5.1 Chat</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5.1-chat-latest</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">ChatGPT 同款,对话 / 课堂讲解</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GPT-5 mini</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gpt-5-mini</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.openai.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高并发 / 批量评测降本</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Opus 4.7</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-opus-4-7</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(tool_use 协议)</td>\n <td className=\"py-2\">旗舰,编程 / Agent / 深度诊断</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Sonnet 4.6</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-sonnet-4-6</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">日常评测 + 性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Claude Haiku 4.5</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">claude-haiku-4-5</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.anthropic.com/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">高并发实时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Gemini 3 Flash</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gemini-3-flash-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">…/v1beta/openai/</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(并行 / 组合)</td>\n <td className=\"py-2\">Pro 级推理 + Flash 价位,Gemini App 默认</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">Gemini 3.1 Pro Preview</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">gemini-3.1-pro-preview</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">…/v1beta/openai/</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(thinking_level 可调)</td>\n <td className=\"py-2\">复杂多步 Agent + 编程任务</td>\n </tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"info\">\n 除 <strong>Claude</strong> 用 Anthropic 原生 SDK(<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">anthropic</code> 包,<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">tool_use</code> / <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">tool_result</code> 协议)外,其余所有模型均可通过 <strong>OpenAI Python SDK</strong>(<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">openai</code> 包)调用,仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 与 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 侧代码<strong>无需改动</strong>。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"code-selfhosted-agent\" title=\"④ 自研后端 Agent(FastAPI / NestJS / Spring)\">\n <p>大部分落地场景其实是:<strong>你已有一个业务后端</strong>(教育 SaaS / 培训系统 / 客服平台),只需要新增一个「语音评测」能力。推荐架构:</p>\n\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">前端录音 / 上传</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">业务后端</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">MCP Client(常驻连接)</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">驰声 MCP</span>\n <span className=\"text-muted-foreground\">↘</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM(诊断 / 出题)</span>\n </div>\n </div>\n\n <p className=\"font-semibold mt-2\">FastAPI 示例(生产最小骨架)</p>\n <CodeBlock filename=\"app.py\" lang=\"python\">{`from fastapi import FastAPI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\nfrom contextlib import asynccontextmanager\n\nmcp_session: ClientSession | None = None\n\n@asynccontextmanager\nasync def lifespan(app):\n global mcp_session\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as s:\n await s.initialize()\n mcp_session = s\n yield\n mcp_session = None\n\napp = FastAPI(lifespan=lifespan)\n\n@app.post(\"/api/evaluate\")\nasync def evaluate(audio_id: str, ref_text: str):\n # 直接选一个最合适的工具执行(不经 LLM,延迟最低)\n r = await mcp_session.call_tool(\"evaluate_english_sentence\",\n {\"audioId\": audio_id, \"sentence\": ref_text})\n return {\"result\": r.content[0].text}`}</CodeBlock>\n\n <Callout type=\"tip\"><strong>关键优化</strong>:① MCP 连接在 lifespan 里做成长连接,不要每次请求都重连;② 对延迟敏感的场景可以「跳过 LLM 直接调 tool」,把 LLM 放到「生成诊断报告」那一步。</Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mt-6 text-sm\">\n <p className=\"font-semibold mb-2\">方案选型速查</p>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 font-medium\">场景</th>\n <th className=\"text-left py-2 font-medium\">推荐方案</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2\">AI 对话 / Copilot 类产品,LLM 供应商可能会换</td><td className=\"py-2\">① 官方 MCP 客户端库</td></tr>\n <tr><td className=\"py-2\">已有 LangChain / Mastra / Agents SDK 技术栈</td><td className=\"py-2\">② Agent 框架 + adapter</td></tr>\n <tr><td className=\"py-2\">火山方舟 / 千帆控制台直连,最小侵入</td><td className=\"py-2\">③ chat.completions + tools</td></tr>\n <tr><td className=\"py-2\">教育 SaaS / 培训 / 客服等业务后端集成</td><td className=\"py-2\">④ 自研后端 Agent</td></tr>\n </tbody>\n </table>\n </div>\n </div>\n </DocSection>\n\n {/* ══════ 评测模式 ══════ */}\n <DocSection id=\"eval-modes\" icon={Radio} title=\"评测模式\">\n\n <SubDoc id=\"stream-eval\" title=\"实时录音评测(边录边评)\">\n <p>通过本地麦克风实时录音,音频以 WebSocket 流式推送到评测服务,<strong>无需生成中间文件</strong>。这是最灵活、延迟最低的评测方式,特别适合交互式口语练习场景。</p>\n <Callout type=\"tip\">实时录音评测需要安装 <strong>SoX</strong> 并使用本地代理模式(Claude Desktop / Claude Code)。Cursor 等远程直连模式暂不支持实时录音。</Callout>\n\n <p className=\"font-semibold mt-4\">需要按顺序调用三个工具:</p>\n\n <div className=\"mt-3 space-y-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">create_stream_session</code> — 创建流式评测会话</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.word.score、en.sent.score、cn.sent.raw' },\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式,默认 mp3' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100,默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code>,后续步骤需使用。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">start_recording</code> — 开始麦克风录音</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '来自 create_stream_session 的会话 ID' },\n { name: 'chunk_size', type: 'number', required: false, desc: '音频块大小(bytes),默认 3200' },\n { name: 'chunk_interval_ms', type: 'number', required: false, desc: '推送间隔(ms),默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">调用后自动启动麦克风,音频通过 WebSocket 实时推送到评测服务。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">stop_recording</code> — 停止录音并获取结果</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '会话 ID' },\n { name: 'timeout', type: 'number', required: false, desc: '等待评测结果超时秒数,默认 30' },\n ]} />\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"stream-flow\" title=\"流式评测完整流程\">\n <div className=\"rounded-lg border border-border/60 p-5 bg-muted/10 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong>用户:</strong>&quot;请评测我的英文发音,文本是 Hello&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">create_stream_session(core_type=&quot;en.word.score&quot;, ref_text=&quot;Hello&quot;)</code></p>\n <p className=\"text-muted-foreground\">→ 获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">session_id</code></p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p>AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">start_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground mt-1\">麦克风开始录音,音频通过 WebSocket 实时推送到评测服务</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>用户</strong>说完后告知 AI &quot;结束&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">stop_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground\">→ 返回评测结果(总分、准确度、流利度、音素得分等)</p>\n </FlowStep>\n </div>\n </SubDoc>\n\n <SubDoc id=\"file-eval\" title=\"音频文件评测\">\n <p>适用于已有音频文件的评测场景,支持三种输入方式:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">适用场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_file_path</td><td className=\"py-2 px-3\">本地文件路径</td><td className=\"py-2 px-3 text-muted-foreground\">代理自动读取并转为 Base64,最便捷</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_base64</td><td className=\"py-2 px-3\">Base64 编码数据</td><td className=\"py-2 px-3 text-muted-foreground\">已有编码数据时使用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_url</td><td className=\"py-2 px-3\">音频文件 URL</td><td className=\"py-2 px-3 text-muted-foreground\">音频托管在云存储时使用</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"tip\">推荐使用 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">audio_file_path</code>,只需传入本地文件路径,代理会自动处理编码和上传。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"tools-en\" title=\"英文评测工具(10 种)\">\n <ToolTable tools={[\n ['en_word_eval', '单词评测', '评测 \"Hello\" 的发音准确度'],\n ['en_word_correction', '单词纠音', '检测多读/漏读/错读并给出纠正建议'],\n ['en_vocab_eval', '词语评测', '同时评测多个单词的发音'],\n ['en_sentence_eval', '句子评测', '评测整句朗读的准确度与流利度'],\n ['en_sentence_correction', '句子纠音', '逐词检测发音问题并给出修正建议'],\n ['en_paragraph_eval', '段落朗读评测', '评测整段英文课文的朗读质量'],\n ['en_phonics_eval', '自然拼读评测', '评测 Phonics 拼读规则掌握程度'],\n ['en_choice_eval', '口语选择题', '从预设选项中识别口语回答'],\n ['en_semi_open_eval', '半开放题评测', '场景对话等半开放口语题型'],\n ['en_realtime_eval', '实时朗读评测', '实时反馈朗读质量,逐句评分'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong>所有英文评测工具均支持以下参数。</p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tools-cn\" title=\"中文评测工具(6 种)\">\n <ToolTable tools={[\n ['cn_word_raw_eval', '单字评测(汉字)', '评测 \"中\" 的发音'],\n ['cn_word_pinyin_eval', '单字评测(拼音)', '评测 \"zhōng\" 的发音'],\n ['cn_sentence_eval', '词句评测', '评测 \"你好世界\" 的朗读'],\n ['cn_paragraph_eval', '段落朗读评测', '评测一段中文课文的朗读质量'],\n ['cn_rec_eval', '有限分支识别评测', '从预设选项中识别用户发音'],\n ['cn_aitalk_eval', 'AI Talk 口语评测', '中文口语对话能力评测'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong></p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本(汉字或拼音)' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结构化结果:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"integrity\": 95,\n \"details\": [\n {\n \"char\": \"hello\",\n \"score\": 85,\n \"phone\": [\n { \"phoneme\": \"h\", \"score\": 90 },\n { \"phoneme\": \"ɛ\", \"score\": 82 },\n { \"phoneme\": \"l\", \"score\": 88 },\n { \"phoneme\": \"oʊ\", \"score\": 80 }\n ]\n }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall', '0–100', '综合总分,反映整体语音质量'],\n ['accuracy', '0–100', '准确度,音素级别的发音准确率'],\n ['pron', '0–100', '发音得分,整体发音质量评价'],\n ['fluency.overall', '0–100', '流利度,语流的自然度与平滑度'],\n ['fluency.speed', '0–100', '语速得分,每分钟词/音节计量'],\n ['fluency.pause', 'number', '不自然停顿次数'],\n ['integrity', '0–100', '完整度,是否完整朗读了参考文本'],\n ['details[].phone[]', 'array', '每个音素的独立评分,可定位具体弱项'],\n ['details[].stress[]', 'array', '重音得分,检测重弱读是否正确'],\n ].map(([field, range, desc]) => (\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期', '音频上传后 5 分钟内有效,评测完成后自动删除', '重新上传音频'],\n ['服务器繁忙', '当前请求过多,排队已满', '稍后重试,使用指数退避策略'],\n ['存储空间已满', '服务器临时音频存储已满', '稍后重试,系统会自动清理'],\n ['音频格式不支持', '上传了不支持的文件类型', '使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限', '超过该题型最大时长限制', '裁剪音频后重试'],\n ['WebSocket 连接断开', '网络不稳定导致流式连接中断', '代理会自动重连,如持续失败请检查网络'],\n ].map(([scene, reason, fix]) => (\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p className=\"text-sm text-muted-foreground\">\n 下面这些模板与{' '}\n <Link href=\"/demo\" className=\"underline underline-offset-2\">免费体验</Link>{' '}\n Demo 中实际使用的「教练 Prompt」一脉相承,全部围绕 <strong>角色 / 任务 / 方法 / 输出格式 / 语气</strong> 五段式结构,并使用占位符{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code> 直接拼接 MCP 工具返回的 JSON。\n 推荐在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">system</code> 消息里挂载,再把工具返回原文塞进\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">user</code> 消息,由 LLM 基于真实分数和音素生成可读诊断 / 练习材料。\n </p>\n\n <Callout type=\"tip\">\n <strong>使用建议:</strong>\n <ul className=\"list-disc list-inside mt-1 space-y-1\">\n <li>把 MCP 工具调用的完整 JSON(含 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">overall / dp_type / phonemes</code> 等字段)原样塞入 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,不要预先做汇总——细节越完整诊断越具体。</li>\n <li>「诊断」类模板让 LLM 输出 Markdown 给最终用户阅读;「练习」类模板要求 LLM 输出严格 JSON 给前端渲染。两步分开调用,效果远好于一次性混合。</li>\n <li>题型决定 prompt:单词题用音素级、句子题加韵律、段落题加 chunk / 整体性,半开放题套四维评级。下面按这 6 类分别给出。</li>\n </ul>\n </Callout>\n\n {/* ─── A. 诊断类 · 输出 Markdown 给学习者阅读 ─── */}\n <h4 className=\"text-sm font-semibold mt-6 mb-3 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> A. 二次诊断 · 输出 Markdown\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Sparkle className=\"h-4 w-4\" /> 1. 单词级 · 音素诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.word.score</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code>。围绕 <code className=\"bg-muted px-1 rounded font-mono\">phonemes[].dp_type</code> 找出错读音。\n </p>\n <CodeBlock filename=\"prompt-word-diagnosis.txt\">{`你是一位专业的英语发音教练。下面是驰声 MCP(en.word.score)返回的单词级评测数据:\n一个目标词、总分与发音分、以及带有 dp_type 的音素数组。\n\n## 任务\n为学习者写一份 **可直接阅读** 的发音诊断,每一条都要引用音素分数(写出 IPA 符号)。\n\n## 方法\n- 按 dp_type 分组:先看 mispron,再补充 normal 但分数 < 70 的\"边缘音\"。\n- 每个问题音素:① 典型母语干扰 → ② 你听到的声音 → ③ 一条具体的发音修正动作(下颌 / 舌位 / 气流 / 唇形)。\n- 重音符号 ˈ 标记的音节,重音错位也要单独列出(即便音素本身分数尚可)。\n- 最多列 4 条,按 **影响力从弱到强** 排序。\n\n## 输出\n精简 Markdown:可选一句总结 → 编号列表 → 单行 **🎯 优先级** 排序。\n\n## 语气\n友善、具体、可执行,不要空喊\"加油\"。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> 2. 句子级 · 节奏 + 重音诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.sent.score</code>。开头先给一行\"计分牌\",再从 phones → words → prosody。\n </p>\n <CodeBlock filename=\"prompt-sentence-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.sent.score)返回的句子级数据:refText、总分、发音子分(accuracy / integrity / fluency / rhythm)、可选 speed(WPM),以及 details[] 中的逐词得分、dp_type 与可选 phonemes[]。\n\n## 任务\n开头给一行紧凑的 **计分牌**(总分 + 各子分 + 速度),再按 **音素 → 词 → 韵律** 顺序展开诊断。\n\n## 检查清单(每项都要覆盖;不适用则写\"无明显问题\")\n1. **音素级**:每个 score < 70 或 dp_type ≠ normal 的词,展开 phonemes,命名典型错误(如 /ð/ → /d/)并给一条修正动作。\n2. **漏读 / 多读**:标注 dp_type=omit / insert,解释如何拉低 integrity。\n3. **韵律**:基于 rhythm + 错读词的重音,指出重音错位、陈述句调型平直、断句过多等。\n4. **流利度 vs 准确度**:若 fluency 高但 accuracy 低,明确指出,让学习者知道优先改哪一项。\n\n## 输出格式\nMarkdown 子标题:**Pronunciation / Prosody / Study priority**(最后一行简短排序,按 ROI 从高到低)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <FileText className=\"h-4 w-4\" /> 3. 段落级 · 音素族归并 + 完整度\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code>。把同类错音合并为\"音素族\",避免逐词复述。\n </p>\n <CodeBlock filename=\"prompt-paragraph-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.pred.score)返回的段落级数据:长 refText、总分、发音子分、可选 speed / integrity,以及大量 details[](同一个词可能在不同子句中重复出现;空 char + dp_type=omit 表示漏读)。\n\n## 任务\n对整段做 **整体诊断**:\n(a) 完整度 / 漏读,\n(b) 反复出现的 **音素族**,\n(c) 节奏与断句,\n(d) 各子分之间是否相互印证。\n\n## 检查清单\n1. **integrity / dp_type**:列出每个 omit / insert,并给出所在子句;解释长读时为什么虚词容易被吞。\n2. **音素聚类**:将弱词按共同音素问题归族(/θ/ 链、/e/ 链、/ɒ/ 等),引用 details 中的实例。\n3. **韵律**:结合 rhythm 与标点 —— 短语内停顿过多?子句边界缺失?\n4. **整体一致性**:若 fluency 正常但 accuracy 差(或反之),点明并指出对练习设计的影响。\n\n## 输出格式\nMarkdown 章节:**Summary / Completeness / Pronunciation (families) / Rhythm / Study priorities**(最后一项最多 4 条,按影响力排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <GraduationCap className=\"h-4 w-4\" /> 4. 半开放题 · IELTS 风格四维评级\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pqan.score</code>。结合学习者 transcript + 四维分数,给出可教学的\"升档建议\"。\n </p>\n <CodeBlock filename=\"prompt-semi-open.txt\">{`你是一位 **IELTS / 课堂口语风格** 的口语教练。下面是驰声 MCP(en.pqan.score)返回的半开放题数据:题目 refText、学习者 transcript、overall、四个维度分(grammar / content / fluency / pron)、speed(WPM)、以及结构化的 issues[] 标记。\n\n## 任务\n给出 **基于 rubric 的诊断**:先用一句话给出 0–100 的整体段位估计,再 **逐维度** 解释,并 **引用 transcript 中的短句**(不要复述全文)。最后给 3–5 条 **可执行的\"升档建议\"**(句型 / 模板,而不是\"多练一点\")。\n\n## 权重(要明确告诉学习者)\n- **Content ~30%** — 想法深度、举例、具体程度\n- **Grammar ~25%** — 句型多样性、主谓一致、从句\n- **Fluency ~25%** — 语速、停顿、口头禅、衔接\n- **Pronunciation ~20%** — 音素 + 可懂度\n\n## 跨维度检查\n- 若 content 最低,先给\"展开模板\",不要先抠音素。\n- 若 fluency 低 + WPM 低,区分\"思考型停顿\"与\"发音卡顿\"。\n- 若 pron 标出特定音,绑定到 transcript 里 **真实出现过的词**。\n\n## 输出格式\nMarkdown:**段位估计** → 紧凑的 **得分表格** → **\"To reach the next band\"** 编号列表(每条给出可仿写的句型)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Languages className=\"h-4 w-4\" /> 5. 中文专项 · 声调 / 儿化 / 变调\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.sent.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.pred.raw</code>。中文评测最大权重在 **声调**,必须单独检查。\n </p>\n <CodeBlock filename=\"prompt-mandarin-coach.txt\">{`你是一位专业的普通话口语教练。下面是驰声 MCP(cn.sent.raw)返回的中文句子评测数据:整句分数 + 逐字 details + 可选 phonemes + 流利 / 节奏分。\n\n## 中文评测的权重\n- **声调错误权重最高** —— 声调读错可能直接改变词义\n- dp_type=mispron 多来自:声调错、平翘舌混用、前后鼻音混用\n- 拼音对、声调错,听感仍是\"洋腔洋调\"\n\n## 检查清单(缺则写\"无明显问题\")\n1. **儿化**:phonemes 出现 r(儿化) 或 dp_type 提示时,确认是否被读成\"主音节 + 单独儿\"两拍。\n2. **轻声**:叠词、词缀、句末语气词 —— 第二字应短促轻读,无完整声调曲线。\n3. **变调**:三声连读、不 + 后字、一 + 后字,是否符合普通话规则。\n4. **节奏**:用 rhythm + 逗号位置,标记停顿过长或过碎,建议 chunk 边界。\n\n## 输出\nMarkdown 子标题:**总览 / 儿化 / 轻声 / 变调 / 节奏 / 学习优先级**(最后一项 1–3 条,按\"听感自然度提升\"排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── B. 练习类 · 输出 JSON 给前端渲染 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <Mic className=\"h-4 w-4\" /> B. 微练习生成 · 输出严格 JSON\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 6. 单词 / 句子 · 微练习\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 建议在 <strong>诊断完成后</strong> 第二轮调用:让 LLM 严格按弱项音素生成,避免\"通用绕口令包\"。\n </p>\n <CodeBlock filename=\"prompt-micro-drills.txt\">{`你仍是这位学习者的发音教练。**第二轮诊断已经完成** —— 现在严格基于诊断里点名的弱音素,生成 **1:1 对应** 的微练习(不要给通用题)。\n\n## 输出(仅返回一段合法 JSON 数组,外层不要任何文字)\n根节点必须是数组,每个元素含 category(字符串)、icon(单个 emoji)、items({ \"label\", \"content\" } 数组)。\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme drills\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Awareness\", \"icon\": \"🗣️\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Whole word\", \"icon\": \"📝\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme drills**:诊断里每个 mispron 音素至少给一对最小对立对 + 一句短绕口令 / 重复句。\n- **Awareness**:当重音偏弱时,给一条 stress / 舌位 / 下颌姿态提示。\n- **Whole word**:2–3 句自然句,目标词分别出现在句首 / 句中 / 句末。\n- 所有 content 必须可念出声;字符串内不要再嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n原始 MCP:\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Notebook className=\"h-4 w-4\" /> 7. 段落 · Chunk Map + 元认知\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 段落级练习要从\"音素\"上升到\"短语 / 整段 / 元认知录回放\"。直接对应 <code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code> 的诊断结果。\n </p>\n <CodeBlock filename=\"prompt-paragraph-drills.txt\">{`你仍是这位学习者的口语教练。**段落级二次诊断** 已经列出了漏读位置和音素族 —— 现在请生成 **整段练习**:把弱音素串成\"族\"练习、用 chunk map 把虚词粘回短语,并附一条录音 → 回放 → 对照 MCP details 的元认知步骤。\n\n## 输出(仅返回一段合法 JSON 数组)\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme families\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Chunking\", \"icon\": \"🎵\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Metacognition\", \"icon\": \"🧠\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme families**:至少 3 条,按诊断里的音素族(/θ/、/e/、/ɒ/ 等)组织最小对立对 / 词链。\n- **Chunking**:① 给一行用 [ ] 和 / 标注的 chunk map(可加大致停顿秒数);② 给一句\"把漏掉的虚词嵌入短语\"的练习(不要孤立练)。\n- **Metacognition**:一条\"录音 → 回放 → 对照 MCP details\"的 N 遍练习(N 写明,2–3 之间)。\n- 字符串只能是纯展示文本,禁止嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── C. 跨题 / 运营 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" /> C. 跨题 / 运营\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Gauge className=\"h-4 w-4\" /> 8. 多日学习报告\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 把过去 N 天的 MCP 评测结果聚合后丢给 LLM,输出适合发给学生 / 家长的周报。\n </p>\n <CodeBlock filename=\"prompt-weekly-report.txt\">{`你是一位英语口语学习顾问。下面是这位学习者过去 7 天的驰声 MCP 评测历史(按时间顺序),每条包含题型、coreType、overall、子分、weak_phonemes 摘要。\n\n## 任务\n为学习者生成一份 **周报**,目标读者:本人 + 家长 / 老师。\n强调 **趋势** 与 **下一步计划**,不要复述每一次具体分数。\n\n## 必须包含\n1. **整体趋势**:进步 / 持平 / 退步(一句话 + 简短依据)\n2. **维度变化**:accuracy / fluency / rhythm / integrity / speed 任意有数据的维度,给出\"本周均分 vs 上周\"\n3. **本周高光**:分数提升最大的题型 / 音素 / 句型\n4. **顽固问题**:连续 3 次以上未达 70 的音素或维度\n5. **下周计划**:3 条具体动作(每条对应一个 MCP 工具或一个练习类型)\n6. **结尾**:1–2 句鼓励性总结,避免空话\n\n## 风格\n- 像一位认真但不啰嗦的私教\n- 出现学生姓名时使用占位 {student_name}\n- 数字一律保留整数\n\n## 评测历史\n\\`\\`\\`json\n{history}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 9. 错误归因 · JSON 摘要\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 用于后端聚合 / 数据看板:让 LLM 把一次评测压缩成结构化摘要,方便入库统计。\n </p>\n <CodeBlock filename=\"prompt-error-summary.txt\">{`你是一个评测结果归类器。下面是驰声 MCP 一次评测的完整返回。请抽取关键信息并 **仅返回合法 JSON**(不要任何解释文字)。\n\n## Schema\n\\`\\`\\`json\n{\n \"core_type\": \"string,例如 en.sent.score\",\n \"overall\": 0,\n \"sub_scores\": { \"accuracy\": 0, \"integrity\": 0, \"fluency\": 0, \"rhythm\": 0 },\n \"weak_phonemes\": [\n { \"ipa\": \"string\", \"avg_score\": 0, \"occur\": 0, \"example_word\": \"string\" }\n ],\n \"omissions\": [{ \"after\": \"string\", \"missing\": \"string\" }],\n \"stress_issues\": [{ \"word\": \"string\", \"expected\": \"string\", \"actual\": \"string\" }],\n \"primary_issue_tag\": \"phoneme|stress|fluency|integrity|content|none\",\n \"next_action_hint\": \"string,10 字以内,例如 \\\"练 /θ/ 对立对\\\"\"\n}\n\\`\\`\\`\n\n## 规则\n- weak_phonemes 只收 avg_score < 70 的项,最多 5 项,按 occur 降序。\n- 不要复述 refText,不要 markdown。\n- 字段缺失时使用 null 或空数组,不要省略 key。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n <Callout type=\"info\">\n <strong>怎么接到 MCP 工具调用?</strong>\n <br />\n 以「直调 chat.completions + 动态 tools」为例:第一轮 LLM 调出 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code> 工具,把工具 result(即 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">tool</code> 消息内容)原样填入上面任意「诊断」模板的{' '}\n <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,作为第二轮 user 消息发回;拿到诊断结论再走第三轮,套「微练习」模板得到 JSON。三轮调用即可串起 <strong>评测 → 诊断 → 练习</strong> 全链路。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频过期:</strong>上传后 5 分钟内有效。建议在上传后 3 分钟内完成评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>服务繁忙:</strong>使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>录音中断:</strong>WebSocket 断线后代理会自动重连。如持续失败,检查网络连接和 SoX 安装。</span></li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>优先使用流式评测:</strong>实时录音模式跳过了文件上传环节,端到端延迟比文件评测低 30-50%。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频压缩:</strong>文件评测时,上传前将音频转为 16kHz 单声道 mp3,可减少 60-80% 的上传体积。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>选择合适的工具:</strong>单词评测比段落评测快 3-5 倍。如只需单词级分数,不要用句子评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>本地文件路径最便捷:</strong>使用 <code className=\"bg-muted px-1 rounded text-xs\">audio_file_path</code> 参数传入本地路径,代理自动处理编码,无需手动转 Base64。</span></li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Globe} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP JSON-RPC 接口(Streamable HTTP)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制', '500MB', '最大音频存储空间'],\n ['最大排队', '10', '等待处理的请求数'],\n ['最大并发', '3', '同时处理的评测数'],\n ['音频有效期', '5 分钟', '上传后未评测自动过期'],\n ['文件大小限制', '50MB', '单个音频文件最大'],\n ].map(([k, v, d]) => (\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">\n 上表为托管侧<strong>技术默认</strong>;计费与试用/商用<strong>调用配额、并发档位</strong>以{' '}\n <Link href=\"/dashboard/plans\" className=\"text-foreground underline underline-offset-2\">\n 会员套餐\n </Link>\n 说明为准,与上表独立。\n </p>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <Callout type=\"info\">\n <strong>当前线上版本:v1.0.0</strong>(网站未正式上线)。以下变更项为开发阶段规划记录,以正式发布公告为准。\n </Callout>\n <div className=\"space-y-3 mt-4\">\n {[\n { ver: 'v1.0.0', date: 'TBD', desc: '当前线上版本(网站未正式上线)' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </DocsShell>\n );\n}\n\nexport default function DocsPage() {\n const locale = useLocale();\n return locale.startsWith('zh') ? <DocsPageZh /> : <DocsPageEn />;\n}\n"}, "5376ba40d9fd235425e84c7e70b98cc70fccee41": {"t": 1785765796.7490954, "n": 122951, "h": "b1230ec5d58ea199", "c": "'use client';\n\nimport { useLocale } from 'next-intl';\nimport { Link } from '@/i18n/routing';\nimport { DocsShell, type NavGroup } from './docs-shell';\nimport {\n CodeBlock,\n DocSection,\n SubDoc,\n ParamTable,\n Callout,\n FlowStep,\n ToolTable,\n} from './docs-shared';\nimport { DocsEnglishBody } from './docs-content-en';\nimport {\n BookOpen,\n Code2,\n Zap,\n FileText,\n Terminal,\n Globe,\n Mic,\n MessageSquare,\n BarChart3,\n AlertTriangle,\n Radio,\n FolderOpen,\n Workflow,\n Sparkles,\n Plug,\n Bot,\n Building2,\n Check,\n Sparkle,\n Languages,\n GraduationCap,\n Notebook,\n Gauge,\n ListChecks,\n} from 'lucide-react';\n\n/* ─── Navigation Structure ─── */\n\nconst NAV = [\n { id: 'concept', icon: Workflow, label: 'MCP 工作原理', children: [\n { id: 'why-mcp', label: '为什么需要 MCP' },\n { id: 'scenario-walkthrough', label: '场景走查 · 6 步' },\n { id: 'dev-responsibility', label: '开发者只做两件事' },\n { id: 'integration-paths', label: '三种接入姿势' },\n ]},\n { id: 'architecture', icon: Terminal, label: '架构说明', children: [\n { id: 'arch-diagram', label: '架构图' },\n { id: 'transport', label: '传输协议' },\n ]},\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'requirements', label: '系统要求' },\n { id: 'install', label: '安装方式' },\n { id: 'env-vars', label: '环境变量' },\n ]},\n { id: 'config', icon: Plug, label: '接入 · 直接用(零代码)', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: '扣子(Coze)' },\n { id: 'config-coze-workflow', label: 'Coze 工作流' },\n { id: 'config-ai-workspace', label: 'AI 工作台 · 豆包 / 飞书 / WorkBuddy / 钉钉' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: '其他 MCP 客户端' },\n ]},\n { id: 'config-code', icon: Code2, label: '接入 · 编程用(SDK / 代码)', children: [\n { id: 'code-mcp-sdk', label: '官方 MCP 客户端库' },\n { id: 'code-agent-frameworks', label: 'LangChain / Mastra / Agents SDK' },\n { id: 'code-function-calling', label: '直调 chat.completions · 上传与动态 tools' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM(智谱)' },\n { id: 'llm-kimi', label: 'KIMI(Moonshot)' },\n { id: 'llm-doubao', label: '豆包 Seed 2.0(火山方舟)' },\n { id: 'llm-comparison', label: '模型对比速查' },\n { id: 'code-selfhosted-agent', label: '自研后端 Agent' },\n ]},\n { id: 'eval-modes', icon: Radio, label: '评测模式', children: [\n { id: 'stream-eval', label: '实时录音评测' },\n { id: 'stream-flow', label: '流式评测流程' },\n { id: 'file-eval', label: '音频文件评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'tools-en', label: '英文评测工具' },\n { id: 'tools-cn', label: '中文评测工具' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n { id: 'service-info', icon: Globe, label: '服务信息', children: [\n { id: 'endpoints', label: '服务端点' },\n { id: 'limits', label: '服务限制' },\n { id: 'changelog', label: '更新日志' },\n ]},\n];\n\nconst NAV_EN: NavGroup[] = [\n { id: 'concept', icon: Workflow, label: 'How MCP works', children: [\n { id: 'why-mcp', label: 'Why MCP' },\n { id: 'scenario-walkthrough', label: 'Scenario · 6 steps' },\n { id: 'dev-responsibility', label: 'Two developer tasks' },\n { id: 'integration-paths', label: 'Three integration styles' },\n ]},\n { id: 'architecture', icon: Terminal, label: 'Architecture', children: [\n { id: 'arch-diagram', label: 'Diagram' },\n { id: 'transport', label: 'Transports' },\n ]},\n { id: 'quick-start', icon: Zap, label: 'Quick start', children: [\n { id: 'overview', label: 'Overview' },\n { id: 'requirements', label: 'Requirements' },\n { id: 'install', label: 'Install' },\n { id: 'env-vars', label: 'Environment' },\n ]},\n { id: 'config', icon: Plug, label: 'Zero-code clients', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: 'Coze' },\n { id: 'config-coze-workflow', label: 'Coze workflows' },\n { id: 'config-ai-workspace', label: 'Enterprise AI workspaces' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: 'Other MCP clients' },\n ]},\n { id: 'config-code', icon: Code2, label: 'SDK / code integration', children: [\n { id: 'code-mcp-sdk', label: 'Official MCP SDK' },\n { id: 'code-agent-frameworks', label: 'Agent frameworks' },\n { id: 'code-function-calling', label: 'chat.completions · upload & tools' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM (Zhipu)' },\n { id: 'llm-kimi', label: 'KIMI (Moonshot)' },\n { id: 'llm-doubao', label: 'Doubao Seed 2.0 (Volcano Ark)' },\n { id: 'llm-comparison', label: 'Model comparison' },\n { id: 'code-selfhosted-agent', label: 'Custom backend' },\n ]},\n { id: 'eval-modes', icon: Radio, label: 'Evaluation modes', children: [\n { id: 'stream-eval', label: 'Streaming mic' },\n { id: 'stream-flow', label: 'Streaming flow' },\n { id: 'file-eval', label: 'Audio files' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API reference', children: [\n { id: 'tools-en', label: 'English tools' },\n { id: 'tools-cn', label: 'Chinese tools' },\n { id: 'result-fields', label: 'Result fields' },\n { id: 'error-codes', label: 'Errors' },\n ]},\n { id: 'best-practices', icon: FileText, label: 'Best practices', children: [\n { id: 'prompt-templates', label: 'Prompt templates' },\n { id: 'error-handling', label: 'Errors & retries' },\n { id: 'performance', label: 'Performance' },\n ]},\n { id: 'service-info', icon: Globe, label: 'Service info', children: [\n { id: 'endpoints', label: 'Endpoints' },\n { id: 'limits', label: 'Limits' },\n { id: 'changelog', label: 'Changelog' },\n ]},\n];\n\n/* ─── Page(英文 / 中文拆成两个组件,避免切换语言时 Hooks 数量变化导致白屏)─── */\n\nfunction DocsPageEn() {\n return (\n <DocsShell\n nav={NAV_EN}\n backLabel=\"Back to Home\"\n title=\"Developer documentation\"\n subtitle=\"Chivox Speech Evaluation MCP · chivox-local-mcp\"\n >\n <DocsEnglishBody />\n </DocsShell>\n );\n}\n\nfunction DocsPageZh() {\n return (\n <DocsShell\n nav={NAV}\n backLabel=\"返回首页\"\n title=\"开发者文档\"\n subtitle=\"驰声语音评测 MCP · chivox-local-mcp\"\n >\n {/* ══════ MCP 工作原理 ══════ */}\n <DocSection id=\"concept\" icon={Workflow} title=\"MCP 工作原理\">\n\n <SubDoc id=\"why-mcp\" title=\"为什么需要 MCP\">\n <p>\n 大模型(GPT / Claude / Gemini / 通义千问 等)本质上是<strong>文字大脑</strong>:它擅长读和写,但天生<strong>听不到</strong>音频、<strong>打不开</strong>你的数据库、<strong>调不通</strong>第三方 API。\n </p>\n <p>\n <strong>MCP(Model Context Protocol)</strong>是一套由 Anthropic 主导、业界共同推进的开放协议,它像一根\"对讲机\":让大模型可以在需要时主动去调用外部能力(工具、数据源、服务),然后把结果接回来继续对话。\n </p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <AlertTriangle className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n <span className=\"text-sm font-semibold\">没有 MCP 的做法</span>\n </div>\n <ul className=\"text-xs text-muted-foreground space-y-1.5 leading-relaxed\">\n <li>· 研究评测服务的 REST 文档</li>\n <li>· 自己写鉴权 / 上传 / 轮询代码</li>\n <li>· 把结果序列化后塞进 prompt</li>\n <li>· 每换一个大模型都得重写一遍</li>\n </ul>\n </div>\n <div className=\"rounded-lg border-2 border-foreground/60 p-4 bg-foreground/[0.02]\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Check className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">用驰声 MCP 之后</span>\n </div>\n <ul className=\"text-xs space-y-1.5 leading-relaxed\">\n <li>· 填一行 MCP Server 地址</li>\n <li>· 大模型自动发现&amp;调用评测工具</li>\n <li>· 换任何支持 MCP 的大模型都能用</li>\n <li>· 不写一行调用代码</li>\n </ul>\n </div>\n </div>\n <Callout type=\"info\">\n MCP 让\"接入语音评测\"从一个\"后端工程\"变成了一个\"配置项\"。这也是为什么 Cursor、Claude Desktop、Dify、Coze、字节豆包、飞书智能助手都已经原生支持 MCP。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"scenario-walkthrough\" title=\"场景走查 · 从一段录音到温柔反馈\">\n <p>\n 想象一个具体场景:一位开发者用 Claude 做了一款 <strong>「AI 雅思口语陪练」小程序</strong>,接入驰声语音评测 MCP。当用户发来一段录音,整个链路是这样的:\n </p>\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-5 my-4 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong className=\"text-foreground\">配置阶段</strong>(开发者 · 一次性)</p>\n <p className=\"text-muted-foreground mt-1\">在 AI 后端的配置文件里填一行驰声 MCP Server 地址。大模型启动时自动发现:「我多了一个叫<strong>语音评测</strong>的工具」。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong className=\"text-foreground\">接收用户指令</strong>(终端用户)</p>\n <p className=\"text-muted-foreground mt-1\">小程序用户发来一段自我介绍录音:「帮我听听,发音哪里有问题?」后端把「文字 + 录音链接」打包给大模型。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong className=\"text-foreground\">大模型自主思考</strong>(LLM · 最核心的魔法)</p>\n <div className=\"text-muted-foreground mt-1 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed\">\n 「用户让我听发音。」<br />\n 「但我只是文字大脑,我听不到。」<br />\n 「等等 —— 开发者给了我一个叫 <code className=\"bg-muted px-1 rounded\">chivox_voice_eval</code> 的 MCP 工具!」\n </div>\n </FlowStep>\n <FlowStep title=\"4\">\n <p><strong className=\"text-foreground\">MCP 协议 · 自动调用</strong>(LLM → 驰声)</p>\n <p className=\"text-muted-foreground mt-1\">大模型自动按 MCP 标准构造 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools/call</code> 请求,把录音链接和参考文本发到驰声服务。<span className=\"text-foreground\">开发者不需要写任何调用代码。</span></p>\n </FlowStep>\n <FlowStep title=\"5\">\n <p><strong className=\"text-foreground\">驰声服务处理</strong>(评测引擎)</p>\n <p className=\"text-muted-foreground mt-1\">驰声对音频进行考试级打分,把结构化结果通过 MCP 原路返回:<code className=\"bg-muted px-1 rounded text-xs font-mono\">overall 85</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">/θ/ 音发音错误</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">流利度 88</code>。</p>\n </FlowStep>\n <FlowStep title=\"6\">\n <p><strong className=\"text-foreground\">大模型整合并输出反馈</strong>(LLM)</p>\n <p className=\"text-muted-foreground mt-1\">大模型把冷冰冰的分数翻译成温柔反馈:</p>\n <div className=\"text-foreground/80 mt-2 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed italic\">\n 「你的自我介绍很棒!综合 85 分 🎉 不过 <strong>think</strong> 这个词的 /θ/ 咬舌音不太标准,我们一起来练一下……」\n </div>\n </FlowStep>\n </div>\n <Callout type=\"tip\">\n <strong>整条链路里,MCP 只出现在 ① 和 ④</strong>:① 让开发者一次性告诉大模型「你多了一个工具」,④ 让大模型可以自动调用它。这两步的标准化,就是 MCP 的全部价值。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"dev-responsibility\" title=\"开发者只做两件事\">\n <p>听起来复杂,但对开发者来说只需要两件事:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">1</span>\n <span className=\"text-sm font-semibold\">填一次 MCP 配置</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 在 AI 后端的配置里填入驰声 MCP Server 地址和 API Key,就这一次,以后新加评测能力完全免开发。\n </p>\n <CodeBlock filename=\"mcp-config.json\" lang=\"json\">{`{\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"apiKey\": \"sk-••••••••••\"\n }\n}`}</CodeBlock>\n </div>\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">2</span>\n <span className=\"text-sm font-semibold\">写一段 System Prompt</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 告诉大模型这个工具是做什么的、什么时候调用、结果怎么转成用户友好的反馈。\n </p>\n <CodeBlock filename=\"system-prompt.md\">{`你是一位英语口语教练。\n当用户发来录音时:\n1. 调用 chivox_voice_eval 评分\n2. 聚焦 <70 分的音素家族\n3. 用高情商话术给出练习建议`}</CodeBlock>\n </div>\n </div>\n <p className=\"text-muted-foreground\">\n 其余所有事情 —— 音频上传、鉴权、工具发现、调用时机、返回格式、结果整合 —— 都由 MCP 协议和大模型自己搞定。\n </p>\n </SubDoc>\n\n <SubDoc id=\"integration-paths\" title=\"三种典型接入姿势\">\n <p>根据你的技术栈,有三种等价的接入方式,底层都走完全相同的 MCP 协议:</p>\n\n {/* ── 姿势一:可视化 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Sparkles className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势一 · 可视化拖拽(扣子 / Coze / 飞书智能助手)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合产品经理、运营、不写后端代码的开发者。整个过程像在手机上装 App。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>找到入口</strong>:在扣子平台的 <strong>「插件 / 工具」</strong> 配置区,点击<strong>「添加 MCP 服务」</strong>。</li>\n <li><strong>填两个字段</strong>(这也是你未来会从驰声这边拿到的):\n <div className=\"mt-2 overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium w-32\">服务器地址</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权密钥</td><td className=\"py-2 px-3 font-mono text-xs\">sk-•••••••••• <span className=\"text-muted-foreground ml-2\">(由驰声分配,用于计费与识别)</span></td></tr>\n </tbody>\n </table>\n </div>\n </li>\n <li><strong>平台自动发现能力</strong>:扣子会访问你填的地址,读取 MCP Server 自描述的\"说明书\"。瞬间明白:「这是个语音评测工具,需要传音频,会返发音分」。</li>\n <li><strong>右侧测试</strong>:在平台的聊天框里发一段语音,大模型就会自动调用工具。<strong>整个过程不需要写一行代码。</strong></li>\n </ol>\n </div>\n\n {/* ── 姿势二:代码框架 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Code2 className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势二 · 代码框架配置(Dify / LangChain / 自研后端)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合做独立 APP / 小程序、用 Dify 或 LangChain 写 Agent 的极客开发者。核心就是往配置文件里加一段 JSON。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>打开配置文件</strong>(项目根目录下的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp.json</code> 或框架指定的配置路径)。</li>\n <li><strong>在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code> 下增加一个节点</strong>:\n <CodeBlock filename=\"mcp.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"env\": {\n \"API_KEY\": \"你的专属密钥\"\n }\n }\n }\n}`}</CodeBlock>\n </li>\n <li><strong>重启后端服务</strong>。大模型的底层调度器会在启动时自动连上驰声 MCP Server,把 16 种评测工具注册成<strong>随时待命</strong>的 tools。</li>\n <li><strong>触发调用</strong>:用户发来录音后,大模型看到 system prompt 的引导,自动选择合适的工具(单词/句子/段落/半开放)调用。</li>\n </ol>\n </div>\n\n {/* ── 姿势三:Function Calling 直连(分 A / B 两种做法) ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Terminal className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势三 · 原生 Function Calling(豆包 / DeepSeek / OpenAI 兼容 API)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合不用任何 Agent 框架、直接调用 LLM API 的后端开发者。豆包 1.5+ / DeepSeek / GPT / Qwen 等模型都走标准 <code className=\"bg-muted px-1 rounded text-xs font-mono\">chat.completions</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> 参数。这里又分 A / B 两种做法,<strong>B 才是生产推荐</strong>。\n </p>\n\n <Callout type=\"warning\">\n 常见疑问:<em>\"要不要手写一个 tool 定义?\"</em><br />\n — <strong>姿势一 / 二不用</strong>(Coze、Dify、火山方舟控制台、Claude Desktop 会自动从 MCP Server 拉工具列表)。<br />\n — <strong>姿势三看你选 A 还是 B</strong>:A 要手写,B 用 MCP 客户端库自动拉。\n </Callout>\n\n {/* ── 3-A:手写 tool schema ── */}\n <div className=\"mt-4 rounded-md border border-border/40 bg-muted/20 p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold\">3-A · 手写 tool schema(最快验证,不推荐生产)</p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20\">快速 Demo</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 自己写一段 JSON Schema 告诉豆包\"有个工具叫 xxx,参数长这样\",豆包返回 tool_call 后你再手动去调驰声 MCP。<strong>缺点</strong>:驰声有 16 个评测工具,你每加一个题型都要改代码。\n </p>\n <CodeBlock filename=\"3a_hardcode_tool.py\" lang=\"python\">{`import os, json\nfrom openai import OpenAI\n\nclient = OpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv('ARK_API_KEY'),\n)\n\n# ─── 手写一段 tool schema(不推荐:16 个工具就要写 16 段)───\ntools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英语单词发音评测。当用户让你评价英语发音并提供录音链接时调用。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"audio_url\": {\"type\": \"string\", \"description\": \"录音 URL\"},\n \"reference_text\": {\"type\": \"string\", \"description\": \"参考文本,如 Apple\"},\n },\n \"required\": [\"audio_url\", \"reference_text\"]\n }\n }\n}]\n\nresp = client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,拿到评测结果后温柔地反馈,不要直接报分数。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n)\n\nmsg = resp.choices[0].message\nif msg.tool_calls:\n args = json.loads(msg.tool_calls[0].function.arguments)\n # ↓↓↓ 这里手动调驰声 MCP,再把结果回填给豆包做第二趟 ↓↓↓\n # result = requests.post(\"https://speech-eval.site/mcp\", json={...})`}</CodeBlock>\n </div>\n\n {/* ── 3-B:MCP 自动发现 ── */}\n <div className=\"mt-3 rounded-md border border-foreground/30 bg-foreground/[0.03] dark:bg-white/[0.03] p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold flex items-center gap-1.5\">\n 3-B · 用 MCP 客户端库自动发现 <Sparkles className=\"h-3 w-3 text-foreground\" />\n </p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-foreground text-background\">生产推荐</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 启动时连上驰声 MCP Server,调一次 <code className=\"bg-muted px-1 rounded\">tools/list</code>,把 16 个评测工具自动转成 Function Calling 的 tools 格式。<strong>驰声加新工具,你代码不用改</strong>。这才是 MCP 协议的真正价值。\n </p>\n <CodeBlock filename=\"3b_mcp_autodiscover.py\" lang=\"python\">{`import asyncio, os\nfrom openai import AsyncOpenAI\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\n\nclient = AsyncOpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv(\"ARK_API_KEY\"),\n)\n\nasync def main():\n # ─── 1. 连驰声 MCP Server,自动拉取全部 16 个工具 ───\n async with streamablehttp_client(\n \"https://speech-eval.site/mcp\",\n headers={\"Authorization\": f\"Bearer {os.getenv('CHIVOX_KEY')}\"},\n ) as (read, write, _):\n async with ClientSession(read, write) as session:\n await session.initialize()\n mcp_tools = (await session.list_tools()).tools # ← 一行拿到全部工具\n\n # ─── 2. MCP schema → OpenAI Function Calling schema ───\n tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema, # MCP 直接给 JSON Schema\n }\n } for t in mcp_tools]\n\n # ─── 3. 正常调豆包,tools 由 MCP 动态注入 ───\n resp = await client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,收到评测结果后温柔反馈。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n )\n\n # ─── 4. 折返跑:豆包选好工具后,直接走 MCP 调驰声 ───\n msg = resp.choices[0].message\n if msg.tool_calls:\n call = msg.tool_calls[0]\n result = await session.call_tool(\n call.function.name,\n arguments=__import__(\"json\").loads(call.function.arguments),\n )\n # 把 result 回填给豆包做第二趟 chat.completions 即可\n\nasyncio.run(main())`}</CodeBlock>\n <p className=\"text-[11px] text-muted-foreground mt-3 leading-relaxed\">\n 想要更省心?直接用 <a href=\"https://github.com/modelcontextprotocol/python-sdk\" target=\"_blank\" rel=\"noreferrer\" className=\"text-foreground underline underline-offset-2\">官方 mcp python sdk</a>、<code className=\"bg-muted px-1 rounded text-xs\">mcp-use</code>、<code className=\"bg-muted px-1 rounded text-xs\">openai-agents</code>,或火山官方 <code className=\"bg-muted px-1 rounded text-xs\">Arkitect</code> 高代码 SDK,它们都把上面这段\"MCP → Function Calling\"的桥接封装好了。\n </p>\n </div>\n </div>\n\n <Callout type=\"info\">\n 三种姿势走的是完全相同的 MCP 协议,能力完全等价。选型建议:<strong>产品经理 / 原型验证用姿势一</strong>(Coze 填表单);<strong>AI 客户端用户用姿势二</strong>(Cursor / Claude Desktop 填 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">mcp.json</code>);<strong>后端生产服务用姿势三-B</strong>(MCP 客户端库自动发现工具,不要手写 tool schema)。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 架构说明 ══════ */}\n <DocSection id=\"architecture\" icon={Terminal} title=\"架构说明\">\n\n <SubDoc id=\"arch-diagram\" title=\"系统架构\">\n <div className=\"flex flex-wrap items-center justify-center gap-4 my-6\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Claude Desktop<br/>Claude Code<br/>Cursor</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">stdio</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-1\">chivox-local-mcp</p>\n <p className=\"text-[11px] text-muted-foreground\">本地代理<br/>SoX 录音</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP / WS</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">Remote Chivox</p>\n <p className=\"text-[11px] text-muted-foreground\">MCP Server<br/>评测引擎</p>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"transport\" title=\"传输协议\">\n <ul className=\"space-y-3\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>stdio</strong> — AI 客户端通过标准输入输出与本地代理通信,这是 MCP 的标准传输方式</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>HTTP</strong> — 音频文件评测工具通过 HTTP 代理到远程服务,适用于非流式场景</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>WebSocket</strong> — 实时录音音频通过 WebSocket 流式推送,支持断线自动重连</span>\n </li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-local-mcp</strong> 是驰声语音评测的 MCP 本地代理,通过 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 将远程驰声评测服务桥接到本地,让 AI 助手(Claude Desktop、Claude Code、Cursor 等)直接调用语音评测能力。</p>\n <p>它支持两种评测模式,覆盖从即时交互到批量处理的全部场景:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Mic className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">实时录音评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">通过麦克风边录边评,音频以 WebSocket 流式推送,无需生成中间文件,延迟更低、体验更佳。</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <FolderOpen className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">音频文件评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">支持本地文件路径、Base64 编码、URL 三种输入,适合已有音频的批量评测场景。</p>\n </div>\n </div>\n <Callout type=\"info\">当前支持 <strong>16 种评测工具</strong>:英文 10 种(单词、句子、段落、纠音、自然拼读、口语选择、半开放题等)+ 中文 6 种(汉字、拼音、词句、段落、有限分支、AI Talk)。</Callout>\n </SubDoc>\n\n <SubDoc id=\"requirements\" title=\"系统要求\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">依赖</th>\n <th className=\"text-left py-2 px-3 font-medium\">版本</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-semibold\">Node.js</td><td className=\"py-2 px-3 font-mono text-xs\">{'>'}= 18</td><td className=\"py-2 px-3 text-muted-foreground\">运行时必须</td></tr>\n <tr><td className=\"py-2 px-3 font-semibold\">SoX</td><td className=\"py-2 px-3 font-mono text-xs\">any</td><td className=\"py-2 px-3 text-muted-foreground\">仅实时录音功能需要(处理麦克风音频流)</td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"mt-3\">安装 SoX(仅在使用实时录音时需要):</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# macOS\nbrew install sox\n\n# Ubuntu / Debian\nsudo apt-get install sox`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"install\" title=\"安装方式\">\n <p><strong>方式一:npm 全局安装</strong>(推荐)</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`npm install -g chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式二:npx 免安装运行</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`MCP_REMOTE_URL=http://your-server:8080 npx chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式三:从源码构建</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`git clone https://git.chivox.com/CLOUD_DEV/cvx_local_mcp.git\ncd cvx_local_mcp\nbash scripts/build.sh`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"env-vars\" title=\"环境变量\">\n <ParamTable params={[\n { name: 'MCP_REMOTE_URL', type: 'string', required: true, desc: '远程驰声 MCP 服务地址,如 http://your-server:8080' },\n { name: 'MCP_API_KEY', type: 'string', required: false, desc: 'API 认证密钥(如已配置鉴权则必填)' },\n ]} />\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 直接用(零代码) ══════ */}\n <DocSection id=\"config\" icon={Plug} title=\"接入 · 直接用 MCP(零代码)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>不用写代码、不用装 SDK,只在客户端的<strong className=\"text-foreground\">「MCP 配置」</strong>或<strong className=\"text-foreground\">「扩展」</strong>里填一段 JSON / URL</li>\n <li>保存后 LLM 自动发现 16 个评测工具,并在对话 / Agent 中自主调用</li>\n <li>覆盖 <strong className=\"text-foreground\">IDE 编程助手</strong>、<strong className=\"text-foreground\">桌面 AI 客户端</strong>、<strong className=\"text-foreground\">企业 AI 工作台</strong>、<strong className=\"text-foreground\">可视化 Bot / 工作流平台</strong>四大生态</li>\n </ul>\n </div>\n\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置并保存:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <Callout type=\"tip\">Cursor 使用远程 Streamable HTTP 模式直连,无需安装本地代理。如需实时录音功能,请使用下方 Claude Desktop 的本地代理配置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-desktop\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件(macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"chivox-local-mcp\",\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\",\n \"MCP_API_KEY\": \"your-api-key\"\n }\n }\n }\n}`}</CodeBlock>\n <p>如果是从源码构建:</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/cvx_local_mcp/dist/index.js\"],\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\"\n }\n }\n }\n}`}</CodeBlock>\n <Callout type=\"info\">Claude Desktop 使用本地代理模式(stdio),支持实时录音评测。本地代理会自动桥接远程服务。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-code\" title=\"Claude Code 配置\">\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# npm 全局安装后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 chivox-local-mcp\n\n# 源码构建后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 node /path/to/cvx_local_mcp/dist/index.js`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-ai-ide\" title=\"Windsurf / Zed / Continue 等 IDE 编程助手\">\n <p>除了 Cursor 和 Claude Code,主流的 AI 编程 IDE 都原生支持 MCP。以下客户端使用<strong>完全一致</strong>的 Streamable HTTP 配置,区别只在配置入口:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">客户端</th>\n <th className=\"text-left py-2 px-3 font-medium\">配置入口</th>\n <th className=\"text-left py-2 px-3 font-medium\">备注</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">Windsurf (Codeium)</td><td className=\"py-2 px-3 text-xs\">Settings → Cascade → MCP Servers</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">直接贴下方 JSON</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Zed</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">~/.config/zed/settings.json</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">context_servers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">Assistant Panel 自动发现</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Continue (VS Code / JetBrains)</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">config.yaml</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">v0.9+ 支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">通义灵码 / CodeGeeX / 文心快码</td><td className=\"py-2 px-3 text-xs\">插件设置 → MCP 服务</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">2026 Q1 起陆续支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Cline (原 Claude Dev)</td><td className=\"py-2 px-3 text-xs\">MCP Servers 面板 → 编辑配置</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">VS Code 扩展</td></tr>\n </tbody>\n </table>\n </div>\n <CodeBlock filename=\"通用 MCP 配置(复制即用)\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox-speech-eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n }\n }\n}`}</CodeBlock>\n <Callout type=\"tip\">所有 IDE 类客户端都走远程 Streamable HTTP,零本地依赖。需要实时录音才需切到 Claude Desktop 的本地代理模式。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-coze\" title=\"扣子(Coze)配置\">\n <p><a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子(Coze)</a>是字节跳动推出的 AI 应用开发平台,支持通过 MCP 扩展为 Agent 赋予外部工具能力。接入驰声语音评测后,你可以构建口语练习 Bot、发音诊断助手等应用。</p>\n <p className=\"font-semibold mt-4\">方式一:扣子空间 MCP 扩展(推荐)</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 <a href=\"https://space.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子空间</a>(space.coze.cn)</li>\n <li>在对话框下方点击 <strong>「添加扩展」</strong></li>\n <li>选择 <strong>「自定义」</strong> 标签,点击添加新的 MCP 服务</li>\n <li>填入以下配置信息:</li>\n </ol>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">名称</td><td className=\"py-2 px-3 font-mono text-xs\">chivox-speech-eval</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>保存后,在对话中输入 <strong>「帮我评测这段英文发音」</strong> 即可触发 Agent 调用</li>\n </ol>\n <Callout type=\"tip\">添加 MCP 扩展后,扣子空间的 Agent 会自动识别并调用评测工具。你可以在 Agent 的思考过程中看到具体的工具调用细节。</Callout>\n\n <p className=\"font-semibold mt-6\">方式二:在 Coze 开发平台的 Bot 中使用</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>进入 <a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Coze 开发平台</a>,创建或编辑 Bot</li>\n <li>在 Bot 的 <strong>「插件」</strong> 或 <strong>「工具」</strong> 面板中,添加 MCP 工具</li>\n <li>选择 <strong>Streamable HTTP</strong> 类型,填入 URL:<code className=\"bg-muted px-1 rounded text-xs font-mono\">https://speech-eval.site/mcp</code></li>\n <li>Bot 将自动发现所有可用的评测工具(如 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_word</code> 等)</li>\n <li>在 Bot 的人设提示词中,添加语音评测相关的引导语</li>\n </ol>\n <CodeBlock filename=\"Bot 人设提示词示例\">{`你是一位专业的英语口语教练。当用户上传音频并请求评测时:\n1. 使用 evaluate_english_word / evaluate_english_sentence 等工具进行评测\n2. 根据评测结果(overall、accuracy、fluency 等分数)给出专业分析\n3. 针对得分较低的维度提供具体改进建议\n4. 鼓励用户持续练习`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze-workflow\" title=\"Coze 工作流接入\">\n <p>如果你需要在 Coze 工作流中集成语音评测能力,可以通过 <strong>HTTP Request 节点</strong>调用评测 API:</p>\n\n <p className=\"font-semibold mt-4\">步骤 1:上传音频</p>\n <p>在工作流中添加一个 HTTP Request 节点,配置如下:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">方法</td><td className=\"py-2 px-3 font-mono text-xs\">POST</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/upload</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Content-Type</td><td className=\"py-2 px-3 font-mono text-xs\">audio/mp3</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Body</td><td className=\"py-2 px-3 text-muted-foreground\">音频文件二进制数据</td></tr>\n </tbody>\n </table>\n </div>\n <p>响应中获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{{audioId}}'}</code>,传递给下一步。</p>\n\n <p className=\"font-semibold mt-4\">步骤 2:调用评测</p>\n <p>添加第二个 HTTP Request 节点,通过 MCP 接口调用评测工具:</p>\n <CodeBlock filename=\"HTTP Request Body\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"evaluate_english_sentence\",\n \"arguments\": {\n \"audioId\": \"{{audioId}}\",\n \"sentence\": \"{{ref_text}}\"\n }\n },\n \"id\": 1\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">步骤 3:处理结果</p>\n <p>评测结果通过 JSON 格式返回,可使用 Coze 的 <strong>代码节点</strong> 或 <strong>LLM 节点</strong> 对结果进行解析和二次处理(如生成学习建议、计算进步趋势等)。</p>\n\n <Callout type=\"info\">工作流方式适合需要固定流程的场景(如批量评测、定时任务、与其他系统联动)。如果是交互式场景,推荐使用上方的 MCP 扩展方式,Agent 会自动选择合适的工具。</Callout>\n\n <div className=\"rounded-lg bg-muted/30 p-5 mt-4\">\n <p className=\"text-xs font-semibold mb-3\">完整工作流示意</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">用户输入音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 上传音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 调用评测</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM: 分析结果</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">输出反馈</span>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"config-ai-workspace\" title=\"AI 工作台 · 豆包 / 飞书智能伙伴 / WorkBuddy / 钉钉 AI\">\n <p>这一类是<strong>面向「上班族」的企业 / 日常 AI 助手</strong>,产品形态是个聊天框 + 插件市场 / 扩展面板。接入后,员工在 App 里打字「帮我评一下这段录音」就能自动触发驰声评测,不用写任何代码。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Bot className=\"h-4 w-4 text-amber-500\" /> 豆包(Doubao)App / 桌面版</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节跳动官方 AI 助手,2026 年起桌面版支持 MCP 扩展。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>桌面版设置 → <strong>「智能体扩展」</strong> → 添加 MCP 服务</li>\n <li>类型选 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">streamable-http</code>,URL 填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>对话中直接说「帮我评测」即可自动调用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Building2 className=\"h-4 w-4 text-blue-500\" /> 飞书智能伙伴(My AI)</p>\n <p className=\"text-xs text-muted-foreground mb-2\">飞书企业 AI,支持以 MCP / Function Calling 挂载外部工具给团队 Bot。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>飞书开发者后台 → 创建应用 → <strong>「AI 能力 / 智能伙伴」</strong></li>\n <li>在「工具」中选 <strong>外部 MCP 服务</strong>,填入上方 URL</li>\n <li>把智能伙伴拉进群聊,@ 它提交音频即可评测</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-rose-500\" /> WorkBuddy / 企业智能助手</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节 WorkBuddy、腾讯会议 AI、企业微信智能助手等企业 AI 工作台场景。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>在工作台的<strong>「技能 / 扩展」</strong>后台新增一个 MCP 连接器</li>\n <li>传输协议选 Streamable HTTP,地址填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>把技能发布到指定部门 / 群组,HR、培训、销售口语陪练场景即可直接用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Workflow className=\"h-4 w-4 text-cyan-500\" /> 钉钉 AI 助理 / 通义千问 App</p>\n <p className=\"text-xs text-muted-foreground mb-2\">阿里生态:钉钉 AI 助理支持自定义插件,通义 App 可通过「AI 工具箱」挂载 MCP。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>钉钉开发者后台 → AI 助理 → <strong>「插件开发 / MCP 接入」</strong></li>\n <li>选 HTTP Streamable,填入驰声 MCP URL 并开启鉴权(可选)</li>\n <li>在钉钉群 @AI 助理上传音频,自动返回评测卡片</li>\n </ol>\n </div>\n </div>\n\n <Callout type=\"tip\"><strong>典型场景 · 小龙虾们的共同剧本</strong>:销售口语陪练 → 外语培训 → 主持 / 播音岗位考核 → K12 英语作业批改 → 客服话术复盘。这些「上班族 AI 工作台」接入驰声 MCP 后,员工 / 学员只要对着话筒说话,AI 就能自动打分并给出个性化改进建议,运营 / 培训 / 教研团队零开发即可上线。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-workflow-platforms\" title=\"Dify / n8n / Flowise 等可视化编排\">\n <p>如果你需要把语音评测编排进<strong>多步业务流</strong>(例如:学员录音 → 评测 → 生成错题集 → 推送到 CRM),可视化工作流平台比聊天 Bot 更合适。</p>\n\n <div className=\"grid gap-3 md:grid-cols-3 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Dify</p>\n <p className=\"text-xs text-muted-foreground mb-2\">开源 LLMOps,v0.15+ 原生 MCP 工具。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>「工具」→「自定义 MCP」→ 填 URL</li>\n <li>在 Chatflow / Workflow 节点里直接拖用</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">n8n</p>\n <p className=\"text-xs text-muted-foreground mb-2\">工作流自动化,用 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">MCP Client</code> 节点。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>添加 <strong>MCP Client</strong> 节点</li>\n <li>Transport 选 HTTP Stream,URL 贴驰声地址</li>\n <li>Tool 下拉自动列出 16 个评测能力</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Flowise / LangFlow</p>\n <p className=\"text-xs text-muted-foreground mb-2\">可视化 LangChain Agent 编排。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>拖出 <strong>MCP Tool</strong> 节点</li>\n <li>连到 AgentExecutor 即可</li>\n </ul>\n </div>\n </div>\n\n <Callout type=\"info\">可视化平台的好处是<strong>流程可追溯、可回放</strong>,适合教育机构 / 培训部门做批量评测 + 报表导出;缺点是单步延迟略高于直接 MCP 调用,对延迟敏感的交互场景建议用 Cursor / Claude Desktop 直连。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"其他 MCP 客户端(通用配置)\">\n <p>任何遵循 MCP 协议的客户端都可以接入,统一使用以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权(可选)</td><td className=\"py-2 px-3 font-mono text-xs\">Header:<code>Authorization: Bearer &lt;MCP_API_KEY&gt;</code></td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">还没找到你用的客户端?任何支持 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 的终端都通用。找不到入口?下一小节「编程用 MCP」里用 SDK 直接接即可。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 编程用 MCP ══════ */}\n <DocSection id=\"config-code\" icon={Code2} title=\"接入 · 编程用 MCP(SDK / 代码集成)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Terminal className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>你在写一段<strong className=\"text-foreground\">真正的后端服务</strong>:一个 Agent Worker、一个 API、一个自动化脚本</li>\n <li>业务里的「语音评测」是其中一个步骤,需要由代码 <strong className=\"text-foreground\">决定何时调用</strong>、<strong className=\"text-foreground\">如何解析结果</strong></li>\n <li>三种主流写法:① 官方 MCP 客户端库 → ② Agent 框架自动桥接 → ③ 退化到 chat.completions + tools 参数</li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 强烈推荐①或②:驰声有 16 个评测工具,<strong className=\"text-foreground\">让 MCP 自动注入 tool schema</strong>,每新增一个题型不用改一行业务代码。</p>\n </div>\n\n <SubDoc id=\"code-mcp-sdk\" title=\"① 官方 MCP 客户端库(Python / TypeScript)\">\n <p>最底层、也最推荐的方式。由 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Anthropic 官方维护</a>,一次连接自动拿到全部工具的 JSON Schema,然后交给任意 LLM。</p>\n\n <p className=\"font-semibold mt-4\">Python:<code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp</code></p>\n <CodeBlock filename=\"chivox_client.py\" lang=\"python\">{`# pip install mcp openai\nimport asyncio, os\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nMCP_URL = \"https://speech-eval.site/mcp\"\nllm = OpenAI(api_key=os.getenv(\"DOUBAO_KEY\"),\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\") # 豆包 / DeepSeek / OpenAI 同构\n\nasync def main(audio_id: str, ref: str):\n async with streamablehttp_client(MCP_URL) as (reader, writer, _):\n async with ClientSession(reader, writer) as sess:\n await sess.initialize()\n\n tools = await sess.list_tools()\n openai_tools = [{\n \"type\": \"function\",\n \"function\": {\"name\": t.name, \"description\": t.description,\n \"parameters\": t.inputSchema},\n } for t in tools.tools]\n\n resp = llm.chat.completions.create(\n model=\"doubao-1-5-pro-32k\",\n messages=[{\"role\": \"user\",\n \"content\": f\"帮我评测 audioId={audio_id},参考文本:{ref}\"}],\n tools=openai_tools,\n )\n\n for call in resp.choices[0].message.tool_calls or []:\n result = await sess.call_tool(call.function.name,\n arguments=eval(call.function.arguments))\n print(call.function.name, \"→\", result.content[0].text[:200])\n\nasyncio.run(main(\"abc123\", \"I think therefore I am\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">TypeScript / Node.js:<code className=\"bg-muted px-1 rounded text-xs font-mono\">@modelcontextprotocol/sdk</code></p>\n <CodeBlock filename=\"chivox-client.ts\" lang=\"typescript\">{`// npm i @modelcontextprotocol/sdk openai\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport OpenAI from \"openai\";\n\nconst mcp = new Client({ name: \"chivox-biz\", version: \"1.0.0\" });\nawait mcp.connect(new StreamableHTTPClientTransport(new URL(\"https://speech-eval.site/mcp\")));\n\nconst { tools } = await mcp.listTools();\nconst openai = new OpenAI({ apiKey: process.env.DEEPSEEK_KEY, baseURL: \"https://api.deepseek.com\" });\n\nconst resp = await openai.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: \"评测 audioId=abc123, 参考 I think therefore I am\" }],\n tools: tools.map(t => ({ type: \"function\", function: {\n name: t.name, description: t.description, parameters: t.inputSchema\n }})),\n});\n\nfor (const call of resp.choices[0].message.tool_calls ?? []) {\n const r = await mcp.callTool({ name: call.function.name,\n arguments: JSON.parse(call.function.arguments) });\n console.log(call.function.name, r.content);\n}`}</CodeBlock>\n <Callout type=\"tip\">这种写法 <strong>LLM 供应商可换</strong>(豆包 / DeepSeek / GPT / Qwen / Moonshot),<strong>工具自动更新</strong>(驰声新增题型无需改业务代码),适合生产后端。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-agent-frameworks\" title=\"② 用 Agent 框架自动桥接(LangChain / Mastra / Agents SDK)\">\n <p>如果项目已经在用 Agent 框架,几行 adapter 就能把驰声 MCP 塞进去,框架本身会接管 tool-calling 循环、重试、日志。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LangChain / LangGraph(Python)</p>\n <CodeBlock filename=\"agent.py\" lang=\"python\">{`# pip install langchain-mcp-adapters langgraph\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom langgraph.prebuilt import create_react_agent\n\nclient = MultiServerMCPClient({\n \"chivox\": {\n \"transport\": \"streamable_http\",\n \"url\": \"https://speech-eval.site/mcp\",\n }\n})\ntools = await client.get_tools()\n\nagent = create_react_agent(\"openai:gpt-4o-mini\", tools)\nresult = await agent.ainvoke({\"messages\":\n [(\"user\", \"评测 audioId=abc123 参考 think\")]})\nprint(result[\"messages\"][-1].content)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Mastra(TypeScript)</p>\n <CodeBlock filename=\"mastra-agent.ts\" lang=\"typescript\">{`// npm i @mastra/core @mastra/mcp\nimport { MCPClient } from \"@mastra/mcp\";\nimport { Agent } from \"@mastra/core/agent\";\nimport { openai } from \"@ai-sdk/openai\";\n\nconst mcp = new MCPClient({ servers: {\n chivox: { url: new URL(\"https://speech-eval.site/mcp\") }\n}});\n\nexport const speechCoach = new Agent({\n name: \"speech-coach\",\n instructions: \"你是口语教练,用 chivox 工具评测并给改进建议。\",\n model: openai(\"gpt-4o-mini\"),\n tools: await mcp.getTools(),\n});`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">OpenAI Agents SDK(Python)</p>\n <CodeBlock filename=\"agents_sdk.py\" lang=\"python\">{`# pip install openai-agents\nfrom agents import Agent, Runner\nfrom agents.mcp import MCPServerStreamableHttp\n\nchivox = MCPServerStreamableHttp(\n params={\"url\": \"https://speech-eval.site/mcp\"},\n name=\"chivox-speech-eval\",\n)\n\nasync with chivox:\n agent = Agent(name=\"coach\",\n instructions=\"专业口语教练\",\n mcp_servers=[chivox])\n r = await Runner.run(agent, \"评测 audioId=abc123\")\n print(r.final_output)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LlamaIndex / AutoGen / CrewAI</p>\n <p className=\"text-xs text-muted-foreground mb-2\">这些框架都有官方或社区 MCP adapter,用法与上述类似:</p>\n <ul className=\"list-disc list-inside space-y-1 text-xs\">\n <li><strong>LlamaIndex</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">llama-index-tools-mcp</code></li>\n <li><strong>AutoGen</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">autogen-ext[mcp]</code></li>\n <li><strong>CrewAI</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">crewai-tools</code> 的 MCPServerAdapter</li>\n <li><strong>Spring AI</strong>(Java):<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">spring-ai-mcp-client</code></li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"code-function-calling\" title=\"③ 直调 chat.completions · 上传与动态 tools(豆包 / DeepSeek / 等)\">\n <p>\n DeepSeek、GLM(智谱)、KIMI(Moonshot)、豆包(火山方舟)、千帆、DashScope 等若提供 <strong>OpenAI 兼容的 HTTP API</strong>,接入驰声 MCP 的套路一致:用 MCP 客户端拉取工具列表 → 转为{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> → 模型决策 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_calls</code> → MCP 执行并回填 → 再让模型生成诊断话术。\n </p>\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">① MCP list_tools()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">② 转 OpenAI tools 格式</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">③ LLM 决策 tool_calls</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">④ MCP call_tool()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">⑤ 评测结果回填 → LLM 诊断</span>\n </div>\n </div>\n <Callout type=\"tip\">各厂商仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 与 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 与 tools 桥接代码可原样复用。</Callout>\n\n <p className=\"font-semibold mt-6\">先上传音频,拿到 audioId</p>\n <p className=\"text-sm text-muted-foreground mb-3\">文件评测工具通常需要 <code className=\"bg-muted px-1 rounded text-xs font-mono\">audioId</code>(或路径参数,视工具而定)。下面是用 HTTP 上传的最小示例:</p>\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://speech-eval.site/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst res = await fetch('https://speech-eval.site/upload', {\n method: 'POST',\n headers: { 'Content-Type': 'audio/mp3' },\n body: audioData,\n});\n\nconst result = await res.json();\nconsole.log('audioId:', result.audioId);`}</CodeBlock>\n\n <p className=\"font-semibold mt-6\">再以豆包为例:MCP 动态 tools + chat.completions 折返跑</p>\n <p>当你用的 LLM SDK <strong>还没有原生 MCP 支持</strong>(比如老版本的火山方舟 / 百度千帆 / 通义 DashScope),退化方案是:用 MCP 客户端<strong>一次性拉到 tool schema</strong>,然后按 OpenAI Function Calling 格式喂给 LLM。</p>\n <CodeBlock filename=\"doubao_tools.py\" lang=\"python\">{`# 适用:豆包(火山方舟)/ DeepSeek / Qwen / Moonshot / GLM 等 OpenAI 兼容 API\n# 核心思路:MCP 列工具 → 转 OpenAI tools → LLM 决策 → MCP 执行 → 回填\n\nasync with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n\n # ① 用 MCP 拉到的 schema 自动转 OpenAI 格式\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 喂给豆包\n messages = [{\"role\": \"user\", \"content\": \"评测一下 audioId=abc123\"}]\n resp = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages, tools=oa_tools,\n )\n\n # ③ LLM 回 tool_calls → 折返跑到 MCP 执行\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for c in msg.tool_calls:\n result = await mcp.call_tool(\n c.function.name, arguments=json.loads(c.function.arguments))\n messages.append({\"role\": \"tool\", \"tool_call_id\": c.id,\n \"content\": result.content[0].text})\n # ④ 再喂一轮,让豆包根据评测结果产出自然语言诊断\n final = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages)\n print(final.choices[0].message.content)`}</CodeBlock>\n <Callout type=\"warning\"><strong>千万别手写 tool schema!</strong>驰声共 <strong>16 个评测工具</strong>(英文 10 + 中文 6),每个工具参数较多,手写既容易错也难维护。一定用 MCP <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">list_tools()</code> 动态拿。</Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-deepseek\" title=\"DeepSeek\">\n <p>DeepSeek 提供与 OpenAI 完全兼容的 API,推荐使用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-chat</code>(V3)或 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-reasoner</code>(R1)。</p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"deepseek_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# DeepSeek 客户端(OpenAI 兼容)\nclient = OpenAI(\n api_key=os.environ[\"DEEPSEEK_API_KEY\"],\n base_url=\"https://api.deepseek.com\",\n)\n\nasync def evaluate_with_deepseek(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 拉取驰声 MCP 工具列表\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 调用 DeepSeek,让它选择合适的评测工具\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"deepseek-chat\", # 或 deepseek-reasoner\n messages=messages,\n tools=oa_tools,\n )\n\n # ③ 执行 tool_calls\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 让 DeepSeek 根据评测结果生成自然语言诊断\n final = client.chat.completions.create(\n model=\"deepseek-chat\",\n messages=messages,\n )\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_deepseek(\"audio_abc123\", \"Hello, nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\"><strong>推荐使用 deepseek-chat(V3)</strong>处理评测结果诊断,速度快、成本低;若需要深度分析或出题逻辑,可换用 deepseek-reasoner(R1),但注意 R1 不支持 Function Calling,需先用 V3 做工具调用再将结果交给 R1 分析。</Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-glm\" title=\"GLM(智谱 AI)\">\n <p>智谱 AI 提供 <code className=\"bg-muted px-1 rounded text-xs font-mono\">glm-4</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">glm-4-flash</code>(免费)等模型,支持 OpenAI 兼容接口,同时也有官方 Python SDK。</p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`ZHIPU_API_KEY=xxxxxxxx.xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式一:OpenAI 兼容接口(推荐)</p>\n <CodeBlock filename=\"glm_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(\n api_key=os.environ[\"ZHIPU_API_KEY\"],\n base_url=\"https://open.bigmodel.cn/api/paas/v4/\",\n)\n\nasync def evaluate_with_glm(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"glm-4-flash\", # 免费模型;生产可换 glm-4 或 glm-4-plus\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"glm-4-flash\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_glm(\"audio_abc123\", \"How are you today?\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式二:官方 zhipuai SDK</p>\n <CodeBlock filename=\"glm_sdk.py\" lang=\"python\">{`from zhipuai import ZhipuAI\n\nclient = ZhipuAI(api_key=os.environ[\"ZHIPU_API_KEY\"])\n\n# 其余逻辑与方式一相同,仅替换 client 实例\nresp = client.chat.completions.create(\n model=\"glm-4-flash\",\n messages=messages,\n tools=oa_tools,\n)`}</CodeBlock>\n <Callout type=\"tip\"><strong>glm-4-flash 免费</strong>,适合开发测试;<strong>glm-4-plus</strong> 上下文更长、指令遵循更准,适合生产环境中的诊断报告生成。</Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-kimi\" title=\"KIMI(Moonshot AI)\">\n <p>Moonshot AI 的 KIMI 模型同样兼容 OpenAI API,支持超长上下文(最高 128k),特别适合需要分析多段录音历史或生成详细学习报告的场景。</p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`MOONSHOT_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"kimi_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(\n api_key=os.environ[\"MOONSHOT_API_KEY\"],\n base_url=\"https://api.moonshot.cn/v1\",\n)\n\nasync def evaluate_with_kimi(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"moonshot-v1-8k\", # 也可选 moonshot-v1-32k / moonshot-v1-128k\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"moonshot-v1-8k\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_kimi(\"audio_abc123\", \"Nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\">KIMI 的长上下文特别适合「多轮口语练习 + 历史分析」场景:把用户过去 10 次的评测 JSON 全部塞进 messages,让 KIMI 生成横跨多次练习的进步报告,这是 8k 模型做不到的。</Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-doubao\" title=\"豆包 Seed 2.0(火山方舟)\">\n <p>\n 字节跳动 <strong>豆包 Seed 2.0</strong>(2026 年 2 月发布)是 ByteDance Seed 团队推出的新一代通用大模型,覆盖 Pro / Lite / Mini / Code 多种规格,\n 通过 <strong>火山方舟(Volcano Ark)</strong> 提供 OpenAI 兼容的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">/api/v3/chat/completions</code> 接口,\n 原生支持 <strong>Function Calling、JSON Schema 结构化输出、深度思考(Chain-of-Thought)模式</strong>,\n 接驰声 MCP 几乎无须改动业务代码。\n </p>\n\n <Callout type=\"tip\">\n 豆包 Seed 2.0 的优势在 <strong>深度思考 + 工具编排</strong>:让模型一次性规划\"先调 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code>,看完弱项再调 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.word.score</code> 进一步定位音素\",\n 适合做\"多步评测 + 自动纠音\"这类 Agent 场景。\n 默认开启思考模式,可通过 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'thinking: { type: \"disabled\" }'}</code> 关闭以节省时延。\n </Callout>\n\n <p className=\"font-semibold mt-4 mb-2\">推荐版本</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">model</th>\n <th className=\"text-left py-2 px-3 font-medium\">规格</th>\n <th className=\"text-left py-2 px-3 font-medium\">推荐用途</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-pro-260215</td><td className=\"py-2 px-3\">Pro · 旗舰</td><td className=\"py-2 px-3\">多步评测 + 深度诊断 + 复杂 Agent 编排</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-lite-260215</td><td className=\"py-2 px-3\">Lite · 平衡</td><td className=\"py-2 px-3\">在线辅导单轮评测 + 自然语言反馈,性价比首选</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-mini-260215</td><td className=\"py-2 px-3\">Mini · 极小</td><td className=\"py-2 px-3\">高并发 / 边缘部署 / 移动端实时反馈</td></tr>\n <tr><td className=\"py-2 px-3 font-mono\">doubao-seed-2-0-code-preview-260215</td><td className=\"py-2 px-3\">Code · 预览</td><td className=\"py-2 px-3\">代码生成 / 工具编排 DSL,对评测场景非主推</td></tr>\n </tbody>\n </table>\n </div>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`# 火山方舟控制台 → API Key 管理\nARK_API_KEY=ak-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python · 动态 tools + 折返跑)</p>\n <CodeBlock filename=\"doubao_seed20_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# 火山方舟 OpenAI 兼容端点\nclient = OpenAI(\n api_key=os.environ[\"ARK_API_KEY\"],\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n)\n\nMODEL = \"doubao-seed-2-0-pro-260215\" # 也可换成 lite / mini\n\nasync def evaluate_with_doubao(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 把驰声 16 个评测工具的 schema 动态注入豆包\n tools = (await mcp.list_tools()).tools\n ark_tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n },\n } for t in tools]\n\n messages = [\n {\"role\": \"system\",\n \"content\": \"你是一位专业的英语口语教练,会主动调用驰声评测工具获取真实分数。\"},\n {\"role\": \"user\",\n \"content\": f\"请评测这段录音,audio_id={audio_id},参考文本:{ref_text}。\"\n f\"先做句子级评测,弱项再追加单词级定位。\"},\n ]\n\n # ② 第一轮 —— 让豆包根据 prompt 自主选工具\n resp = client.chat.completions.create(\n model=MODEL,\n messages=messages,\n tools=ark_tools,\n tool_choice=\"auto\",\n # extra_body={\"thinking\": {\"type\": \"disabled\"}}, # 需要时可关思考\n )\n\n msg = resp.choices[0].message\n messages.append(msg)\n\n # ③ 折返跑:豆包可能一次发起多个 tool_calls,全部回填\n for call in (msg.tool_calls or []):\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 第二轮 —— 让豆包基于真实评测 JSON 输出诊断 / 练习建议\n final = client.chat.completions.create(\n model=MODEL,\n messages=messages,\n )\n return final.choices[0].message.content\n\nprint(asyncio.run(evaluate_with_doubao(\n audio_id=\"audio_abc123\",\n ref_text=\"The quick brown fox jumps over the lazy dog.\",\n)))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">深度思考模式(可选)</p>\n <p className=\"text-sm text-muted-foreground\">\n 豆包 Seed 2.0 默认开启 <strong>thinking</strong> 模式,会在内部生成思维链再产出答案,\n 对\"多步评测编排 / 复杂诊断\"质量明显更好;但会增加 30%~80% 的端到端延迟。\n 在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">extra_body</code> 里可显式控制:\n </p>\n <CodeBlock filename=\"thinking_mode.py\" lang=\"python\">{`# 关闭思考(更快、适合实时反馈)\nclient.chat.completions.create(\n model=\"doubao-seed-2-0-lite-260215\",\n messages=messages,\n tools=ark_tools,\n extra_body={\"thinking\": {\"type\": \"disabled\"}},\n)\n\n# 开启思考(默认,适合 Pro 模型 + 多步 Agent)\nclient.chat.completions.create(\n model=\"doubao-seed-2-0-pro-260215\",\n messages=messages,\n tools=ark_tools,\n extra_body={\"thinking\": {\"type\": \"enabled\"}},\n)`}</CodeBlock>\n\n <Callout type=\"info\">\n <strong>选型建议:</strong>\n 课堂 / 培训类一对一辅导用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-lite</code> + 关思考;\n K12 / 考试场景需要\"多题型联动 + 升级建议\"时用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-pro</code> + 开思考;\n 移动端实时口语对话用 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">doubao-seed-2-0-mini</code> 控制成本与时延。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-comparison\" title=\"模型对比速查\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead>\n <tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 pr-4 font-medium\">模型</th>\n <th className=\"text-left py-2 pr-4 font-medium\">推荐版本</th>\n <th className=\"text-left py-2 pr-4 font-medium\">base_url</th>\n <th className=\"text-left py-2 pr-4 font-medium\">Function Calling</th>\n <th className=\"text-left py-2 font-medium\">适合场景</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek V3</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-chat</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">评测调用 + 诊断,性价比最高</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek R1</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-reasoner</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com</code></td>\n <td className=\"py-2 pr-4 text-rose-500\">✗ 不支持</td>\n <td className=\"py-2\">深度分析(需先用 V3 调工具)</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4-Flash</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4-flash</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">开发测试(免费额度)</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4-Plus</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4-plus</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">生产诊断报告,指令遵循强</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">KIMI 8k</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">moonshot-v1-8k</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">单次评测 + 即时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">KIMI 128k</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">moonshot-v1-128k</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">多轮历史 + 学习进度报告</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Pro</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-pro-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持(含深度思考)</td>\n <td className=\"py-2\">多步评测编排 + 深度诊断 Agent</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Lite</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-lite-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">在线辅导 + 单轮诊断,性价比首选</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">豆包 Seed 2.0 Mini</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">doubao-seed-2-0-mini-260215</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">ark.cn-beijing.volces.com/api/v3</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">移动端 / 边缘部署 / 低延迟实时反馈</td>\n </tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"info\">所有模型均通过 <strong>OpenAI Python SDK</strong>(<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">openai</code> 包)调用,仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 和 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 侧代码<strong>无需改动</strong>。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-selfhosted-agent\" title=\"④ 自研后端 Agent(FastAPI / NestJS / Spring)\">\n <p>大部分落地场景其实是:<strong>你已有一个业务后端</strong>(教育 SaaS / 培训系统 / 客服平台),只需要新增一个「语音评测」能力。推荐架构:</p>\n\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">前端录音 / 上传</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">业务后端</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">MCP Client(常驻连接)</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">驰声 MCP</span>\n <span className=\"text-muted-foreground\">↘</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM(诊断 / 出题)</span>\n </div>\n </div>\n\n <p className=\"font-semibold mt-2\">FastAPI 示例(生产最小骨架)</p>\n <CodeBlock filename=\"app.py\" lang=\"python\">{`from fastapi import FastAPI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\nfrom contextlib import asynccontextmanager\n\nmcp_session: ClientSession | None = None\n\n@asynccontextmanager\nasync def lifespan(app):\n global mcp_session\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as s:\n await s.initialize()\n mcp_session = s\n yield\n mcp_session = None\n\napp = FastAPI(lifespan=lifespan)\n\n@app.post(\"/api/evaluate\")\nasync def evaluate(audio_id: str, ref_text: str):\n # 直接选一个最合适的工具执行(不经 LLM,延迟最低)\n r = await mcp_session.call_tool(\"evaluate_english_sentence\",\n {\"audioId\": audio_id, \"sentence\": ref_text})\n return {\"result\": r.content[0].text}`}</CodeBlock>\n\n <Callout type=\"tip\"><strong>关键优化</strong>:① MCP 连接在 lifespan 里做成长连接,不要每次请求都重连;② 对延迟敏感的场景可以「跳过 LLM 直接调 tool」,把 LLM 放到「生成诊断报告」那一步。</Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mt-6 text-sm\">\n <p className=\"font-semibold mb-2\">方案选型速查</p>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 font-medium\">场景</th>\n <th className=\"text-left py-2 font-medium\">推荐方案</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2\">AI 对话 / Copilot 类产品,LLM 供应商可能会换</td><td className=\"py-2\">① 官方 MCP 客户端库</td></tr>\n <tr><td className=\"py-2\">已有 LangChain / Mastra / Agents SDK 技术栈</td><td className=\"py-2\">② Agent 框架 + adapter</td></tr>\n <tr><td className=\"py-2\">火山方舟 / 千帆控制台直连,最小侵入</td><td className=\"py-2\">③ chat.completions + tools</td></tr>\n <tr><td className=\"py-2\">教育 SaaS / 培训 / 客服等业务后端集成</td><td className=\"py-2\">④ 自研后端 Agent</td></tr>\n </tbody>\n </table>\n </div>\n </div>\n </DocSection>\n\n {/* ══════ 评测模式 ══════ */}\n <DocSection id=\"eval-modes\" icon={Radio} title=\"评测模式\">\n\n <SubDoc id=\"stream-eval\" title=\"实时录音评测(边录边评)\">\n <p>通过本地麦克风实时录音,音频以 WebSocket 流式推送到评测服务,<strong>无需生成中间文件</strong>。这是最灵活、延迟最低的评测方式,特别适合交互式口语练习场景。</p>\n <Callout type=\"tip\">实时录音评测需要安装 <strong>SoX</strong> 并使用本地代理模式(Claude Desktop / Claude Code)。Cursor 等远程直连模式暂不支持实时录音。</Callout>\n\n <p className=\"font-semibold mt-4\">需要按顺序调用三个工具:</p>\n\n <div className=\"mt-3 space-y-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">create_stream_session</code> — 创建流式评测会话</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.word.score、en.sent.score、cn.sent.raw' },\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式,默认 mp3' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100,默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code>,后续步骤需使用。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">start_recording</code> — 开始麦克风录音</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '来自 create_stream_session 的会话 ID' },\n { name: 'chunk_size', type: 'number', required: false, desc: '音频块大小(bytes),默认 3200' },\n { name: 'chunk_interval_ms', type: 'number', required: false, desc: '推送间隔(ms),默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">调用后自动启动麦克风,音频通过 WebSocket 实时推送到评测服务。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">stop_recording</code> — 停止录音并获取结果</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '会话 ID' },\n { name: 'timeout', type: 'number', required: false, desc: '等待评测结果超时秒数,默认 30' },\n ]} />\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"stream-flow\" title=\"流式评测完整流程\">\n <div className=\"rounded-lg border border-border/60 p-5 bg-muted/10 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong>用户:</strong>&quot;请评测我的英文发音,文本是 Hello&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">create_stream_session(core_type=&quot;en.word.score&quot;, ref_text=&quot;Hello&quot;)</code></p>\n <p className=\"text-muted-foreground\">→ 获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">session_id</code></p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p>AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">start_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground mt-1\">麦克风开始录音,音频通过 WebSocket 实时推送到评测服务</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>用户</strong>说完后告知 AI &quot;结束&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">stop_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground\">→ 返回评测结果(总分、准确度、流利度、音素得分等)</p>\n </FlowStep>\n </div>\n </SubDoc>\n\n <SubDoc id=\"file-eval\" title=\"音频文件评测\">\n <p>适用于已有音频文件的评测场景,支持三种输入方式:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">适用场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_file_path</td><td className=\"py-2 px-3\">本地文件路径</td><td className=\"py-2 px-3 text-muted-foreground\">代理自动读取并转为 Base64,最便捷</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_base64</td><td className=\"py-2 px-3\">Base64 编码数据</td><td className=\"py-2 px-3 text-muted-foreground\">已有编码数据时使用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_url</td><td className=\"py-2 px-3\">音频文件 URL</td><td className=\"py-2 px-3 text-muted-foreground\">音频托管在云存储时使用</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"tip\">推荐使用 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">audio_file_path</code>,只需传入本地文件路径,代理会自动处理编码和上传。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"tools-en\" title=\"英文评测工具(10 种)\">\n <ToolTable tools={[\n ['en_word_eval', '单词评测', '评测 \"Hello\" 的发音准确度'],\n ['en_word_correction', '单词纠音', '检测多读/漏读/错读并给出纠正建议'],\n ['en_vocab_eval', '词语评测', '同时评测多个单词的发音'],\n ['en_sentence_eval', '句子评测', '评测整句朗读的准确度与流利度'],\n ['en_sentence_correction', '句子纠音', '逐词检测发音问题并给出修正建议'],\n ['en_paragraph_eval', '段落朗读评测', '评测整段英文课文的朗读质量'],\n ['en_phonics_eval', '自然拼读评测', '评测 Phonics 拼读规则掌握程度'],\n ['en_choice_eval', '口语选择题', '从预设选项中识别口语回答'],\n ['en_semi_open_eval', '半开放题评测', '场景对话等半开放口语题型'],\n ['en_realtime_eval', '实时朗读评测', '实时反馈朗读质量,逐句评分'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong>所有英文评测工具均支持以下参数。</p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tools-cn\" title=\"中文评测工具(6 种)\">\n <ToolTable tools={[\n ['cn_word_raw_eval', '单字评测(汉字)', '评测 \"中\" 的发音'],\n ['cn_word_pinyin_eval', '单字评测(拼音)', '评测 \"zhōng\" 的发音'],\n ['cn_sentence_eval', '词句评测', '评测 \"你好世界\" 的朗读'],\n ['cn_paragraph_eval', '段落朗读评测', '评测一段中文课文的朗读质量'],\n ['cn_rec_eval', '有限分支识别评测', '从预设选项中识别用户发音'],\n ['cn_aitalk_eval', 'AI Talk 口语评测', '中文口语对话能力评测'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong></p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本(汉字或拼音)' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结构化结果:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"integrity\": 95,\n \"details\": [\n {\n \"char\": \"hello\",\n \"score\": 85,\n \"phone\": [\n { \"phoneme\": \"h\", \"score\": 90 },\n { \"phoneme\": \"ɛ\", \"score\": 82 },\n { \"phoneme\": \"l\", \"score\": 88 },\n { \"phoneme\": \"oʊ\", \"score\": 80 }\n ]\n }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall', '0–100', '综合总分,反映整体语音质量'],\n ['accuracy', '0–100', '准确度,音素级别的发音准确率'],\n ['pron', '0–100', '发音得分,整体发音质量评价'],\n ['fluency.overall', '0–100', '流利度,语流的自然度与平滑度'],\n ['fluency.speed', '0–100', '语速得分,每分钟词/音节计量'],\n ['fluency.pause', 'number', '不自然停顿次数'],\n ['integrity', '0–100', '完整度,是否完整朗读了参考文本'],\n ['details[].phone[]', 'array', '每个音素的独立评分,可定位具体弱项'],\n ['details[].stress[]', 'array', '重音得分,检测重弱读是否正确'],\n ].map(([field, range, desc]) => (\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期', '音频上传后 5 分钟内有效,评测完成后自动删除', '重新上传音频'],\n ['服务器繁忙', '当前请求过多,排队已满', '稍后重试,使用指数退避策略'],\n ['存储空间已满', '服务器临时音频存储已满', '稍后重试,系统会自动清理'],\n ['音频格式不支持', '上传了不支持的文件类型', '使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限', '超过该题型最大时长限制', '裁剪音频后重试'],\n ['WebSocket 连接断开', '网络不稳定导致流式连接中断', '代理会自动重连,如持续失败请检查网络'],\n ].map(([scene, reason, fix]) => (\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p className=\"text-sm text-muted-foreground\">\n 下面这些模板与{' '}\n <Link href=\"/demo\" className=\"underline underline-offset-2\">免费体验</Link>{' '}\n Demo 中实际使用的「教练 Prompt」一脉相承,全部围绕 <strong>角色 / 任务 / 方法 / 输出格式 / 语气</strong> 五段式结构,并使用占位符{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code> 直接拼接 MCP 工具返回的 JSON。\n 推荐在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">system</code> 消息里挂载,再把工具返回原文塞进\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">user</code> 消息,由 LLM 基于真实分数和音素生成可读诊断 / 练习材料。\n </p>\n\n <Callout type=\"tip\">\n <strong>使用建议:</strong>\n <ul className=\"list-disc list-inside mt-1 space-y-1\">\n <li>把 MCP 工具调用的完整 JSON(含 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">overall / dp_type / phonemes</code> 等字段)原样塞入 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,不要预先做汇总——细节越完整诊断越具体。</li>\n <li>「诊断」类模板让 LLM 输出 Markdown 给最终用户阅读;「练习」类模板要求 LLM 输出严格 JSON 给前端渲染。两步分开调用,效果远好于一次性混合。</li>\n <li>题型决定 prompt:单词题用音素级、句子题加韵律、段落题加 chunk / 整体性,半开放题套四维评级。下面按这 6 类分别给出。</li>\n </ul>\n </Callout>\n\n {/* ─── A. 诊断类 · 输出 Markdown 给学习者阅读 ─── */}\n <h4 className=\"text-sm font-semibold mt-6 mb-3 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> A. 二次诊断 · 输出 Markdown\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Sparkle className=\"h-4 w-4\" /> 1. 单词级 · 音素诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.word.score</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code>。围绕 <code className=\"bg-muted px-1 rounded font-mono\">phonemes[].dp_type</code> 找出错读音。\n </p>\n <CodeBlock filename=\"prompt-word-diagnosis.txt\">{`你是一位专业的英语发音教练。下面是驰声 MCP(en.word.score)返回的单词级评测数据:\n一个目标词、总分与发音分、以及带有 dp_type 的音素数组。\n\n## 任务\n为学习者写一份 **可直接阅读** 的发音诊断,每一条都要引用音素分数(写出 IPA 符号)。\n\n## 方法\n- 按 dp_type 分组:先看 mispron,再补充 normal 但分数 < 70 的\"边缘音\"。\n- 每个问题音素:① 典型母语干扰 → ② 你听到的声音 → ③ 一条具体的发音修正动作(下颌 / 舌位 / 气流 / 唇形)。\n- 重音符号 ˈ 标记的音节,重音错位也要单独列出(即便音素本身分数尚可)。\n- 最多列 4 条,按 **影响力从弱到强** 排序。\n\n## 输出\n精简 Markdown:可选一句总结 → 编号列表 → 单行 **🎯 优先级** 排序。\n\n## 语气\n友善、具体、可执行,不要空喊\"加油\"。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> 2. 句子级 · 节奏 + 重音诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.sent.score</code>。开头先给一行\"计分牌\",再从 phones → words → prosody。\n </p>\n <CodeBlock filename=\"prompt-sentence-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.sent.score)返回的句子级数据:refText、总分、发音子分(accuracy / integrity / fluency / rhythm)、可选 speed(WPM),以及 details[] 中的逐词得分、dp_type 与可选 phonemes[]。\n\n## 任务\n开头给一行紧凑的 **计分牌**(总分 + 各子分 + 速度),再按 **音素 → 词 → 韵律** 顺序展开诊断。\n\n## 检查清单(每项都要覆盖;不适用则写\"无明显问题\")\n1. **音素级**:每个 score < 70 或 dp_type ≠ normal 的词,展开 phonemes,命名典型错误(如 /ð/ → /d/)并给一条修正动作。\n2. **漏读 / 多读**:标注 dp_type=omit / insert,解释如何拉低 integrity。\n3. **韵律**:基于 rhythm + 错读词的重音,指出重音错位、陈述句调型平直、断句过多等。\n4. **流利度 vs 准确度**:若 fluency 高但 accuracy 低,明确指出,让学习者知道优先改哪一项。\n\n## 输出格式\nMarkdown 子标题:**Pronunciation / Prosody / Study priority**(最后一行简短排序,按 ROI 从高到低)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <FileText className=\"h-4 w-4\" /> 3. 段落级 · 音素族归并 + 完整度\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code>。把同类错音合并为\"音素族\",避免逐词复述。\n </p>\n <CodeBlock filename=\"prompt-paragraph-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.pred.score)返回的段落级数据:长 refText、总分、发音子分、可选 speed / integrity,以及大量 details[](同一个词可能在不同子句中重复出现;空 char + dp_type=omit 表示漏读)。\n\n## 任务\n对整段做 **整体诊断**:\n(a) 完整度 / 漏读,\n(b) 反复出现的 **音素族**,\n(c) 节奏与断句,\n(d) 各子分之间是否相互印证。\n\n## 检查清单\n1. **integrity / dp_type**:列出每个 omit / insert,并给出所在子句;解释长读时为什么虚词容易被吞。\n2. **音素聚类**:将弱词按共同音素问题归族(/θ/ 链、/e/ 链、/ɒ/ 等),引用 details 中的实例。\n3. **韵律**:结合 rhythm 与标点 —— 短语内停顿过多?子句边界缺失?\n4. **整体一致性**:若 fluency 正常但 accuracy 差(或反之),点明并指出对练习设计的影响。\n\n## 输出格式\nMarkdown 章节:**Summary / Completeness / Pronunciation (families) / Rhythm / Study priorities**(最后一项最多 4 条,按影响力排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <GraduationCap className=\"h-4 w-4\" /> 4. 半开放题 · IELTS 风格四维评级\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pqan.score</code>。结合学习者 transcript + 四维分数,给出可教学的\"升档建议\"。\n </p>\n <CodeBlock filename=\"prompt-semi-open.txt\">{`你是一位 **IELTS / 课堂口语风格** 的口语教练。下面是驰声 MCP(en.pqan.score)返回的半开放题数据:题目 refText、学习者 transcript、overall、四个维度分(grammar / content / fluency / pron)、speed(WPM)、以及结构化的 issues[] 标记。\n\n## 任务\n给出 **基于 rubric 的诊断**:先用一句话给出 0–100 的整体段位估计,再 **逐维度** 解释,并 **引用 transcript 中的短句**(不要复述全文)。最后给 3–5 条 **可执行的\"升档建议\"**(句型 / 模板,而不是\"多练一点\")。\n\n## 权重(要明确告诉学习者)\n- **Content ~30%** — 想法深度、举例、具体程度\n- **Grammar ~25%** — 句型多样性、主谓一致、从句\n- **Fluency ~25%** — 语速、停顿、口头禅、衔接\n- **Pronunciation ~20%** — 音素 + 可懂度\n\n## 跨维度检查\n- 若 content 最低,先给\"展开模板\",不要先抠音素。\n- 若 fluency 低 + WPM 低,区分\"思考型停顿\"与\"发音卡顿\"。\n- 若 pron 标出特定音,绑定到 transcript 里 **真实出现过的词**。\n\n## 输出格式\nMarkdown:**段位估计** → 紧凑的 **得分表格** → **\"To reach the next band\"** 编号列表(每条给出可仿写的句型)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Languages className=\"h-4 w-4\" /> 5. 中文专项 · 声调 / 儿化 / 变调\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.sent.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.pred.raw</code>。中文评测最大权重在 **声调**,必须单独检查。\n </p>\n <CodeBlock filename=\"prompt-mandarin-coach.txt\">{`你是一位专业的普通话口语教练。下面是驰声 MCP(cn.sent.raw)返回的中文句子评测数据:整句分数 + 逐字 details + 可选 phonemes + 流利 / 节奏分。\n\n## 中文评测的权重\n- **声调错误权重最高** —— 声调读错可能直接改变词义\n- dp_type=mispron 多来自:声调错、平翘舌混用、前后鼻音混用\n- 拼音对、声调错,听感仍是\"洋腔洋调\"\n\n## 检查清单(缺则写\"无明显问题\")\n1. **儿化**:phonemes 出现 r(儿化) 或 dp_type 提示时,确认是否被读成\"主音节 + 单独儿\"两拍。\n2. **轻声**:叠词、词缀、句末语气词 —— 第二字应短促轻读,无完整声调曲线。\n3. **变调**:三声连读、不 + 后字、一 + 后字,是否符合普通话规则。\n4. **节奏**:用 rhythm + 逗号位置,标记停顿过长或过碎,建议 chunk 边界。\n\n## 输出\nMarkdown 子标题:**总览 / 儿化 / 轻声 / 变调 / 节奏 / 学习优先级**(最后一项 1–3 条,按\"听感自然度提升\"排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── B. 练习类 · 输出 JSON 给前端渲染 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <Mic className=\"h-4 w-4\" /> B. 微练习生成 · 输出严格 JSON\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 6. 单词 / 句子 · 微练习\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 建议在 <strong>诊断完成后</strong> 第二轮调用:让 LLM 严格按弱项音素生成,避免\"通用绕口令包\"。\n </p>\n <CodeBlock filename=\"prompt-micro-drills.txt\">{`你仍是这位学习者的发音教练。**第二轮诊断已经完成** —— 现在严格基于诊断里点名的弱音素,生成 **1:1 对应** 的微练习(不要给通用题)。\n\n## 输出(仅返回一段合法 JSON 数组,外层不要任何文字)\n根节点必须是数组,每个元素含 category(字符串)、icon(单个 emoji)、items({ \"label\", \"content\" } 数组)。\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme drills\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Awareness\", \"icon\": \"🗣️\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Whole word\", \"icon\": \"📝\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme drills**:诊断里每个 mispron 音素至少给一对最小对立对 + 一句短绕口令 / 重复句。\n- **Awareness**:当重音偏弱时,给一条 stress / 舌位 / 下颌姿态提示。\n- **Whole word**:2–3 句自然句,目标词分别出现在句首 / 句中 / 句末。\n- 所有 content 必须可念出声;字符串内不要再嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n原始 MCP:\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Notebook className=\"h-4 w-4\" /> 7. 段落 · Chunk Map + 元认知\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 段落级练习要从\"音素\"上升到\"短语 / 整段 / 元认知录回放\"。直接对应 <code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code> 的诊断结果。\n </p>\n <CodeBlock filename=\"prompt-paragraph-drills.txt\">{`你仍是这位学习者的口语教练。**段落级二次诊断** 已经列出了漏读位置和音素族 —— 现在请生成 **整段练习**:把弱音素串成\"族\"练习、用 chunk map 把虚词粘回短语,并附一条录音 → 回放 → 对照 MCP details 的元认知步骤。\n\n## 输出(仅返回一段合法 JSON 数组)\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme families\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Chunking\", \"icon\": \"🎵\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Metacognition\", \"icon\": \"🧠\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme families**:至少 3 条,按诊断里的音素族(/θ/、/e/、/ɒ/ 等)组织最小对立对 / 词链。\n- **Chunking**:① 给一行用 [ ] 和 / 标注的 chunk map(可加大致停顿秒数);② 给一句\"把漏掉的虚词嵌入短语\"的练习(不要孤立练)。\n- **Metacognition**:一条\"录音 → 回放 → 对照 MCP details\"的 N 遍练习(N 写明,2–3 之间)。\n- 字符串只能是纯展示文本,禁止嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── C. 跨题 / 运营 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" /> C. 跨题 / 运营\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Gauge className=\"h-4 w-4\" /> 8. 多日学习报告\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 把过去 N 天的 MCP 评测结果聚合后丢给 LLM,输出适合发给学生 / 家长的周报。\n </p>\n <CodeBlock filename=\"prompt-weekly-report.txt\">{`你是一位英语口语学习顾问。下面是这位学习者过去 7 天的驰声 MCP 评测历史(按时间顺序),每条包含题型、coreType、overall、子分、weak_phonemes 摘要。\n\n## 任务\n为学习者生成一份 **周报**,目标读者:本人 + 家长 / 老师。\n强调 **趋势** 与 **下一步计划**,不要复述每一次具体分数。\n\n## 必须包含\n1. **整体趋势**:进步 / 持平 / 退步(一句话 + 简短依据)\n2. **维度变化**:accuracy / fluency / rhythm / integrity / speed 任意有数据的维度,给出\"本周均分 vs 上周\"\n3. **本周高光**:分数提升最大的题型 / 音素 / 句型\n4. **顽固问题**:连续 3 次以上未达 70 的音素或维度\n5. **下周计划**:3 条具体动作(每条对应一个 MCP 工具或一个练习类型)\n6. **结尾**:1–2 句鼓励性总结,避免空话\n\n## 风格\n- 像一位认真但不啰嗦的私教\n- 出现学生姓名时使用占位 {student_name}\n- 数字一律保留整数\n\n## 评测历史\n\\`\\`\\`json\n{history}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 9. 错误归因 · JSON 摘要\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 用于后端聚合 / 数据看板:让 LLM 把一次评测压缩成结构化摘要,方便入库统计。\n </p>\n <CodeBlock filename=\"prompt-error-summary.txt\">{`你是一个评测结果归类器。下面是驰声 MCP 一次评测的完整返回。请抽取关键信息并 **仅返回合法 JSON**(不要任何解释文字)。\n\n## Schema\n\\`\\`\\`json\n{\n \"core_type\": \"string,例如 en.sent.score\",\n \"overall\": 0,\n \"sub_scores\": { \"accuracy\": 0, \"integrity\": 0, \"fluency\": 0, \"rhythm\": 0 },\n \"weak_phonemes\": [\n { \"ipa\": \"string\", \"avg_score\": 0, \"occur\": 0, \"example_word\": \"string\" }\n ],\n \"omissions\": [{ \"after\": \"string\", \"missing\": \"string\" }],\n \"stress_issues\": [{ \"word\": \"string\", \"expected\": \"string\", \"actual\": \"string\" }],\n \"primary_issue_tag\": \"phoneme|stress|fluency|integrity|content|none\",\n \"next_action_hint\": \"string,10 字以内,例如 \\\"练 /θ/ 对立对\\\"\"\n}\n\\`\\`\\`\n\n## 规则\n- weak_phonemes 只收 avg_score < 70 的项,最多 5 项,按 occur 降序。\n- 不要复述 refText,不要 markdown。\n- 字段缺失时使用 null 或空数组,不要省略 key。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n <Callout type=\"info\">\n <strong>怎么接到 MCP 工具调用?</strong>\n <br />\n 以「直调 chat.completions + 动态 tools」为例:第一轮 LLM 调出 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code> 工具,把工具 result(即 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">tool</code> 消息内容)原样填入上面任意「诊断」模板的{' '}\n <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,作为第二轮 user 消息发回;拿到诊断结论再走第三轮,套「微练习」模板得到 JSON。三轮调用即可串起 <strong>评测 → 诊断 → 练习</strong> 全链路。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频过期:</strong>上传后 5 分钟内有效。建议在上传后 3 分钟内完成评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>服务繁忙:</strong>使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>录音中断:</strong>WebSocket 断线后代理会自动重连。如持续失败,检查网络连接和 SoX 安装。</span></li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>优先使用流式评测:</strong>实时录音模式跳过了文件上传环节,端到端延迟比文件评测低 30-50%。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频压缩:</strong>文件评测时,上传前将音频转为 16kHz 单声道 mp3,可减少 60-80% 的上传体积。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>选择合适的工具:</strong>单词评测比段落评测快 3-5 倍。如只需单词级分数,不要用句子评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>本地文件路径最便捷:</strong>使用 <code className=\"bg-muted px-1 rounded text-xs\">audio_file_path</code> 参数传入本地路径,代理自动处理编码,无需手动转 Base64。</span></li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Globe} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP JSON-RPC 接口(Streamable HTTP)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制', '500MB', '最大音频存储空间'],\n ['最大排队', '10', '等待处理的请求数'],\n ['最大并发', '3', '同时处理的评测数'],\n ['音频有效期', '5 分钟', '上传后未评测自动过期'],\n ['文件大小限制', '50MB', '单个音频文件最大'],\n ].map(([k, v, d]) => (\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">\n 上表为托管侧<strong>技术默认</strong>;计费与试用/商用<strong>调用配额、并发档位</strong>以{' '}\n <Link href=\"/dashboard/plans\" className=\"text-foreground underline underline-offset-2\">\n 会员套餐\n </Link>\n 说明为准,与上表独立。\n </p>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <Callout type=\"info\">\n <strong>当前线上版本:v1.0.0</strong>(网站未正式上线)。以下变更项为开发阶段规划记录,以正式发布公告为准。\n </Callout>\n <div className=\"space-y-3 mt-4\">\n {[\n { ver: 'v1.0.0', date: 'TBD', desc: '当前线上版本(网站未正式上线)' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </DocsShell>\n );\n}\n\nexport default function DocsPage() {\n const locale = useLocale();\n return locale.startsWith('zh') ? <DocsPageZh /> : <DocsPageEn />;\n}\n"}, "f6779e85462691f3c58b16e2b4652c6092bac434": {"t": 1785765798.5010319, "n": 114093, "h": "70f80afe4a363010", "c": "'use client';\n\nimport { useLocale } from 'next-intl';\nimport { Link } from '@/i18n/routing';\nimport { DocsShell, type NavGroup } from './docs-shell';\nimport {\n CodeBlock,\n DocSection,\n SubDoc,\n ParamTable,\n Callout,\n FlowStep,\n ToolTable,\n} from './docs-shared';\nimport { DocsEnglishBody } from './docs-content-en';\nimport {\n BookOpen,\n Code2,\n Zap,\n FileText,\n Terminal,\n Globe,\n Mic,\n MessageSquare,\n BarChart3,\n AlertTriangle,\n Radio,\n FolderOpen,\n Workflow,\n Sparkles,\n Plug,\n Bot,\n Building2,\n Check,\n Sparkle,\n Languages,\n GraduationCap,\n Notebook,\n Gauge,\n ListChecks,\n} from 'lucide-react';\n\n/* ─── Navigation Structure ─── */\n\nconst NAV = [\n { id: 'concept', icon: Workflow, label: 'MCP 工作原理', children: [\n { id: 'why-mcp', label: '为什么需要 MCP' },\n { id: 'scenario-walkthrough', label: '场景走查 · 6 步' },\n { id: 'dev-responsibility', label: '开发者只做两件事' },\n { id: 'integration-paths', label: '三种接入姿势' },\n ]},\n { id: 'architecture', icon: Terminal, label: '架构说明', children: [\n { id: 'arch-diagram', label: '架构图' },\n { id: 'transport', label: '传输协议' },\n ]},\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'requirements', label: '系统要求' },\n { id: 'install', label: '安装方式' },\n { id: 'env-vars', label: '环境变量' },\n ]},\n { id: 'config', icon: Plug, label: '接入 · 直接用(零代码)', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: '扣子(Coze)' },\n { id: 'config-coze-workflow', label: 'Coze 工作流' },\n { id: 'config-ai-workspace', label: 'AI 工作台 · 豆包 / 飞书 / WorkBuddy / 钉钉' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: '其他 MCP 客户端' },\n ]},\n { id: 'config-code', icon: Code2, label: '接入 · 编程用(SDK / 代码)', children: [\n { id: 'code-mcp-sdk', label: '官方 MCP 客户端库' },\n { id: 'code-agent-frameworks', label: 'LangChain / Mastra / Agents SDK' },\n { id: 'code-function-calling', label: '直调 chat.completions · 上传与动态 tools' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM(智谱)' },\n { id: 'llm-kimi', label: 'KIMI(Moonshot)' },\n { id: 'llm-comparison', label: '模型对比速查' },\n { id: 'code-selfhosted-agent', label: '自研后端 Agent' },\n ]},\n { id: 'eval-modes', icon: Radio, label: '评测模式', children: [\n { id: 'stream-eval', label: '实时录音评测' },\n { id: 'stream-flow', label: '流式评测流程' },\n { id: 'file-eval', label: '音频文件评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'tools-en', label: '英文评测工具' },\n { id: 'tools-cn', label: '中文评测工具' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n { id: 'service-info', icon: Globe, label: '服务信息', children: [\n { id: 'endpoints', label: '服务端点' },\n { id: 'limits', label: '服务限制' },\n { id: 'changelog', label: '更新日志' },\n ]},\n];\n\nconst NAV_EN: NavGroup[] = [\n { id: 'concept', icon: Workflow, label: 'How MCP works', children: [\n { id: 'why-mcp', label: 'Why MCP' },\n { id: 'scenario-walkthrough', label: 'Scenario · 6 steps' },\n { id: 'dev-responsibility', label: 'Two developer tasks' },\n { id: 'integration-paths', label: 'Three integration styles' },\n ]},\n { id: 'architecture', icon: Terminal, label: 'Architecture', children: [\n { id: 'arch-diagram', label: 'Diagram' },\n { id: 'transport', label: 'Transports' },\n ]},\n { id: 'quick-start', icon: Zap, label: 'Quick start', children: [\n { id: 'overview', label: 'Overview' },\n { id: 'requirements', label: 'Requirements' },\n { id: 'install', label: 'Install' },\n { id: 'env-vars', label: 'Environment' },\n ]},\n { id: 'config', icon: Plug, label: 'Zero-code clients', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: 'Coze' },\n { id: 'config-coze-workflow', label: 'Coze workflows' },\n { id: 'config-ai-workspace', label: 'Enterprise AI workspaces' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: 'Other MCP clients' },\n ]},\n { id: 'config-code', icon: Code2, label: 'SDK / code integration', children: [\n { id: 'code-mcp-sdk', label: 'Official MCP SDK' },\n { id: 'code-agent-frameworks', label: 'Agent frameworks' },\n { id: 'code-function-calling', label: 'chat.completions · upload & tools' },\n { id: 'llm-deepseek', label: 'DeepSeek' },\n { id: 'llm-glm', label: 'GLM (Zhipu)' },\n { id: 'llm-kimi', label: 'KIMI (Moonshot)' },\n { id: 'llm-comparison', label: 'Model comparison' },\n { id: 'code-selfhosted-agent', label: 'Custom backend' },\n ]},\n { id: 'eval-modes', icon: Radio, label: 'Evaluation modes', children: [\n { id: 'stream-eval', label: 'Streaming mic' },\n { id: 'stream-flow', label: 'Streaming flow' },\n { id: 'file-eval', label: 'Audio files' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API reference', children: [\n { id: 'tools-en', label: 'English tools' },\n { id: 'tools-cn', label: 'Chinese tools' },\n { id: 'result-fields', label: 'Result fields' },\n { id: 'error-codes', label: 'Errors' },\n ]},\n { id: 'best-practices', icon: FileText, label: 'Best practices', children: [\n { id: 'prompt-templates', label: 'Prompt templates' },\n { id: 'error-handling', label: 'Errors & retries' },\n { id: 'performance', label: 'Performance' },\n ]},\n { id: 'service-info', icon: Globe, label: 'Service info', children: [\n { id: 'endpoints', label: 'Endpoints' },\n { id: 'limits', label: 'Limits' },\n { id: 'changelog', label: 'Changelog' },\n ]},\n];\n\n/* ─── Page(英文 / 中文拆成两个组件,避免切换语言时 Hooks 数量变化导致白屏)─── */\n\nfunction DocsPageEn() {\n return (\n <DocsShell\n nav={NAV_EN}\n backLabel=\"Back to Home\"\n title=\"Developer documentation\"\n subtitle=\"Chivox Speech Evaluation MCP · chivox-local-mcp\"\n >\n <DocsEnglishBody />\n </DocsShell>\n );\n}\n\nfunction DocsPageZh() {\n return (\n <DocsShell\n nav={NAV}\n backLabel=\"返回首页\"\n title=\"开发者文档\"\n subtitle=\"驰声语音评测 MCP · chivox-local-mcp\"\n >\n {/* ══════ MCP 工作原理 ══════ */}\n <DocSection id=\"concept\" icon={Workflow} title=\"MCP 工作原理\">\n\n <SubDoc id=\"why-mcp\" title=\"为什么需要 MCP\">\n <p>\n 大模型(GPT / Claude / Gemini / 通义千问 等)本质上是<strong>文字大脑</strong>:它擅长读和写,但天生<strong>听不到</strong>音频、<strong>打不开</strong>你的数据库、<strong>调不通</strong>第三方 API。\n </p>\n <p>\n <strong>MCP(Model Context Protocol)</strong>是一套由 Anthropic 主导、业界共同推进的开放协议,它像一根\"对讲机\":让大模型可以在需要时主动去调用外部能力(工具、数据源、服务),然后把结果接回来继续对话。\n </p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <AlertTriangle className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n <span className=\"text-sm font-semibold\">没有 MCP 的做法</span>\n </div>\n <ul className=\"text-xs text-muted-foreground space-y-1.5 leading-relaxed\">\n <li>· 研究评测服务的 REST 文档</li>\n <li>· 自己写鉴权 / 上传 / 轮询代码</li>\n <li>· 把结果序列化后塞进 prompt</li>\n <li>· 每换一个大模型都得重写一遍</li>\n </ul>\n </div>\n <div className=\"rounded-lg border-2 border-foreground/60 p-4 bg-foreground/[0.02]\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Check className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">用驰声 MCP 之后</span>\n </div>\n <ul className=\"text-xs space-y-1.5 leading-relaxed\">\n <li>· 填一行 MCP Server 地址</li>\n <li>· 大模型自动发现&amp;调用评测工具</li>\n <li>· 换任何支持 MCP 的大模型都能用</li>\n <li>· 不写一行调用代码</li>\n </ul>\n </div>\n </div>\n <Callout type=\"info\">\n MCP 让\"接入语音评测\"从一个\"后端工程\"变成了一个\"配置项\"。这也是为什么 Cursor、Claude Desktop、Dify、Coze、字节豆包、飞书智能助手都已经原生支持 MCP。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"scenario-walkthrough\" title=\"场景走查 · 从一段录音到温柔反馈\">\n <p>\n 想象一个具体场景:一位开发者用 Claude 做了一款 <strong>「AI 雅思口语陪练」小程序</strong>,接入驰声语音评测 MCP。当用户发来一段录音,整个链路是这样的:\n </p>\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-5 my-4 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong className=\"text-foreground\">配置阶段</strong>(开发者 · 一次性)</p>\n <p className=\"text-muted-foreground mt-1\">在 AI 后端的配置文件里填一行驰声 MCP Server 地址。大模型启动时自动发现:「我多了一个叫<strong>语音评测</strong>的工具」。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong className=\"text-foreground\">接收用户指令</strong>(终端用户)</p>\n <p className=\"text-muted-foreground mt-1\">小程序用户发来一段自我介绍录音:「帮我听听,发音哪里有问题?」后端把「文字 + 录音链接」打包给大模型。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong className=\"text-foreground\">大模型自主思考</strong>(LLM · 最核心的魔法)</p>\n <div className=\"text-muted-foreground mt-1 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed\">\n 「用户让我听发音。」<br />\n 「但我只是文字大脑,我听不到。」<br />\n 「等等 —— 开发者给了我一个叫 <code className=\"bg-muted px-1 rounded\">chivox_voice_eval</code> 的 MCP 工具!」\n </div>\n </FlowStep>\n <FlowStep title=\"4\">\n <p><strong className=\"text-foreground\">MCP 协议 · 自动调用</strong>(LLM → 驰声)</p>\n <p className=\"text-muted-foreground mt-1\">大模型自动按 MCP 标准构造 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools/call</code> 请求,把录音链接和参考文本发到驰声服务。<span className=\"text-foreground\">开发者不需要写任何调用代码。</span></p>\n </FlowStep>\n <FlowStep title=\"5\">\n <p><strong className=\"text-foreground\">驰声服务处理</strong>(评测引擎)</p>\n <p className=\"text-muted-foreground mt-1\">驰声对音频进行考试级打分,把结构化结果通过 MCP 原路返回:<code className=\"bg-muted px-1 rounded text-xs font-mono\">overall 85</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">/θ/ 音发音错误</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">流利度 88</code>。</p>\n </FlowStep>\n <FlowStep title=\"6\">\n <p><strong className=\"text-foreground\">大模型整合并输出反馈</strong>(LLM)</p>\n <p className=\"text-muted-foreground mt-1\">大模型把冷冰冰的分数翻译成温柔反馈:</p>\n <div className=\"text-foreground/80 mt-2 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed italic\">\n 「你的自我介绍很棒!综合 85 分 🎉 不过 <strong>think</strong> 这个词的 /θ/ 咬舌音不太标准,我们一起来练一下……」\n </div>\n </FlowStep>\n </div>\n <Callout type=\"tip\">\n <strong>整条链路里,MCP 只出现在 ① 和 ④</strong>:① 让开发者一次性告诉大模型「你多了一个工具」,④ 让大模型可以自动调用它。这两步的标准化,就是 MCP 的全部价值。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"dev-responsibility\" title=\"开发者只做两件事\">\n <p>听起来复杂,但对开发者来说只需要两件事:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">1</span>\n <span className=\"text-sm font-semibold\">填一次 MCP 配置</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 在 AI 后端的配置里填入驰声 MCP Server 地址和 API Key,就这一次,以后新加评测能力完全免开发。\n </p>\n <CodeBlock filename=\"mcp-config.json\" lang=\"json\">{`{\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"apiKey\": \"sk-••••••••••\"\n }\n}`}</CodeBlock>\n </div>\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">2</span>\n <span className=\"text-sm font-semibold\">写一段 System Prompt</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 告诉大模型这个工具是做什么的、什么时候调用、结果怎么转成用户友好的反馈。\n </p>\n <CodeBlock filename=\"system-prompt.md\">{`你是一位英语口语教练。\n当用户发来录音时:\n1. 调用 chivox_voice_eval 评分\n2. 聚焦 <70 分的音素家族\n3. 用高情商话术给出练习建议`}</CodeBlock>\n </div>\n </div>\n <p className=\"text-muted-foreground\">\n 其余所有事情 —— 音频上传、鉴权、工具发现、调用时机、返回格式、结果整合 —— 都由 MCP 协议和大模型自己搞定。\n </p>\n </SubDoc>\n\n <SubDoc id=\"integration-paths\" title=\"三种典型接入姿势\">\n <p>根据你的技术栈,有三种等价的接入方式,底层都走完全相同的 MCP 协议:</p>\n\n {/* ── 姿势一:可视化 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Sparkles className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势一 · 可视化拖拽(扣子 / Coze / 飞书智能助手)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合产品经理、运营、不写后端代码的开发者。整个过程像在手机上装 App。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>找到入口</strong>:在扣子平台的 <strong>「插件 / 工具」</strong> 配置区,点击<strong>「添加 MCP 服务」</strong>。</li>\n <li><strong>填两个字段</strong>(这也是你未来会从驰声这边拿到的):\n <div className=\"mt-2 overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium w-32\">服务器地址</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权密钥</td><td className=\"py-2 px-3 font-mono text-xs\">sk-•••••••••• <span className=\"text-muted-foreground ml-2\">(由驰声分配,用于计费与识别)</span></td></tr>\n </tbody>\n </table>\n </div>\n </li>\n <li><strong>平台自动发现能力</strong>:扣子会访问你填的地址,读取 MCP Server 自描述的\"说明书\"。瞬间明白:「这是个语音评测工具,需要传音频,会返发音分」。</li>\n <li><strong>右侧测试</strong>:在平台的聊天框里发一段语音,大模型就会自动调用工具。<strong>整个过程不需要写一行代码。</strong></li>\n </ol>\n </div>\n\n {/* ── 姿势二:代码框架 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Code2 className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势二 · 代码框架配置(Dify / LangChain / 自研后端)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合做独立 APP / 小程序、用 Dify 或 LangChain 写 Agent 的极客开发者。核心就是往配置文件里加一段 JSON。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>打开配置文件</strong>(项目根目录下的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp.json</code> 或框架指定的配置路径)。</li>\n <li><strong>在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code> 下增加一个节点</strong>:\n <CodeBlock filename=\"mcp.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"env\": {\n \"API_KEY\": \"你的专属密钥\"\n }\n }\n }\n}`}</CodeBlock>\n </li>\n <li><strong>重启后端服务</strong>。大模型的底层调度器会在启动时自动连上驰声 MCP Server,把 16 种评测工具注册成<strong>随时待命</strong>的 tools。</li>\n <li><strong>触发调用</strong>:用户发来录音后,大模型看到 system prompt 的引导,自动选择合适的工具(单词/句子/段落/半开放)调用。</li>\n </ol>\n </div>\n\n {/* ── 姿势三:Function Calling 直连(分 A / B 两种做法) ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Terminal className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势三 · 原生 Function Calling(豆包 / DeepSeek / OpenAI 兼容 API)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合不用任何 Agent 框架、直接调用 LLM API 的后端开发者。豆包 1.5+ / DeepSeek / GPT / Qwen 等模型都走标准 <code className=\"bg-muted px-1 rounded text-xs font-mono\">chat.completions</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> 参数。这里又分 A / B 两种做法,<strong>B 才是生产推荐</strong>。\n </p>\n\n <Callout type=\"warning\">\n 常见疑问:<em>\"要不要手写一个 tool 定义?\"</em><br />\n — <strong>姿势一 / 二不用</strong>(Coze、Dify、火山方舟控制台、Claude Desktop 会自动从 MCP Server 拉工具列表)。<br />\n — <strong>姿势三看你选 A 还是 B</strong>:A 要手写,B 用 MCP 客户端库自动拉。\n </Callout>\n\n {/* ── 3-A:手写 tool schema ── */}\n <div className=\"mt-4 rounded-md border border-border/40 bg-muted/20 p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold\">3-A · 手写 tool schema(最快验证,不推荐生产)</p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20\">快速 Demo</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 自己写一段 JSON Schema 告诉豆包\"有个工具叫 xxx,参数长这样\",豆包返回 tool_call 后你再手动去调驰声 MCP。<strong>缺点</strong>:驰声有 16 个评测工具,你每加一个题型都要改代码。\n </p>\n <CodeBlock filename=\"3a_hardcode_tool.py\" lang=\"python\">{`import os, json\nfrom openai import OpenAI\n\nclient = OpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv('ARK_API_KEY'),\n)\n\n# ─── 手写一段 tool schema(不推荐:16 个工具就要写 16 段)───\ntools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英语单词发音评测。当用户让你评价英语发音并提供录音链接时调用。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"audio_url\": {\"type\": \"string\", \"description\": \"录音 URL\"},\n \"reference_text\": {\"type\": \"string\", \"description\": \"参考文本,如 Apple\"},\n },\n \"required\": [\"audio_url\", \"reference_text\"]\n }\n }\n}]\n\nresp = client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,拿到评测结果后温柔地反馈,不要直接报分数。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n)\n\nmsg = resp.choices[0].message\nif msg.tool_calls:\n args = json.loads(msg.tool_calls[0].function.arguments)\n # ↓↓↓ 这里手动调驰声 MCP,再把结果回填给豆包做第二趟 ↓↓↓\n # result = requests.post(\"https://speech-eval.site/mcp\", json={...})`}</CodeBlock>\n </div>\n\n {/* ── 3-B:MCP 自动发现 ── */}\n <div className=\"mt-3 rounded-md border border-foreground/30 bg-foreground/[0.03] dark:bg-white/[0.03] p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold flex items-center gap-1.5\">\n 3-B · 用 MCP 客户端库自动发现 <Sparkles className=\"h-3 w-3 text-foreground\" />\n </p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-foreground text-background\">生产推荐</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 启动时连上驰声 MCP Server,调一次 <code className=\"bg-muted px-1 rounded\">tools/list</code>,把 16 个评测工具自动转成 Function Calling 的 tools 格式。<strong>驰声加新工具,你代码不用改</strong>。这才是 MCP 协议的真正价值。\n </p>\n <CodeBlock filename=\"3b_mcp_autodiscover.py\" lang=\"python\">{`import asyncio, os\nfrom openai import AsyncOpenAI\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\n\nclient = AsyncOpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv(\"ARK_API_KEY\"),\n)\n\nasync def main():\n # ─── 1. 连驰声 MCP Server,自动拉取全部 16 个工具 ───\n async with streamablehttp_client(\n \"https://speech-eval.site/mcp\",\n headers={\"Authorization\": f\"Bearer {os.getenv('CHIVOX_KEY')}\"},\n ) as (read, write, _):\n async with ClientSession(read, write) as session:\n await session.initialize()\n mcp_tools = (await session.list_tools()).tools # ← 一行拿到全部工具\n\n # ─── 2. MCP schema → OpenAI Function Calling schema ───\n tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema, # MCP 直接给 JSON Schema\n }\n } for t in mcp_tools]\n\n # ─── 3. 正常调豆包,tools 由 MCP 动态注入 ───\n resp = await client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,收到评测结果后温柔反馈。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n )\n\n # ─── 4. 折返跑:豆包选好工具后,直接走 MCP 调驰声 ───\n msg = resp.choices[0].message\n if msg.tool_calls:\n call = msg.tool_calls[0]\n result = await session.call_tool(\n call.function.name,\n arguments=__import__(\"json\").loads(call.function.arguments),\n )\n # 把 result 回填给豆包做第二趟 chat.completions 即可\n\nasyncio.run(main())`}</CodeBlock>\n <p className=\"text-[11px] text-muted-foreground mt-3 leading-relaxed\">\n 想要更省心?直接用 <a href=\"https://github.com/modelcontextprotocol/python-sdk\" target=\"_blank\" rel=\"noreferrer\" className=\"text-foreground underline underline-offset-2\">官方 mcp python sdk</a>、<code className=\"bg-muted px-1 rounded text-xs\">mcp-use</code>、<code className=\"bg-muted px-1 rounded text-xs\">openai-agents</code>,或火山官方 <code className=\"bg-muted px-1 rounded text-xs\">Arkitect</code> 高代码 SDK,它们都把上面这段\"MCP → Function Calling\"的桥接封装好了。\n </p>\n </div>\n </div>\n\n <Callout type=\"info\">\n 三种姿势走的是完全相同的 MCP 协议,能力完全等价。选型建议:<strong>产品经理 / 原型验证用姿势一</strong>(Coze 填表单);<strong>AI 客户端用户用姿势二</strong>(Cursor / Claude Desktop 填 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">mcp.json</code>);<strong>后端生产服务用姿势三-B</strong>(MCP 客户端库自动发现工具,不要手写 tool schema)。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 架构说明 ══════ */}\n <DocSection id=\"architecture\" icon={Terminal} title=\"架构说明\">\n\n <SubDoc id=\"arch-diagram\" title=\"系统架构\">\n <div className=\"flex flex-wrap items-center justify-center gap-4 my-6\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Claude Desktop<br/>Claude Code<br/>Cursor</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">stdio</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-1\">chivox-local-mcp</p>\n <p className=\"text-[11px] text-muted-foreground\">本地代理<br/>SoX 录音</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP / WS</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">Remote Chivox</p>\n <p className=\"text-[11px] text-muted-foreground\">MCP Server<br/>评测引擎</p>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"transport\" title=\"传输协议\">\n <ul className=\"space-y-3\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>stdio</strong> — AI 客户端通过标准输入输出与本地代理通信,这是 MCP 的标准传输方式</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>HTTP</strong> — 音频文件评测工具通过 HTTP 代理到远程服务,适用于非流式场景</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>WebSocket</strong> — 实时录音音频通过 WebSocket 流式推送,支持断线自动重连</span>\n </li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-local-mcp</strong> 是驰声语音评测的 MCP 本地代理,通过 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 将远程驰声评测服务桥接到本地,让 AI 助手(Claude Desktop、Claude Code、Cursor 等)直接调用语音评测能力。</p>\n <p>它支持两种评测模式,覆盖从即时交互到批量处理的全部场景:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Mic className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">实时录音评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">通过麦克风边录边评,音频以 WebSocket 流式推送,无需生成中间文件,延迟更低、体验更佳。</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <FolderOpen className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">音频文件评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">支持本地文件路径、Base64 编码、URL 三种输入,适合已有音频的批量评测场景。</p>\n </div>\n </div>\n <Callout type=\"info\">当前支持 <strong>16 种评测工具</strong>:英文 10 种(单词、句子、段落、纠音、自然拼读、口语选择、半开放题等)+ 中文 6 种(汉字、拼音、词句、段落、有限分支、AI Talk)。</Callout>\n </SubDoc>\n\n <SubDoc id=\"requirements\" title=\"系统要求\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">依赖</th>\n <th className=\"text-left py-2 px-3 font-medium\">版本</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-semibold\">Node.js</td><td className=\"py-2 px-3 font-mono text-xs\">{'>'}= 18</td><td className=\"py-2 px-3 text-muted-foreground\">运行时必须</td></tr>\n <tr><td className=\"py-2 px-3 font-semibold\">SoX</td><td className=\"py-2 px-3 font-mono text-xs\">any</td><td className=\"py-2 px-3 text-muted-foreground\">仅实时录音功能需要(处理麦克风音频流)</td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"mt-3\">安装 SoX(仅在使用实时录音时需要):</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# macOS\nbrew install sox\n\n# Ubuntu / Debian\nsudo apt-get install sox`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"install\" title=\"安装方式\">\n <p><strong>方式一:npm 全局安装</strong>(推荐)</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`npm install -g chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式二:npx 免安装运行</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`MCP_REMOTE_URL=http://your-server:8080 npx chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式三:从源码构建</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`git clone https://git.chivox.com/CLOUD_DEV/cvx_local_mcp.git\ncd cvx_local_mcp\nbash scripts/build.sh`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"env-vars\" title=\"环境变量\">\n <ParamTable params={[\n { name: 'MCP_REMOTE_URL', type: 'string', required: true, desc: '远程驰声 MCP 服务地址,如 http://your-server:8080' },\n { name: 'MCP_API_KEY', type: 'string', required: false, desc: 'API 认证密钥(如已配置鉴权则必填)' },\n ]} />\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 直接用(零代码) ══════ */}\n <DocSection id=\"config\" icon={Plug} title=\"接入 · 直接用 MCP(零代码)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>不用写代码、不用装 SDK,只在客户端的<strong className=\"text-foreground\">「MCP 配置」</strong>或<strong className=\"text-foreground\">「扩展」</strong>里填一段 JSON / URL</li>\n <li>保存后 LLM 自动发现 16 个评测工具,并在对话 / Agent 中自主调用</li>\n <li>覆盖 <strong className=\"text-foreground\">IDE 编程助手</strong>、<strong className=\"text-foreground\">桌面 AI 客户端</strong>、<strong className=\"text-foreground\">企业 AI 工作台</strong>、<strong className=\"text-foreground\">可视化 Bot / 工作流平台</strong>四大生态</li>\n </ul>\n </div>\n\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置并保存:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <Callout type=\"tip\">Cursor 使用远程 Streamable HTTP 模式直连,无需安装本地代理。如需实时录音功能,请使用下方 Claude Desktop 的本地代理配置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-desktop\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件(macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"chivox-local-mcp\",\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\",\n \"MCP_API_KEY\": \"your-api-key\"\n }\n }\n }\n}`}</CodeBlock>\n <p>如果是从源码构建:</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/cvx_local_mcp/dist/index.js\"],\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\"\n }\n }\n }\n}`}</CodeBlock>\n <Callout type=\"info\">Claude Desktop 使用本地代理模式(stdio),支持实时录音评测。本地代理会自动桥接远程服务。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-code\" title=\"Claude Code 配置\">\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# npm 全局安装后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 chivox-local-mcp\n\n# 源码构建后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 node /path/to/cvx_local_mcp/dist/index.js`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-ai-ide\" title=\"Windsurf / Zed / Continue 等 IDE 编程助手\">\n <p>除了 Cursor 和 Claude Code,主流的 AI 编程 IDE 都原生支持 MCP。以下客户端使用<strong>完全一致</strong>的 Streamable HTTP 配置,区别只在配置入口:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">客户端</th>\n <th className=\"text-left py-2 px-3 font-medium\">配置入口</th>\n <th className=\"text-left py-2 px-3 font-medium\">备注</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">Windsurf (Codeium)</td><td className=\"py-2 px-3 text-xs\">Settings → Cascade → MCP Servers</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">直接贴下方 JSON</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Zed</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">~/.config/zed/settings.json</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">context_servers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">Assistant Panel 自动发现</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Continue (VS Code / JetBrains)</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">config.yaml</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">v0.9+ 支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">通义灵码 / CodeGeeX / 文心快码</td><td className=\"py-2 px-3 text-xs\">插件设置 → MCP 服务</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">2026 Q1 起陆续支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Cline (原 Claude Dev)</td><td className=\"py-2 px-3 text-xs\">MCP Servers 面板 → 编辑配置</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">VS Code 扩展</td></tr>\n </tbody>\n </table>\n </div>\n <CodeBlock filename=\"通用 MCP 配置(复制即用)\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox-speech-eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n }\n }\n}`}</CodeBlock>\n <Callout type=\"tip\">所有 IDE 类客户端都走远程 Streamable HTTP,零本地依赖。需要实时录音才需切到 Claude Desktop 的本地代理模式。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-coze\" title=\"扣子(Coze)配置\">\n <p><a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子(Coze)</a>是字节跳动推出的 AI 应用开发平台,支持通过 MCP 扩展为 Agent 赋予外部工具能力。接入驰声语音评测后,你可以构建口语练习 Bot、发音诊断助手等应用。</p>\n <p className=\"font-semibold mt-4\">方式一:扣子空间 MCP 扩展(推荐)</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 <a href=\"https://space.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子空间</a>(space.coze.cn)</li>\n <li>在对话框下方点击 <strong>「添加扩展」</strong></li>\n <li>选择 <strong>「自定义」</strong> 标签,点击添加新的 MCP 服务</li>\n <li>填入以下配置信息:</li>\n </ol>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">名称</td><td className=\"py-2 px-3 font-mono text-xs\">chivox-speech-eval</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>保存后,在对话中输入 <strong>「帮我评测这段英文发音」</strong> 即可触发 Agent 调用</li>\n </ol>\n <Callout type=\"tip\">添加 MCP 扩展后,扣子空间的 Agent 会自动识别并调用评测工具。你可以在 Agent 的思考过程中看到具体的工具调用细节。</Callout>\n\n <p className=\"font-semibold mt-6\">方式二:在 Coze 开发平台的 Bot 中使用</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>进入 <a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Coze 开发平台</a>,创建或编辑 Bot</li>\n <li>在 Bot 的 <strong>「插件」</strong> 或 <strong>「工具」</strong> 面板中,添加 MCP 工具</li>\n <li>选择 <strong>Streamable HTTP</strong> 类型,填入 URL:<code className=\"bg-muted px-1 rounded text-xs font-mono\">https://speech-eval.site/mcp</code></li>\n <li>Bot 将自动发现所有可用的评测工具(如 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_word</code> 等)</li>\n <li>在 Bot 的人设提示词中,添加语音评测相关的引导语</li>\n </ol>\n <CodeBlock filename=\"Bot 人设提示词示例\">{`你是一位专业的英语口语教练。当用户上传音频并请求评测时:\n1. 使用 evaluate_english_word / evaluate_english_sentence 等工具进行评测\n2. 根据评测结果(overall、accuracy、fluency 等分数)给出专业分析\n3. 针对得分较低的维度提供具体改进建议\n4. 鼓励用户持续练习`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze-workflow\" title=\"Coze 工作流接入\">\n <p>如果你需要在 Coze 工作流中集成语音评测能力,可以通过 <strong>HTTP Request 节点</strong>调用评测 API:</p>\n\n <p className=\"font-semibold mt-4\">步骤 1:上传音频</p>\n <p>在工作流中添加一个 HTTP Request 节点,配置如下:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">方法</td><td className=\"py-2 px-3 font-mono text-xs\">POST</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/upload</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Content-Type</td><td className=\"py-2 px-3 font-mono text-xs\">audio/mp3</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Body</td><td className=\"py-2 px-3 text-muted-foreground\">音频文件二进制数据</td></tr>\n </tbody>\n </table>\n </div>\n <p>响应中获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{{audioId}}'}</code>,传递给下一步。</p>\n\n <p className=\"font-semibold mt-4\">步骤 2:调用评测</p>\n <p>添加第二个 HTTP Request 节点,通过 MCP 接口调用评测工具:</p>\n <CodeBlock filename=\"HTTP Request Body\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"evaluate_english_sentence\",\n \"arguments\": {\n \"audioId\": \"{{audioId}}\",\n \"sentence\": \"{{ref_text}}\"\n }\n },\n \"id\": 1\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">步骤 3:处理结果</p>\n <p>评测结果通过 JSON 格式返回,可使用 Coze 的 <strong>代码节点</strong> 或 <strong>LLM 节点</strong> 对结果进行解析和二次处理(如生成学习建议、计算进步趋势等)。</p>\n\n <Callout type=\"info\">工作流方式适合需要固定流程的场景(如批量评测、定时任务、与其他系统联动)。如果是交互式场景,推荐使用上方的 MCP 扩展方式,Agent 会自动选择合适的工具。</Callout>\n\n <div className=\"rounded-lg bg-muted/30 p-5 mt-4\">\n <p className=\"text-xs font-semibold mb-3\">完整工作流示意</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">用户输入音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 上传音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 调用评测</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM: 分析结果</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">输出反馈</span>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"config-ai-workspace\" title=\"AI 工作台 · 豆包 / 飞书智能伙伴 / WorkBuddy / 钉钉 AI\">\n <p>这一类是<strong>面向「上班族」的企业 / 日常 AI 助手</strong>,产品形态是个聊天框 + 插件市场 / 扩展面板。接入后,员工在 App 里打字「帮我评一下这段录音」就能自动触发驰声评测,不用写任何代码。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Bot className=\"h-4 w-4 text-amber-500\" /> 豆包(Doubao)App / 桌面版</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节跳动官方 AI 助手,2026 年起桌面版支持 MCP 扩展。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>桌面版设置 → <strong>「智能体扩展」</strong> → 添加 MCP 服务</li>\n <li>类型选 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">streamable-http</code>,URL 填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>对话中直接说「帮我评测」即可自动调用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Building2 className=\"h-4 w-4 text-blue-500\" /> 飞书智能伙伴(My AI)</p>\n <p className=\"text-xs text-muted-foreground mb-2\">飞书企业 AI,支持以 MCP / Function Calling 挂载外部工具给团队 Bot。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>飞书开发者后台 → 创建应用 → <strong>「AI 能力 / 智能伙伴」</strong></li>\n <li>在「工具」中选 <strong>外部 MCP 服务</strong>,填入上方 URL</li>\n <li>把智能伙伴拉进群聊,@ 它提交音频即可评测</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-rose-500\" /> WorkBuddy / 企业智能助手</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节 WorkBuddy、腾讯会议 AI、企业微信智能助手等企业 AI 工作台场景。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>在工作台的<strong>「技能 / 扩展」</strong>后台新增一个 MCP 连接器</li>\n <li>传输协议选 Streamable HTTP,地址填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>把技能发布到指定部门 / 群组,HR、培训、销售口语陪练场景即可直接用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Workflow className=\"h-4 w-4 text-cyan-500\" /> 钉钉 AI 助理 / 通义千问 App</p>\n <p className=\"text-xs text-muted-foreground mb-2\">阿里生态:钉钉 AI 助理支持自定义插件,通义 App 可通过「AI 工具箱」挂载 MCP。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>钉钉开发者后台 → AI 助理 → <strong>「插件开发 / MCP 接入」</strong></li>\n <li>选 HTTP Streamable,填入驰声 MCP URL 并开启鉴权(可选)</li>\n <li>在钉钉群 @AI 助理上传音频,自动返回评测卡片</li>\n </ol>\n </div>\n </div>\n\n <Callout type=\"tip\"><strong>典型场景 · 小龙虾们的共同剧本</strong>:销售口语陪练 → 外语培训 → 主持 / 播音岗位考核 → K12 英语作业批改 → 客服话术复盘。这些「上班族 AI 工作台」接入驰声 MCP 后,员工 / 学员只要对着话筒说话,AI 就能自动打分并给出个性化改进建议,运营 / 培训 / 教研团队零开发即可上线。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-workflow-platforms\" title=\"Dify / n8n / Flowise 等可视化编排\">\n <p>如果你需要把语音评测编排进<strong>多步业务流</strong>(例如:学员录音 → 评测 → 生成错题集 → 推送到 CRM),可视化工作流平台比聊天 Bot 更合适。</p>\n\n <div className=\"grid gap-3 md:grid-cols-3 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Dify</p>\n <p className=\"text-xs text-muted-foreground mb-2\">开源 LLMOps,v0.15+ 原生 MCP 工具。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>「工具」→「自定义 MCP」→ 填 URL</li>\n <li>在 Chatflow / Workflow 节点里直接拖用</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">n8n</p>\n <p className=\"text-xs text-muted-foreground mb-2\">工作流自动化,用 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">MCP Client</code> 节点。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>添加 <strong>MCP Client</strong> 节点</li>\n <li>Transport 选 HTTP Stream,URL 贴驰声地址</li>\n <li>Tool 下拉自动列出 16 个评测能力</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Flowise / LangFlow</p>\n <p className=\"text-xs text-muted-foreground mb-2\">可视化 LangChain Agent 编排。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>拖出 <strong>MCP Tool</strong> 节点</li>\n <li>连到 AgentExecutor 即可</li>\n </ul>\n </div>\n </div>\n\n <Callout type=\"info\">可视化平台的好处是<strong>流程可追溯、可回放</strong>,适合教育机构 / 培训部门做批量评测 + 报表导出;缺点是单步延迟略高于直接 MCP 调用,对延迟敏感的交互场景建议用 Cursor / Claude Desktop 直连。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"其他 MCP 客户端(通用配置)\">\n <p>任何遵循 MCP 协议的客户端都可以接入,统一使用以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权(可选)</td><td className=\"py-2 px-3 font-mono text-xs\">Header:<code>Authorization: Bearer &lt;MCP_API_KEY&gt;</code></td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">还没找到你用的客户端?任何支持 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 的终端都通用。找不到入口?下一小节「编程用 MCP」里用 SDK 直接接即可。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 编程用 MCP ══════ */}\n <DocSection id=\"config-code\" icon={Code2} title=\"接入 · 编程用 MCP(SDK / 代码集成)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Terminal className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>你在写一段<strong className=\"text-foreground\">真正的后端服务</strong>:一个 Agent Worker、一个 API、一个自动化脚本</li>\n <li>业务里的「语音评测」是其中一个步骤,需要由代码 <strong className=\"text-foreground\">决定何时调用</strong>、<strong className=\"text-foreground\">如何解析结果</strong></li>\n <li>三种主流写法:① 官方 MCP 客户端库 → ② Agent 框架自动桥接 → ③ 退化到 chat.completions + tools 参数</li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 强烈推荐①或②:驰声有 16 个评测工具,<strong className=\"text-foreground\">让 MCP 自动注入 tool schema</strong>,每新增一个题型不用改一行业务代码。</p>\n </div>\n\n <SubDoc id=\"code-mcp-sdk\" title=\"① 官方 MCP 客户端库(Python / TypeScript)\">\n <p>最底层、也最推荐的方式。由 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Anthropic 官方维护</a>,一次连接自动拿到全部工具的 JSON Schema,然后交给任意 LLM。</p>\n\n <p className=\"font-semibold mt-4\">Python:<code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp</code></p>\n <CodeBlock filename=\"chivox_client.py\" lang=\"python\">{`# pip install mcp openai\nimport asyncio, os\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nMCP_URL = \"https://speech-eval.site/mcp\"\nllm = OpenAI(api_key=os.getenv(\"DOUBAO_KEY\"),\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\") # 豆包 / DeepSeek / OpenAI 同构\n\nasync def main(audio_id: str, ref: str):\n async with streamablehttp_client(MCP_URL) as (reader, writer, _):\n async with ClientSession(reader, writer) as sess:\n await sess.initialize()\n\n tools = await sess.list_tools()\n openai_tools = [{\n \"type\": \"function\",\n \"function\": {\"name\": t.name, \"description\": t.description,\n \"parameters\": t.inputSchema},\n } for t in tools.tools]\n\n resp = llm.chat.completions.create(\n model=\"doubao-1-5-pro-32k\",\n messages=[{\"role\": \"user\",\n \"content\": f\"帮我评测 audioId={audio_id},参考文本:{ref}\"}],\n tools=openai_tools,\n )\n\n for call in resp.choices[0].message.tool_calls or []:\n result = await sess.call_tool(call.function.name,\n arguments=eval(call.function.arguments))\n print(call.function.name, \"→\", result.content[0].text[:200])\n\nasyncio.run(main(\"abc123\", \"I think therefore I am\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">TypeScript / Node.js:<code className=\"bg-muted px-1 rounded text-xs font-mono\">@modelcontextprotocol/sdk</code></p>\n <CodeBlock filename=\"chivox-client.ts\" lang=\"typescript\">{`// npm i @modelcontextprotocol/sdk openai\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport OpenAI from \"openai\";\n\nconst mcp = new Client({ name: \"chivox-biz\", version: \"1.0.0\" });\nawait mcp.connect(new StreamableHTTPClientTransport(new URL(\"https://speech-eval.site/mcp\")));\n\nconst { tools } = await mcp.listTools();\nconst openai = new OpenAI({ apiKey: process.env.DEEPSEEK_KEY, baseURL: \"https://api.deepseek.com\" });\n\nconst resp = await openai.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: \"评测 audioId=abc123, 参考 I think therefore I am\" }],\n tools: tools.map(t => ({ type: \"function\", function: {\n name: t.name, description: t.description, parameters: t.inputSchema\n }})),\n});\n\nfor (const call of resp.choices[0].message.tool_calls ?? []) {\n const r = await mcp.callTool({ name: call.function.name,\n arguments: JSON.parse(call.function.arguments) });\n console.log(call.function.name, r.content);\n}`}</CodeBlock>\n <Callout type=\"tip\">这种写法 <strong>LLM 供应商可换</strong>(豆包 / DeepSeek / GPT / Qwen / Moonshot),<strong>工具自动更新</strong>(驰声新增题型无需改业务代码),适合生产后端。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-agent-frameworks\" title=\"② 用 Agent 框架自动桥接(LangChain / Mastra / Agents SDK)\">\n <p>如果项目已经在用 Agent 框架,几行 adapter 就能把驰声 MCP 塞进去,框架本身会接管 tool-calling 循环、重试、日志。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LangChain / LangGraph(Python)</p>\n <CodeBlock filename=\"agent.py\" lang=\"python\">{`# pip install langchain-mcp-adapters langgraph\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom langgraph.prebuilt import create_react_agent\n\nclient = MultiServerMCPClient({\n \"chivox\": {\n \"transport\": \"streamable_http\",\n \"url\": \"https://speech-eval.site/mcp\",\n }\n})\ntools = await client.get_tools()\n\nagent = create_react_agent(\"openai:gpt-4o-mini\", tools)\nresult = await agent.ainvoke({\"messages\":\n [(\"user\", \"评测 audioId=abc123 参考 think\")]})\nprint(result[\"messages\"][-1].content)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Mastra(TypeScript)</p>\n <CodeBlock filename=\"mastra-agent.ts\" lang=\"typescript\">{`// npm i @mastra/core @mastra/mcp\nimport { MCPClient } from \"@mastra/mcp\";\nimport { Agent } from \"@mastra/core/agent\";\nimport { openai } from \"@ai-sdk/openai\";\n\nconst mcp = new MCPClient({ servers: {\n chivox: { url: new URL(\"https://speech-eval.site/mcp\") }\n}});\n\nexport const speechCoach = new Agent({\n name: \"speech-coach\",\n instructions: \"你是口语教练,用 chivox 工具评测并给改进建议。\",\n model: openai(\"gpt-4o-mini\"),\n tools: await mcp.getTools(),\n});`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">OpenAI Agents SDK(Python)</p>\n <CodeBlock filename=\"agents_sdk.py\" lang=\"python\">{`# pip install openai-agents\nfrom agents import Agent, Runner\nfrom agents.mcp import MCPServerStreamableHttp\n\nchivox = MCPServerStreamableHttp(\n params={\"url\": \"https://speech-eval.site/mcp\"},\n name=\"chivox-speech-eval\",\n)\n\nasync with chivox:\n agent = Agent(name=\"coach\",\n instructions=\"专业口语教练\",\n mcp_servers=[chivox])\n r = await Runner.run(agent, \"评测 audioId=abc123\")\n print(r.final_output)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LlamaIndex / AutoGen / CrewAI</p>\n <p className=\"text-xs text-muted-foreground mb-2\">这些框架都有官方或社区 MCP adapter,用法与上述类似:</p>\n <ul className=\"list-disc list-inside space-y-1 text-xs\">\n <li><strong>LlamaIndex</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">llama-index-tools-mcp</code></li>\n <li><strong>AutoGen</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">autogen-ext[mcp]</code></li>\n <li><strong>CrewAI</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">crewai-tools</code> 的 MCPServerAdapter</li>\n <li><strong>Spring AI</strong>(Java):<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">spring-ai-mcp-client</code></li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"code-function-calling\" title=\"③ 直调 chat.completions · 上传与动态 tools(豆包 / DeepSeek / 等)\">\n <p>\n DeepSeek、GLM(智谱)、KIMI(Moonshot)、豆包(火山方舟)、千帆、DashScope 等若提供 <strong>OpenAI 兼容的 HTTP API</strong>,接入驰声 MCP 的套路一致:用 MCP 客户端拉取工具列表 → 转为{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> → 模型决策 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tool_calls</code> → MCP 执行并回填 → 再让模型生成诊断话术。\n </p>\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">① MCP list_tools()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">② 转 OpenAI tools 格式</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">③ LLM 决策 tool_calls</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">④ MCP call_tool()</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">⑤ 评测结果回填 → LLM 诊断</span>\n </div>\n </div>\n <Callout type=\"tip\">各厂商仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 与 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 与 tools 桥接代码可原样复用。</Callout>\n\n <p className=\"font-semibold mt-6\">先上传音频,拿到 audioId</p>\n <p className=\"text-sm text-muted-foreground mb-3\">文件评测工具通常需要 <code className=\"bg-muted px-1 rounded text-xs font-mono\">audioId</code>(或路径参数,视工具而定)。下面是用 HTTP 上传的最小示例:</p>\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://speech-eval.site/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst res = await fetch('https://speech-eval.site/upload', {\n method: 'POST',\n headers: { 'Content-Type': 'audio/mp3' },\n body: audioData,\n});\n\nconst result = await res.json();\nconsole.log('audioId:', result.audioId);`}</CodeBlock>\n\n <p className=\"font-semibold mt-6\">再以豆包为例:MCP 动态 tools + chat.completions 折返跑</p>\n <p>当你用的 LLM SDK <strong>还没有原生 MCP 支持</strong>(比如老版本的火山方舟 / 百度千帆 / 通义 DashScope),退化方案是:用 MCP 客户端<strong>一次性拉到 tool schema</strong>,然后按 OpenAI Function Calling 格式喂给 LLM。</p>\n <CodeBlock filename=\"doubao_tools.py\" lang=\"python\">{`# 适用:豆包(火山方舟)/ DeepSeek / Qwen / Moonshot / GLM 等 OpenAI 兼容 API\n# 核心思路:MCP 列工具 → 转 OpenAI tools → LLM 决策 → MCP 执行 → 回填\n\nasync with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n\n # ① 用 MCP 拉到的 schema 自动转 OpenAI 格式\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 喂给豆包\n messages = [{\"role\": \"user\", \"content\": \"评测一下 audioId=abc123\"}]\n resp = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages, tools=oa_tools,\n )\n\n # ③ LLM 回 tool_calls → 折返跑到 MCP 执行\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for c in msg.tool_calls:\n result = await mcp.call_tool(\n c.function.name, arguments=json.loads(c.function.arguments))\n messages.append({\"role\": \"tool\", \"tool_call_id\": c.id,\n \"content\": result.content[0].text})\n # ④ 再喂一轮,让豆包根据评测结果产出自然语言诊断\n final = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages)\n print(final.choices[0].message.content)`}</CodeBlock>\n <Callout type=\"warning\"><strong>千万别手写 tool schema!</strong>驰声共 <strong>16 个评测工具</strong>(英文 10 + 中文 6),每个工具参数较多,手写既容易错也难维护。一定用 MCP <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">list_tools()</code> 动态拿。</Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-deepseek\" title=\"DeepSeek\">\n <p>DeepSeek 提供与 OpenAI 完全兼容的 API,推荐使用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-chat</code>(V3)或 <code className=\"bg-muted px-1 rounded text-xs font-mono\">deepseek-reasoner</code>(R1)。</p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"deepseek_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\n# DeepSeek 客户端(OpenAI 兼容)\nclient = OpenAI(\n api_key=os.environ[\"DEEPSEEK_API_KEY\"],\n base_url=\"https://api.deepseek.com\",\n)\n\nasync def evaluate_with_deepseek(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n\n # ① 拉取驰声 MCP 工具列表\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 调用 DeepSeek,让它选择合适的评测工具\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"deepseek-chat\", # 或 deepseek-reasoner\n messages=messages,\n tools=oa_tools,\n )\n\n # ③ 执行 tool_calls\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n\n # ④ 让 DeepSeek 根据评测结果生成自然语言诊断\n final = client.chat.completions.create(\n model=\"deepseek-chat\",\n messages=messages,\n )\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_deepseek(\"audio_abc123\", \"Hello, nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\"><strong>推荐使用 deepseek-chat(V3)</strong>处理评测结果诊断,速度快、成本低;若需要深度分析或出题逻辑,可换用 deepseek-reasoner(R1),但注意 R1 不支持 Function Calling,需先用 V3 做工具调用再将结果交给 R1 分析。</Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-glm\" title=\"GLM(智谱 AI)\">\n <p>智谱 AI 提供 <code className=\"bg-muted px-1 rounded text-xs font-mono\">glm-4</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">glm-4-flash</code>(免费)等模型,支持 OpenAI 兼容接口,同时也有官方 Python SDK。</p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`ZHIPU_API_KEY=xxxxxxxx.xxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式一:OpenAI 兼容接口(推荐)</p>\n <CodeBlock filename=\"glm_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(\n api_key=os.environ[\"ZHIPU_API_KEY\"],\n base_url=\"https://open.bigmodel.cn/api/paas/v4/\",\n)\n\nasync def evaluate_with_glm(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"glm-4-flash\", # 免费模型;生产可换 glm-4 或 glm-4-plus\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"glm-4-flash\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_glm(\"audio_abc123\", \"How are you today?\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">方式二:官方 zhipuai SDK</p>\n <CodeBlock filename=\"glm_sdk.py\" lang=\"python\">{`from zhipuai import ZhipuAI\n\nclient = ZhipuAI(api_key=os.environ[\"ZHIPU_API_KEY\"])\n\n# 其余逻辑与方式一相同,仅替换 client 实例\nresp = client.chat.completions.create(\n model=\"glm-4-flash\",\n messages=messages,\n tools=oa_tools,\n)`}</CodeBlock>\n <Callout type=\"tip\"><strong>glm-4-flash 免费</strong>,适合开发测试;<strong>glm-4-plus</strong> 上下文更长、指令遵循更准,适合生产环境中的诊断报告生成。</Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-kimi\" title=\"KIMI(Moonshot AI)\">\n <p>Moonshot AI 的 KIMI 模型同样兼容 OpenAI API,支持超长上下文(最高 128k),特别适合需要分析多段录音历史或生成详细学习报告的场景。</p>\n\n <p className=\"font-semibold mt-4 mb-2\">环境准备</p>\n <CodeBlock filename=\".env\" lang=\"bash\">{`MOONSHOT_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\nCHIVOX_APP_KEY=your_chivox_app_key\nCHIVOX_SECRET_KEY=your_chivox_secret_key`}</CodeBlock>\n\n <p className=\"font-semibold mt-4 mb-2\">完整示例(Python)</p>\n <CodeBlock filename=\"kimi_chivox.py\" lang=\"python\">{`import os, json, asyncio\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nclient = OpenAI(\n api_key=os.environ[\"MOONSHOT_API_KEY\"],\n base_url=\"https://api.moonshot.cn/v1\",\n)\n\nasync def evaluate_with_kimi(audio_id: str, ref_text: str):\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n messages = [{\"role\": \"user\", \"content\":\n f\"请评测这段英文录音,audioId={audio_id},参考文本:{ref_text}\"}]\n\n resp = client.chat.completions.create(\n model=\"moonshot-v1-8k\", # 也可选 moonshot-v1-32k / moonshot-v1-128k\n messages=messages,\n tools=oa_tools,\n )\n\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for call in msg.tool_calls:\n result = await mcp.call_tool(\n call.function.name,\n arguments=json.loads(call.function.arguments),\n )\n messages.append({\n \"role\": \"tool\",\n \"tool_call_id\": call.id,\n \"content\": result.content[0].text,\n })\n final = client.chat.completions.create(\n model=\"moonshot-v1-8k\", messages=messages)\n return final.choices[0].message.content\n\nasyncio.run(evaluate_with_kimi(\"audio_abc123\", \"Nice to meet you.\"))`}</CodeBlock>\n <Callout type=\"tip\">KIMI 的长上下文特别适合「多轮口语练习 + 历史分析」场景:把用户过去 10 次的评测 JSON 全部塞进 messages,让 KIMI 生成横跨多次练习的进步报告,这是 8k 模型做不到的。</Callout>\n </SubDoc>\n\n <SubDoc id=\"llm-comparison\" title=\"模型对比速查\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead>\n <tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 pr-4 font-medium\">模型</th>\n <th className=\"text-left py-2 pr-4 font-medium\">推荐版本</th>\n <th className=\"text-left py-2 pr-4 font-medium\">base_url</th>\n <th className=\"text-left py-2 pr-4 font-medium\">Function Calling</th>\n <th className=\"text-left py-2 font-medium\">适合场景</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek V3</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-chat</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">评测调用 + 诊断,性价比最高</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">DeepSeek R1</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">deepseek-reasoner</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.deepseek.com</code></td>\n <td className=\"py-2 pr-4 text-rose-500\">✗ 不支持</td>\n <td className=\"py-2\">深度分析(需先用 V3 调工具)</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4-Flash</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4-flash</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">开发测试(免费额度)</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">GLM-4-Plus</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">glm-4-plus</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">open.bigmodel.cn/…/v4</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">生产诊断报告,指令遵循强</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">KIMI 8k</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">moonshot-v1-8k</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">单次评测 + 即时反馈</td>\n </tr>\n <tr>\n <td className=\"py-2 pr-4 font-medium\">KIMI 128k</td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">moonshot-v1-128k</code></td>\n <td className=\"py-2 pr-4\"><code className=\"bg-muted px-1 rounded font-mono\">api.moonshot.cn/v1</code></td>\n <td className=\"py-2 pr-4 text-emerald-600 dark:text-emerald-400\">✓ 支持</td>\n <td className=\"py-2\">多轮历史 + 学习进度报告</td>\n </tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"info\">所有模型均通过 <strong>OpenAI Python SDK</strong>(<code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">openai</code> 包)调用,仅需替换 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">base_url</code> 和 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">api_key</code>,驰声 MCP 侧代码<strong>无需改动</strong>。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-selfhosted-agent\" title=\"④ 自研后端 Agent(FastAPI / NestJS / Spring)\">\n <p>大部分落地场景其实是:<strong>你已有一个业务后端</strong>(教育 SaaS / 培训系统 / 客服平台),只需要新增一个「语音评测」能力。推荐架构:</p>\n\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">前端录音 / 上传</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">业务后端</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">MCP Client(常驻连接)</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">驰声 MCP</span>\n <span className=\"text-muted-foreground\">↘</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM(诊断 / 出题)</span>\n </div>\n </div>\n\n <p className=\"font-semibold mt-2\">FastAPI 示例(生产最小骨架)</p>\n <CodeBlock filename=\"app.py\" lang=\"python\">{`from fastapi import FastAPI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\nfrom contextlib import asynccontextmanager\n\nmcp_session: ClientSession | None = None\n\n@asynccontextmanager\nasync def lifespan(app):\n global mcp_session\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as s:\n await s.initialize()\n mcp_session = s\n yield\n mcp_session = None\n\napp = FastAPI(lifespan=lifespan)\n\n@app.post(\"/api/evaluate\")\nasync def evaluate(audio_id: str, ref_text: str):\n # 直接选一个最合适的工具执行(不经 LLM,延迟最低)\n r = await mcp_session.call_tool(\"evaluate_english_sentence\",\n {\"audioId\": audio_id, \"sentence\": ref_text})\n return {\"result\": r.content[0].text}`}</CodeBlock>\n\n <Callout type=\"tip\"><strong>关键优化</strong>:① MCP 连接在 lifespan 里做成长连接,不要每次请求都重连;② 对延迟敏感的场景可以「跳过 LLM 直接调 tool」,把 LLM 放到「生成诊断报告」那一步。</Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mt-6 text-sm\">\n <p className=\"font-semibold mb-2\">方案选型速查</p>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 font-medium\">场景</th>\n <th className=\"text-left py-2 font-medium\">推荐方案</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2\">AI 对话 / Copilot 类产品,LLM 供应商可能会换</td><td className=\"py-2\">① 官方 MCP 客户端库</td></tr>\n <tr><td className=\"py-2\">已有 LangChain / Mastra / Agents SDK 技术栈</td><td className=\"py-2\">② Agent 框架 + adapter</td></tr>\n <tr><td className=\"py-2\">火山方舟 / 千帆控制台直连,最小侵入</td><td className=\"py-2\">③ chat.completions + tools</td></tr>\n <tr><td className=\"py-2\">教育 SaaS / 培训 / 客服等业务后端集成</td><td className=\"py-2\">④ 自研后端 Agent</td></tr>\n </tbody>\n </table>\n </div>\n </div>\n </DocSection>\n\n {/* ══════ 评测模式 ══════ */}\n <DocSection id=\"eval-modes\" icon={Radio} title=\"评测模式\">\n\n <SubDoc id=\"stream-eval\" title=\"实时录音评测(边录边评)\">\n <p>通过本地麦克风实时录音,音频以 WebSocket 流式推送到评测服务,<strong>无需生成中间文件</strong>。这是最灵活、延迟最低的评测方式,特别适合交互式口语练习场景。</p>\n <Callout type=\"tip\">实时录音评测需要安装 <strong>SoX</strong> 并使用本地代理模式(Claude Desktop / Claude Code)。Cursor 等远程直连模式暂不支持实时录音。</Callout>\n\n <p className=\"font-semibold mt-4\">需要按顺序调用三个工具:</p>\n\n <div className=\"mt-3 space-y-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">create_stream_session</code> — 创建流式评测会话</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.word.score、en.sent.score、cn.sent.raw' },\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式,默认 mp3' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100,默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code>,后续步骤需使用。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">start_recording</code> — 开始麦克风录音</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '来自 create_stream_session 的会话 ID' },\n { name: 'chunk_size', type: 'number', required: false, desc: '音频块大小(bytes),默认 3200' },\n { name: 'chunk_interval_ms', type: 'number', required: false, desc: '推送间隔(ms),默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">调用后自动启动麦克风,音频通过 WebSocket 实时推送到评测服务。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">stop_recording</code> — 停止录音并获取结果</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '会话 ID' },\n { name: 'timeout', type: 'number', required: false, desc: '等待评测结果超时秒数,默认 30' },\n ]} />\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"stream-flow\" title=\"流式评测完整流程\">\n <div className=\"rounded-lg border border-border/60 p-5 bg-muted/10 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong>用户:</strong>&quot;请评测我的英文发音,文本是 Hello&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">create_stream_session(core_type=&quot;en.word.score&quot;, ref_text=&quot;Hello&quot;)</code></p>\n <p className=\"text-muted-foreground\">→ 获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">session_id</code></p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p>AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">start_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground mt-1\">麦克风开始录音,音频通过 WebSocket 实时推送到评测服务</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>用户</strong>说完后告知 AI &quot;结束&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">stop_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground\">→ 返回评测结果(总分、准确度、流利度、音素得分等)</p>\n </FlowStep>\n </div>\n </SubDoc>\n\n <SubDoc id=\"file-eval\" title=\"音频文件评测\">\n <p>适用于已有音频文件的评测场景,支持三种输入方式:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">适用场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_file_path</td><td className=\"py-2 px-3\">本地文件路径</td><td className=\"py-2 px-3 text-muted-foreground\">代理自动读取并转为 Base64,最便捷</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_base64</td><td className=\"py-2 px-3\">Base64 编码数据</td><td className=\"py-2 px-3 text-muted-foreground\">已有编码数据时使用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_url</td><td className=\"py-2 px-3\">音频文件 URL</td><td className=\"py-2 px-3 text-muted-foreground\">音频托管在云存储时使用</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"tip\">推荐使用 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">audio_file_path</code>,只需传入本地文件路径,代理会自动处理编码和上传。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"tools-en\" title=\"英文评测工具(10 种)\">\n <ToolTable tools={[\n ['en_word_eval', '单词评测', '评测 \"Hello\" 的发音准确度'],\n ['en_word_correction', '单词纠音', '检测多读/漏读/错读并给出纠正建议'],\n ['en_vocab_eval', '词语评测', '同时评测多个单词的发音'],\n ['en_sentence_eval', '句子评测', '评测整句朗读的准确度与流利度'],\n ['en_sentence_correction', '句子纠音', '逐词检测发音问题并给出修正建议'],\n ['en_paragraph_eval', '段落朗读评测', '评测整段英文课文的朗读质量'],\n ['en_phonics_eval', '自然拼读评测', '评测 Phonics 拼读规则掌握程度'],\n ['en_choice_eval', '口语选择题', '从预设选项中识别口语回答'],\n ['en_semi_open_eval', '半开放题评测', '场景对话等半开放口语题型'],\n ['en_realtime_eval', '实时朗读评测', '实时反馈朗读质量,逐句评分'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong>所有英文评测工具均支持以下参数。</p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tools-cn\" title=\"中文评测工具(6 种)\">\n <ToolTable tools={[\n ['cn_word_raw_eval', '单字评测(汉字)', '评测 \"中\" 的发音'],\n ['cn_word_pinyin_eval', '单字评测(拼音)', '评测 \"zhōng\" 的发音'],\n ['cn_sentence_eval', '词句评测', '评测 \"你好世界\" 的朗读'],\n ['cn_paragraph_eval', '段落朗读评测', '评测一段中文课文的朗读质量'],\n ['cn_rec_eval', '有限分支识别评测', '从预设选项中识别用户发音'],\n ['cn_aitalk_eval', 'AI Talk 口语评测', '中文口语对话能力评测'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong></p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本(汉字或拼音)' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结构化结果:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"integrity\": 95,\n \"details\": [\n {\n \"char\": \"hello\",\n \"score\": 85,\n \"phone\": [\n { \"phoneme\": \"h\", \"score\": 90 },\n { \"phoneme\": \"ɛ\", \"score\": 82 },\n { \"phoneme\": \"l\", \"score\": 88 },\n { \"phoneme\": \"oʊ\", \"score\": 80 }\n ]\n }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall', '0–100', '综合总分,反映整体语音质量'],\n ['accuracy', '0–100', '准确度,音素级别的发音准确率'],\n ['pron', '0–100', '发音得分,整体发音质量评价'],\n ['fluency.overall', '0–100', '流利度,语流的自然度与平滑度'],\n ['fluency.speed', '0–100', '语速得分,每分钟词/音节计量'],\n ['fluency.pause', 'number', '不自然停顿次数'],\n ['integrity', '0–100', '完整度,是否完整朗读了参考文本'],\n ['details[].phone[]', 'array', '每个音素的独立评分,可定位具体弱项'],\n ['details[].stress[]', 'array', '重音得分,检测重弱读是否正确'],\n ].map(([field, range, desc]) => (\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期', '音频上传后 5 分钟内有效,评测完成后自动删除', '重新上传音频'],\n ['服务器繁忙', '当前请求过多,排队已满', '稍后重试,使用指数退避策略'],\n ['存储空间已满', '服务器临时音频存储已满', '稍后重试,系统会自动清理'],\n ['音频格式不支持', '上传了不支持的文件类型', '使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限', '超过该题型最大时长限制', '裁剪音频后重试'],\n ['WebSocket 连接断开', '网络不稳定导致流式连接中断', '代理会自动重连,如持续失败请检查网络'],\n ].map(([scene, reason, fix]) => (\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p className=\"text-sm text-muted-foreground\">\n 下面这些模板与{' '}\n <Link href=\"/demo\" className=\"underline underline-offset-2\">免费体验</Link>{' '}\n Demo 中实际使用的「教练 Prompt」一脉相承,全部围绕 <strong>角色 / 任务 / 方法 / 输出格式 / 语气</strong> 五段式结构,并使用占位符{' '}\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code> 直接拼接 MCP 工具返回的 JSON。\n 推荐在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">system</code> 消息里挂载,再把工具返回原文塞进\n <code className=\"bg-muted px-1 rounded text-xs font-mono\">user</code> 消息,由 LLM 基于真实分数和音素生成可读诊断 / 练习材料。\n </p>\n\n <Callout type=\"tip\">\n <strong>使用建议:</strong>\n <ul className=\"list-disc list-inside mt-1 space-y-1\">\n <li>把 MCP 工具调用的完整 JSON(含 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">overall / dp_type / phonemes</code> 等字段)原样塞入 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,不要预先做汇总——细节越完整诊断越具体。</li>\n <li>「诊断」类模板让 LLM 输出 Markdown 给最终用户阅读;「练习」类模板要求 LLM 输出严格 JSON 给前端渲染。两步分开调用,效果远好于一次性混合。</li>\n <li>题型决定 prompt:单词题用音素级、句子题加韵律、段落题加 chunk / 整体性,半开放题套四维评级。下面按这 6 类分别给出。</li>\n </ul>\n </Callout>\n\n {/* ─── A. 诊断类 · 输出 Markdown 给学习者阅读 ─── */}\n <h4 className=\"text-sm font-semibold mt-6 mb-3 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> A. 二次诊断 · 输出 Markdown\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Sparkle className=\"h-4 w-4\" /> 1. 单词级 · 音素诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.word.score</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code>。围绕 <code className=\"bg-muted px-1 rounded font-mono\">phonemes[].dp_type</code> 找出错读音。\n </p>\n <CodeBlock filename=\"prompt-word-diagnosis.txt\">{`你是一位专业的英语发音教练。下面是驰声 MCP(en.word.score)返回的单词级评测数据:\n一个目标词、总分与发音分、以及带有 dp_type 的音素数组。\n\n## 任务\n为学习者写一份 **可直接阅读** 的发音诊断,每一条都要引用音素分数(写出 IPA 符号)。\n\n## 方法\n- 按 dp_type 分组:先看 mispron,再补充 normal 但分数 < 70 的\"边缘音\"。\n- 每个问题音素:① 典型母语干扰 → ② 你听到的声音 → ③ 一条具体的发音修正动作(下颌 / 舌位 / 气流 / 唇形)。\n- 重音符号 ˈ 标记的音节,重音错位也要单独列出(即便音素本身分数尚可)。\n- 最多列 4 条,按 **影响力从弱到强** 排序。\n\n## 输出\n精简 Markdown:可选一句总结 → 编号列表 → 单行 **🎯 优先级** 排序。\n\n## 语气\n友善、具体、可执行,不要空喊\"加油\"。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <MessageSquare className=\"h-4 w-4\" /> 2. 句子级 · 节奏 + 重音诊断\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.sent.score</code>。开头先给一行\"计分牌\",再从 phones → words → prosody。\n </p>\n <CodeBlock filename=\"prompt-sentence-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.sent.score)返回的句子级数据:refText、总分、发音子分(accuracy / integrity / fluency / rhythm)、可选 speed(WPM),以及 details[] 中的逐词得分、dp_type 与可选 phonemes[]。\n\n## 任务\n开头给一行紧凑的 **计分牌**(总分 + 各子分 + 速度),再按 **音素 → 词 → 韵律** 顺序展开诊断。\n\n## 检查清单(每项都要覆盖;不适用则写\"无明显问题\")\n1. **音素级**:每个 score < 70 或 dp_type ≠ normal 的词,展开 phonemes,命名典型错误(如 /ð/ → /d/)并给一条修正动作。\n2. **漏读 / 多读**:标注 dp_type=omit / insert,解释如何拉低 integrity。\n3. **韵律**:基于 rhythm + 错读词的重音,指出重音错位、陈述句调型平直、断句过多等。\n4. **流利度 vs 准确度**:若 fluency 高但 accuracy 低,明确指出,让学习者知道优先改哪一项。\n\n## 输出格式\nMarkdown 子标题:**Pronunciation / Prosody / Study priority**(最后一行简短排序,按 ROI 从高到低)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <FileText className=\"h-4 w-4\" /> 3. 段落级 · 音素族归并 + 完整度\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code>。把同类错音合并为\"音素族\",避免逐词复述。\n </p>\n <CodeBlock filename=\"prompt-paragraph-diagnosis.txt\">{`你是一位专业的英语口语教练。下面是驰声 MCP(en.pred.score)返回的段落级数据:长 refText、总分、发音子分、可选 speed / integrity,以及大量 details[](同一个词可能在不同子句中重复出现;空 char + dp_type=omit 表示漏读)。\n\n## 任务\n对整段做 **整体诊断**:\n(a) 完整度 / 漏读,\n(b) 反复出现的 **音素族**,\n(c) 节奏与断句,\n(d) 各子分之间是否相互印证。\n\n## 检查清单\n1. **integrity / dp_type**:列出每个 omit / insert,并给出所在子句;解释长读时为什么虚词容易被吞。\n2. **音素聚类**:将弱词按共同音素问题归族(/θ/ 链、/e/ 链、/ɒ/ 等),引用 details 中的实例。\n3. **韵律**:结合 rhythm 与标点 —— 短语内停顿过多?子句边界缺失?\n4. **整体一致性**:若 fluency 正常但 accuracy 差(或反之),点明并指出对练习设计的影响。\n\n## 输出格式\nMarkdown 章节:**Summary / Completeness / Pronunciation (families) / Rhythm / Study priorities**(最后一项最多 4 条,按影响力排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <GraduationCap className=\"h-4 w-4\" /> 4. 半开放题 · IELTS 风格四维评级\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">en.pqan.score</code>。结合学习者 transcript + 四维分数,给出可教学的\"升档建议\"。\n </p>\n <CodeBlock filename=\"prompt-semi-open.txt\">{`你是一位 **IELTS / 课堂口语风格** 的口语教练。下面是驰声 MCP(en.pqan.score)返回的半开放题数据:题目 refText、学习者 transcript、overall、四个维度分(grammar / content / fluency / pron)、speed(WPM)、以及结构化的 issues[] 标记。\n\n## 任务\n给出 **基于 rubric 的诊断**:先用一句话给出 0–100 的整体段位估计,再 **逐维度** 解释,并 **引用 transcript 中的短句**(不要复述全文)。最后给 3–5 条 **可执行的\"升档建议\"**(句型 / 模板,而不是\"多练一点\")。\n\n## 权重(要明确告诉学习者)\n- **Content ~30%** — 想法深度、举例、具体程度\n- **Grammar ~25%** — 句型多样性、主谓一致、从句\n- **Fluency ~25%** — 语速、停顿、口头禅、衔接\n- **Pronunciation ~20%** — 音素 + 可懂度\n\n## 跨维度检查\n- 若 content 最低,先给\"展开模板\",不要先抠音素。\n- 若 fluency 低 + WPM 低,区分\"思考型停顿\"与\"发音卡顿\"。\n- 若 pron 标出特定音,绑定到 transcript 里 **真实出现过的词**。\n\n## 输出格式\nMarkdown:**段位估计** → 紧凑的 **得分表格** → **\"To reach the next band\"** 编号列表(每条给出可仿写的句型)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Languages className=\"h-4 w-4\" /> 5. 中文专项 · 声调 / 儿化 / 变调\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 适用工具:<code className=\"bg-muted px-1 rounded font-mono\">cn.word.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.sent.raw</code> / <code className=\"bg-muted px-1 rounded font-mono\">cn.pred.raw</code>。中文评测最大权重在 **声调**,必须单独检查。\n </p>\n <CodeBlock filename=\"prompt-mandarin-coach.txt\">{`你是一位专业的普通话口语教练。下面是驰声 MCP(cn.sent.raw)返回的中文句子评测数据:整句分数 + 逐字 details + 可选 phonemes + 流利 / 节奏分。\n\n## 中文评测的权重\n- **声调错误权重最高** —— 声调读错可能直接改变词义\n- dp_type=mispron 多来自:声调错、平翘舌混用、前后鼻音混用\n- 拼音对、声调错,听感仍是\"洋腔洋调\"\n\n## 检查清单(缺则写\"无明显问题\")\n1. **儿化**:phonemes 出现 r(儿化) 或 dp_type 提示时,确认是否被读成\"主音节 + 单独儿\"两拍。\n2. **轻声**:叠词、词缀、句末语气词 —— 第二字应短促轻读,无完整声调曲线。\n3. **变调**:三声连读、不 + 后字、一 + 后字,是否符合普通话规则。\n4. **节奏**:用 rhythm + 逗号位置,标记停顿过长或过碎,建议 chunk 边界。\n\n## 输出\nMarkdown 子标题:**总览 / 儿化 / 轻声 / 变调 / 节奏 / 学习优先级**(最后一项 1–3 条,按\"听感自然度提升\"排序)。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── B. 练习类 · 输出 JSON 给前端渲染 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <Mic className=\"h-4 w-4\" /> B. 微练习生成 · 输出严格 JSON\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 6. 单词 / 句子 · 微练习\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 建议在 <strong>诊断完成后</strong> 第二轮调用:让 LLM 严格按弱项音素生成,避免\"通用绕口令包\"。\n </p>\n <CodeBlock filename=\"prompt-micro-drills.txt\">{`你仍是这位学习者的发音教练。**第二轮诊断已经完成** —— 现在严格基于诊断里点名的弱音素,生成 **1:1 对应** 的微练习(不要给通用题)。\n\n## 输出(仅返回一段合法 JSON 数组,外层不要任何文字)\n根节点必须是数组,每个元素含 category(字符串)、icon(单个 emoji)、items({ \"label\", \"content\" } 数组)。\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme drills\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Awareness\", \"icon\": \"🗣️\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Whole word\", \"icon\": \"📝\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme drills**:诊断里每个 mispron 音素至少给一对最小对立对 + 一句短绕口令 / 重复句。\n- **Awareness**:当重音偏弱时,给一条 stress / 舌位 / 下颌姿态提示。\n- **Whole word**:2–3 句自然句,目标词分别出现在句首 / 句中 / 句末。\n- 所有 content 必须可念出声;字符串内不要再嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n原始 MCP:\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Notebook className=\"h-4 w-4\" /> 7. 段落 · Chunk Map + 元认知\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 段落级练习要从\"音素\"上升到\"短语 / 整段 / 元认知录回放\"。直接对应 <code className=\"bg-muted px-1 rounded font-mono\">en.pred.score</code> 的诊断结果。\n </p>\n <CodeBlock filename=\"prompt-paragraph-drills.txt\">{`你仍是这位学习者的口语教练。**段落级二次诊断** 已经列出了漏读位置和音素族 —— 现在请生成 **整段练习**:把弱音素串成\"族\"练习、用 chunk map 把虚词粘回短语,并附一条录音 → 回放 → 对照 MCP details 的元认知步骤。\n\n## 输出(仅返回一段合法 JSON 数组)\n\\`\\`\\`json\n[\n { \"category\": \"Phoneme families\", \"icon\": \"🎯\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Chunking\", \"icon\": \"🎵\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] },\n { \"category\": \"Metacognition\", \"icon\": \"🧠\", \"items\": [ { \"label\": \"…\", \"content\": \"…\" } ] }\n]\n\\`\\`\\`\n\n## 规则\n- **Phoneme families**:至少 3 条,按诊断里的音素族(/θ/、/e/、/ɒ/ 等)组织最小对立对 / 词链。\n- **Chunking**:① 给一行用 [ ] 和 / 标注的 chunk map(可加大致停顿秒数);② 给一句\"把漏掉的虚词嵌入短语\"的练习(不要孤立练)。\n- **Metacognition**:一条\"录音 → 回放 → 对照 MCP details\"的 N 遍练习(N 写明,2–3 之间)。\n- 字符串只能是纯展示文本,禁止嵌套 JSON。\n\n## 上下文\n诊断结论:{previous_diagnosis}\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n {/* ─── C. 跨题 / 运营 ─── */}\n <h4 className=\"text-sm font-semibold mt-7 mb-3 flex items-center gap-2\">\n <BarChart3 className=\"h-4 w-4\" /> C. 跨题 / 运营\n </h4>\n <div className=\"space-y-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <Gauge className=\"h-4 w-4\" /> 8. 多日学习报告\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 把过去 N 天的 MCP 评测结果聚合后丢给 LLM,输出适合发给学生 / 家长的周报。\n </p>\n <CodeBlock filename=\"prompt-weekly-report.txt\">{`你是一位英语口语学习顾问。下面是这位学习者过去 7 天的驰声 MCP 评测历史(按时间顺序),每条包含题型、coreType、overall、子分、weak_phonemes 摘要。\n\n## 任务\n为学习者生成一份 **周报**,目标读者:本人 + 家长 / 老师。\n强调 **趋势** 与 **下一步计划**,不要复述每一次具体分数。\n\n## 必须包含\n1. **整体趋势**:进步 / 持平 / 退步(一句话 + 简短依据)\n2. **维度变化**:accuracy / fluency / rhythm / integrity / speed 任意有数据的维度,给出\"本周均分 vs 上周\"\n3. **本周高光**:分数提升最大的题型 / 音素 / 句型\n4. **顽固问题**:连续 3 次以上未达 70 的音素或维度\n5. **下周计划**:3 条具体动作(每条对应一个 MCP 工具或一个练习类型)\n6. **结尾**:1–2 句鼓励性总结,避免空话\n\n## 风格\n- 像一位认真但不啰嗦的私教\n- 出现学生姓名时使用占位 {student_name}\n- 数字一律保留整数\n\n## 评测历史\n\\`\\`\\`json\n{history}\n\\`\\`\\``}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\">\n <ListChecks className=\"h-4 w-4\" /> 9. 错误归因 · JSON 摘要\n </p>\n <p className=\"text-xs text-muted-foreground mb-2\">\n 用于后端聚合 / 数据看板:让 LLM 把一次评测压缩成结构化摘要,方便入库统计。\n </p>\n <CodeBlock filename=\"prompt-error-summary.txt\">{`你是一个评测结果归类器。下面是驰声 MCP 一次评测的完整返回。请抽取关键信息并 **仅返回合法 JSON**(不要任何解释文字)。\n\n## Schema\n\\`\\`\\`json\n{\n \"core_type\": \"string,例如 en.sent.score\",\n \"overall\": 0,\n \"sub_scores\": { \"accuracy\": 0, \"integrity\": 0, \"fluency\": 0, \"rhythm\": 0 },\n \"weak_phonemes\": [\n { \"ipa\": \"string\", \"avg_score\": 0, \"occur\": 0, \"example_word\": \"string\" }\n ],\n \"omissions\": [{ \"after\": \"string\", \"missing\": \"string\" }],\n \"stress_issues\": [{ \"word\": \"string\", \"expected\": \"string\", \"actual\": \"string\" }],\n \"primary_issue_tag\": \"phoneme|stress|fluency|integrity|content|none\",\n \"next_action_hint\": \"string,10 字以内,例如 \\\"练 /θ/ 对立对\\\"\"\n}\n\\`\\`\\`\n\n## 规则\n- weak_phonemes 只收 avg_score < 70 的项,最多 5 项,按 occur 降序。\n- 不要复述 refText,不要 markdown。\n- 字段缺失时使用 null 或空数组,不要省略 key。\n\n## MCP 数据\n\\`\\`\\`json\n{mcp_response}\n\\`\\`\\``}</CodeBlock>\n </div>\n </div>\n\n <Callout type=\"info\">\n <strong>怎么接到 MCP 工具调用?</strong>\n <br />\n 以「直调 chat.completions + 动态 tools」为例:第一轮 LLM 调出 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">en.sent.score</code> 工具,把工具 result(即 <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">tool</code> 消息内容)原样填入上面任意「诊断」模板的{' '}\n <code className=\"bg-white/40 dark:bg-black/30 px-1 rounded text-xs font-mono\">{'{mcp_response}'}</code>,作为第二轮 user 消息发回;拿到诊断结论再走第三轮,套「微练习」模板得到 JSON。三轮调用即可串起 <strong>评测 → 诊断 → 练习</strong> 全链路。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频过期:</strong>上传后 5 分钟内有效。建议在上传后 3 分钟内完成评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>服务繁忙:</strong>使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>录音中断:</strong>WebSocket 断线后代理会自动重连。如持续失败,检查网络连接和 SoX 安装。</span></li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>优先使用流式评测:</strong>实时录音模式跳过了文件上传环节,端到端延迟比文件评测低 30-50%。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频压缩:</strong>文件评测时,上传前将音频转为 16kHz 单声道 mp3,可减少 60-80% 的上传体积。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>选择合适的工具:</strong>单词评测比段落评测快 3-5 倍。如只需单词级分数,不要用句子评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>本地文件路径最便捷:</strong>使用 <code className=\"bg-muted px-1 rounded text-xs\">audio_file_path</code> 参数传入本地路径,代理自动处理编码,无需手动转 Base64。</span></li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Globe} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP JSON-RPC 接口(Streamable HTTP)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制', '500MB', '最大音频存储空间'],\n ['最大排队', '10', '等待处理的请求数'],\n ['最大并发', '3', '同时处理的评测数'],\n ['音频有效期', '5 分钟', '上传后未评测自动过期'],\n ['文件大小限制', '50MB', '单个音频文件最大'],\n ].map(([k, v, d]) => (\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">\n 上表为托管侧<strong>技术默认</strong>;计费与试用/商用<strong>调用配额、并发档位</strong>以{' '}\n <Link href=\"/dashboard/plans\" className=\"text-foreground underline underline-offset-2\">\n 会员套餐\n </Link>\n 说明为准,与上表独立。\n </p>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <Callout type=\"info\">\n <strong>当前线上版本:v1.0.0</strong>(网站未正式上线)。以下变更项为开发阶段规划记录,以正式发布公告为准。\n </Callout>\n <div className=\"space-y-3 mt-4\">\n {[\n { ver: 'v1.0.0', date: 'TBD', desc: '当前线上版本(网站未正式上线)' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </DocsShell>\n );\n}\n\nexport default function DocsPage() {\n const locale = useLocale();\n return locale.startsWith('zh') ? <DocsPageZh /> : <DocsPageEn />;\n}\n"}, "48038d7e90e26ecc97c74b118f4445ce86cd45c1": {"t": 1785765800.678828, "n": 85736, "h": "d3b98ac9016dafb0", "c": "'use client';\n\nimport { useLocale } from 'next-intl';\nimport { DocsShell, type NavGroup } from './docs-shell';\nimport {\n CodeBlock,\n DocSection,\n SubDoc,\n ParamTable,\n Callout,\n FlowStep,\n ToolTable,\n} from './docs-shared';\nimport { DocsEnglishBody } from './docs-content-en';\nimport {\n BookOpen,\n Code2,\n Zap,\n FileText,\n Terminal,\n Globe,\n Mic,\n MessageSquare,\n BarChart3,\n AlertTriangle,\n Radio,\n FolderOpen,\n Workflow,\n Sparkles,\n Plug,\n Bot,\n Building2,\n Check,\n} from 'lucide-react';\n\n/* ─── Navigation Structure ─── */\n\nconst NAV = [\n { id: 'concept', icon: Workflow, label: 'MCP 工作原理', children: [\n { id: 'why-mcp', label: '为什么需要 MCP' },\n { id: 'scenario-walkthrough', label: '场景走查 · 6 步' },\n { id: 'dev-responsibility', label: '开发者只做两件事' },\n { id: 'integration-paths', label: '三种接入姿势' },\n ]},\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'requirements', label: '系统要求' },\n { id: 'install', label: '安装方式' },\n { id: 'env-vars', label: '环境变量' },\n ]},\n { id: 'config', icon: Plug, label: '接入 · 直接用(零代码)', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: '扣子(Coze)' },\n { id: 'config-coze-workflow', label: 'Coze 工作流' },\n { id: 'config-ai-workspace', label: 'AI 工作台 · 豆包 / 飞书 / WorkBuddy / 钉钉' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: '其他 MCP 客户端' },\n ]},\n { id: 'config-code', icon: Code2, label: '接入 · 编程用(SDK / 代码)', children: [\n { id: 'code-mcp-sdk', label: '官方 MCP 客户端库' },\n { id: 'code-agent-frameworks', label: 'LangChain / Mastra / Agents SDK' },\n { id: 'code-function-calling', label: '直调 chat.completions · tools' },\n { id: 'code-selfhosted-agent', label: '自研后端 Agent' },\n ]},\n { id: 'eval-modes', icon: Radio, label: '评测模式', children: [\n { id: 'stream-eval', label: '实时录音评测' },\n { id: 'stream-flow', label: '流式评测流程' },\n { id: 'file-eval', label: '音频文件评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'tools-en', label: '英文评测工具' },\n { id: 'tools-cn', label: '中文评测工具' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'architecture', icon: Terminal, label: '架构说明', children: [\n { id: 'arch-diagram', label: '架构图' },\n { id: 'transport', label: '传输协议' },\n ]},\n { id: 'integration', icon: Code2, label: '示例代码', children: [\n { id: 'code-python', label: 'Python' },\n { id: 'code-javascript', label: 'JavaScript' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n { id: 'service-info', icon: Globe, label: '服务信息', children: [\n { id: 'endpoints', label: '服务端点' },\n { id: 'limits', label: '服务限制' },\n { id: 'changelog', label: '更新日志' },\n ]},\n];\n\nconst NAV_EN: NavGroup[] = [\n { id: 'concept', icon: Workflow, label: 'How MCP works', children: [\n { id: 'why-mcp', label: 'Why MCP' },\n { id: 'scenario-walkthrough', label: 'Scenario · 6 steps' },\n { id: 'dev-responsibility', label: 'Two developer tasks' },\n { id: 'integration-paths', label: 'Three integration styles' },\n ]},\n { id: 'quick-start', icon: Zap, label: 'Quick start', children: [\n { id: 'overview', label: 'Overview' },\n { id: 'requirements', label: 'Requirements' },\n { id: 'install', label: 'Install' },\n { id: 'env-vars', label: 'Environment' },\n ]},\n { id: 'config', icon: Plug, label: 'Zero-code clients', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: 'Coze' },\n { id: 'config-coze-workflow', label: 'Coze workflows' },\n { id: 'config-ai-workspace', label: 'Enterprise AI workspaces' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: 'Other MCP clients' },\n ]},\n { id: 'config-code', icon: Code2, label: 'SDK / code integration', children: [\n { id: 'code-mcp-sdk', label: 'Official MCP SDK' },\n { id: 'code-agent-frameworks', label: 'Agent frameworks' },\n { id: 'code-function-calling', label: 'chat.completions + tools' },\n { id: 'code-selfhosted-agent', label: 'Custom backend' },\n ]},\n { id: 'eval-modes', icon: Radio, label: 'Evaluation modes', children: [\n { id: 'stream-eval', label: 'Streaming mic' },\n { id: 'stream-flow', label: 'Streaming flow' },\n { id: 'file-eval', label: 'Audio files' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API reference', children: [\n { id: 'tools-en', label: 'English tools' },\n { id: 'tools-cn', label: 'Chinese tools' },\n { id: 'result-fields', label: 'Result fields' },\n { id: 'error-codes', label: 'Errors' },\n ]},\n { id: 'architecture', icon: Terminal, label: 'Architecture', children: [\n { id: 'arch-diagram', label: 'Diagram' },\n { id: 'transport', label: 'Transports' },\n ]},\n { id: 'integration', icon: Code2, label: 'Sample code', children: [\n { id: 'code-python', label: 'Python' },\n { id: 'code-javascript', label: 'JavaScript' },\n ]},\n { id: 'best-practices', icon: FileText, label: 'Best practices', children: [\n { id: 'prompt-templates', label: 'Prompt templates' },\n { id: 'error-handling', label: 'Errors & retries' },\n { id: 'performance', label: 'Performance' },\n ]},\n { id: 'service-info', icon: Globe, label: 'Service info', children: [\n { id: 'endpoints', label: 'Endpoints' },\n { id: 'limits', label: 'Limits' },\n { id: 'changelog', label: 'Changelog' },\n ]},\n];\n\n/* ─── Page(英文 / 中文拆成两个组件,避免切换语言时 Hooks 数量变化导致白屏)─── */\n\nfunction DocsPageEn() {\n return (\n <DocsShell\n nav={NAV_EN}\n backLabel=\"Back to Home\"\n title=\"Developer documentation\"\n subtitle=\"Chivox Speech Evaluation MCP · chivox-local-mcp\"\n >\n <DocsEnglishBody />\n </DocsShell>\n );\n}\n\nfunction DocsPageZh() {\n return (\n <DocsShell\n nav={NAV}\n backLabel=\"返回首页\"\n title=\"开发者文档\"\n subtitle=\"驰声语音评测 MCP · chivox-local-mcp\"\n >\n {/* ══════ MCP 工作原理 ══════ */}\n <DocSection id=\"concept\" icon={Workflow} title=\"MCP 工作原理\">\n\n <SubDoc id=\"why-mcp\" title=\"为什么需要 MCP\">\n <p>\n 大模型(GPT / Claude / Gemini / 通义千问 等)本质上是<strong>文字大脑</strong>:它擅长读和写,但天生<strong>听不到</strong>音频、<strong>打不开</strong>你的数据库、<strong>调不通</strong>第三方 API。\n </p>\n <p>\n <strong>MCP(Model Context Protocol)</strong>是一套由 Anthropic 主导、业界共同推进的开放协议,它像一根\"对讲机\":让大模型可以在需要时主动去调用外部能力(工具、数据源、服务),然后把结果接回来继续对话。\n </p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <AlertTriangle className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n <span className=\"text-sm font-semibold\">没有 MCP 的做法</span>\n </div>\n <ul className=\"text-xs text-muted-foreground space-y-1.5 leading-relaxed\">\n <li>· 研究评测服务的 REST 文档</li>\n <li>· 自己写鉴权 / 上传 / 轮询代码</li>\n <li>· 把结果序列化后塞进 prompt</li>\n <li>· 每换一个大模型都得重写一遍</li>\n </ul>\n </div>\n <div className=\"rounded-lg border-2 border-foreground/60 p-4 bg-foreground/[0.02]\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Check className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">用驰声 MCP 之后</span>\n </div>\n <ul className=\"text-xs space-y-1.5 leading-relaxed\">\n <li>· 填一行 MCP Server 地址</li>\n <li>· 大模型自动发现&amp;调用评测工具</li>\n <li>· 换任何支持 MCP 的大模型都能用</li>\n <li>· 不写一行调用代码</li>\n </ul>\n </div>\n </div>\n <Callout type=\"info\">\n MCP 让\"接入语音评测\"从一个\"后端工程\"变成了一个\"配置项\"。这也是为什么 Cursor、Claude Desktop、Dify、Coze、字节豆包、飞书智能助手都已经原生支持 MCP。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"scenario-walkthrough\" title=\"场景走查 · 从一段录音到温柔反馈\">\n <p>\n 想象一个具体场景:一位开发者用 Claude 做了一款 <strong>「AI 雅思口语陪练」小程序</strong>,接入驰声语音评测 MCP。当用户发来一段录音,整个链路是这样的:\n </p>\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-5 my-4 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong className=\"text-foreground\">配置阶段</strong>(开发者 · 一次性)</p>\n <p className=\"text-muted-foreground mt-1\">在 AI 后端的配置文件里填一行驰声 MCP Server 地址。大模型启动时自动发现:「我多了一个叫<strong>语音评测</strong>的工具」。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong className=\"text-foreground\">接收用户指令</strong>(终端用户)</p>\n <p className=\"text-muted-foreground mt-1\">小程序用户发来一段自我介绍录音:「帮我听听,发音哪里有问题?」后端把「文字 + 录音链接」打包给大模型。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong className=\"text-foreground\">大模型自主思考</strong>(LLM · 最核心的魔法)</p>\n <div className=\"text-muted-foreground mt-1 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed\">\n 「用户让我听发音。」<br />\n 「但我只是文字大脑,我听不到。」<br />\n 「等等 —— 开发者给了我一个叫 <code className=\"bg-muted px-1 rounded\">chivox_voice_eval</code> 的 MCP 工具!」\n </div>\n </FlowStep>\n <FlowStep title=\"4\">\n <p><strong className=\"text-foreground\">MCP 协议 · 自动调用</strong>(LLM → 驰声)</p>\n <p className=\"text-muted-foreground mt-1\">大模型自动按 MCP 标准构造 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools/call</code> 请求,把录音链接和参考文本发到驰声服务。<span className=\"text-foreground\">开发者不需要写任何调用代码。</span></p>\n </FlowStep>\n <FlowStep title=\"5\">\n <p><strong className=\"text-foreground\">驰声服务处理</strong>(评测引擎)</p>\n <p className=\"text-muted-foreground mt-1\">驰声对音频进行考试级打分,把结构化结果通过 MCP 原路返回:<code className=\"bg-muted px-1 rounded text-xs font-mono\">overall 85</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">/θ/ 音发音错误</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">流利度 88</code>。</p>\n </FlowStep>\n <FlowStep title=\"6\">\n <p><strong className=\"text-foreground\">大模型整合并输出反馈</strong>(LLM)</p>\n <p className=\"text-muted-foreground mt-1\">大模型把冷冰冰的分数翻译成温柔反馈:</p>\n <div className=\"text-foreground/80 mt-2 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed italic\">\n 「你的自我介绍很棒!综合 85 分 🎉 不过 <strong>think</strong> 这个词的 /θ/ 咬舌音不太标准,我们一起来练一下……」\n </div>\n </FlowStep>\n </div>\n <Callout type=\"tip\">\n <strong>整条链路里,MCP 只出现在 ① 和 ④</strong>:① 让开发者一次性告诉大模型「你多了一个工具」,④ 让大模型可以自动调用它。这两步的标准化,就是 MCP 的全部价值。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"dev-responsibility\" title=\"开发者只做两件事\">\n <p>听起来复杂,但对开发者来说只需要两件事:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">1</span>\n <span className=\"text-sm font-semibold\">填一次 MCP 配置</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 在 AI 后端的配置里填入驰声 MCP Server 地址和 API Key,就这一次,以后新加评测能力完全免开发。\n </p>\n <CodeBlock filename=\"mcp-config.json\" lang=\"json\">{`{\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"apiKey\": \"sk-••••••••••\"\n }\n}`}</CodeBlock>\n </div>\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">2</span>\n <span className=\"text-sm font-semibold\">写一段 System Prompt</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 告诉大模型这个工具是做什么的、什么时候调用、结果怎么转成用户友好的反馈。\n </p>\n <CodeBlock filename=\"system-prompt.md\">{`你是一位英语口语教练。\n当用户发来录音时:\n1. 调用 chivox_voice_eval 评分\n2. 聚焦 <70 分的音素家族\n3. 用高情商话术给出练习建议`}</CodeBlock>\n </div>\n </div>\n <p className=\"text-muted-foreground\">\n 其余所有事情 —— 音频上传、鉴权、工具发现、调用时机、返回格式、结果整合 —— 都由 MCP 协议和大模型自己搞定。\n </p>\n </SubDoc>\n\n <SubDoc id=\"integration-paths\" title=\"三种典型接入姿势\">\n <p>根据你的技术栈,有三种等价的接入方式,底层都走完全相同的 MCP 协议:</p>\n\n {/* ── 姿势一:可视化 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Sparkles className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势一 · 可视化拖拽(扣子 / Coze / 飞书智能助手)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合产品经理、运营、不写后端代码的开发者。整个过程像在手机上装 App。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>找到入口</strong>:在扣子平台的 <strong>「插件 / 工具」</strong> 配置区,点击<strong>「添加 MCP 服务」</strong>。</li>\n <li><strong>填两个字段</strong>(这也是你未来会从驰声这边拿到的):\n <div className=\"mt-2 overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium w-32\">服务器地址</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权密钥</td><td className=\"py-2 px-3 font-mono text-xs\">sk-•••••••••• <span className=\"text-muted-foreground ml-2\">(由驰声分配,用于计费与识别)</span></td></tr>\n </tbody>\n </table>\n </div>\n </li>\n <li><strong>平台自动发现能力</strong>:扣子会访问你填的地址,读取 MCP Server 自描述的\"说明书\"。瞬间明白:「这是个语音评测工具,需要传音频,会返发音分」。</li>\n <li><strong>右侧测试</strong>:在平台的聊天框里发一段语音,大模型就会自动调用工具。<strong>整个过程不需要写一行代码。</strong></li>\n </ol>\n </div>\n\n {/* ── 姿势二:代码框架 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Code2 className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势二 · 代码框架配置(Dify / LangChain / 自研后端)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合做独立 APP / 小程序、用 Dify 或 LangChain 写 Agent 的极客开发者。核心就是往配置文件里加一段 JSON。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>打开配置文件</strong>(项目根目录下的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp.json</code> 或框架指定的配置路径)。</li>\n <li><strong>在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code> 下增加一个节点</strong>:\n <CodeBlock filename=\"mcp.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"env\": {\n \"API_KEY\": \"你的专属密钥\"\n }\n }\n }\n}`}</CodeBlock>\n </li>\n <li><strong>重启后端服务</strong>。大模型的底层调度器会在启动时自动连上驰声 MCP Server,把 16 种评测工具注册成<strong>随时待命</strong>的 tools。</li>\n <li><strong>触发调用</strong>:用户发来录音后,大模型看到 system prompt 的引导,自动选择合适的工具(单词/句子/段落/半开放)调用。</li>\n </ol>\n </div>\n\n {/* ── 姿势三:Function Calling 直连(分 A / B 两种做法) ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Terminal className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势三 · 原生 Function Calling(豆包 / DeepSeek / OpenAI 兼容 API)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合不用任何 Agent 框架、直接调用 LLM API 的后端开发者。豆包 1.5+ / DeepSeek / GPT / Qwen 等模型都走标准 <code className=\"bg-muted px-1 rounded text-xs font-mono\">chat.completions</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> 参数。这里又分 A / B 两种做法,<strong>B 才是生产推荐</strong>。\n </p>\n\n <Callout type=\"warning\">\n 常见疑问:<em>\"要不要手写一个 tool 定义?\"</em><br />\n — <strong>姿势一 / 二不用</strong>(Coze、Dify、火山方舟控制台、Claude Desktop 会自动从 MCP Server 拉工具列表)。<br />\n — <strong>姿势三看你选 A 还是 B</strong>:A 要手写,B 用 MCP 客户端库自动拉。\n </Callout>\n\n {/* ── 3-A:手写 tool schema ── */}\n <div className=\"mt-4 rounded-md border border-border/40 bg-muted/20 p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold\">3-A · 手写 tool schema(最快验证,不推荐生产)</p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20\">快速 Demo</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 自己写一段 JSON Schema 告诉豆包\"有个工具叫 xxx,参数长这样\",豆包返回 tool_call 后你再手动去调驰声 MCP。<strong>缺点</strong>:驰声有 16 个评测工具,你每加一个题型都要改代码。\n </p>\n <CodeBlock filename=\"3a_hardcode_tool.py\" lang=\"python\">{`import os, json\nfrom openai import OpenAI\n\nclient = OpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv('ARK_API_KEY'),\n)\n\n# ─── 手写一段 tool schema(不推荐:16 个工具就要写 16 段)───\ntools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英语单词发音评测。当用户让你评价英语发音并提供录音链接时调用。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"audio_url\": {\"type\": \"string\", \"description\": \"录音 URL\"},\n \"reference_text\": {\"type\": \"string\", \"description\": \"参考文本,如 Apple\"},\n },\n \"required\": [\"audio_url\", \"reference_text\"]\n }\n }\n}]\n\nresp = client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,拿到评测结果后温柔地反馈,不要直接报分数。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n)\n\nmsg = resp.choices[0].message\nif msg.tool_calls:\n args = json.loads(msg.tool_calls[0].function.arguments)\n # ↓↓↓ 这里手动调驰声 MCP,再把结果回填给豆包做第二趟 ↓↓↓\n # result = requests.post(\"https://speech-eval.site/mcp\", json={...})`}</CodeBlock>\n </div>\n\n {/* ── 3-B:MCP 自动发现 ── */}\n <div className=\"mt-3 rounded-md border border-foreground/30 bg-foreground/[0.03] dark:bg-white/[0.03] p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold flex items-center gap-1.5\">\n 3-B · 用 MCP 客户端库自动发现 <Sparkles className=\"h-3 w-3 text-foreground\" />\n </p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-foreground text-background\">生产推荐</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 启动时连上驰声 MCP Server,调一次 <code className=\"bg-muted px-1 rounded\">tools/list</code>,把 16 个评测工具自动转成 Function Calling 的 tools 格式。<strong>驰声加新工具,你代码不用改</strong>。这才是 MCP 协议的真正价值。\n </p>\n <CodeBlock filename=\"3b_mcp_autodiscover.py\" lang=\"python\">{`import asyncio, os\nfrom openai import AsyncOpenAI\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\n\nclient = AsyncOpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv(\"ARK_API_KEY\"),\n)\n\nasync def main():\n # ─── 1. 连驰声 MCP Server,自动拉取全部 16 个工具 ───\n async with streamablehttp_client(\n \"https://speech-eval.site/mcp\",\n headers={\"Authorization\": f\"Bearer {os.getenv('CHIVOX_KEY')}\"},\n ) as (read, write, _):\n async with ClientSession(read, write) as session:\n await session.initialize()\n mcp_tools = (await session.list_tools()).tools # ← 一行拿到全部工具\n\n # ─── 2. MCP schema → OpenAI Function Calling schema ───\n tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema, # MCP 直接给 JSON Schema\n }\n } for t in mcp_tools]\n\n # ─── 3. 正常调豆包,tools 由 MCP 动态注入 ───\n resp = await client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,收到评测结果后温柔反馈。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n )\n\n # ─── 4. 折返跑:豆包选好工具后,直接走 MCP 调驰声 ───\n msg = resp.choices[0].message\n if msg.tool_calls:\n call = msg.tool_calls[0]\n result = await session.call_tool(\n call.function.name,\n arguments=__import__(\"json\").loads(call.function.arguments),\n )\n # 把 result 回填给豆包做第二趟 chat.completions 即可\n\nasyncio.run(main())`}</CodeBlock>\n <p className=\"text-[11px] text-muted-foreground mt-3 leading-relaxed\">\n 想要更省心?直接用 <a href=\"https://github.com/modelcontextprotocol/python-sdk\" target=\"_blank\" rel=\"noreferrer\" className=\"text-foreground underline underline-offset-2\">官方 mcp python sdk</a>、<code className=\"bg-muted px-1 rounded text-xs\">mcp-use</code>、<code className=\"bg-muted px-1 rounded text-xs\">openai-agents</code>,或火山官方 <code className=\"bg-muted px-1 rounded text-xs\">Arkitect</code> 高代码 SDK,它们都把上面这段\"MCP → Function Calling\"的桥接封装好了。\n </p>\n </div>\n </div>\n\n <Callout type=\"info\">\n 三种姿势走的是完全相同的 MCP 协议,能力完全等价。选型建议:<strong>产品经理 / 原型验证用姿势一</strong>(Coze 填表单);<strong>AI 客户端用户用姿势二</strong>(Cursor / Claude Desktop 填 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">mcp.json</code>);<strong>后端生产服务用姿势三-B</strong>(MCP 客户端库自动发现工具,不要手写 tool schema)。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-local-mcp</strong> 是驰声语音评测的 MCP 本地代理,通过 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 将远程驰声评测服务桥接到本地,让 AI 助手(Claude Desktop、Claude Code、Cursor 等)直接调用语音评测能力。</p>\n <p>它支持两种评测模式,覆盖从即时交互到批量处理的全部场景:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Mic className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">实时录音评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">通过麦克风边录边评,音频以 WebSocket 流式推送,无需生成中间文件,延迟更低、体验更佳。</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <FolderOpen className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">音频文件评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">支持本地文件路径、Base64 编码、URL 三种输入,适合已有音频的批量评测场景。</p>\n </div>\n </div>\n <Callout type=\"info\">当前支持 <strong>16 种评测工具</strong>:英文 10 种(单词、句子、段落、纠音、自然拼读、口语选择、半开放题等)+ 中文 6 种(汉字、拼音、词句、段落、有限分支、AI Talk)。</Callout>\n </SubDoc>\n\n <SubDoc id=\"requirements\" title=\"系统要求\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">依赖</th>\n <th className=\"text-left py-2 px-3 font-medium\">版本</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-semibold\">Node.js</td><td className=\"py-2 px-3 font-mono text-xs\">{'>'}= 18</td><td className=\"py-2 px-3 text-muted-foreground\">运行时必须</td></tr>\n <tr><td className=\"py-2 px-3 font-semibold\">SoX</td><td className=\"py-2 px-3 font-mono text-xs\">any</td><td className=\"py-2 px-3 text-muted-foreground\">仅实时录音功能需要(处理麦克风音频流)</td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"mt-3\">安装 SoX(仅在使用实时录音时需要):</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# macOS\nbrew install sox\n\n# Ubuntu / Debian\nsudo apt-get install sox`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"install\" title=\"安装方式\">\n <p><strong>方式一:npm 全局安装</strong>(推荐)</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`npm install -g chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式二:npx 免安装运行</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`MCP_REMOTE_URL=http://your-server:8080 npx chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式三:从源码构建</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`git clone https://git.chivox.com/CLOUD_DEV/cvx_local_mcp.git\ncd cvx_local_mcp\nbash scripts/build.sh`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"env-vars\" title=\"环境变量\">\n <ParamTable params={[\n { name: 'MCP_REMOTE_URL', type: 'string', required: true, desc: '远程驰声 MCP 服务地址,如 http://your-server:8080' },\n { name: 'MCP_API_KEY', type: 'string', required: false, desc: 'API 认证密钥(如已配置鉴权则必填)' },\n ]} />\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 直接用(零代码) ══════ */}\n <DocSection id=\"config\" icon={Plug} title=\"接入 · 直接用 MCP(零代码)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>不用写代码、不用装 SDK,只在客户端的<strong className=\"text-foreground\">「MCP 配置」</strong>或<strong className=\"text-foreground\">「扩展」</strong>里填一段 JSON / URL</li>\n <li>保存后 LLM 自动发现 16 个评测工具,并在对话 / Agent 中自主调用</li>\n <li>覆盖 <strong className=\"text-foreground\">IDE 编程助手</strong>、<strong className=\"text-foreground\">桌面 AI 客户端</strong>、<strong className=\"text-foreground\">企业 AI 工作台</strong>、<strong className=\"text-foreground\">可视化 Bot / 工作流平台</strong>四大生态</li>\n </ul>\n </div>\n\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置并保存:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <Callout type=\"tip\">Cursor 使用远程 Streamable HTTP 模式直连,无需安装本地代理。如需实时录音功能,请使用下方 Claude Desktop 的本地代理配置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-desktop\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件(macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"chivox-local-mcp\",\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\",\n \"MCP_API_KEY\": \"your-api-key\"\n }\n }\n }\n}`}</CodeBlock>\n <p>如果是从源码构建:</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/cvx_local_mcp/dist/index.js\"],\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\"\n }\n }\n }\n}`}</CodeBlock>\n <Callout type=\"info\">Claude Desktop 使用本地代理模式(stdio),支持实时录音评测。本地代理会自动桥接远程服务。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-code\" title=\"Claude Code 配置\">\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# npm 全局安装后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 chivox-local-mcp\n\n# 源码构建后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 node /path/to/cvx_local_mcp/dist/index.js`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-ai-ide\" title=\"Windsurf / Zed / Continue 等 IDE 编程助手\">\n <p>除了 Cursor 和 Claude Code,主流的 AI 编程 IDE 都原生支持 MCP。以下客户端使用<strong>完全一致</strong>的 Streamable HTTP 配置,区别只在配置入口:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">客户端</th>\n <th className=\"text-left py-2 px-3 font-medium\">配置入口</th>\n <th className=\"text-left py-2 px-3 font-medium\">备注</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">Windsurf (Codeium)</td><td className=\"py-2 px-3 text-xs\">Settings → Cascade → MCP Servers</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">直接贴下方 JSON</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Zed</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">~/.config/zed/settings.json</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">context_servers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">Assistant Panel 自动发现</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Continue (VS Code / JetBrains)</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">config.yaml</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">v0.9+ 支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">通义灵码 / CodeGeeX / 文心快码</td><td className=\"py-2 px-3 text-xs\">插件设置 → MCP 服务</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">2026 Q1 起陆续支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Cline (原 Claude Dev)</td><td className=\"py-2 px-3 text-xs\">MCP Servers 面板 → 编辑配置</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">VS Code 扩展</td></tr>\n </tbody>\n </table>\n </div>\n <CodeBlock filename=\"通用 MCP 配置(复制即用)\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox-speech-eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n }\n }\n}`}</CodeBlock>\n <Callout type=\"tip\">所有 IDE 类客户端都走远程 Streamable HTTP,零本地依赖。需要实时录音才需切到 Claude Desktop 的本地代理模式。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-coze\" title=\"扣子(Coze)配置\">\n <p><a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子(Coze)</a>是字节跳动推出的 AI 应用开发平台,支持通过 MCP 扩展为 Agent 赋予外部工具能力。接入驰声语音评测后,你可以构建口语练习 Bot、发音诊断助手等应用。</p>\n <p className=\"font-semibold mt-4\">方式一:扣子空间 MCP 扩展(推荐)</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 <a href=\"https://space.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子空间</a>(space.coze.cn)</li>\n <li>在对话框下方点击 <strong>「添加扩展」</strong></li>\n <li>选择 <strong>「自定义」</strong> 标签,点击添加新的 MCP 服务</li>\n <li>填入以下配置信息:</li>\n </ol>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">名称</td><td className=\"py-2 px-3 font-mono text-xs\">chivox-speech-eval</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>保存后,在对话中输入 <strong>「帮我评测这段英文发音」</strong> 即可触发 Agent 调用</li>\n </ol>\n <Callout type=\"tip\">添加 MCP 扩展后,扣子空间的 Agent 会自动识别并调用评测工具。你可以在 Agent 的思考过程中看到具体的工具调用细节。</Callout>\n\n <p className=\"font-semibold mt-6\">方式二:在 Coze 开发平台的 Bot 中使用</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>进入 <a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Coze 开发平台</a>,创建或编辑 Bot</li>\n <li>在 Bot 的 <strong>「插件」</strong> 或 <strong>「工具」</strong> 面板中,添加 MCP 工具</li>\n <li>选择 <strong>Streamable HTTP</strong> 类型,填入 URL:<code className=\"bg-muted px-1 rounded text-xs font-mono\">https://speech-eval.site/mcp</code></li>\n <li>Bot 将自动发现所有可用的评测工具(如 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_word</code> 等)</li>\n <li>在 Bot 的人设提示词中,添加语音评测相关的引导语</li>\n </ol>\n <CodeBlock filename=\"Bot 人设提示词示例\">{`你是一位专业的英语口语教练。当用户上传音频并请求评测时:\n1. 使用 evaluate_english_word / evaluate_english_sentence 等工具进行评测\n2. 根据评测结果(overall、accuracy、fluency 等分数)给出专业分析\n3. 针对得分较低的维度提供具体改进建议\n4. 鼓励用户持续练习`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze-workflow\" title=\"Coze 工作流接入\">\n <p>如果你需要在 Coze 工作流中集成语音评测能力,可以通过 <strong>HTTP Request 节点</strong>调用评测 API:</p>\n\n <p className=\"font-semibold mt-4\">步骤 1:上传音频</p>\n <p>在工作流中添加一个 HTTP Request 节点,配置如下:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">方法</td><td className=\"py-2 px-3 font-mono text-xs\">POST</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/upload</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Content-Type</td><td className=\"py-2 px-3 font-mono text-xs\">audio/mp3</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Body</td><td className=\"py-2 px-3 text-muted-foreground\">音频文件二进制数据</td></tr>\n </tbody>\n </table>\n </div>\n <p>响应中获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{{audioId}}'}</code>,传递给下一步。</p>\n\n <p className=\"font-semibold mt-4\">步骤 2:调用评测</p>\n <p>添加第二个 HTTP Request 节点,通过 MCP 接口调用评测工具:</p>\n <CodeBlock filename=\"HTTP Request Body\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"evaluate_english_sentence\",\n \"arguments\": {\n \"audioId\": \"{{audioId}}\",\n \"sentence\": \"{{ref_text}}\"\n }\n },\n \"id\": 1\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">步骤 3:处理结果</p>\n <p>评测结果通过 JSON 格式返回,可使用 Coze 的 <strong>代码节点</strong> 或 <strong>LLM 节点</strong> 对结果进行解析和二次处理(如生成学习建议、计算进步趋势等)。</p>\n\n <Callout type=\"info\">工作流方式适合需要固定流程的场景(如批量评测、定时任务、与其他系统联动)。如果是交互式场景,推荐使用上方的 MCP 扩展方式,Agent 会自动选择合适的工具。</Callout>\n\n <div className=\"rounded-lg bg-muted/30 p-5 mt-4\">\n <p className=\"text-xs font-semibold mb-3\">完整工作流示意</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">用户输入音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 上传音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 调用评测</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM: 分析结果</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">输出反馈</span>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"config-ai-workspace\" title=\"AI 工作台 · 豆包 / 飞书智能伙伴 / WorkBuddy / 钉钉 AI\">\n <p>这一类是<strong>面向「上班族」的企业 / 日常 AI 助手</strong>,产品形态是个聊天框 + 插件市场 / 扩展面板。接入后,员工在 App 里打字「帮我评一下这段录音」就能自动触发驰声评测,不用写任何代码。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Bot className=\"h-4 w-4 text-amber-500\" /> 豆包(Doubao)App / 桌面版</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节跳动官方 AI 助手,2026 年起桌面版支持 MCP 扩展。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>桌面版设置 → <strong>「智能体扩展」</strong> → 添加 MCP 服务</li>\n <li>类型选 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">streamable-http</code>,URL 填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>对话中直接说「帮我评测」即可自动调用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Building2 className=\"h-4 w-4 text-blue-500\" /> 飞书智能伙伴(My AI)</p>\n <p className=\"text-xs text-muted-foreground mb-2\">飞书企业 AI,支持以 MCP / Function Calling 挂载外部工具给团队 Bot。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>飞书开发者后台 → 创建应用 → <strong>「AI 能力 / 智能伙伴」</strong></li>\n <li>在「工具」中选 <strong>外部 MCP 服务</strong>,填入上方 URL</li>\n <li>把智能伙伴拉进群聊,@ 它提交音频即可评测</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-rose-500\" /> WorkBuddy / 企业智能助手</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节 WorkBuddy、腾讯会议 AI、企业微信智能助手等企业 AI 工作台场景。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>在工作台的<strong>「技能 / 扩展」</strong>后台新增一个 MCP 连接器</li>\n <li>传输协议选 Streamable HTTP,地址填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>把技能发布到指定部门 / 群组,HR、培训、销售口语陪练场景即可直接用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Workflow className=\"h-4 w-4 text-cyan-500\" /> 钉钉 AI 助理 / 通义千问 App</p>\n <p className=\"text-xs text-muted-foreground mb-2\">阿里生态:钉钉 AI 助理支持自定义插件,通义 App 可通过「AI 工具箱」挂载 MCP。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>钉钉开发者后台 → AI 助理 → <strong>「插件开发 / MCP 接入」</strong></li>\n <li>选 HTTP Streamable,填入驰声 MCP URL 并开启鉴权(可选)</li>\n <li>在钉钉群 @AI 助理上传音频,自动返回评测卡片</li>\n </ol>\n </div>\n </div>\n\n <Callout type=\"tip\"><strong>典型场景 · 小龙虾们的共同剧本</strong>:销售口语陪练 → 外语培训 → 主持 / 播音岗位考核 → K12 英语作业批改 → 客服话术复盘。这些「上班族 AI 工作台」接入驰声 MCP 后,员工 / 学员只要对着话筒说话,AI 就能自动打分并给出个性化改进建议,运营 / 培训 / 教研团队零开发即可上线。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-workflow-platforms\" title=\"Dify / n8n / Flowise 等可视化编排\">\n <p>如果你需要把语音评测编排进<strong>多步业务流</strong>(例如:学员录音 → 评测 → 生成错题集 → 推送到 CRM),可视化工作流平台比聊天 Bot 更合适。</p>\n\n <div className=\"grid gap-3 md:grid-cols-3 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Dify</p>\n <p className=\"text-xs text-muted-foreground mb-2\">开源 LLMOps,v0.15+ 原生 MCP 工具。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>「工具」→「自定义 MCP」→ 填 URL</li>\n <li>在 Chatflow / Workflow 节点里直接拖用</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">n8n</p>\n <p className=\"text-xs text-muted-foreground mb-2\">工作流自动化,用 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">MCP Client</code> 节点。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>添加 <strong>MCP Client</strong> 节点</li>\n <li>Transport 选 HTTP Stream,URL 贴驰声地址</li>\n <li>Tool 下拉自动列出 16 个评测能力</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Flowise / LangFlow</p>\n <p className=\"text-xs text-muted-foreground mb-2\">可视化 LangChain Agent 编排。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>拖出 <strong>MCP Tool</strong> 节点</li>\n <li>连到 AgentExecutor 即可</li>\n </ul>\n </div>\n </div>\n\n <Callout type=\"info\">可视化平台的好处是<strong>流程可追溯、可回放</strong>,适合教育机构 / 培训部门做批量评测 + 报表导出;缺点是单步延迟略高于直接 MCP 调用,对延迟敏感的交互场景建议用 Cursor / Claude Desktop 直连。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"其他 MCP 客户端(通用配置)\">\n <p>任何遵循 MCP 协议的客户端都可以接入,统一使用以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权(可选)</td><td className=\"py-2 px-3 font-mono text-xs\">Header:<code>Authorization: Bearer &lt;MCP_API_KEY&gt;</code></td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">还没找到你用的客户端?任何支持 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 的终端都通用。找不到入口?下一小节「编程用 MCP」里用 SDK 直接接即可。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 编程用 MCP ══════ */}\n <DocSection id=\"config-code\" icon={Code2} title=\"接入 · 编程用 MCP(SDK / 代码集成)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Terminal className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>你在写一段<strong className=\"text-foreground\">真正的后端服务</strong>:一个 Agent Worker、一个 API、一个自动化脚本</li>\n <li>业务里的「语音评测」是其中一个步骤,需要由代码 <strong className=\"text-foreground\">决定何时调用</strong>、<strong className=\"text-foreground\">如何解析结果</strong></li>\n <li>三种主流写法:① 官方 MCP 客户端库 → ② Agent 框架自动桥接 → ③ 退化到 chat.completions + tools 参数</li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 强烈推荐①或②:驰声有 16 个评测工具,<strong className=\"text-foreground\">让 MCP 自动注入 tool schema</strong>,每新增一个题型不用改一行业务代码。</p>\n </div>\n\n <SubDoc id=\"code-mcp-sdk\" title=\"① 官方 MCP 客户端库(Python / TypeScript)\">\n <p>最底层、也最推荐的方式。由 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Anthropic 官方维护</a>,一次连接自动拿到全部工具的 JSON Schema,然后交给任意 LLM。</p>\n\n <p className=\"font-semibold mt-4\">Python:<code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp</code></p>\n <CodeBlock filename=\"chivox_client.py\" lang=\"python\">{`# pip install mcp openai\nimport asyncio, os\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nMCP_URL = \"https://speech-eval.site/mcp\"\nllm = OpenAI(api_key=os.getenv(\"DOUBAO_KEY\"),\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\") # 豆包 / DeepSeek / OpenAI 同构\n\nasync def main(audio_id: str, ref: str):\n async with streamablehttp_client(MCP_URL) as (reader, writer, _):\n async with ClientSession(reader, writer) as sess:\n await sess.initialize()\n\n tools = await sess.list_tools()\n openai_tools = [{\n \"type\": \"function\",\n \"function\": {\"name\": t.name, \"description\": t.description,\n \"parameters\": t.inputSchema},\n } for t in tools.tools]\n\n resp = llm.chat.completions.create(\n model=\"doubao-1-5-pro-32k\",\n messages=[{\"role\": \"user\",\n \"content\": f\"帮我评测 audioId={audio_id},参考文本:{ref}\"}],\n tools=openai_tools,\n )\n\n for call in resp.choices[0].message.tool_calls or []:\n result = await sess.call_tool(call.function.name,\n arguments=eval(call.function.arguments))\n print(call.function.name, \"→\", result.content[0].text[:200])\n\nasyncio.run(main(\"abc123\", \"I think therefore I am\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">TypeScript / Node.js:<code className=\"bg-muted px-1 rounded text-xs font-mono\">@modelcontextprotocol/sdk</code></p>\n <CodeBlock filename=\"chivox-client.ts\" lang=\"typescript\">{`// npm i @modelcontextprotocol/sdk openai\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport OpenAI from \"openai\";\n\nconst mcp = new Client({ name: \"chivox-biz\", version: \"1.0.0\" });\nawait mcp.connect(new StreamableHTTPClientTransport(new URL(\"https://speech-eval.site/mcp\")));\n\nconst { tools } = await mcp.listTools();\nconst openai = new OpenAI({ apiKey: process.env.DEEPSEEK_KEY, baseURL: \"https://api.deepseek.com\" });\n\nconst resp = await openai.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: \"评测 audioId=abc123, 参考 I think therefore I am\" }],\n tools: tools.map(t => ({ type: \"function\", function: {\n name: t.name, description: t.description, parameters: t.inputSchema\n }})),\n});\n\nfor (const call of resp.choices[0].message.tool_calls ?? []) {\n const r = await mcp.callTool({ name: call.function.name,\n arguments: JSON.parse(call.function.arguments) });\n console.log(call.function.name, r.content);\n}`}</CodeBlock>\n <Callout type=\"tip\">这种写法 <strong>LLM 供应商可换</strong>(豆包 / DeepSeek / GPT / Qwen / Moonshot),<strong>工具自动更新</strong>(驰声新增题型无需改业务代码),适合生产后端。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-agent-frameworks\" title=\"② 用 Agent 框架自动桥接(LangChain / Mastra / Agents SDK)\">\n <p>如果项目已经在用 Agent 框架,几行 adapter 就能把驰声 MCP 塞进去,框架本身会接管 tool-calling 循环、重试、日志。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LangChain / LangGraph(Python)</p>\n <CodeBlock filename=\"agent.py\" lang=\"python\">{`# pip install langchain-mcp-adapters langgraph\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom langgraph.prebuilt import create_react_agent\n\nclient = MultiServerMCPClient({\n \"chivox\": {\n \"transport\": \"streamable_http\",\n \"url\": \"https://speech-eval.site/mcp\",\n }\n})\ntools = await client.get_tools()\n\nagent = create_react_agent(\"openai:gpt-4o-mini\", tools)\nresult = await agent.ainvoke({\"messages\":\n [(\"user\", \"评测 audioId=abc123 参考 think\")]})\nprint(result[\"messages\"][-1].content)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Mastra(TypeScript)</p>\n <CodeBlock filename=\"mastra-agent.ts\" lang=\"typescript\">{`// npm i @mastra/core @mastra/mcp\nimport { MCPClient } from \"@mastra/mcp\";\nimport { Agent } from \"@mastra/core/agent\";\nimport { openai } from \"@ai-sdk/openai\";\n\nconst mcp = new MCPClient({ servers: {\n chivox: { url: new URL(\"https://speech-eval.site/mcp\") }\n}});\n\nexport const speechCoach = new Agent({\n name: \"speech-coach\",\n instructions: \"你是口语教练,用 chivox 工具评测并给改进建议。\",\n model: openai(\"gpt-4o-mini\"),\n tools: await mcp.getTools(),\n});`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">OpenAI Agents SDK(Python)</p>\n <CodeBlock filename=\"agents_sdk.py\" lang=\"python\">{`# pip install openai-agents\nfrom agents import Agent, Runner\nfrom agents.mcp import MCPServerStreamableHttp\n\nchivox = MCPServerStreamableHttp(\n params={\"url\": \"https://speech-eval.site/mcp\"},\n name=\"chivox-speech-eval\",\n)\n\nasync with chivox:\n agent = Agent(name=\"coach\",\n instructions=\"专业口语教练\",\n mcp_servers=[chivox])\n r = await Runner.run(agent, \"评测 audioId=abc123\")\n print(r.final_output)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LlamaIndex / AutoGen / CrewAI</p>\n <p className=\"text-xs text-muted-foreground mb-2\">这些框架都有官方或社区 MCP adapter,用法与上述类似:</p>\n <ul className=\"list-disc list-inside space-y-1 text-xs\">\n <li><strong>LlamaIndex</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">llama-index-tools-mcp</code></li>\n <li><strong>AutoGen</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">autogen-ext[mcp]</code></li>\n <li><strong>CrewAI</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">crewai-tools</code> 的 MCPServerAdapter</li>\n <li><strong>Spring AI</strong>(Java):<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">spring-ai-mcp-client</code></li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"code-function-calling\" title=\"③ 直调 chat.completions · tools 参数(豆包 / DeepSeek / 火山方舟)\">\n <p>当你用的 LLM SDK <strong>还没有原生 MCP 支持</strong>(比如老版本的火山方舟 / 百度千帆 / 通义 DashScope),退化方案是:用 MCP 客户端<strong>一次性拉到 tool schema</strong>,然后按 OpenAI Function Calling 格式喂给 LLM。</p>\n <CodeBlock filename=\"doubao_tools.py\" lang=\"python\">{`# 适用:豆包(火山方舟)/ DeepSeek / Qwen / Moonshot / GLM 等 OpenAI 兼容 API\n# 核心思路:MCP 列工具 → 转 OpenAI tools → LLM 决策 → MCP 执行 → 回填\n\nasync with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n\n # ① 用 MCP 拉到的 schema 自动转 OpenAI 格式\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 喂给豆包\n messages = [{\"role\": \"user\", \"content\": \"评测一下 audioId=abc123\"}]\n resp = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages, tools=oa_tools,\n )\n\n # ③ LLM 回 tool_calls → 折返跑到 MCP 执行\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for c in msg.tool_calls:\n result = await mcp.call_tool(\n c.function.name, arguments=json.loads(c.function.arguments))\n messages.append({\"role\": \"tool\", \"tool_call_id\": c.id,\n \"content\": result.content[0].text})\n # ④ 再喂一轮,让豆包根据评测结果产出自然语言诊断\n final = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages)\n print(final.choices[0].message.content)`}</CodeBlock>\n <Callout type=\"warning\"><strong>千万别手写 tool schema!</strong>驰声 16 个评测工具每个都有几十个字段,手写既容易错也难维护。一定用 MCP <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">list_tools()</code> 动态拿。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-selfhosted-agent\" title=\"④ 自研后端 Agent(FastAPI / NestJS / Spring)\">\n <p>大部分落地场景其实是:<strong>你已有一个业务后端</strong>(教育 SaaS / 培训系统 / 客服平台),只需要新增一个「语音评测」能力。推荐架构:</p>\n\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">前端录音 / 上传</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">业务后端</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">MCP Client(常驻连接)</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">驰声 MCP</span>\n <span className=\"text-muted-foreground\">↘</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM(诊断 / 出题)</span>\n </div>\n </div>\n\n <p className=\"font-semibold mt-2\">FastAPI 示例(生产最小骨架)</p>\n <CodeBlock filename=\"app.py\" lang=\"python\">{`from fastapi import FastAPI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\nfrom contextlib import asynccontextmanager\n\nmcp_session: ClientSession | None = None\n\n@asynccontextmanager\nasync def lifespan(app):\n global mcp_session\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as s:\n await s.initialize()\n mcp_session = s\n yield\n mcp_session = None\n\napp = FastAPI(lifespan=lifespan)\n\n@app.post(\"/api/evaluate\")\nasync def evaluate(audio_id: str, ref_text: str):\n # 直接选一个最合适的工具执行(不经 LLM,延迟最低)\n r = await mcp_session.call_tool(\"evaluate_english_sentence\",\n {\"audioId\": audio_id, \"sentence\": ref_text})\n return {\"result\": r.content[0].text}`}</CodeBlock>\n\n <Callout type=\"tip\"><strong>关键优化</strong>:① MCP 连接在 lifespan 里做成长连接,不要每次请求都重连;② 对延迟敏感的场景可以「跳过 LLM 直接调 tool」,把 LLM 放到「生成诊断报告」那一步。</Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mt-6 text-sm\">\n <p className=\"font-semibold mb-2\">方案选型速查</p>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 font-medium\">场景</th>\n <th className=\"text-left py-2 font-medium\">推荐方案</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2\">AI 对话 / Copilot 类产品,LLM 供应商可能会换</td><td className=\"py-2\">① 官方 MCP 客户端库</td></tr>\n <tr><td className=\"py-2\">已有 LangChain / Mastra / Agents SDK 技术栈</td><td className=\"py-2\">② Agent 框架 + adapter</td></tr>\n <tr><td className=\"py-2\">火山方舟 / 千帆控制台直连,最小侵入</td><td className=\"py-2\">③ chat.completions + tools</td></tr>\n <tr><td className=\"py-2\">教育 SaaS / 培训 / 客服等业务后端集成</td><td className=\"py-2\">④ 自研后端 Agent</td></tr>\n </tbody>\n </table>\n </div>\n </div>\n </DocSection>\n\n {/* ══════ 评测模式 ══════ */}\n <DocSection id=\"eval-modes\" icon={Radio} title=\"评测模式\">\n\n <SubDoc id=\"stream-eval\" title=\"实时录音评测(边录边评)\">\n <p>通过本地麦克风实时录音,音频以 WebSocket 流式推送到评测服务,<strong>无需生成中间文件</strong>。这是最灵活、延迟最低的评测方式,特别适合交互式口语练习场景。</p>\n <Callout type=\"tip\">实时录音评测需要安装 <strong>SoX</strong> 并使用本地代理模式(Claude Desktop / Claude Code)。Cursor 等远程直连模式暂不支持实时录音。</Callout>\n\n <p className=\"font-semibold mt-4\">需要按顺序调用三个工具:</p>\n\n <div className=\"mt-3 space-y-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">create_stream_session</code> — 创建流式评测会话</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.word.score、en.sent.score、cn.sent.raw' },\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式,默认 mp3' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100,默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code>,后续步骤需使用。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">start_recording</code> — 开始麦克风录音</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '来自 create_stream_session 的会话 ID' },\n { name: 'chunk_size', type: 'number', required: false, desc: '音频块大小(bytes),默认 3200' },\n { name: 'chunk_interval_ms', type: 'number', required: false, desc: '推送间隔(ms),默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">调用后自动启动麦克风,音频通过 WebSocket 实时推送到评测服务。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">stop_recording</code> — 停止录音并获取结果</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '会话 ID' },\n { name: 'timeout', type: 'number', required: false, desc: '等待评测结果超时秒数,默认 30' },\n ]} />\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"stream-flow\" title=\"流式评测完整流程\">\n <div className=\"rounded-lg border border-border/60 p-5 bg-muted/10 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong>用户:</strong>&quot;请评测我的英文发音,文本是 Hello&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">create_stream_session(core_type=&quot;en.word.score&quot;, ref_text=&quot;Hello&quot;)</code></p>\n <p className=\"text-muted-foreground\">→ 获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">session_id</code></p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p>AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">start_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground mt-1\">麦克风开始录音,音频通过 WebSocket 实时推送到评测服务</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>用户</strong>说完后告知 AI &quot;结束&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">stop_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground\">→ 返回评测结果(总分、准确度、流利度、音素得分等)</p>\n </FlowStep>\n </div>\n </SubDoc>\n\n <SubDoc id=\"file-eval\" title=\"音频文件评测\">\n <p>适用于已有音频文件的评测场景,支持三种输入方式:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">适用场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_file_path</td><td className=\"py-2 px-3\">本地文件路径</td><td className=\"py-2 px-3 text-muted-foreground\">代理自动读取并转为 Base64,最便捷</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_base64</td><td className=\"py-2 px-3\">Base64 编码数据</td><td className=\"py-2 px-3 text-muted-foreground\">已有编码数据时使用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_url</td><td className=\"py-2 px-3\">音频文件 URL</td><td className=\"py-2 px-3 text-muted-foreground\">音频托管在云存储时使用</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"tip\">推荐使用 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">audio_file_path</code>,只需传入本地文件路径,代理会自动处理编码和上传。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"tools-en\" title=\"英文评测工具(10 种)\">\n <ToolTable tools={[\n ['en_word_eval', '单词评测', '评测 \"Hello\" 的发音准确度'],\n ['en_word_correction', '单词纠音', '检测多读/漏读/错读并给出纠正建议'],\n ['en_vocab_eval', '词语评测', '同时评测多个单词的发音'],\n ['en_sentence_eval', '句子评测', '评测整句朗读的准确度与流利度'],\n ['en_sentence_correction', '句子纠音', '逐词检测发音问题并给出修正建议'],\n ['en_paragraph_eval', '段落朗读评测', '评测整段英文课文的朗读质量'],\n ['en_phonics_eval', '自然拼读评测', '评测 Phonics 拼读规则掌握程度'],\n ['en_choice_eval', '口语选择题', '从预设选项中识别口语回答'],\n ['en_semi_open_eval', '半开放题评测', '场景对话等半开放口语题型'],\n ['en_realtime_eval', '实时朗读评测', '实时反馈朗读质量,逐句评分'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong>所有英文评测工具均支持以下参数。</p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tools-cn\" title=\"中文评测工具(6 种)\">\n <ToolTable tools={[\n ['cn_word_raw_eval', '单字评测(汉字)', '评测 \"中\" 的发音'],\n ['cn_word_pinyin_eval', '单字评测(拼音)', '评测 \"zhōng\" 的发音'],\n ['cn_sentence_eval', '词句评测', '评测 \"你好世界\" 的朗读'],\n ['cn_paragraph_eval', '段落朗读评测', '评测一段中文课文的朗读质量'],\n ['cn_rec_eval', '有限分支识别评测', '从预设选项中识别用户发音'],\n ['cn_aitalk_eval', 'AI Talk 口语评测', '中文口语对话能力评测'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong></p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本(汉字或拼音)' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结构化结果:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"integrity\": 95,\n \"details\": [\n {\n \"char\": \"hello\",\n \"score\": 85,\n \"phone\": [\n { \"phoneme\": \"h\", \"score\": 90 },\n { \"phoneme\": \"ɛ\", \"score\": 82 },\n { \"phoneme\": \"l\", \"score\": 88 },\n { \"phoneme\": \"oʊ\", \"score\": 80 }\n ]\n }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall', '0–100', '综合总分,反映整体语音质量'],\n ['accuracy', '0–100', '准确度,音素级别的发音准确率'],\n ['pron', '0–100', '发音得分,整体发音质量评价'],\n ['fluency.overall', '0–100', '流利度,语流的自然度与平滑度'],\n ['fluency.speed', '0–100', '语速得分,每分钟词/音节计量'],\n ['fluency.pause', 'number', '不自然停顿次数'],\n ['integrity', '0–100', '完整度,是否完整朗读了参考文本'],\n ['details[].phone[]', 'array', '每个音素的独立评分,可定位具体弱项'],\n ['details[].stress[]', 'array', '重音得分,检测重弱读是否正确'],\n ].map(([field, range, desc]) => (\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期', '音频上传后 5 分钟内有效,评测完成后自动删除', '重新上传音频'],\n ['服务器繁忙', '当前请求过多,排队已满', '稍后重试,使用指数退避策略'],\n ['存储空间已满', '服务器临时音频存储已满', '稍后重试,系统会自动清理'],\n ['音频格式不支持', '上传了不支持的文件类型', '使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限', '超过该题型最大时长限制', '裁剪音频后重试'],\n ['WebSocket 连接断开', '网络不稳定导致流式连接中断', '代理会自动重连,如持续失败请检查网络'],\n ].map(([scene, reason, fix]) => (\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 架构说明 ══════ */}\n <DocSection id=\"architecture\" icon={Terminal} title=\"架构说明\">\n\n <SubDoc id=\"arch-diagram\" title=\"系统架构\">\n <div className=\"flex flex-wrap items-center justify-center gap-4 my-6\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Claude Desktop<br/>Claude Code<br/>Cursor</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">stdio</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-1\">chivox-local-mcp</p>\n <p className=\"text-[11px] text-muted-foreground\">本地代理<br/>SoX 录音</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP / WS</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">Remote Chivox</p>\n <p className=\"text-[11px] text-muted-foreground\">MCP Server<br/>评测引擎</p>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"transport\" title=\"传输协议\">\n <ul className=\"space-y-3\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>stdio</strong> — AI 客户端通过标准输入输出与本地代理通信,这是 MCP 的标准传输方式</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>HTTP</strong> — 音频文件评测工具通过 HTTP 代理到远程服务,适用于非流式场景</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>WebSocket</strong> — 实时录音音频通过 WebSocket 流式推送,支持断线自动重连</span>\n </li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 示例代码 ══════ */}\n <DocSection id=\"integration\" icon={Code2} title=\"示例代码\">\n\n <SubDoc id=\"code-python\" title=\"Python 上传音频\">\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://speech-eval.site/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"code-javascript\" title=\"JavaScript 上传音频\">\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst res = await fetch('https://speech-eval.site/upload', {\n method: 'POST',\n headers: { 'Content-Type': 'audio/mp3' },\n body: audioData,\n});\n\nconst result = await res.json();\nconsole.log('audioId:', result.audioId);`}</CodeBlock>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p>以下模板帮助你引导 LLM 对评测结果进行深度分析:</p>\n <div className=\"space-y-4 mt-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><MessageSquare className=\"h-4 w-4\" /> 发音诊断</p>\n <CodeBlock filename=\"prompt-diagnosis.txt\">{`你是一位专业的英语口语教练。请根据以下 MCP 评测结果分析学生的发音表现:\n\n评测结果:{evaluation_result}\n\n请按以下格式输出:\n1. 总体评价(一句话)\n2. 优势项(>80 分的指标)\n3. 弱项分析(<70 分的指标,定位到具体音素)\n4. 针对性练习建议(2-3 条可操作的建议)`}</CodeBlock>\n </div>\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><Mic className=\"h-4 w-4\" /> 练习生成</p>\n <CodeBlock filename=\"prompt-practice.txt\">{`根据以下弱项列表,为学生生成针对性的口语练习材料:\n\n弱项:{weak_points}\n\n请生成:\n1. 3 个绕口令(专门针对弱项音素)\n2. 5 个包含目标音素的常用短句(标注重音和连读)\n3. 1 段跟读练习段落(50-80 词)`}</CodeBlock>\n </div>\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><BarChart3 className=\"h-4 w-4\" /> 学习报告</p>\n <CodeBlock filename=\"prompt-report.txt\">{`请根据学生近 7 天的评测历史生成学习报告:\n\n评测历史:{history}\n\n报告要求:\n1. 整体趋势(进步/持平/退步)\n2. 各维度变化(准确度、流利度、语速)\n3. 本周最大进步项\n4. 下周重点练习建议\n5. 鼓励性总结`}</CodeBlock>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频过期:</strong>上传后 5 分钟内有效。建议在上传后 3 分钟内完成评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>服务繁忙:</strong>使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>录音中断:</strong>WebSocket 断线后代理会自动重连。如持续失败,检查网络连接和 SoX 安装。</span></li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>优先使用流式评测:</strong>实时录音模式跳过了文件上传环节,端到端延迟比文件评测低 30-50%。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频压缩:</strong>文件评测时,上传前将音频转为 16kHz 单声道 mp3,可减少 60-80% 的上传体积。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>选择合适的工具:</strong>单词评测比段落评测快 3-5 倍。如只需单词级分数,不要用句子评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>本地文件路径最便捷:</strong>使用 <code className=\"bg-muted px-1 rounded text-xs\">audio_file_path</code> 参数传入本地路径,代理自动处理编码,无需手动转 Base64。</span></li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Globe} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP JSON-RPC 接口(Streamable HTTP)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制', '500MB', '最大音频存储空间'],\n ['最大排队', '10', '等待处理的请求数'],\n ['最大并发', '3', '同时处理的评测数'],\n ['音频有效期', '5 分钟', '上传后未评测自动过期'],\n ['文件大小限制', '50MB', '单个音频文件最大'],\n ].map(([k, v, d]) => (\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <div className=\"space-y-3\">\n {[\n { ver: 'v3.0.0', date: '2026-04-16', desc: '新增本地代理模式,支持实时录音流式评测,工具扩展至 16 种' },\n { ver: 'v2.3.0', date: '2026-03-25', desc: '支持 HTTPS,域名 speech-eval.site' },\n { ver: 'v2.2.0', date: '2026-03-24', desc: '添加 MCP 配置指南(Cursor / Claude Desktop)' },\n { ver: 'v2.1.0', date: '2026-03-24', desc: '简化上传流程,统一使用 HTTP API' },\n { ver: 'v2.0.0', date: '2026-03-24', desc: '初始版本,支持多种评测类型' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </DocsShell>\n );\n}\n\nexport default function DocsPage() {\n const locale = useLocale();\n return locale.startsWith('zh') ? <DocsPageZh /> : <DocsPageEn />;\n}\n"}, "32fd97e4c3e451e9645b39a311fe3e4e0b8ebd50": {"t": 1785765804.3718727, "n": 95760, "h": "eabab60f01cf5d60", "c": "'use client';\n\nimport { useState, useEffect, useMemo, useRef, useCallback } from 'react';\nimport { Link } from '@/i18n/routing';\nimport { ArrowLeft, BookOpen, Code2, Zap, FileText, Copy, Check, Terminal, Globe, Mic, MessageSquare, BarChart3, AlertTriangle, Lightbulb, Radio, FolderOpen, Settings, Workflow, Sparkles, Plug, Bot, Building2 } from 'lucide-react';\n\n/* ─── Reusable Components ─── */\n\nfunction CopyButton({ text }: { text: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <button\n onClick={() => { navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); }}\n className=\"absolute top-3 right-3 p-1.5 rounded-md bg-white/5 hover:bg-white/10 transition-colors text-zinc-400 hover:text-zinc-200\"\n >\n {copied ? <Check className=\"h-3.5 w-3.5\" /> : <Copy className=\"h-3.5 w-3.5\" />}\n </button>\n );\n}\n\nfunction CodeBlock({ filename, lang, children }: { filename?: string; lang?: string; children: string }) {\n return (\n <div className=\"rounded-lg border border-border/60 bg-zinc-950 text-zinc-100 overflow-hidden my-4 relative\">\n {filename && (\n <div className=\"flex items-center gap-1.5 px-3 sm:px-4 py-2 border-b border-white/[0.06] text-[11px] sm:text-xs text-zinc-500 font-mono\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-red-500/60\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-yellow-500/60\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-green-500/60\" />\n <span className=\"ml-2 truncate\">{filename}</span>\n {lang && <span className=\"ml-auto text-zinc-600\">{lang}</span>}\n </div>\n )}\n <CopyButton text={children.trim()} />\n <pre className=\"p-3 sm:p-4 text-[12px] sm:text-sm font-mono leading-relaxed overflow-x-auto\"><code>{children.trim()}</code></pre>\n <div className=\"sm:hidden px-3 pb-2 text-[10px] text-zinc-500\">左右滑动查看完整代码</div>\n </div>\n );\n}\n\nfunction DocSection({ id, icon: Icon, title, children }: { id: string; icon: React.ElementType; title: string; children: React.ReactNode }) {\n return (\n <section id={id} className=\"scroll-mt-24 mb-16\">\n <div className=\"flex items-center gap-3 mb-6 pb-3 border-b border-border/40\">\n <Icon className=\"h-5 w-5 text-muted-foreground shrink-0\" />\n <h2 className=\"text-xl font-bold tracking-tight\">{title}</h2>\n </div>\n <div className=\"text-sm leading-relaxed text-foreground/90 space-y-6\">{children}</div>\n </section>\n );\n}\n\nfunction SubDoc({ id, title, children }: { id: string; title: string; children: React.ReactNode }) {\n return (\n <div id={id} className=\"scroll-mt-24 mb-10\">\n <h3 className=\"text-base font-semibold mb-3\">{title}</h3>\n <div className=\"space-y-4\">{children}</div>\n </div>\n );\n}\n\nfunction ParamTable({ params }: { params: { name: string; type: string; required: boolean; desc: string }[] }) {\n return (\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full min-w-[620px] text-xs sm:text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">类型</th>\n <th className=\"text-left py-2 px-3 font-medium\">必填</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {params.map(p => (\n <tr key={p.name}>\n <td className=\"py-2 px-3 font-mono text-xs\">{p.name}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{p.type}</td>\n <td className=\"py-2 px-3\">{p.required ? <span className=\"text-emerald-600 dark:text-emerald-400\">✓</span> : <span className=\"text-muted-foreground\">—</span>}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{p.desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction Callout({ type = 'info', children }: { type?: 'info' | 'warning' | 'tip'; children: React.ReactNode }) {\n const styles = {\n info: 'border-blue-200 bg-blue-50 text-blue-900 dark:border-blue-900 dark:bg-blue-950/30 dark:text-blue-200',\n warning: 'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950/30 dark:text-amber-200',\n tip: 'border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-900 dark:bg-emerald-950/30 dark:text-emerald-200',\n };\n const icons = { info: Globe, warning: AlertTriangle, tip: Lightbulb };\n const Ic = icons[type];\n return (\n <div className={`flex gap-3 rounded-lg border p-4 text-sm ${styles[type]}`}>\n <Ic className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div>{children}</div>\n </div>\n );\n}\n\nfunction FlowStep({ title, children }: { title: string; children: React.ReactNode }) {\n return (\n <div className=\"flex gap-4 items-start\">\n <div className=\"h-7 w-7 rounded-full bg-foreground text-background flex items-center justify-center text-xs font-bold shrink-0 mt-0.5\">{title}</div>\n <div className=\"flex-1 text-sm\">{children}</div>\n </div>\n );\n}\n\nfunction ToolTable({ tools }: { tools: [string, string, string][] }) {\n return (\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full min-w-[680px] text-xs sm:text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">工具名</th>\n <th className=\"text-left py-2 px-3 font-medium\">功能</th>\n <th className=\"text-left py-2 px-3 font-medium\">典型场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {tools.map(([name, desc, scene]) => (\n <tr key={name}>\n <td className=\"py-2 px-3 font-mono text-xs\">{name}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{scene}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\n/* ─── Navigation Structure ─── */\n\nconst NAV = [\n { id: 'concept', icon: Workflow, label: 'MCP 工作原理', children: [\n { id: 'why-mcp', label: '为什么需要 MCP' },\n { id: 'scenario-walkthrough', label: '场景走查 · 6 步' },\n { id: 'dev-responsibility', label: '开发者只做两件事' },\n { id: 'integration-paths', label: '三种接入姿势' },\n ]},\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'requirements', label: '系统要求' },\n { id: 'install', label: '安装方式' },\n { id: 'env-vars', label: '环境变量' },\n ]},\n { id: 'config', icon: Plug, label: '接入 · 直接用(零代码)', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: '扣子(Coze)' },\n { id: 'config-coze-workflow', label: 'Coze 工作流' },\n { id: 'config-ai-workspace', label: 'AI 工作台 · 豆包 / 飞书 / WorkBuddy / 钉钉' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: '其他 MCP 客户端' },\n ]},\n { id: 'config-code', icon: Code2, label: '接入 · 编程用(SDK / 代码)', children: [\n { id: 'code-mcp-sdk', label: '官方 MCP 客户端库' },\n { id: 'code-agent-frameworks', label: 'LangChain / Mastra / Agents SDK' },\n { id: 'code-function-calling', label: '直调 chat.completions · tools' },\n { id: 'code-selfhosted-agent', label: '自研后端 Agent' },\n ]},\n { id: 'eval-modes', icon: Radio, label: '评测模式', children: [\n { id: 'stream-eval', label: '实时录音评测' },\n { id: 'stream-flow', label: '流式评测流程' },\n { id: 'file-eval', label: '音频文件评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'tools-en', label: '英文评测工具' },\n { id: 'tools-cn', label: '中文评测工具' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'architecture', icon: Terminal, label: '架构说明', children: [\n { id: 'arch-diagram', label: '架构图' },\n { id: 'transport', label: '传输协议' },\n ]},\n { id: 'integration', icon: Code2, label: '示例代码', children: [\n { id: 'code-python', label: 'Python' },\n { id: 'code-javascript', label: 'JavaScript' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n { id: 'service-info', icon: Globe, label: '服务信息', children: [\n { id: 'endpoints', label: '服务端点' },\n { id: 'limits', label: '服务限制' },\n { id: 'changelog', label: '更新日志' },\n ]},\n];\n\n/* ─── Page ─── */\n\nexport default function DocsPage() {\n const [activeId, setActiveId] = useState<string>('quick-start');\n const contentRef = useRef<HTMLDivElement | null>(null);\n const navRef = useRef<HTMLElement | null>(null);\n\n // 收集所有可滚动的锚点 id(包含分组和子章节),用于 scroll-spy\n const allIds = useMemo(() => {\n const ids: string[] = [];\n NAV.forEach(g => {\n ids.push(g.id);\n g.children.forEach(c => ids.push(c.id));\n });\n return ids;\n }, []);\n\n const scrollToAnchor = useCallback((id: string) => {\n const target = document.getElementById(id);\n if (!target) return;\n\n const scroller = contentRef.current;\n if (scroller && window.innerWidth >= 1024) {\n const scrollerRect = scroller.getBoundingClientRect();\n const targetRect = target.getBoundingClientRect();\n const delta = targetRect.top - scrollerRect.top;\n const top = scroller.scrollTop + delta - 24;\n scroller.scrollTo({ top, behavior: 'smooth' });\n window.history.replaceState(null, '', `#${id}`);\n return;\n }\n\n window.scrollTo({ top: target.offsetTop - 88, behavior: 'smooth' });\n window.history.replaceState(null, '', `#${id}`);\n }, []);\n\n useEffect(() => {\n const getAnchors = () =>\n allIds\n .map(id => document.getElementById(id))\n .filter((el): el is HTMLElement => el !== null)\n .map(el => ({\n id: el.id,\n top: (() => {\n if (useInnerScroller) {\n const scrollerRect = scroller.getBoundingClientRect();\n const elRect = el.getBoundingClientRect();\n return scroller.scrollTop + (elRect.top - scrollerRect.top);\n }\n // 使用文档绝对坐标,避免 offsetTop 在复杂父级下失真\n return el.getBoundingClientRect().top + window.scrollY;\n })(),\n }))\n .sort((a, b) => a.top - b.top);\n\n let rafId = 0;\n const scroller = contentRef.current;\n const useInnerScroller = !!scroller && window.innerWidth >= 1024;\n\n const computeActive = () => {\n const anchors = getAnchors();\n if (anchors.length === 0) return;\n\n const scrollTop = useInnerScroller ? scroller.scrollTop : window.scrollY;\n const probeY = scrollTop + 170; // 顶栏 + 阅读缓冲\n let currentId = anchors[0].id;\n\n for (const a of anchors) {\n if (a.top <= probeY) currentId = a.id;\n else break;\n }\n\n // 滚到底部时,强制高亮最后一项\n const atBottom = useInnerScroller\n ? scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 4\n : window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 4;\n if (atBottom) {\n currentId = anchors[anchors.length - 1].id;\n }\n\n setActiveId(prev => (prev === currentId ? prev : currentId));\n };\n\n const onScroll = () => {\n if (rafId) return;\n rafId = window.requestAnimationFrame(() => {\n rafId = 0;\n computeActive();\n });\n };\n\n // 初次与异步内容稳定后各计算一次,避免首屏偏差\n computeActive();\n const t = window.setTimeout(computeActive, 120);\n\n if (useInnerScroller) {\n scroller.addEventListener('scroll', onScroll, { passive: true });\n } else {\n window.addEventListener('scroll', onScroll, { passive: true });\n }\n window.addEventListener('resize', computeActive);\n window.addEventListener('hashchange', computeActive);\n return () => {\n window.clearTimeout(t);\n if (rafId) window.cancelAnimationFrame(rafId);\n if (useInnerScroller) {\n scroller.removeEventListener('scroll', onScroll);\n } else {\n window.removeEventListener('scroll', onScroll);\n }\n window.removeEventListener('resize', computeActive);\n window.removeEventListener('hashchange', computeActive);\n };\n }, [allIds]);\n\n // 根据当前激活的子节点,找出其所属分组 id,用于同步高亮父级\n const activeGroupId = useMemo(() => {\n for (const g of NAV) {\n if (g.id === activeId) return g.id;\n if (g.children.some(c => c.id === activeId)) return g.id;\n }\n return activeId;\n }, [activeId]);\n\n // 左侧目录如果出现滚动,则保证当前激活项自动进入可视区域\n useEffect(() => {\n const nav = navRef.current;\n if (!nav) return;\n if (window.innerWidth < 1024) return;\n\n const target = nav.querySelector<HTMLElement>(`[data-nav-id=\"${activeId}\"]`);\n if (!target) return;\n\n const navRect = nav.getBoundingClientRect();\n const targetRect = target.getBoundingClientRect();\n const outOfView = targetRect.top < navRect.top + 8 || targetRect.bottom > navRect.bottom - 8;\n\n if (outOfView) {\n target.scrollIntoView({ block: 'nearest', behavior: 'smooth' });\n }\n }, [activeId]);\n\n return (\n <main className=\"flex-1\">\n <div className=\"container mx-auto px-6 py-12 md:py-20\">\n <Link href=\"/\" className=\"inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors mb-8\">\n <ArrowLeft className=\"h-4 w-4\" /> 返回首页\n </Link>\n\n <h1 className=\"text-3xl md:text-4xl font-bold tracking-tight mb-2\">开发者文档</h1>\n <p className=\"text-muted-foreground mb-10\">驰声语音评测 MCP · chivox-local-mcp</p>\n\n <div className=\"grid gap-8 md:gap-10 lg:grid-cols-[18rem_minmax(0,1fr)]\">\n {/* Sidebar */}\n <nav\n ref={navRef}\n className=\"hidden lg:block self-start h-[calc(100vh-6rem)] overflow-y-auto pr-2\"\n >\n {NAV.map(group => {\n const isGroupActive = activeGroupId === group.id;\n return (\n <div key={group.id} className=\"mb-5\">\n <a\n href={`#${group.id}`}\n data-nav-id={group.id}\n onClick={(e) => {\n e.preventDefault();\n setActiveId(group.id);\n scrollToAnchor(group.id);\n }}\n className={`flex items-center gap-2 text-sm font-semibold mb-1.5 transition-colors ${\n isGroupActive ? 'text-foreground' : 'text-foreground/70 hover:text-foreground'\n }`}\n >\n <group.icon className={`h-4 w-4 ${isGroupActive ? 'text-foreground' : 'text-muted-foreground'}`} />\n {group.label}\n </a>\n <ul className=\"ml-6 space-y-1 border-l border-border/40 pl-3 relative\">\n {group.children.map(child => {\n const isActive = activeId === child.id;\n return (\n <li key={child.id} className=\"relative\">\n {isActive && (\n <span className=\"absolute -left-[13px] top-0 bottom-0 w-0.5 bg-foreground rounded-full\" />\n )}\n <a\n href={`#${child.id}`}\n data-nav-id={child.id}\n onClick={(e) => {\n e.preventDefault();\n setActiveId(child.id);\n scrollToAnchor(child.id);\n }}\n className={`text-xs block py-0.5 leading-5 transition-colors ${\n isActive\n ? 'text-foreground font-medium'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {child.label}\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n );\n })}\n </nav>\n\n {/* Content */}\n <div\n ref={contentRef}\n className=\"min-w-0 max-w-3xl lg:max-w-none lg:h-[calc(100vh-6rem)] lg:overflow-y-auto lg:pr-3\"\n >\n\n {/* ══════ MCP 工作原理 ══════ */}\n <DocSection id=\"concept\" icon={Workflow} title=\"MCP 工作原理\">\n\n <SubDoc id=\"why-mcp\" title=\"为什么需要 MCP\">\n <p>\n 大模型(GPT / Claude / Gemini / 通义千问 等)本质上是<strong>文字大脑</strong>:它擅长读和写,但天生<strong>听不到</strong>音频、<strong>打不开</strong>你的数据库、<strong>调不通</strong>第三方 API。\n </p>\n <p>\n <strong>MCP(Model Context Protocol)</strong>是一套由 Anthropic 主导、业界共同推进的开放协议,它像一根\"对讲机\":让大模型可以在需要时主动去调用外部能力(工具、数据源、服务),然后把结果接回来继续对话。\n </p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <AlertTriangle className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n <span className=\"text-sm font-semibold\">没有 MCP 的做法</span>\n </div>\n <ul className=\"text-xs text-muted-foreground space-y-1.5 leading-relaxed\">\n <li>· 研究评测服务的 REST 文档</li>\n <li>· 自己写鉴权 / 上传 / 轮询代码</li>\n <li>· 把结果序列化后塞进 prompt</li>\n <li>· 每换一个大模型都得重写一遍</li>\n </ul>\n </div>\n <div className=\"rounded-lg border-2 border-foreground/60 p-4 bg-foreground/[0.02]\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Check className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">用驰声 MCP 之后</span>\n </div>\n <ul className=\"text-xs space-y-1.5 leading-relaxed\">\n <li>· 填一行 MCP Server 地址</li>\n <li>· 大模型自动发现&amp;调用评测工具</li>\n <li>· 换任何支持 MCP 的大模型都能用</li>\n <li>· 不写一行调用代码</li>\n </ul>\n </div>\n </div>\n <Callout type=\"info\">\n MCP 让\"接入语音评测\"从一个\"后端工程\"变成了一个\"配置项\"。这也是为什么 Cursor、Claude Desktop、Dify、Coze、字节豆包、飞书智能助手都已经原生支持 MCP。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"scenario-walkthrough\" title=\"场景走查 · 从一段录音到温柔反馈\">\n <p>\n 想象一个具体场景:一位开发者用 Claude 做了一款 <strong>「AI 雅思口语陪练」小程序</strong>,接入驰声语音评测 MCP。当用户发来一段录音,整个链路是这样的:\n </p>\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-5 my-4 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong className=\"text-foreground\">配置阶段</strong>(开发者 · 一次性)</p>\n <p className=\"text-muted-foreground mt-1\">在 AI 后端的配置文件里填一行驰声 MCP Server 地址。大模型启动时自动发现:「我多了一个叫<strong>语音评测</strong>的工具」。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong className=\"text-foreground\">接收用户指令</strong>(终端用户)</p>\n <p className=\"text-muted-foreground mt-1\">小程序用户发来一段自我介绍录音:「帮我听听,发音哪里有问题?」后端把「文字 + 录音链接」打包给大模型。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong className=\"text-foreground\">大模型自主思考</strong>(LLM · 最核心的魔法)</p>\n <div className=\"text-muted-foreground mt-1 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed\">\n 「用户让我听发音。」<br />\n 「但我只是文字大脑,我听不到。」<br />\n 「等等 —— 开发者给了我一个叫 <code className=\"bg-muted px-1 rounded\">chivox_voice_eval</code> 的 MCP 工具!」\n </div>\n </FlowStep>\n <FlowStep title=\"4\">\n <p><strong className=\"text-foreground\">MCP 协议 · 自动调用</strong>(LLM → 驰声)</p>\n <p className=\"text-muted-foreground mt-1\">大模型自动按 MCP 标准构造 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools/call</code> 请求,把录音链接和参考文本发到驰声服务。<span className=\"text-foreground\">开发者不需要写任何调用代码。</span></p>\n </FlowStep>\n <FlowStep title=\"5\">\n <p><strong className=\"text-foreground\">驰声服务处理</strong>(评测引擎)</p>\n <p className=\"text-muted-foreground mt-1\">驰声对音频进行考试级打分,把结构化结果通过 MCP 原路返回:<code className=\"bg-muted px-1 rounded text-xs font-mono\">overall 85</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">/θ/ 音发音错误</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">流利度 88</code>。</p>\n </FlowStep>\n <FlowStep title=\"6\">\n <p><strong className=\"text-foreground\">大模型整合并输出反馈</strong>(LLM)</p>\n <p className=\"text-muted-foreground mt-1\">大模型把冷冰冰的分数翻译成温柔反馈:</p>\n <div className=\"text-foreground/80 mt-2 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed italic\">\n 「你的自我介绍很棒!综合 85 分 🎉 不过 <strong>think</strong> 这个词的 /θ/ 咬舌音不太标准,我们一起来练一下……」\n </div>\n </FlowStep>\n </div>\n <Callout type=\"tip\">\n <strong>整条链路里,MCP 只出现在 ① 和 ④</strong>:① 让开发者一次性告诉大模型「你多了一个工具」,④ 让大模型可以自动调用它。这两步的标准化,就是 MCP 的全部价值。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"dev-responsibility\" title=\"开发者只做两件事\">\n <p>听起来复杂,但对开发者来说只需要两件事:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">1</span>\n <span className=\"text-sm font-semibold\">填一次 MCP 配置</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 在 AI 后端的配置里填入驰声 MCP Server 地址和 API Key,就这一次,以后新加评测能力完全免开发。\n </p>\n <CodeBlock filename=\"mcp-config.json\" lang=\"json\">{`{\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"apiKey\": \"sk-••••••••••\"\n }\n}`}</CodeBlock>\n </div>\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">2</span>\n <span className=\"text-sm font-semibold\">写一段 System Prompt</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 告诉大模型这个工具是做什么的、什么时候调用、结果怎么转成用户友好的反馈。\n </p>\n <CodeBlock filename=\"system-prompt.md\">{`你是一位英语口语教练。\n当用户发来录音时:\n1. 调用 chivox_voice_eval 评分\n2. 聚焦 <70 分的音素家族\n3. 用高情商话术给出练习建议`}</CodeBlock>\n </div>\n </div>\n <p className=\"text-muted-foreground\">\n 其余所有事情 —— 音频上传、鉴权、工具发现、调用时机、返回格式、结果整合 —— 都由 MCP 协议和大模型自己搞定。\n </p>\n </SubDoc>\n\n <SubDoc id=\"integration-paths\" title=\"三种典型接入姿势\">\n <p>根据你的技术栈,有三种等价的接入方式,底层都走完全相同的 MCP 协议:</p>\n\n {/* ── 姿势一:可视化 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Sparkles className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势一 · 可视化拖拽(扣子 / Coze / 飞书智能助手)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合产品经理、运营、不写后端代码的开发者。整个过程像在手机上装 App。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>找到入口</strong>:在扣子平台的 <strong>「插件 / 工具」</strong> 配置区,点击<strong>「添加 MCP 服务」</strong>。</li>\n <li><strong>填两个字段</strong>(这也是你未来会从驰声这边拿到的):\n <div className=\"mt-2 overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium w-32\">服务器地址</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权密钥</td><td className=\"py-2 px-3 font-mono text-xs\">sk-•••••••••• <span className=\"text-muted-foreground ml-2\">(由驰声分配,用于计费与识别)</span></td></tr>\n </tbody>\n </table>\n </div>\n </li>\n <li><strong>平台自动发现能力</strong>:扣子会访问你填的地址,读取 MCP Server 自描述的\"说明书\"。瞬间明白:「这是个语音评测工具,需要传音频,会返发音分」。</li>\n <li><strong>右侧测试</strong>:在平台的聊天框里发一段语音,大模型就会自动调用工具。<strong>整个过程不需要写一行代码。</strong></li>\n </ol>\n </div>\n\n {/* ── 姿势二:代码框架 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Code2 className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势二 · 代码框架配置(Dify / LangChain / 自研后端)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合做独立 APP / 小程序、用 Dify 或 LangChain 写 Agent 的极客开发者。核心就是往配置文件里加一段 JSON。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>打开配置文件</strong>(项目根目录下的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp.json</code> 或框架指定的配置路径)。</li>\n <li><strong>在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code> 下增加一个节点</strong>:\n <CodeBlock filename=\"mcp.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"env\": {\n \"API_KEY\": \"你的专属密钥\"\n }\n }\n }\n}`}</CodeBlock>\n </li>\n <li><strong>重启后端服务</strong>。大模型的底层调度器会在启动时自动连上驰声 MCP Server,把 16 种评测工具注册成<strong>随时待命</strong>的 tools。</li>\n <li><strong>触发调用</strong>:用户发来录音后,大模型看到 system prompt 的引导,自动选择合适的工具(单词/句子/段落/半开放)调用。</li>\n </ol>\n </div>\n\n {/* ── 姿势三:Function Calling 直连(分 A / B 两种做法) ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Terminal className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势三 · 原生 Function Calling(豆包 / DeepSeek / OpenAI 兼容 API)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合不用任何 Agent 框架、直接调用 LLM API 的后端开发者。豆包 1.5+ / DeepSeek / GPT / Qwen 等模型都走标准 <code className=\"bg-muted px-1 rounded text-xs font-mono\">chat.completions</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> 参数。这里又分 A / B 两种做法,<strong>B 才是生产推荐</strong>。\n </p>\n\n <Callout type=\"warning\">\n 常见疑问:<em>\"要不要手写一个 tool 定义?\"</em><br />\n — <strong>姿势一 / 二不用</strong>(Coze、Dify、火山方舟控制台、Claude Desktop 会自动从 MCP Server 拉工具列表)。<br />\n — <strong>姿势三看你选 A 还是 B</strong>:A 要手写,B 用 MCP 客户端库自动拉。\n </Callout>\n\n {/* ── 3-A:手写 tool schema ── */}\n <div className=\"mt-4 rounded-md border border-border/40 bg-muted/20 p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold\">3-A · 手写 tool schema(最快验证,不推荐生产)</p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20\">快速 Demo</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 自己写一段 JSON Schema 告诉豆包\"有个工具叫 xxx,参数长这样\",豆包返回 tool_call 后你再手动去调驰声 MCP。<strong>缺点</strong>:驰声有 16 个评测工具,你每加一个题型都要改代码。\n </p>\n <CodeBlock filename=\"3a_hardcode_tool.py\" lang=\"python\">{`import os, json\nfrom openai import OpenAI\n\nclient = OpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv('ARK_API_KEY'),\n)\n\n# ─── 手写一段 tool schema(不推荐:16 个工具就要写 16 段)───\ntools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英语单词发音评测。当用户让你评价英语发音并提供录音链接时调用。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"audio_url\": {\"type\": \"string\", \"description\": \"录音 URL\"},\n \"reference_text\": {\"type\": \"string\", \"description\": \"参考文本,如 Apple\"},\n },\n \"required\": [\"audio_url\", \"reference_text\"]\n }\n }\n}]\n\nresp = client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,拿到评测结果后温柔地反馈,不要直接报分数。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n)\n\nmsg = resp.choices[0].message\nif msg.tool_calls:\n args = json.loads(msg.tool_calls[0].function.arguments)\n # ↓↓↓ 这里手动调驰声 MCP,再把结果回填给豆包做第二趟 ↓↓↓\n # result = requests.post(\"https://speech-eval.site/mcp\", json={...})`}</CodeBlock>\n </div>\n\n {/* ── 3-B:MCP 自动发现 ── */}\n <div className=\"mt-3 rounded-md border border-foreground/30 bg-foreground/[0.03] dark:bg-white/[0.03] p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold flex items-center gap-1.5\">\n 3-B · 用 MCP 客户端库自动发现 <Sparkles className=\"h-3 w-3 text-foreground\" />\n </p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-foreground text-background\">生产推荐</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 启动时连上驰声 MCP Server,调一次 <code className=\"bg-muted px-1 rounded\">tools/list</code>,把 16 个评测工具自动转成 Function Calling 的 tools 格式。<strong>驰声加新工具,你代码不用改</strong>。这才是 MCP 协议的真正价值。\n </p>\n <CodeBlock filename=\"3b_mcp_autodiscover.py\" lang=\"python\">{`import asyncio, os\nfrom openai import AsyncOpenAI\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\n\nclient = AsyncOpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv(\"ARK_API_KEY\"),\n)\n\nasync def main():\n # ─── 1. 连驰声 MCP Server,自动拉取全部 16 个工具 ───\n async with streamablehttp_client(\n \"https://speech-eval.site/mcp\",\n headers={\"Authorization\": f\"Bearer {os.getenv('CHIVOX_KEY')}\"},\n ) as (read, write, _):\n async with ClientSession(read, write) as session:\n await session.initialize()\n mcp_tools = (await session.list_tools()).tools # ← 一行拿到全部工具\n\n # ─── 2. MCP schema → OpenAI Function Calling schema ───\n tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema, # MCP 直接给 JSON Schema\n }\n } for t in mcp_tools]\n\n # ─── 3. 正常调豆包,tools 由 MCP 动态注入 ───\n resp = await client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,收到评测结果后温柔反馈。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n )\n\n # ─── 4. 折返跑:豆包选好工具后,直接走 MCP 调驰声 ───\n msg = resp.choices[0].message\n if msg.tool_calls:\n call = msg.tool_calls[0]\n result = await session.call_tool(\n call.function.name,\n arguments=__import__(\"json\").loads(call.function.arguments),\n )\n # 把 result 回填给豆包做第二趟 chat.completions 即可\n\nasyncio.run(main())`}</CodeBlock>\n <p className=\"text-[11px] text-muted-foreground mt-3 leading-relaxed\">\n 想要更省心?直接用 <a href=\"https://github.com/modelcontextprotocol/python-sdk\" target=\"_blank\" rel=\"noreferrer\" className=\"text-foreground underline underline-offset-2\">官方 mcp python sdk</a>、<code className=\"bg-muted px-1 rounded text-xs\">mcp-use</code>、<code className=\"bg-muted px-1 rounded text-xs\">openai-agents</code>,或火山官方 <code className=\"bg-muted px-1 rounded text-xs\">Arkitect</code> 高代码 SDK,它们都把上面这段\"MCP → Function Calling\"的桥接封装好了。\n </p>\n </div>\n </div>\n\n <Callout type=\"info\">\n 三种姿势走的是完全相同的 MCP 协议,能力完全等价。选型建议:<strong>产品经理 / 原型验证用姿势一</strong>(Coze 填表单);<strong>AI 客户端用户用姿势二</strong>(Cursor / Claude Desktop 填 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">mcp.json</code>);<strong>后端生产服务用姿势三-B</strong>(MCP 客户端库自动发现工具,不要手写 tool schema)。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-local-mcp</strong> 是驰声语音评测的 MCP 本地代理,通过 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 将远程驰声评测服务桥接到本地,让 AI 助手(Claude Desktop、Claude Code、Cursor 等)直接调用语音评测能力。</p>\n <p>它支持两种评测模式,覆盖从即时交互到批量处理的全部场景:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Mic className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">实时录音评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">通过麦克风边录边评,音频以 WebSocket 流式推送,无需生成中间文件,延迟更低、体验更佳。</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <FolderOpen className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">音频文件评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">支持本地文件路径、Base64 编码、URL 三种输入,适合已有音频的批量评测场景。</p>\n </div>\n </div>\n <Callout type=\"info\">当前支持 <strong>16 种评测工具</strong>:英文 10 种(单词、句子、段落、纠音、自然拼读、口语选择、半开放题等)+ 中文 6 种(汉字、拼音、词句、段落、有限分支、AI Talk)。</Callout>\n </SubDoc>\n\n <SubDoc id=\"requirements\" title=\"系统要求\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">依赖</th>\n <th className=\"text-left py-2 px-3 font-medium\">版本</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-semibold\">Node.js</td><td className=\"py-2 px-3 font-mono text-xs\">{'>'}= 18</td><td className=\"py-2 px-3 text-muted-foreground\">运行时必须</td></tr>\n <tr><td className=\"py-2 px-3 font-semibold\">SoX</td><td className=\"py-2 px-3 font-mono text-xs\">any</td><td className=\"py-2 px-3 text-muted-foreground\">仅实时录音功能需要(处理麦克风音频流)</td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"mt-3\">安装 SoX(仅在使用实时录音时需要):</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# macOS\nbrew install sox\n\n# Ubuntu / Debian\nsudo apt-get install sox`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"install\" title=\"安装方式\">\n <p><strong>方式一:npm 全局安装</strong>(推荐)</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`npm install -g chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式二:npx 免安装运行</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`MCP_REMOTE_URL=http://your-server:8080 npx chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式三:从源码构建</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`git clone https://git.chivox.com/CLOUD_DEV/cvx_local_mcp.git\ncd cvx_local_mcp\nbash scripts/build.sh`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"env-vars\" title=\"环境变量\">\n <ParamTable params={[\n { name: 'MCP_REMOTE_URL', type: 'string', required: true, desc: '远程驰声 MCP 服务地址,如 http://your-server:8080' },\n { name: 'MCP_API_KEY', type: 'string', required: false, desc: 'API 认证密钥(如已配置鉴权则必填)' },\n ]} />\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 直接用(零代码) ══════ */}\n <DocSection id=\"config\" icon={Plug} title=\"接入 · 直接用 MCP(零代码)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>不用写代码、不用装 SDK,只在客户端的<strong className=\"text-foreground\">「MCP 配置」</strong>或<strong className=\"text-foreground\">「扩展」</strong>里填一段 JSON / URL</li>\n <li>保存后 LLM 自动发现 16 个评测工具,并在对话 / Agent 中自主调用</li>\n <li>覆盖 <strong className=\"text-foreground\">IDE 编程助手</strong>、<strong className=\"text-foreground\">桌面 AI 客户端</strong>、<strong className=\"text-foreground\">企业 AI 工作台</strong>、<strong className=\"text-foreground\">可视化 Bot / 工作流平台</strong>四大生态</li>\n </ul>\n </div>\n\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置并保存:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <Callout type=\"tip\">Cursor 使用远程 Streamable HTTP 模式直连,无需安装本地代理。如需实时录音功能,请使用下方 Claude Desktop 的本地代理配置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-desktop\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件(macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"chivox-local-mcp\",\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\",\n \"MCP_API_KEY\": \"your-api-key\"\n }\n }\n }\n}`}</CodeBlock>\n <p>如果是从源码构建:</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/cvx_local_mcp/dist/index.js\"],\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\"\n }\n }\n }\n}`}</CodeBlock>\n <Callout type=\"info\">Claude Desktop 使用本地代理模式(stdio),支持实时录音评测。本地代理会自动桥接远程服务。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-code\" title=\"Claude Code 配置\">\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# npm 全局安装后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 chivox-local-mcp\n\n# 源码构建后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 node /path/to/cvx_local_mcp/dist/index.js`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-ai-ide\" title=\"Windsurf / Zed / Continue 等 IDE 编程助手\">\n <p>除了 Cursor 和 Claude Code,主流的 AI 编程 IDE 都原生支持 MCP。以下客户端使用<strong>完全一致</strong>的 Streamable HTTP 配置,区别只在配置入口:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">客户端</th>\n <th className=\"text-left py-2 px-3 font-medium\">配置入口</th>\n <th className=\"text-left py-2 px-3 font-medium\">备注</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">Windsurf (Codeium)</td><td className=\"py-2 px-3 text-xs\">Settings → Cascade → MCP Servers</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">直接贴下方 JSON</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Zed</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">~/.config/zed/settings.json</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">context_servers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">Assistant Panel 自动发现</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Continue (VS Code / JetBrains)</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">config.yaml</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">v0.9+ 支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">通义灵码 / CodeGeeX / 文心快码</td><td className=\"py-2 px-3 text-xs\">插件设置 → MCP 服务</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">2026 Q1 起陆续支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Cline (原 Claude Dev)</td><td className=\"py-2 px-3 text-xs\">MCP Servers 面板 → 编辑配置</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">VS Code 扩展</td></tr>\n </tbody>\n </table>\n </div>\n <CodeBlock filename=\"通用 MCP 配置(复制即用)\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox-speech-eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n }\n }\n}`}</CodeBlock>\n <Callout type=\"tip\">所有 IDE 类客户端都走远程 Streamable HTTP,零本地依赖。需要实时录音才需切到 Claude Desktop 的本地代理模式。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-coze\" title=\"扣子(Coze)配置\">\n <p><a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子(Coze)</a>是字节跳动推出的 AI 应用开发平台,支持通过 MCP 扩展为 Agent 赋予外部工具能力。接入驰声语音评测后,你可以构建口语练习 Bot、发音诊断助手等应用。</p>\n <p className=\"font-semibold mt-4\">方式一:扣子空间 MCP 扩展(推荐)</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 <a href=\"https://space.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子空间</a>(space.coze.cn)</li>\n <li>在对话框下方点击 <strong>「添加扩展」</strong></li>\n <li>选择 <strong>「自定义」</strong> 标签,点击添加新的 MCP 服务</li>\n <li>填入以下配置信息:</li>\n </ol>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">名称</td><td className=\"py-2 px-3 font-mono text-xs\">chivox-speech-eval</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>保存后,在对话中输入 <strong>「帮我评测这段英文发音」</strong> 即可触发 Agent 调用</li>\n </ol>\n <Callout type=\"tip\">添加 MCP 扩展后,扣子空间的 Agent 会自动识别并调用评测工具。你可以在 Agent 的思考过程中看到具体的工具调用细节。</Callout>\n\n <p className=\"font-semibold mt-6\">方式二:在 Coze 开发平台的 Bot 中使用</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>进入 <a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Coze 开发平台</a>,创建或编辑 Bot</li>\n <li>在 Bot 的 <strong>「插件」</strong> 或 <strong>「工具」</strong> 面板中,添加 MCP 工具</li>\n <li>选择 <strong>Streamable HTTP</strong> 类型,填入 URL:<code className=\"bg-muted px-1 rounded text-xs font-mono\">https://speech-eval.site/mcp</code></li>\n <li>Bot 将自动发现所有可用的评测工具(如 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_word</code> 等)</li>\n <li>在 Bot 的人设提示词中,添加语音评测相关的引导语</li>\n </ol>\n <CodeBlock filename=\"Bot 人设提示词示例\">{`你是一位专业的英语口语教练。当用户上传音频并请求评测时:\n1. 使用 evaluate_english_word / evaluate_english_sentence 等工具进行评测\n2. 根据评测结果(overall、accuracy、fluency 等分数)给出专业分析\n3. 针对得分较低的维度提供具体改进建议\n4. 鼓励用户持续练习`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze-workflow\" title=\"Coze 工作流接入\">\n <p>如果你需要在 Coze 工作流中集成语音评测能力,可以通过 <strong>HTTP Request 节点</strong>调用评测 API:</p>\n\n <p className=\"font-semibold mt-4\">步骤 1:上传音频</p>\n <p>在工作流中添加一个 HTTP Request 节点,配置如下:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">方法</td><td className=\"py-2 px-3 font-mono text-xs\">POST</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/upload</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Content-Type</td><td className=\"py-2 px-3 font-mono text-xs\">audio/mp3</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Body</td><td className=\"py-2 px-3 text-muted-foreground\">音频文件二进制数据</td></tr>\n </tbody>\n </table>\n </div>\n <p>响应中获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{{audioId}}'}</code>,传递给下一步。</p>\n\n <p className=\"font-semibold mt-4\">步骤 2:调用评测</p>\n <p>添加第二个 HTTP Request 节点,通过 MCP 接口调用评测工具:</p>\n <CodeBlock filename=\"HTTP Request Body\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"evaluate_english_sentence\",\n \"arguments\": {\n \"audioId\": \"{{audioId}}\",\n \"sentence\": \"{{ref_text}}\"\n }\n },\n \"id\": 1\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">步骤 3:处理结果</p>\n <p>评测结果通过 JSON 格式返回,可使用 Coze 的 <strong>代码节点</strong> 或 <strong>LLM 节点</strong> 对结果进行解析和二次处理(如生成学习建议、计算进步趋势等)。</p>\n\n <Callout type=\"info\">工作流方式适合需要固定流程的场景(如批量评测、定时任务、与其他系统联动)。如果是交互式场景,推荐使用上方的 MCP 扩展方式,Agent 会自动选择合适的工具。</Callout>\n\n <div className=\"rounded-lg bg-muted/30 p-5 mt-4\">\n <p className=\"text-xs font-semibold mb-3\">完整工作流示意</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">用户输入音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 上传音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 调用评测</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM: 分析结果</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">输出反馈</span>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"config-ai-workspace\" title=\"AI 工作台 · 豆包 / 飞书智能伙伴 / WorkBuddy / 钉钉 AI\">\n <p>这一类是<strong>面向「上班族」的企业 / 日常 AI 助手</strong>,产品形态是个聊天框 + 插件市场 / 扩展面板。接入后,员工在 App 里打字「帮我评一下这段录音」就能自动触发驰声评测,不用写任何代码。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Bot className=\"h-4 w-4 text-amber-500\" /> 豆包(Doubao)App / 桌面版</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节跳动官方 AI 助手,2026 年起桌面版支持 MCP 扩展。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>桌面版设置 → <strong>「智能体扩展」</strong> → 添加 MCP 服务</li>\n <li>类型选 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">streamable-http</code>,URL 填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>对话中直接说「帮我评测」即可自动调用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Building2 className=\"h-4 w-4 text-blue-500\" /> 飞书智能伙伴(My AI)</p>\n <p className=\"text-xs text-muted-foreground mb-2\">飞书企业 AI,支持以 MCP / Function Calling 挂载外部工具给团队 Bot。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>飞书开发者后台 → 创建应用 → <strong>「AI 能力 / 智能伙伴」</strong></li>\n <li>在「工具」中选 <strong>外部 MCP 服务</strong>,填入上方 URL</li>\n <li>把智能伙伴拉进群聊,@ 它提交音频即可评测</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-rose-500\" /> WorkBuddy / 企业智能助手</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节 WorkBuddy、腾讯会议 AI、企业微信智能助手等企业 AI 工作台场景。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>在工作台的<strong>「技能 / 扩展」</strong>后台新增一个 MCP 连接器</li>\n <li>传输协议选 Streamable HTTP,地址填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>把技能发布到指定部门 / 群组,HR、培训、销售口语陪练场景即可直接用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Workflow className=\"h-4 w-4 text-cyan-500\" /> 钉钉 AI 助理 / 通义千问 App</p>\n <p className=\"text-xs text-muted-foreground mb-2\">阿里生态:钉钉 AI 助理支持自定义插件,通义 App 可通过「AI 工具箱」挂载 MCP。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>钉钉开发者后台 → AI 助理 → <strong>「插件开发 / MCP 接入」</strong></li>\n <li>选 HTTP Streamable,填入驰声 MCP URL 并开启鉴权(可选)</li>\n <li>在钉钉群 @AI 助理上传音频,自动返回评测卡片</li>\n </ol>\n </div>\n </div>\n\n <Callout type=\"tip\"><strong>典型场景 · 小龙虾们的共同剧本</strong>:销售口语陪练 → 外语培训 → 主持 / 播音岗位考核 → K12 英语作业批改 → 客服话术复盘。这些「上班族 AI 工作台」接入驰声 MCP 后,员工 / 学员只要对着话筒说话,AI 就能自动打分并给出个性化改进建议,运营 / 培训 / 教研团队零开发即可上线。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-workflow-platforms\" title=\"Dify / n8n / Flowise 等可视化编排\">\n <p>如果你需要把语音评测编排进<strong>多步业务流</strong>(例如:学员录音 → 评测 → 生成错题集 → 推送到 CRM),可视化工作流平台比聊天 Bot 更合适。</p>\n\n <div className=\"grid gap-3 md:grid-cols-3 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Dify</p>\n <p className=\"text-xs text-muted-foreground mb-2\">开源 LLMOps,v0.15+ 原生 MCP 工具。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>「工具」→「自定义 MCP」→ 填 URL</li>\n <li>在 Chatflow / Workflow 节点里直接拖用</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">n8n</p>\n <p className=\"text-xs text-muted-foreground mb-2\">工作流自动化,用 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">MCP Client</code> 节点。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>添加 <strong>MCP Client</strong> 节点</li>\n <li>Transport 选 HTTP Stream,URL 贴驰声地址</li>\n <li>Tool 下拉自动列出 16 个评测能力</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Flowise / LangFlow</p>\n <p className=\"text-xs text-muted-foreground mb-2\">可视化 LangChain Agent 编排。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>拖出 <strong>MCP Tool</strong> 节点</li>\n <li>连到 AgentExecutor 即可</li>\n </ul>\n </div>\n </div>\n\n <Callout type=\"info\">可视化平台的好处是<strong>流程可追溯、可回放</strong>,适合教育机构 / 培训部门做批量评测 + 报表导出;缺点是单步延迟略高于直接 MCP 调用,对延迟敏感的交互场景建议用 Cursor / Claude Desktop 直连。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"其他 MCP 客户端(通用配置)\">\n <p>任何遵循 MCP 协议的客户端都可以接入,统一使用以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权(可选)</td><td className=\"py-2 px-3 font-mono text-xs\">Header:<code>Authorization: Bearer &lt;MCP_API_KEY&gt;</code></td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">还没找到你用的客户端?任何支持 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 的终端都通用。找不到入口?下一小节「编程用 MCP」里用 SDK 直接接即可。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 编程用 MCP ══════ */}\n <DocSection id=\"config-code\" icon={Code2} title=\"接入 · 编程用 MCP(SDK / 代码集成)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Terminal className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>你在写一段<strong className=\"text-foreground\">真正的后端服务</strong>:一个 Agent Worker、一个 API、一个自动化脚本</li>\n <li>业务里的「语音评测」是其中一个步骤,需要由代码 <strong className=\"text-foreground\">决定何时调用</strong>、<strong className=\"text-foreground\">如何解析结果</strong></li>\n <li>三种主流写法:① 官方 MCP 客户端库 → ② Agent 框架自动桥接 → ③ 退化到 chat.completions + tools 参数</li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 强烈推荐①或②:驰声有 16 个评测工具,<strong className=\"text-foreground\">让 MCP 自动注入 tool schema</strong>,每新增一个题型不用改一行业务代码。</p>\n </div>\n\n <SubDoc id=\"code-mcp-sdk\" title=\"① 官方 MCP 客户端库(Python / TypeScript)\">\n <p>最底层、也最推荐的方式。由 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Anthropic 官方维护</a>,一次连接自动拿到全部工具的 JSON Schema,然后交给任意 LLM。</p>\n\n <p className=\"font-semibold mt-4\">Python:<code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp</code></p>\n <CodeBlock filename=\"chivox_client.py\" lang=\"python\">{`# pip install mcp openai\nimport asyncio, os\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nMCP_URL = \"https://speech-eval.site/mcp\"\nllm = OpenAI(api_key=os.getenv(\"DOUBAO_KEY\"),\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\") # 豆包 / DeepSeek / OpenAI 同构\n\nasync def main(audio_id: str, ref: str):\n async with streamablehttp_client(MCP_URL) as (reader, writer, _):\n async with ClientSession(reader, writer) as sess:\n await sess.initialize()\n\n tools = await sess.list_tools()\n openai_tools = [{\n \"type\": \"function\",\n \"function\": {\"name\": t.name, \"description\": t.description,\n \"parameters\": t.inputSchema},\n } for t in tools.tools]\n\n resp = llm.chat.completions.create(\n model=\"doubao-1-5-pro-32k\",\n messages=[{\"role\": \"user\",\n \"content\": f\"帮我评测 audioId={audio_id},参考文本:{ref}\"}],\n tools=openai_tools,\n )\n\n for call in resp.choices[0].message.tool_calls or []:\n result = await sess.call_tool(call.function.name,\n arguments=eval(call.function.arguments))\n print(call.function.name, \"→\", result.content[0].text[:200])\n\nasyncio.run(main(\"abc123\", \"I think therefore I am\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">TypeScript / Node.js:<code className=\"bg-muted px-1 rounded text-xs font-mono\">@modelcontextprotocol/sdk</code></p>\n <CodeBlock filename=\"chivox-client.ts\" lang=\"typescript\">{`// npm i @modelcontextprotocol/sdk openai\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport OpenAI from \"openai\";\n\nconst mcp = new Client({ name: \"chivox-biz\", version: \"1.0.0\" });\nawait mcp.connect(new StreamableHTTPClientTransport(new URL(\"https://speech-eval.site/mcp\")));\n\nconst { tools } = await mcp.listTools();\nconst openai = new OpenAI({ apiKey: process.env.DEEPSEEK_KEY, baseURL: \"https://api.deepseek.com\" });\n\nconst resp = await openai.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: \"评测 audioId=abc123, 参考 I think therefore I am\" }],\n tools: tools.map(t => ({ type: \"function\", function: {\n name: t.name, description: t.description, parameters: t.inputSchema\n }})),\n});\n\nfor (const call of resp.choices[0].message.tool_calls ?? []) {\n const r = await mcp.callTool({ name: call.function.name,\n arguments: JSON.parse(call.function.arguments) });\n console.log(call.function.name, r.content);\n}`}</CodeBlock>\n <Callout type=\"tip\">这种写法 <strong>LLM 供应商可换</strong>(豆包 / DeepSeek / GPT / Qwen / Moonshot),<strong>工具自动更新</strong>(驰声新增题型无需改业务代码),适合生产后端。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-agent-frameworks\" title=\"② 用 Agent 框架自动桥接(LangChain / Mastra / Agents SDK)\">\n <p>如果项目已经在用 Agent 框架,几行 adapter 就能把驰声 MCP 塞进去,框架本身会接管 tool-calling 循环、重试、日志。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LangChain / LangGraph(Python)</p>\n <CodeBlock filename=\"agent.py\" lang=\"python\">{`# pip install langchain-mcp-adapters langgraph\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom langgraph.prebuilt import create_react_agent\n\nclient = MultiServerMCPClient({\n \"chivox\": {\n \"transport\": \"streamable_http\",\n \"url\": \"https://speech-eval.site/mcp\",\n }\n})\ntools = await client.get_tools()\n\nagent = create_react_agent(\"openai:gpt-4o-mini\", tools)\nresult = await agent.ainvoke({\"messages\":\n [(\"user\", \"评测 audioId=abc123 参考 think\")]})\nprint(result[\"messages\"][-1].content)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Mastra(TypeScript)</p>\n <CodeBlock filename=\"mastra-agent.ts\" lang=\"typescript\">{`// npm i @mastra/core @mastra/mcp\nimport { MCPClient } from \"@mastra/mcp\";\nimport { Agent } from \"@mastra/core/agent\";\nimport { openai } from \"@ai-sdk/openai\";\n\nconst mcp = new MCPClient({ servers: {\n chivox: { url: new URL(\"https://speech-eval.site/mcp\") }\n}});\n\nexport const speechCoach = new Agent({\n name: \"speech-coach\",\n instructions: \"你是口语教练,用 chivox 工具评测并给改进建议。\",\n model: openai(\"gpt-4o-mini\"),\n tools: await mcp.getTools(),\n});`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">OpenAI Agents SDK(Python)</p>\n <CodeBlock filename=\"agents_sdk.py\" lang=\"python\">{`# pip install openai-agents\nfrom agents import Agent, Runner\nfrom agents.mcp import MCPServerStreamableHttp\n\nchivox = MCPServerStreamableHttp(\n params={\"url\": \"https://speech-eval.site/mcp\"},\n name=\"chivox-speech-eval\",\n)\n\nasync with chivox:\n agent = Agent(name=\"coach\",\n instructions=\"专业口语教练\",\n mcp_servers=[chivox])\n r = await Runner.run(agent, \"评测 audioId=abc123\")\n print(r.final_output)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LlamaIndex / AutoGen / CrewAI</p>\n <p className=\"text-xs text-muted-foreground mb-2\">这些框架都有官方或社区 MCP adapter,用法与上述类似:</p>\n <ul className=\"list-disc list-inside space-y-1 text-xs\">\n <li><strong>LlamaIndex</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">llama-index-tools-mcp</code></li>\n <li><strong>AutoGen</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">autogen-ext[mcp]</code></li>\n <li><strong>CrewAI</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">crewai-tools</code> 的 MCPServerAdapter</li>\n <li><strong>Spring AI</strong>(Java):<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">spring-ai-mcp-client</code></li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"code-function-calling\" title=\"③ 直调 chat.completions · tools 参数(豆包 / DeepSeek / 火山方舟)\">\n <p>当你用的 LLM SDK <strong>还没有原生 MCP 支持</strong>(比如老版本的火山方舟 / 百度千帆 / 通义 DashScope),退化方案是:用 MCP 客户端<strong>一次性拉到 tool schema</strong>,然后按 OpenAI Function Calling 格式喂给 LLM。</p>\n <CodeBlock filename=\"doubao_tools.py\" lang=\"python\">{`# 适用:豆包(火山方舟)/ DeepSeek / Qwen / Moonshot / GLM 等 OpenAI 兼容 API\n# 核心思路:MCP 列工具 → 转 OpenAI tools → LLM 决策 → MCP 执行 → 回填\n\nasync with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n\n # ① 用 MCP 拉到的 schema 自动转 OpenAI 格式\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 喂给豆包\n messages = [{\"role\": \"user\", \"content\": \"评测一下 audioId=abc123\"}]\n resp = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages, tools=oa_tools,\n )\n\n # ③ LLM 回 tool_calls → 折返跑到 MCP 执行\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for c in msg.tool_calls:\n result = await mcp.call_tool(\n c.function.name, arguments=json.loads(c.function.arguments))\n messages.append({\"role\": \"tool\", \"tool_call_id\": c.id,\n \"content\": result.content[0].text})\n # ④ 再喂一轮,让豆包根据评测结果产出自然语言诊断\n final = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages)\n print(final.choices[0].message.content)`}</CodeBlock>\n <Callout type=\"warning\"><strong>千万别手写 tool schema!</strong>驰声 16 个评测工具每个都有几十个字段,手写既容易错也难维护。一定用 MCP <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">list_tools()</code> 动态拿。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-selfhosted-agent\" title=\"④ 自研后端 Agent(FastAPI / NestJS / Spring)\">\n <p>大部分落地场景其实是:<strong>你已有一个业务后端</strong>(教育 SaaS / 培训系统 / 客服平台),只需要新增一个「语音评测」能力。推荐架构:</p>\n\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">前端录音 / 上传</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">业务后端</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">MCP Client(常驻连接)</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">驰声 MCP</span>\n <span className=\"text-muted-foreground\">↘</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM(诊断 / 出题)</span>\n </div>\n </div>\n\n <p className=\"font-semibold mt-2\">FastAPI 示例(生产最小骨架)</p>\n <CodeBlock filename=\"app.py\" lang=\"python\">{`from fastapi import FastAPI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\nfrom contextlib import asynccontextmanager\n\nmcp_session: ClientSession | None = None\n\n@asynccontextmanager\nasync def lifespan(app):\n global mcp_session\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as s:\n await s.initialize()\n mcp_session = s\n yield\n mcp_session = None\n\napp = FastAPI(lifespan=lifespan)\n\n@app.post(\"/api/evaluate\")\nasync def evaluate(audio_id: str, ref_text: str):\n # 直接选一个最合适的工具执行(不经 LLM,延迟最低)\n r = await mcp_session.call_tool(\"evaluate_english_sentence\",\n {\"audioId\": audio_id, \"sentence\": ref_text})\n return {\"result\": r.content[0].text}`}</CodeBlock>\n\n <Callout type=\"tip\"><strong>关键优化</strong>:① MCP 连接在 lifespan 里做成长连接,不要每次请求都重连;② 对延迟敏感的场景可以「跳过 LLM 直接调 tool」,把 LLM 放到「生成诊断报告」那一步。</Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mt-6 text-sm\">\n <p className=\"font-semibold mb-2\">方案选型速查</p>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 font-medium\">场景</th>\n <th className=\"text-left py-2 font-medium\">推荐方案</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2\">AI 对话 / Copilot 类产品,LLM 供应商可能会换</td><td className=\"py-2\">① 官方 MCP 客户端库</td></tr>\n <tr><td className=\"py-2\">已有 LangChain / Mastra / Agents SDK 技术栈</td><td className=\"py-2\">② Agent 框架 + adapter</td></tr>\n <tr><td className=\"py-2\">火山方舟 / 千帆控制台直连,最小侵入</td><td className=\"py-2\">③ chat.completions + tools</td></tr>\n <tr><td className=\"py-2\">教育 SaaS / 培训 / 客服等业务后端集成</td><td className=\"py-2\">④ 自研后端 Agent</td></tr>\n </tbody>\n </table>\n </div>\n </div>\n </DocSection>\n\n {/* ══════ 评测模式 ══════ */}\n <DocSection id=\"eval-modes\" icon={Radio} title=\"评测模式\">\n\n <SubDoc id=\"stream-eval\" title=\"实时录音评测(边录边评)\">\n <p>通过本地麦克风实时录音,音频以 WebSocket 流式推送到评测服务,<strong>无需生成中间文件</strong>。这是最灵活、延迟最低的评测方式,特别适合交互式口语练习场景。</p>\n <Callout type=\"tip\">实时录音评测需要安装 <strong>SoX</strong> 并使用本地代理模式(Claude Desktop / Claude Code)。Cursor 等远程直连模式暂不支持实时录音。</Callout>\n\n <p className=\"font-semibold mt-4\">需要按顺序调用三个工具:</p>\n\n <div className=\"mt-3 space-y-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">create_stream_session</code> — 创建流式评测会话</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.word.score、en.sent.score、cn.sent.raw' },\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式,默认 mp3' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100,默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code>,后续步骤需使用。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">start_recording</code> — 开始麦克风录音</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '来自 create_stream_session 的会话 ID' },\n { name: 'chunk_size', type: 'number', required: false, desc: '音频块大小(bytes),默认 3200' },\n { name: 'chunk_interval_ms', type: 'number', required: false, desc: '推送间隔(ms),默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">调用后自动启动麦克风,音频通过 WebSocket 实时推送到评测服务。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">stop_recording</code> — 停止录音并获取结果</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '会话 ID' },\n { name: 'timeout', type: 'number', required: false, desc: '等待评测结果超时秒数,默认 30' },\n ]} />\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"stream-flow\" title=\"流式评测完整流程\">\n <div className=\"rounded-lg border border-border/60 p-5 bg-muted/10 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong>用户:</strong>&quot;请评测我的英文发音,文本是 Hello&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">create_stream_session(core_type=&quot;en.word.score&quot;, ref_text=&quot;Hello&quot;)</code></p>\n <p className=\"text-muted-foreground\">→ 获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">session_id</code></p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p>AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">start_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground mt-1\">麦克风开始录音,音频通过 WebSocket 实时推送到评测服务</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>用户</strong>说完后告知 AI &quot;结束&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">stop_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground\">→ 返回评测结果(总分、准确度、流利度、音素得分等)</p>\n </FlowStep>\n </div>\n </SubDoc>\n\n <SubDoc id=\"file-eval\" title=\"音频文件评测\">\n <p>适用于已有音频文件的评测场景,支持三种输入方式:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">适用场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_file_path</td><td className=\"py-2 px-3\">本地文件路径</td><td className=\"py-2 px-3 text-muted-foreground\">代理自动读取并转为 Base64,最便捷</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_base64</td><td className=\"py-2 px-3\">Base64 编码数据</td><td className=\"py-2 px-3 text-muted-foreground\">已有编码数据时使用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_url</td><td className=\"py-2 px-3\">音频文件 URL</td><td className=\"py-2 px-3 text-muted-foreground\">音频托管在云存储时使用</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"tip\">推荐使用 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">audio_file_path</code>,只需传入本地文件路径,代理会自动处理编码和上传。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"tools-en\" title=\"英文评测工具(10 种)\">\n <ToolTable tools={[\n ['en_word_eval', '单词评测', '评测 \"Hello\" 的发音准确度'],\n ['en_word_correction', '单词纠音', '检测多读/漏读/错读并给出纠正建议'],\n ['en_vocab_eval', '词语评测', '同时评测多个单词的发音'],\n ['en_sentence_eval', '句子评测', '评测整句朗读的准确度与流利度'],\n ['en_sentence_correction', '句子纠音', '逐词检测发音问题并给出修正建议'],\n ['en_paragraph_eval', '段落朗读评测', '评测整段英文课文的朗读质量'],\n ['en_phonics_eval', '自然拼读评测', '评测 Phonics 拼读规则掌握程度'],\n ['en_choice_eval', '口语选择题', '从预设选项中识别口语回答'],\n ['en_semi_open_eval', '半开放题评测', '场景对话等半开放口语题型'],\n ['en_realtime_eval', '实时朗读评测', '实时反馈朗读质量,逐句评分'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong>所有英文评测工具均支持以下参数。</p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tools-cn\" title=\"中文评测工具(6 种)\">\n <ToolTable tools={[\n ['cn_word_raw_eval', '单字评测(汉字)', '评测 \"中\" 的发音'],\n ['cn_word_pinyin_eval', '单字评测(拼音)', '评测 \"zhōng\" 的发音'],\n ['cn_sentence_eval', '词句评测', '评测 \"你好世界\" 的朗读'],\n ['cn_paragraph_eval', '段落朗读评测', '评测一段中文课文的朗读质量'],\n ['cn_rec_eval', '有限分支识别评测', '从预设选项中识别用户发音'],\n ['cn_aitalk_eval', 'AI Talk 口语评测', '中文口语对话能力评测'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong></p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本(汉字或拼音)' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结构化结果:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"integrity\": 95,\n \"details\": [\n {\n \"char\": \"hello\",\n \"score\": 85,\n \"phone\": [\n { \"phoneme\": \"h\", \"score\": 90 },\n { \"phoneme\": \"ɛ\", \"score\": 82 },\n { \"phoneme\": \"l\", \"score\": 88 },\n { \"phoneme\": \"oʊ\", \"score\": 80 }\n ]\n }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall', '0–100', '综合总分,反映整体语音质量'],\n ['accuracy', '0–100', '准确度,音素级别的发音准确率'],\n ['pron', '0–100', '发音得分,整体发音质量评价'],\n ['fluency.overall', '0–100', '流利度,语流的自然度与平滑度'],\n ['fluency.speed', '0–100', '语速得分,每分钟词/音节计量'],\n ['fluency.pause', 'number', '不自然停顿次数'],\n ['integrity', '0–100', '完整度,是否完整朗读了参考文本'],\n ['details[].phone[]', 'array', '每个音素的独立评分,可定位具体弱项'],\n ['details[].stress[]', 'array', '重音得分,检测重弱读是否正确'],\n ].map(([field, range, desc]) => (\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期', '音频上传后 5 分钟内有效,评测完成后自动删除', '重新上传音频'],\n ['服务器繁忙', '当前请求过多,排队已满', '稍后重试,使用指数退避策略'],\n ['存储空间已满', '服务器临时音频存储已满', '稍后重试,系统会自动清理'],\n ['音频格式不支持', '上传了不支持的文件类型', '使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限', '超过该题型最大时长限制', '裁剪音频后重试'],\n ['WebSocket 连接断开', '网络不稳定导致流式连接中断', '代理会自动重连,如持续失败请检查网络'],\n ].map(([scene, reason, fix]) => (\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 架构说明 ══════ */}\n <DocSection id=\"architecture\" icon={Terminal} title=\"架构说明\">\n\n <SubDoc id=\"arch-diagram\" title=\"系统架构\">\n <div className=\"flex flex-wrap items-center justify-center gap-4 my-6\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Claude Desktop<br/>Claude Code<br/>Cursor</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">stdio</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-1\">chivox-local-mcp</p>\n <p className=\"text-[11px] text-muted-foreground\">本地代理<br/>SoX 录音</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP / WS</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">Remote Chivox</p>\n <p className=\"text-[11px] text-muted-foreground\">MCP Server<br/>评测引擎</p>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"transport\" title=\"传输协议\">\n <ul className=\"space-y-3\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>stdio</strong> — AI 客户端通过标准输入输出与本地代理通信,这是 MCP 的标准传输方式</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>HTTP</strong> — 音频文件评测工具通过 HTTP 代理到远程服务,适用于非流式场景</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>WebSocket</strong> — 实时录音音频通过 WebSocket 流式推送,支持断线自动重连</span>\n </li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 示例代码 ══════ */}\n <DocSection id=\"integration\" icon={Code2} title=\"示例代码\">\n\n <SubDoc id=\"code-python\" title=\"Python 上传音频\">\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://speech-eval.site/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"code-javascript\" title=\"JavaScript 上传音频\">\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst res = await fetch('https://speech-eval.site/upload', {\n method: 'POST',\n headers: { 'Content-Type': 'audio/mp3' },\n body: audioData,\n});\n\nconst result = await res.json();\nconsole.log('audioId:', result.audioId);`}</CodeBlock>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p>以下模板帮助你引导 LLM 对评测结果进行深度分析:</p>\n <div className=\"space-y-4 mt-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><MessageSquare className=\"h-4 w-4\" /> 发音诊断</p>\n <CodeBlock filename=\"prompt-diagnosis.txt\">{`你是一位专业的英语口语教练。请根据以下 MCP 评测结果分析学生的发音表现:\n\n评测结果:{evaluation_result}\n\n请按以下格式输出:\n1. 总体评价(一句话)\n2. 优势项(>80 分的指标)\n3. 弱项分析(<70 分的指标,定位到具体音素)\n4. 针对性练习建议(2-3 条可操作的建议)`}</CodeBlock>\n </div>\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><Mic className=\"h-4 w-4\" /> 练习生成</p>\n <CodeBlock filename=\"prompt-practice.txt\">{`根据以下弱项列表,为学生生成针对性的口语练习材料:\n\n弱项:{weak_points}\n\n请生成:\n1. 3 个绕口令(专门针对弱项音素)\n2. 5 个包含目标音素的常用短句(标注重音和连读)\n3. 1 段跟读练习段落(50-80 词)`}</CodeBlock>\n </div>\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><BarChart3 className=\"h-4 w-4\" /> 学习报告</p>\n <CodeBlock filename=\"prompt-report.txt\">{`请根据学生近 7 天的评测历史生成学习报告:\n\n评测历史:{history}\n\n报告要求:\n1. 整体趋势(进步/持平/退步)\n2. 各维度变化(准确度、流利度、语速)\n3. 本周最大进步项\n4. 下周重点练习建议\n5. 鼓励性总结`}</CodeBlock>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频过期:</strong>上传后 5 分钟内有效。建议在上传后 3 分钟内完成评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>服务繁忙:</strong>使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>录音中断:</strong>WebSocket 断线后代理会自动重连。如持续失败,检查网络连接和 SoX 安装。</span></li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>优先使用流式评测:</strong>实时录音模式跳过了文件上传环节,端到端延迟比文件评测低 30-50%。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频压缩:</strong>文件评测时,上传前将音频转为 16kHz 单声道 mp3,可减少 60-80% 的上传体积。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>选择合适的工具:</strong>单词评测比段落评测快 3-5 倍。如只需单词级分数,不要用句子评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>本地文件路径最便捷:</strong>使用 <code className=\"bg-muted px-1 rounded text-xs\">audio_file_path</code> 参数传入本地路径,代理自动处理编码,无需手动转 Base64。</span></li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Globe} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP JSON-RPC 接口(Streamable HTTP)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制', '500MB', '最大音频存储空间'],\n ['最大排队', '10', '等待处理的请求数'],\n ['最大并发', '3', '同时处理的评测数'],\n ['音频有效期', '5 分钟', '上传后未评测自动过期'],\n ['文件大小限制', '50MB', '单个音频文件最大'],\n ].map(([k, v, d]) => (\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <div className=\"space-y-3\">\n {[\n { ver: 'v3.0.0', date: '2026-04-16', desc: '新增本地代理模式,支持实时录音流式评测,工具扩展至 16 种' },\n { ver: 'v2.3.0', date: '2026-03-25', desc: '支持 HTTPS,域名 speech-eval.site' },\n { ver: 'v2.2.0', date: '2026-03-24', desc: '添加 MCP 配置指南(Cursor / Claude Desktop)' },\n { ver: 'v2.1.0', date: '2026-03-24', desc: '简化上传流程,统一使用 HTTP API' },\n { ver: 'v2.0.0', date: '2026-03-24', desc: '初始版本,支持多种评测类型' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </div>\n </div>\n </div>\n </main>\n );\n}\n"}, "89fc7eafa404d2f31537035c15917423600cc7a7": {"t": 1785765808.283272, "n": 92106, "h": "044e44207c1f88e1", "c": "'use client';\n\nimport { useState, useEffect, useMemo } from 'react';\nimport { Link } from '@/i18n/routing';\nimport { ArrowLeft, BookOpen, Code2, Zap, FileText, Copy, Check, Terminal, Globe, Mic, MessageSquare, BarChart3, AlertTriangle, Lightbulb, Radio, FolderOpen, Settings, Workflow, Sparkles, Plug, Bot, Building2 } from 'lucide-react';\n\n/* ─── Reusable Components ─── */\n\nfunction CopyButton({ text }: { text: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <button\n onClick={() => { navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); }}\n className=\"absolute top-3 right-3 p-1.5 rounded-md bg-white/5 hover:bg-white/10 transition-colors text-zinc-400 hover:text-zinc-200\"\n >\n {copied ? <Check className=\"h-3.5 w-3.5\" /> : <Copy className=\"h-3.5 w-3.5\" />}\n </button>\n );\n}\n\nfunction CodeBlock({ filename, lang, children }: { filename?: string; lang?: string; children: string }) {\n return (\n <div className=\"rounded-lg border border-border/60 bg-zinc-950 text-zinc-100 overflow-hidden my-4 relative\">\n {filename && (\n <div className=\"flex items-center gap-1.5 px-4 py-2.5 border-b border-white/[0.06] text-xs text-zinc-500 font-mono\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-red-500/60\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-yellow-500/60\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-green-500/60\" />\n <span className=\"ml-2\">{filename}</span>\n {lang && <span className=\"ml-auto text-zinc-600\">{lang}</span>}\n </div>\n )}\n <CopyButton text={children.trim()} />\n <pre className=\"p-4 text-sm font-mono leading-relaxed overflow-x-auto\"><code>{children.trim()}</code></pre>\n </div>\n );\n}\n\nfunction DocSection({ id, icon: Icon, title, children }: { id: string; icon: React.ElementType; title: string; children: React.ReactNode }) {\n return (\n <section id={id} className=\"scroll-mt-24 mb-16\">\n <div className=\"flex items-center gap-3 mb-6 pb-3 border-b border-border/40\">\n <Icon className=\"h-5 w-5 text-muted-foreground shrink-0\" />\n <h2 className=\"text-xl font-bold tracking-tight\">{title}</h2>\n </div>\n <div className=\"text-sm leading-relaxed text-foreground/90 space-y-6\">{children}</div>\n </section>\n );\n}\n\nfunction SubDoc({ id, title, children }: { id: string; title: string; children: React.ReactNode }) {\n return (\n <div id={id} className=\"scroll-mt-24 mb-10\">\n <h3 className=\"text-base font-semibold mb-3\">{title}</h3>\n <div className=\"space-y-4\">{children}</div>\n </div>\n );\n}\n\nfunction ParamTable({ params }: { params: { name: string; type: string; required: boolean; desc: string }[] }) {\n return (\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">类型</th>\n <th className=\"text-left py-2 px-3 font-medium\">必填</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {params.map(p => (\n <tr key={p.name}>\n <td className=\"py-2 px-3 font-mono text-xs\">{p.name}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{p.type}</td>\n <td className=\"py-2 px-3\">{p.required ? <span className=\"text-emerald-600 dark:text-emerald-400\">✓</span> : <span className=\"text-muted-foreground\">—</span>}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{p.desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction Callout({ type = 'info', children }: { type?: 'info' | 'warning' | 'tip'; children: React.ReactNode }) {\n const styles = {\n info: 'border-blue-200 bg-blue-50 text-blue-900 dark:border-blue-900 dark:bg-blue-950/30 dark:text-blue-200',\n warning: 'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950/30 dark:text-amber-200',\n tip: 'border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-900 dark:bg-emerald-950/30 dark:text-emerald-200',\n };\n const icons = { info: Globe, warning: AlertTriangle, tip: Lightbulb };\n const Ic = icons[type];\n return (\n <div className={`flex gap-3 rounded-lg border p-4 text-sm ${styles[type]}`}>\n <Ic className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div>{children}</div>\n </div>\n );\n}\n\nfunction FlowStep({ title, children }: { title: string; children: React.ReactNode }) {\n return (\n <div className=\"flex gap-4 items-start\">\n <div className=\"h-7 w-7 rounded-full bg-foreground text-background flex items-center justify-center text-xs font-bold shrink-0 mt-0.5\">{title}</div>\n <div className=\"flex-1 text-sm\">{children}</div>\n </div>\n );\n}\n\nfunction ToolTable({ tools }: { tools: [string, string, string][] }) {\n return (\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">工具名</th>\n <th className=\"text-left py-2 px-3 font-medium\">功能</th>\n <th className=\"text-left py-2 px-3 font-medium\">典型场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {tools.map(([name, desc, scene]) => (\n <tr key={name}>\n <td className=\"py-2 px-3 font-mono text-xs\">{name}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{scene}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\n/* ─── Navigation Structure ─── */\n\nconst NAV = [\n { id: 'concept', icon: Workflow, label: 'MCP 工作原理', children: [\n { id: 'why-mcp', label: '为什么需要 MCP' },\n { id: 'scenario-walkthrough', label: '场景走查 · 6 步' },\n { id: 'dev-responsibility', label: '开发者只做两件事' },\n { id: 'integration-paths', label: '三种接入姿势' },\n ]},\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'requirements', label: '系统要求' },\n { id: 'install', label: '安装方式' },\n { id: 'env-vars', label: '环境变量' },\n ]},\n { id: 'config', icon: Plug, label: '接入 · 直接用(零代码)', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-ai-ide', label: 'Windsurf / Zed / Continue' },\n { id: 'config-coze', label: '扣子(Coze)' },\n { id: 'config-coze-workflow', label: 'Coze 工作流' },\n { id: 'config-ai-workspace', label: 'AI 工作台 · 豆包 / 飞书 / WorkBuddy / 钉钉' },\n { id: 'config-workflow-platforms', label: 'Dify / n8n / Flowise' },\n { id: 'config-other', label: '其他 MCP 客户端' },\n ]},\n { id: 'config-code', icon: Code2, label: '接入 · 编程用(SDK / 代码)', children: [\n { id: 'code-mcp-sdk', label: '官方 MCP 客户端库' },\n { id: 'code-agent-frameworks', label: 'LangChain / Mastra / Agents SDK' },\n { id: 'code-function-calling', label: '直调 chat.completions · tools' },\n { id: 'code-selfhosted-agent', label: '自研后端 Agent' },\n ]},\n { id: 'eval-modes', icon: Radio, label: '评测模式', children: [\n { id: 'stream-eval', label: '实时录音评测' },\n { id: 'stream-flow', label: '流式评测流程' },\n { id: 'file-eval', label: '音频文件评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'tools-en', label: '英文评测工具' },\n { id: 'tools-cn', label: '中文评测工具' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'architecture', icon: Terminal, label: '架构说明', children: [\n { id: 'arch-diagram', label: '架构图' },\n { id: 'transport', label: '传输协议' },\n ]},\n { id: 'integration', icon: Code2, label: '示例代码', children: [\n { id: 'code-python', label: 'Python' },\n { id: 'code-javascript', label: 'JavaScript' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n { id: 'service-info', icon: Globe, label: '服务信息', children: [\n { id: 'endpoints', label: '服务端点' },\n { id: 'limits', label: '服务限制' },\n { id: 'changelog', label: '更新日志' },\n ]},\n];\n\n/* ─── Page ─── */\n\nexport default function DocsPage() {\n const [activeId, setActiveId] = useState<string>('quick-start');\n\n // 收集所有可滚动的锚点 id(包含分组和子章节),用于 scroll-spy\n const allIds = useMemo(() => {\n const ids: string[] = [];\n NAV.forEach(g => {\n ids.push(g.id);\n g.children.forEach(c => ids.push(c.id));\n });\n return ids;\n }, []);\n\n useEffect(() => {\n const elements = allIds\n .map(id => document.getElementById(id))\n .filter((el): el is HTMLElement => el !== null);\n\n if (elements.length === 0) return;\n\n // 以顶部偏移 120px 作为\"激活线\",选取距离激活线最近的上方/同位章节\n const computeActive = () => {\n const line = 120;\n let currentId = elements[0]?.id ?? '';\n for (const el of elements) {\n const top = el.getBoundingClientRect().top;\n if (top - line <= 0) {\n currentId = el.id;\n } else {\n break;\n }\n }\n // 滚到底部时,强制高亮最后一项\n if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 4) {\n currentId = elements[elements.length - 1].id;\n }\n setActiveId(prev => (prev === currentId ? prev : currentId));\n };\n\n computeActive();\n window.addEventListener('scroll', computeActive, { passive: true });\n window.addEventListener('resize', computeActive);\n return () => {\n window.removeEventListener('scroll', computeActive);\n window.removeEventListener('resize', computeActive);\n };\n }, [allIds]);\n\n // 根据当前激活的子节点,找出其所属分组 id,用于同步高亮父级\n const activeGroupId = useMemo(() => {\n for (const g of NAV) {\n if (g.id === activeId) return g.id;\n if (g.children.some(c => c.id === activeId)) return g.id;\n }\n return activeId;\n }, [activeId]);\n\n return (\n <main className=\"flex-1\">\n <div className=\"container mx-auto px-6 py-12 md:py-20\">\n <Link href=\"/\" className=\"inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors mb-8\">\n <ArrowLeft className=\"h-4 w-4\" /> 返回首页\n </Link>\n\n <h1 className=\"text-3xl md:text-4xl font-bold tracking-tight mb-2\">开发者文档</h1>\n <p className=\"text-muted-foreground mb-10\">驰声语音评测 MCP · chivox-local-mcp</p>\n\n <div className=\"flex gap-10\">\n {/* Sidebar */}\n <nav className=\"hidden lg:block w-56 shrink-0 sticky top-24 self-start max-h-[calc(100vh-8rem)] overflow-y-auto\">\n {NAV.map(group => {\n const isGroupActive = activeGroupId === group.id;\n return (\n <div key={group.id} className=\"mb-5\">\n <a\n href={`#${group.id}`}\n onClick={() => setActiveId(group.id)}\n className={`flex items-center gap-2 text-sm font-semibold mb-1.5 transition-colors ${\n isGroupActive ? 'text-foreground' : 'text-foreground/70 hover:text-foreground'\n }`}\n >\n <group.icon className={`h-4 w-4 ${isGroupActive ? 'text-foreground' : 'text-muted-foreground'}`} />\n {group.label}\n </a>\n <ul className=\"ml-6 space-y-1 border-l border-border/40 pl-3 relative\">\n {group.children.map(child => {\n const isActive = activeId === child.id;\n return (\n <li key={child.id} className=\"relative\">\n {isActive && (\n <span className=\"absolute -left-[13px] top-0 bottom-0 w-0.5 bg-foreground rounded-full\" />\n )}\n <a\n href={`#${child.id}`}\n onClick={() => setActiveId(child.id)}\n className={`text-xs block py-0.5 truncate transition-colors ${\n isActive\n ? 'text-foreground font-medium'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {child.label}\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n );\n })}\n </nav>\n\n {/* Content */}\n <div className=\"flex-1 min-w-0 max-w-3xl\">\n\n {/* ══════ MCP 工作原理 ══════ */}\n <DocSection id=\"concept\" icon={Workflow} title=\"MCP 工作原理\">\n\n <SubDoc id=\"why-mcp\" title=\"为什么需要 MCP\">\n <p>\n 大模型(GPT / Claude / Gemini / 通义千问 等)本质上是<strong>文字大脑</strong>:它擅长读和写,但天生<strong>听不到</strong>音频、<strong>打不开</strong>你的数据库、<strong>调不通</strong>第三方 API。\n </p>\n <p>\n <strong>MCP(Model Context Protocol)</strong>是一套由 Anthropic 主导、业界共同推进的开放协议,它像一根\"对讲机\":让大模型可以在需要时主动去调用外部能力(工具、数据源、服务),然后把结果接回来继续对话。\n </p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <AlertTriangle className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n <span className=\"text-sm font-semibold\">没有 MCP 的做法</span>\n </div>\n <ul className=\"text-xs text-muted-foreground space-y-1.5 leading-relaxed\">\n <li>· 研究评测服务的 REST 文档</li>\n <li>· 自己写鉴权 / 上传 / 轮询代码</li>\n <li>· 把结果序列化后塞进 prompt</li>\n <li>· 每换一个大模型都得重写一遍</li>\n </ul>\n </div>\n <div className=\"rounded-lg border-2 border-foreground/60 p-4 bg-foreground/[0.02]\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Check className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">用驰声 MCP 之后</span>\n </div>\n <ul className=\"text-xs space-y-1.5 leading-relaxed\">\n <li>· 填一行 MCP Server 地址</li>\n <li>· 大模型自动发现&amp;调用评测工具</li>\n <li>· 换任何支持 MCP 的大模型都能用</li>\n <li>· 不写一行调用代码</li>\n </ul>\n </div>\n </div>\n <Callout type=\"info\">\n MCP 让\"接入语音评测\"从一个\"后端工程\"变成了一个\"配置项\"。这也是为什么 Cursor、Claude Desktop、Dify、Coze、字节豆包、飞书智能助手都已经原生支持 MCP。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"scenario-walkthrough\" title=\"场景走查 · 从一段录音到温柔反馈\">\n <p>\n 想象一个具体场景:一位开发者用 Claude 做了一款 <strong>「AI 雅思口语陪练」小程序</strong>,接入驰声语音评测 MCP。当用户发来一段录音,整个链路是这样的:\n </p>\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-5 my-4 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong className=\"text-foreground\">配置阶段</strong>(开发者 · 一次性)</p>\n <p className=\"text-muted-foreground mt-1\">在 AI 后端的配置文件里填一行驰声 MCP Server 地址。大模型启动时自动发现:「我多了一个叫<strong>语音评测</strong>的工具」。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong className=\"text-foreground\">接收用户指令</strong>(终端用户)</p>\n <p className=\"text-muted-foreground mt-1\">小程序用户发来一段自我介绍录音:「帮我听听,发音哪里有问题?」后端把「文字 + 录音链接」打包给大模型。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong className=\"text-foreground\">大模型自主思考</strong>(LLM · 最核心的魔法)</p>\n <div className=\"text-muted-foreground mt-1 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed\">\n 「用户让我听发音。」<br />\n 「但我只是文字大脑,我听不到。」<br />\n 「等等 —— 开发者给了我一个叫 <code className=\"bg-muted px-1 rounded\">chivox_voice_eval</code> 的 MCP 工具!」\n </div>\n </FlowStep>\n <FlowStep title=\"4\">\n <p><strong className=\"text-foreground\">MCP 协议 · 自动调用</strong>(LLM → 驰声)</p>\n <p className=\"text-muted-foreground mt-1\">大模型自动按 MCP 标准构造 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools/call</code> 请求,把录音链接和参考文本发到驰声服务。<span className=\"text-foreground\">开发者不需要写任何调用代码。</span></p>\n </FlowStep>\n <FlowStep title=\"5\">\n <p><strong className=\"text-foreground\">驰声服务处理</strong>(评测引擎)</p>\n <p className=\"text-muted-foreground mt-1\">驰声对音频进行考试级打分,把结构化结果通过 MCP 原路返回:<code className=\"bg-muted px-1 rounded text-xs font-mono\">overall 85</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">/θ/ 音发音错误</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">流利度 88</code>。</p>\n </FlowStep>\n <FlowStep title=\"6\">\n <p><strong className=\"text-foreground\">大模型整合并输出反馈</strong>(LLM)</p>\n <p className=\"text-muted-foreground mt-1\">大模型把冷冰冰的分数翻译成温柔反馈:</p>\n <div className=\"text-foreground/80 mt-2 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed italic\">\n 「你的自我介绍很棒!综合 85 分 🎉 不过 <strong>think</strong> 这个词的 /θ/ 咬舌音不太标准,我们一起来练一下……」\n </div>\n </FlowStep>\n </div>\n <Callout type=\"tip\">\n <strong>整条链路里,MCP 只出现在 ① 和 ④</strong>:① 让开发者一次性告诉大模型「你多了一个工具」,④ 让大模型可以自动调用它。这两步的标准化,就是 MCP 的全部价值。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"dev-responsibility\" title=\"开发者只做两件事\">\n <p>听起来复杂,但对开发者来说只需要两件事:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">1</span>\n <span className=\"text-sm font-semibold\">填一次 MCP 配置</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 在 AI 后端的配置里填入驰声 MCP Server 地址和 API Key,就这一次,以后新加评测能力完全免开发。\n </p>\n <CodeBlock filename=\"mcp-config.json\" lang=\"json\">{`{\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"apiKey\": \"sk-••••••••••\"\n }\n}`}</CodeBlock>\n </div>\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">2</span>\n <span className=\"text-sm font-semibold\">写一段 System Prompt</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 告诉大模型这个工具是做什么的、什么时候调用、结果怎么转成用户友好的反馈。\n </p>\n <CodeBlock filename=\"system-prompt.md\">{`你是一位英语口语教练。\n当用户发来录音时:\n1. 调用 chivox_voice_eval 评分\n2. 聚焦 <70 分的音素家族\n3. 用高情商话术给出练习建议`}</CodeBlock>\n </div>\n </div>\n <p className=\"text-muted-foreground\">\n 其余所有事情 —— 音频上传、鉴权、工具发现、调用时机、返回格式、结果整合 —— 都由 MCP 协议和大模型自己搞定。\n </p>\n </SubDoc>\n\n <SubDoc id=\"integration-paths\" title=\"三种典型接入姿势\">\n <p>根据你的技术栈,有三种等价的接入方式,底层都走完全相同的 MCP 协议:</p>\n\n {/* ── 姿势一:可视化 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Sparkles className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势一 · 可视化拖拽(扣子 / Coze / 飞书智能助手)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合产品经理、运营、不写后端代码的开发者。整个过程像在手机上装 App。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>找到入口</strong>:在扣子平台的 <strong>「插件 / 工具」</strong> 配置区,点击<strong>「添加 MCP 服务」</strong>。</li>\n <li><strong>填两个字段</strong>(这也是你未来会从驰声这边拿到的):\n <div className=\"mt-2 overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium w-32\">服务器地址</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权密钥</td><td className=\"py-2 px-3 font-mono text-xs\">sk-•••••••••• <span className=\"text-muted-foreground ml-2\">(由驰声分配,用于计费与识别)</span></td></tr>\n </tbody>\n </table>\n </div>\n </li>\n <li><strong>平台自动发现能力</strong>:扣子会访问你填的地址,读取 MCP Server 自描述的\"说明书\"。瞬间明白:「这是个语音评测工具,需要传音频,会返发音分」。</li>\n <li><strong>右侧测试</strong>:在平台的聊天框里发一段语音,大模型就会自动调用工具。<strong>整个过程不需要写一行代码。</strong></li>\n </ol>\n </div>\n\n {/* ── 姿势二:代码框架 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Code2 className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势二 · 代码框架配置(Dify / LangChain / 自研后端)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合做独立 APP / 小程序、用 Dify 或 LangChain 写 Agent 的极客开发者。核心就是往配置文件里加一段 JSON。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>打开配置文件</strong>(项目根目录下的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp.json</code> 或框架指定的配置路径)。</li>\n <li><strong>在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code> 下增加一个节点</strong>:\n <CodeBlock filename=\"mcp.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"env\": {\n \"API_KEY\": \"你的专属密钥\"\n }\n }\n }\n}`}</CodeBlock>\n </li>\n <li><strong>重启后端服务</strong>。大模型的底层调度器会在启动时自动连上驰声 MCP Server,把 16 种评测工具注册成<strong>随时待命</strong>的 tools。</li>\n <li><strong>触发调用</strong>:用户发来录音后,大模型看到 system prompt 的引导,自动选择合适的工具(单词/句子/段落/半开放)调用。</li>\n </ol>\n </div>\n\n {/* ── 姿势三:Function Calling 直连(分 A / B 两种做法) ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Terminal className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势三 · 原生 Function Calling(豆包 / DeepSeek / OpenAI 兼容 API)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合不用任何 Agent 框架、直接调用 LLM API 的后端开发者。豆包 1.5+ / DeepSeek / GPT / Qwen 等模型都走标准 <code className=\"bg-muted px-1 rounded text-xs font-mono\">chat.completions</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> 参数。这里又分 A / B 两种做法,<strong>B 才是生产推荐</strong>。\n </p>\n\n <Callout type=\"warning\">\n 常见疑问:<em>\"要不要手写一个 tool 定义?\"</em><br />\n — <strong>姿势一 / 二不用</strong>(Coze、Dify、火山方舟控制台、Claude Desktop 会自动从 MCP Server 拉工具列表)。<br />\n — <strong>姿势三看你选 A 还是 B</strong>:A 要手写,B 用 MCP 客户端库自动拉。\n </Callout>\n\n {/* ── 3-A:手写 tool schema ── */}\n <div className=\"mt-4 rounded-md border border-border/40 bg-muted/20 p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold\">3-A · 手写 tool schema(最快验证,不推荐生产)</p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20\">快速 Demo</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 自己写一段 JSON Schema 告诉豆包\"有个工具叫 xxx,参数长这样\",豆包返回 tool_call 后你再手动去调驰声 MCP。<strong>缺点</strong>:驰声有 16 个评测工具,你每加一个题型都要改代码。\n </p>\n <CodeBlock filename=\"3a_hardcode_tool.py\" lang=\"python\">{`import os, json\nfrom openai import OpenAI\n\nclient = OpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv('ARK_API_KEY'),\n)\n\n# ─── 手写一段 tool schema(不推荐:16 个工具就要写 16 段)───\ntools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英语单词发音评测。当用户让你评价英语发音并提供录音链接时调用。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"audio_url\": {\"type\": \"string\", \"description\": \"录音 URL\"},\n \"reference_text\": {\"type\": \"string\", \"description\": \"参考文本,如 Apple\"},\n },\n \"required\": [\"audio_url\", \"reference_text\"]\n }\n }\n}]\n\nresp = client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,拿到评测结果后温柔地反馈,不要直接报分数。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n)\n\nmsg = resp.choices[0].message\nif msg.tool_calls:\n args = json.loads(msg.tool_calls[0].function.arguments)\n # ↓↓↓ 这里手动调驰声 MCP,再把结果回填给豆包做第二趟 ↓↓↓\n # result = requests.post(\"https://speech-eval.site/mcp\", json={...})`}</CodeBlock>\n </div>\n\n {/* ── 3-B:MCP 自动发现 ── */}\n <div className=\"mt-3 rounded-md border border-foreground/30 bg-foreground/[0.03] dark:bg-white/[0.03] p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold flex items-center gap-1.5\">\n 3-B · 用 MCP 客户端库自动发现 <Sparkles className=\"h-3 w-3 text-foreground\" />\n </p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-foreground text-background\">生产推荐</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 启动时连上驰声 MCP Server,调一次 <code className=\"bg-muted px-1 rounded\">tools/list</code>,把 16 个评测工具自动转成 Function Calling 的 tools 格式。<strong>驰声加新工具,你代码不用改</strong>。这才是 MCP 协议的真正价值。\n </p>\n <CodeBlock filename=\"3b_mcp_autodiscover.py\" lang=\"python\">{`import asyncio, os\nfrom openai import AsyncOpenAI\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\n\nclient = AsyncOpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv(\"ARK_API_KEY\"),\n)\n\nasync def main():\n # ─── 1. 连驰声 MCP Server,自动拉取全部 16 个工具 ───\n async with streamablehttp_client(\n \"https://speech-eval.site/mcp\",\n headers={\"Authorization\": f\"Bearer {os.getenv('CHIVOX_KEY')}\"},\n ) as (read, write, _):\n async with ClientSession(read, write) as session:\n await session.initialize()\n mcp_tools = (await session.list_tools()).tools # ← 一行拿到全部工具\n\n # ─── 2. MCP schema → OpenAI Function Calling schema ───\n tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema, # MCP 直接给 JSON Schema\n }\n } for t in mcp_tools]\n\n # ─── 3. 正常调豆包,tools 由 MCP 动态注入 ───\n resp = await client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,收到评测结果后温柔反馈。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n )\n\n # ─── 4. 折返跑:豆包选好工具后,直接走 MCP 调驰声 ───\n msg = resp.choices[0].message\n if msg.tool_calls:\n call = msg.tool_calls[0]\n result = await session.call_tool(\n call.function.name,\n arguments=__import__(\"json\").loads(call.function.arguments),\n )\n # 把 result 回填给豆包做第二趟 chat.completions 即可\n\nasyncio.run(main())`}</CodeBlock>\n <p className=\"text-[11px] text-muted-foreground mt-3 leading-relaxed\">\n 想要更省心?直接用 <a href=\"https://github.com/modelcontextprotocol/python-sdk\" target=\"_blank\" rel=\"noreferrer\" className=\"text-foreground underline underline-offset-2\">官方 mcp python sdk</a>、<code className=\"bg-muted px-1 rounded text-xs\">mcp-use</code>、<code className=\"bg-muted px-1 rounded text-xs\">openai-agents</code>,或火山官方 <code className=\"bg-muted px-1 rounded text-xs\">Arkitect</code> 高代码 SDK,它们都把上面这段\"MCP → Function Calling\"的桥接封装好了。\n </p>\n </div>\n </div>\n\n <Callout type=\"info\">\n 三种姿势走的是完全相同的 MCP 协议,能力完全等价。选型建议:<strong>产品经理 / 原型验证用姿势一</strong>(Coze 填表单);<strong>AI 客户端用户用姿势二</strong>(Cursor / Claude Desktop 填 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">mcp.json</code>);<strong>后端生产服务用姿势三-B</strong>(MCP 客户端库自动发现工具,不要手写 tool schema)。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-local-mcp</strong> 是驰声语音评测的 MCP 本地代理,通过 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 将远程驰声评测服务桥接到本地,让 AI 助手(Claude Desktop、Claude Code、Cursor 等)直接调用语音评测能力。</p>\n <p>它支持两种评测模式,覆盖从即时交互到批量处理的全部场景:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Mic className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">实时录音评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">通过麦克风边录边评,音频以 WebSocket 流式推送,无需生成中间文件,延迟更低、体验更佳。</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <FolderOpen className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">音频文件评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">支持本地文件路径、Base64 编码、URL 三种输入,适合已有音频的批量评测场景。</p>\n </div>\n </div>\n <Callout type=\"info\">当前支持 <strong>16 种评测工具</strong>:英文 10 种(单词、句子、段落、纠音、自然拼读、口语选择、半开放题等)+ 中文 6 种(汉字、拼音、词句、段落、有限分支、AI Talk)。</Callout>\n </SubDoc>\n\n <SubDoc id=\"requirements\" title=\"系统要求\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">依赖</th>\n <th className=\"text-left py-2 px-3 font-medium\">版本</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-semibold\">Node.js</td><td className=\"py-2 px-3 font-mono text-xs\">{'>'}= 18</td><td className=\"py-2 px-3 text-muted-foreground\">运行时必须</td></tr>\n <tr><td className=\"py-2 px-3 font-semibold\">SoX</td><td className=\"py-2 px-3 font-mono text-xs\">any</td><td className=\"py-2 px-3 text-muted-foreground\">仅实时录音功能需要(处理麦克风音频流)</td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"mt-3\">安装 SoX(仅在使用实时录音时需要):</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# macOS\nbrew install sox\n\n# Ubuntu / Debian\nsudo apt-get install sox`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"install\" title=\"安装方式\">\n <p><strong>方式一:npm 全局安装</strong>(推荐)</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`npm install -g chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式二:npx 免安装运行</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`MCP_REMOTE_URL=http://your-server:8080 npx chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式三:从源码构建</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`git clone https://git.chivox.com/CLOUD_DEV/cvx_local_mcp.git\ncd cvx_local_mcp\nbash scripts/build.sh`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"env-vars\" title=\"环境变量\">\n <ParamTable params={[\n { name: 'MCP_REMOTE_URL', type: 'string', required: true, desc: '远程驰声 MCP 服务地址,如 http://your-server:8080' },\n { name: 'MCP_API_KEY', type: 'string', required: false, desc: 'API 认证密钥(如已配置鉴权则必填)' },\n ]} />\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 直接用(零代码) ══════ */}\n <DocSection id=\"config\" icon={Plug} title=\"接入 · 直接用 MCP(零代码)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Sparkles className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>不用写代码、不用装 SDK,只在客户端的<strong className=\"text-foreground\">「MCP 配置」</strong>或<strong className=\"text-foreground\">「扩展」</strong>里填一段 JSON / URL</li>\n <li>保存后 LLM 自动发现 16 个评测工具,并在对话 / Agent 中自主调用</li>\n <li>覆盖 <strong className=\"text-foreground\">IDE 编程助手</strong>、<strong className=\"text-foreground\">桌面 AI 客户端</strong>、<strong className=\"text-foreground\">企业 AI 工作台</strong>、<strong className=\"text-foreground\">可视化 Bot / 工作流平台</strong>四大生态</li>\n </ul>\n </div>\n\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置并保存:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <Callout type=\"tip\">Cursor 使用远程 Streamable HTTP 模式直连,无需安装本地代理。如需实时录音功能,请使用下方 Claude Desktop 的本地代理配置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-desktop\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件(macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"chivox-local-mcp\",\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\",\n \"MCP_API_KEY\": \"your-api-key\"\n }\n }\n }\n}`}</CodeBlock>\n <p>如果是从源码构建:</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/cvx_local_mcp/dist/index.js\"],\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\"\n }\n }\n }\n}`}</CodeBlock>\n <Callout type=\"info\">Claude Desktop 使用本地代理模式(stdio),支持实时录音评测。本地代理会自动桥接远程服务。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-code\" title=\"Claude Code 配置\">\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# npm 全局安装后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 chivox-local-mcp\n\n# 源码构建后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 node /path/to/cvx_local_mcp/dist/index.js`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-ai-ide\" title=\"Windsurf / Zed / Continue 等 IDE 编程助手\">\n <p>除了 Cursor 和 Claude Code,主流的 AI 编程 IDE 都原生支持 MCP。以下客户端使用<strong>完全一致</strong>的 Streamable HTTP 配置,区别只在配置入口:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">客户端</th>\n <th className=\"text-left py-2 px-3 font-medium\">配置入口</th>\n <th className=\"text-left py-2 px-3 font-medium\">备注</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">Windsurf (Codeium)</td><td className=\"py-2 px-3 text-xs\">Settings → Cascade → MCP Servers</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">直接贴下方 JSON</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Zed</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">~/.config/zed/settings.json</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">context_servers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">Assistant Panel 自动发现</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Continue (VS Code / JetBrains)</td><td className=\"py-2 px-3 text-xs\"><code className=\"bg-muted px-1 rounded text-xs font-mono\">config.yaml</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code></td><td className=\"py-2 px-3 text-xs text-muted-foreground\">v0.9+ 支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">通义灵码 / CodeGeeX / 文心快码</td><td className=\"py-2 px-3 text-xs\">插件设置 → MCP 服务</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">2026 Q1 起陆续支持</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Cline (原 Claude Dev)</td><td className=\"py-2 px-3 text-xs\">MCP Servers 面板 → 编辑配置</td><td className=\"py-2 px-3 text-xs text-muted-foreground\">VS Code 扩展</td></tr>\n </tbody>\n </table>\n </div>\n <CodeBlock filename=\"通用 MCP 配置(复制即用)\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox-speech-eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n }\n }\n}`}</CodeBlock>\n <Callout type=\"tip\">所有 IDE 类客户端都走远程 Streamable HTTP,零本地依赖。需要实时录音才需切到 Claude Desktop 的本地代理模式。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-coze\" title=\"扣子(Coze)配置\">\n <p><a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子(Coze)</a>是字节跳动推出的 AI 应用开发平台,支持通过 MCP 扩展为 Agent 赋予外部工具能力。接入驰声语音评测后,你可以构建口语练习 Bot、发音诊断助手等应用。</p>\n <p className=\"font-semibold mt-4\">方式一:扣子空间 MCP 扩展(推荐)</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 <a href=\"https://space.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子空间</a>(space.coze.cn)</li>\n <li>在对话框下方点击 <strong>「添加扩展」</strong></li>\n <li>选择 <strong>「自定义」</strong> 标签,点击添加新的 MCP 服务</li>\n <li>填入以下配置信息:</li>\n </ol>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">名称</td><td className=\"py-2 px-3 font-mono text-xs\">chivox-speech-eval</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>保存后,在对话中输入 <strong>「帮我评测这段英文发音」</strong> 即可触发 Agent 调用</li>\n </ol>\n <Callout type=\"tip\">添加 MCP 扩展后,扣子空间的 Agent 会自动识别并调用评测工具。你可以在 Agent 的思考过程中看到具体的工具调用细节。</Callout>\n\n <p className=\"font-semibold mt-6\">方式二:在 Coze 开发平台的 Bot 中使用</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>进入 <a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Coze 开发平台</a>,创建或编辑 Bot</li>\n <li>在 Bot 的 <strong>「插件」</strong> 或 <strong>「工具」</strong> 面板中,添加 MCP 工具</li>\n <li>选择 <strong>Streamable HTTP</strong> 类型,填入 URL:<code className=\"bg-muted px-1 rounded text-xs font-mono\">https://speech-eval.site/mcp</code></li>\n <li>Bot 将自动发现所有可用的评测工具(如 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_word</code> 等)</li>\n <li>在 Bot 的人设提示词中,添加语音评测相关的引导语</li>\n </ol>\n <CodeBlock filename=\"Bot 人设提示词示例\">{`你是一位专业的英语口语教练。当用户上传音频并请求评测时:\n1. 使用 evaluate_english_word / evaluate_english_sentence 等工具进行评测\n2. 根据评测结果(overall、accuracy、fluency 等分数)给出专业分析\n3. 针对得分较低的维度提供具体改进建议\n4. 鼓励用户持续练习`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze-workflow\" title=\"Coze 工作流接入\">\n <p>如果你需要在 Coze 工作流中集成语音评测能力,可以通过 <strong>HTTP Request 节点</strong>调用评测 API:</p>\n\n <p className=\"font-semibold mt-4\">步骤 1:上传音频</p>\n <p>在工作流中添加一个 HTTP Request 节点,配置如下:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">方法</td><td className=\"py-2 px-3 font-mono text-xs\">POST</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/upload</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Content-Type</td><td className=\"py-2 px-3 font-mono text-xs\">audio/mp3</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Body</td><td className=\"py-2 px-3 text-muted-foreground\">音频文件二进制数据</td></tr>\n </tbody>\n </table>\n </div>\n <p>响应中获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{{audioId}}'}</code>,传递给下一步。</p>\n\n <p className=\"font-semibold mt-4\">步骤 2:调用评测</p>\n <p>添加第二个 HTTP Request 节点,通过 MCP 接口调用评测工具:</p>\n <CodeBlock filename=\"HTTP Request Body\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"evaluate_english_sentence\",\n \"arguments\": {\n \"audioId\": \"{{audioId}}\",\n \"sentence\": \"{{ref_text}}\"\n }\n },\n \"id\": 1\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">步骤 3:处理结果</p>\n <p>评测结果通过 JSON 格式返回,可使用 Coze 的 <strong>代码节点</strong> 或 <strong>LLM 节点</strong> 对结果进行解析和二次处理(如生成学习建议、计算进步趋势等)。</p>\n\n <Callout type=\"info\">工作流方式适合需要固定流程的场景(如批量评测、定时任务、与其他系统联动)。如果是交互式场景,推荐使用上方的 MCP 扩展方式,Agent 会自动选择合适的工具。</Callout>\n\n <div className=\"rounded-lg bg-muted/30 p-5 mt-4\">\n <p className=\"text-xs font-semibold mb-3\">完整工作流示意</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">用户输入音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 上传音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 调用评测</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM: 分析结果</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">输出反馈</span>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"config-ai-workspace\" title=\"AI 工作台 · 豆包 / 飞书智能伙伴 / WorkBuddy / 钉钉 AI\">\n <p>这一类是<strong>面向「上班族」的企业 / 日常 AI 助手</strong>,产品形态是个聊天框 + 插件市场 / 扩展面板。接入后,员工在 App 里打字「帮我评一下这段录音」就能自动触发驰声评测,不用写任何代码。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Bot className=\"h-4 w-4 text-amber-500\" /> 豆包(Doubao)App / 桌面版</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节跳动官方 AI 助手,2026 年起桌面版支持 MCP 扩展。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>桌面版设置 → <strong>「智能体扩展」</strong> → 添加 MCP 服务</li>\n <li>类型选 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">streamable-http</code>,URL 填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>对话中直接说「帮我评测」即可自动调用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Building2 className=\"h-4 w-4 text-blue-500\" /> 飞书智能伙伴(My AI)</p>\n <p className=\"text-xs text-muted-foreground mb-2\">飞书企业 AI,支持以 MCP / Function Calling 挂载外部工具给团队 Bot。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>飞书开发者后台 → 创建应用 → <strong>「AI 能力 / 智能伙伴」</strong></li>\n <li>在「工具」中选 <strong>外部 MCP 服务</strong>,填入上方 URL</li>\n <li>把智能伙伴拉进群聊,@ 它提交音频即可评测</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Sparkles className=\"h-4 w-4 text-rose-500\" /> WorkBuddy / 企业智能助手</p>\n <p className=\"text-xs text-muted-foreground mb-2\">字节 WorkBuddy、腾讯会议 AI、企业微信智能助手等企业 AI 工作台场景。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>在工作台的<strong>「技能 / 扩展」</strong>后台新增一个 MCP 连接器</li>\n <li>传输协议选 Streamable HTTP,地址填 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">https://speech-eval.site/mcp</code></li>\n <li>把技能发布到指定部门 / 群组,HR、培训、销售口语陪练场景即可直接用</li>\n </ol>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-1 flex items-center gap-2\"><Workflow className=\"h-4 w-4 text-cyan-500\" /> 钉钉 AI 助理 / 通义千问 App</p>\n <p className=\"text-xs text-muted-foreground mb-2\">阿里生态:钉钉 AI 助理支持自定义插件,通义 App 可通过「AI 工具箱」挂载 MCP。</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs\">\n <li>钉钉开发者后台 → AI 助理 → <strong>「插件开发 / MCP 接入」</strong></li>\n <li>选 HTTP Streamable,填入驰声 MCP URL 并开启鉴权(可选)</li>\n <li>在钉钉群 @AI 助理上传音频,自动返回评测卡片</li>\n </ol>\n </div>\n </div>\n\n <Callout type=\"tip\"><strong>典型场景 · 小龙虾们的共同剧本</strong>:销售口语陪练 → 外语培训 → 主持 / 播音岗位考核 → K12 英语作业批改 → 客服话术复盘。这些「上班族 AI 工作台」接入驰声 MCP 后,员工 / 学员只要对着话筒说话,AI 就能自动打分并给出个性化改进建议,运营 / 培训 / 教研团队零开发即可上线。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-workflow-platforms\" title=\"Dify / n8n / Flowise 等可视化编排\">\n <p>如果你需要把语音评测编排进<strong>多步业务流</strong>(例如:学员录音 → 评测 → 生成错题集 → 推送到 CRM),可视化工作流平台比聊天 Bot 更合适。</p>\n\n <div className=\"grid gap-3 md:grid-cols-3 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Dify</p>\n <p className=\"text-xs text-muted-foreground mb-2\">开源 LLMOps,v0.15+ 原生 MCP 工具。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>「工具」→「自定义 MCP」→ 填 URL</li>\n <li>在 Chatflow / Workflow 节点里直接拖用</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">n8n</p>\n <p className=\"text-xs text-muted-foreground mb-2\">工作流自动化,用 <code className=\"bg-muted px-1 rounded text-[10px] font-mono\">MCP Client</code> 节点。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>添加 <strong>MCP Client</strong> 节点</li>\n <li>Transport 选 HTTP Stream,URL 贴驰声地址</li>\n <li>Tool 下拉自动列出 16 个评测能力</li>\n </ul>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Flowise / LangFlow</p>\n <p className=\"text-xs text-muted-foreground mb-2\">可视化 LangChain Agent 编排。</p>\n <ul className=\"list-disc list-inside space-y-0.5 text-xs\">\n <li>拖出 <strong>MCP Tool</strong> 节点</li>\n <li>连到 AgentExecutor 即可</li>\n </ul>\n </div>\n </div>\n\n <Callout type=\"info\">可视化平台的好处是<strong>流程可追溯、可回放</strong>,适合教育机构 / 培训部门做批量评测 + 报表导出;缺点是单步延迟略高于直接 MCP 调用,对延迟敏感的交互场景建议用 Cursor / Claude Desktop 直连。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"其他 MCP 客户端(通用配置)\">\n <p>任何遵循 MCP 协议的客户端都可以接入,统一使用以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权(可选)</td><td className=\"py-2 px-3 font-mono text-xs\">Header:<code>Authorization: Bearer &lt;MCP_API_KEY&gt;</code></td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"text-xs text-muted-foreground mt-3\">还没找到你用的客户端?任何支持 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 的终端都通用。找不到入口?下一小节「编程用 MCP」里用 SDK 直接接即可。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入 · 编程用 MCP ══════ */}\n <DocSection id=\"config-code\" icon={Code2} title=\"接入 · 编程用 MCP(SDK / 代码集成)\">\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mb-5 text-sm\">\n <p className=\"font-semibold mb-1.5 flex items-center gap-2\"><Terminal className=\"h-4 w-4\" /> 这一类接入的共同特征</p>\n <ul className=\"list-disc list-inside space-y-1 text-muted-foreground\">\n <li>你在写一段<strong className=\"text-foreground\">真正的后端服务</strong>:一个 Agent Worker、一个 API、一个自动化脚本</li>\n <li>业务里的「语音评测」是其中一个步骤,需要由代码 <strong className=\"text-foreground\">决定何时调用</strong>、<strong className=\"text-foreground\">如何解析结果</strong></li>\n <li>三种主流写法:① 官方 MCP 客户端库 → ② Agent 框架自动桥接 → ③ 退化到 chat.completions + tools 参数</li>\n </ul>\n <p className=\"mt-2 text-xs text-muted-foreground\">💡 强烈推荐①或②:驰声有 16 个评测工具,<strong className=\"text-foreground\">让 MCP 自动注入 tool schema</strong>,每新增一个题型不用改一行业务代码。</p>\n </div>\n\n <SubDoc id=\"code-mcp-sdk\" title=\"① 官方 MCP 客户端库(Python / TypeScript)\">\n <p>最底层、也最推荐的方式。由 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Anthropic 官方维护</a>,一次连接自动拿到全部工具的 JSON Schema,然后交给任意 LLM。</p>\n\n <p className=\"font-semibold mt-4\">Python:<code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp</code></p>\n <CodeBlock filename=\"chivox_client.py\" lang=\"python\">{`# pip install mcp openai\nimport asyncio, os\nfrom openai import OpenAI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\n\nMCP_URL = \"https://speech-eval.site/mcp\"\nllm = OpenAI(api_key=os.getenv(\"DOUBAO_KEY\"),\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\") # 豆包 / DeepSeek / OpenAI 同构\n\nasync def main(audio_id: str, ref: str):\n async with streamablehttp_client(MCP_URL) as (reader, writer, _):\n async with ClientSession(reader, writer) as sess:\n await sess.initialize()\n\n tools = await sess.list_tools()\n openai_tools = [{\n \"type\": \"function\",\n \"function\": {\"name\": t.name, \"description\": t.description,\n \"parameters\": t.inputSchema},\n } for t in tools.tools]\n\n resp = llm.chat.completions.create(\n model=\"doubao-1-5-pro-32k\",\n messages=[{\"role\": \"user\",\n \"content\": f\"帮我评测 audioId={audio_id},参考文本:{ref}\"}],\n tools=openai_tools,\n )\n\n for call in resp.choices[0].message.tool_calls or []:\n result = await sess.call_tool(call.function.name,\n arguments=eval(call.function.arguments))\n print(call.function.name, \"→\", result.content[0].text[:200])\n\nasyncio.run(main(\"abc123\", \"I think therefore I am\"))`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">TypeScript / Node.js:<code className=\"bg-muted px-1 rounded text-xs font-mono\">@modelcontextprotocol/sdk</code></p>\n <CodeBlock filename=\"chivox-client.ts\" lang=\"typescript\">{`// npm i @modelcontextprotocol/sdk openai\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport OpenAI from \"openai\";\n\nconst mcp = new Client({ name: \"chivox-biz\", version: \"1.0.0\" });\nawait mcp.connect(new StreamableHTTPClientTransport(new URL(\"https://speech-eval.site/mcp\")));\n\nconst { tools } = await mcp.listTools();\nconst openai = new OpenAI({ apiKey: process.env.DEEPSEEK_KEY, baseURL: \"https://api.deepseek.com\" });\n\nconst resp = await openai.chat.completions.create({\n model: \"deepseek-chat\",\n messages: [{ role: \"user\", content: \"评测 audioId=abc123, 参考 I think therefore I am\" }],\n tools: tools.map(t => ({ type: \"function\", function: {\n name: t.name, description: t.description, parameters: t.inputSchema\n }})),\n});\n\nfor (const call of resp.choices[0].message.tool_calls ?? []) {\n const r = await mcp.callTool({ name: call.function.name,\n arguments: JSON.parse(call.function.arguments) });\n console.log(call.function.name, r.content);\n}`}</CodeBlock>\n <Callout type=\"tip\">这种写法 <strong>LLM 供应商可换</strong>(豆包 / DeepSeek / GPT / Qwen / Moonshot),<strong>工具自动更新</strong>(驰声新增题型无需改业务代码),适合生产后端。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-agent-frameworks\" title=\"② 用 Agent 框架自动桥接(LangChain / Mastra / Agents SDK)\">\n <p>如果项目已经在用 Agent 框架,几行 adapter 就能把驰声 MCP 塞进去,框架本身会接管 tool-calling 循环、重试、日志。</p>\n\n <div className=\"grid gap-3 md:grid-cols-2 mt-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LangChain / LangGraph(Python)</p>\n <CodeBlock filename=\"agent.py\" lang=\"python\">{`# pip install langchain-mcp-adapters langgraph\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom langgraph.prebuilt import create_react_agent\n\nclient = MultiServerMCPClient({\n \"chivox\": {\n \"transport\": \"streamable_http\",\n \"url\": \"https://speech-eval.site/mcp\",\n }\n})\ntools = await client.get_tools()\n\nagent = create_react_agent(\"openai:gpt-4o-mini\", tools)\nresult = await agent.ainvoke({\"messages\":\n [(\"user\", \"评测 audioId=abc123 参考 think\")]})\nprint(result[\"messages\"][-1].content)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">Mastra(TypeScript)</p>\n <CodeBlock filename=\"mastra-agent.ts\" lang=\"typescript\">{`// npm i @mastra/core @mastra/mcp\nimport { MCPClient } from \"@mastra/mcp\";\nimport { Agent } from \"@mastra/core/agent\";\nimport { openai } from \"@ai-sdk/openai\";\n\nconst mcp = new MCPClient({ servers: {\n chivox: { url: new URL(\"https://speech-eval.site/mcp\") }\n}});\n\nexport const speechCoach = new Agent({\n name: \"speech-coach\",\n instructions: \"你是口语教练,用 chivox 工具评测并给改进建议。\",\n model: openai(\"gpt-4o-mini\"),\n tools: await mcp.getTools(),\n});`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">OpenAI Agents SDK(Python)</p>\n <CodeBlock filename=\"agents_sdk.py\" lang=\"python\">{`# pip install openai-agents\nfrom agents import Agent, Runner\nfrom agents.mcp import MCPServerStreamableHttp\n\nchivox = MCPServerStreamableHttp(\n params={\"url\": \"https://speech-eval.site/mcp\"},\n name=\"chivox-speech-eval\",\n)\n\nasync with chivox:\n agent = Agent(name=\"coach\",\n instructions=\"专业口语教练\",\n mcp_servers=[chivox])\n r = await Runner.run(agent, \"评测 audioId=abc123\")\n print(r.final_output)`}</CodeBlock>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\">LlamaIndex / AutoGen / CrewAI</p>\n <p className=\"text-xs text-muted-foreground mb-2\">这些框架都有官方或社区 MCP adapter,用法与上述类似:</p>\n <ul className=\"list-disc list-inside space-y-1 text-xs\">\n <li><strong>LlamaIndex</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">llama-index-tools-mcp</code></li>\n <li><strong>AutoGen</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">autogen-ext[mcp]</code></li>\n <li><strong>CrewAI</strong>:<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">crewai-tools</code> 的 MCPServerAdapter</li>\n <li><strong>Spring AI</strong>(Java):<code className=\"bg-muted px-1 rounded text-[10px] font-mono\">spring-ai-mcp-client</code></li>\n </ul>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"code-function-calling\" title=\"③ 直调 chat.completions · tools 参数(豆包 / DeepSeek / 火山方舟)\">\n <p>当你用的 LLM SDK <strong>还没有原生 MCP 支持</strong>(比如老版本的火山方舟 / 百度千帆 / 通义 DashScope),退化方案是:用 MCP 客户端<strong>一次性拉到 tool schema</strong>,然后按 OpenAI Function Calling 格式喂给 LLM。</p>\n <CodeBlock filename=\"doubao_tools.py\" lang=\"python\">{`# 适用:豆包(火山方舟)/ DeepSeek / Qwen / Moonshot / GLM 等 OpenAI 兼容 API\n# 核心思路:MCP 列工具 → 转 OpenAI tools → LLM 决策 → MCP 执行 → 回填\n\nasync with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as mcp:\n await mcp.initialize()\n tools = (await mcp.list_tools()).tools\n\n # ① 用 MCP 拉到的 schema 自动转 OpenAI 格式\n oa_tools = [{\"type\": \"function\", \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema,\n }} for t in tools]\n\n # ② 喂给豆包\n messages = [{\"role\": \"user\", \"content\": \"评测一下 audioId=abc123\"}]\n resp = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages, tools=oa_tools,\n )\n\n # ③ LLM 回 tool_calls → 折返跑到 MCP 执行\n msg = resp.choices[0].message\n if msg.tool_calls:\n messages.append(msg)\n for c in msg.tool_calls:\n result = await mcp.call_tool(\n c.function.name, arguments=json.loads(c.function.arguments))\n messages.append({\"role\": \"tool\", \"tool_call_id\": c.id,\n \"content\": result.content[0].text})\n # ④ 再喂一轮,让豆包根据评测结果产出自然语言诊断\n final = doubao.chat.completions.create(\n model=\"doubao-1-5-pro-32k\", messages=messages)\n print(final.choices[0].message.content)`}</CodeBlock>\n <Callout type=\"warning\"><strong>千万别手写 tool schema!</strong>驰声 16 个评测工具每个都有几十个字段,手写既容易错也难维护。一定用 MCP <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs font-mono\">list_tools()</code> 动态拿。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-selfhosted-agent\" title=\"④ 自研后端 Agent(FastAPI / NestJS / Spring)\">\n <p>大部分落地场景其实是:<strong>你已有一个业务后端</strong>(教育 SaaS / 培训系统 / 客服平台),只需要新增一个「语音评测」能力。推荐架构:</p>\n\n <div className=\"rounded-lg bg-muted/30 p-5 my-4\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">前端录音 / 上传</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">业务后端</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-primary/10 border border-primary/30 font-medium\">MCP Client(常驻连接)</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">驰声 MCP</span>\n <span className=\"text-muted-foreground\">↘</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM(诊断 / 出题)</span>\n </div>\n </div>\n\n <p className=\"font-semibold mt-2\">FastAPI 示例(生产最小骨架)</p>\n <CodeBlock filename=\"app.py\" lang=\"python\">{`from fastapi import FastAPI\nfrom mcp.client.streamable_http import streamablehttp_client\nfrom mcp import ClientSession\nfrom contextlib import asynccontextmanager\n\nmcp_session: ClientSession | None = None\n\n@asynccontextmanager\nasync def lifespan(app):\n global mcp_session\n async with streamablehttp_client(\"https://speech-eval.site/mcp\") as (r, w, _):\n async with ClientSession(r, w) as s:\n await s.initialize()\n mcp_session = s\n yield\n mcp_session = None\n\napp = FastAPI(lifespan=lifespan)\n\n@app.post(\"/api/evaluate\")\nasync def evaluate(audio_id: str, ref_text: str):\n # 直接选一个最合适的工具执行(不经 LLM,延迟最低)\n r = await mcp_session.call_tool(\"evaluate_english_sentence\",\n {\"audioId\": audio_id, \"sentence\": ref_text})\n return {\"result\": r.content[0].text}`}</CodeBlock>\n\n <Callout type=\"tip\"><strong>关键优化</strong>:① MCP 连接在 lifespan 里做成长连接,不要每次请求都重连;② 对延迟敏感的场景可以「跳过 LLM 直接调 tool」,把 LLM 放到「生成诊断报告」那一步。</Callout>\n </SubDoc>\n\n <div className=\"rounded-lg border border-border/60 bg-muted/30 p-4 mt-6 text-sm\">\n <p className=\"font-semibold mb-2\">方案选型速查</p>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-xs\">\n <thead><tr className=\"border-b border-border/40\">\n <th className=\"text-left py-2 font-medium\">场景</th>\n <th className=\"text-left py-2 font-medium\">推荐方案</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2\">AI 对话 / Copilot 类产品,LLM 供应商可能会换</td><td className=\"py-2\">① 官方 MCP 客户端库</td></tr>\n <tr><td className=\"py-2\">已有 LangChain / Mastra / Agents SDK 技术栈</td><td className=\"py-2\">② Agent 框架 + adapter</td></tr>\n <tr><td className=\"py-2\">火山方舟 / 千帆控制台直连,最小侵入</td><td className=\"py-2\">③ chat.completions + tools</td></tr>\n <tr><td className=\"py-2\">教育 SaaS / 培训 / 客服等业务后端集成</td><td className=\"py-2\">④ 自研后端 Agent</td></tr>\n </tbody>\n </table>\n </div>\n </div>\n </DocSection>\n\n {/* ══════ 评测模式 ══════ */}\n <DocSection id=\"eval-modes\" icon={Radio} title=\"评测模式\">\n\n <SubDoc id=\"stream-eval\" title=\"实时录音评测(边录边评)\">\n <p>通过本地麦克风实时录音,音频以 WebSocket 流式推送到评测服务,<strong>无需生成中间文件</strong>。这是最灵活、延迟最低的评测方式,特别适合交互式口语练习场景。</p>\n <Callout type=\"tip\">实时录音评测需要安装 <strong>SoX</strong> 并使用本地代理模式(Claude Desktop / Claude Code)。Cursor 等远程直连模式暂不支持实时录音。</Callout>\n\n <p className=\"font-semibold mt-4\">需要按顺序调用三个工具:</p>\n\n <div className=\"mt-3 space-y-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">create_stream_session</code> — 创建流式评测会话</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.word.score、en.sent.score、cn.sent.raw' },\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式,默认 mp3' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100,默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code>,后续步骤需使用。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">start_recording</code> — 开始麦克风录音</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '来自 create_stream_session 的会话 ID' },\n { name: 'chunk_size', type: 'number', required: false, desc: '音频块大小(bytes),默认 3200' },\n { name: 'chunk_interval_ms', type: 'number', required: false, desc: '推送间隔(ms),默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">调用后自动启动麦克风,音频通过 WebSocket 实时推送到评测服务。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">stop_recording</code> — 停止录音并获取结果</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '会话 ID' },\n { name: 'timeout', type: 'number', required: false, desc: '等待评测结果超时秒数,默认 30' },\n ]} />\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"stream-flow\" title=\"流式评测完整流程\">\n <div className=\"rounded-lg border border-border/60 p-5 bg-muted/10 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong>用户:</strong>&quot;请评测我的英文发音,文本是 Hello&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">create_stream_session(core_type=&quot;en.word.score&quot;, ref_text=&quot;Hello&quot;)</code></p>\n <p className=\"text-muted-foreground\">→ 获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">session_id</code></p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p>AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">start_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground mt-1\">麦克风开始录音,音频通过 WebSocket 实时推送到评测服务</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>用户</strong>说完后告知 AI &quot;结束&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">stop_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground\">→ 返回评测结果(总分、准确度、流利度、音素得分等)</p>\n </FlowStep>\n </div>\n </SubDoc>\n\n <SubDoc id=\"file-eval\" title=\"音频文件评测\">\n <p>适用于已有音频文件的评测场景,支持三种输入方式:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">适用场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_file_path</td><td className=\"py-2 px-3\">本地文件路径</td><td className=\"py-2 px-3 text-muted-foreground\">代理自动读取并转为 Base64,最便捷</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_base64</td><td className=\"py-2 px-3\">Base64 编码数据</td><td className=\"py-2 px-3 text-muted-foreground\">已有编码数据时使用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_url</td><td className=\"py-2 px-3\">音频文件 URL</td><td className=\"py-2 px-3 text-muted-foreground\">音频托管在云存储时使用</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"tip\">推荐使用 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">audio_file_path</code>,只需传入本地文件路径,代理会自动处理编码和上传。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"tools-en\" title=\"英文评测工具(10 种)\">\n <ToolTable tools={[\n ['en_word_eval', '单词评测', '评测 \"Hello\" 的发音准确度'],\n ['en_word_correction', '单词纠音', '检测多读/漏读/错读并给出纠正建议'],\n ['en_vocab_eval', '词语评测', '同时评测多个单词的发音'],\n ['en_sentence_eval', '句子评测', '评测整句朗读的准确度与流利度'],\n ['en_sentence_correction', '句子纠音', '逐词检测发音问题并给出修正建议'],\n ['en_paragraph_eval', '段落朗读评测', '评测整段英文课文的朗读质量'],\n ['en_phonics_eval', '自然拼读评测', '评测 Phonics 拼读规则掌握程度'],\n ['en_choice_eval', '口语选择题', '从预设选项中识别口语回答'],\n ['en_semi_open_eval', '半开放题评测', '场景对话等半开放口语题型'],\n ['en_realtime_eval', '实时朗读评测', '实时反馈朗读质量,逐句评分'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong>所有英文评测工具均支持以下参数。</p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tools-cn\" title=\"中文评测工具(6 种)\">\n <ToolTable tools={[\n ['cn_word_raw_eval', '单字评测(汉字)', '评测 \"中\" 的发音'],\n ['cn_word_pinyin_eval', '单字评测(拼音)', '评测 \"zhōng\" 的发音'],\n ['cn_sentence_eval', '词句评测', '评测 \"你好世界\" 的朗读'],\n ['cn_paragraph_eval', '段落朗读评测', '评测一段中文课文的朗读质量'],\n ['cn_rec_eval', '有限分支识别评测', '从预设选项中识别用户发音'],\n ['cn_aitalk_eval', 'AI Talk 口语评测', '中文口语对话能力评测'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong></p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本(汉字或拼音)' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结构化结果:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"integrity\": 95,\n \"details\": [\n {\n \"char\": \"hello\",\n \"score\": 85,\n \"phone\": [\n { \"phoneme\": \"h\", \"score\": 90 },\n { \"phoneme\": \"ɛ\", \"score\": 82 },\n { \"phoneme\": \"l\", \"score\": 88 },\n { \"phoneme\": \"oʊ\", \"score\": 80 }\n ]\n }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall', '0–100', '综合总分,反映整体语音质量'],\n ['accuracy', '0–100', '准确度,音素级别的发音准确率'],\n ['pron', '0–100', '发音得分,整体发音质量评价'],\n ['fluency.overall', '0–100', '流利度,语流的自然度与平滑度'],\n ['fluency.speed', '0–100', '语速得分,每分钟词/音节计量'],\n ['fluency.pause', 'number', '不自然停顿次数'],\n ['integrity', '0–100', '完整度,是否完整朗读了参考文本'],\n ['details[].phone[]', 'array', '每个音素的独立评分,可定位具体弱项'],\n ['details[].stress[]', 'array', '重音得分,检测重弱读是否正确'],\n ].map(([field, range, desc]) => (\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期', '音频上传后 5 分钟内有效,评测完成后自动删除', '重新上传音频'],\n ['服务器繁忙', '当前请求过多,排队已满', '稍后重试,使用指数退避策略'],\n ['存储空间已满', '服务器临时音频存储已满', '稍后重试,系统会自动清理'],\n ['音频格式不支持', '上传了不支持的文件类型', '使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限', '超过该题型最大时长限制', '裁剪音频后重试'],\n ['WebSocket 连接断开', '网络不稳定导致流式连接中断', '代理会自动重连,如持续失败请检查网络'],\n ].map(([scene, reason, fix]) => (\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 架构说明 ══════ */}\n <DocSection id=\"architecture\" icon={Terminal} title=\"架构说明\">\n\n <SubDoc id=\"arch-diagram\" title=\"系统架构\">\n <div className=\"flex flex-wrap items-center justify-center gap-4 my-6\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Claude Desktop<br/>Claude Code<br/>Cursor</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">stdio</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-1\">chivox-local-mcp</p>\n <p className=\"text-[11px] text-muted-foreground\">本地代理<br/>SoX 录音</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP / WS</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">Remote Chivox</p>\n <p className=\"text-[11px] text-muted-foreground\">MCP Server<br/>评测引擎</p>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"transport\" title=\"传输协议\">\n <ul className=\"space-y-3\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>stdio</strong> — AI 客户端通过标准输入输出与本地代理通信,这是 MCP 的标准传输方式</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>HTTP</strong> — 音频文件评测工具通过 HTTP 代理到远程服务,适用于非流式场景</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>WebSocket</strong> — 实时录音音频通过 WebSocket 流式推送,支持断线自动重连</span>\n </li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 示例代码 ══════ */}\n <DocSection id=\"integration\" icon={Code2} title=\"示例代码\">\n\n <SubDoc id=\"code-python\" title=\"Python 上传音频\">\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://speech-eval.site/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"code-javascript\" title=\"JavaScript 上传音频\">\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst res = await fetch('https://speech-eval.site/upload', {\n method: 'POST',\n headers: { 'Content-Type': 'audio/mp3' },\n body: audioData,\n});\n\nconst result = await res.json();\nconsole.log('audioId:', result.audioId);`}</CodeBlock>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p>以下模板帮助你引导 LLM 对评测结果进行深度分析:</p>\n <div className=\"space-y-4 mt-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><MessageSquare className=\"h-4 w-4\" /> 发音诊断</p>\n <CodeBlock filename=\"prompt-diagnosis.txt\">{`你是一位专业的英语口语教练。请根据以下 MCP 评测结果分析学生的发音表现:\n\n评测结果:{evaluation_result}\n\n请按以下格式输出:\n1. 总体评价(一句话)\n2. 优势项(>80 分的指标)\n3. 弱项分析(<70 分的指标,定位到具体音素)\n4. 针对性练习建议(2-3 条可操作的建议)`}</CodeBlock>\n </div>\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><Mic className=\"h-4 w-4\" /> 练习生成</p>\n <CodeBlock filename=\"prompt-practice.txt\">{`根据以下弱项列表,为学生生成针对性的口语练习材料:\n\n弱项:{weak_points}\n\n请生成:\n1. 3 个绕口令(专门针对弱项音素)\n2. 5 个包含目标音素的常用短句(标注重音和连读)\n3. 1 段跟读练习段落(50-80 词)`}</CodeBlock>\n </div>\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><BarChart3 className=\"h-4 w-4\" /> 学习报告</p>\n <CodeBlock filename=\"prompt-report.txt\">{`请根据学生近 7 天的评测历史生成学习报告:\n\n评测历史:{history}\n\n报告要求:\n1. 整体趋势(进步/持平/退步)\n2. 各维度变化(准确度、流利度、语速)\n3. 本周最大进步项\n4. 下周重点练习建议\n5. 鼓励性总结`}</CodeBlock>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频过期:</strong>上传后 5 分钟内有效。建议在上传后 3 分钟内完成评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>服务繁忙:</strong>使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>录音中断:</strong>WebSocket 断线后代理会自动重连。如持续失败,检查网络连接和 SoX 安装。</span></li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>优先使用流式评测:</strong>实时录音模式跳过了文件上传环节,端到端延迟比文件评测低 30-50%。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频压缩:</strong>文件评测时,上传前将音频转为 16kHz 单声道 mp3,可减少 60-80% 的上传体积。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>选择合适的工具:</strong>单词评测比段落评测快 3-5 倍。如只需单词级分数,不要用句子评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>本地文件路径最便捷:</strong>使用 <code className=\"bg-muted px-1 rounded text-xs\">audio_file_path</code> 参数传入本地路径,代理自动处理编码,无需手动转 Base64。</span></li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Globe} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP JSON-RPC 接口(Streamable HTTP)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制', '500MB', '最大音频存储空间'],\n ['最大排队', '10', '等待处理的请求数'],\n ['最大并发', '3', '同时处理的评测数'],\n ['音频有效期', '5 分钟', '上传后未评测自动过期'],\n ['文件大小限制', '50MB', '单个音频文件最大'],\n ].map(([k, v, d]) => (\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <div className=\"space-y-3\">\n {[\n { ver: 'v3.0.0', date: '2026-04-16', desc: '新增本地代理模式,支持实时录音流式评测,工具扩展至 16 种' },\n { ver: 'v2.3.0', date: '2026-03-25', desc: '支持 HTTPS,域名 speech-eval.site' },\n { ver: 'v2.2.0', date: '2026-03-24', desc: '添加 MCP 配置指南(Cursor / Claude Desktop)' },\n { ver: 'v2.1.0', date: '2026-03-24', desc: '简化上传流程,统一使用 HTTP API' },\n { ver: 'v2.0.0', date: '2026-03-24', desc: '初始版本,支持多种评测类型' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </div>\n </div>\n </div>\n </main>\n );\n}\n"}, "6b999e0c93041f270d4893d5f99ccb51cdfdbc00": {"t": 1785765809.9466977, "n": 67953, "h": "56162107c7a05525", "c": "'use client';\n\nimport { useState, useEffect, useMemo } from 'react';\nimport { Link } from '@/i18n/routing';\nimport { ArrowLeft, BookOpen, Code2, Zap, FileText, Copy, Check, Terminal, Globe, Mic, MessageSquare, BarChart3, AlertTriangle, Lightbulb, Radio, FolderOpen, Settings, Workflow, Sparkles } from 'lucide-react';\n\n/* ─── Reusable Components ─── */\n\nfunction CopyButton({ text }: { text: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <button\n onClick={() => { navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); }}\n className=\"absolute top-3 right-3 p-1.5 rounded-md bg-white/5 hover:bg-white/10 transition-colors text-zinc-400 hover:text-zinc-200\"\n >\n {copied ? <Check className=\"h-3.5 w-3.5\" /> : <Copy className=\"h-3.5 w-3.5\" />}\n </button>\n );\n}\n\nfunction CodeBlock({ filename, lang, children }: { filename?: string; lang?: string; children: string }) {\n return (\n <div className=\"rounded-lg border border-border/60 bg-zinc-950 text-zinc-100 overflow-hidden my-4 relative\">\n {filename && (\n <div className=\"flex items-center gap-1.5 px-4 py-2.5 border-b border-white/[0.06] text-xs text-zinc-500 font-mono\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-red-500/60\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-yellow-500/60\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-green-500/60\" />\n <span className=\"ml-2\">{filename}</span>\n {lang && <span className=\"ml-auto text-zinc-600\">{lang}</span>}\n </div>\n )}\n <CopyButton text={children.trim()} />\n <pre className=\"p-4 text-sm font-mono leading-relaxed overflow-x-auto\"><code>{children.trim()}</code></pre>\n </div>\n );\n}\n\nfunction DocSection({ id, icon: Icon, title, children }: { id: string; icon: React.ElementType; title: string; children: React.ReactNode }) {\n return (\n <section id={id} className=\"scroll-mt-24 mb-16\">\n <div className=\"flex items-center gap-3 mb-6 pb-3 border-b border-border/40\">\n <Icon className=\"h-5 w-5 text-muted-foreground shrink-0\" />\n <h2 className=\"text-xl font-bold tracking-tight\">{title}</h2>\n </div>\n <div className=\"text-sm leading-relaxed text-foreground/90 space-y-6\">{children}</div>\n </section>\n );\n}\n\nfunction SubDoc({ id, title, children }: { id: string; title: string; children: React.ReactNode }) {\n return (\n <div id={id} className=\"scroll-mt-24 mb-10\">\n <h3 className=\"text-base font-semibold mb-3\">{title}</h3>\n <div className=\"space-y-4\">{children}</div>\n </div>\n );\n}\n\nfunction ParamTable({ params }: { params: { name: string; type: string; required: boolean; desc: string }[] }) {\n return (\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">类型</th>\n <th className=\"text-left py-2 px-3 font-medium\">必填</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {params.map(p => (\n <tr key={p.name}>\n <td className=\"py-2 px-3 font-mono text-xs\">{p.name}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{p.type}</td>\n <td className=\"py-2 px-3\">{p.required ? <span className=\"text-emerald-600 dark:text-emerald-400\">✓</span> : <span className=\"text-muted-foreground\">—</span>}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{p.desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction Callout({ type = 'info', children }: { type?: 'info' | 'warning' | 'tip'; children: React.ReactNode }) {\n const styles = {\n info: 'border-blue-200 bg-blue-50 text-blue-900 dark:border-blue-900 dark:bg-blue-950/30 dark:text-blue-200',\n warning: 'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950/30 dark:text-amber-200',\n tip: 'border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-900 dark:bg-emerald-950/30 dark:text-emerald-200',\n };\n const icons = { info: Globe, warning: AlertTriangle, tip: Lightbulb };\n const Ic = icons[type];\n return (\n <div className={`flex gap-3 rounded-lg border p-4 text-sm ${styles[type]}`}>\n <Ic className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div>{children}</div>\n </div>\n );\n}\n\nfunction FlowStep({ title, children }: { title: string; children: React.ReactNode }) {\n return (\n <div className=\"flex gap-4 items-start\">\n <div className=\"h-7 w-7 rounded-full bg-foreground text-background flex items-center justify-center text-xs font-bold shrink-0 mt-0.5\">{title}</div>\n <div className=\"flex-1 text-sm\">{children}</div>\n </div>\n );\n}\n\nfunction ToolTable({ tools }: { tools: [string, string, string][] }) {\n return (\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">工具名</th>\n <th className=\"text-left py-2 px-3 font-medium\">功能</th>\n <th className=\"text-left py-2 px-3 font-medium\">典型场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {tools.map(([name, desc, scene]) => (\n <tr key={name}>\n <td className=\"py-2 px-3 font-mono text-xs\">{name}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{scene}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\n/* ─── Navigation Structure ─── */\n\nconst NAV = [\n { id: 'concept', icon: Workflow, label: 'MCP 工作原理', children: [\n { id: 'why-mcp', label: '为什么需要 MCP' },\n { id: 'scenario-walkthrough', label: '场景走查 · 6 步' },\n { id: 'dev-responsibility', label: '开发者只做两件事' },\n { id: 'integration-paths', label: '三种接入姿势' },\n ]},\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'requirements', label: '系统要求' },\n { id: 'install', label: '安装方式' },\n { id: 'env-vars', label: '环境变量' },\n ]},\n { id: 'config', icon: Settings, label: '接入配置', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-coze', label: '扣子(Coze)' },\n { id: 'config-coze-workflow', label: 'Coze 工作流接入' },\n { id: 'config-other', label: '豆包 / 钉钉 / 其他' },\n ]},\n { id: 'eval-modes', icon: Radio, label: '评测模式', children: [\n { id: 'stream-eval', label: '实时录音评测' },\n { id: 'stream-flow', label: '流式评测流程' },\n { id: 'file-eval', label: '音频文件评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'tools-en', label: '英文评测工具' },\n { id: 'tools-cn', label: '中文评测工具' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'architecture', icon: Terminal, label: '架构说明', children: [\n { id: 'arch-diagram', label: '架构图' },\n { id: 'transport', label: '传输协议' },\n ]},\n { id: 'integration', icon: Code2, label: '示例代码', children: [\n { id: 'code-python', label: 'Python' },\n { id: 'code-javascript', label: 'JavaScript' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n { id: 'service-info', icon: Globe, label: '服务信息', children: [\n { id: 'endpoints', label: '服务端点' },\n { id: 'limits', label: '服务限制' },\n { id: 'changelog', label: '更新日志' },\n ]},\n];\n\n/* ─── Page ─── */\n\nexport default function DocsPage() {\n const [activeId, setActiveId] = useState<string>('quick-start');\n\n // 收集所有可滚动的锚点 id(包含分组和子章节),用于 scroll-spy\n const allIds = useMemo(() => {\n const ids: string[] = [];\n NAV.forEach(g => {\n ids.push(g.id);\n g.children.forEach(c => ids.push(c.id));\n });\n return ids;\n }, []);\n\n useEffect(() => {\n const elements = allIds\n .map(id => document.getElementById(id))\n .filter((el): el is HTMLElement => el !== null);\n\n if (elements.length === 0) return;\n\n // 以顶部偏移 120px 作为\"激活线\",选取距离激活线最近的上方/同位章节\n const computeActive = () => {\n const line = 120;\n let currentId = elements[0]?.id ?? '';\n for (const el of elements) {\n const top = el.getBoundingClientRect().top;\n if (top - line <= 0) {\n currentId = el.id;\n } else {\n break;\n }\n }\n // 滚到底部时,强制高亮最后一项\n if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 4) {\n currentId = elements[elements.length - 1].id;\n }\n setActiveId(prev => (prev === currentId ? prev : currentId));\n };\n\n computeActive();\n window.addEventListener('scroll', computeActive, { passive: true });\n window.addEventListener('resize', computeActive);\n return () => {\n window.removeEventListener('scroll', computeActive);\n window.removeEventListener('resize', computeActive);\n };\n }, [allIds]);\n\n // 根据当前激活的子节点,找出其所属分组 id,用于同步高亮父级\n const activeGroupId = useMemo(() => {\n for (const g of NAV) {\n if (g.id === activeId) return g.id;\n if (g.children.some(c => c.id === activeId)) return g.id;\n }\n return activeId;\n }, [activeId]);\n\n return (\n <main className=\"flex-1\">\n <div className=\"container mx-auto px-6 py-12 md:py-20\">\n <Link href=\"/\" className=\"inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors mb-8\">\n <ArrowLeft className=\"h-4 w-4\" /> 返回首页\n </Link>\n\n <h1 className=\"text-3xl md:text-4xl font-bold tracking-tight mb-2\">开发者文档</h1>\n <p className=\"text-muted-foreground mb-10\">驰声语音评测 MCP · chivox-local-mcp</p>\n\n <div className=\"flex gap-10\">\n {/* Sidebar */}\n <nav className=\"hidden lg:block w-56 shrink-0 sticky top-24 self-start max-h-[calc(100vh-8rem)] overflow-y-auto\">\n {NAV.map(group => {\n const isGroupActive = activeGroupId === group.id;\n return (\n <div key={group.id} className=\"mb-5\">\n <a\n href={`#${group.id}`}\n onClick={() => setActiveId(group.id)}\n className={`flex items-center gap-2 text-sm font-semibold mb-1.5 transition-colors ${\n isGroupActive ? 'text-foreground' : 'text-foreground/70 hover:text-foreground'\n }`}\n >\n <group.icon className={`h-4 w-4 ${isGroupActive ? 'text-foreground' : 'text-muted-foreground'}`} />\n {group.label}\n </a>\n <ul className=\"ml-6 space-y-1 border-l border-border/40 pl-3 relative\">\n {group.children.map(child => {\n const isActive = activeId === child.id;\n return (\n <li key={child.id} className=\"relative\">\n {isActive && (\n <span className=\"absolute -left-[13px] top-0 bottom-0 w-0.5 bg-foreground rounded-full\" />\n )}\n <a\n href={`#${child.id}`}\n onClick={() => setActiveId(child.id)}\n className={`text-xs block py-0.5 truncate transition-colors ${\n isActive\n ? 'text-foreground font-medium'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {child.label}\n </a>\n </li>\n );\n })}\n </ul>\n </div>\n );\n })}\n </nav>\n\n {/* Content */}\n <div className=\"flex-1 min-w-0 max-w-3xl\">\n\n {/* ══════ MCP 工作原理 ══════ */}\n <DocSection id=\"concept\" icon={Workflow} title=\"MCP 工作原理\">\n\n <SubDoc id=\"why-mcp\" title=\"为什么需要 MCP\">\n <p>\n 大模型(GPT / Claude / Gemini / 通义千问 等)本质上是<strong>文字大脑</strong>:它擅长读和写,但天生<strong>听不到</strong>音频、<strong>打不开</strong>你的数据库、<strong>调不通</strong>第三方 API。\n </p>\n <p>\n <strong>MCP(Model Context Protocol)</strong>是一套由 Anthropic 主导、业界共同推进的开放协议,它像一根\"对讲机\":让大模型可以在需要时主动去调用外部能力(工具、数据源、服务),然后把结果接回来继续对话。\n </p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <AlertTriangle className=\"h-4 w-4 text-amber-600 dark:text-amber-400\" />\n <span className=\"text-sm font-semibold\">没有 MCP 的做法</span>\n </div>\n <ul className=\"text-xs text-muted-foreground space-y-1.5 leading-relaxed\">\n <li>· 研究评测服务的 REST 文档</li>\n <li>· 自己写鉴权 / 上传 / 轮询代码</li>\n <li>· 把结果序列化后塞进 prompt</li>\n <li>· 每换一个大模型都得重写一遍</li>\n </ul>\n </div>\n <div className=\"rounded-lg border-2 border-foreground/60 p-4 bg-foreground/[0.02]\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Check className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">用驰声 MCP 之后</span>\n </div>\n <ul className=\"text-xs space-y-1.5 leading-relaxed\">\n <li>· 填一行 MCP Server 地址</li>\n <li>· 大模型自动发现&amp;调用评测工具</li>\n <li>· 换任何支持 MCP 的大模型都能用</li>\n <li>· 不写一行调用代码</li>\n </ul>\n </div>\n </div>\n <Callout type=\"info\">\n MCP 让\"接入语音评测\"从一个\"后端工程\"变成了一个\"配置项\"。这也是为什么 Cursor、Claude Desktop、Dify、Coze、字节豆包、飞书智能助手都已经原生支持 MCP。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"scenario-walkthrough\" title=\"场景走查 · 从一段录音到温柔反馈\">\n <p>\n 想象一个具体场景:一位开发者用 Claude 做了一款 <strong>「AI 雅思口语陪练」小程序</strong>,接入驰声语音评测 MCP。当用户发来一段录音,整个链路是这样的:\n </p>\n <div className=\"rounded-lg border border-border/60 bg-muted/20 p-5 my-4 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong className=\"text-foreground\">配置阶段</strong>(开发者 · 一次性)</p>\n <p className=\"text-muted-foreground mt-1\">在 AI 后端的配置文件里填一行驰声 MCP Server 地址。大模型启动时自动发现:「我多了一个叫<strong>语音评测</strong>的工具」。</p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p><strong className=\"text-foreground\">接收用户指令</strong>(终端用户)</p>\n <p className=\"text-muted-foreground mt-1\">小程序用户发来一段自我介绍录音:「帮我听听,发音哪里有问题?」后端把「文字 + 录音链接」打包给大模型。</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong className=\"text-foreground\">大模型自主思考</strong>(LLM · 最核心的魔法)</p>\n <div className=\"text-muted-foreground mt-1 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed\">\n 「用户让我听发音。」<br />\n 「但我只是文字大脑,我听不到。」<br />\n 「等等 —— 开发者给了我一个叫 <code className=\"bg-muted px-1 rounded\">chivox_voice_eval</code> 的 MCP 工具!」\n </div>\n </FlowStep>\n <FlowStep title=\"4\">\n <p><strong className=\"text-foreground\">MCP 协议 · 自动调用</strong>(LLM → 驰声)</p>\n <p className=\"text-muted-foreground mt-1\">大模型自动按 MCP 标准构造 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools/call</code> 请求,把录音链接和参考文本发到驰声服务。<span className=\"text-foreground\">开发者不需要写任何调用代码。</span></p>\n </FlowStep>\n <FlowStep title=\"5\">\n <p><strong className=\"text-foreground\">驰声服务处理</strong>(评测引擎)</p>\n <p className=\"text-muted-foreground mt-1\">驰声对音频进行考试级打分,把结构化结果通过 MCP 原路返回:<code className=\"bg-muted px-1 rounded text-xs font-mono\">overall 85</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">/θ/ 音发音错误</code>、<code className=\"bg-muted px-1 rounded text-xs font-mono\">流利度 88</code>。</p>\n </FlowStep>\n <FlowStep title=\"6\">\n <p><strong className=\"text-foreground\">大模型整合并输出反馈</strong>(LLM)</p>\n <p className=\"text-muted-foreground mt-1\">大模型把冷冰冰的分数翻译成温柔反馈:</p>\n <div className=\"text-foreground/80 mt-2 rounded-md bg-background/80 border border-border/40 px-3 py-2 text-xs leading-relaxed italic\">\n 「你的自我介绍很棒!综合 85 分 🎉 不过 <strong>think</strong> 这个词的 /θ/ 咬舌音不太标准,我们一起来练一下……」\n </div>\n </FlowStep>\n </div>\n <Callout type=\"tip\">\n <strong>整条链路里,MCP 只出现在 ① 和 ④</strong>:① 让开发者一次性告诉大模型「你多了一个工具」,④ 让大模型可以自动调用它。这两步的标准化,就是 MCP 的全部价值。\n </Callout>\n </SubDoc>\n\n <SubDoc id=\"dev-responsibility\" title=\"开发者只做两件事\">\n <p>听起来复杂,但对开发者来说只需要两件事:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">1</span>\n <span className=\"text-sm font-semibold\">填一次 MCP 配置</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 在 AI 后端的配置里填入驰声 MCP Server 地址和 API Key,就这一次,以后新加评测能力完全免开发。\n </p>\n <CodeBlock filename=\"mcp-config.json\" lang=\"json\">{`{\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"apiKey\": \"sk-••••••••••\"\n }\n}`}</CodeBlock>\n </div>\n <div className=\"rounded-lg border border-border/60 p-5\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"h-6 w-6 rounded-full bg-foreground text-background text-xs font-bold flex items-center justify-center\">2</span>\n <span className=\"text-sm font-semibold\">写一段 System Prompt</span>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 告诉大模型这个工具是做什么的、什么时候调用、结果怎么转成用户友好的反馈。\n </p>\n <CodeBlock filename=\"system-prompt.md\">{`你是一位英语口语教练。\n当用户发来录音时:\n1. 调用 chivox_voice_eval 评分\n2. 聚焦 <70 分的音素家族\n3. 用高情商话术给出练习建议`}</CodeBlock>\n </div>\n </div>\n <p className=\"text-muted-foreground\">\n 其余所有事情 —— 音频上传、鉴权、工具发现、调用时机、返回格式、结果整合 —— 都由 MCP 协议和大模型自己搞定。\n </p>\n </SubDoc>\n\n <SubDoc id=\"integration-paths\" title=\"三种典型接入姿势\">\n <p>根据你的技术栈,有三种等价的接入方式,底层都走完全相同的 MCP 协议:</p>\n\n {/* ── 姿势一:可视化 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Sparkles className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势一 · 可视化拖拽(扣子 / Coze / 飞书智能助手)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合产品经理、运营、不写后端代码的开发者。整个过程像在手机上装 App。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>找到入口</strong>:在扣子平台的 <strong>「插件 / 工具」</strong> 配置区,点击<strong>「添加 MCP 服务」</strong>。</li>\n <li><strong>填两个字段</strong>(这也是你未来会从驰声这边拿到的):\n <div className=\"mt-2 overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium w-32\">服务器地址</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">鉴权密钥</td><td className=\"py-2 px-3 font-mono text-xs\">sk-•••••••••• <span className=\"text-muted-foreground ml-2\">(由驰声分配,用于计费与识别)</span></td></tr>\n </tbody>\n </table>\n </div>\n </li>\n <li><strong>平台自动发现能力</strong>:扣子会访问你填的地址,读取 MCP Server 自描述的\"说明书\"。瞬间明白:「这是个语音评测工具,需要传音频,会返发音分」。</li>\n <li><strong>右侧测试</strong>:在平台的聊天框里发一段语音,大模型就会自动调用工具。<strong>整个过程不需要写一行代码。</strong></li>\n </ol>\n </div>\n\n {/* ── 姿势二:代码框架 ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Code2 className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势二 · 代码框架配置(Dify / LangChain / 自研后端)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合做独立 APP / 小程序、用 Dify 或 LangChain 写 Agent 的极客开发者。核心就是往配置文件里加一段 JSON。\n </p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1 text-sm\">\n <li><strong>打开配置文件</strong>(项目根目录下的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcp.json</code> 或框架指定的配置路径)。</li>\n <li><strong>在 <code className=\"bg-muted px-1 rounded text-xs font-mono\">mcpServers</code> 下增加一个节点</strong>:\n <CodeBlock filename=\"mcp.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox_voice_eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\",\n \"env\": {\n \"API_KEY\": \"你的专属密钥\"\n }\n }\n }\n}`}</CodeBlock>\n </li>\n <li><strong>重启后端服务</strong>。大模型的底层调度器会在启动时自动连上驰声 MCP Server,把 16 种评测工具注册成<strong>随时待命</strong>的 tools。</li>\n <li><strong>触发调用</strong>:用户发来录音后,大模型看到 system prompt 的引导,自动选择合适的工具(单词/句子/段落/半开放)调用。</li>\n </ol>\n </div>\n\n {/* ── 姿势三:Function Calling 直连(分 A / B 两种做法) ── */}\n <div className=\"rounded-lg border border-border/60 p-5 mt-3\">\n <div className=\"flex items-center gap-2 mb-3\">\n <Terminal className=\"h-4 w-4 text-foreground\" />\n <h4 className=\"text-sm font-semibold\">姿势三 · 原生 Function Calling(豆包 / DeepSeek / OpenAI 兼容 API)</h4>\n </div>\n <p className=\"text-xs text-muted-foreground leading-relaxed mb-3\">\n 适合不用任何 Agent 框架、直接调用 LLM API 的后端开发者。豆包 1.5+ / DeepSeek / GPT / Qwen 等模型都走标准 <code className=\"bg-muted px-1 rounded text-xs font-mono\">chat.completions</code> 的 <code className=\"bg-muted px-1 rounded text-xs font-mono\">tools</code> 参数。这里又分 A / B 两种做法,<strong>B 才是生产推荐</strong>。\n </p>\n\n <Callout type=\"warning\">\n 常见疑问:<em>\"要不要手写一个 tool 定义?\"</em><br />\n — <strong>姿势一 / 二不用</strong>(Coze、Dify、火山方舟控制台、Claude Desktop 会自动从 MCP Server 拉工具列表)。<br />\n — <strong>姿势三看你选 A 还是 B</strong>:A 要手写,B 用 MCP 客户端库自动拉。\n </Callout>\n\n {/* ── 3-A:手写 tool schema ── */}\n <div className=\"mt-4 rounded-md border border-border/40 bg-muted/20 p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold\">3-A · 手写 tool schema(最快验证,不推荐生产)</p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400 border border-amber-500/20\">快速 Demo</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 自己写一段 JSON Schema 告诉豆包\"有个工具叫 xxx,参数长这样\",豆包返回 tool_call 后你再手动去调驰声 MCP。<strong>缺点</strong>:驰声有 16 个评测工具,你每加一个题型都要改代码。\n </p>\n <CodeBlock filename=\"3a_hardcode_tool.py\" lang=\"python\">{`import os, json\nfrom openai import OpenAI\n\nclient = OpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv('ARK_API_KEY'),\n)\n\n# ─── 手写一段 tool schema(不推荐:16 个工具就要写 16 段)───\ntools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"en_word_eval\",\n \"description\": \"英语单词发音评测。当用户让你评价英语发音并提供录音链接时调用。\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"audio_url\": {\"type\": \"string\", \"description\": \"录音 URL\"},\n \"reference_text\": {\"type\": \"string\", \"description\": \"参考文本,如 Apple\"},\n },\n \"required\": [\"audio_url\", \"reference_text\"]\n }\n }\n}]\n\nresp = client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,拿到评测结果后温柔地反馈,不要直接报分数。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n)\n\nmsg = resp.choices[0].message\nif msg.tool_calls:\n args = json.loads(msg.tool_calls[0].function.arguments)\n # ↓↓↓ 这里手动调驰声 MCP,再把结果回填给豆包做第二趟 ↓↓↓\n # result = requests.post(\"https://speech-eval.site/mcp\", json={...})`}</CodeBlock>\n </div>\n\n {/* ── 3-B:MCP 自动发现 ── */}\n <div className=\"mt-3 rounded-md border border-foreground/30 bg-foreground/[0.03] dark:bg-white/[0.03] p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"text-xs font-semibold flex items-center gap-1.5\">\n 3-B · 用 MCP 客户端库自动发现 <Sparkles className=\"h-3 w-3 text-foreground\" />\n </p>\n <span className=\"text-[10px] px-2 py-0.5 rounded-full bg-foreground text-background\">生产推荐</span>\n </div>\n <p className=\"text-[11px] text-muted-foreground leading-relaxed mb-3\">\n 启动时连上驰声 MCP Server,调一次 <code className=\"bg-muted px-1 rounded\">tools/list</code>,把 16 个评测工具自动转成 Function Calling 的 tools 格式。<strong>驰声加新工具,你代码不用改</strong>。这才是 MCP 协议的真正价值。\n </p>\n <CodeBlock filename=\"3b_mcp_autodiscover.py\" lang=\"python\">{`import asyncio, os\nfrom openai import AsyncOpenAI\nfrom mcp import ClientSession\nfrom mcp.client.streamable_http import streamablehttp_client\n\nclient = AsyncOpenAI(\n base_url=\"https://ark.cn-beijing.volces.com/api/v3\",\n api_key=os.getenv(\"ARK_API_KEY\"),\n)\n\nasync def main():\n # ─── 1. 连驰声 MCP Server,自动拉取全部 16 个工具 ───\n async with streamablehttp_client(\n \"https://speech-eval.site/mcp\",\n headers={\"Authorization\": f\"Bearer {os.getenv('CHIVOX_KEY')}\"},\n ) as (read, write, _):\n async with ClientSession(read, write) as session:\n await session.initialize()\n mcp_tools = (await session.list_tools()).tools # ← 一行拿到全部工具\n\n # ─── 2. MCP schema → OpenAI Function Calling schema ───\n tools = [{\n \"type\": \"function\",\n \"function\": {\n \"name\": t.name,\n \"description\": t.description,\n \"parameters\": t.inputSchema, # MCP 直接给 JSON Schema\n }\n } for t in mcp_tools]\n\n # ─── 3. 正常调豆包,tools 由 MCP 动态注入 ───\n resp = await client.chat.completions.create(\n model=\"ep-xxx-doubao-tools\",\n messages=[\n {\"role\": \"system\", \"content\": \"你是英语老师,收到评测结果后温柔反馈。\"},\n {\"role\": \"user\", \"content\": \"老师,我读了 Apple,录音 https://demo.com/u1.mp3\"},\n ],\n tools=tools,\n tool_choice=\"auto\",\n )\n\n # ─── 4. 折返跑:豆包选好工具后,直接走 MCP 调驰声 ───\n msg = resp.choices[0].message\n if msg.tool_calls:\n call = msg.tool_calls[0]\n result = await session.call_tool(\n call.function.name,\n arguments=__import__(\"json\").loads(call.function.arguments),\n )\n # 把 result 回填给豆包做第二趟 chat.completions 即可\n\nasyncio.run(main())`}</CodeBlock>\n <p className=\"text-[11px] text-muted-foreground mt-3 leading-relaxed\">\n 想要更省心?直接用 <a href=\"https://github.com/modelcontextprotocol/python-sdk\" target=\"_blank\" rel=\"noreferrer\" className=\"text-foreground underline underline-offset-2\">官方 mcp python sdk</a>、<code className=\"bg-muted px-1 rounded text-xs\">mcp-use</code>、<code className=\"bg-muted px-1 rounded text-xs\">openai-agents</code>,或火山官方 <code className=\"bg-muted px-1 rounded text-xs\">Arkitect</code> 高代码 SDK,它们都把上面这段\"MCP → Function Calling\"的桥接封装好了。\n </p>\n </div>\n </div>\n\n <Callout type=\"info\">\n 三种姿势走的是完全相同的 MCP 协议,能力完全等价。选型建议:<strong>产品经理 / 原型验证用姿势一</strong>(Coze 填表单);<strong>AI 客户端用户用姿势二</strong>(Cursor / Claude Desktop 填 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">mcp.json</code>);<strong>后端生产服务用姿势三-B</strong>(MCP 客户端库自动发现工具,不要手写 tool schema)。\n </Callout>\n </SubDoc>\n\n </DocSection>\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-local-mcp</strong> 是驰声语音评测的 MCP 本地代理,通过 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 将远程驰声评测服务桥接到本地,让 AI 助手(Claude Desktop、Claude Code、Cursor 等)直接调用语音评测能力。</p>\n <p>它支持两种评测模式,覆盖从即时交互到批量处理的全部场景:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Mic className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">实时录音评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">通过麦克风边录边评,音频以 WebSocket 流式推送,无需生成中间文件,延迟更低、体验更佳。</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <FolderOpen className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">音频文件评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">支持本地文件路径、Base64 编码、URL 三种输入,适合已有音频的批量评测场景。</p>\n </div>\n </div>\n <Callout type=\"info\">当前支持 <strong>16 种评测工具</strong>:英文 10 种(单词、句子、段落、纠音、自然拼读、口语选择、半开放题等)+ 中文 6 种(汉字、拼音、词句、段落、有限分支、AI Talk)。</Callout>\n </SubDoc>\n\n <SubDoc id=\"requirements\" title=\"系统要求\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">依赖</th>\n <th className=\"text-left py-2 px-3 font-medium\">版本</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-semibold\">Node.js</td><td className=\"py-2 px-3 font-mono text-xs\">{'>'}= 18</td><td className=\"py-2 px-3 text-muted-foreground\">运行时必须</td></tr>\n <tr><td className=\"py-2 px-3 font-semibold\">SoX</td><td className=\"py-2 px-3 font-mono text-xs\">any</td><td className=\"py-2 px-3 text-muted-foreground\">仅实时录音功能需要(处理麦克风音频流)</td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"mt-3\">安装 SoX(仅在使用实时录音时需要):</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# macOS\nbrew install sox\n\n# Ubuntu / Debian\nsudo apt-get install sox`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"install\" title=\"安装方式\">\n <p><strong>方式一:npm 全局安装</strong>(推荐)</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`npm install -g chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式二:npx 免安装运行</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`MCP_REMOTE_URL=http://your-server:8080 npx chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式三:从源码构建</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`git clone https://git.chivox.com/CLOUD_DEV/cvx_local_mcp.git\ncd cvx_local_mcp\nbash scripts/build.sh`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"env-vars\" title=\"环境变量\">\n <ParamTable params={[\n { name: 'MCP_REMOTE_URL', type: 'string', required: true, desc: '远程驰声 MCP 服务地址,如 http://your-server:8080' },\n { name: 'MCP_API_KEY', type: 'string', required: false, desc: 'API 认证密钥(如已配置鉴权则必填)' },\n ]} />\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入配置 ══════ */}\n <DocSection id=\"config\" icon={Settings} title=\"接入配置\">\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置并保存:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <Callout type=\"tip\">Cursor 使用远程 Streamable HTTP 模式直连,无需安装本地代理。如需实时录音功能,请使用下方 Claude Desktop 的本地代理配置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-desktop\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件(macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"chivox-local-mcp\",\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\",\n \"MCP_API_KEY\": \"your-api-key\"\n }\n }\n }\n}`}</CodeBlock>\n <p>如果是从源码构建:</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/cvx_local_mcp/dist/index.js\"],\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\"\n }\n }\n }\n}`}</CodeBlock>\n <Callout type=\"info\">Claude Desktop 使用本地代理模式(stdio),支持实时录音评测。本地代理会自动桥接远程服务。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-code\" title=\"Claude Code 配置\">\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# npm 全局安装后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 chivox-local-mcp\n\n# 源码构建后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 node /path/to/cvx_local_mcp/dist/index.js`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze\" title=\"扣子(Coze)配置\">\n <p><a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子(Coze)</a>是字节跳动推出的 AI 应用开发平台,支持通过 MCP 扩展为 Agent 赋予外部工具能力。接入驰声语音评测后,你可以构建口语练习 Bot、发音诊断助手等应用。</p>\n <p className=\"font-semibold mt-4\">方式一:扣子空间 MCP 扩展(推荐)</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 <a href=\"https://space.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子空间</a>(space.coze.cn)</li>\n <li>在对话框下方点击 <strong>「添加扩展」</strong></li>\n <li>选择 <strong>「自定义」</strong> 标签,点击添加新的 MCP 服务</li>\n <li>填入以下配置信息:</li>\n </ol>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">名称</td><td className=\"py-2 px-3 font-mono text-xs\">chivox-speech-eval</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>保存后,在对话中输入 <strong>「帮我评测这段英文发音」</strong> 即可触发 Agent 调用</li>\n </ol>\n <Callout type=\"tip\">添加 MCP 扩展后,扣子空间的 Agent 会自动识别并调用评测工具。你可以在 Agent 的思考过程中看到具体的工具调用细节。</Callout>\n\n <p className=\"font-semibold mt-6\">方式二:在 Coze 开发平台的 Bot 中使用</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>进入 <a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Coze 开发平台</a>,创建或编辑 Bot</li>\n <li>在 Bot 的 <strong>「插件」</strong> 或 <strong>「工具」</strong> 面板中,添加 MCP 工具</li>\n <li>选择 <strong>Streamable HTTP</strong> 类型,填入 URL:<code className=\"bg-muted px-1 rounded text-xs font-mono\">https://speech-eval.site/mcp</code></li>\n <li>Bot 将自动发现所有可用的评测工具(如 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_word</code> 等)</li>\n <li>在 Bot 的人设提示词中,添加语音评测相关的引导语</li>\n </ol>\n <CodeBlock filename=\"Bot 人设提示词示例\">{`你是一位专业的英语口语教练。当用户上传音频并请求评测时:\n1. 使用 evaluate_english_word / evaluate_english_sentence 等工具进行评测\n2. 根据评测结果(overall、accuracy、fluency 等分数)给出专业分析\n3. 针对得分较低的维度提供具体改进建议\n4. 鼓励用户持续练习`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze-workflow\" title=\"Coze 工作流接入\">\n <p>如果你需要在 Coze 工作流中集成语音评测能力,可以通过 <strong>HTTP Request 节点</strong>调用评测 API:</p>\n\n <p className=\"font-semibold mt-4\">步骤 1:上传音频</p>\n <p>在工作流中添加一个 HTTP Request 节点,配置如下:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">方法</td><td className=\"py-2 px-3 font-mono text-xs\">POST</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/upload</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Content-Type</td><td className=\"py-2 px-3 font-mono text-xs\">audio/mp3</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Body</td><td className=\"py-2 px-3 text-muted-foreground\">音频文件二进制数据</td></tr>\n </tbody>\n </table>\n </div>\n <p>响应中获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{{audioId}}'}</code>,传递给下一步。</p>\n\n <p className=\"font-semibold mt-4\">步骤 2:调用评测</p>\n <p>添加第二个 HTTP Request 节点,通过 MCP 接口调用评测工具:</p>\n <CodeBlock filename=\"HTTP Request Body\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"evaluate_english_sentence\",\n \"arguments\": {\n \"audioId\": \"{{audioId}}\",\n \"sentence\": \"{{ref_text}}\"\n }\n },\n \"id\": 1\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">步骤 3:处理结果</p>\n <p>评测结果通过 JSON 格式返回,可使用 Coze 的 <strong>代码节点</strong> 或 <strong>LLM 节点</strong> 对结果进行解析和二次处理(如生成学习建议、计算进步趋势等)。</p>\n\n <Callout type=\"info\">工作流方式适合需要固定流程的场景(如批量评测、定时任务、与其他系统联动)。如果是交互式场景,推荐使用上方的 MCP 扩展方式,Agent 会自动选择合适的工具。</Callout>\n\n <div className=\"rounded-lg bg-muted/30 p-5 mt-4\">\n <p className=\"text-xs font-semibold mb-3\">完整工作流示意</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">用户输入音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 上传音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 调用评测</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM: 分析结果</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">输出反馈</span>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"豆包 / 钉钉 / 其他 MCP 客户端\">\n <p>支持 MCP 的客户端均可接入,统一使用以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 评测模式 ══════ */}\n <DocSection id=\"eval-modes\" icon={Radio} title=\"评测模式\">\n\n <SubDoc id=\"stream-eval\" title=\"实时录音评测(边录边评)\">\n <p>通过本地麦克风实时录音,音频以 WebSocket 流式推送到评测服务,<strong>无需生成中间文件</strong>。这是最灵活、延迟最低的评测方式,特别适合交互式口语练习场景。</p>\n <Callout type=\"tip\">实时录音评测需要安装 <strong>SoX</strong> 并使用本地代理模式(Claude Desktop / Claude Code)。Cursor 等远程直连模式暂不支持实时录音。</Callout>\n\n <p className=\"font-semibold mt-4\">需要按顺序调用三个工具:</p>\n\n <div className=\"mt-3 space-y-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">create_stream_session</code> — 创建流式评测会话</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.word.score、en.sent.score、cn.sent.raw' },\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式,默认 mp3' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100,默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code>,后续步骤需使用。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">start_recording</code> — 开始麦克风录音</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '来自 create_stream_session 的会话 ID' },\n { name: 'chunk_size', type: 'number', required: false, desc: '音频块大小(bytes),默认 3200' },\n { name: 'chunk_interval_ms', type: 'number', required: false, desc: '推送间隔(ms),默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">调用后自动启动麦克风,音频通过 WebSocket 实时推送到评测服务。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">stop_recording</code> — 停止录音并获取结果</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '会话 ID' },\n { name: 'timeout', type: 'number', required: false, desc: '等待评测结果超时秒数,默认 30' },\n ]} />\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"stream-flow\" title=\"流式评测完整流程\">\n <div className=\"rounded-lg border border-border/60 p-5 bg-muted/10 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong>用户:</strong>&quot;请评测我的英文发音,文本是 Hello&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">create_stream_session(core_type=&quot;en.word.score&quot;, ref_text=&quot;Hello&quot;)</code></p>\n <p className=\"text-muted-foreground\">→ 获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">session_id</code></p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p>AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">start_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground mt-1\">麦克风开始录音,音频通过 WebSocket 实时推送到评测服务</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>用户</strong>说完后告知 AI &quot;结束&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">stop_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground\">→ 返回评测结果(总分、准确度、流利度、音素得分等)</p>\n </FlowStep>\n </div>\n </SubDoc>\n\n <SubDoc id=\"file-eval\" title=\"音频文件评测\">\n <p>适用于已有音频文件的评测场景,支持三种输入方式:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">适用场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_file_path</td><td className=\"py-2 px-3\">本地文件路径</td><td className=\"py-2 px-3 text-muted-foreground\">代理自动读取并转为 Base64,最便捷</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_base64</td><td className=\"py-2 px-3\">Base64 编码数据</td><td className=\"py-2 px-3 text-muted-foreground\">已有编码数据时使用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_url</td><td className=\"py-2 px-3\">音频文件 URL</td><td className=\"py-2 px-3 text-muted-foreground\">音频托管在云存储时使用</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"tip\">推荐使用 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">audio_file_path</code>,只需传入本地文件路径,代理会自动处理编码和上传。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"tools-en\" title=\"英文评测工具(10 种)\">\n <ToolTable tools={[\n ['en_word_eval', '单词评测', '评测 \"Hello\" 的发音准确度'],\n ['en_word_correction', '单词纠音', '检测多读/漏读/错读并给出纠正建议'],\n ['en_vocab_eval', '词语评测', '同时评测多个单词的发音'],\n ['en_sentence_eval', '句子评测', '评测整句朗读的准确度与流利度'],\n ['en_sentence_correction', '句子纠音', '逐词检测发音问题并给出修正建议'],\n ['en_paragraph_eval', '段落朗读评测', '评测整段英文课文的朗读质量'],\n ['en_phonics_eval', '自然拼读评测', '评测 Phonics 拼读规则掌握程度'],\n ['en_choice_eval', '口语选择题', '从预设选项中识别口语回答'],\n ['en_semi_open_eval', '半开放题评测', '场景对话等半开放口语题型'],\n ['en_realtime_eval', '实时朗读评测', '实时反馈朗读质量,逐句评分'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong>所有英文评测工具均支持以下参数。</p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tools-cn\" title=\"中文评测工具(6 种)\">\n <ToolTable tools={[\n ['cn_word_raw_eval', '单字评测(汉字)', '评测 \"中\" 的发音'],\n ['cn_word_pinyin_eval', '单字评测(拼音)', '评测 \"zhōng\" 的发音'],\n ['cn_sentence_eval', '词句评测', '评测 \"你好世界\" 的朗读'],\n ['cn_paragraph_eval', '段落朗读评测', '评测一段中文课文的朗读质量'],\n ['cn_rec_eval', '有限分支识别评测', '从预设选项中识别用户发音'],\n ['cn_aitalk_eval', 'AI Talk 口语评测', '中文口语对话能力评测'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong></p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本(汉字或拼音)' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结构化结果:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"integrity\": 95,\n \"details\": [\n {\n \"char\": \"hello\",\n \"score\": 85,\n \"phone\": [\n { \"phoneme\": \"h\", \"score\": 90 },\n { \"phoneme\": \"ɛ\", \"score\": 82 },\n { \"phoneme\": \"l\", \"score\": 88 },\n { \"phoneme\": \"oʊ\", \"score\": 80 }\n ]\n }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall', '0–100', '综合总分,反映整体语音质量'],\n ['accuracy', '0–100', '准确度,音素级别的发音准确率'],\n ['pron', '0–100', '发音得分,整体发音质量评价'],\n ['fluency.overall', '0–100', '流利度,语流的自然度与平滑度'],\n ['fluency.speed', '0–100', '语速得分,每分钟词/音节计量'],\n ['fluency.pause', 'number', '不自然停顿次数'],\n ['integrity', '0–100', '完整度,是否完整朗读了参考文本'],\n ['details[].phone[]', 'array', '每个音素的独立评分,可定位具体弱项'],\n ['details[].stress[]', 'array', '重音得分,检测重弱读是否正确'],\n ].map(([field, range, desc]) => (\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期', '音频上传后 5 分钟内有效,评测完成后自动删除', '重新上传音频'],\n ['服务器繁忙', '当前请求过多,排队已满', '稍后重试,使用指数退避策略'],\n ['存储空间已满', '服务器临时音频存储已满', '稍后重试,系统会自动清理'],\n ['音频格式不支持', '上传了不支持的文件类型', '使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限', '超过该题型最大时长限制', '裁剪音频后重试'],\n ['WebSocket 连接断开', '网络不稳定导致流式连接中断', '代理会自动重连,如持续失败请检查网络'],\n ].map(([scene, reason, fix]) => (\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 架构说明 ══════ */}\n <DocSection id=\"architecture\" icon={Terminal} title=\"架构说明\">\n\n <SubDoc id=\"arch-diagram\" title=\"系统架构\">\n <div className=\"flex flex-wrap items-center justify-center gap-4 my-6\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Claude Desktop<br/>Claude Code<br/>Cursor</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">stdio</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-1\">chivox-local-mcp</p>\n <p className=\"text-[11px] text-muted-foreground\">本地代理<br/>SoX 录音</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP / WS</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">Remote Chivox</p>\n <p className=\"text-[11px] text-muted-foreground\">MCP Server<br/>评测引擎</p>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"transport\" title=\"传输协议\">\n <ul className=\"space-y-3\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>stdio</strong> — AI 客户端通过标准输入输出与本地代理通信,这是 MCP 的标准传输方式</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>HTTP</strong> — 音频文件评测工具通过 HTTP 代理到远程服务,适用于非流式场景</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>WebSocket</strong> — 实时录音音频通过 WebSocket 流式推送,支持断线自动重连</span>\n </li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 示例代码 ══════ */}\n <DocSection id=\"integration\" icon={Code2} title=\"示例代码\">\n\n <SubDoc id=\"code-python\" title=\"Python 上传音频\">\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://speech-eval.site/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"code-javascript\" title=\"JavaScript 上传音频\">\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst res = await fetch('https://speech-eval.site/upload', {\n method: 'POST',\n headers: { 'Content-Type': 'audio/mp3' },\n body: audioData,\n});\n\nconst result = await res.json();\nconsole.log('audioId:', result.audioId);`}</CodeBlock>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p>以下模板帮助你引导 LLM 对评测结果进行深度分析:</p>\n <div className=\"space-y-4 mt-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><MessageSquare className=\"h-4 w-4\" /> 发音诊断</p>\n <CodeBlock filename=\"prompt-diagnosis.txt\">{`你是一位专业的英语口语教练。请根据以下 MCP 评测结果分析学生的发音表现:\n\n评测结果:{evaluation_result}\n\n请按以下格式输出:\n1. 总体评价(一句话)\n2. 优势项(>80 分的指标)\n3. 弱项分析(<70 分的指标,定位到具体音素)\n4. 针对性练习建议(2-3 条可操作的建议)`}</CodeBlock>\n </div>\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><Mic className=\"h-4 w-4\" /> 练习生成</p>\n <CodeBlock filename=\"prompt-practice.txt\">{`根据以下弱项列表,为学生生成针对性的口语练习材料:\n\n弱项:{weak_points}\n\n请生成:\n1. 3 个绕口令(专门针对弱项音素)\n2. 5 个包含目标音素的常用短句(标注重音和连读)\n3. 1 段跟读练习段落(50-80 词)`}</CodeBlock>\n </div>\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><BarChart3 className=\"h-4 w-4\" /> 学习报告</p>\n <CodeBlock filename=\"prompt-report.txt\">{`请根据学生近 7 天的评测历史生成学习报告:\n\n评测历史:{history}\n\n报告要求:\n1. 整体趋势(进步/持平/退步)\n2. 各维度变化(准确度、流利度、语速)\n3. 本周最大进步项\n4. 下周重点练习建议\n5. 鼓励性总结`}</CodeBlock>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频过期:</strong>上传后 5 分钟内有效。建议在上传后 3 分钟内完成评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>服务繁忙:</strong>使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>录音中断:</strong>WebSocket 断线后代理会自动重连。如持续失败,检查网络连接和 SoX 安装。</span></li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>优先使用流式评测:</strong>实时录音模式跳过了文件上传环节,端到端延迟比文件评测低 30-50%。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频压缩:</strong>文件评测时,上传前将音频转为 16kHz 单声道 mp3,可减少 60-80% 的上传体积。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>选择合适的工具:</strong>单词评测比段落评测快 3-5 倍。如只需单词级分数,不要用句子评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>本地文件路径最便捷:</strong>使用 <code className=\"bg-muted px-1 rounded text-xs\">audio_file_path</code> 参数传入本地路径,代理自动处理编码,无需手动转 Base64。</span></li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Globe} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP JSON-RPC 接口(Streamable HTTP)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制', '500MB', '最大音频存储空间'],\n ['最大排队', '10', '等待处理的请求数'],\n ['最大并发', '3', '同时处理的评测数'],\n ['音频有效期', '5 分钟', '上传后未评测自动过期'],\n ['文件大小限制', '50MB', '单个音频文件最大'],\n ].map(([k, v, d]) => (\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <div className=\"space-y-3\">\n {[\n { ver: 'v3.0.0', date: '2026-04-16', desc: '新增本地代理模式,支持实时录音流式评测,工具扩展至 16 种' },\n { ver: 'v2.3.0', date: '2026-03-25', desc: '支持 HTTPS,域名 speech-eval.site' },\n { ver: 'v2.2.0', date: '2026-03-24', desc: '添加 MCP 配置指南(Cursor / Claude Desktop)' },\n { ver: 'v2.1.0', date: '2026-03-24', desc: '简化上传流程,统一使用 HTTP API' },\n { ver: 'v2.0.0', date: '2026-03-24', desc: '初始版本,支持多种评测类型' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </div>\n </div>\n </div>\n </main>\n );\n}\n"}, "df6978fcba4dfa6353b0a2b2f0f21a9fea9fd5d0": {"t": 1785765813.6600416, "n": 47075, "h": "6df90c0ede446342", "c": "'use client';\n\nimport { useState } from 'react';\nimport { Link } from '@/i18n/routing';\nimport { ArrowLeft, BookOpen, Code2, Zap, FileText, Copy, Check, Terminal, Globe, Mic, MessageSquare, BarChart3, AlertTriangle, Lightbulb, Radio, FolderOpen, Settings } from 'lucide-react';\n\n/* ─── Reusable Components ─── */\n\nfunction CopyButton({ text }: { text: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <button\n onClick={() => { navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); }}\n className=\"absolute top-3 right-3 p-1.5 rounded-md bg-white/5 hover:bg-white/10 transition-colors text-zinc-400 hover:text-zinc-200\"\n >\n {copied ? <Check className=\"h-3.5 w-3.5\" /> : <Copy className=\"h-3.5 w-3.5\" />}\n </button>\n );\n}\n\nfunction CodeBlock({ filename, lang, children }: { filename?: string; lang?: string; children: string }) {\n return (\n <div className=\"rounded-lg border border-border/60 bg-zinc-950 text-zinc-100 overflow-hidden my-4 relative\">\n {filename && (\n <div className=\"flex items-center gap-1.5 px-4 py-2.5 border-b border-white/[0.06] text-xs text-zinc-500 font-mono\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-red-500/60\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-yellow-500/60\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-green-500/60\" />\n <span className=\"ml-2\">{filename}</span>\n {lang && <span className=\"ml-auto text-zinc-600\">{lang}</span>}\n </div>\n )}\n <CopyButton text={children.trim()} />\n <pre className=\"p-4 text-sm font-mono leading-relaxed overflow-x-auto\"><code>{children.trim()}</code></pre>\n </div>\n );\n}\n\nfunction DocSection({ id, icon: Icon, title, children }: { id: string; icon: React.ElementType; title: string; children: React.ReactNode }) {\n return (\n <section id={id} className=\"scroll-mt-24 mb-16\">\n <div className=\"flex items-center gap-3 mb-6 pb-3 border-b border-border/40\">\n <Icon className=\"h-5 w-5 text-muted-foreground shrink-0\" />\n <h2 className=\"text-xl font-bold tracking-tight\">{title}</h2>\n </div>\n <div className=\"text-sm leading-relaxed text-foreground/90 space-y-6\">{children}</div>\n </section>\n );\n}\n\nfunction SubDoc({ id, title, children }: { id: string; title: string; children: React.ReactNode }) {\n return (\n <div id={id} className=\"scroll-mt-24 mb-10\">\n <h3 className=\"text-base font-semibold mb-3\">{title}</h3>\n <div className=\"space-y-4\">{children}</div>\n </div>\n );\n}\n\nfunction ParamTable({ params }: { params: { name: string; type: string; required: boolean; desc: string }[] }) {\n return (\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">类型</th>\n <th className=\"text-left py-2 px-3 font-medium\">必填</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {params.map(p => (\n <tr key={p.name}>\n <td className=\"py-2 px-3 font-mono text-xs\">{p.name}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{p.type}</td>\n <td className=\"py-2 px-3\">{p.required ? <span className=\"text-emerald-600 dark:text-emerald-400\">✓</span> : <span className=\"text-muted-foreground\">—</span>}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{p.desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction Callout({ type = 'info', children }: { type?: 'info' | 'warning' | 'tip'; children: React.ReactNode }) {\n const styles = {\n info: 'border-blue-200 bg-blue-50 text-blue-900 dark:border-blue-900 dark:bg-blue-950/30 dark:text-blue-200',\n warning: 'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950/30 dark:text-amber-200',\n tip: 'border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-900 dark:bg-emerald-950/30 dark:text-emerald-200',\n };\n const icons = { info: Globe, warning: AlertTriangle, tip: Lightbulb };\n const Ic = icons[type];\n return (\n <div className={`flex gap-3 rounded-lg border p-4 text-sm ${styles[type]}`}>\n <Ic className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div>{children}</div>\n </div>\n );\n}\n\nfunction FlowStep({ title, children }: { title: string; children: React.ReactNode }) {\n return (\n <div className=\"flex gap-4 items-start\">\n <div className=\"h-7 w-7 rounded-full bg-foreground text-background flex items-center justify-center text-xs font-bold shrink-0 mt-0.5\">{title}</div>\n <div className=\"flex-1 text-sm\">{children}</div>\n </div>\n );\n}\n\nfunction ToolTable({ tools }: { tools: [string, string, string][] }) {\n return (\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">工具名</th>\n <th className=\"text-left py-2 px-3 font-medium\">功能</th>\n <th className=\"text-left py-2 px-3 font-medium\">典型场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {tools.map(([name, desc, scene]) => (\n <tr key={name}>\n <td className=\"py-2 px-3 font-mono text-xs\">{name}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{scene}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\n/* ─── Navigation Structure ─── */\n\nconst NAV = [\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'requirements', label: '系统要求' },\n { id: 'install', label: '安装方式' },\n { id: 'env-vars', label: '环境变量' },\n ]},\n { id: 'config', icon: Settings, label: '接入配置', children: [\n { id: 'config-cursor', label: 'Cursor' },\n { id: 'config-claude-desktop', label: 'Claude Desktop' },\n { id: 'config-claude-code', label: 'Claude Code' },\n { id: 'config-coze', label: '扣子(Coze)' },\n { id: 'config-coze-workflow', label: 'Coze 工作流接入' },\n { id: 'config-other', label: '豆包 / 钉钉 / 其他' },\n ]},\n { id: 'eval-modes', icon: Radio, label: '评测模式', children: [\n { id: 'stream-eval', label: '实时录音评测' },\n { id: 'stream-flow', label: '流式评测流程' },\n { id: 'file-eval', label: '音频文件评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'tools-en', label: '英文评测工具' },\n { id: 'tools-cn', label: '中文评测工具' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'architecture', icon: Terminal, label: '架构说明', children: [\n { id: 'arch-diagram', label: '架构图' },\n { id: 'transport', label: '传输协议' },\n ]},\n { id: 'integration', icon: Code2, label: '示例代码', children: [\n { id: 'code-python', label: 'Python' },\n { id: 'code-javascript', label: 'JavaScript' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n { id: 'service-info', icon: Globe, label: '服务信息', children: [\n { id: 'endpoints', label: '服务端点' },\n { id: 'limits', label: '服务限制' },\n { id: 'changelog', label: '更新日志' },\n ]},\n];\n\n/* ─── Page ─── */\n\nexport default function DocsPage() {\n const [, setActiveSection] = useState('quick-start');\n\n return (\n <main className=\"flex-1\">\n <div className=\"container mx-auto px-6 py-12 md:py-20\">\n <Link href=\"/\" className=\"inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors mb-8\">\n <ArrowLeft className=\"h-4 w-4\" /> 返回首页\n </Link>\n\n <h1 className=\"text-3xl md:text-4xl font-bold tracking-tight mb-2\">开发者文档</h1>\n <p className=\"text-muted-foreground mb-10\">驰声语音评测 MCP · chivox-local-mcp</p>\n\n <div className=\"flex gap-10\">\n {/* Sidebar */}\n <nav className=\"hidden lg:block w-56 shrink-0 sticky top-24 self-start max-h-[calc(100vh-8rem)] overflow-y-auto\">\n {NAV.map(group => (\n <div key={group.id} className=\"mb-5\">\n <a href={`#${group.id}`} onClick={() => setActiveSection(group.id)} className=\"flex items-center gap-2 text-sm font-semibold mb-1.5 hover:text-foreground transition-colors\">\n <group.icon className=\"h-4 w-4 text-muted-foreground\" />\n {group.label}\n </a>\n <ul className=\"ml-6 space-y-1 border-l border-border/40 pl-3\">\n {group.children.map(child => (\n <li key={child.id}>\n <a href={`#${child.id}`} className=\"text-xs text-muted-foreground hover:text-foreground transition-colors block py-0.5 truncate\">{child.label}</a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </nav>\n\n {/* Content */}\n <div className=\"flex-1 min-w-0 max-w-3xl\">\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-local-mcp</strong> 是驰声语音评测的 MCP 本地代理,通过 <a href=\"https://modelcontextprotocol.io/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Model Context Protocol</a> 将远程驰声评测服务桥接到本地,让 AI 助手(Claude Desktop、Claude Code、Cursor 等)直接调用语音评测能力。</p>\n <p>它支持两种评测模式,覆盖从即时交互到批量处理的全部场景:</p>\n <div className=\"grid sm:grid-cols-2 gap-3 my-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <Mic className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">实时录音评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">通过麦克风边录边评,音频以 WebSocket 流式推送,无需生成中间文件,延迟更低、体验更佳。</p>\n </div>\n <div className=\"rounded-lg border border-border/60 p-4\">\n <div className=\"flex items-center gap-2 mb-2\">\n <FolderOpen className=\"h-4 w-4 text-foreground\" />\n <span className=\"text-sm font-semibold\">音频文件评测</span>\n </div>\n <p className=\"text-xs text-muted-foreground\">支持本地文件路径、Base64 编码、URL 三种输入,适合已有音频的批量评测场景。</p>\n </div>\n </div>\n <Callout type=\"info\">当前支持 <strong>16 种评测工具</strong>:英文 10 种(单词、句子、段落、纠音、自然拼读、口语选择、半开放题等)+ 中文 6 种(汉字、拼音、词句、段落、有限分支、AI Talk)。</Callout>\n </SubDoc>\n\n <SubDoc id=\"requirements\" title=\"系统要求\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">依赖</th>\n <th className=\"text-left py-2 px-3 font-medium\">版本</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-semibold\">Node.js</td><td className=\"py-2 px-3 font-mono text-xs\">{'>'}= 18</td><td className=\"py-2 px-3 text-muted-foreground\">运行时必须</td></tr>\n <tr><td className=\"py-2 px-3 font-semibold\">SoX</td><td className=\"py-2 px-3 font-mono text-xs\">any</td><td className=\"py-2 px-3 text-muted-foreground\">仅实时录音功能需要(处理麦克风音频流)</td></tr>\n </tbody>\n </table>\n </div>\n <p className=\"mt-3\">安装 SoX(仅在使用实时录音时需要):</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# macOS\nbrew install sox\n\n# Ubuntu / Debian\nsudo apt-get install sox`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"install\" title=\"安装方式\">\n <p><strong>方式一:npm 全局安装</strong>(推荐)</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`npm install -g chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式二:npx 免安装运行</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`MCP_REMOTE_URL=http://your-server:8080 npx chivox-local-mcp`}</CodeBlock>\n\n <p><strong>方式三:从源码构建</strong></p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`git clone https://git.chivox.com/CLOUD_DEV/cvx_local_mcp.git\ncd cvx_local_mcp\nbash scripts/build.sh`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"env-vars\" title=\"环境变量\">\n <ParamTable params={[\n { name: 'MCP_REMOTE_URL', type: 'string', required: true, desc: '远程驰声 MCP 服务地址,如 http://your-server:8080' },\n { name: 'MCP_API_KEY', type: 'string', required: false, desc: 'API 认证密钥(如已配置鉴权则必填)' },\n ]} />\n </SubDoc>\n </DocSection>\n\n {/* ══════ 接入配置 ══════ */}\n <DocSection id=\"config\" icon={Settings} title=\"接入配置\">\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置并保存:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <Callout type=\"tip\">Cursor 使用远程 Streamable HTTP 模式直连,无需安装本地代理。如需实时录音功能,请使用下方 Claude Desktop 的本地代理配置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-desktop\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件(macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"chivox-local-mcp\",\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\",\n \"MCP_API_KEY\": \"your-api-key\"\n }\n }\n }\n}`}</CodeBlock>\n <p>如果是从源码构建:</p>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox\": {\n \"command\": \"node\",\n \"args\": [\"/path/to/cvx_local_mcp/dist/index.js\"],\n \"env\": {\n \"MCP_REMOTE_URL\": \"http://your-server:8080\"\n }\n }\n }\n}`}</CodeBlock>\n <Callout type=\"info\">Claude Desktop 使用本地代理模式(stdio),支持实时录音评测。本地代理会自动桥接远程服务。</Callout>\n </SubDoc>\n\n <SubDoc id=\"config-claude-code\" title=\"Claude Code 配置\">\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`# npm 全局安装后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 chivox-local-mcp\n\n# 源码构建后\nclaude mcp add chivox -- env MCP_REMOTE_URL=http://your-server:8080 node /path/to/cvx_local_mcp/dist/index.js`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze\" title=\"扣子(Coze)配置\">\n <p><a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子(Coze)</a>是字节跳动推出的 AI 应用开发平台,支持通过 MCP 扩展为 Agent 赋予外部工具能力。接入驰声语音评测后,你可以构建口语练习 Bot、发音诊断助手等应用。</p>\n <p className=\"font-semibold mt-4\">方式一:扣子空间 MCP 扩展(推荐)</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 <a href=\"https://space.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">扣子空间</a>(space.coze.cn)</li>\n <li>在对话框下方点击 <strong>「添加扩展」</strong></li>\n <li>选择 <strong>「自定义」</strong> 标签,点击添加新的 MCP 服务</li>\n <li>填入以下配置信息:</li>\n </ol>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">名称</td><td className=\"py-2 px-3 font-mono text-xs\">chivox-speech-eval</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>保存后,在对话中输入 <strong>「帮我评测这段英文发音」</strong> 即可触发 Agent 调用</li>\n </ol>\n <Callout type=\"tip\">添加 MCP 扩展后,扣子空间的 Agent 会自动识别并调用评测工具。你可以在 Agent 的思考过程中看到具体的工具调用细节。</Callout>\n\n <p className=\"font-semibold mt-6\">方式二:在 Coze 开发平台的 Bot 中使用</p>\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>进入 <a href=\"https://www.coze.cn/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"text-blue-600 dark:text-blue-400 hover:underline\">Coze 开发平台</a>,创建或编辑 Bot</li>\n <li>在 Bot 的 <strong>「插件」</strong> 或 <strong>「工具」</strong> 面板中,添加 MCP 工具</li>\n <li>选择 <strong>Streamable HTTP</strong> 类型,填入 URL:<code className=\"bg-muted px-1 rounded text-xs font-mono\">https://speech-eval.site/mcp</code></li>\n <li>Bot 将自动发现所有可用的评测工具(如 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_word</code> 等)</li>\n <li>在 Bot 的人设提示词中,添加语音评测相关的引导语</li>\n </ol>\n <CodeBlock filename=\"Bot 人设提示词示例\">{`你是一位专业的英语口语教练。当用户上传音频并请求评测时:\n1. 使用 evaluate_english_word / evaluate_english_sentence 等工具进行评测\n2. 根据评测结果(overall、accuracy、fluency 等分数)给出专业分析\n3. 针对得分较低的维度提供具体改进建议\n4. 鼓励用户持续练习`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-coze-workflow\" title=\"Coze 工作流接入\">\n <p>如果你需要在 Coze 工作流中集成语音评测能力,可以通过 <strong>HTTP Request 节点</strong>调用评测 API:</p>\n\n <p className=\"font-semibold mt-4\">步骤 1:上传音频</p>\n <p>在工作流中添加一个 HTTP Request 节点,配置如下:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60 my-3\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">方法</td><td className=\"py-2 px-3 font-mono text-xs\">POST</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/upload</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Content-Type</td><td className=\"py-2 px-3 font-mono text-xs\">audio/mp3</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">Body</td><td className=\"py-2 px-3 text-muted-foreground\">音频文件二进制数据</td></tr>\n </tbody>\n </table>\n </div>\n <p>响应中获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">{'{{audioId}}'}</code>,传递给下一步。</p>\n\n <p className=\"font-semibold mt-4\">步骤 2:调用评测</p>\n <p>添加第二个 HTTP Request 节点,通过 MCP 接口调用评测工具:</p>\n <CodeBlock filename=\"HTTP Request Body\" lang=\"json\">{`{\n \"jsonrpc\": \"2.0\",\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"evaluate_english_sentence\",\n \"arguments\": {\n \"audioId\": \"{{audioId}}\",\n \"sentence\": \"{{ref_text}}\"\n }\n },\n \"id\": 1\n}`}</CodeBlock>\n\n <p className=\"font-semibold mt-4\">步骤 3:处理结果</p>\n <p>评测结果通过 JSON 格式返回,可使用 Coze 的 <strong>代码节点</strong> 或 <strong>LLM 节点</strong> 对结果进行解析和二次处理(如生成学习建议、计算进步趋势等)。</p>\n\n <Callout type=\"info\">工作流方式适合需要固定流程的场景(如批量评测、定时任务、与其他系统联动)。如果是交互式场景,推荐使用上方的 MCP 扩展方式,Agent 会自动选择合适的工具。</Callout>\n\n <div className=\"rounded-lg bg-muted/30 p-5 mt-4\">\n <p className=\"text-xs font-semibold mb-3\">完整工作流示意</p>\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">用户输入音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 上传音频</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">HTTP: 调用评测</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">LLM: 分析结果</span>\n <span className=\"text-muted-foreground\">→</span>\n <span className=\"px-3 py-1.5 rounded-md bg-background border border-border/60 font-medium\">输出反馈</span>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"豆包 / 钉钉 / 其他 MCP 客户端\">\n <p>支持 MCP 的客户端均可接入,统一使用以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">传输类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 评测模式 ══════ */}\n <DocSection id=\"eval-modes\" icon={Radio} title=\"评测模式\">\n\n <SubDoc id=\"stream-eval\" title=\"实时录音评测(边录边评)\">\n <p>通过本地麦克风实时录音,音频以 WebSocket 流式推送到评测服务,<strong>无需生成中间文件</strong>。这是最灵活、延迟最低的评测方式,特别适合交互式口语练习场景。</p>\n <Callout type=\"tip\">实时录音评测需要安装 <strong>SoX</strong> 并使用本地代理模式(Claude Desktop / Claude Code)。Cursor 等远程直连模式暂不支持实时录音。</Callout>\n\n <p className=\"font-semibold mt-4\">需要按顺序调用三个工具:</p>\n\n <div className=\"mt-3 space-y-4\">\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">create_stream_session</code> — 创建流式评测会话</p>\n <ParamTable params={[\n { name: 'core_type', type: 'string', required: true, desc: '评测类型,如 en.word.score、en.sent.score、cn.sent.raw' },\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_type', type: 'string', required: false, desc: '音频格式,默认 mp3' },\n { name: 'sample_rate', type: 'number', required: false, desc: '采样率,默认 16000' },\n { name: 'rank', type: 'number', required: false, desc: '评分制:4 或 100,默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">返回 <code className=\"bg-muted px-1 rounded text-xs\">session_id</code>,后续步骤需使用。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">start_recording</code> — 开始麦克风录音</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '来自 create_stream_session 的会话 ID' },\n { name: 'chunk_size', type: 'number', required: false, desc: '音频块大小(bytes),默认 3200' },\n { name: 'chunk_interval_ms', type: 'number', required: false, desc: '推送间隔(ms),默认 100' },\n ]} />\n <p className=\"text-xs text-muted-foreground mt-2\">调用后自动启动麦克风,音频通过 WebSocket 实时推送到评测服务。</p>\n </div>\n\n <div className=\"rounded-lg border border-border/60 p-4\">\n <p className=\"font-semibold text-sm mb-2\"><code className=\"bg-muted px-1.5 py-0.5 rounded text-xs\">stop_recording</code> — 停止录音并获取结果</p>\n <ParamTable params={[\n { name: 'session_id', type: 'string', required: true, desc: '会话 ID' },\n { name: 'timeout', type: 'number', required: false, desc: '等待评测结果超时秒数,默认 30' },\n ]} />\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"stream-flow\" title=\"流式评测完整流程\">\n <div className=\"rounded-lg border border-border/60 p-5 bg-muted/10 space-y-5\">\n <FlowStep title=\"1\">\n <p><strong>用户:</strong>&quot;请评测我的英文发音,文本是 Hello&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">create_stream_session(core_type=&quot;en.word.score&quot;, ref_text=&quot;Hello&quot;)</code></p>\n <p className=\"text-muted-foreground\">→ 获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">session_id</code></p>\n </FlowStep>\n <FlowStep title=\"2\">\n <p>AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">start_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground mt-1\">麦克风开始录音,音频通过 WebSocket 实时推送到评测服务</p>\n </FlowStep>\n <FlowStep title=\"3\">\n <p><strong>用户</strong>说完后告知 AI &quot;结束&quot;</p>\n <p className=\"text-muted-foreground mt-1\">AI 调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">stop_recording(session_id=xxx)</code></p>\n <p className=\"text-muted-foreground\">→ 返回评测结果(总分、准确度、流利度、音素得分等)</p>\n </FlowStep>\n </div>\n </SubDoc>\n\n <SubDoc id=\"file-eval\" title=\"音频文件评测\">\n <p>适用于已有音频文件的评测场景,支持三种输入方式:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n <th className=\"text-left py-2 px-3 font-medium\">适用场景</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_file_path</td><td className=\"py-2 px-3\">本地文件路径</td><td className=\"py-2 px-3 text-muted-foreground\">代理自动读取并转为 Base64,最便捷</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_base64</td><td className=\"py-2 px-3\">Base64 编码数据</td><td className=\"py-2 px-3 text-muted-foreground\">已有编码数据时使用</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">audio_url</td><td className=\"py-2 px-3\">音频文件 URL</td><td className=\"py-2 px-3 text-muted-foreground\">音频托管在云存储时使用</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"tip\">推荐使用 <code className=\"bg-black/5 dark:bg-white/10 px-1 rounded text-xs\">audio_file_path</code>,只需传入本地文件路径,代理会自动处理编码和上传。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"tools-en\" title=\"英文评测工具(10 种)\">\n <ToolTable tools={[\n ['en_word_eval', '单词评测', '评测 \"Hello\" 的发音准确度'],\n ['en_word_correction', '单词纠音', '检测多读/漏读/错读并给出纠正建议'],\n ['en_vocab_eval', '词语评测', '同时评测多个单词的发音'],\n ['en_sentence_eval', '句子评测', '评测整句朗读的准确度与流利度'],\n ['en_sentence_correction', '句子纠音', '逐词检测发音问题并给出修正建议'],\n ['en_paragraph_eval', '段落朗读评测', '评测整段英文课文的朗读质量'],\n ['en_phonics_eval', '自然拼读评测', '评测 Phonics 拼读规则掌握程度'],\n ['en_choice_eval', '口语选择题', '从预设选项中识别口语回答'],\n ['en_semi_open_eval', '半开放题评测', '场景对话等半开放口语题型'],\n ['en_realtime_eval', '实时朗读评测', '实时反馈朗读质量,逐句评分'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong>所有英文评测工具均支持以下参数。</p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tools-cn\" title=\"中文评测工具(6 种)\">\n <ToolTable tools={[\n ['cn_word_raw_eval', '单字评测(汉字)', '评测 \"中\" 的发音'],\n ['cn_word_pinyin_eval', '单字评测(拼音)', '评测 \"zhōng\" 的发音'],\n ['cn_sentence_eval', '词句评测', '评测 \"你好世界\" 的朗读'],\n ['cn_paragraph_eval', '段落朗读评测', '评测一段中文课文的朗读质量'],\n ['cn_rec_eval', '有限分支识别评测', '从预设选项中识别用户发音'],\n ['cn_aitalk_eval', 'AI Talk 口语评测', '中文口语对话能力评测'],\n ]} />\n\n <p className=\"mt-4\"><strong>通用参数:</strong></p>\n <ParamTable params={[\n { name: 'ref_text', type: 'string', required: true, desc: '评测参考文本(汉字或拼音)' },\n { name: 'audio_file_path', type: 'string', required: false, desc: '本地音频文件路径(三选一)' },\n { name: 'audio_base64', type: 'string', required: false, desc: 'Base64 编码的音频数据(三选一)' },\n { name: 'audio_url', type: 'string', required: false, desc: '音频文件 URL(三选一)' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结构化结果:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"integrity\": 95,\n \"details\": [\n {\n \"char\": \"hello\",\n \"score\": 85,\n \"phone\": [\n { \"phoneme\": \"h\", \"score\": 90 },\n { \"phoneme\": \"ɛ\", \"score\": 82 },\n { \"phoneme\": \"l\", \"score\": 88 },\n { \"phoneme\": \"oʊ\", \"score\": 80 }\n ]\n }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall', '0–100', '综合总分,反映整体语音质量'],\n ['accuracy', '0–100', '准确度,音素级别的发音准确率'],\n ['pron', '0–100', '发音得分,整体发音质量评价'],\n ['fluency.overall', '0–100', '流利度,语流的自然度与平滑度'],\n ['fluency.speed', '0–100', '语速得分,每分钟词/音节计量'],\n ['fluency.pause', 'number', '不自然停顿次数'],\n ['integrity', '0–100', '完整度,是否完整朗读了参考文本'],\n ['details[].phone[]', 'array', '每个音素的独立评分,可定位具体弱项'],\n ['details[].stress[]', 'array', '重音得分,检测重弱读是否正确'],\n ].map(([field, range, desc]) => (\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期', '音频上传后 5 分钟内有效,评测完成后自动删除', '重新上传音频'],\n ['服务器繁忙', '当前请求过多,排队已满', '稍后重试,使用指数退避策略'],\n ['存储空间已满', '服务器临时音频存储已满', '稍后重试,系统会自动清理'],\n ['音频格式不支持', '上传了不支持的文件类型', '使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限', '超过该题型最大时长限制', '裁剪音频后重试'],\n ['WebSocket 连接断开', '网络不稳定导致流式连接中断', '代理会自动重连,如持续失败请检查网络'],\n ].map(([scene, reason, fix]) => (\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 架构说明 ══════ */}\n <DocSection id=\"architecture\" icon={Terminal} title=\"架构说明\">\n\n <SubDoc id=\"arch-diagram\" title=\"系统架构\">\n <div className=\"flex flex-wrap items-center justify-center gap-4 my-6\">\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">AI 客户端</p>\n <p className=\"text-[11px] text-muted-foreground\">Claude Desktop<br/>Claude Code<br/>Cursor</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">stdio</p>\n </div>\n <div className=\"rounded-xl border-2 border-emerald-500/50 bg-emerald-500/5 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold text-emerald-600 dark:text-emerald-400 mb-1\">chivox-local-mcp</p>\n <p className=\"text-[11px] text-muted-foreground\">本地代理<br/>SoX 录音</p>\n </div>\n <div className=\"text-center\">\n <p className=\"text-lg font-bold text-muted-foreground\">⇄</p>\n <p className=\"text-[10px] text-muted-foreground\">HTTP / WS</p>\n </div>\n <div className=\"rounded-xl border-2 border-foreground/30 px-6 py-4 text-center min-w-[140px]\">\n <p className=\"text-xs font-semibold mb-1\">Remote Chivox</p>\n <p className=\"text-[11px] text-muted-foreground\">MCP Server<br/>评测引擎</p>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"transport\" title=\"传输协议\">\n <ul className=\"space-y-3\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>stdio</strong> — AI 客户端通过标准输入输出与本地代理通信,这是 MCP 的标准传输方式</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>HTTP</strong> — 音频文件评测工具通过 HTTP 代理到远程服务,适用于非流式场景</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1.5 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>WebSocket</strong> — 实时录音音频通过 WebSocket 流式推送,支持断线自动重连</span>\n </li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 示例代码 ══════ */}\n <DocSection id=\"integration\" icon={Code2} title=\"示例代码\">\n\n <SubDoc id=\"code-python\" title=\"Python 上传音频\">\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://speech-eval.site/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"code-javascript\" title=\"JavaScript 上传音频\">\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst res = await fetch('https://speech-eval.site/upload', {\n method: 'POST',\n headers: { 'Content-Type': 'audio/mp3' },\n body: audioData,\n});\n\nconst result = await res.json();\nconsole.log('audioId:', result.audioId);`}</CodeBlock>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p>以下模板帮助你引导 LLM 对评测结果进行深度分析:</p>\n <div className=\"space-y-4 mt-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><MessageSquare className=\"h-4 w-4\" /> 发音诊断</p>\n <CodeBlock filename=\"prompt-diagnosis.txt\">{`你是一位专业的英语口语教练。请根据以下 MCP 评测结果分析学生的发音表现:\n\n评测结果:{evaluation_result}\n\n请按以下格式输出:\n1. 总体评价(一句话)\n2. 优势项(>80 分的指标)\n3. 弱项分析(<70 分的指标,定位到具体音素)\n4. 针对性练习建议(2-3 条可操作的建议)`}</CodeBlock>\n </div>\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><Mic className=\"h-4 w-4\" /> 练习生成</p>\n <CodeBlock filename=\"prompt-practice.txt\">{`根据以下弱项列表,为学生生成针对性的口语练习材料:\n\n弱项:{weak_points}\n\n请生成:\n1. 3 个绕口令(专门针对弱项音素)\n2. 5 个包含目标音素的常用短句(标注重音和连读)\n3. 1 段跟读练习段落(50-80 词)`}</CodeBlock>\n </div>\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><BarChart3 className=\"h-4 w-4\" /> 学习报告</p>\n <CodeBlock filename=\"prompt-report.txt\">{`请根据学生近 7 天的评测历史生成学习报告:\n\n评测历史:{history}\n\n报告要求:\n1. 整体趋势(进步/持平/退步)\n2. 各维度变化(准确度、流利度、语速)\n3. 本周最大进步项\n4. 下周重点练习建议\n5. 鼓励性总结`}</CodeBlock>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频过期:</strong>上传后 5 分钟内有效。建议在上传后 3 分钟内完成评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>服务繁忙:</strong>使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>录音中断:</strong>WebSocket 断线后代理会自动重连。如持续失败,检查网络连接和 SoX 安装。</span></li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>优先使用流式评测:</strong>实时录音模式跳过了文件上传环节,端到端延迟比文件评测低 30-50%。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>音频压缩:</strong>文件评测时,上传前将音频转为 16kHz 单声道 mp3,可减少 60-80% 的上传体积。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>选择合适的工具:</strong>单词评测比段落评测快 3-5 倍。如只需单词级分数,不要用句子评测。</span></li>\n <li className=\"flex items-start gap-2\"><span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" /><span><strong>本地文件路径最便捷:</strong>使用 <code className=\"bg-muted px-1 rounded text-xs\">audio_file_path</code> 参数传入本地路径,代理自动处理编码,无需手动转 Base64。</span></li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Globe} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP JSON-RPC 接口(Streamable HTTP)</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制', '500MB', '最大音频存储空间'],\n ['最大排队', '10', '等待处理的请求数'],\n ['最大并发', '3', '同时处理的评测数'],\n ['音频有效期', '5 分钟', '上传后未评测自动过期'],\n ['文件大小限制', '50MB', '单个音频文件最大'],\n ].map(([k, v, d]) => (\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <div className=\"space-y-3\">\n {[\n { ver: 'v3.0.0', date: '2026-04-16', desc: '新增本地代理模式,支持实时录音流式评测,工具扩展至 16 种' },\n { ver: 'v2.3.0', date: '2026-03-25', desc: '支持 HTTPS,域名 speech-eval.site' },\n { ver: 'v2.2.0', date: '2026-03-24', desc: '添加 MCP 配置指南(Cursor / Claude Desktop)' },\n { ver: 'v2.1.0', date: '2026-03-24', desc: '简化上传流程,统一使用 HTTP API' },\n { ver: 'v2.0.0', date: '2026-03-24', desc: '初始版本,支持多种评测类型' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </div>\n </div>\n </div>\n </main>\n );\n}\n"}, "ea92804d243613a262247fb779bb9c06c68ca15b": {"t": 1785765815.1899729, "n": 33768, "h": "33a6b55cdb1e185b", "c": "'use client';\n\nimport { useState } from 'react';\nimport { Link } from '@/i18n/routing';\nimport { ArrowLeft, BookOpen, Code2, Zap, FileText, Copy, Check, Terminal, Globe, Mic, MessageSquare, BarChart3, AlertTriangle, Lightbulb } from 'lucide-react';\n\nfunction CopyButton({ text }: { text: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <button\n onClick={() => { navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); }}\n className=\"absolute top-3 right-3 p-1.5 rounded-md bg-white/5 hover:bg-white/10 transition-colors text-zinc-400 hover:text-zinc-200\"\n >\n {copied ? <Check className=\"h-3.5 w-3.5\" /> : <Copy className=\"h-3.5 w-3.5\" />}\n </button>\n );\n}\n\nfunction CodeBlock({ filename, lang, children }: { filename?: string; lang?: string; children: string }) {\n return (\n <div className=\"rounded-lg border border-border/60 bg-zinc-950 text-zinc-100 overflow-hidden my-4 relative\">\n {filename && (\n <div className=\"flex items-center gap-1.5 px-4 py-2.5 border-b border-white/[0.06] text-xs text-zinc-500 font-mono\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-red-500/60\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-yellow-500/60\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-green-500/60\" />\n <span className=\"ml-2\">{filename}</span>\n {lang && <span className=\"ml-auto text-zinc-600\">{lang}</span>}\n </div>\n )}\n <CopyButton text={children.trim()} />\n <pre className=\"p-4 text-sm font-mono leading-relaxed overflow-x-auto\"><code>{children.trim()}</code></pre>\n </div>\n );\n}\n\nfunction DocSection({ id, icon: Icon, title, children }: { id: string; icon: React.ElementType; title: string; children: React.ReactNode }) {\n return (\n <section id={id} className=\"scroll-mt-24 mb-16\">\n <div className=\"flex items-center gap-3 mb-6 pb-3 border-b border-border/40\">\n <Icon className=\"h-5 w-5 text-muted-foreground shrink-0\" />\n <h2 className=\"text-xl font-bold tracking-tight\">{title}</h2>\n </div>\n <div className=\"text-sm leading-relaxed text-foreground/90 space-y-6\">\n {children}\n </div>\n </section>\n );\n}\n\nfunction SubDoc({ id, title, children }: { id: string; title: string; children: React.ReactNode }) {\n return (\n <div id={id} className=\"scroll-mt-24 mb-10\">\n <h3 className=\"text-base font-semibold mb-3\">{title}</h3>\n <div className=\"space-y-4\">{children}</div>\n </div>\n );\n}\n\nfunction ParamTable({ params }: { params: { name: string; type: string; required: boolean; desc: string }[] }) {\n return (\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">类型</th>\n <th className=\"text-left py-2 px-3 font-medium\">必填</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {params.map(p => (\n <tr key={p.name}>\n <td className=\"py-2 px-3 font-mono text-xs\">{p.name}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{p.type}</td>\n <td className=\"py-2 px-3\">{p.required ? <span className=\"text-emerald-600\">✓</span> : <span className=\"text-muted-foreground\">—</span>}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{p.desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction Callout({ type = 'info', children }: { type?: 'info' | 'warning' | 'tip'; children: React.ReactNode }) {\n const styles = {\n info: 'border-blue-200 bg-blue-50 text-blue-900 dark:border-blue-900 dark:bg-blue-950/30 dark:text-blue-200',\n warning: 'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950/30 dark:text-amber-200',\n tip: 'border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-900 dark:bg-emerald-950/30 dark:text-emerald-200',\n };\n const icons = { info: Globe, warning: AlertTriangle, tip: Lightbulb };\n const Ic = icons[type];\n return (\n <div className={`flex gap-3 rounded-lg border p-4 text-sm ${styles[type]}`}>\n <Ic className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div>{children}</div>\n </div>\n );\n}\n\nconst NAV = [\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'mcp-config', label: 'MCP 配置' },\n { id: 'upload-audio', label: '上传音频' },\n { id: 'first-eval', label: '第一次评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'audio-upload-api', label: '音频上传 API' },\n { id: 'eval-tools', label: '评测工具总览' },\n { id: 'tool-english-word', label: 'evaluate_english_word' },\n { id: 'tool-english-sentence', label: 'evaluate_english_sentence' },\n { id: 'tool-english-paragraph', label: 'evaluate_english_paragraph' },\n { id: 'tool-english-word-pron', label: 'evaluate_english_word_pron' },\n { id: 'tool-english-sentence-pron', label: 'evaluate_english_sentence_pron' },\n { id: 'tool-english-phonics', label: 'evaluate_english_phonics' },\n { id: 'tool-chinese', label: 'evaluate_chinese' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'integration', icon: Code2, label: '集成指南', children: [\n { id: 'config-cursor', label: 'Cursor 配置' },\n { id: 'config-claude', label: 'Claude Desktop 配置' },\n { id: 'config-other', label: '豆包 / 钉钉 / 其他' },\n { id: 'code-python', label: 'Python 示例' },\n { id: 'code-javascript', label: 'JavaScript 示例' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n];\n\nexport default function DocsPage() {\n const [, setActiveSection] = useState('quick-start');\n\n return (\n <main className=\"flex-1\">\n <div className=\"container mx-auto px-6 py-12 md:py-20\">\n <Link href=\"/\" className=\"inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors mb-8\">\n <ArrowLeft className=\"h-4 w-4\" /> 返回首页\n </Link>\n\n <h1 className=\"text-3xl md:text-4xl font-bold tracking-tight mb-2\">开发者文档</h1>\n <p className=\"text-muted-foreground mb-10\">驰声语音评测 MCP · v2.3.0</p>\n\n <div className=\"flex gap-10\">\n {/* Sidebar */}\n <nav className=\"hidden lg:block w-56 shrink-0 sticky top-24 self-start max-h-[calc(100vh-8rem)] overflow-y-auto\">\n {NAV.map(group => (\n <div key={group.id} className=\"mb-5\">\n <a\n href={`#${group.id}`}\n onClick={() => setActiveSection(group.id)}\n className=\"flex items-center gap-2 text-sm font-semibold mb-1.5 hover:text-foreground transition-colors\"\n >\n <group.icon className=\"h-4 w-4 text-muted-foreground\" />\n {group.label}\n </a>\n <ul className=\"ml-6 space-y-1 border-l border-border/40 pl-3\">\n {group.children.map(child => (\n <li key={child.id}>\n <a\n href={`#${child.id}`}\n className=\"text-xs text-muted-foreground hover:text-foreground transition-colors block py-0.5 truncate\"\n >\n {child.label}\n </a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </nav>\n\n {/* Content */}\n <div className=\"flex-1 min-w-0 max-w-3xl\">\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-speech-eval</strong> 是一个基于 MCP(Model Context Protocol)的语音评测服务器,为 AI 助手提供驰声中英文语音评测能力。</p>\n <p>MCP 是 AI 助手与外部工具/服务通信的标准化协议。通过 MCP,AI 可以直接调用专业工具,无需用户手动复制粘贴数据。</p>\n <Callout type=\"info\">当前版本 v2.3.0,服务端点:<code className=\"bg-black/5 px-1 rounded text-xs font-mono\">https://speech-eval.site</code></Callout>\n <p><strong>工作流程:</strong></p>\n <ol className=\"list-decimal list-inside space-y-1 ml-2\">\n <li>用户上传音频 → <code className=\"bg-muted px-1 rounded text-xs font-mono\">POST /upload</code> → 获得 audioId</li>\n <li>AI 调用评测 → <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_xxx(audioId, text)</code> → 获得评测结果</li>\n <li>音频自动删除(评测完成或 5 分钟后过期)</li>\n </ol>\n </SubDoc>\n\n <SubDoc id=\"mcp-config\" title=\"MCP 配置\">\n <p>在你的 AI 客户端中添加以下 MCP 服务器配置:</p>\n <CodeBlock filename=\"MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <Callout type=\"tip\">所有支持 MCP 的客户端(Cursor、Claude Desktop、豆包、钉钉等)均使用以上配置,仅 UI 入口不同。详见下方集成指南。</Callout>\n </SubDoc>\n\n <SubDoc id=\"upload-audio\" title=\"上传音频\">\n <p>评测前需先上传音频文件,获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">audioId</code>。</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`curl -X POST https://speech-eval.site/upload \\\\\n -H \"Content-Type: audio/mp3\" \\\\\n --data-binary @audio.mp3`}</CodeBlock>\n <p>返回示例:</p>\n <CodeBlock filename=\"response.json\" lang=\"json\">{`{\n \"audioId\": \"audio_1234567890_abc123\",\n \"size\": 38528,\n \"compressed\": false,\n \"expiresIn\": \"5 分钟\"\n}`}</CodeBlock>\n <Callout type=\"warning\">音频上传后 <strong>5 分钟内</strong>有效,评测完成后会自动删除。请在上传后尽快进行评测。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n\n <SubDoc id=\"first-eval\" title=\"第一次评测\">\n <p>上传音频获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">audioId</code> 后,告诉 AI 你要评测的内容,AI 会自动调用相应工具。</p>\n <div className=\"rounded-lg border border-border/60 p-4 bg-muted/20 space-y-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"h-6 w-6 rounded-full bg-blue-100 text-blue-700 flex items-center justify-center text-xs font-bold shrink-0\">你</div>\n <p className=\"text-sm\">请帮我评测这个音频的英文句子发音,参考文本是 &quot;The quick brown fox jumps over the lazy dog.&quot;</p>\n </div>\n <div className=\"flex items-start gap-3\">\n <div className=\"h-6 w-6 rounded-full bg-zinc-200 text-zinc-700 flex items-center justify-center text-xs font-bold shrink-0\">AI</div>\n <div className=\"text-sm space-y-1\">\n <p>正在调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_sentence</code> ...</p>\n <p>评测完成!总分 <strong>82</strong>,准确度 <strong>78</strong>,流利度 <strong>85</strong>。</p>\n </div>\n </div>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"audio-upload-api\" title=\"音频上传 API\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"text-xs font-bold px-2 py-0.5 bg-emerald-100 text-emerald-800 rounded\">POST</span>\n <code className=\"text-sm font-mono\">https://speech-eval.site/upload</code>\n </div>\n <ParamTable params={[\n { name: 'Content-Type', type: 'header', required: true, desc: '音频 MIME 类型,如 audio/mp3, audio/wav' },\n { name: 'Body', type: 'binary', required: true, desc: '音频文件二进制数据' },\n ]} />\n <p className=\"mt-3\"><strong>响应字段:</strong></p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频唯一标识,后续评测时使用' },\n { name: 'size', type: 'number', required: true, desc: '文件大小(字节)' },\n { name: 'compressed', type: 'boolean', required: true, desc: '是否经过自动压缩' },\n { name: 'expiresIn', type: 'string', required: true, desc: '过期时间描述' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"eval-tools\" title=\"评测工具总览\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">工具名</th>\n <th className=\"text-left py-2 px-3 font-medium\">功能</th>\n <th className=\"text-left py-2 px-3 font-medium\">题型 ID</th>\n <th className=\"text-left py-2 px-3 font-medium\">最大时长</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['evaluate_english_word','英文单词评测','en.word.score','< 20秒'],\n ['evaluate_english_sentence','英文句子评测','en.sent.score','≤ 40秒'],\n ['evaluate_english_paragraph','英文段落评测','en.pred.score','≤ 300秒'],\n ['evaluate_english_word_pron','英文单词纠音','en.word.pron','< 20秒'],\n ['evaluate_english_sentence_pron','英文句子纠音','en.sent.pron','≤ 40秒'],\n ['evaluate_english_phonics','自然拼读评测','en.nsp.score','< 20秒'],\n ['evaluate_chinese','中文评测','cn.word/sent.score','< 60秒'],\n ].map(([tool,fn,id,dur]) => (\n <tr key={tool}>\n <td className=\"py-2 px-3 font-mono text-xs\"><a href={`#tool-${tool.replace('evaluate_','').replace(/_/g,'-')}`} className=\"text-blue-600 hover:underline\">{tool}</a></td>\n <td className=\"py-2 px-3\">{fn}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{id}</td>\n <td className=\"py-2 px-3\">{dur}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n {/* 每个评测工具 */}\n <SubDoc id=\"tool-english-word\" title=\"evaluate_english_word\">\n <p>评测英文单词的发音质量。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID(从上传获得)' },\n { name: 'word', type: 'string', required: true, desc: '要评测的单词' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n <p><strong>示例对话:</strong></p>\n <CodeBlock filename=\"AI 调用\">{`evaluate_english_word(\n audioId = \"audio_xxx\",\n word = \"magnificent\",\n accent = \"american\"\n)`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"tool-english-sentence\" title=\"evaluate_english_sentence\">\n <p>评测英文句子的发音质量,包括流利度、准确度等。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID' },\n { name: 'sentence', type: 'string', required: true, desc: '要评测的句子' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american' },\n ]} />\n <CodeBlock filename=\"AI 调用\">{`evaluate_english_sentence(\n audioId = \"audio_xxx\",\n sentence = \"The quick brown fox jumps over the lazy dog.\"\n)`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"tool-english-paragraph\" title=\"evaluate_english_paragraph\">\n <p>评测英文段落的整体发音质量。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID' },\n { name: 'paragraph', type: 'string', required: true, desc: '要评测的段落' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tool-english-word-pron\" title=\"evaluate_english_word_pron\">\n <p>英文单词纠音,可检测多读、漏读、错读。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID' },\n { name: 'word', type: 'string', required: true, desc: '要评测的单词' },\n { name: 'accent', type: 'string', required: false, desc: '口音' },\n ]} />\n <Callout type=\"tip\">纠音工具会返回详细的音素级错误信息,包括多读(insertion)、漏读(deletion)、错读(substitution)的具体位置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"tool-english-sentence-pron\" title=\"evaluate_english_sentence_pron\">\n <p>英文句子纠音,检测句子中每个单词的发音问题。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID' },\n { name: 'sentence', type: 'string', required: true, desc: '要评测的句子' },\n { name: 'accent', type: 'string', required: false, desc: '口音' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tool-english-phonics\" title=\"evaluate_english_phonics\">\n <p>自然拼读评测,适用于拼读规则练习场景。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID' },\n { name: 'word', type: 'string', required: true, desc: '要拼读的内容' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tool-chinese\" title=\"evaluate_chinese\">\n <p>评测中文发音,自动识别词语/句子模式。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID' },\n { name: 'text', type: 'string', required: true, desc: '要评测的中文内容' },\n ]} />\n <Callout type=\"info\">自动识别模式:≤ 10 个字符使用词语模式,{'>'}10 个字符使用句子模式。</Callout>\n <CodeBlock filename=\"AI 调用\">{`evaluate_chinese(\n audioId = \"audio_xxx\",\n text = \"你好,世界\"\n)`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结果结构:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"details\": [\n { \"char\": \"hello\", \"score\": 85 },\n { \"char\": \"world\", \"score\": 82 }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall','0-100','总分,综合语音质量评分'],\n ['accuracy','0-100','准确度,音素级别的发音准确率'],\n ['pron','0-100','发音分,整体发音质量'],\n ['fluency.overall','0-100','流利度,语流的自然度与平滑度'],\n ['fluency.speed','0-100','语速,每分钟词/音节计量'],\n ['fluency.pause','number','停顿次数,不自然停顿检测'],\n ['details','array','逐词/逐字评分详情'],\n ].map(([field,range,desc])=>(\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期','音频上传后 5 分钟内有效,评测完成后自动删除','重新上传音频'],\n ['服务器繁忙,排队已满','当前请求过多','稍后重试'],\n ['存储空间已满','服务器临时音频存储已满','稍后重试,系统会自动清理'],\n ['音频格式不支持','上传了不支持的文件类型','使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限','音频时长超过该题型最大限制','裁剪音频后重试'],\n ].map(([scene,reason,fix])=>(\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 集成指南 ══════ */}\n <DocSection id=\"integration\" icon={Code2} title=\"集成指南\">\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>点击 <strong>Save</strong> 保存</li>\n <li>在对话中输入「帮我评测这段英文发音」即可触发</li>\n </ol>\n </SubDoc>\n\n <SubDoc id=\"config-claude\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件:</p>\n <ul className=\"list-disc list-inside space-y-1 ml-1 text-muted-foreground\">\n <li>macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code></li>\n <li>Windows:<code className=\"bg-muted px-1 rounded text-xs font-mono\">%APPDATA%\\Claude\\claude_desktop_config.json</code></li>\n </ul>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox-speech-eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n }\n }\n}`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"豆包 / 钉钉 / 其他\">\n <p>参照各自的 MCP 配置界面,添加以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"info\">现已支持 HTTPS,可安全用于各类 AI 平台。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-python\" title=\"Python 示例\">\n <p>使用 Python requests 库上传音频并获取 audioId:</p>\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://speech-eval.site/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"code-javascript\" title=\"JavaScript 示例\">\n <p>使用 Node.js 上传音频:</p>\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\nconst https = require('https');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst req = https.request({\n hostname: 'speech-eval.site',\n port: 443,\n path: '/upload',\n method: 'POST',\n headers: {\n 'Content-Type': 'audio/mp3',\n 'Content-Length': audioData.length\n }\n}, (res) => {\n let data = '';\n res.on('data', chunk => data += chunk);\n res.on('end', () => {\n const result = JSON.parse(data);\n console.log('audioId:', result.audioId);\n });\n});\n\nreq.write(audioData);\nreq.end();`}</CodeBlock>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p>以下是推荐的 Prompt 模板,帮助你引导 LLM 对评测结果进行深度分析:</p>\n\n <div className=\"space-y-4 mt-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><MessageSquare className=\"h-4 w-4\" /> 模板 1:发音诊断</p>\n <CodeBlock filename=\"prompt-diagnosis.txt\">{`你是一位专业的英语口语教练。请根据以下 MCP 评测结果分析学生的发音表现:\n\n评测结果:{evaluation_result}\n\n请按以下格式输出:\n1. 总体评价(一句话)\n2. 优势项(准确度/流利度等 >80 分的指标)\n3. 弱项分析(<70 分的指标,定位到具体音素)\n4. 针对性练习建议(2-3 条具体可操作的建议)`}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><Mic className=\"h-4 w-4\" /> 模板 2:练习生成</p>\n <CodeBlock filename=\"prompt-practice.txt\">{`根据以下弱项列表,为学生生成针对性的口语练习材料:\n\n弱项:{weak_points}\n\n请生成:\n1. 3 个绕口令(专门针对弱项音素)\n2. 5 个包含目标音素的常用短句(标注重音和连读)\n3. 1 段跟读练习段落(50-80 词,高频使用目标音素)`}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><BarChart3 className=\"h-4 w-4\" /> 模板 3:学习报告</p>\n <CodeBlock filename=\"prompt-report.txt\">{`你是一位数据分析师,请根据学生近 7 天的评测历史生成学习报告:\n\n评测历史:{history}\n\n报告要求:\n1. 整体趋势(进步/持平/退步)\n2. 各维度变化(准确度、流利度、语速)\n3. 本周最大进步项\n4. 下周重点练习建议\n5. 鼓励性总结(保持学习动力)`}</CodeBlock>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <p>推荐的错误处理策略:</p>\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>音频过期:</strong>评测前检查 audioId 是否仍有效。如果返回「已过期」,提示用户重新上传。建议在上传后 3 分钟内完成评测。</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>服务繁忙:</strong>遇到排队满时,使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>音频质量差:</strong>建议在上传前在客户端进行基本的音频质量检查(静音检测、时长检测),减少无效调用。</span>\n </li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>音频压缩:</strong>上传前在客户端将音频转为 16kHz 单声道 mp3 格式,可减少 60-80% 的上传体积和评测延迟。</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>避免重复评测:</strong>对相同音频 + 参考文本的组合做客户端缓存,避免重复调用消耗配额。</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>选择合适的工具:</strong>单词评测比句子评测快 3-5 倍。如果只需要单词级别的分数,不要用句子评测工具。</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>批量场景:</strong>如果需要同时评测多段音频(如班级考试),建议使用批量评测接口(即将上线),避免并发限制。</span>\n </li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Terminal} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP JSON-RPC 接口</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制','500MB','最大音频存储空间'],\n ['最大排队','10','等待处理的请求数'],\n ['最大并发','3','同时处理的评测数'],\n ['音频有效期','5 分钟','上传后未评测自动过期'],\n ['文件大小限制','50MB','单个音频文件最大'],\n ].map(([k,v,d])=>(\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <div className=\"space-y-3\">\n {[\n { ver: 'v2.3.0', date: '2026-03-25', desc: '支持 HTTPS,域名 speech-eval.site' },\n { ver: 'v2.2.0', date: '2026-03-24', desc: '添加 MCP 配置指南(Cursor / Claude Desktop)' },\n { ver: 'v2.1.0', date: '2026-03-24', desc: '简化上传流程,统一使用 HTTP API' },\n { ver: 'v2.0.0', date: '2026-03-24', desc: '初始版本,支持多种评测类型' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </div>\n </div>\n </div>\n </main>\n );\n}\n"}, "bfe46122c8fad240ab22d7975addecc9deae3416": {"t": 1785765818.3646426, "n": 33795, "h": "6a5f3755d0e35235", "c": "'use client';\n\nimport { useState } from 'react';\nimport { Link } from '@/i18n/routing';\nimport { ArrowLeft, BookOpen, Code2, Zap, FileText, ChevronRight, Copy, Check, Terminal, Globe, Mic, MessageSquare, BarChart3, AlertTriangle, Lightbulb } from 'lucide-react';\n\nfunction CopyButton({ text }: { text: string }) {\n const [copied, setCopied] = useState(false);\n return (\n <button\n onClick={() => { navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); }}\n className=\"absolute top-3 right-3 p-1.5 rounded-md bg-white/5 hover:bg-white/10 transition-colors text-zinc-400 hover:text-zinc-200\"\n >\n {copied ? <Check className=\"h-3.5 w-3.5\" /> : <Copy className=\"h-3.5 w-3.5\" />}\n </button>\n );\n}\n\nfunction CodeBlock({ filename, lang, children }: { filename?: string; lang?: string; children: string }) {\n return (\n <div className=\"rounded-lg border border-border/60 bg-zinc-950 text-zinc-100 overflow-hidden my-4 relative\">\n {filename && (\n <div className=\"flex items-center gap-1.5 px-4 py-2.5 border-b border-white/[0.06] text-xs text-zinc-500 font-mono\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-red-500/60\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-yellow-500/60\" />\n <span className=\"h-2.5 w-2.5 rounded-full bg-green-500/60\" />\n <span className=\"ml-2\">{filename}</span>\n {lang && <span className=\"ml-auto text-zinc-600\">{lang}</span>}\n </div>\n )}\n <CopyButton text={children.trim()} />\n <pre className=\"p-4 text-sm font-mono leading-relaxed overflow-x-auto\"><code>{children.trim()}</code></pre>\n </div>\n );\n}\n\nfunction DocSection({ id, icon: Icon, title, children }: { id: string; icon: React.ElementType; title: string; children: React.ReactNode }) {\n return (\n <section id={id} className=\"scroll-mt-24 mb-16\">\n <div className=\"flex items-center gap-3 mb-6 pb-3 border-b border-border/40\">\n <Icon className=\"h-5 w-5 text-muted-foreground shrink-0\" />\n <h2 className=\"text-xl font-bold tracking-tight\">{title}</h2>\n </div>\n <div className=\"text-sm leading-relaxed text-foreground/90 space-y-6\">\n {children}\n </div>\n </section>\n );\n}\n\nfunction SubDoc({ id, title, children }: { id: string; title: string; children: React.ReactNode }) {\n return (\n <div id={id} className=\"scroll-mt-24 mb-10\">\n <h3 className=\"text-base font-semibold mb-3\">{title}</h3>\n <div className=\"space-y-4\">{children}</div>\n </div>\n );\n}\n\nfunction ParamTable({ params }: { params: { name: string; type: string; required: boolean; desc: string }[] }) {\n return (\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">参数</th>\n <th className=\"text-left py-2 px-3 font-medium\">类型</th>\n <th className=\"text-left py-2 px-3 font-medium\">必填</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {params.map(p => (\n <tr key={p.name}>\n <td className=\"py-2 px-3 font-mono text-xs\">{p.name}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{p.type}</td>\n <td className=\"py-2 px-3\">{p.required ? <span className=\"text-emerald-600\">✓</span> : <span className=\"text-muted-foreground\">—</span>}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{p.desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n\nfunction Callout({ type = 'info', children }: { type?: 'info' | 'warning' | 'tip'; children: React.ReactNode }) {\n const styles = {\n info: 'border-blue-200 bg-blue-50 text-blue-900 dark:border-blue-900 dark:bg-blue-950/30 dark:text-blue-200',\n warning: 'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950/30 dark:text-amber-200',\n tip: 'border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-900 dark:bg-emerald-950/30 dark:text-emerald-200',\n };\n const icons = { info: Globe, warning: AlertTriangle, tip: Lightbulb };\n const Ic = icons[type];\n return (\n <div className={`flex gap-3 rounded-lg border p-4 text-sm ${styles[type]}`}>\n <Ic className=\"h-4 w-4 mt-0.5 shrink-0\" />\n <div>{children}</div>\n </div>\n );\n}\n\nconst NAV = [\n { id: 'quick-start', icon: Zap, label: '快速开始', children: [\n { id: 'overview', label: '概述' },\n { id: 'mcp-config', label: 'MCP 配置' },\n { id: 'upload-audio', label: '上传音频' },\n { id: 'first-eval', label: '第一次评测' },\n ]},\n { id: 'api-reference', icon: BookOpen, label: 'API 参考', children: [\n { id: 'audio-upload-api', label: '音频上传 API' },\n { id: 'eval-tools', label: '评测工具总览' },\n { id: 'tool-english-word', label: 'evaluate_english_word' },\n { id: 'tool-english-sentence', label: 'evaluate_english_sentence' },\n { id: 'tool-english-paragraph', label: 'evaluate_english_paragraph' },\n { id: 'tool-english-word-pron', label: 'evaluate_english_word_pron' },\n { id: 'tool-english-sentence-pron', label: 'evaluate_english_sentence_pron' },\n { id: 'tool-english-phonics', label: 'evaluate_english_phonics' },\n { id: 'tool-chinese', label: 'evaluate_chinese' },\n { id: 'result-fields', label: '评测结果字段' },\n { id: 'error-codes', label: '错误码' },\n ]},\n { id: 'integration', icon: Code2, label: '集成指南', children: [\n { id: 'config-cursor', label: 'Cursor 配置' },\n { id: 'config-claude', label: 'Claude Desktop 配置' },\n { id: 'config-other', label: '豆包 / 钉钉 / 其他' },\n { id: 'code-python', label: 'Python 示例' },\n { id: 'code-javascript', label: 'JavaScript 示例' },\n ]},\n { id: 'best-practices', icon: FileText, label: '最佳实践', children: [\n { id: 'prompt-templates', label: 'Prompt 模板' },\n { id: 'error-handling', label: '错误处理' },\n { id: 'performance', label: '性能优化' },\n ]},\n];\n\nexport default function DocsPage() {\n const [activeSection, setActiveSection] = useState('quick-start');\n\n return (\n <main className=\"flex-1\">\n <div className=\"container mx-auto px-6 py-12 md:py-20\">\n <Link href=\"/\" className=\"inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors mb-8\">\n <ArrowLeft className=\"h-4 w-4\" /> 返回首页\n </Link>\n\n <h1 className=\"text-3xl md:text-4xl font-bold tracking-tight mb-2\">开发者文档</h1>\n <p className=\"text-muted-foreground mb-10\">驰声语音评测 MCP · v2.3.0</p>\n\n <div className=\"flex gap-10\">\n {/* Sidebar */}\n <nav className=\"hidden lg:block w-56 shrink-0 sticky top-24 self-start max-h-[calc(100vh-8rem)] overflow-y-auto\">\n {NAV.map(group => (\n <div key={group.id} className=\"mb-5\">\n <a\n href={`#${group.id}`}\n onClick={() => setActiveSection(group.id)}\n className=\"flex items-center gap-2 text-sm font-semibold mb-1.5 hover:text-foreground transition-colors\"\n >\n <group.icon className=\"h-4 w-4 text-muted-foreground\" />\n {group.label}\n </a>\n <ul className=\"ml-6 space-y-1 border-l border-border/40 pl-3\">\n {group.children.map(child => (\n <li key={child.id}>\n <a\n href={`#${child.id}`}\n className=\"text-xs text-muted-foreground hover:text-foreground transition-colors block py-0.5 truncate\"\n >\n {child.label}\n </a>\n </li>\n ))}\n </ul>\n </div>\n ))}\n </nav>\n\n {/* Content */}\n <div className=\"flex-1 min-w-0 max-w-3xl\">\n\n {/* ══════ 快速开始 ══════ */}\n <DocSection id=\"quick-start\" icon={Zap} title=\"快速开始\">\n\n <SubDoc id=\"overview\" title=\"概述\">\n <p><strong>chivox-speech-eval</strong> 是一个基于 MCP(Model Context Protocol)的语音评测服务器,为 AI 助手提供驰声中英文语音评测能力。</p>\n <p>MCP 是 AI 助手与外部工具/服务通信的标准化协议。通过 MCP,AI 可以直接调用专业工具,无需用户手动复制粘贴数据。</p>\n <Callout type=\"info\">当前版本 v2.3.0,服务端点:<code className=\"bg-black/5 px-1 rounded text-xs font-mono\">https://speech-eval.site</code></Callout>\n <p><strong>工作流程:</strong></p>\n <ol className=\"list-decimal list-inside space-y-1 ml-2\">\n <li>用户上传音频 → <code className=\"bg-muted px-1 rounded text-xs font-mono\">POST /upload</code> → 获得 audioId</li>\n <li>AI 调用评测 → <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_xxx(audioId, text)</code> → 获得评测结果</li>\n <li>音频自动删除(评测完成或 5 分钟后过期)</li>\n </ol>\n </SubDoc>\n\n <SubDoc id=\"mcp-config\" title=\"MCP 配置\">\n <p>在你的 AI 客户端中添加以下 MCP 服务器配置:</p>\n <CodeBlock filename=\"MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <Callout type=\"tip\">所有支持 MCP 的客户端(Cursor、Claude Desktop、豆包、钉钉等)均使用以上配置,仅 UI 入口不同。详见下方集成指南。</Callout>\n </SubDoc>\n\n <SubDoc id=\"upload-audio\" title=\"上传音频\">\n <p>评测前需先上传音频文件,获取 <code className=\"bg-muted px-1 rounded text-xs font-mono\">audioId</code>。</p>\n <CodeBlock filename=\"Terminal\" lang=\"bash\">{`curl -X POST https://speech-eval.site/upload \\\\\n -H \"Content-Type: audio/mp3\" \\\\\n --data-binary @audio.mp3`}</CodeBlock>\n <p>返回示例:</p>\n <CodeBlock filename=\"response.json\" lang=\"json\">{`{\n \"audioId\": \"audio_1234567890_abc123\",\n \"size\": 38528,\n \"compressed\": false,\n \"expiresIn\": \"5 分钟\"\n}`}</CodeBlock>\n <Callout type=\"warning\">音频上传后 <strong>5 分钟内</strong>有效,评测完成后会自动删除。请在上传后尽快进行评测。</Callout>\n <p><strong>支持的音频格式:</strong>mp3, wav, ogg, m4a, aac, pcm</p>\n <p><strong>限制:</strong>单文件最大 50MB,大于 500KB 的文件自动压缩为 16kHz / 单声道 / 32kbps。</p>\n </SubDoc>\n\n <SubDoc id=\"first-eval\" title=\"第一次评测\">\n <p>上传音频获得 <code className=\"bg-muted px-1 rounded text-xs font-mono\">audioId</code> 后,告诉 AI 你要评测的内容,AI 会自动调用相应工具。</p>\n <div className=\"rounded-lg border border-border/60 p-4 bg-muted/20 space-y-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"h-6 w-6 rounded-full bg-blue-100 text-blue-700 flex items-center justify-center text-xs font-bold shrink-0\">你</div>\n <p className=\"text-sm\">请帮我评测这个音频的英文句子发音,参考文本是 &quot;The quick brown fox jumps over the lazy dog.&quot;</p>\n </div>\n <div className=\"flex items-start gap-3\">\n <div className=\"h-6 w-6 rounded-full bg-zinc-200 text-zinc-700 flex items-center justify-center text-xs font-bold shrink-0\">AI</div>\n <div className=\"text-sm space-y-1\">\n <p>正在调用 <code className=\"bg-muted px-1 rounded text-xs font-mono\">evaluate_english_sentence</code> ...</p>\n <p>评测完成!总分 <strong>82</strong>,准确度 <strong>78</strong>,流利度 <strong>85</strong>。</p>\n </div>\n </div>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ API 参考 ══════ */}\n <DocSection id=\"api-reference\" icon={BookOpen} title=\"API 参考\">\n\n <SubDoc id=\"audio-upload-api\" title=\"音频上传 API\">\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"text-xs font-bold px-2 py-0.5 bg-emerald-100 text-emerald-800 rounded\">POST</span>\n <code className=\"text-sm font-mono\">https://speech-eval.site/upload</code>\n </div>\n <ParamTable params={[\n { name: 'Content-Type', type: 'header', required: true, desc: '音频 MIME 类型,如 audio/mp3, audio/wav' },\n { name: 'Body', type: 'binary', required: true, desc: '音频文件二进制数据' },\n ]} />\n <p className=\"mt-3\"><strong>响应字段:</strong></p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频唯一标识,后续评测时使用' },\n { name: 'size', type: 'number', required: true, desc: '文件大小(字节)' },\n { name: 'compressed', type: 'boolean', required: true, desc: '是否经过自动压缩' },\n { name: 'expiresIn', type: 'string', required: true, desc: '过期时间描述' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"eval-tools\" title=\"评测工具总览\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">工具名</th>\n <th className=\"text-left py-2 px-3 font-medium\">功能</th>\n <th className=\"text-left py-2 px-3 font-medium\">题型 ID</th>\n <th className=\"text-left py-2 px-3 font-medium\">最大时长</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['evaluate_english_word','英文单词评测','en.word.score','< 20秒'],\n ['evaluate_english_sentence','英文句子评测','en.sent.score','≤ 40秒'],\n ['evaluate_english_paragraph','英文段落评测','en.pred.score','≤ 300秒'],\n ['evaluate_english_word_pron','英文单词纠音','en.word.pron','< 20秒'],\n ['evaluate_english_sentence_pron','英文句子纠音','en.sent.pron','≤ 40秒'],\n ['evaluate_english_phonics','自然拼读评测','en.nsp.score','< 20秒'],\n ['evaluate_chinese','中文评测','cn.word/sent.score','< 60秒'],\n ].map(([tool,fn,id,dur]) => (\n <tr key={tool}>\n <td className=\"py-2 px-3 font-mono text-xs\"><a href={`#tool-${tool.replace('evaluate_','').replace(/_/g,'-')}`} className=\"text-blue-600 hover:underline\">{tool}</a></td>\n <td className=\"py-2 px-3\">{fn}</td>\n <td className=\"py-2 px-3 font-mono text-xs text-muted-foreground\">{id}</td>\n <td className=\"py-2 px-3\">{dur}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n {/* 每个评测工具 */}\n <SubDoc id=\"tool-english-word\" title=\"evaluate_english_word\">\n <p>评测英文单词的发音质量。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID(从上传获得)' },\n { name: 'word', type: 'string', required: true, desc: '要评测的单词' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american(默认)' },\n ]} />\n <p><strong>示例对话:</strong></p>\n <CodeBlock filename=\"AI 调用\">{`evaluate_english_word(\n audioId = \"audio_xxx\",\n word = \"magnificent\",\n accent = \"american\"\n)`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"tool-english-sentence\" title=\"evaluate_english_sentence\">\n <p>评测英文句子的发音质量,包括流利度、准确度等。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID' },\n { name: 'sentence', type: 'string', required: true, desc: '要评测的句子' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american' },\n ]} />\n <CodeBlock filename=\"AI 调用\">{`evaluate_english_sentence(\n audioId = \"audio_xxx\",\n sentence = \"The quick brown fox jumps over the lazy dog.\"\n)`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"tool-english-paragraph\" title=\"evaluate_english_paragraph\">\n <p>评测英文段落的整体发音质量。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID' },\n { name: 'paragraph', type: 'string', required: true, desc: '要评测的段落' },\n { name: 'accent', type: 'string', required: false, desc: '口音:british / american' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tool-english-word-pron\" title=\"evaluate_english_word_pron\">\n <p>英文单词纠音,可检测多读、漏读、错读。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID' },\n { name: 'word', type: 'string', required: true, desc: '要评测的单词' },\n { name: 'accent', type: 'string', required: false, desc: '口音' },\n ]} />\n <Callout type=\"tip\">纠音工具会返回详细的音素级错误信息,包括多读(insertion)、漏读(deletion)、错读(substitution)的具体位置。</Callout>\n </SubDoc>\n\n <SubDoc id=\"tool-english-sentence-pron\" title=\"evaluate_english_sentence_pron\">\n <p>英文句子纠音,检测句子中每个单词的发音问题。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID' },\n { name: 'sentence', type: 'string', required: true, desc: '要评测的句子' },\n { name: 'accent', type: 'string', required: false, desc: '口音' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tool-english-phonics\" title=\"evaluate_english_phonics\">\n <p>自然拼读评测,适用于拼读规则练习场景。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID' },\n { name: 'word', type: 'string', required: true, desc: '要拼读的内容' },\n ]} />\n </SubDoc>\n\n <SubDoc id=\"tool-chinese\" title=\"evaluate_chinese\">\n <p>评测中文发音,自动识别词语/句子模式。</p>\n <ParamTable params={[\n { name: 'audioId', type: 'string', required: true, desc: '音频 ID' },\n { name: 'text', type: 'string', required: true, desc: '要评测的中文内容' },\n ]} />\n <Callout type=\"info\">自动识别模式:≤ 10 个字符使用词语模式,{'>'}10 个字符使用句子模式。</Callout>\n <CodeBlock filename=\"AI 调用\">{`evaluate_chinese(\n audioId = \"audio_xxx\",\n text = \"你好,世界\"\n)`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"result-fields\" title=\"评测结果字段\">\n <p>所有评测工具返回统一的结果结构:</p>\n <CodeBlock filename=\"result.json\" lang=\"json\">{`{\n \"overall\": 85,\n \"accuracy\": 82,\n \"pron\": 88,\n \"fluency\": {\n \"overall\": 78,\n \"speed\": 65,\n \"pause\": 2\n },\n \"details\": [\n { \"char\": \"hello\", \"score\": 85 },\n { \"char\": \"world\", \"score\": 82 }\n ]\n}`}</CodeBlock>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">字段</th>\n <th className=\"text-left py-2 px-3 font-medium\">范围</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['overall','0-100','总分,综合语音质量评分'],\n ['accuracy','0-100','准确度,音素级别的发音准确率'],\n ['pron','0-100','发音分,整体发音质量'],\n ['fluency.overall','0-100','流利度,语流的自然度与平滑度'],\n ['fluency.speed','0-100','语速,每分钟词/音节计量'],\n ['fluency.pause','number','停顿次数,不自然停顿检测'],\n ['details','array','逐词/逐字评分详情'],\n ].map(([field,range,desc])=>(\n <tr key={field}>\n <td className=\"py-2 px-3 font-mono text-xs\">{field}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{range}</td>\n <td className=\"py-2 px-3\">{desc}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-codes\" title=\"错误码\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">场景</th>\n <th className=\"text-left py-2 px-3 font-medium\">原因</th>\n <th className=\"text-left py-2 px-3 font-medium\">解决方式</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['音频 ID 无效或已过期','音频上传后 5 分钟内有效,评测完成后自动删除','重新上传音频'],\n ['服务器繁忙,排队已满','当前请求过多','稍后重试'],\n ['存储空间已满','服务器临时音频存储已满','稍后重试,系统会自动清理'],\n ['音频格式不支持','上传了不支持的文件类型','使用 mp3/wav/ogg/m4a/aac/pcm'],\n ['音频时长超限','音频时长超过该题型最大限制','裁剪音频后重试'],\n ].map(([scene,reason,fix])=>(\n <tr key={scene}>\n <td className=\"py-2 px-3 font-medium\">{scene}</td>\n <td className=\"py-2 px-3 text-muted-foreground\">{reason}</td>\n <td className=\"py-2 px-3\">{fix}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 集成指南 ══════ */}\n <DocSection id=\"integration\" icon={Code2} title=\"集成指南\">\n\n <SubDoc id=\"config-cursor\" title=\"Cursor 配置\">\n <ol className=\"list-decimal list-inside space-y-2 ml-1\">\n <li>打开 Cursor 设置(<kbd className=\"px-1.5 py-0.5 bg-muted rounded text-xs font-mono\">Cmd + ,</kbd>)</li>\n <li>左侧选择 <strong>MCP</strong>(或搜索 &quot;MCP&quot;)</li>\n <li>点击 <strong>Add new MCP server</strong></li>\n <li>填写配置:</li>\n </ol>\n <CodeBlock filename=\"Cursor MCP 配置\" lang=\"json\">{`{\n \"name\": \"chivox-speech-eval\",\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n}`}</CodeBlock>\n <ol className=\"list-decimal list-inside space-y-1 ml-1\" start={5}>\n <li>点击 <strong>Save</strong> 保存</li>\n <li>在对话中输入「帮我评测这段英文发音」即可触发</li>\n </ol>\n </SubDoc>\n\n <SubDoc id=\"config-claude\" title=\"Claude Desktop 配置\">\n <p>编辑配置文件:</p>\n <ul className=\"list-disc list-inside space-y-1 ml-1 text-muted-foreground\">\n <li>macOS:<code className=\"bg-muted px-1 rounded text-xs font-mono\">~/Library/Application Support/Claude/claude_desktop_config.json</code></li>\n <li>Windows:<code className=\"bg-muted px-1 rounded text-xs font-mono\">%APPDATA%\\Claude\\claude_desktop_config.json</code></li>\n </ul>\n <CodeBlock filename=\"claude_desktop_config.json\" lang=\"json\">{`{\n \"mcpServers\": {\n \"chivox-speech-eval\": {\n \"type\": \"streamable-http\",\n \"url\": \"https://speech-eval.site/mcp\"\n }\n }\n}`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"config-other\" title=\"豆包 / 钉钉 / 其他\">\n <p>参照各自的 MCP 配置界面,添加以下配置:</p>\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">值</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-medium\">类型</td><td className=\"py-2 px-3 font-mono text-xs\">streamable-http</td></tr>\n <tr><td className=\"py-2 px-3 font-medium\">URL</td><td className=\"py-2 px-3 font-mono text-xs\">https://speech-eval.site/mcp</td></tr>\n </tbody>\n </table>\n </div>\n <Callout type=\"info\">现已支持 HTTPS,可安全用于各类 AI 平台。</Callout>\n </SubDoc>\n\n <SubDoc id=\"code-python\" title=\"Python 示例\">\n <p>使用 Python requests 库上传音频并获取 audioId:</p>\n <CodeBlock filename=\"upload.py\" lang=\"python\">{`import requests\n\nwith open('audio.mp3', 'rb') as f:\n r = requests.post(\n 'https://speech-eval.site/upload',\n data=f,\n headers={'Content-Type': 'audio/mp3'}\n )\n\nresult = r.json()\nprint(f\"audioId: {result['audioId']}\")\nprint(f\"size: {result['size']} bytes\")\nprint(f\"expires: {result['expiresIn']}\")`}</CodeBlock>\n </SubDoc>\n\n <SubDoc id=\"code-javascript\" title=\"JavaScript 示例\">\n <p>使用 Node.js 上传音频:</p>\n <CodeBlock filename=\"upload.js\" lang=\"javascript\">{`const fs = require('fs');\nconst https = require('https');\n\nconst audioData = fs.readFileSync('audio.mp3');\n\nconst req = https.request({\n hostname: 'speech-eval.site',\n port: 443,\n path: '/upload',\n method: 'POST',\n headers: {\n 'Content-Type': 'audio/mp3',\n 'Content-Length': audioData.length\n }\n}, (res) => {\n let data = '';\n res.on('data', chunk => data += chunk);\n res.on('end', () => {\n const result = JSON.parse(data);\n console.log('audioId:', result.audioId);\n });\n});\n\nreq.write(audioData);\nreq.end();`}</CodeBlock>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 最佳实践 ══════ */}\n <DocSection id=\"best-practices\" icon={FileText} title=\"最佳实践\">\n\n <SubDoc id=\"prompt-templates\" title=\"Prompt 模板\">\n <p>以下是推荐的 Prompt 模板,帮助你引导 LLM 对评测结果进行深度分析:</p>\n\n <div className=\"space-y-4 mt-4\">\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><MessageSquare className=\"h-4 w-4\" /> 模板 1:发音诊断</p>\n <CodeBlock filename=\"prompt-diagnosis.txt\">{`你是一位专业的英语口语教练。请根据以下 MCP 评测结果分析学生的发音表现:\n\n评测结果:{evaluation_result}\n\n请按以下格式输出:\n1. 总体评价(一句话)\n2. 优势项(准确度/流利度等 >80 分的指标)\n3. 弱项分析(<70 分的指标,定位到具体音素)\n4. 针对性练习建议(2-3 条具体可操作的建议)`}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><Mic className=\"h-4 w-4\" /> 模板 2:练习生成</p>\n <CodeBlock filename=\"prompt-practice.txt\">{`根据以下弱项列表,为学生生成针对性的口语练习材料:\n\n弱项:{weak_points}\n\n请生成:\n1. 3 个绕口令(专门针对弱项音素)\n2. 5 个包含目标音素的常用短句(标注重音和连读)\n3. 1 段跟读练习段落(50-80 词,高频使用目标音素)`}</CodeBlock>\n </div>\n\n <div className=\"border border-border/60 rounded-lg p-4\">\n <p className=\"font-semibold text-sm mb-2 flex items-center gap-2\"><BarChart3 className=\"h-4 w-4\" /> 模板 3:学习报告</p>\n <CodeBlock filename=\"prompt-report.txt\">{`你是一位数据分析师,请根据学生近 7 天的评测历史生成学习报告:\n\n评测历史:{history}\n\n报告要求:\n1. 整体趋势(进步/持平/退步)\n2. 各维度变化(准确度、流利度、语速)\n3. 本周最大进步项\n4. 下周重点练习建议\n5. 鼓励性总结(保持学习动力)`}</CodeBlock>\n </div>\n </div>\n </SubDoc>\n\n <SubDoc id=\"error-handling\" title=\"错误处理\">\n <p>推荐的错误处理策略:</p>\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>音频过期:</strong>评测前检查 audioId 是否仍有效。如果返回「已过期」,提示用户重新上传。建议在上传后 3 分钟内完成评测。</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>服务繁忙:</strong>遇到排队满时,使用指数退避策略重试(1s → 2s → 4s),最多 3 次。</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>音频质量差:</strong>建议在上传前在客户端进行基本的音频质量检查(静音检测、时长检测),减少无效调用。</span>\n </li>\n </ul>\n </SubDoc>\n\n <SubDoc id=\"performance\" title=\"性能优化\">\n <ul className=\"space-y-3 mt-2\">\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>音频压缩:</strong>上传前在客户端将音频转为 16kHz 单声道 mp3 格式,可减少 60-80% 的上传体积和评测延迟。</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>避免重复评测:</strong>对相同音频 + 参考文本的组合做客户端缓存,避免重复调用消耗配额。</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>选择合适的工具:</strong>单词评测比句子评测快 3-5 倍。如果只需要单词级别的分数,不要用句子评测工具。</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"mt-1 h-1.5 w-1.5 rounded-full bg-foreground shrink-0\" />\n <span><strong>批量场景:</strong>如果需要同时评测多段音频(如班级考试),建议使用批量评测接口(即将上线),避免并发限制。</span>\n </li>\n </ul>\n </SubDoc>\n </DocSection>\n\n {/* ══════ 服务信息 ══════ */}\n <DocSection id=\"service-info\" icon={Terminal} title=\"服务信息\">\n <SubDoc id=\"endpoints\" title=\"服务端点\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">端点</th>\n <th className=\"text-left py-2 px-3 font-medium\">方法</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/upload</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">音频文件上传</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/mcp</td><td className=\"py-2 px-3\">POST</td><td className=\"py-2 px-3\">MCP JSON-RPC 接口</td></tr>\n <tr><td className=\"py-2 px-3 font-mono text-xs\">/health</td><td className=\"py-2 px-3\">GET</td><td className=\"py-2 px-3\">健康检查</td></tr>\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"limits\" title=\"服务限制\">\n <div className=\"overflow-x-auto rounded-lg border border-border/60\">\n <table className=\"w-full text-sm\">\n <thead><tr className=\"border-b border-border/40 bg-muted/30\">\n <th className=\"text-left py-2 px-3 font-medium\">配置项</th>\n <th className=\"text-left py-2 px-3 font-medium\">默认值</th>\n <th className=\"text-left py-2 px-3 font-medium\">说明</th>\n </tr></thead>\n <tbody className=\"divide-y divide-border/30\">\n {[\n ['存储限制','500MB','最大音频存储空间'],\n ['最大排队','10','等待处理的请求数'],\n ['最大并发','3','同时处理的评测数'],\n ['音频有效期','5 分钟','上传后未评测自动过期'],\n ['文件大小限制','50MB','单个音频文件最大'],\n ].map(([k,v,d])=>(\n <tr key={k}><td className=\"py-2 px-3 font-medium\">{k}</td><td className=\"py-2 px-3 font-mono text-xs\">{v}</td><td className=\"py-2 px-3 text-muted-foreground\">{d}</td></tr>\n ))}\n </tbody>\n </table>\n </div>\n </SubDoc>\n <SubDoc id=\"changelog\" title=\"更新日志\">\n <div className=\"space-y-3\">\n {[\n { ver: 'v2.3.0', date: '2026-03-25', desc: '支持 HTTPS,域名 speech-eval.site' },\n { ver: 'v2.2.0', date: '2026-03-24', desc: '添加 MCP 配置指南(Cursor / Claude Desktop)' },\n { ver: 'v2.1.0', date: '2026-03-24', desc: '简化上传流程,统一使用 HTTP API' },\n { ver: 'v2.0.0', date: '2026-03-24', desc: '初始版本,支持多种评测类型' },\n ].map(l => (\n <div key={l.ver} className=\"flex items-start gap-3\">\n <span className=\"font-mono text-xs font-bold bg-muted px-2 py-0.5 rounded shrink-0\">{l.ver}</span>\n <span className=\"text-xs text-muted-foreground shrink-0 mt-0.5\">{l.date}</span>\n <span className=\"text-sm\">{l.desc}</span>\n </div>\n ))}\n </div>\n </SubDoc>\n </DocSection>\n\n </div>\n </div>\n </div>\n </main>\n );\n}\n"}}}}