claude-mem 观察者错误路径设计:网关一次分类、错误信封端到端透传、让用户在一个会话内知道"发生了什么、为什么、该做什么"
本文以 claude-mem 仓库中的错误路径改造计划 为核心,讲解 claude-mem 观察者(observer)链路中"错误分类一次完成、结构化错误信封逐跳透传、用户可见文案四段式"的完整设计:从 OpenRouter 上游的实测错误响应,到 cmem.ai 网关的分类与 6 码错误词表,再到 worker 分类器、重试策略、observer-health 台账与会话启动警告的渲染。读完你可以掌握"如何让一条付费用户的推理额度耗尽错误,在日志、会话上下文和网关响应三处呈现同一句话"的工程方案,以及当前仓库中已落地的对应源码。
问题起源:一串"小损失"叠加成的静默 502 重试循环
该计划对应一次真实事故(2026-08-14 → 16 观察):付费 Pro 用户用尽推理额度后,worker 端无限循环出现 OpenRouter upstream error (status 502) ×3 重试,全程静默。6 名付费用户暗停了 3–5 天而不自知。
计划把根因拆成一条"信息损耗链",每一环都在丢东西:
- OpenRouter 对额度耗尽的 child key 返回的是 403
Key limit exceeded(而不是文档声称的 402); - cmem.ai 网关只特判 402,其余错误一律 502 包装,丢失上游状态语义;
- worker 分类器把 5xx 归为
transient,触发重试,并且丢弃了上游响应体——而响应体里恰好含有用户可自行恢复的补救链接; - observer-health 台账(PR #3538)只记录了一个无用的扁平字符串。
这个"损耗链"正是计划标题的含义——classify once, carry the message, tell the user what to do。计划的最高目标被写成一句可验收的话:额度耗尽的付费用户在一个会话内就会发现,并且用大白话知道发生了什么、以及唯一该做的一件事。 永远不要静默的 502 循环,也永远不要"只有我们自己看得懂"的报错。
核心设计:一次分类 + 6 码错误词表 + 四段式消息
整个方案只有一个设计原则:在源头(网关)分类一次。之后每一跳只"携带"结构化错误 {code, message, action, url, request_id},绝不改写、绝不对不可能成功的错误重试,并且在日志、会话启动上下文(以及未来的邮件)中呈现同一句话。
错误词表只有 6 个 code,这就是全部词汇:
code |
HTTP | 可重试? | worker kind |
|---|---|---|---|
allowance_exhausted |
402 | 否 | quota_exhausted |
key_invalid |
401 | 否 | auth_invalid |
subscription_inactive |
402 | 否 | auth_invalid |
rate_limited |
429 | 是(按 Retry-After) | rate_limit |
upstream_unavailable |
503 | 是 | transient |
bad_request |
400 | 否 | unrecoverable |
每一条消息固定 4 个部分:what(发生了什么)· why(为什么)· do this(一个具体动作)· 链接 + request id。
仓库分工:Phase 1 在 claude-mem-pro(网关)仓库,Phase 2–3 在 claude-mem(worker)仓库,Phase 3 以 PR #3538 先合入为前提。计划的硬约束是:只做根因修复;不新增重试层、不加 fallback、不加环境变量逃生门;diff 大小等于缺陷大小;只允许使用 https://cmem.ai/dashboard、support@cmem.ai、https://github.com/thedotmack/claude-mem/issues 三个链接,不得虚构 URL。
Phase 0:先钉死事实,再动刀
计划专门设了一个"整合发现"阶段,理由很直接:你没法为一次你没亲眼见过的失败写诚实的错误消息。它把三类事实全部钉死,确保后续每个 Phase 修改的是"真实的接缝"而不是猜测。
OpenRouter 上游的实测响应(2026-08-15/16,生产 key 捕获)
| 场景 | HTTP | 响应体 |
|---|---|---|
child key 触顶(limit_reset: null,即试用期) |
403 | {"error":{"message":"Key limit exceeded (total limit). Manage it using https://openrouter.ai/workspaces/default/keys/<hash>","code":403}} |
child key 触顶(limit_reset: 'monthly') |
403(括号措辞不同,如 (monthly limit)) |
匹配 /key limit exceeded/i,绝不匹配括号里的措辞 |
| key 无效 | 401 | {"error":{"message":"User not found.","code":401}} |
| 文档声称额度耗尽返回 402 | 生产中未观察到 | 保留该分支,成本为零 |
这条实测表直接决定了两处实现:网关的 403 额度判定(Phase 1)和 worker 分类器 body 标记列表里加入 key limit exceeded(Phase 2)。
计划盘点出的网关现状(claude-mem-pro 仓库)
针对 src/app/api/inference/v1/chat/completions/route.ts(432 行),计划逐行盘点了当时的每个错误返回点(401 未授权 → 402 订阅不活跃 → 429 限流 → 400 body 解析 → 500 凭据不可读 → 503 签发失败 → 仅 402 特判的额度文案 → !upstream.ok || !payload 兜底把 403 包成 429/502,"403 今天就从这里漏出去")。盘点还确认了几个关键缺口:
- 全链路没有任何 request id——不生成、不读 OpenRouter 的、不记录。必须在服务端用
randomUUID()铸造,并贯穿 JSON body、x-request-id头与每行console.*; PRIVATE_HEADERS = { 'Cache-Control': 'private, no-store' }逐路由复制粘贴,src/lib中不存在共享的错误响应助手(greperrorResponse|jsonError|ApiError零命中)——Phase 1 负责创建;paymentStatus实际取值'none' | 'pending' | 'active' | 'trialing' | 'past_due' | 'cancelled';- "下周期 <日期> 重置" 在不做 Stripe 往返的前提下不可推导(
current_period_end不在proUsers表上),因此决定:错误消息里不写日期,只说 "at the start of your next billing cycle"; - 测试约定是独立 tsx 脚本
scripts/test-<name>.ts,模式上"导出纯函数并测试它"(参考路由里为可测试而导出的bearerFrom),无需 HTTP mock。
计划盘点出的 worker 现状(本仓库,改造前)
classifyOpenRouterError旧版分支顺序:body 标记quota exceeded|insufficient credits|insufficient_quota→quota_exhausted;429 →rate_limit;401/403 →auth_invalid;400/404 →unrecoverable;5xx →transient(事故放大器);无状态 →transient;兜底(含 402)→unrecoverable且仅保留 body 前 200 字符。input.requestId被接收但从未使用;ClassifiedProviderError只有{ kind, retryAfterMs?, cause },kind 是开放字符串联合,没有结构化字段;- 每次失败产生 5 行日志:重试 warn ×2、
init query failed、✗ <Provider> agent error、Generator failed——用户或 Agent 无法从中挑出"该读哪一行"; - 服务端有一份不得跨边界 import 的分类器副本
src/server/generation/providers/shared/error-classification.ts(文件头注释明确禁止 import worker 代码),Phase 2 要求只镜像 body 标记列表、不跨边界引用。
Phase 1:网关——一次分类、诚实状态码、结构化信封、request id
这一阶段是消息的"出生地":网关是唯一知道用户套餐、试用状态和额度的跳,所以只有它能说出"你已用尽本周期 $30 额度——下个计费周期重置,急需可邮件 support"。诚实的状态码让 worker 停止对不可重试错误的重试;request id 让"联系 support"成为真实通路而非死胡同。分支 feat/inference-error-taxonomy off main(claude-mem-pro 仓库)。
1.1 新模块 src/lib/http/gateway-error.ts
计划给出的完整实现形状如下(6 码到状态码的映射表即前面词表的可执行版本):
import { NextResponse } from 'next/server';
export type GatewayErrorCode =
| 'allowance_exhausted' | 'key_invalid' | 'subscription_inactive'
| 'rate_limited' | 'upstream_unavailable' | 'bad_request';
export const GATEWAY_ERROR_STATUS: Record<GatewayErrorCode, number> = {
allowance_exhausted: 402, key_invalid: 401, subscription_inactive: 402,
rate_limited: 429, upstream_unavailable: 503, bad_request: 400,
};
export interface GatewayError {
code: GatewayErrorCode;
message: string; // what happened + why, one sentence
action: string; // what the user should do, one sentence
url?: string; // approved links only
request_id: string;
}
export const PRIVATE_HEADERS = { 'Cache-Control': 'private, no-store' } as const;
export function gatewayErrorResponse(err: GatewayError, extraHeaders: Record<string,string> = {}) {
return NextResponse.json({ error: err }, {
status: GATEWAY_ERROR_STATUS[err.code],
headers: { ...PRIVATE_HEADERS, 'x-request-id': err.request_id, ...extraHeaders },
});
}
同时导出一个纯函数的上游映射器(这是被测试的对象):
export type UpstreamOutcome =
| { kind: 'allowance_exhausted' }
| { kind: 'rate_limited'; retryAfterSec: number }
| { kind: 'upstream_unavailable'; detail: string };
/** Map a non-OK (or unparseable) OpenRouter response to a taxonomy outcome. */
export function mapUpstreamFailure(status: number, payload: unknown): UpstreamOutcome
映射规则(顺序敏感):
status === 402,或(403 || 401且/key limit exceeded|limit exceeded|insufficient credits|negative credit/i命中 message)→allowance_exhausted;status === 429→rate_limited(retryAfterSec 60——当前代码不读上游retry-after头,保持 60);- 其余一切(5xx、401
User not found.、无额度措辞的 403、非 JSON/!payload、未知 4xx)→upstream_unavailable,detail = payload?.error?.message ?? 'HTTP <status>'只进服务端日志——绝不发给客户端,因为它可能含有指向"我们自己工作区"的 manage-key URL。
1.2 消息文案(最终版;客户端逐字渲染 message + action)
| code | message | action | url |
|---|---|---|---|
allowance_exhausted(active) |
You've used your $<limit> CMEM Pro inference allowance for this billing cycle. |
It resets at the start of your next billing cycle. Need more before then? Email support@cmem.ai. |
https://cmem.ai/dashboard |
allowance_exhausted(trialing) |
You've used your free-week inference allowance ($<limit>). |
Your full allowance unlocks when your trial converts. Want it sooner? Email support@cmem.ai. |
https://cmem.ai/dashboard |
key_invalid |
This CMEM Pro key isn't recognized. |
Run \npx claude-mem pro-setup` to re-link this machine, or copy a fresh key from your dashboard.` |
https://cmem.ai/dashboard |
subscription_inactive(past_due) |
Your CMEM Pro payment didn't go through, so the observer is paused. |
Update your card in the dashboard and observations resume immediately. |
https://cmem.ai/dashboard |
subscription_inactive(cancelled) |
Your CMEM Pro subscription has ended. |
Resubscribe from the dashboard to turn the observer back on. |
https://cmem.ai/dashboard |
subscription_inactive(其他) |
Your CMEM Pro subscription isn't active. |
Check billing in the dashboard, or email support@cmem.ai. |
https://cmem.ai/dashboard |
rate_limited |
Too many observer requests in the last minute. |
Retrying automatically in 60s — nothing to do. |
— |
upstream_unavailable |
The observer model is temporarily unavailable. |
claude-mem retries automatically. If this lasts more than an hour, email support@cmem.ai with the request id. |
— |
bad_request |
The observer sent a request the gateway couldn't parse. |
This is a claude-mem bug — please open an issue with the request id. |
https://github.com/thedotmack/claude-mem/issues |
<limit> = proUser.openrouterKeyLimitUsd 去掉尾部零格式化($30、$2.33);为 null 时省略金额子句。
1.3 路由改动与验证清单
路由层面的替换要点:POST 顶部 const requestId = randomUUID();,所有 console.warn/error 上下文对象加 { requestId };unauthorized()、402 订阅文案、429、400 body 解析、500 解密失败、503 签发失败分别替换为对应 taxonomy code;整个 !upstream.ok 兜底块替换为 mapUpstreamFailure + switch,其中 allowance_exhausted 分支保留原有的 captureServer('pro_inference_cap_exhausted', { limit_usd, trialing }) 事件不变(analytics 规则 A3"props 里不放 token/邮箱"、A7"analytics 永不拖垮推理");上游失败一律 503,永不 502;fetch 抛错/abort 也归 503,超时区分只进服务端日志。成功路径不动。
验证是 grep 级别的硬门槛:
grep -n "502" …/chat/completions/route.ts→ 0 命中;grep -n "NextResponse.json({ error" …→ 0 命中(全部经gatewayErrorResponse);grep -n "randomUUID" route.ts→ POST 顶部 1 命中;- 部署后真机冒烟:
curl -sD - https://cmem.ai/api/inference/v1/chat/completions -H "Authorization: Bearer cm_pro_bogus" -d '{}'→ HTTP 401、JSONerror.code === 'key_invalid'、x-request-id头存在。
测试脚本 scripts/test-inference-errors.ts 只测纯函数:mapUpstreamFailure 对 403 total/monthly、402、401 User not found.、429、500/null 等 7 类输入的映射,GATEWAY_ERROR_STATUS 恰好 6 键且状态码与词表一致,gatewayErrorResponse 设置 x-request-id 与 Cache-Control: private, no-store。反模式护栏:不加 Stripe 调用、不加新环境变量、上游 detail 永不进响应体、渲染出的消息里断言不含 cm_pro_/sk-or- 片段。
Phase 2:worker——透传信封、额度错误不再重试、日志只打一行
这一阶段让 worker 成为"忠实的信差":网关的话逐字存活、额度错误快速失败而非循环、恰好存在一行用户(或其 Agent)能读懂并据此行动的日志。该阶段独立于 Phase 1(对旧式响应体同样有效),也顺带修复了非 Pro 用户直连 OpenRouter 时 Key limit exceeded 响应体被丢弃的同样损失。
结构化字段:ClassifiedProviderError 的扩展
当前仓库 provider-errors.ts 中可以看到计划 2.1 的落地形态——ProviderErrorDetail { code?, action?, url?, requestId? } 作为构造参数的可选结构化字段,并暴露为只读属性:
export interface ProviderErrorDetail {
code?: string;
action?: string;
url?: string;
requestId?: string;
}
文件头注释点明了用途:当上游(如 cmem.ai 网关)返回 taxonomy 信封时,worker verbatim 携带这些字段,使日志行与会话启动警告呈现同一句话。同文件的 describeProviderError 是唯一的人工可读渲染:
export function describeProviderError(err: ClassifiedProviderError): string {
return `${err.message}${err.action ? ' — ' + err.action : ''}${err.url ? ' ' + err.url : ''}${err.requestId ? ` (req ${err.requestId})` : ''}`;
}
即 message — action url (req id),缺失部分自动省略。日志行用它,台账存结构化字段。
分类器:先解析信封,再走 legacy 兜底
OpenRouterProvider.ts 中的实现与计划 2.2 一一对应。首先是 GATEWAY_CODE_TO_KIND 词表到 kind 的映射:
const GATEWAY_CODE_TO_KIND: Record<string, ProviderErrorClass> = {
allowance_exhausted: 'quota_exhausted',
key_invalid: 'auth_invalid',
subscription_inactive: 'auth_invalid',
rate_limited: 'rate_limit',
upstream_unavailable: 'transient',
bad_request: 'unrecoverable',
};
parseUpstreamErrorEnvelope 做 best-effort 的 { error: {...} } 解析,非 JSON 时静默回落到 legacy 路径。classifyOpenRouterError 的分支顺序是:
- 信封命中(
error.code是 6 个 taxonomy 字符串之一):message = envelope.message逐字保留、action/url透传、requestId = envelope.request_id ?? input.requestId,kind 按词表映射;rate_limit额外带retryAfterMs ?? 60_000。直接返回,不再走后续判断; - legacy 兜底:消息必须包含上游响应体——JSON 取
error.message,否则bodyText.substring(0, 300),形状统一为`OpenRouter <class> (status N): <upstream message>`。例如OpenRouter quota exhausted (status 403): Key limit exceeded (total limit). Manage it using https://openrouter.ai/…。额度判定(L118-L133)的标记列表按计划扩为quota exceeded|insufficient credits|insufficient_quota|key limit exceeded|limit exceeded(429 除外)|negative credit,且status === 402直接判quota_exhausted(旧版 402 落入 unrecoverable 兜底);429 →rate_limit(带 Retry-After)、401/403 →auth_invalid、400/404 →unrecoverable、5xx 与网络错误 →transient,每个分支都带requestId。
一个值得注意的细节写在源码注释里:"Rate limit exceeded" on a 429 is a rate limit, not quota——所以通用的 limit exceeded 标记只在非 429 路径生效,key-limit 标记永远优先。
服务端镜像 error-classification.ts 同步更新了标记列表(同样含 key limit exceeded 与 status === 402),文件头注释解释了为什么是复制而不是 import:Phase 5 反模式护栏要求 src/server/* 不得 import src/services/worker/*。
重试策略与"日志只打一行"
retry.ts 中的 isRetryableKind 是重试决策的唯一入口:
export function isRetryableKind(err: unknown): boolean {
if (!isClassified(err)) {
// Unclassified errors are treated as transient (preserve old default).
return true;
}
return err.kind === 'transient' || err.kind === 'rate_limit';
}
改造后 quota_exhausted/auth_invalid/unrecoverable 不再进入重试循环——这正是事故中"502 无限 ×3 重试"的终结点。计划同时明确不得改变未分类错误的可重试性(那是范围外的行为变更),源码注释也把这钉死为"preserve old default"。withRetry 对 rate_limit 尊重 retryAfterMs,其余走指数退避 + 抖动,上限 2 次(这些 POST 不严格幂等)。
日志扇出从 5 行收敛为 1 行:分类错误在 init query failed/handleSessionError 处降为 debug,唯一 error 级行落在 SessionRoutes.ts——
if (isClassified(error)) {
logger.error('SESSION', 'Observer failed', {
sessionId: session.sessionDbId,
provider,
kind: error.kind,
...(error.code ? { code: error.code } : {}),
...(error.requestId ? { requestId: error.requestId } : {}),
}, describeProviderError(error));
}
源码注释解释了两个工程决策:传递渲染后的字符串而非 Error 对象,避免 errorSink/captureException 对同一事件三重触发;以及"分类错误是用户状态(额度/认证/限流)而非 bug,所以不触发异常捕获"。最终效果:每次重试最多一行 WARN(仅 transient/rate-limit),每次失败恰好一行 ERROR,该行同时给出 code、message、action 与 request id。
Phase 2 的测试与验证
计划要求把 tests/worker/provider-classifiers.test.ts 中 describe('classifyOpenRouterError') 的用例从"只断言 kind/retryAfterMs"升级为断言 message、code、action、url、requestId,当前仓库中可以看到落地,例如 L206 起的用例:allowance_exhausted 信封(402 + {code, message, action, url, request_id: 'abc'})→ kind 'quota_exhausted'、code 'allowance_exhausted'、message/action 逐字、requestId 'abc'。legacy 用例则断言 403 Key limit exceeded 的 message 同时包含措辞与 https://openrouter.ai/ URL。计划还要求新增 tests/worker/retry-policy.test.ts 钉住 isRetryableKind 的完整真值表(含"普通 Error 仍重试"这一现状)。
验证清单里最有实战价值的是手动复现步骤:把子 key 的 limit 临时 PATCH 到当前用量,触发一次 observer 回合,确认 worker 日志恰好一行 Observer failed、文本含 Key limit exceeded(Phase 1 之前)或 taxonomy action(之后)、且没有任何 Retrying OpenRouter 行;然后恢复 limit。grep 门槛:grep -rn "key limit exceeded" 在 worker 与 server 两处分类器各 1 命中;grep -n "requestId" OpenRouterProvider.ts 显示它被真正使用而非仅仅接收。
Phase 3:台账 + 会话启动警告渲染结构化错误
这是"一个会话内发现"的另一半:用户不读 worker 日志,他们打开 Claude Code。PR #3538 已经在下一个会话顶部放置了警告,本阶段让警告说出正确的话——网关的消息、"What to do:" 行、链接、request id,而不是一句对 Pro 用户完全错误的通用 "check your settings.json"。没有这个阶段,Phase 1–2 只是生产了一条没人看到的好消息。
当前仓库 observer-health.ts 是这一阶段的完整落地(文件头注释交代了动机:2026-08-09 的 provider 额度故障曾 17 小时无人察觉,所以"observations 断流"必须永不静默)。关键设计点:
状态扩展且向后兼容。ObserverHealthState 在 lastErrorMessage 之外新增 lastErrorCode、lastErrorAction、lastErrorUrl、lastErrorRequestId(以及后来追加的 lastErrorKind),readObserverHealth 用 { ...EMPTY_STATE, ...parsed } 合并,旧版台账文件读出来新字段自动为 null——正是计划 3.1 的"additive fields are backward compatible"。
双形态入账。recordObserverFailure 第二参数为 string | ObserverFailureDetail:字符串形态保持旧行为;对象形态填充新字段,其中 message/action 经 scrubErrorMessage 清洗(正则抹掉 sk-…、cm_pro_…、Bearer 令牌与 name=value 形态的凭据赋值,但刻意保留数字——"数字是限额/计数,不是凭据",让 key limit exceeded 这类诊断原文存活)。注释特别强调清洗写入前与渲染时各做一次,防止旧版本写入的台账向会话上下文注入秘密。还有一个计划未提及但同属此模块的稳健性细节:台账的读-改-写被一个 fail-open 的进程间文件锁(withLedgerLock)串行化,因为丢增量会把 consecutiveFailures 压在阈值下、反而压掉了这个模块存在的意义。
hook 侧传结构化对象。SessionRoutes.ts 在唯一 error 行之后入账:
recordObserverFailure(provider, isClassified(error)
? { message: error.message, kind: error.kind, code: error.code, action: error.action, url: error.url, requestId: error.requestId }
: errorMsg);
渲染器替换通用补救文案。renderObserverHealthWarning 在 Latest error: <message> 之后按需追加 What to do: <action>、Link: <url>、Request id: <requestId>;当 action 存在时替换那句对 Pro 用户错误的通用 "check the observer provider's API key, spend limit, and base URL in ~/.claude-mem/settings.json"(源码注释原话:A classified error already says what to do; the generic settings.json remedy is wrong for e.g. Pro users whose allowance ran out)。实现中还演化出了计划未覆盖的精细分支:quota_exhausted 的失败有专属警告路径(isQuotaFailure 分支),刻意不提供重启链接——重启清不掉额度耗尽,反而会解除正在保护账户的退避断路器。
注入位置。ContextBuilder.ts 的 withObserverHealthWarning 把警告应用到每一条上下文路径(含空库、无记忆欢迎语),且放在上下文下方而非顶部——时间线很长,警告放顶部在上下文打印完之前就已经滚出屏幕了;最后渲染的内容才是模型第一次回复前仍在屏上的内容。
Phase 3 的验收是端到端的:用受限 key 跑一个 observer 回合,然后开新会话,确认启动上下文包含 taxonomy message、What to do: 行、https://cmem.ai/dashboard 与 request id;再跑一个成功回合,确认 recordObserverSuccess 清零后警告消失。对应测试 tests/observer-health.test.ts 覆盖对象形态四字段往返、字符串形态保持 null、有 action 时不含 settings.json 文案、旧台账文件兼容性。
Phase 4:验证与发布
计划坚持用户结果才是证明,绿测试不算:给真实 key 设限、看着恰好一行日志出现且零重试、开新会话读到警告。且三个呈现面(日志、会话警告、网关响应)若出现措辞不一致,阶段即失败。具体门槛:
- 两仓库测试全绿、PR 合入(Phase 1 在
claude-mem-pro,合入 main 自动部署;Phase 2–3 在claude-mem); - grep 护栏:
claude-mem-pro中grep -rn "502" src/app/api/inference= 0;claude-mem中grep -rn "OpenRouter upstream error (status" src/= 0(消息形状改为含 body); - 真机验证:worker 日志一行
Observer failed+ 零重试;下一会话启动显示带 action 的警告;网关响应带x-request-id;PostHog 收到pro_inference_cap_exhausted; - 按
version-bump流程发 patch 版claude-mem,不编辑 CHANGELOG。
计划同时明确列出范围外的跟进项(需要邮箱查找 + 去重状态的升级提醒邮件、让 allowance_exhausted 能说日期的反规范化计费周期列、per-plan 额度差异化),避免范围蔓延。
贯穿全文的反模式清单
计划的 "Anti-patterns (do not)" 一节值得完整保留,它是这套设计的负面规格:
- 不得 502 包装任何用户可行动的错误;不得在 body 里返回
code: <upstream status>; - PostHog props 与错误消息里不得出现子 key、setup token、邮箱;
- 不得虚构
cmem.ai/upgrade或/billing链接——它们不存在,只允许三个核准链接; - 不得在错误路径上发起 Stripe 调用只为一个日期;
- 不得新增重试层;不得让未分类错误变为不可重试(范围外的行为变更);
src/server/*不得 importsrc/services/worker/*。
小结:这条错误路径回答了什么问题
回顾整个链路,每个环节现在都有明确的职责:网关在唯一知道套餐状态的地方分类一次并把 detail 留在服务端日志;worker 作为信差逐字透传信封,让不可重试的错误在 isRetryableKind 处短路;describeProviderError 保证日志行是唯一的 error 级呈现;observer-health 台账 以向后兼容的方式结构化存盘、双次清洗防泄密;会话启动警告 把同一句话送到用户真正会看的地方。对设计同类"跨进程错误传播"系统的读者,这套方案的可迁移经验是:错误词汇表要小到能背下来(6 个 code)、消息结构要固定(4 段)、每一跳要么分类要么透传、并且用 grep 级护栏防止 502 包装这类回归悄悄长回来。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00