1 line
1.8 MiB
Plaintext
1 line
1.8 MiB
Plaintext
{"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>· 大模型自动发现&调用评测工具</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 <your_api_key></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/{id}</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/{id}</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>(或搜索 "MCP")</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 <MCP_API_KEY></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="medium",质量与延迟平衡);\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>"请评测我的英文发音,文本是 Hello"</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="en.word.score", ref_text="Hello")</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 "结束"</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>· 大模型自动发现&调用评测工具</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 <your_api_key></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/{id}</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/{id}</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>(或搜索 "MCP")</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 <MCP_API_KEY></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="medium",质量与延迟平衡);\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>"请评测我的英文发音,文本是 Hello"</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="en.word.score", ref_text="Hello")</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 "结束"</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>· 大模型自动发现&调用评测工具</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 <your_api_key></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/{id}</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/{id}</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>(或搜索 "MCP")</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 <MCP_API_KEY></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="medium",质量与延迟平衡);\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>"请评测我的英文发音,文本是 Hello"</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="en.word.score", ref_text="Hello")</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 "结束"</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>· 大模型自动发现&调用评测工具</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>(或搜索 "MCP")</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 <MCP_API_KEY></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="medium",质量与延迟平衡);\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>"请评测我的英文发音,文本是 Hello"</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="en.word.score", ref_text="Hello")</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 "结束"</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>· 大模型自动发现&调用评测工具</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>(或搜索 "MCP")</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 <MCP_API_KEY></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="medium",质量与延迟平衡);\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>"请评测我的英文发音,文本是 Hello"</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="en.word.score", ref_text="Hello")</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 "结束"</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>· 大模型自动发现&调用评测工具</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>(或搜索 "MCP")</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 <MCP_API_KEY></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>"请评测我的英文发音,文本是 Hello"</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="en.word.score", ref_text="Hello")</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 "结束"</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>· 大模型自动发现&调用评测工具</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>(或搜索 "MCP")</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 <MCP_API_KEY></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>"请评测我的英文发音,文本是 Hello"</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="en.word.score", ref_text="Hello")</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 "结束"</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>· 大模型自动发现&调用评测工具</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>(或搜索 "MCP")</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 <MCP_API_KEY></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>"请评测我的英文发音,文本是 Hello"</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="en.word.score", ref_text="Hello")</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 "结束"</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>· 大模型自动发现&调用评测工具</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>(或搜索 "MCP")</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 <MCP_API_KEY></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>"请评测我的英文发音,文本是 Hello"</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="en.word.score", ref_text="Hello")</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 "结束"</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>· 大模型自动发现&调用评测工具</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>(或搜索 "MCP")</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 <MCP_API_KEY></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>"请评测我的英文发音,文本是 Hello"</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="en.word.score", ref_text="Hello")</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 "结束"</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>· 大模型自动发现&调用评测工具</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>(或搜索 "MCP")</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>"请评测我的英文发音,文本是 Hello"</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="en.word.score", ref_text="Hello")</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 "结束"</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>(或搜索 "MCP")</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>"请评测我的英文发音,文本是 Hello"</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="en.word.score", ref_text="Hello")</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 "结束"</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\">请帮我评测这个音频的英文句子发音,参考文本是 "The quick brown fox jumps over the lazy dog."</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>(或搜索 "MCP")</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\">请帮我评测这个音频的英文句子发音,参考文本是 "The quick brown fox jumps over the lazy dog."</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>(或搜索 "MCP")</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"}}}} |