
1. OpenClaw 2.0 升级后本地模型会话为什么需要 SQLite 与 CLI 配合OpenClaw 2.0 这次把会话和转录记录从原来的文件存储迁进了 SQLite同时把本地模型接入从 node-llama-cpp 换成了托管的 llama-serverllama.cpp 默认上下文长度提到 64K。对个人开发者来说这意味着两件事一是你终端里的多模型会话终于有了结构化的查询入口二是本地模型llama.cpp / Ollama和远端模型可以走同一套 CLI 工作流。但升级之后很多人会卡在同一个地方本地模型跑起来了会话记录也进了 SQLite可调用链路是断的——CLI 里切换模型要手动改配置调用记录散在几个地方想统计一下今天用了多少次本地模型、多少次远端模型得自己写脚本去翻数据库。更麻烦的是如果你同时用 Ollama 和 llama.cpp两边的模型 ID 命名规则不一样CLI 里传参很容易传错。我试过把本地模型和远端模型统一到一个 Key/API 通道上用 TaoToken 做统一入口CLI 侧只维护一份配置SQLite 侧只维护一张调用记录表。这样做的直接好处是本地模型请求和远端模型请求在调用记录里是同一张表字段结构一致统计和排查都不用分两套逻辑。这篇文章面向的是在终端里管理多模型会话的个人开发者。你会拿到三样东西一份可以直接执行的 SQLite 表结构一份 CLI 配置文件片段含 Base URL、Key、Model ID 三件套以及一次本地模型请求经统一通道的完整验证动作。目标不是讲概念是把配置和调用链路跑通。先说清楚 OpenClaw 2.0 在存储上的变化。旧版本会话是文件形式存的回滚到旧版本前必须用当前 CLI 恢复归档的旧格式转录文件而且迁移之后创建的会话在旧版本里根本不会出现。官方建议升级前先做一次经过验证的备份。这个破坏性降级路径是 2.0 的一个硬约束所以你在动 SQLite 之前先把备份做掉。SQLite 在这里的角色不是替代 OpenClaw 自己的存储而是给你一个额外的、可查询的调用记录层。OpenClaw 管的是会话和转录你管的是「谁在什么时候用什么模型发了什么请求、走了哪条通道、返回了什么状态」。这两层分开升级 OpenClaw 的时候你的调用记录不会跟着迁移排查问题的时候也不会因为 OpenClaw 的 schema 变动而抓瞎。CLI 工作流的核心是把模型选择、通道选择、记录写入这三件事串起来。OpenClaw 2.0 的引导式安装会扫描机器上已有的 AI 访问权限能复用已经验证过的 Codex、ChatGPT 或 Claude CLI 登录也能找出本机安装的 Ollama 与 LM Studio 模型。但扫描出来的模型和你要在 CLI 里实际调用的模型之间还差一层映射。这层映射就是你要在配置文件里写死的东西。本地模型走 llama.cpp 的时候llama-server 默认监听一个本地端口模型 ID 通常是你启动时指定的别名。Ollama 的模型 ID 是ollama list里显示的那个名字。这两个 ID 在 CLI 里如果直接透传很容易和远端模型的 ID 冲突。统一通道的做法是CLI 里只认一个 Model ID 字段本地模型和远端模型都映射到这个字段上由通道侧决定实际路由到哪个后端。这样做的代价是你需要维护一份映射表好处是 CLI 侧的逻辑变得极简。对于个人开发者来说这个交换是划算的因为 CLI 脚本一旦复杂起来调试成本远高于维护一张映射表。2. TaoToken 前置准备统一 Key 与 API 通道的接入位置在写 SQLite 表结构和 CLI 配置之前先把统一通道这一侧准备好。TaoToken 在这里承担的是统一 Key 和 API 入口的角色本地模型请求和远端模型请求都从这一个入口出去CLI 侧不需要为每个后端维护一套鉴权逻辑。你需要先拿到一个 API Key。入口在控制台的 API Keys 页面地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。拿到 Key 之后API 的基础地址是 https://taotoken.net/api 注意这个地址不带 UTM 参数直接用在配置文件里。这里要区分两个地址官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 用于了解产品和文档API 调用地址是 https://taotoken.net/api 用于实际请求。配置文件里写的是后者。模型 ID 这一侧你需要确认你要调用的模型在通道侧的名称。如果你打算把本地 llama.cpp 或 Ollama 的模型也接进来通道侧需要能识别你传过去的模型标识。实际操作中比较稳的做法是远端模型直接用通道侧的标准 Model ID本地模型用一个你自定义的别名然后在通道侧或 CLI 侧做一次映射。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面会说明请求格式、鉴权头和模型列表的获取方式。建议在写配置之前先过一遍特别是鉴权头的字段名不同通道的写法有差异。如果你用的是 Claude Code 这类工具它的配置方式和普通 CLI 不太一样需要单独处理。Claude Code 的接入说明在 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_code_anthropicutm_campaignrewrite 里面会给出 Base URL 和 Key 的填写位置。这一篇主要讲通用 CLI 工作流Claude Code 的细节你可以对照那份文档。Coding Plan 适合长期编码和 Agent 场景如果你打算把 OpenClaw 的 CLI 工作流跑成常态化的编码助手可以看一下 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。模型对话的入口在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 用于验证模型是否正常响应。前置准备的核心是三件套Base URL、Key、Model ID。这三样在后面的 CLI 配置和 SQLite 记录里都会用到。Base URL 固定是 https://taotoken.net/api Key 从控制台拿Model ID 根据你要调用的模型确定。本地模型的 Model ID 建议加一个前缀比如local-或ollama-这样在 SQLite 里查询的时候一眼能区分。有一点要注意不要把 Key 硬编码在会提交到版本库的文件里。CLI 配置建议用环境变量引用SQLite 里只存 Key 的标识比如 key 的前几位或一个自定义的 label不存完整 Key。这是基本的安全习惯和通道本身无关。准备好这三样之后就可以进入配置环节了。下一节给出可以直接复制的 SQLite 表结构和 CLI 配置片段。3. 可复制配置SQLite 表结构与 CLI 配置文件片段这一节给两份可以直接用的配置。第一份是 SQLite 表结构用于记录调用第二份是 CLI 配置文件用于把本地模型和远端模型统一到同一个通道上。先看 SQLite 表结构。这张表的设计目标是一次调用一行记录字段覆盖通道、模型、请求状态、耗时和错误信息。本地模型和远端模型共用这张表通过channel字段区分。-- 调用记录表本地模型与远端模型共用 CREATE TABLE IF NOT EXISTS llm_calls ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_at TEXT NOT NULL DEFAULT (datetime(now)), channel TEXT NOT NULL, -- local-llamacpp | local-ollama | remote model_id TEXT NOT NULL, -- 实际传给通道的 Model ID base_url TEXT NOT NULL, -- 本次请求使用的 Base URL key_label TEXT, -- Key 的标识不存完整 Key request_id TEXT, -- 通道返回的请求 ID如有 status TEXT NOT NULL, -- ok | error http_code INTEGER, latency_ms INTEGER, prompt_tokens INTEGER, output_tokens INTEGER, error_msg TEXT, session_id TEXT -- 关联 OpenClaw 会话可选 ); -- 按时间和通道查询的索引 CREATE INDEX IF NOT EXISTS idx_llm_calls_created_at ON llm_calls(created_at); CREATE INDEX IF NOT EXISTS idx_llm_calls_channel ON llm_calls(channel); CREATE INDEX IF NOT EXISTS idx_llm_calls_model ON llm_calls(model_id); -- 模型映射表CLI 侧别名 - 通道侧实际 Model ID CREATE TABLE IF NOT EXISTS model_map ( alias TEXT PRIMARY KEY, -- CLI 里使用的别名 channel TEXT NOT NULL, -- 路由到哪个通道 target_model TEXT NOT NULL, -- 通道侧实际 Model ID base_url TEXT NOT NULL, note TEXT ); -- 初始化几条映射示例 INSERT OR REPLACE INTO model_map (alias, channel, target_model, base_url, note) VALUES (local-qwen, local-llamacpp, qwen2.5-7b-instruct, http://127.0.0.1:8080/v1, llama-server 本地), (local-llama, local-ollama, llama3.1:8b, http://127.0.0.1:11434/v1, Ollama 本地), (remote-fast, remote, gpt-4o-mini, https://taotoken.net/api, 统一通道远端);这张表的关键设计点是channel和model_id分开。channel表示请求走哪条路本地 llama.cpp、本地 Ollama、远端统一通道model_id表示实际传给后端的模型标识。这样你在统计的时候可以按通道聚合也可以按模型聚合两个维度互不干扰。model_map表解决的是别名映射问题。CLI 里你只写local-qwen这样的别名实际请求的时候从这张表里查出target_model和base_url。本地模型和远端模型都走这套逻辑CLI 侧不需要为本地模型写特殊分支。再看 CLI 配置文件。这里用 TOML 格式路径放在~/.config/openclaw/cli.toml。如果你的 OpenClaw 版本用的是别的配置路径按实际路径调整字段结构不变。# ~/.config/openclaw/cli.toml # OpenClaw 2.0 CLI 统一通道配置 [channel] # 统一通道入口本地模型和远端模型都从这里出去 base_url https://taotoken.net/api # Key 从环境变量读取不硬编码 api_key_env TAOTOKEN_API_KEY # 默认模型别名对应 model_map 表里的 alias default_model remote-fast [channel.headers] # 鉴权头字段名以接入文档为准 Authorization Bearer ${TAOTOKEN_API_KEY} Content-Type application/json [local.llamacpp] # llama-server 默认监听地址 base_url http://127.0.0.1:8080/v1 # 启动参数里的上下文长度2.0 默认 64K context_length 65536 [local.ollama] base_url http://127.0.0.1:11434/v1 [storage] # SQLite 调用记录库路径 db_path ~/.local/share/openclaw/calls.db # 是否写入调用记录 log_calls true [cli] # 会话默认超时秒 timeout 120 # 是否在终端打印请求摘要 verbose true这份配置里[channel]段是统一入口[local.*]段是本地后端的地址。CLI 在发起请求时先根据别名查model_map如果channel是remote就用[channel].base_url如果是local-llamacpp或local-ollama就用对应的本地地址。这样本地模型和远端模型在 CLI 侧是同一套调用逻辑只是目标地址不同。环境变量这样设置export TAOTOKEN_API_KEY你的Key如果你用的是 Codex 的auth.json方式管理凭据可以把 Key 写进~/.codex/auth.jsonCLI 侧读取这个文件。Cline MCP 的场景下Base URL、Key、Model ID 三件套要写在 MCP 的配置里字段名对照接入文档。CC Switch 的场景类似切换配置的时候确保这三样同步切换不要只换 Model ID 不换 Base URL。配置写完之后先不要急着跑请求。用一条 SQL 确认表结构建好了sqlite3 ~/.local/share/openclaw/calls.db .tables应该能看到llm_calls和model_map两张表。再看一下映射表里的数据sqlite3 ~/.local/share/openclaw/calls.db SELECT alias, channel, target_model FROM model_map;确认别名和实际模型 ID 对得上。这一步做完配置环节就结束了。4. 验证请求一次本地模型经统一通道的完整调用配置写好了接下来跑一次真实请求确认本地模型能经统一通道出去并且调用记录能写进 SQLite。验证分三步先确认本地模型服务在跑再发一次请求最后查 SQLite 记录。第一步确认 llama-server 或 Ollama 在监听。llama.cpp 的 llama-server 启动后默认监听 8080 端口Ollama 默认监听 11434。用 curl 探一下# 探 llama-server curl -s http://127.0.0.1:8080/v1/models | head -c 500 # 探 Ollama curl -s http://127.0.0.1:11434/v1/models | head -c 500如果返回里有模型列表说明本地服务正常。如果连接被拒先启动服务。llama-server 的启动命令大致是这样llama-server -m /path/to/qwen2.5-7b-instruct.gguf \ --host 127.0.0.1 --port 8080 \ --ctx-size 65536注意--ctx-size设成 65536和配置文件里的context_length对齐。OpenClaw 2.0 把 llama.cpp 的默认上下文长度提到 64K你的启动参数要跟上否则 CLI 侧按 64K 发请求、服务端只开 8K长上下文会截断。第二步发一次请求。这里用 curl 模拟 CLI 的行为先走本地 llama.cpp再走统一通道的远端模型对比两次请求的差异。# 本地 llama.cpp 请求 curl -s -X POST http://127.0.0.1:8080/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen2.5-7b-instruct, messages: [{role: user, content: 用一句话说明 SQLite 的 WAL 模式是什么}], max_tokens: 128 } | head -c 800# 统一通道远端请求 curl -s -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: gpt-4o-mini, messages: [{role: user, content: 用一句话说明 SQLite 的 WAL 模式是什么}], max_tokens: 128 } | head -c 800两次请求的返回结构应该是一致的都是 OpenAI 兼容格式choices[0].message.content里是模型输出。如果本地请求返回正常、远端请求返回 401说明 Key 或鉴权头有问题对照下一节的排查清单。第三步把这次调用写进 SQLite。CLI 侧如果开了log_calls true会自动写入。手动验证的话用一条 INSERT 模拟sqlite3 ~/.local/share/openclaw/calls.db SQL INSERT INTO llm_calls (channel, model_id, base_url, key_label, status, http_code, latency_ms, prompt_tokens, output_tokens) VALUES (local-llamacpp, qwen2.5-7b-instruct, http://127.0.0.1:8080/v1, NULL, ok, 200, 842, 28, 64); SQL然后查一下sqlite3 -header -column ~/.local/share/openclaw/calls.db \ SELECT id, channel, model_id, status, latency_ms, created_at FROM llm_calls ORDER BY id DESC LIMIT 5;应该能看到刚才插入的那条记录。到这里本地模型经统一通道的调用链路就跑通了CLI 读配置、按别名查映射、发请求、写记录。如果你想验证远端模型也走同一条记录链路把上面 INSERT 的channel改成remote、model_id改成gpt-4o-mini、base_url改成https://taotoken.net/api再插一条。两条记录在同一张表里按channel聚合就能看出本地和远端各调了多少次。这一步做完之后你可以把 CLI 的调用逻辑封装成一个 shell 函数参数只传别名和 prompt其余的都从配置和映射表里读。这样终端里的多模型会话管理就变成了「换别名」这一个动作。5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth配置和验证过程中最常见的几类报错集中在这一节。每一条都给出触发条件和处理方式。401 Unauthorized。触发条件通常是 Key 没设、Key 设错、或者鉴权头字段名不对。先确认环境变量echo $TAOTOKEN_API_KEY | head -c 8如果输出为空说明环境变量没导出。如果输出有值但请求还是 401检查鉴权头。不同通道对鉴权头的字段名要求可能不同有的用Authorization: Bearer xxx有的用x-api-key: xxx。以接入文档为准。另外注意 Key 前后不要有空格从控制台复制的时候容易带上换行。local proxy failed。这个报错通常出现在本地模型请求上含义是 CLI 尝试连接本地服务但失败了。先确认本地服务在监听ss -tlnp | grep -E 8080|11434如果没有输出说明 llama-server 或 Ollama 没启动。如果端口在监听但请求还是失败检查配置文件里的base_url是否带了/v1后缀。llama-server 和 Ollama 的 OpenAI 兼容接口都在/v1路径下漏掉/v1会返回 404有些 CLI 会把 404 报成 proxy failed。reading choices 报错。这个报错的形式通常是cannot read property choices of undefined或类似含义是返回体里没有choices字段。触发条件有三种一是请求根本没发出去返回的是错误对象二是返回体是流式格式但 CLI 按非流式解析三是通道返回了非 OpenAI 兼容的结构。先看原始返回curl -s -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {model:gpt-4o-mini,messages:[{role:user,content:hi}]} | head -c 1000如果返回里有error字段按错误信息处理。如果返回正常但 CLI 还是报 reading choices检查 CLI 是否开了流式模式而通道返回的是非流式或者反过来。OAuth 相关报错。OpenClaw 2.0 的引导式安装支持复用已经验证过的 Codex、ChatGPT 或 Claude CLI 登录。如果你在 CLI 里同时用了 OAuth 凭据和 API Key可能会出现凭据冲突。表现是请求发出去了但返回 403或者 CLI 提示凭据无效。处理方式是明确指定用哪一套凭据如果用 API Key就把 OAuth 相关的环境变量清掉如果用 OAuth就不要在配置里写api_key_env。SQLite 写入失败。如果 CLI 报unable to open database file检查db_path的目录是否存在。SQLite 不会自动创建父目录mkdir -p ~/.local/share/openclaw如果报database is locked说明有另一个进程在写同一个库。OpenClaw 自己的存储和你的调用记录库建议分开不要用同一个文件避免锁竞争。模型 ID 不匹配。本地模型的 ID 在不同后端下不一样。llama-server 的模型 ID 是你启动时-m参数对应的模型名Ollama 的模型 ID 是ollama list里的名字。如果 CLI 传的 Model ID 和后端实际加载的不一致会返回model not found。用model_map表把别名和实际 ID 对齐不要靠记忆。排查的顺序建议是先确认本地服务在跑再确认 Key 和鉴权头再确认 Model ID最后看 SQLite 写入。这个顺序能覆盖大部分问题因为调用链路是从本地服务到通道到记录前面的环节不通后面的报错都是表象。6. 把 CLI 工作流跑成常态从单次验证到日常使用单次验证跑通之后接下来是把它变成日常可用的工作流。这一步不需要新配置主要是把重复动作封装掉。第一个封装是把请求逻辑写成一个 shell 函数放在~/.bashrc或~/.zshrc里oc() { local alias$1; shift local prompt$* # 从 model_map 查别名对应的通道和模型 local row row$(sqlite3 -separator | ~/.local/share/openclaw/calls.db \ SELECT channel, target_model, base_url FROM model_map WHERE alias$alias;) if [ -z $row ]; then echo unknown alias: $alias 2 return 1 fi local channel model base IFS| read -r channel model base $row # 发请求 local start$(date %s%3N) local resp resp$(curl -s -X POST $base/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d {\model\:\$model\,\messages\:[{\role\:\user\,\content\:\$prompt\}]}) local end$(date %s%3N) # 写记录 sqlite3 ~/.local/share/openclaw/calls.db \ INSERT INTO llm_calls (channel, model_id, base_url, status, latency_ms) VALUES ($channel,$model,$base,ok,$((end-start))); echo $resp | head -c 500 }这个函数做了三件事查映射、发请求、写记录。调用的时候只传别名和 promptoc local-qwen 解释一下 SQLite 的 WAL 模式 oc remote-fast 写一个 bash 函数统计今天的调用次数第二个封装是统计查询。日常用的时候你可能想知道今天本地模型和远端模型各调了多少次、平均延迟多少。一条 SQL 就够sqlite3 -header -column ~/.local/share/openclaw/calls.db SQL SELECT channel, COUNT(*) AS calls, ROUND(AVG(latency_ms)) AS avg_ms, SUM(CASE WHEN statuserror THEN 1 ELSE 0 END) AS errors FROM llm_calls WHERE created_at date(now) GROUP BY channel ORDER BY calls DESC; SQL这条查询按通道聚合输出调用次数、平均延迟和错误数。本地模型和远端模型在同一张表里对比起来很直观。第三个封装是清理。调用记录会一直增长建议定期归档或删除旧记录# 删除 30 天前的记录 sqlite3 ~/.local/share/openclaw/calls.db \ DELETE FROM llm_calls WHERE created_at date(now,-30 days); # 回收空间 sqlite3 ~/.local/share/openclaw/calls.db VACUUM;如果你开了 WAL 模式VACUUM之前先做一次 checkpointsqlite3 ~/.local/share/openclaw/calls.db PRAGMA wal_checkpoint(TRUNCATE);WAL 模式对调用记录这种写入频繁、读取也频繁的场景比较合适但要注意 WAL 文件会增长定期 checkpoint 能控制大小。日常使用中还有一个容易忽略的点OpenClaw 2.0 的会话存储在它自己的 SQLite 库里你的调用记录在另一个库里。两个库不要混用也不要在 OpenClaw 升级的时候把你的调用记录库一起迁移。分开的好处是 OpenClaw 的 schema 变动不影响你的记录你的记录清理也不影响 OpenClaw 的会话。如果你用的是 Claude Code 或 Cline MCPCLI 侧的封装逻辑类似只是配置文件的路径和字段名不同。Base URL、Key、Model ID 这三件套在哪个工具里都是核心换工具的时候先确认这三样再调其他参数。最后留一个实用技巧在model_map表里给每个别名加一个note字段写清楚这个模型适合什么场景。比如local-qwen适合长文本总结remote-fast适合快速问答。终端里oc函数调用的时候如果传了未知别名把model_map里的所有别名和 note 打出来相当于一个模型选择菜单。这个习惯能省掉很多「我上次用的是哪个模型」的翻找时间。