☰
Learn-Claude-Code 笔记 | Concurrency | s08 Background Tasks 的 Base URL 改到 TaoToken 实践
2026/10/4 9:42:43 网站建设 项目流程

1. 从 s08 Background Tasks 说起:为什么同步工具调用会卡住整个 Agent

如果你正在跟着 learn-claude-code 这个开源项目学 Claude Code 的底层实现,学到 Concurrency 这一部分时,大概率会在 s08 Background Tasks 这里停一下。原因很简单:前面 s01 到 s07,整个 agent loop 一直是同步的——模型发一个工具调用,程序执行,等结果回来,再塞进 messages,继续下一轮。这个节奏在 read_file、write_file、task_create 这些短操作上完全没问题,但一旦遇到pytest、npm install、docker build这种要跑几分钟的命令,主循环就被死死卡住了。

s08 要解决的核心问题就是:当某些工具调用很慢时,Agent 如何把它们放到后台执行,同时让主循环继续推进?文档里给的方案很克制——不推翻原来的 loop,而是在 loop 外围加一个后台执行层:主线程继续跑 agent loop,后台线程去执行慢命令,完成后把结果放进通知队列,在下一次 LLM 调用之前统一注入回上下文。

这套机制本身设计得很漂亮,但真正落地到日常开发时,还有一个绕不开的前置问题:你的 Base URL 指向哪里?如果你用的是官方 Anthropic 端点,在国内网络环境下调用会非常不稳定,后台任务刚跑起来,主循环那边 LLM 调用就超时了,整个并发验证根本做不下去。所以这篇笔记的重点,除了梳理 s08 的后台任务调度机制,还会把 Base URL 改到 TaoToken 的完整配置过程写清楚,让你能真正把 s08 的并发能力跑起来验证。

TaoToken 在这里扮演的角色很明确:它提供统一的 API 通道,你只需要一个 Key、一个 Base URL,就能稳定调用 Claude 系列模型。对于 s08 这种需要频繁发起 LLM 调用(每一轮 loop 都要调一次)的场景来说,通道稳定性直接决定了后台任务机制能不能被验证成功。官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api ,下面会给出可直接复制的配置片段。

2. TaoToken 前置准备:拿到统一 Key 和 Base URL

在改配置之前,先把 TaoToken 这边的准备工作做完。这一步不复杂,但有几个细节容易踩坑,我按顺序说清楚。

首先访问 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册并登录。登录之后进入控制台,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。在控制台里你能看到账户余额、调用统计,以及最关键的 API Keys 管理入口:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。

在 API Keys 页面创建一个新的 Key。创建时注意两点:一是 Key 只在创建时完整显示一次,复制后立刻保存到安全的地方;二是如果你打算在多个项目里共用,建议按项目命名,方便后面排查问题时定位是哪个 Key 出的错。创建完成后你会拿到一串以sk-开头的字符串,这就是后面配置里要填的ANTHROPIC_AUTH_TOKEN。

接下来确认 Base URL。TaoToken 的 API 端点是:

https://taotoken.net/api

注意这里不要加 UTM 参数,API 调用只需要干净的端点地址。这个地址会作为ANTHROPIC_BASE_URL填进环境变量或配置文件。

关于模型 ID,TaoToken 支持 Claude 系列模型,你在配置里填的ANTHROPIC_MODEL需要和 TaoToken 侧支持的模型名一致。常见的比如claude-sonnet-4-20250514、claude-3-5-sonnet-20241022等。如果你不确定当前账户支持哪些模型,可以在模型对话页面先手动测一下:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。在对话页面选一个模型发一条消息,能正常返回就说明这个模型 ID 可用,然后再把它填进 s08 的配置里。

这里有个容易忽略的点:s08 的 agent loop 每一轮都会调用一次 LLM,如果你的后台任务跑了 5 秒,主循环在这 5 秒里可能已经发起了 2 到 3 次 LLM 调用。所以 Key 的额度和通道稳定性都要提前确认好,避免跑到一半因为额度不足或通道抖动导致 loop 中断。控制台里的调用统计可以帮你观察这一点。

