export const builds = { flash: { title: "Flash", directory: "flash", modelDirectory: "Sophon-Realtime-32B-Flash", flow: ["用户语音", "共享 32B 理解与文本", "Flash 语音头", "24 kHz 语音"], detail: "声音侧以每 80 ms 帧一次前向为设计。作者报告单 H100 HTTP 首包中位数 0.31 s,实例约 55 GB 显存。", modes: ["http", "engine", "realtime", "live"], }, tag: { title: "Tag", directory: "tag", modelDirectory: "Sophon-Realtime-32B-Tag", flow: [ "用户语音", "共享 32B 与声音标签", "tag-tts 语音模型", "Flow + HiFT 语音", ], detail: "模型写出笑声、叹气等声音标签,由专用语音模块表现。作者报告单 80 GB GPU、实例约 65 GB 显存。", modes: ["http", "engine", "realtime", "live"], }, e2e: { title: "E2E 预览", directory: "", modelDirectory: "Sophon-Realtime-32B-E2E", flow: [ "用户语音", "32B Thinker 与 Talker", "连续语音向量", "E2 解码与语音", ], detail: "采用独立推理引擎与 /ws 网关,文档要求两张 80 GB GPU。当前作为研究预览,部署文档与模型目录版本需逐项固定。", modes: ["e2e"], }, }; export const callModes = { http: { title: "HTTP 音频生成", kind: "POST", endpoint: "http://HOST:8000/v1/chat/completions", detail: "先用完整 WAV 验证语音生成。官方客户端先取 /v1/models,再流式解码文本与音频。", }, engine: { title: "基础引擎双工", kind: "WebSocket", endpoint: "ws://HOST:8000/v1/realtime", detail: "持续推送音频,模型参与话轮决策。原生工具桥需要自行对接,默认 mock 与真实执行分别核验。", }, realtime: { title: "网关函数调用", kind: "WebSocket", endpoint: "ws://HOST:8780/v1/realtime", detail: "启动 Live 网关后,使用其 Realtime 适配器接收 function_call 与回填结果。当前协议覆盖有边界。", }, live: { title: "Live 客户端委派", kind: "WebSocket", endpoint: "ws://HOST:8780/v1/live/sessions", detail: "前台生成 delegate(task),客户端接委派事件,并把后台结果经 commentary.append 返回。", }, e2e: { title: "E2E 自有协议", kind: "WebSocket", endpoint: "ws://HOST:8000/ws", detail: "按 E2E 文档使用 config、二进制 PCM16、text、barge_in、done 等事件。", }, }; export function makeRecipe(buildKey, modeKey) { const build = builds[buildKey]; if (!build || !build.modes.includes(modeKey)) { throw new RangeError( "The selected build does not support this documented path.", ); } if (modeKey === "e2e") { return { startup: `# 在已准备 Linux / CUDA / 权重的环境中 SKIP_DOWNLOAD=1 MODEL_DIR=/path/to/${build.modelDirectory} bash scripts/install.sh SOPHON_MODEL_DIR=/path/to/${build.modelDirectory} THINKER_GPU=0 TALKER_GPU=1 bash scripts/start.sh`, request: `连接 ws://HOST:8000/ws 后先发送 {"type":"config","lang":"zh","history":true} 随后持续发送 16 kHz、单声道 PCM16 二进制音频。 接收 text / 二进制 24 kHz 音频 / barge_in / metrics / done。`, }; } const startup = `# 在官方仓库 ${build.directory}/ 中,权重须预先准备 SKIP_DOWNLOAD=1 bash install.sh MODEL_DIR=/path/to/${build.modelDirectory} GPU=0 PORT=8000 bash start.sh # 等待 READY 与预热完成`; if (modeKey === "http") { return { startup: `${startup} python client_example.py --url http://HOST:8000 --wav question.wav --lang zh --voice chelsie`, request: `先从 GET /v1/models 取得模型 ID。 POST /v1/chat/completions { "model": "<返回的模型 ID>", "stream": true, "modalities": ["text", "audio"], "language": "zh", "messages": [ {"role":"system","content":""}, {"role":"user","content":[ {"type":"text","text":"<|audio|>"}, {"type":"input_audio","input_audio":{"data":"","format":"wav"}} ]} ] }`, }; } if (modeKey === "engine") { return { startup, request: `连接 ws://HOST:8000/v1/realtime {"type":"session.update","session":{"extra_body":{"auto_response":true,"language":"zh"}}} {"type":"input_audio_buffer.append","audio":"<持续发送的 PCM16_BASE64>"} 接真实工具时,额外配置 tools / tool_mode=client / bridge_key。 工具桥默认在引擎本机 PORT+700,需配套执行与回填逻辑。`, }; } const gatewayStartup = `${startup} # 同一环境启动外层网关 cd ../live SOPHON_LIVE_ASR=/path/to/Qwen3-ASR-0.6B-hf BUILD=${build.directory} PORT=8000 LIVE_PORT=8780 bash start_live.sh`; if (modeKey === "realtime") { return { startup: gatewayStartup, request: `连接 ws://HOST:8780/v1/realtime 后配置工具 {"type":"session.update","session":{"instructions":"简短回答用户","tools":[ {"type":"function","name":"get_weather","description":"查询城市天气", "parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}} ]}} 收到 function_call 后,由客户端执行真实函数,再回填 {"type":"conversation.item.create","item":{"type":"function_call_output","call_id":"<返回的 call_id>","output":"<真实结果>"}} {"type":"response.create"}`, }; } return { startup: gatewayStartup, request: `连接 ws://HOST:8780/v1/live/sessions {"type":"session.start","session":{"instructions":"用中文简短回答","delegation":{"type":"client"}}} {"type":"session.input_audio.append","audio":""} 接收 session.delegation.created,后台完成后回填 {"type":"session.commentary.append","delegation_id":"<返回的委派 ID>","content":"<真实后台结果>"}`, }; } export const toolSteps = [ { title: "提供函数定义", explanation: "调用方声明函数名、说明与参数 schema。运行时经 chat template 把这些定义提供给模型。", code: '{"type":"function","name":"get_weather","parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}', }, { title: "模型生成调用", explanation: "模型可以先给一句承接语,再输出工具名与参数。下面的天气任务为合成示例。", code: '我帮你查一下。\n\n{"name":"get_weather","arguments":{"city":"上海"}}\n', }, { title: "口语流与控制流分开", explanation: "cut_tool 保留调用标记前的口语。完整或尚未写完的工具标记会被拦住,避免进入语音合成。", code: '送往语音模块\n我帮你查一下。\n\n留给工具解析器\n{"name":"get_weather","arguments":{"city":"上海"}}', }, { title: "解析与桥接", explanation: "Thinker 写完后,服务解析函数调用,发出带 call_id、name 和 arguments 的工具桥事件。此时承接语音可能仍在播放。", code: '{"type":"tool_call","call_id":"call_demo","name":"get_weather","arguments":{"city":"上海"}}', }, { title: "客户端执行真实函数", explanation: "真实执行端取出函数和参数,调用业务系统。mock 模式则返回模拟值。两类证据必须分开记录。", code: 'result = await actual_weather_service(city="上海")\n# 记录真实调用 ID、参数、返回、耗时与最终状态', }, { title: "回填结果", explanation: "结果经客户端工具桥,或网关的 function_call_output / commentary.append 返回。下方内容显式标为示意。", code: '{"type":"function_call_output","call_id":"call_demo","output":"示例结果:天气晴朗"}', }, { title: "模型继续回应", explanation: "结果被加入工具历史,后续语音从该结果继续生成。正确选择函数、真实执行和正确告知各自评分。", code: "role: tool\n示例结果:天气晴朗\n\nassistant\n查到了,示例结果显示天气晴朗。", }, ];