|
|
0d9fb26244
|
feat(adapters): qwen38 —— tokenplan/discovery-api 适配器
接 https://discovery-api.intern-ai.org.cn/v1(qwen3.8-27b 等 10 个模型,5 个支持
视觉)。这是一个 OpenAI 兼容端点,但有 Stock OpenAI 适配器处理不了的地方。
## 为什么需要专用适配器
**① 思考内容占绝大部分输出,且是独立字段**
实测视觉问答:completion=303,其中 reasoning=295 —— 97% 的 token 预算用在
思考上,只有 8 token 变成正文。
后果是**静默失败**:直接透传上游的 content,调用方看到的是空字符串,看起来
像模型坏了。实测 max_tokens=60 时上游返回 HTTP 200 + content=null +
finish_reason="length" —— 一个健康的请求,什么都没产出。
所以思考内容走自己的字段,绝不折进 content:折进去比丢掉更糟,页面会把模型
的内心戏当成答案显示。
**② `finish_reason: "length"` 配 `content: null` 是正常的**
不是上游故障。上报真实的 finish_reason 和已产生的部分思考,让调用方能区分
「预算用完了」和「上游挂了」。
**③ 服务端会改写模型名**
请求 `qwen3.8-27b`,返回 `model: "qwen3.8-27b-fp8"`。响应里的 model 才是
「谁真的服务了这次请求」,计费按它记账。适配器**不改回去** —— 改回去会让审计
记录撒谎。
## 踩到并修正的一个真 bug
reasoning_tokens 最初放在 `token_usage.reasoning_tokens`(顶层)。类型检查
通过,然后被丢弃。
internal/gateway/chat.go 只在一个地方读 reasoning:
resp.TokenUsage.CompletionTokensDetails.ReasoningTokens
实测证据:模型报 completion=88,网关响应里 reasoning=null。字段在正确的地方、
错误的层级,等于没有。
判据因此改成断言**路径**而不是断言「某处有个数字」,变异把顶层放回去立刻变红。
(顺带发现:deepseek adapter 根本没发这个字段,所以线上 reasoning 计费一直
是 0。那是另一个缺口,本适配器不依赖它被修。)
## 配置时要知道的一件事
远程图片 URL 由**上游自己去拉**,会稳定超时(实测 Wikimedia 图片返回
media_fetch_failed)。图片必须用 data: 内联。
## 判据(9 条,夹具全是真实响应)
- 思考与正文分离,且互不污染
- 截断响应上报 finish_reason 与部分思考
- reasoning_tokens 走正确路径,且是 completion 的子集
- tool_calls 的 arguments 从 JSON 字符串解码
- 流式:思考 chunk 不漏、正文 chunk 不丢、末尾 usage 带 reasoning
- 媒体失败带 code(media_fetch_failed),invalid_api_key 不带(免得重复)
- 带 tool_calls 的 assistant 轮补 reasoning_content(否则下一轮被拒)
- 多模态 content 数组原样透传
变异自证九次全部变红,其中三次是本次专门补的:reasoning 折进 content、
顶层 vs 嵌套路径、设成 0。
## 隔离实例实测(生产配置 + 真实上游)
非流式 completion=252 reasoning=167 ✓ 透出
流式 26 chunk / 714ms,正文 37 字符,思考 55 字符 ✓ 分离
视觉 base64 图片 → "白色。" reasoning=44 ✓ 无损
Co-Authored-By: ModelRouter <noreply@modelrouter.dev>
|
2026-10-06 14:53:15 +08:00 |
|