另外,如果你打算长期跑 coding 类任务或者 Agent 实验,可以看一下 Coding Plan 页面:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。它针对的就是这种高频、长时运行的编码场景,比按量付费更适合做 s08 这种并发验证。

3. 可复制配置:把 s08 的 Base URL 改到 TaoToken

s08 的代码本身用的是 Anthropic SDK,所以配置方式和 Claude Code 是一致的:通过环境变量或者.env文件指定 Base URL、Key 和 Model。下面给出三种常见形态,你按自己项目的实际情况选一种。

方式一:.env文件(推荐,learn-claude-code 项目默认用这个)

在项目根目录创建或编辑.env文件,写入:

ANTHROPIC_BASE_URL=https://taotoken.net/api ANTHROPIC_AUTH_TOKEN=sk-你的TaoToken密钥 ANTHROPIC_MODEL=claude-sonnet-4-20250514

注意ANTHROPIC_AUTH_TOKEN后面直接跟 Key,不要加引号,也不要有空格。ANTHROPIC_MODEL填你在模型对话页面验证过可用的那个模型 ID。

方式二:Claude Code 的 settings.json

如果你是在 Claude Code 里跑 s08 的代码,配置文件通常在~/.claude/settings.json。写入:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的TaoToken密钥", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } }

这个文件路径和字段名要和 Claude Code 实际读取的一致,改完重启 Claude Code 生效。

方式三:Codex 的 auth.json(如果你同时用 Codex 做对照实验)

Codex 的认证文件一般在~/.codex/auth.json,写入:

{ "OPENAI_API_KEY": "sk-你的TaoToken密钥", "OPENAI_BASE_URL": "https://taotoken.net/api" }

这里要提醒一句:Codex 和 Claude Code 的字段名不一样,Claude Code 用ANTHROPIC_*,Codex 用OPENAI_*,不要混用。如果你在同一个终端里同时跑两个工具,建议用不同的 shell 会话,避免环境变量互相覆盖。

方式四:Cline MCP 配置

如果你用 Cline 的 MCP 模式接入,配置里需要同时写全三件套:Base URL、Key、Model ID。在 Cline 的 MCP 设置里填入:

{ "mcpServers": { "taotoken": { "command": "npx", "args": ["-y", "@taotoken/mcp-server"], "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的TaoToken密钥", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } } } }

三件套缺一不可:只填 Base URL 不填 Key 会报 401,只填 Key 不填 Model ID 会报模型不存在,只填 Model ID 不填 Base URL 会走默认端点然后超时。

配置改完之后,先别急着跑 s08 的完整流程,用一条最简单的请求验证通道是否通。在终端里执行:

curl https://taotoken.net/api/v1/messages \ -H "x-api-key: sk-你的TaoToken密钥" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "max_tokens": 64, "messages": [{"role": "user", "content": "reply with ok"}] }'

如果返回里能看到"content"字段和正常的文本,说明 Base URL、Key、Model 三件套都对了。如果返回 401,检查 Key 是否复制完整;如果返回模型不存在,回到模型对话页面确认模型 ID。

4. 验证请求:跑通 s08 的后台任务并发执行

配置通了之后,就可以真正验证 s08 的后台任务机制了。这一节我会用文档里给出的第一个 prompt 来演示,因为它最能体现 s08 和前面同步模式的区别。

第一步:确认 s08 代码里的 BackgroundManager 已就位

打开agents/s08_background_tasks.py,确认这几个关键组件存在:BackgroundManager类、run()方法、_execute()方法、drain_notifications()方法,以及background_run和check_background两个工具注册。如果是从 s07 升级过来的,重点检查agent_loop()开头是否加了这段:

notifs = BG.drain_notifications() if notifs and messages: notif_text = "\n".join( f"[bg:{n['task_id']}] {n['status']}: {n['result']}" for n in notifs ) messages.append({"role": "user", "content": f"<background-results>\n{notif_text}\n</background-results>"}) messages.append({"role": "assistant", "content": "Noted background results."})

这段就是 s08 的精髓:在每次新的 LLM 调用之前,把通知队列排空并注入上下文。

