OpenCode CLI 配 TaoToken:智能体会话管理一条 Key 走通
2026/9/18 18:11:34 网站建设 项目流程

opencode CLI 的智能体会话管理,用 Python 包一层挺省事:create_session 里拼一次opencode run,send_message 带上会话 ID 续一次,export 把历史会话落成文本。麻烦的是每跑一次都要先确认这次走哪条模型通道、用哪把 Key。这次把接入选型收拢到 TaoToken:打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建一把 Key,写进 opencode.json 的 provider,之后 session list、run、export 三条路径共用同一把 Key 走兼容通道。

下面按原文的推进顺序走:先看封装脚本里通道和 Key 散在哪些位置,再把 Key 申请出来,接着是 opencode.json 里 provider 块的写法、Python 侧的最小改动、跑通后的验证动作,最后是几个 opencode 侧特有的报错。全程只改配置和几行 Python,不重写业务逻辑。

1. Python 封装 opencode 之后,通道和 Key 被写进了每个函数调用

1.1 原文那套 create_session / send_message 卡在哪

原文的封装思路很直接:用 subprocess 调 opencode CLI,一个 Python 函数对应一类会话操作。create_session 起一个新会话,send_message 带着会话 ID 继续聊,export 把某次会话的完整记录写出来。这三条路径本身没问题,问题在于它们各自都揣着一份「接入信息」,而且往往是不同的写法。

def create_session(workdir: str) -> str: return subprocess.run( ["opencode", "run", "--model", "SOME_MODEL_ID", "--dir", workdir, "先梳理改动点"], capture_output=True, text=True, ).stdout def send_message(session_id: str, text: str) -> str: return subprocess.run( ["opencode", "run", "--session", session_id, text], capture_output=True, text=True, ).stdout

两个函数摆在一起,问题就露出来了:第一个显式指定了--model,第二个什么都没写,靠的是 opencode 的默认模型。于是「这次到底走哪条通道」取决于运行时的环境变量、当前目录下的配置文件、以及上一次会话记录里存了什么,三者任意一个变了,行为就不一样。换台机器、换个终端、换个 workdir,都可能把同一条 prompt 送到一个你没预期的通道上。

再往下还有第二层麻烦。会话列表那一步通常靠扫本地会话存储目录拿到 session ID,而导出那一步是把会话记录文件读出来做格式化。如果存储记录里保存的模型名和实际调用时用的模型名对不上(一个是裸 ID,一个是带 provider 前缀的写法),导出报表里同一次会话会被切成两类统计。这类问题不会报错,只会让你在某天对账时发现数字很怪。

1.2 把「接入选型」从业务代码里剥出来

修法不是给每个函数都补一遍参数,而是分层:Python 层只负责编排——什么时候开会话、什么时候续聊、什么时候导出;模型通道交给 opencode 自己的配置文件去决定。opencode 支持在配置里声明自定义 provider,把 baseURL、apiKey、模型列表写进去,opencode run每次启动读一次配置,之后所有子命令都吃同一份。

这么改之后,Python 里那些--model参数可以整批删掉。只有需要临时跨模型做 A/B 对比时,才用命令行参数覆盖一次,属于例外而非常态。判断标准很简单:如果你的封装函数里出现了模型名、URL、Key 中的任何一个,就说明这一层管得太多了。

2. 去 TaoToken 建一把 Key,再想清楚它放在哪一层

2.1 申请密钥这一步,顺手把模型 ID 抄下来

打开 TaoToken 注册登录,进控制台创建 API Key。Key 通常只在创建时完整显示一次,复制之后先放进密码管理器或者本机一个不进版本库的私有文件里。别把它写进.env.example、别写进 README 的示例片段、更别直接 commit——这些位置后来都会被截图发出去。

创建完 Key,旁边就是模型广场,把准备给 opencode 用的模型 ID 原样抄下来。这个字符串后面要一模一样地写进 opencode.json,大小写、连字符、版本后缀都别自己改,也不要凭记忆拼一个「看起来差不多」的名字。模型广场里的列表就是当时的可用清单,以它为准,不要拿网上旧文章里的 ID 直接套。

另外值得顺手确认一遍:这个 Key 打算给几个项目共用。如果只是本机跑 opencode 做会话管理,一把就够;如果 CI 里也要跑,建议另建一把,方便单独吊销和单独看用量。

2.2 Key 的三种放法,选最不容易漏的那种

放法换机器成本泄漏风险适用场景
写死在 Python 常量改代码高,容易跟着提交走不建议
.env用 python-dotenv 读复制文件中,取决于 .gitignore单人临时调试
放 shell 环境变量,配置里用{env:...}引用重新导出一次长期使用,推荐

