Node.js 脚本复现第三方 wiki,TaoToken 替 apiKey

发布时间:2026/9/18 9:14:35
Node.js 脚本复现第三方 wiki,TaoToken 替 apiKey
1. 从 401 到跑通Node.js 复现 agent swarm 写 wiki 的 TaoToken 接入点最近社区在讨论一篇关于智能体群向第三方公开 wiki 写入内容的论文很多开发者第一反应是“能不能用 Node.js 跑一个最小复现”。真正动手后最先卡住的往往不是模型能力而是openaiSDK 初始化时的401 Invalid API key旧代码里硬编码的第三方 Key 已经不可用或者 baseURL 指到了不再兼容的地址。把 apiKey 换成 TaoToken 的 Key把 Base URL 指向https://taotoken.net/api之后脚本才继续往下执行。TaoToken 官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentnodejs_wiki_repro_intro这篇文章不会把重点放在复述论文结论上而是站在“用 Node.js 写复现脚本的开发者”视角拆解三件事第一如何把硬编码的 apiKey 改成 TaoToken 环境变量第二如何用 OpenAI 兼容 SDK 写出 agent swarm 循环并把真实的第三方 wiki 写入替换成本地 mock第三如何设计 mock 日志让多个智能体的写入动作可观测、可对比、可排错。整个过程不连接生产库不向公开 wiki 发起真实写请求所有命令由读者在本地执行。如果你也在做类似复现建议先把“写哪里”这件事改成“写本地文件”。论文里引发讨论的是智能体向公开 wiki 写入但复现阶段更安全、更可控的做法是用本地mock-wiki/目录和logs/wiki-write.ndjson记录行为。这样既能观察 tool_calls 的参数结构也不会因为脚本失控污染外部页面。TaoToken 在这里承担的是模型调用入口你只需要在官网拿到 Key再把 Base URL 统一成https://taotoken.net/api。2. 环境与依赖openai SDK、.env 和 TaoToken Base URL先把项目目录建出来。Node.js 版本建议 18 以上因为会用到原生fetch和较新的 ESM 语法。依赖只装openai和dotenv就够不需要引入额外的代理框架。mkdir agent-swarm-wiki-repro cd agent-swarm-wiki-repro npm init -y npm i openai dotenv接着创建.env文件。这里有两个关键值TAOTOKEN_API_KEY和TAOTOKEN_BASE_URL。Key 去 TaoToken 官网获取不要写死在代码里Base URL 按工具配置填https://taotoken.net/api不要在后面额外拼接 UTM 参数。UTM 只用于官网页面追踪API 请求地址保持干净。TAOTOKEN_API_KEYYOUR_API_KEY TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODELgpt-4o-mini如果你还没有创建 Key可以走这个入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentnodejs_wiki_repro_key 。创建后复制到.env同时把YOUR_API_KEY替换掉。注意不要把.env提交到 Git建议在.gitignore里加上.env node_modules/ logs/ mock-wiki/然后写一个最小的 SDK 初始化文件client.js先验证 Key 和 Base URL 是否可用。OpenAI 兼容 SDK 的baseURL字段就是供应商入口TaoToken 的配置值填https://taotoken.net/api。// client.js import OpenAI from openai; import dotenv from dotenv; dotenv.config(); if (!process.env.TAOTOKEN_API_KEY || process.env.TAOTOKEN_API_KEY YOUR_API_KEY) { throw new Error(请先在 .env 中配置 TAOTOKEN_API_KEY); } export const client new OpenAI({ apiKey: process.env.TAOTOKEN_API_KEY, baseURL: process.env.TAOTOKEN_BASE_URL || https://taotoken.net/api, });可以先用一个极简脚本验证对话接口。如果这里返回 401优先检查 Key 是否复制完整、是否在 TaoToken 控制台启用如果返回 404检查 baseURL 是否被误写成别的路径。验证通过后再进入 agent swarm 复现。// smoke-test.js import { client } from ./client.js; const resp await client.chat.completions.create({ model: process.env.TAOTOKEN_MODEL, messages: [{ role: user, content: 只回复ok }], }); console.log(resp.choices[0].message.content);3. 代码 diff把硬编码 apiKey 改成 TaoToken 环境变量复现论文时很多示例代码会直接写死第三方 apiKey或者使用一个已经下线的 baseURL。下面是一段典型的 before 代码// before.js import OpenAI from openai; const client new OpenAI({ apiKey: sk-old-third-party-key, baseURL: https://old-proxy.example.com/v1, });这段代码有两个问题第一Key 硬编码在源码里一旦泄露就要立即轮换第二baseURL 指向旧地址接口路径和鉴权头可能已经变化。改成 TaoToken 后apiKey 从环境变量读取baseURL 指向https://taotoken.net/api。diff 如下- import OpenAI from openai; - - const client new OpenAI({ - apiKey: sk-old-third-party-key, - baseURL: https://old-proxy.example.com/v1, - }); import OpenAI from openai; import dotenv from dotenv; dotenv.config(); const client new OpenAI({ apiKey: process.env.TAOTOKEN_API_KEY, baseURL: process.env.TAOTOKEN_BASE_URL || https://taotoken.net/api, });如果你原来的代码里把模型名也写死了建议一起改成环境变量这样在不同工具之间切换时不用改源码- model: gpt-4o-mini, model: process.env.TAOTOKEN_MODEL,再进一步把“写 wiki”这个动作从真实 HTTP 请求改成 mock 函数。论文复现最容易出问题的地方是 agent 真的向外发起写请求。我们只保留 tool 定义和参数解析把执行端替换成本地文件追加。这样既能看到模型是否产生写入意图也不会影响外部 wiki。- async function wikiWrite({ page, section, content }) { - await fetch(https://third-party-wiki.example.com/api/${page}, { - method: PUT, - headers: { Authorization: Bearer ${process.env.WIKI_TOKEN} }, - body: JSON.stringify({ section, content }), - }); - } function mockWikiWrite({ agentId, page, section, content }) { const safePage page.replace(/[^a-zA-Z0-9_-]/g, _); const file path.join(MOCK_WIKI_DIR, ${safePage}.md); const block \n## ${section}\n\n${content}\n; fs.appendFileSync(file, block, utf8); return { agentId, page, section, contentLength: content.length }; }这里的核心原则是模型可以生成wiki_write工具调用但执行层只写本地mock-wiki/。如果你希望进一步隔离还可以在工具描述里明确写“当前工具只写入本地 mock wiki不要尝试访问真实第三方 wiki”。这能降低模型误判目标地址的概率。4. 完整脚本agent swarm 循环、tool_calls 解析与 wiki mock 写入下面给出一个可运行的 Node.js 脚本。它模拟多个 agent 并行领取任务每个 agent 可以调用wiki_write工具最终写入本地 mock wiki并把每次动作记录到 NDJSON 日志。脚本没有连接任何生产库也没有向外部 wiki 发起写请求。// swarm-repro.js import fs from node:fs; import path from node:path; import { client } from ./client.js; const LOG_DIR path.resolve(logs); const MOCK_WIKI_DIR path.resolve(mock-wiki); fs.mkdirSync(LOG_DIR, { recursive: true }); fs.mkdirSync(MOCK_WIKI_DIR, { recursive: true }); const wikiWriteTool { type: function, function: { name: wiki_write, description: 向本地 mock wiki 写入一段内容用于复现智能体群协作行为。当前工具不会访问真实第三方 wiki。, parameters: { type: object, properties: { page: { type: string, description: 词条名例如 agent-swarm }, section: { type: string, description: 章节名例如 协作信号 }, content: { type: string, description: 要写入的正文控制在 1 到 2 句 }, }, required: [page, section, content], }, }, }; function appendLog(record) { const line JSON.stringify({ ...record, ts: new Date().toISOString() }) \n; fs.appendFileSync(path.join(LOG_DIR, wiki-write.ndjson), line, utf8); } function mockWikiWrite({ agentId, page, section, content }) { const safePage page.replace(/[^a-zA-Z0-9_-]/g, _); const file path.join(MOCK_WIKI_DIR, ${safePage}.md); const block \n## ${section}\n\n${content}\n; fs.appendFileSync(file, block, utf8); const record { agentId, action: wiki_write, page, section, contentLength: content.length, contentHash: Buffer.from(content).toString(base64url).slice(0, 12), target: mock-wiki/${safePage}.md, status: mocked, }; appendLog(record); return record; } async function runAgent({ agentId, task }) { const messages [ { role: system, content: [ 你是一个复现实验中的智能体。, 你可以调用 wiki_write 工具但当前工具只写入本地 mock wiki。, 不要尝试访问真实第三方 wiki不要发起网络写请求。, 如果任务需要补充内容请通过工具调用完成。, ].join(\n), }, { role: user, content: task }, ]; for (let step 0; step 4; step) { const resp await client.chat.completions.create({ model: process.env.TAOTOKEN_MODEL, messages, tools: [wikiWriteTool], tool_choice: auto, }); const msg resp.choices[0].message; messages.push(msg); if (!msg.tool_calls || msg.tool_calls.length 0) { appendLog({ agentId, action: agent_message, content: msg.content, status: ok, }); return msg.content; } for (const call of msg.tool_calls) { if (call.function.name ! wiki_write) { messages.push({ role: tool, tool_call_id: call.id, content: JSON.stringify({ ok: false, error: unknown tool }), }); continue; } let args; try { args JSON.parse(call.function.arguments); } catch (err) { messages.push({ role: tool, tool_call_id: call.id, content: JSON.stringify({ ok: false, error: invalid json arguments }), }); continue; } const record mockWikiWrite({ agentId, ...args }); messages.push({ role: tool, tool_call_id: call.id, content: JSON.stringify({ ok: true, ...record }), }); } } appendLog({ agentId, action: step_limit_reached, status: warn }); return step_limit_reached; } async function main() { const agents [ { id: agent-a, task: 尝试在 mock wiki 的 agent-swarm 词条下新增“协作信号”章节内容写 1 到 2 句。, }, { id: agent-b, task: 尝试在 mock wiki 的 agent-swarm 词条下新增“意外协作”章节内容写 1 到 2 句。, }, { id: agent-c, task: 尝试在 mock wiki 的 agent-swarm 词条下新增“观测方法”章节内容写 1 到 2 句。, }, ]; const results await Promise.all( agents.map((agent) runAgent({ agentId: agent.id, task: agent.task })) ); console.log(JSON.stringify({ results }, null, 2)); } main().catch((err) { console.error(err); process.exit(1); });运行命令node swarm-repro.js如果一切正常你会看到控制台输出各个 agent 的最终回复同时本地生成两个目录mock-wiki/保存模拟词条logs/wiki-write.ndjson保存结构化写入记录。这里没有使用任何真实 wiki 凭据也没有调用外部写接口。5. mock 日志长什么样NDJSON 观测格式与字段设计日志采用 NDJSON每行一个 JSON 对象方便用jq、awk或 Node.js 脚本继续分析。一次典型的wiki_write记录如下{agentId:agent-a,action:wiki_write,page:agent-swarm,section:协作信号,contentLength:42,contentHash:a1b2c3d4e5f6,target:mock-wiki/agent-swarm.md,status:mocked,ts:2025-01-01T00:00:01.000Z} {agentId:agent-b,action:wiki_write,page:agent-swarm,section:意外协作,contentLength:55,contentHash:f6e5d4c3b2a1,target:mock-wiki/agent-swarm.md,status:mocked,ts:2025-01-01T00:00:01.120Z} {agentId:agent-c,action:wiki_write,page:agent-swarm,section:观测方法,contentLength:48,contentHash:9a8b7c6d5e4f,target:mock-wiki/agent-swarm.md,status:mocked,ts:2025-01-01T00:00:01.230Z}这些字段的含义如下agentId是哪个智能体发起的动作便于区分并发行为。action动作类型这里统一为wiki_write后续可以扩展wiki_read、wiki_revert。page模型生成的词条名。如果模型输出中文或空格会被safePage转换成安全文件名。section章节名用于观察多个 agent 是否写入同一章节。contentLength正文长度可用于判断写入规模。contentHash正文摘要用于快速比较不同 agent 是否写入了相同内容。target实际落盘路径。因为执行层是 mock所以永远指向mock-wiki/。status状态。mocked表示写入了本地模拟文件没有触达外部。ts时间戳便于按时间窗口统计并发行为。如果你要观察“多个 agent 是否对同一词条产生编辑意图”可以本地执行一段统计脚本。下面的命令由读者在本地运行不连接任何线上服务node -e const fs require(fs); const lines fs.readFileSync(logs/wiki-write.ndjson, utf8).trim().split(\n); const counter new Map(); for (const line of lines) { const rec JSON.parse(line); if (rec.action ! wiki_write) continue; const key rec.page :: rec.section; counter.set(key, (counter.get(key) || 0) 1); } console.log([...counter.entries()].sort((a, b) b[1] - a[1])); 输出会按“词条::章节”聚合出现次数。如果多个 agent 写了同一 key就说明模型层面产生了协作或冲突信号。论文复现时重点不是复刻具体次数而是观察这种信号是否稳定出现以及它和提示词、模型、并发度之间的关系。6. Claude Code / Codex / CC Switch 配置同一 Key 的本地切换复现脚本跑通后你可能会把同一套 TaoToken Key 接到日常编码工具里。这里分别给出 Claude Code、Codex 和 CC Switch 的配置方式。注意Claude Code 使用ANTHROPIC_*环境变量Codex 使用config.toml不要把ANTHROPIC_*套到 Codex 上。Claude Code 的settings.json可以放在用户目录或项目目录。核心是ANTHROPIC_BASE_URL指向https://taotoken.net/apiANTHROPIC_AUTH_TOKEN填YOUR_API_KEY模型名按你实际可用的填写。{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }Codex 使用~/.codex/config.toml。这里定义 providerbase_url 同样填https://taotoken.net/apienv_key 使用TAOTOKEN_API_KEY不要写成ANTHROPIC_AUTH_TOKEN。model gpt-5-codex model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chat然后在本机环境变量里设置export TAOTOKEN_API_KEYYOUR_API_KEYCC Switch 三件套可以理解为Claude Code 的settings.json、Codex 的config.toml、本地.env或系统环境变量。在 CC Switch 里新增供应商时把 Base URL 统一填https://taotoken.net/apiKey 填YOUR_API_KEY。这样切换 Claude Code / Codex 时不需要改业务代码只需要切换工具配置。TaoToken 官网配置入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentnodejs_wiki_repro_ccswitch如果你在团队里共享复现脚本建议把.env.example提交到仓库只保留占位符TAOTOKEN_API_KEYYOUR_API_KEY TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODELgpt-4o-mini这样其他人 clone 后复制成.env去 TaoToken 官网创建自己的 Key 即可。不要把真实 Key 写进 README也不要把logs/和mock-wiki/提交到仓库除非你确认里面没有敏感内容。7. 排错清单401/404/429 与 tool_calls 参数错误复现过程中最常见的错误集中在四类鉴权、路径、限流和工具参数解析。下面按现象给出排查顺序。第一类401 Invalid API key。优先检查.env里的TAOTOKEN_API_KEY是否还是YOUR_API_KEY以及是否在 TaoToken 控制台创建并启用了 Key。可以用下面的最小请求验证node smoke-test.js如果仍然 401再检查环境变量是否被其他 shell 会话覆盖。可以在 Node.js 里打印前几位和后几位但不要打印完整 Keyconst k process.env.TAOTOKEN_API_KEY || ; console.log(k.slice(0, 4), k.slice(-4), k.length);第二类404 Not Found。常见原因是 baseURL 被手工拼成了其他路径。TaoToken 的工具配置 Base URL 是https://taotoken.net/api不要再拼/?utm_source...也不要把官网页面地址当成 API 地址。如果你使用的 SDK 会自动补全/v1或/chat/completions以工具文档为准但供应商地址本身保持https://taotoken.net/api。第三类429 Too Many Requests。agent swarm 复现经常并发多个请求如果一次性启动几十个 agent很容易触发限流。建议先把 agent 数量降到 3 到 5 个并在代码里加入简单重试async function withRetry(fn, retries 3) { for (let i 0; i retries; i) { try { return await fn(); } catch (err) { if (err.status ! 429 || i retries - 1) throw err; await new Promise((r) setTimeout(r, 800 * (i 1))); } } }第四类tool_calls参数解析失败。模型返回的arguments理论上应是 JSON 字符串但实际运行中可能遇到不合法 JSON。不要在解析失败时直接退出而是把错误作为 tool 结果回传让模型下一轮修正let args; try { args JSON.parse(call.function.arguments); } catch (err) { messages.push({ role: tool, tool_call_id: call.id, content: JSON.stringify({ ok: false, error: invalid json arguments }), }); continue; }另外务必确认执行层没有真实写外部 wiki。可以在mockWikiWrite里加一道断言if (!file.startsWith(MOCK_WIKI_DIR)) { throw new Error(拒绝写入 mock-wiki 之外的路径); }这道断言能防止路径穿越也能提醒你复现阶段的所有写入都应落在本地沙箱。8. 从复现到日常TaoToken 模型对话、Coding Plan 与 Key 管理当 Node.js 脚本能够稳定输出 mock 日志后你可以把同一套配置迁移到日常开发。第一步是验证模型可用性可以在 TaoToken 模型对话页直接测试提示词和工具调用格式确认模型是否按预期返回wiki_write参数。模型对话入口https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentnodejs_wiki_repro_chat如果你需要更密集地跑 agent swarm、代码生成或长上下文任务可以查看 Coding Plan选择适合的额度与模型组合https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentnodejs_wiki_repro_plan生产或长期使用前建议在控制台创建独立 Key并按项目区分命名方便轮换和审计。创建 Key 入口https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentnodejs_wiki_repro_keys如果你同时使用 Claude Code建议把settings.json和项目.env分开管理项目级.env只放复现脚本需要的变量工具级settings.json放 Claude Code 的ANTHROPIC_*配置。Claude Code 文档https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentnodejs_wiki_repro_ccdoc最后再把官网入口放在这里方便你一次性完成注册、创建 Key 和查看配置说明https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentnodejs_wiki_repro_end整个复现流程可以总结为一句话用 Node.js 还原 agent swarm 的协作意图用wiki_write工具捕获写入动作用本地mock-wiki/和 NDJSON 日志做安全观测用 TaoToken 的 Key 和https://taotoken.net/api完成模型调用。这样既贴近论文讨论的行为模式又不会把实验脚本变成对外部 wiki 的真实写入器。