第二步:启动 s08,输入验证 prompt

运行python agents/s08_background_tasks.py,在交互界面里输入:

Run "sleep 5 && echo done" in the background, then create a file while it runs

第三步:观察 loop 的执行节奏

按照文档里的调试分析,你应该能看到这样的执行序列:

第一轮 loop,模型返回两个 block:一个 TextBlock 说明要去后台跑 sleep 命令,一个 ToolUseBlock 调用background_run,参数是sleep 5 && echo done。BackgroundManager.run()生成 task_id(比如17308874),在self.tasks里登记status='running',启动 daemon 线程,然后立即返回Background task 17308874 started: sleep 5 && echo done。注意,这里返回的不是done,而是 task_id。

第二轮 loop,模型拿到"任务已启动"的确认后,没有等待,直接调用write_file创建文件。这一步是 s08 和同步模式的分水岭:如果是 s01 到 s07 的同步模式,主循环会被sleep 5卡住整整 5 秒,根本进不到写文件这一步。但现在它做到了,因为等待被剥离到了后台线程。

第三轮 loop,在调用 LLM 之前,drain_notifications()被触发,把后台线程已经完成的done结果包装成<background-results>注入 messages。模型看到这个结果后,调用read_file读取刚才创建的文件,验证内容。

第四轮 loop,模型调用check_background(task_id="17308874"),查询单个任务的详细状态,返回[completed] sleep 5 && echo done done。

第五轮 loop,模型调用check_background()不传 task_id,列出所有后台任务,返回17308874: [completed] sleep 5 && echo done。

第六轮 loop,模型输出最终总结,循环结束。

第四步:验证并发能力

如果你想更直观地看到并发,用文档里的第二个 prompt:

Start 3 background tasks: "sleep 2", "sleep 4", "sleep 6". Check their status.

这个 prompt 会连续启动三个后台任务,它们在不同的 daemon 线程里并行执行。你可以在终端里观察:三个任务几乎同时启动,但完成时间分别是 2 秒、4 秒、6 秒。主循环在这期间不会被任何一个任务阻塞,可以继续处理其他逻辑。等三个任务都完成后,通知队列里会积累三条结果,在下一次 LLM 调用前统一注入。

第五步:确认结果注入的时机

这一步是验证 s08 设计是否正确的关键。你可以在drain_notifications()里加一行打印,或者在agent_loop()注入<background-results>的地方加日志。正常情况下,你应该看到后台结果不是一完成就立即注入,而是在下一次 LLM 调用边界才统一注入。这对应文档里流程图第六张和第七张图强调的:结果先在队列里积累,直到下一次 LLM call 前才统一 drain。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

配置和验证过程中,最容易遇到的几类报错,我按实际踩过的顺序列出来,每个都给出定位方法和修复步骤。

报错一:401 Unauthorized

这是最常见的。终端里会看到类似:

anthropic.AuthenticationError: Error code: 401 - {'error': {'message': 'Invalid API key'}}

定位方法:先确认ANTHROPIC_AUTH_TOKEN的值是否完整。TaoToken 的 Key 以sk-开头,复制时容易漏掉末尾几位。其次确认.env文件是否被正确加载——learn-claude-code 项目用python-dotenv加载,如果你在代码里手动改了环境变量但没重启进程,旧值还在。

修复步骤:重新从 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 复制一次 Key,粘贴到.env里,确保没有多余空格和换行。然后重启 s08 进程。

报错二:local proxy failed / connection refused

这个报错通常长这样:

httpx.ConnectError: [Errno 111] Connection refused

或者:

anthropic.APIConnectionError: Connection error.

定位方法:先确认ANTHROPIC_BASE_URL是否写成了https://taotoken.net/api,而不是https://taotoken.net/api/v1或其他路径。TaoToken 的端点就是/api,SDK 会自动拼接/v1/messages。如果你手动加了/v1,就会变成/api/v1/v1/messages,导致 404 或连接失败。

修复步骤:把 Base URL 改回https://taotoken.net/api,不要加任何后缀。然后用第 3 节里的 curl 命令单独测一次,确认通道本身是通的。