推荐第三种的原因不是洁癖,而是它同时解决了「Python 子进程」和「opencode 本体」两个执行入口。opencode 由 subprocess 拉起来时,环境变量天然被继承;你手动在终端敲opencode run调试时,同一份变量也在。配置里不出现明文 Key,仓库里也就不会出现。

# 追加到 ~/.zshrc 或 ~/.bashrc export TAOTOKEN_API_KEY=YOUR_API_KEY

写完记得新开一个终端窗口,或者source ~/.zshrc生效一次。用 IDE 内置终端的人尤其要注意:有些 IDE 是从图形界面启动的,不会加载你的 shell rc 文件,变量在里面可能是空的,这是后面 401 报错最常见的来源。

3. opencode.json 的 provider 块:baseURL 只写 https://taotoken.net/api

3.1 一份能直接照抄的配置

opencode 的配置可以放全局,也可以放项目根目录。先给一份完整可用的 provider 声明,路径取~/.config/opencode/opencode.json

{ "$schema": "https://opencode.ai/config.json", "provider": { "taotoken": { "npm": "@ai-sdk/openai-compatible", "name": "TaoToken", "options": { "baseURL": "https://taotoken.net/api", "apiKey": "{env:TAOTOKEN_API_KEY}" }, "models": { "YOUR_MODEL_ID": { "name": "YOUR_MODEL_ID" } } } }, "model": "taotoken/YOUR_MODEL_ID" }

三个地方值得单独说。第一,options.baseURLhttps://taotoken.net/api,末尾不要加/v1,SDK 会自己在后面拼具体路径,多写一层会直接 404。第二,apiKey{env:TAOTOKEN_API_KEY}这种引用写法,不要贴明文。第三,models里的 key 必须是模型广场上真实存在的 ID,name字段是给人看的显示名,写一样就行。

要区分两类地址:上面这个https://taotoken.net/api是填进工具、给程序调用的;注册账号、创建 Key、看模型广场和用量,走的是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end ,两边的用途不要混着填。

3.2 provider 名、model 前缀、模型 ID 三者要对上

最容易错的是前缀。上面配置里 provider 的 key 叫taotoken,那么顶层的"model"就必须写成taotoken/YOUR_MODEL_ID,斜杠前面是 provider 名,后面是模型 ID。有人把 provider 改名成tt,却忘了同步改 model 前缀,结果 opencode 报找不到 provider——它不是在说网络有问题,是在说这个名字在配置里没定义。

模型 ID 同理。如果你在模型广场看到的是带版本或带命名空间的长字符串,就整段抄进去,别只取后半截。opencode 会把provider/model拆开发给对应的 SDK,拆错了就是一个 unknown model。

3.3 全局配置和项目配置怎么选

常用的两三个模型,写进全局配置,这样任何目录下敲opencode run都有默认通道。某个仓库需要固定用另一个模型,就在那个仓库根目录放一份opencode.json,只覆盖model字段。项目级配置优先级更高,合并规则以 opencode 当前版本的文档为准。

这里有个隐蔽的坑:opencode 是按当前工作目录向上找配置的,而你的 Python 封装里如果给subprocess.run传了cwd=/some/other/path,读到的就是那个路径下的配置。会话跑得「像是换了模型」但你又没改过配置时,先检查cwd,再检查配置。

4. 让 session list、run、export 共用同一把 Key

4.1 opencode run 子进程到底继承了什么

subprocess.run的行为规则很简单也很关键:不传env参数时,子进程完整继承父进程的环境变量;一旦传了env,就是整体替换,不是合并。很多封装代码为了「干净」传了一个手工拼的字典,里面只有 PATH,结果TAOTOKEN_API_KEY直接消失,opencode 启动时配置里的{env:TAOTOKEN_API_KEY}解析成空字符串,最终表现为 401。

会话列表和导出这两步虽然不发起模型调用,但它们读的是同一个会话存储目录。只要 run 那一步稳定走同一条通道,后两步拿到的记录就是一致的,不需要额外配 Key。

4.2 Python 封装里最小改动

改动集中在两处:去掉所有--model参数,以及确保环境变量被继承。

import os import subprocess WORKDIR = "/path/to/your/repo" def _env() -> dict: # 用 copy(),不要手拼一个只含 PATH 的空字典 return os.environ.copy() def run_opencode(prompt: str, session_id: str | None = None) -> str: cmd = ["opencode", "run", "--dir", WORKDIR] if session_id: cmd += ["--session", session_id] cmd.append(prompt) result = subprocess.run(cmd, capture_output=True, text=True, env=_env()) if result.returncode != 0: raise RuntimeError(result.stderr.strip() or "opencode run failed") return result.stdout.strip()

