LLM-Chat-API 多模型路由,Base URL 填 TaoToken 后核对 token 计费

📅 发布时间:2026/9/18 13:40:29
LLM-Chat-API 多模型路由,Base URL 填 TaoToken 后核对 token 计费
LLM-Chat-API 多模型路由跑通之后最容易被忽略的不是 route_model而是模型调用凭据。把 Base URL 填成 https://taotoken.net/api再去 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentllm_chat_api_usage 创建 TaoToken Key然后用原压测脚本发一轮请求确认流式输出和 usage 都能拿到最后对照 token 配额看板核对每个模型的调用是否成功。四年前写 Flask 的时候接口层换个数据库连接串最多改一行 config现在维护 LLM-Chat-API路由表把 chitchat 发 gpt-3.5-turbo、长文摘要发 qwen-max、其余走 gpt-4-turboStreamingResponse、Redis 记忆、log_llm_usage 都还在原处。真正让这轮转型卡住的往往不是路由函数写错而是底层 OpenAI 兼容通道从直连切成 TaoToken 之后Key 和 Base URL 没对齐导致看板上只有部分模型有记录。1. 从四年 Flask 到 LLM-Chat-API路由层之前的凭据层1.1 route_model 只管分流不管用哪把钥匙四年前做 Flask我的思维习惯是“业务逻辑在哪配置就在哪”。转到 LLM-Chat-API 之后route_model 的职责其实很窄它只判断用户意图是 chitchat、长文摘要还是默认问答然后返回对应模型名。它不关心这个模型名后续通过哪个 endpoint 发送、用哪把 Key 计费、响应里的 usage 字段长什么样。很多同学习惯性认为改多模型路由就要重写路由表结果一上来改 ROUTE_TABLE反而把原本稳定的分流逻辑打乱了。更稳妥的做法是保留 route_model 原样把模型调用凭据抽到统一客户端里所有分支都走同一个 OpenAI 兼容对象。这样切换底层通道时改动面只有 api_key 和 base_url 两处。1.2 原文里的 log_llm_usage 为什么会在切换后对不上原文的 log_llm_usage 记录 prompt_tokens、completion_tokens 和 latency这三项通常来自 OpenAI 兼容响应里的 usage。问题在于部分 SDK 和部分模型在流式返回时默认不把 usage 塞进每个 chunk如果切换 Base URL 后没有同步打开 stream_options就会出现文本正常输出、看板没有 token 记录的情况。另一种对不上是把模型 ID 从旧文档直接复制过来而新通道的模型广场里该 ID 已经改名或下线。route_model 返回的字符串没变但请求体里的 model 字段已经不再被服务端识别调用自然失败。所以这一轮验证的重点不是“路由准不准”而是“凭据通不通、usage 回没回、看板记没记”。2. 把 OpenAI 兼容客户端指到 https://taotoken.net/api2.1 在环境变量里放 YOUR_API_KEY先别改 Flask 或 FastAPI 的路由文件。打开 TaoToken完成注册后进入控制台创建 API Key把 Key 放到环境变量里不要写死在代码仓库。可以用.env文件配合 python-dotenv也可以直接在启动进程时 export。Key 一律用占位符 YOUR_API_KEY 表示实际值从官网控制台复制。export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_API_KEYYOUR_API_KEY注意这里有两个地址不要混给人点的官网落地页是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentllm_chat_api_usage 填进 OpenAI 客户端的 Base URL 是 https://taotoken.net/api 。后者末尾不要加 /v1也不要拼任何查询参数。很多 404 或路径重复都是因为把落地页地址或带 /v1 的旧习惯带进了代码。2.2 llm_client.py 的 base_url 不要带 /v1在原来的 LLM-Chat-API 项目里找一个集中创建客户端的文件通常叫 llm_client.py、model_client.py 或 services/llm.py。把原来的 OpenAI 客户端替换成下面这段保留 route_model 和业务调用不变。关键点是base_url参数只写 https://taotoken.net/api api_key从环境变量读取。import os from openai import OpenAI TAOTOKEN_BASE_URL os.getenv(TAOTOKEN_BASE_URL, https://taotoken.net/api) TAOTOKEN_API_KEY os.getenv(TAOTOKEN_API_KEY, YOUR_API_KEY) client OpenAI( api_keyTAOTOKEN_API_KEY, base_urlTAOTOKEN_BASE_URL, )如果你用的是 FastAPI可以在依赖注入里复用这个 client如果是 Flask可以挂在 app.extensions 上或者写一个工厂函数。无论哪种都不需要在每个路由里重新 new 一个 OpenAI 对象。TaoToken 在这里只提供 Key 和 Base URL不参与 StreamingResponse 的封装也不参与 Redis 记忆的读写更不参与 route_model 的判断。你的业务代码仍然按原来的方式处理流式生成器、缓存和日志。2.3 ROUTE_TABLE 里的模型 ID 从模型广场抄route_model 里的映射可以先保持原样但模型 ID 必须重新核对。原文把 chitchat 映射到 gpt-3.5-turbo长文摘要映射到 qwen-max其余走 gpt-4-turbo这是旧通道的写法。切到 TaoToken 之后模型是否可用、ID 是否一致以模型广场当时列表为准。打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentllm_chat_api_usage 的模型广场把当前可用的模型 ID 复制到你的 ROUTE_TABLE 或配置中心。不要凭记忆手写也不要把带日期后缀的猜测 ID 当成正式配置。ROUTE_TABLE { chitchat: gpt-3.5-turbo, long_summary: qwen-max, default: gpt-4-turbo, } def route_model(intent: str) - str: if intent chitchat: return ROUTE_TABLE[chitchat] if intent long_summary: return ROUTE_TABLE[long_summary] return ROUTE_TABLE[default]上面这些模型名只是保留原文的分流结构真正上线前请以官网模型广场的实时列表为准。如果某个 ID 不在列表里调用会在服务端被拒绝而 route_model 本身不会报错因为它的返回值只是一个字符串。这也是为什么验证用量时要按模型分别发请求而不是只发一条默认请求。3. 用原压测脚本打穿 chitchat、长文摘要和默认模型3.1 非流式先验 usage先把压测脚本里的 Base URL 和 Key 改成同一套 TaoToken 凭据但第一轮建议用非流式请求因为非流式响应里的 usage 字段最直观。构造三条消息一条短的日常聊天一条长文摘要一条默认技术问答分别调用 route_model 返回的模型名。观察响应对象里是否有usage.prompt_tokens、usage.completion_tokens和总 token 数。如果这一步就报 401先查 Key 是否复制完整、环境变量是否带上了引号或空格。如果报模型不存在回到模型广场重新复制 ID。import time from openai import OpenAI client OpenAI( api_keyYOUR_API_KEY, base_urlhttps://taotoken.net/api, ) def run_non_stream(model: str, prompt: str): start time.time() resp client.chat.completions.create( modelmodel, messages[{role: user, content: prompt}], ) latency time.time() - start usage resp.usage print(model, { prompt_tokens: usage.prompt_tokens, completion_tokens: usage.completion_tokens, latency: round(latency, 3), text: resp.choices[0].message.content[:40], })非流式跑通只能说明 Key 和 Base URL 没问题还不能证明你的 StreamingResponse 也没问题。LLM-Chat-API 如果对外提供流式接口业务代码里通常会把 OpenAI 返回的 chunk 再包一层生成器。这层包装不会因为 Base URL 改变而失效但它会掩盖 usage 的缺失。所以下一轮必须回到流式模式专门看最后一个 chunk。3.2 流式补上 stream_options流式请求要拿到 usage需要在请求体里加stream_options{include_usage: True}。这个参数不是所有旧版 SDK 都支持建议先把 openai 包升到较新的版本。然后按下面对方式写一个最小验证脚本一边拼文本一边记录最后一个 chunk 的 usage。注意流式返回时 usage 可能只在最后一个 chunk 出现前面的 chunk 的 usage 为 None这是正常现象。def run_stream(model: str, prompt: str): stream client.chat.completions.create( modelmodel, messages[{role: user, content: prompt}], streamTrue, stream_options{include_usage: True}, ) parts [] usage None for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: parts.append(chunk.choices[0].delta.content) if getattr(chunk, usage, None): usage chunk.usage prompt_tokens usage.prompt_tokens if usage else None completion_tokens usage.completion_tokens if usage else None print(model, .join(parts)[:40], prompt_tokens, completion_tokens)把 chitchat、长文摘要和默认模型各跑一遍。如果文本能流出来但 usage 始终是 None先确认stream_options有没有拼错再确认当前模型是否支持返回 usage。有些兼容通道对 usage 的返回时机和直连略有差异但字段名通常还是 prompt_tokens 和 completion_tokens。如果差异很大先用非流式把计费链路跑通再考虑是否要在业务层做兜底统计。3.3 把 prompt_tokens / completion_tokens / latency 接回 log_llm_usage原文的 log_llm_usage 是看板的数据来源切换 Base URL 后这个函数不需要重写只需要确认传参没有被流式包装截断。下面是一个保底写法非流式直接用响应里的 usage流式在生成器结束后把累计的 usage 传进去。如果原项目用 Redis 或数据库记录保持原来的写入方式不要把 TaoToken 的地址写进存储层。def log_llm_usage(model: str, prompt_tokens: int, completion_tokens: int, latency: float): record { model: model, prompt_tokens: prompt_tokens, completion_tokens: completion_tokens, latency: latency, } # 这里保留你原来的写入逻辑Redis、SQLite、日志或看板 print(usage_record, record)验证时把 log_llm_usage 的打印打开对照压测脚本里的模型名、token 数和延迟。如果文本输出正常但 log_llm_usage 没打印说明生成器没有被消费到底或者 usage 提取逻辑有问题。如果打印了但 token 数为 0优先检查是否把include_usage加在了错误的位置。TaoToken 只负责把请求送到模型并返回 usage它不会替你补写日志所以这一步的业务埋点仍然要自己核对。4. token 配额看板对不上时先查这五个点4.1 401 与 Key 空格最常见的 401 不是 Key 失效而是复制时带上了换行或空格。从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentllm_chat_api_usage 创建 Key 后先粘贴到文本编辑器里看一眼首尾再放进环境变量。如果是 Docker 或 systemd 启动确认环境变量确实传进了进程而不是只在当前 shell 生效。另外OpenAI 兼容客户端通常会自动加 Bearer 前缀你只需要填原始 Key不要手动拼Bearer。4.2 usage 为空的两种常见原因第一种是流式请求没加stream_options{include_usage: True}第二种是 SDK 版本过旧即使加了参数也不会解析。解决顺序是先升级 openai 包再用非流式请求确认响应里有 usage最后回到流式请求。如果业务层自己实现了 SSE还要确认最后一个 chunk 没有被业务代码过滤掉。很多 LLM-Chat-API 的流式包装只转发delta.content却把包含 usage 的空 choices chunk 丢了看板自然没有记录。4.3 看板延迟与模型 ID 写错控制台的用量统计通常不是实时毫秒级跑完压测后等几十秒到一两分钟再刷新。如果长时间没有记录检查 route_model 返回的模型 ID 是否和模型广场一致。比如 chitchat 分支仍返回旧 ID请求会被服务端拒绝看板只会显示失败调用或者干脆不显示。把三个分支的模型名逐个打印出来和模型广场的列表对一遍比盲目重试更有效。4.4 别让 StreamingResponse 和 Redis 背锅Base URL 改变不会影响 FastAPI 的 StreamingResponse也不会影响 Flask 的流式生成器。Redis 记忆的读写发生在你的业务层和模型通道无关。如果切换后流式输出正常、usage 也拿到了但 Redis 里的会话记录没更新应该去查业务代码里的缓存键和过期时间而不是改 Base URL。TaoToken 不介入这些环节所以排障时要先把网络通道问题和业务状态问题分开。5. 跑通一次多模型请求后去控制台对账5.1 在模型对话里用同一把 Key 复现压测脚本跑通之后先别急着把这套配置推到所有环境。打开 TaoToken 模型对话用同一把 YOUR_API_KEY 和同一个模型 ID 发一条测试消息。模型对话页面能帮你排除代码里的 Base URL 拼写问题也能快速对比流式输出是否正常。如果这里能通、代码里不通大概率是环境变量没生效或者客户端初始化时用了旧地址。5.2 控制台 API Keys 与用量页然后进入 控制台 API Keys确认刚才压测用的 Key 还在、没有过期并查看对应的用量记录。重点核对三件事调用时间是否对得上、模型名是否和 route_model 返回值一致、token 消耗是否接近压测脚本打印的数值。如果看板里有调用但 token 数偏低先检查是否命中了缓存或是否有一部分请求走了非流式分支没被记录。5.3 长期跑之前看 Coding Plan如果你的 LLM-Chat-API 要长期给团队或线上业务用建议在 Coding Plan 里看当前套餐是否覆盖多模型并发。多模型路由的特点是 chitchat 请求量大但单次 token 少长文摘要请求量小但 token 消耗高默认问答介于两者之间。把这三种流量拆开看比只看总调用次数更能判断额度是否够用。后面如果要用 Claude Code 之类的工具生成对照代码环境变量对照可以看 Claude Code 接入文档但记住工具只负责生成和解释执行 SQL 或诊断命令仍然要在本地终端自己跑。配完这轮之后最踏实的感觉不是路由函数写得多优雅而是看板上每个模型都有一笔清楚的 token 记录。chitchat 走短请求、长文摘要走大 token、默认问答走另一条模型三条线在同一个 Base URL 下各记各的账这才算把四年 Flask 的手感和 LLM-Chat-API 的计费真正接上了。