1. 为什么服务端要统一 Key三类模型各接一套的痛上一篇把「为什么给女儿自建 AI 朋友」讲完了这一篇直接进服务端。我最早那版文字机器人能聊天但语音链路一直没做成卡的地方不是模型本身而是 ASR、TTS、LLM 三类能力各自要一套鉴权、一套地址、一套超时重试。你想想一个三岁小孩对着手机说「播放西游记」背后要跑语音识别把声音转文字、大模型理解并生成回复、语音合成把文字变回声音。三个环节如果分别对接三家服务Key 管理、额度监控、报错排查就是三份工作量任何一环挂了都要单独查。TaoToken 在这里的价值不是「多一个模型」而是把 ASR、TTS、LLM 收敛到同一个 API 入口和同一把 Key。服务端只需要维护一份配置骨架三类能力用同一套鉴权、同一套重试、同一套日志。对家庭本地化场景来说这直接决定了你半夜被孩子叫醒时是改一个配置文件还是翻三个后台。这篇给的是可复制的settings.json与config.toml片段以及一次端到端语音对话验证动作。目标很明确确认本地服务端能稳定调用三类模型而不是停留在「能跑一次」。2. TaoToken 前置Key 与通道准备在动手改配置前先把入口理清楚。TaoToken 的 API 地址是https://taotoken.net/api注意这个地址不带任何查询参数直接作为 base_url 使用。控制台里创建 Key 的入口在 API Keys 页面建议按用途分 Key一个给服务端主链路一个给调试脚本方便出问题时快速定位是配置错还是额度问题。模型对话的在线调试入口可以先用起来确认你要调的模型名拼写正确再写进配置文件。长期跑编码类或 Agent 类任务的话Coding Plan 的额度模型更适合常驻服务不会因为单次调用波动影响体验。注意Key 只放在服务端环境变量或本地配置文件里不要写进客户端 App也不要提交到公开仓库。家庭局域网场景下客户端只连你自己的服务端不直连模型通道。接入文档里有各语言 SDK 的 base_url 写法Python 侧就是 OpenAI 兼容风格把base_url指向https://taotoken.net/api即可。下面所有配置都围绕这个入口展开。3. 可复制配置settings.json 与 config.toml服务端我用 FastAPI 做统一网关配置分两层settings.json管服务级参数端口、日志、Key 引用config.toml管三类模型的调用参数。这样拆的好处是换模型只动 toml不动服务代码。先看settings.json{ server: { host: 0.0.0.0, port: 8000, log_level: info, request_timeout: 60 }, taotoken: { base_url: https://taotoken.net/api, api_key_env: TAOTOKEN_API_KEY, max_retries: 3, retry_backoff: 1.5 }, audio: { sample_rate: 16000, channels: 1, format: wav } }关键点api_key_env指向环境变量名而不是把 Key 明文写进来。启动前export TAOTOKEN_API_KEY你的Key服务读取时用os.environ取。max_retries和retry_backoff是三类能力共用的重试策略避免每个模块各写一套。再看config.toml三类模型分节[llm] model qwen3.5-2b base_url https://taotoken.net/api temperature 0.7 max_tokens 256 num_ctx 8192 keep_alive 30m think false [asr] model sensevoice-small base_url https://taotoken.net/api language zh sample_rate 16000 [tts] model matcha-tts-zh base_url https://taotoken.net/api voice baker speed 1.0这里有个我踩过的坑num_ctx必须三类调用保持一致。之前主对话写 8192、摘要请求写 4096同一个模型两种窗口交替运行时每切换一次就重载首 token 延迟恒定在 10 秒左右。统一成 8192 后模型常驻延迟降到 1 秒出头。think false也是必须的Qwen 系列默认带思考模式会把输出预算吃光正式回答为空。Python 侧读取配置的骨架import json, os, tomllib from openai import OpenAI with open(settings.json, r, encodingutf-8) as f: settings json.load(f) with open(config.toml, rb) as f: config tomllib.load(f) client OpenAI( base_urlsettings[taotoken][base_url], api_keyos.environ[settings[taotoken][api_key_env]], timeoutsettings[server][request_timeout], max_retriessettings[taotoken][max_retries], )这样 LLM、ASR、TTS 三个模块共用同一个client实例鉴权和重试逻辑只维护一份。4. 验证请求一次端到端语音对话配置写完不能只看不跑。验证动作分三步先单独确认 LLM 通再确认 ASR 通最后串起来跑一次完整语音对话。第一步LLM 冒烟测试resp client.chat.completions.create( modelconfig[llm][model], messages[ {role: system, content: 你是小朋友的 AI 朋友回答简短、口语化。}, {role: user, content: 你好呀你叫什么名字}, ], temperatureconfig[llm][temperature], max_tokensconfig[llm][max_tokens], extra_body{think: False, num_ctx: config[llm][num_ctx]}, ) print(resp.choices[0].message.content)跑通会看到一句简短回复比如「我叫小星很高兴认识你」。如果返回空字符串先检查think是否传了 false。第二步ASR 验证。准备一段 16kHz 单声道 WAV调用识别接口with open(test_16k.wav, rb) as f: audio_bytes f.read() asr_resp client.audio.transcriptions.create( modelconfig[asr][model], file(test_16k.wav, audio_bytes), languageconfig[asr][language], ) print(asr_resp.text)这里要提醒一个真实坑Android 录音 API 在部分机型上会静默回退到 AMR-WB 编码文件名却还是.wav。服务端按 WAV 解析就是乱码。正确做法是按文件头魔数嗅探#!AMR-WB开头先用 FFmpeg 转 16kHz WAVRIFF开头才直通。第三步端到端串联。把 ASR 输出喂给 LLM再把 LLM 输出喂给 TTSuser_text asr_resp.text llm_resp client.chat.completions.create( modelconfig[llm][model], messages[ {role: system, content: 你是小朋友的 AI 朋友。}, {role: user, content: user_text}, ], max_tokensconfig[llm][max_tokens], extra_body{think: False, num_ctx: config[llm][num_ctx]}, ) reply_text llm_resp.choices[0].message.content tts_resp client.audio.speech.create( modelconfig[tts][model], voiceconfig[tts][voice], inputreply_text, speedconfig[tts][speed], ) with open(reply.wav, wb) as f: f.write(tts_resp.content)成功结果是reply.wav能正常播放内容是模型对你说的话的语音回复。实测下来从音频输入到音频输出整条链路在本地服务端跑通三类模型都走同一个 base_url 和同一把 Key。5. 本篇常见错排查报错一401 Unauthorized。九成是环境变量没生效。检查echo $TAOTOKEN_API_KEY是否有值以及settings.json里的api_key_env名字是否和实际环境变量名一致。别把 Key 直接写进 json那样换环境容易漏。报错二模型名不存在。ASR、TTS、LLM 的模型名拼写要和控制台里一致。建议先在模型对话入口手动发一条消息确认模型可用再写进config.toml。报错三TTS 合成报错或卡住。子句里混入这类标点会进词表外路径。合成前做一次清洗过滤掉无发音字符。另外切分可能产生纯标点空子句要按「无发音字符」过滤否则长度计算会除零崩溃。报错四首 token 延迟异常高。先看运行时ollama ps里 CONTEXT 是否稳定。如果主对话和摘要请求的num_ctx不一致模型会在两种窗口间反复重载KV Cache 每轮清空。统一窗口、显式传keep_alive、开启 Flash Attention三刀下去延迟能从 10 秒级降到 1 秒级。报错五中文乱码。Windows 下子进程输出按控制台代码页编码GBK 和 UTF-8 都可能出现。解码要做双回退先试 UTF-8失败再试 GBK否则中文全乱。提示排障时优先看服务端日志里的请求耗时和状态码再对照上面五类。接入相关的细节可以查接入文档模型可用性用模型对话入口快速验证。6. 下一步把这条链路交给客户端服务端这套骨架跑通后客户端要做的就简单了录音、推流、播放。但弱机上的录音插件、双模式交互、动态播放队列和打断机制还有华为机型那个「麦克风被系统静音」的坑是下一篇的内容。如果你打算长期跑编码类或 Agent 类任务Coding Plan 的额度模型比按次调用更适合常驻服务。先把这篇的配置骨架落地确认三类模型都能稳定调用再往下走客户端会顺很多。
阅读完成 · 觉得有帮助?