create_session就是不传session_id调一次run_opencodesend_message就是带上会话 ID 再调一次。会话 ID 从哪来、会话列表怎么列,不同 opencode 版本给出的子命令名称不完全一样,用opencode --helpopencode run --help确认你本机这一版支持哪些旗标,别照抄某个旧版本的写法。

4.3 续聊和导出,不需要再指定模型

续聊之所以不用带模型,是因为 opencode 会把会话当时的 provider 和 model 记在会话记录里,--session恢复时按记录走。这正好是你想要的效果:一次会话中途不会因为默认模型变了而漂到别的通道上。

导出时唯一要注意的是分组键。记录里存下来的模型名很可能是taotoken/YOUR_MODEL_ID这种带前缀的形式。做用量统计、成本归集或者简单的会话分类时,按这个完整字符串分组;如果按裸模型名分组,同一批会话可能被算进两个桶里,而两边的数量都「看起来合理」,很难发现。

5. 验证一次 opencode run 是不是真走了 TaoToken 兼容通道

5.1 先在模型对话里发一条

配置改完别急着跑完整会话,先做一次最小验证。打开 模型对话,用同一把 Key 和同一个模型 ID 发一条短消息。这一步能把「Key 是否有效」「模型 ID 是否写对」两件事单独摘出来确认,避免它们和 opencode 的配置问题混在一起排查。

如果这里就失败,先别碰 opencode.json:去看 Key 有没有复制完整、有没有多余空格、模型 ID 是不是从模型广场复制的。这一步通过之后再往下走,后面的问题范围就小很多。

5.2 CLI 侧跑一条最短命令

回到终端,用最快的方式验证配置链路:

opencode run --dir /path/to/your/repo "只回复两个字:收到"

命令能返回内容,说明 baseURL、Key 引用、provider 前缀这三关都过了。接着跑一次你的 Python 封装,确认create_sessionsend_message用的是同一条通道——比较两次返回的模型标识,或者去看 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 控制台里的调用记录,两次调用应该挂在同一个模型名下。

注意每次新建会话都会产生一次真实调用,验证阶段用短提示词就够,别拿一段几千行的代码去测连通性。

6. opencode 侧报错对照:401、模型名对不上、子进程丢环境

6.1 401 与 api key is required

这类报错基本只有一个原因:配置里的{env:TAOTOKEN_API_KEY}没解析出值。按顺序查三点——echo $TAOTOKEN_API_KEY在当前终端里是否有输出;变量名和配置里花括号内的名字是否完全一致(多一个下划线、少一个字母都算不一致);当前终端是不是从图形界面启动的、压根没加载 shell rc 文件。三点都过了还是 401,就回控制台确认 Key 是否被吊销或过期,必要时重新创建一把。

6.2 Unknown model / provider not found

先看拼写:顶层model的斜杠前缀必须等于provider下的那个 key。再看模型 ID 是否与模型广场当前列表一致,旧的 ID 可能已经下线。最后看有没有顺手在 baseURL 末尾加了/v1——这种写法不会报「地址错」,而是走到一个不存在的路径上,最终以 404 或模型不存在的形式出现,很容易被误判成模型名写错。

6.3 subprocess 里环境变量被清空

表现是:终端里手敲opencode run一切正常,从 Python 调用就 401。这就是env参数被显式传入导致的整体替换。改回os.environ.copy(),或者干脆不传env。另外顺手确认cwd:如果子进程在另一个目录里启动,读到的是那边的 opencode.json,配置内容可能完全不同。

7. 会话越攒越多之前,把 Key 和套餐定下来

会话管理这套脚本跑顺之后,会话记录会慢慢堆起来,调用也会从「偶尔试一次」变成「每天都在跑」。这时候值得回头把两件事固定下来:一是 Key 的归属,按项目或按环境分开放,别让测试脚本和正式会话共用一把;二是模型 ID 的写法,团队里统一从模型广场取值,避免同一个模型在三份配置里写成三种样子。

想先手动感受一下通道是否顺手,可以直接在 模型对话 里用同一把 Key 试几轮;如果 opencode 会话是长期高频使用,去 Coding Plan 对一下额度是否够用;需要给 CI 或第二台机器再建 Key,直接在 控制台 API Keys 里创建,然后把同一个 opencode.json 复制过去、导出一次环境变量即可。全部配完之后,回到控制台看一眼刚才那次opencode run有没有记上账——记上了,说明这条会话链路是真的通了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询