报错三:reading choices / 响应解析失败

这个报错在流式响应场景下比较常见:

KeyError: 'choices'

或者:

json.decoder.JSONDecodeError: Expecting value: line 1 column 1

定位方法:这类报错通常说明返回的不是预期的 JSON 结构。可能原因有两个:一是 Base URL 指向了错误的端点,返回了 HTML 错误页;二是 Model ID 填错了,TaoToken 侧返回了错误信息而不是正常的 messages 结构。

修复步骤:先用 curl 命令看原始返回内容。如果返回的是 HTML,说明端点错了;如果返回的是 JSON 但结构不对,检查 Model ID 是否和 TaoToken 支持的模型名一致。回到模型对话页面确认可用模型,再填进配置。

报错四:OAuth 相关错误

如果你在 Claude Code 里看到:

OAuth token expired

或者:

Please run 'claude login' to authenticate

定位方法:这说明 Claude Code 还在尝试用 OAuth 方式认证,而不是用你配置的 API Key。Claude Code 的认证优先级是:OAuth token > API Key。如果你之前登录过官方账号,OAuth token 还在缓存里,就会覆盖你的 API Key 配置。

修复步骤:先执行claude logout清除 OAuth 缓存,然后确认~/.claude/settings.json里的ANTHROPIC_AUTH_TOKEN和ANTHROPIC_BASE_URL配置正确,再重启 Claude Code。如果还是不行,检查是否有其他环境变量(比如 shell 的.bashrc或.zshrc)里设置了ANTHROPIC_API_KEY,它可能会覆盖 settings.json 里的配置。

报错五:后台任务超时但主循环没收到通知

这个不是报错,而是行为异常。你发现后台任务明明已经跑完了,但模型一直没收到<background-results>。

定位方法:检查drain_notifications()是否在agent_loop()开头被调用。如果这段代码被注释掉了,或者位置放错了(比如放在了 LLM 调用之后),通知就不会被注入。

修复步骤:确认agent_loop()的while True循环里,第一件事就是notifs = BG.drain_notifications(),然后再调用client.messages.create()。顺序不能反。

报错六:并发任务互相干扰

你启动多个后台任务后,发现结果串了,或者某个任务的结果被覆盖。

定位方法:检查_notification_queue的写入是否加了锁。_execute()里往队列 append 的时候,必须在with self._lock:保护下进行。如果漏了锁,多线程同时写队列就会出问题。

修复步骤:确认_execute()里的这段:

with self._lock: self._notification_queue.append({ "task_id": task_id, "status": status, "command": command[:80], "result": (output or "(no output)")[:500], })

锁不能省。这是 s08 并发安全的基础。

6. 语义一致 CTA:把 s08 的并发能力真正用起来

s08 的 Background Tasks 机制本身不复杂,核心就是三件事:background_run把慢命令丢到后台线程并立即返回 task_id,_notification_queue暂存完成结果,drain_notifications()在下一次 LLM 调用前统一注入。但要让这套机制稳定跑起来,Base URL 的配置是绕不过去的一步。

如果你在排障过程中遇到 401、连接失败、模型不存在这类问题,优先去 API Keys 页面重新确认 Key 和模型 ID:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。接入文档里有完整的端点和参数说明:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。

如果你想先手动验证某个模型在 TaoToken 上是否可用,直接去模型对话页面发一条消息最快:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。确认可用后再填进 s08 的ANTHROPIC_MODEL。

如果你打算长期跑 s08 这类并发 Agent 实验,或者把 learn-claude-code 的后续章节(s09 Agent Teams 等)也一起跟下去,Coding Plan 会比按量付费更合适:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。它针对的就是这种高频、长时运行的编码场景。

配置改完之后,建议先用文档里的三个 prompt 各跑一遍,观察后台任务的启动、完成、通知注入三个阶段的时序。特别是第三个 promptRun pytest in the background and keep working on other things,它最接近真实工程场景——测试在后台跑,主循环继续处理其他任务,等测试完成后结果自动汇合。这个流程跑通了,s08 的并发能力才算真正落地。

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

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

立即咨询