用一把 genmux 密钥在 OpenCode 里跑 DeepSeek V4
2 分钟阅读作者 genmux API Team
OpenCode 是一个终端里的编程智能体,能对接任何兼容 OpenAI 的后端;而 genmux 现在提供了智能体离不开的那一样东西:函数调用。从 v0.4.0 起,POST /v1/chat/completions 接受 tools、返回 tool_calls,并像 OpenAI 那样流式推送 delta.tool_calls——所以 OpenCode 通过 DeepSeek V4 读文件、改文件、跑测试的方式,和它对接任何托管模型时完全一样。本文讲配置、模型选择,以及一次会话诚实的成本估算。
配置块
把这段放进项目里的 opencode.json(想对所有项目生效就放 ~/.config/opencode/opencode.json):
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"genmux": {
"npm": "@ai-sdk/openai-compatible",
"name": "genmux",
"options": { "baseURL": "https://api.genmux.tech/v1", "apiKey": "{env:GENMUX_API_KEY}" },
"models": {
"deepseek-v4-flash": { "name": "DeepSeek V4 Flash" },
"deepseek-v4-pro": { "name": "DeepSeek V4 Pro" },
"qwen3-max": { "name": "Qwen3-Max" }
}
}
}
}然后导出密钥并启动:
export GENMUX_API_KEY=nb_live_...
opencode --model genmux/deepseek-v4-flash
# or one-shot, for scripts and CI:
opencode run --model genmux/deepseek-v4-flash "add a --json flag to cmd/report and update the README"{env:GENMUX_API_KEY} 让密钥不落进文件里,如果 opencode.json 会被提交,这一点很重要。models 映射只影响 OpenCode 选择器里显示什么——列进去之后,七个支持工具调用的 id 都能以 genmux/<id> 的形式使用。
Flash 还是 Pro
两个 V4 模型都支持工具、都有 100 万 token 上下文,也都能思考(reasoning_effort 对应思维链的开关)。差别在价格和深度:
| 模型 | 用在哪 | 每 100 万 token 价格(输入 / 输出) |
|---|---|---|
deepseek-v4-flash | 日常智能体循环:读代码、小改动、跑测试、写提交信息 | $0.198 / $0.594 |
deepseek-v4-pro | 硬核重构、跨文件改动、「这个测试为什么挂」的排查 | $0.594 / $1.782 |
一个实用做法:默认跑 Flash,当智能体开始原地打转时用 /model 切到 Pro。在我们的智能体风格工具评测中,Flash 得分 19/20,Pro 得分 20/20(细节见模拟函数调用那篇),所以选默认值并不是在可靠性上让步——让步的是模型在动手之前推理得有多深。上面配置块里放了 qwen3-max,是因为它多了视觉能力,当任务从一张截图开始时很有用。
从 OpenCode 这一侧看,工具调用是什么样
OpenCode 自带二十来个工具——read、write、edit、bash、glob、grep、list、webfetch、一个待办列表,还有几个——并在每次请求里把它们全部作为 JSON schema 放进 tools 数组。genmux 把这些定义渲染进 system prompt,把模型的回复流式送回,再把模型的调用块变成一个正规的 delta.tool_calls 分块,带上 finish_reason: "tool_calls"。OpenCode 完全看不出区别:它收到一次工具调用、在本地执行、把结果作为 role: tool 消息追加进去,然后再问一次。
线上的一个轮次,节选:
→ POST /v1/chat/completions {"model":"deepseek-v4-flash","stream":true,"tools":[…20 tools…],"messages":[…]}
← data: {"choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_7f…","type":"function","function":{"name":"read","arguments":"{\"filePath\":\"cmd/report/main.go\"}"}}]}}]}
← data: {"choices":[{"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":6814,"completion_tokens":31,"total_tokens":6845,"cost":0.109,"cost_source":"estimated"}}
← data: [DONE]
→ POST … messages now end with {"role":"tool","tool_call_id":"call_7f…","content":"package main\n…"}有两个特性值得知道。模型通常每轮发一次调用,而不是并行一批;OpenCode 是循环的,所以改五个文件是五次短往返,而不是一次长往返。另外,模型在调用之前写的那句话(「我先读一下这个文件」)会作为普通内容流出来,所以终端界面在调用被拼装的同时仍在讲述进度。
一次会话要花多少钱
工具定义不是免费的:它们被渲染成文本,所以每次请求都算 prompt token——每个工具约 40 个 token 外加它的 schema,对 OpenCode 这一套来说每次请求大约 1.5K token。不过更大的开销来自对话历史:每次请求都会把目前为止读过的一切重新发一遍。
拿一次现实的会话来算——40 次请求,prompt 随着文件被读取从 3K 涨到 25K token(平均 12K),回复平均 400 token。也就是 48 万 prompt token 和 1.6 万 completion token:
| 模型 | Prompt(48 万) | Completion(1.6 万) | 整场会话 | 其中工具占(6 万) |
|---|---|---|---|---|
deepseek-v4-flash | $0.095 | $0.010 | ≈ $0.105 | $0.012 |
deepseek-v4-pro | $0.285 | $0.029 | ≈ $0.314 | $0.036 |
qwen3-max | $0.518 | $0.086 | ≈ $0.605 | $0.065 |
按 Scale 套餐单价(每积分 $0.0125)折算成积分,就是 8.4 积分≈ $0.10Flash,40 次请求 对 25.1 积分≈ $0.31Pro,40 次请求。工具开销在 Flash 上大约落在一美分——真正占大头的是历史,这也是为什么 /compact(或者每个任务开一场新会话)比精简工具省下的钱多得多。
排查
400 tools_unsupported——模型 id 属于不支持工具的那三个之一(qwen3-6-flash、doubao-seed-2-lite、doubao-seed-2-mini)。函数调用是模拟实现的,只在评测通过的地方开启:两个 V4 模型、deepseek-v3、qwen3-7-max、qwen3-max、doubao-seed-2-pro 和 doubao-seed-2-code。GET /v1/models 里以 params_schema.tools 展示这个标志。
模型用自然语言作答,而没有调用工具。 tool_choice: required 和指定函数只是模型可以忽略的指令——小模型有时就会忽略。OpenCode 用的是 auto,所以实际表现是你多说一句「继续」,而不是循环坏掉。在我们那一轮里,Pro 20 次一次都没忽略。
max_tokens 相关的抱怨。 OpenCode 对每个模型都会发 32K 起步的 max_tokens。genmux 会把它收敛到该模型的 max_output_tokens,而不是拒绝请求,所以没什么要配置的。工具模型的默认回复预算是 8192 token,足够整文件重写。
一次调用以文本形式到达。 如果回复里出现了原始的 <function_call> 块而不是工具卡片,说明解析器没能解出来——请带上模型 id 和那段回复提个 issue;我们为每个模型跟踪一个 malformed 计数,模型出现漂移时就扩展解析器。
开始使用
从控制台拿一把密钥,粘贴上面那段配置,然后 opencode --model genmux/deepseek-v4-flash——整个迁移就这么多。Aider、Cline、Roo Code 和 Continue 的方式相同——Aider 有单独一篇,每个客户端的简版都在编程智能体页面。tools、tool_choice 和 response_format 的完整参数说明见文档。