1. learn-claude-code -s11 里为什么绕不开 settings.json如果你跟着 learn-claude-code 的 s11 一路写下来会发现这一节的代码重心全在agent_loop的错误恢复上429 限流退避、529 过载切模型、max_tokens升级、prompt_too_long触发 reactive compact。逻辑写得很完整但真正跑起来的时候很多人卡住的地方根本不在这些分支里而是在更前面一步——client.messages.create(...)这个调用到底连到哪里、用哪个 Key、走哪条通道。learn-claude-code -s11 的定位是「让 Agent 从出错即退出变成出错先分类再恢复」它默认你已经有一个能正常返回的 LLM 客户端。可现实是本地开发调试时这个客户端往往是最容易出问题的一环Key 放错位置、base_url 没改、环境变量没生效、模型名对不上任何一个都会让with_retry里的 10 次重试全部打空最后抛一个RuntimeError(重试 10 次全失败了)你盯着日志根本分不清是网络抖动还是配置写错了。这篇就聚焦 s11 场景下的配置落地用统一 Key / API 通道接入 TaoToken把settings.json的骨架搭好字段逐个说清楚再配一张常见报错对照表最后做一次连通性验证。目标很直接——让你在跑 s11 的恢复逻辑之前先确认「调用本身是通的」否则后面所有 try/except 都是在给一个连不上的服务做无用功。适合谁看正在跟 learn-claude-code 系列、已经写到 s11、本地能跑 Python 但 LLM 调用老是报错的人。你不需要懂太多网络细节照着骨架填、照着表排查就行。2. 接入前的准备TaoToken 通道与 Key 的定位在 s11 的代码结构里client是一个全局对象agent_loop里通过闭包直接引用它。这意味着配置必须在程序启动时就确定好而不是在循环里临时改。所以我们要做的第一件事是把「用哪个通道、用哪个 Key」这件事从代码里抽出来放进settings.json。TaoToken 在这里扮演的角色是统一 Key / API 通道你拿到一个 Key配一个 base_url就能让 s11 里的client.messages.create正常发出请求。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 这条不带 UTM 参数配置里填的就是它。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-开头的一串字符先别急着贴进代码我们统一放进settings.json。这里有个容易踩的坑s11 的with_retry会对 429 和 529 做退避重试如果你 Key 本身是错的服务端返回的可能是 401 或 403这类错误不在重试白名单里会直接被raise抛出然后被外层except Exception as e捕获走到「unrecoverable」分支打印[unrecoverable] AuthenticationError。看起来像是恢复逻辑没生效其实是 Key 没配对。所以配置阶段先把认证问题解决干净后面调试恢复逻辑才不会被干扰。另外提醒一句settings.json里不要写死生产环境的 Key本地调试建议单独建一个 Key方便随时吊销。s11 本身是教学性质的 Agent Loop跑起来会频繁调用用独立 Key 也便于你在控制台看调用量。3. settings.json 骨架与字段说明下面这份骨架可以直接复制改掉api_key的值就能用。我把它设计成 s11 能直接读取的结构顶层一个llm对象里面放通道、认证、模型和恢复相关的参数。{ llm: { provider: taotoken, base_url: https://taotoken.net/api, api_key: sk-替换成你自己的Key, primary_model: claude-sonnet-4-20250514, fallback_model: claude-3-5-haiku-20241022, default_max_tokens: 8192, escalated_max_tokens: 64000, max_retries: 10, max_recovery_retries: 3 }, runtime: { request_timeout: 120, log_level: INFO } }字段逐个说。provider只是个标记方便你在代码里做分支判断填taotoken即可。base_url必须是https://taotoken.net/api注意结尾不要多加斜杠也不要填成官网首页地址否则请求会打到错误路径。api_key就是控制台创建的那串本地调试可以直接写在这里但更推荐用环境变量覆盖后面会讲。primary_model是 s11 里RecoveryState.current_model的初始值对应 excerpt 里的PRIMARY_MODEL。fallback_model对应FALLBACK_MODEL当consecutive_529 3时切换过去。这两个模型名要填服务端实际支持的填错了会在第一次调用就报model not found而不是等到 529 才暴露。default_max_tokens对应DEFAULT_MAX_TOKENSescalated_max_tokens对应ESCALATED_MAX_TOKENS。s11 的逻辑是第一次遇到stop_reason max_tokens时把上限从默认值升到升级值再重试一次。所以升级值要明显大于默认值否则升级没意义。max_retries对应with_retry里的MAX_RETRIESmax_recovery_retries对应续写次数上限MAX_RECOVERY_RETRIES。runtime里的request_timeout建议设成 120 秒因为 s11 在升级到 64000 tokens 之后单次请求耗时可能比较长超时太短会误判成网络错误触发不必要的重试。log_level控制日志详细程度调试阶段用DEBUG稳定后改回INFO。读取这份配置的代码大概长这样放在agent_loop定义之前import json import os def load_settings(pathsettings.json): with open(path, r, encodingutf-8) as f: cfg json.load(f) llm cfg[llm] # 环境变量优先方便本地调试时临时覆盖 llm[api_key] os.environ.get(TAOTOKEN_API_KEY, llm[api_key]) return cfg SETTINGS load_settings()这样你在终端里export TAOTOKEN_API_KEYsk-xxx就能覆盖文件里的值不用每次改 JSON也避免把 Key 提交到 git。4. 把配置接进 s11 的 client 与 RecoveryState配置读进来之后要接到 s11 的两个关键位置一个是client的初始化一个是RecoveryState的初始值。先看 client。s11 用的是client.messages.create(...)所以 client 需要支持 messages 接口。用统一通道时初始化方式如下from anthropic import Anthropic client Anthropic( api_keySETTINGS[llm][api_key], base_urlSETTINGS[llm][base_url], timeoutSETTINGS[runtime][request_timeout], )这里base_url直接取配置里的值指向 TaoToken 的 API 通道。timeout用 runtime 里的设置。初始化完成后client.messages.create就会把请求发到统一通道而不是默认的官方地址。接着改RecoveryState让它从配置里取值而不是硬编码class RecoveryState: def __init__(self, cfg): llm cfg[llm] self.has_escalated False self.recovery_count 0 self.consecutive_529 0 self.has_attempted_reactive_compact False self.current_model llm[primary_model] self.fallback_model llm[fallback_model] self.max_retries llm[max_retries] self.max_recovery_retries llm[max_recovery_retries]然后在agent_loop里把state RecoveryState()改成state RecoveryState(SETTINGS)max_tokens DEFAULT_MAX_TOKENS改成max_tokens SETTINGS[llm][default_max_tokens]升级那行max_tokens ESCALATED_MAX_TOKENS改成max_tokens SETTINGS[llm][escalated_max_tokens]。with_retry里的MAX_RETRIES和FALLBACK_MODEL也分别换成state.max_retries和state.fallback_model。这样一改s11 的恢复逻辑就完全由settings.json驱动了。你想调重试次数、换模型、改 token 上限都只动配置文件不用碰循环代码。这也是把配置抽出来的最大好处调试恢复逻辑时变量是可控的。有一点要注意with_retry里判断 429 和 529 用的是错误信息里的字符串匹配比如ratelimit in name or 429 in msg。不同通道返回的错误结构可能略有差异如果发现 429 没被识别到可以在with_retry里加一行日志把type(e).__name__和str(e)[:200]打出来对照实际返回内容调整匹配关键词。这一步在接入新通道时很常见不算 bug属于适配。5. 连通性验证一次请求确认通道可用配置接好之后别急着跑完整的agent_loop先做一次最小连通性验证。这一步的目的是把「通道是否通」和「恢复逻辑是否正确」分开排查。如果最小请求都失败那问题一定在配置如果最小请求成功但 agent_loop 报错才去看恢复分支。验证脚本单独写一个文件比如check_conn.pyfrom anthropic import Anthropic import json with open(settings.json, r, encodingutf-8) as f: cfg json.load(f)[llm] client Anthropic( api_keycfg[api_key], base_urlcfg[base_url], timeout60, ) resp client.messages.create( modelcfg[primary_model], max_tokens64, messages[{role: user, content: 只回复两个字通了}], ) print(stop_reason:, resp.stop_reason) print(content:, resp.content[0].text)跑python check_conn.py期望输出类似stop_reason: end_turn content: 通了看到stop_reason: end_turn和正常文本说明 Key、base_url、模型名三者都对通道是通的。这时候再去跑 s11 的agent_loop如果还报错就可以确定问题在恢复逻辑或工具执行而不是接入配置。如果这一步就失败了先看报错类型。AuthenticationError基本是 Key 问题检查是不是复制时带了空格或者环境变量覆盖成了空值。NotFoundError通常是模型名写错或者 base_url 路径不对。APIConnectionError是网络层没通检查 base_url 是不是https://taotoken.net/api有没有多写斜杠或漏写/api。RateLimitError说明 Key 有效但触发了限流等一会儿再试或者换个 Key。验证通过后建议把这个脚本留着每次改完settings.json都先跑一遍再进 agent_loop。这个习惯能帮你省掉大量「以为是恢复逻辑写错了其实是配置改坏了」的排查时间。6. 常见报错对照与排查表下面这张表覆盖了 s11 接入阶段最常遇到的几类报错。左边是你在终端看到的错误类型或关键词中间是可能原因右边是对应动作。排查顺序建议从上往下因为认证和路径问题会掩盖后面的问题。报错类型 / 关键词可能原因排查动作AuthenticationError/401Key 错误、过期、被环境变量覆盖为空检查settings.json的api_key确认TAOTOKEN_API_KEY没有设成空串PermissionDeniedError/403Key 无权限或已被吊销到控制台 API Keys 页面确认 Key 状态必要时重建NotFoundError/404base_url 路径错误或模型名不存在确认 base_url 为https://taotoken.net/api核对模型名拼写APIConnectionError网络不通、base_url 写错、超时太短先用check_conn.py验证确认地址无多余斜杠RateLimitError/429触发限流s11 的with_retry会自动退避重试若持续失败可降低调用频率overloaded/529服务端过载s11 连续 3 次后会切fallback_model确认备用模型名有效model not found模型名不在服务端支持列表换成实际支持的模型名主备都要检查prompt_too_long上下文超出窗口s11 会触发 reactive compact若压缩后仍报错则需减少历史消息重试 10 次全失败了with_retry内所有重试耗尽看被包裹的真实异常通常是认证或路径问题被反复重试[unrecoverable]后跟错误名非 429/529 的异常直接抛出根据错误名对照本表前几行定位这张表里最值得说的是最后两行。s11 的with_retry只对 429 和 529 做重试其他异常直接raise然后被外层捕获打印[unrecoverable]。所以当你看到[unrecoverable] AuthenticationError时不要以为是恢复逻辑没写对它本来就不该重试认证错误。真正要修的是 Key。反过来如果你看到重试 10 次全失败了说明错误被判定成了可重试类型但 10 次都没成功这时候要去看被包裹的真实异常是什么很可能是 429 持续触发或者通道地址间歇性不通。还有一个隐蔽的坑with_retry里判断 529 用的是overloaded in name or 529 in msg。如果通道返回的错误信息里既没有overloaded也没有529但实际是过载就不会触发切模型逻辑而是被当成不可恢复错误抛出。遇到这种情况把str(e)[:200]打出来看看实际返回的措辞再决定要不要扩充匹配关键词。这属于接入不同通道时的正常适配工作。排查时建议把log_level设成DEBUG让每次请求的 URL、模型、token 数都打出来。这样对照表格时你能直接看到请求发到了哪里、用了哪个模型比猜要快得多。7. 下一步把配置稳定下来再调恢复逻辑走到这里你应该已经能用settings.json把 s11 的 client 和 RecoveryState 都驱动起来并且通过check_conn.py确认通道是通的。接下来再回去调agent_loop里的恢复分支心里就有底了429 没触发退避那是匹配关键词的问题529 没切模型那是consecutive_529计数或备用模型名的问题max_tokens升级没生效那是配置里升级值没大于默认值。每一个分支都能对应到一个具体字段而不是一团模糊的「调用失败」。如果你还想验证不同模型在恢复逻辑下的表现可以到模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 手动发几条消息观察不同模型的返回风格和 token 消耗再决定primary_model和fallback_model怎么配。长期跑编码类 Agent 的话Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 里有适合持续调用的方案说明。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到字段含义不确定时可以对照查。ClaudeCodeAnthropic 相关说明在 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode-anthropicutm_campaignrewrite 。最后留一个我调试时的小习惯每次改完settings.json先跑check_conn.py再跑一个只发一轮、不触发工具的 agent_loop 最小用例确认基础路径通了再放开工具执行。这样出问题时你能立刻判断是新改的配置坏了还是恢复逻辑本身有 bug。s11 的价值在于让 Agent 能扛住异常但前提是异常分类准确——而准确的分类建立在配置正确的基础上。
阅读完成 · 觉得有帮助?