專屬AI Agent:基于OpenClaw龍蝦智能體完整實(shí)戰(zhàn)指南(TaoToken統(tǒng)一Key接入篇))
1. 為什么我要自己擼一個(gè) OpenClaw 龍蝦智能體市面上大部分 AI 對(duì)話工具本質(zhì)還是“你問一句它答一句”。關(guān)掉窗口它就把你忘了想讓它幫你整理個(gè)文件、查個(gè)天氣、跑個(gè)定時(shí)任務(wù)它只會(huì)禮貌地告訴你“我做不到”。我想要的不是這種問答機(jī)器人而是一個(gè)能自己感知、自己規(guī)劃、自己動(dòng)手、還能記住事的本地智能體。OpenClaw圈里叫龍蝦智能體就是沖著這個(gè)目標(biāo)去的開源框架它把“理解—規(guī)劃—執(zhí)行—記憶—迭代”這一整套閉環(huán)塞進(jìn)了一個(gè)可以本地跑的 Node.js 項(xiàng)目里。這篇實(shí)戰(zhàn)指南面向的是想從零手寫一個(gè)專屬 AI Agent 的開發(fā)者尤其是習(xí)慣 Node.js、想用 SQLite 做本地記憶、又不想被各種模型 Key 管理折騰的人。我會(huì)帶你搭出 OpenClaw 的最小可運(yùn)行骨架目錄結(jié)構(gòu)、SQLite 建表、Agent 主循環(huán)代碼最后把模型 endpoint 和 Key 統(tǒng)一改到 TaoToken 通道上發(fā)一條測試消息驗(yàn)證閉環(huán)真的跑通了。全程可復(fù)制踩坑點(diǎn)我會(huì)標(biāo)出來。核心檢索詞先擺在這OpenClaw 是一個(gè)本地自主智能體框架AI Agent 是它的產(chǎn)物Node.js 是運(yùn)行底座SQLite 是記憶底座。適合誰適合想擁有一個(gè)“越用越懂你”的本地?cái)?shù)字助手、又愿意動(dòng)手寫點(diǎn)代碼的人。下面直接開干。2. 前置準(zhǔn)備TaoToken 統(tǒng)一 Key 與 OpenClaw 環(huán)境在寫代碼之前先把兩件事搞定模型通道和環(huán)境依賴。模型這塊我用 TaoToken 做統(tǒng)一入口好處是一個(gè) Key 能覆蓋多種模型不用在 OpenClaw 里到處改 provider 配置。官網(wǎng)在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 注意這個(gè) API 地址后面不加任何參數(shù)。先去控制臺(tái)建一個(gè) Key路徑是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 頁面生成頁面在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。拿到形如sk-xxxx的字符串先存好后面配置里要用。想先確認(rèn)模型通不通可以直接在模型對(duì)話頁 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 發(fā)一句話試試能回就說明 Key 沒問題。環(huán)境依賴三樣Node.js 18 以上我用 18.20 實(shí)測穩(wěn)定、Git、SQLite3macOS 和多數(shù) Linux 自帶Windows 建議裝個(gè) sqlite3 命令行方便調(diào)試。檢查命令node -v npm -v sqlite3 --version三個(gè)都出版本號(hào)就 OK。接著建項(xiàng)目目錄我習(xí)慣叫openclaw-lobstermkdir openclaw-lobster cd openclaw-lobster npm init -y npm install better-sqlite3 node-fetch3這里我選better-sqlite3而不是原生 sqlite3因?yàn)樗峭?API寫 Agent 主循環(huán)時(shí)邏輯更直白不用被回調(diào)繞暈。node-fetch3用來發(fā)模型請(qǐng)求。裝完目錄里會(huì)有node_modules和package.json在package.json里加一行type: module這樣后面能用 import 語法。目錄結(jié)構(gòu)我規(guī)劃成這樣先建好空文件夾openclaw-lobster/ ├── src/ │ ├── agent.js # Agent 主循環(huán) │ ├── memory.js # SQLite 記憶層 │ ├── llm.js # 模型調(diào)用封裝 │ └── tools.js # 技能注冊(cè) ├── data/ │ └── lobster.db # SQLite 數(shù)據(jù)庫文件 ├── config.json # 模型與網(wǎng)關(guān)配置 └── package.jsonmkdir -p src data到這一步TaoToken 的 Key 和環(huán)境都齊了。接下來進(jìn)入真正的代碼環(huán)節(jié)先把記憶底座 SQLite 建起來因?yàn)?Agent 的“記性”全靠它。3. 可復(fù)制配置SQLite 建表與 config.json 接入 TaoTokenAgent 的記憶分三塊原始對(duì)話、任務(wù)日志、配置信息。我用一張messages表存對(duì)話一張tasks表存任務(wù)執(zhí)行記錄再加一張kv表存運(yùn)行時(shí)狀態(tài)。建表語句直接寫進(jìn)src/memory.js的初始化函數(shù)里這樣每次啟動(dòng)自動(dòng)建表不用手動(dòng)跑 SQL。// src/memory.js import Database from better-sqlite3; const db new Database(./data/lobster.db); db.pragma(journal_mode WAL); export function initDB() { db.exec( CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, role TEXT NOT NULL, content TEXT NOT NULL, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, status TEXT NOT NULL, result TEXT, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS kv ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); ); } export function saveMessage(role, content) { const stmt db.prepare( INSERT INTO messages (role, content, created_at) VALUES (?, ?, ?) ); return stmt.run(role, content, Date.now()); } export function recentMessages(limit 20) { const stmt db.prepare( SELECT role, content FROM messages ORDER BY id DESC LIMIT ? ); return stmt.all(limit).reverse(); } export function saveTask(name, status, result ) { const stmt db.prepare( INSERT INTO tasks (name, status, result, created_at) VALUES (?, ?, ?, ?) ); return stmt.run(name, status, result, Date.now()); }journal_mode WAL這行別省Agent 頻繁讀寫時(shí)它能明顯減少鎖等待。recentMessages里我做了reverse()因?yàn)?SQL 是倒序取最近 N 條返回給模型時(shí)要按時(shí)間正序排。然后是config.json這是接入 TaoToken 的關(guān)鍵。Base URL、Key、Model ID 三件套都在這里{ model: { provider: openai-compatible, baseUrl: https://taotoken.net/api, apiKey: sk-你的TaoToken密鑰, modelId: gpt-4o-mini, maxTokens: 1024 }, agent: { name: 小鉗, heartbeatInterval: 1800000, memoryLimit: 20 } }注意baseUrl寫https://taotoken.net/api不要帶斜杠結(jié)尾也不要在后面拼/v1之類的路徑具體路徑由llm.js里的請(qǐng)求拼接決定。modelId可以換成你在模型對(duì)話頁看到的任意可用模型名。Key 建議用環(huán)境變量覆蓋避免明文進(jìn) Git// src/llm.js 里讀取時(shí) const apiKey process.env.TAOTOKEN_API_KEY || config.model.apiKey;啟動(dòng)前export TAOTOKEN_API_KEYsk-xxxx即可。這樣配置和代碼分離換 Key 不用改文件。配置就緒下面寫 Agent 主循環(huán)。4. 驗(yàn)證請(qǐng)求Agent 主循環(huán)跑通首個(gè)對(duì)話閉環(huán)主循環(huán)是 OpenClaw 的心臟邏輯是加載歷史記憶 → 拼上下文 → 調(diào)模型 → 解析回復(fù) → 回寫記憶。先寫src/llm.js封裝請(qǐng)求// src/llm.js import fetch from node-fetch; import fs from fs; const config JSON.parse(fs.readFileSync(./config.json, utf-8)); const apiKey process.env.TAOTOKEN_API_KEY || config.model.apiKey; export async function chat(messages) { const res await fetch(${config.model.baseUrl}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey} }, body: JSON.stringify({ model: config.model.modelId, messages, max_tokens: config.model.maxTokens }) }); if (!res.ok) { const errText await res.text(); throw new Error(LLM request failed: ${res.status} ${errText}); } const data await res.json(); return data.choices[0].message.content; }這里res.ok判斷很關(guān)鍵401 和 404 都會(huì)在這里被攔下來并打印原始錯(cuò)誤方便排障。接著寫src/agent.js// src/agent.js import { initDB, saveMessage, recentMessages } from ./memory.js; import { chat } from ./llm.js; initDB(); const SYSTEM_PROMPT 你是本地智能體「小鉗」回答簡潔精準(zhǔn)優(yōu)先給出可執(zhí)行結(jié)果。 涉及文件刪除、覆蓋等高危操作必須先向用戶確認(rèn)。; export async function runAgent(userInput) { saveMessage(user, userInput); const history recentMessages(20); const messages [ { role: system, content: SYSTEM_PROMPT }, ...history ]; const reply await chat(messages); saveMessage(assistant, reply); return reply; } // 命令行直接測試 const input process.argv.slice(2).join( ) || 你好做個(gè)自我介紹; runAgent(input) .then((r) console.log(\n[小鉗] r)) .catch((e) console.error(\n[錯(cuò)誤] e.message));跑起來驗(yàn)證node src/agent.js 你好你現(xiàn)在能記住我說的話嗎如果配置正確終端會(huì)打印小鉗的回復(fù)。再跑一次帶上下文的node src/agent.js 我上一句問了你什么第二次能答出第一次的內(nèi)容說明 SQLite 記憶閉環(huán)生效了。這一步是整個(gè)實(shí)戰(zhàn)的驗(yàn)收點(diǎn)模型請(qǐng)求走的是 TaoToken 的https://taotoken.net/apiKey 是統(tǒng)一 Key返回正常就代表接入成功。如果第一次就報(bào)錯(cuò)別慌下一節(jié)專門排。5. 本篇常見錯(cuò)排查401、local proxy failed 與 reading choices排障這塊我按真實(shí)遇到的報(bào)錯(cuò)來每個(gè)都給定位思路。401 Unauthorized。最常見報(bào)錯(cuò)長這樣LLM request failed: 401 {error:{message:Invalid API key}}。原因通常是 Key 沒讀到或?qū)戝e(cuò)了。先確認(rèn)echo $TAOTOKEN_API_KEY有值再檢查config.json里的apiKey是不是還留著占位符。還有一種情況是 Key 前后帶了空格或換行復(fù)制時(shí)容易帶上用trim()處理一下。如果 Key 確認(rèn)沒問題還是 401去 API Keys 頁面看這個(gè) Key 是不是被禁用或額度用盡。local proxy failed / ECONNREFUSED。這個(gè)報(bào)錯(cuò)說明請(qǐng)求根本沒發(fā)出去卡在本地網(wǎng)絡(luò)層。檢查baseUrl是不是寫成了https://taotoken.net/api/多了斜杠或者誤加了端口。另外確認(rèn)機(jī)器能正常訪問外網(wǎng)curl https://taotoken.net/api看有沒有響應(yīng)。如果公司網(wǎng)絡(luò)有出口限制換網(wǎng)絡(luò)環(huán)境再試。注意這里不要引入任何本地代理配置直連即可。Cannot read properties of undefined (reading choices)。這個(gè)報(bào)錯(cuò)說明data.choices是 undefined通常是響應(yīng)結(jié)構(gòu)和你預(yù)期的不一樣??赡苁莔odelId寫錯(cuò)了服務(wù)端返回了一個(gè)錯(cuò)誤對(duì)象而不是正常 completion。打印完整data看看const data await res.json(); console.log(JSON.stringify(data, null, 2));如果看到{error: ...}那就是模型名或參數(shù)問題。還有一種可能是baseUrl拼出來的路徑不對(duì)比如重復(fù)拼了/v1實(shí)際請(qǐng)求打到了不存在的路由。確認(rèn)baseUrl是https://taotoken.net/api代碼里拼/v1/chat/completions。OAuth / token 過期類報(bào)錯(cuò)。如果你用的是某些需要 OAuth 的客戶端配置報(bào)錯(cuò)會(huì)提示 token invalid。OpenClaw 這套走的是標(biāo)準(zhǔn) Bearer Key不涉及 OAuth 流程。如果看到 OAuth 字樣多半是配置文件里混入了別的客戶端殘留字段把config.json精簡成上面那三件套即可。SQLite 報(bào) database is locked。并發(fā)寫的時(shí)候會(huì)出現(xiàn)加WAL模式基本能解決。如果還鎖檢查是不是有另一個(gè)進(jìn)程占著lobster.db關(guān)掉再跑。排障的核心思路就一條先看 HTTP 狀態(tài)碼再看響應(yīng)體原文最后看請(qǐng)求 URL 拼得對(duì)不對(duì)。把這三樣打印出來九成問題能自己定位。6. 繼續(xù)深入把 OpenClaw 接到 Coding Plan 與文檔跑通首個(gè)閉環(huán)只是起點(diǎn)。接下來你可以給 Agent 加技能比如在src/tools.js里注冊(cè)一個(gè)查天氣的工具讓模型通過 function calling 自主調(diào)用。技能多了之后模型調(diào)用量會(huì)上來這時(shí)候用 Coding Plan 會(huì)更劃算適合長期編碼和 Agent 場景入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。如果你更想先摸清接口細(xì)節(jié)再動(dòng)手接入文檔在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有請(qǐng)求格式和參數(shù)說明。我自己的做法是本地開發(fā)階段用按量 Key 調(diào)試等 Agent 穩(wěn)定跑起來、每天調(diào)用量上去了再切到 Coding Plan。記憶層也可以繼續(xù)優(yōu)化比如把messages表里的遠(yuǎn)期對(duì)話做摘要壓縮只把摘要喂給模型控制 token 消耗。這些都在你現(xiàn)有骨架上加不用推倒重來。最后留一個(gè)實(shí)用技巧給runAgent加個(gè)超時(shí)控制避免模型卡住時(shí)整個(gè)循環(huán)掛死。const controller new AbortController(); const timer setTimeout(() controller.abort(), 30000); // fetch 里帶上 signal: controller.signal30 秒沒響應(yīng)就中斷Agent 主循環(huán)能繼續(xù)處理下一條。這個(gè)細(xì)節(jié)在長時(shí)間運(yùn)行的本地智能體里很值錢早加早省心。