Repository navigation
[2/3] Error cause in string variants - #290
Conversation
|
本地实验( 上游在 schema 校验失败时,cause 是
|
|
已在 402bce5 处理:
Generated by Claude Code |
8606180 to
a04f514
Compare
Describes how aimux's error system differs from the AI SDK's, traces today's error paths from the public entry functions to the lines that drop the underlying error, and designs `cause` across the core, the transport layer, the ops envelope, the generated DTOs and every binding. The open questions in section 10 stay open. Line numbers refer to master 4220847, and the text includes what implementing #289, #290 and #293 found. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
095464c to
e3a861b
Compare
a04f514 to
7d78192
Compare
eric8810
left a comment
There was a problem hiding this comment.
Review
核心改动(六个变体、构造函数、消息/错误码、下载 URL 脱敏)基本正确,但有一个安全问题需要先修:
- 重要(安全)
aimux-providers/src/bedrock/model.rs:54:bytes_stream()的错误直接作为 cause,没有先without_url()。reqwest 的 body/decode 错误可能带完整 URL 和 query,与 #289 的 URL 去除规则不一致,存在把签名 URL/密钥写进 cause 的风险。建议let error = error.without_url();后 message 和 cause 都用它。 - 次要
aimux-core/src/output.rs对jsonschema::validator_for(schema)失败用了TypeValidation,而parse_tool_call.rs:286同样场景用SourceError;建议统一,避免把“调用方 schema 非法”说成“模型值不符合 schema”。 - 次要 缺少
AI_TypeValidationError的 DTO 向量(value+ nullable cause);tools/aimux-web/src/api/replay.rs和traces.rs仍丢弃 serde cause。 - 提示
parse_tool_call.rs新增测试插在了 integral-number 测试的文档注释和测试之间,注释位置需要调整。
ErrorCause is a nested AiMuxError or a SourceError chain copied from an external error's source() chain. It is what source() returns and serializes as an optional `cause`. ApiCallError gains cause and caused_by(..); RetryError's source is its last attempt. Part 1 of RFC-0047. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
FetchError carries a SourceError built from reqwest's error after without_url(), and fetch, redirects, body reads, the JSON and SSE handlers and WebSocket keep the original error as the cause. Transport messages lose the URL: 'error sending request for url (...)' is now 'error sending request'. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
A Swift struct cannot store itself inline, so SourceError.cause did not compile. The field keeps its public name and type and is stored in a WireIndirect. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
ErrorCause and SourceError reach every binding through the generator. AiMuxError/004 and AiMuxErrorInput/004 carry an ApiCall cause chain and are registered in index.json and sources.json. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
CHANGELOG Breaking, the request pipeline's cause row, and 228 DTO vectors in the conformance doc. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
JsonParse, NoOutputGenerated, InvalidResponseData, InvalidArgument,
InvalidPrompt and Other become { message, cause }, with constructors of
the same names; From<serde_json::Error> keeps the serde error as the
cause. InvalidToolInput.cause is an ErrorCause instead of a string.
The new TypeValidation { message, value, cause } is the AI SDK's
TypeValidationError. It only appears as a cause and shares ops error
code 3 with InvalidResponseData.
Part 2 of RFC-0047.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
As in the AI SDK (parse-json.ts, validate-types.ts, parse-tool-call.ts), InvalidToolInput's cause is the JsonParse or TypeValidation error itself, and structured output that does not match its schema has a TypeValidation cause. Download transport errors drop the URL before they become the message and the cause: a signed URL carries its credentials in the query. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
The sites that formatted an underlying error into one of the six variants now also keep it as the cause; the others use the new constructors. Messages are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
…-web The ops manifest lists TypeValidation under error code 3, and the ops error goldens show InvalidToolInput and InvalidResponseData with their causes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
The generated types follow the new variant shapes. Python, Java and Kotlin dispatch errors by name and get a TypeValidationError class. AiMuxError/005-006 and AiMuxErrorInput/005-006 come from the ops error goldens. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
CHANGELOG Breaking, the error model's cause section, the code 3 aliases, 232 DTO vectors, and RFC-0035's repair-context example. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
e3a861b to
b570ada
Compare
7d78192 to
bec5828
Compare
Describes how aimux's error system differs from the AI SDK's, traces today's error paths from the public entry functions to the lines that drop the underlying error, and designs `cause` across the core, the transport layer, the ops envelope, the generated DTOs and every binding. The open questions in section 10 stay open. Line numbers refer to master 4220847, and the text includes what implementing #289, #290 and #293 found. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C
|
回复 @eric8810 的 review(左列是 eric 的原话):
整条栈已 rebase 到 master Generated by Claude Code |
* docs(rfc): RFC-0047 error system and error cause Describes how aimux's error system differs from the AI SDK's, traces today's error paths from the public entry functions to the lines that drop the underlying error, and designs `cause` across the core, the transport layer, the ops envelope, the generated DTOs and every binding. The open questions in section 10 stay open. Line numbers refer to master 4220847, and the text includes what implementing #289, #290 and #293 found. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C * docs(rfc): answer the RFC-0047 review Retry's lastError as every binding's native cause, the Node and Java cause classes, an accurate security section with a size and redaction question, the full list of breaking changes, Swift's WireIndirect, and how RFC-0044, RFC-0048 and #349 meet this RFC. Q2 and Q3 are marked adopted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S2GJxE3fH7rQvAwXLg2f1C --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
eric8810
left a comment
There was a problem hiding this comment.
评审:[2/3] Error cause in string variants —— 代码可批;分支上的合并提交必须重做(阻塞)
阻塞项(重要):当前分支头 20fc9c5f(由维护者侧评审流程误推,责任在我方)是一次丢弃 master 内容的合并——树哈希与合并前 tip 完全相同。照此合并会删除 rfc/0044、rfc/0046、rfc/0047(−1311 行,含本 PR 自己实施的规范)并回退 #333 的 CI 门禁改动(ci.yml)。GitHub 仍显示 MERGEABLE,但这是假象。请对 master 重做真实合并([1/3] 内容已验证与 master 等价,应无冲突)或至少恢复那 4 个文件后重推。修复后本 PR 可批。
代码评审(基于真实 [2/3] 增量,+3026/−1421):
- RFC-0047 §2.5–2.7/§4.2/§9/§11 如实落地:六个字符串变体改形
{message, cause: Option<ErrorCause>}、TypeValidation 与 InvalidResponseData 共用码 3 并在 manifest 别名表登记、InvalidToolInput.cause 对象化(Display 旧文字不变)、serde 错误作为 cause、download.rs的transport_error()消息与 cause 均不带 URL(有?X-Amz-Signature=SECRET泄露测试)。 - 构造点 448 处无 cause / 120 处带 cause 与自报吻合(47 个 provider 文件),无元组残留。
- 生成物同步完整:八绑定 + schema/types + DTO 向量 238(新增 8 个,含输入方向 cause 的双向向量)+ manifest digest + ops golden + 错误模型文档——没有重蹈 #288 的生成链失步。
- 测试覆盖两层失败模式(JsonParse vs TypeValidation vs SourceError)、wire 快照含嵌套 cause 形状、穷尽 match。
修复合并方式后即可合并。
eric8810
left a comment
There was a problem hiding this comment.
复核:阻塞已解除,改判批准
作者的 448460b3(restore master content lost by the cause stack merge)完整恢复了被误合并丢弃的内容:rfc/0044/0046/0047(+1311 行)与 ci.yml 的 #333 门禁均已核实在树中;分支与 master 的差异回到 [2/3] 真实增量(260 文件,+3034/−1429)。1a33d20e 的 refused-connection fixture 可移植化小修(fetch.rs 15 行)一并看过,无问题。
此前的阻塞项(维护者侧误推的丢弃式合并提交 20fc9c5f)就此中和——责任在我方评审流程,已在上一条评审中说明;作者以修复前进处理,等效且保留了栈上后续工作,无需撤销。
原评审对代码本体的结论不变(RFC-0047 §2.5–2.7/§4.2/§9/§11 逐条落地、生成物同步完整、构造点统计吻合、两层失败与 SECRET 泄露测试到位)。CI 绿后可合并;合并后 #293 记得 rebase。
Brings in the error-cause series (#289, #290, #293). Conflicts: ops tests take master's `InvalidParams { .. }` without `unprojected()`; envelope.rs keeps `BindingError::result_serialization` without a clock; binding READMEs and Node error.ts keep the cause text without retryAfterMs. Bindings regenerated with aimux-codegen. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G5j9hYv7hDeYgMvvRu2F9T
…h-the-ai-sdk master's error-cause change (#289/#290/#293) made InvalidResponseData a struct variant; closed_before_finishing now builds it with AiMuxError::invalid_response_data. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
Keeps this PR's API call error for a response without output (master's #290 had turned the old message into InvalidResponseData), and keeps both this PR's tests and master's raw-chunk tests (#285) at the end of open_responses_test.rs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoxcKU4zBD64Lri3e4Lrq5
Requested by li · project thread
Summary
RFC-0047(#286)实现的第 2 步:只带一个字符串的六个错误变体改成
{ message, cause },117 处构造它们的地方把手上的底层错误(serde、base64、url、io、TLS)作为 cause 保留;InvalidToolInput.cause从字符串改为错误对象。值不符合 schema 时的 cause 是新增的TypeValidation(AI_TypeValidationError),和上游一样。下载错误先去掉 URL,所以下载失败的消息不再带 URL;其他消息文字不变。叠在 #289(第 1 步:核心类型和网络层)上。第 3 步是 ops 信封和各语言绑定(#293)。
字符串变体错误体系概览
基于 #289,整条栈在 master
e115e723上。六个变体在调用路径的各层产生,行号是开 PR 时的 master42208472(#268 合并后):这些位置都是
format!("…: {error}"):底层错误的文字进了消息,错误本身丢掉。AI SDK 的JSONParseError、TypeValidationError、InvalidArgumentError、InvalidPromptError、NoOutputGeneratedError、AISDKError构造时都接受cause,InvalidToolInputError.cause是解析或校验错误对象。本 PR 的改动
类型,
aimux-core/src/error.rs:构造处,按底层错误分:
工具调用和结构化输出,和 AI SDK 一样拆成两层:
调用方看到的(Display 取 cause 的 message,文字和以前的字符串一样;JSON 见
aimux-core/tests/error_value_golden_test.rs):下载,
aimux-core/src/download.rs:DTO:类型变化由 #268 的生成器重新生成到每种语言;
contract-tests/dto/AiMuxError/006.json(InvalidToolInput的 cause 是AI_JSONParseError)、007.json(InvalidResponseData带嵌套 cause)取自 ops 错误黄金样例,这两个样例也一起更新;008.json是带value和校验器 cause 的AI_TypeValidationError(取自新黄金样例aimux-type-validation.json),009.json是value: null、没有 cause 的AI_TypeValidationError。AiMuxErrorInput有同样四个,都登记在contract-tests/dto/index.json和sources.json,向量总数从 230 变成 238。新增 ops 错误黄金样例aimux-type-validation.json;Python、Java、Kotlin 的异常类按名字分派,各加一个TypeValidationError。文档:CHANGELOG Breaking 合并成一条,
docs/error-model.md新增「错误原因」一节(含TypeValidation),docs/ai-sdk-request-pipeline.md的cause一行改为所有接受 cause 的错误类,docs/rfc-0039-implementation.md的错误码 3 别名加上TypeValidation,docs/rfc-0039-conformance.md的向量数改为 238。新测试:
parse_tool_call.rs经过parse_tool_call测两种失败,非法 JSON 的 cause 是带解析器错误的AI_JSONParseError,不符合 schema 的 cause 是带value和校验器错误的AI_TypeValidationError;output.rs检查结构化输出不符合 schema 时 cause 是TypeValidation,schema 本身非法({"type": 12})时 cause 是SourceError;download.rs用带?X-Amz-Signature=SECRET的 URL 检查消息和 cause 都不含 query。Type of change
Breaking:六个变体从元组变体改为结构体变体,
InvalidToolInput.cause类型变化,新增变体TypeValidation(对AiMuxError做穷尽匹配的代码要加一支)。已有错误名、错误码不变;JSON 里InvalidToolInput.cause从字符串变为错误对象;下载失败的消息不再带 URL。Checklist
cargo fmt --all -- --checkpassescargo clippy --workspace --all-targets -- -D warningspassescargo test --workspacepasses (cassette playback — no network/keys)README.md,docs/,rfc/,bindings/README.md)Notes for reviewers
X(_)→X { .. });值得看的是error.rs、output.rs、parse_tool_call.rs,以及 provider 里 117 处带 cause 的地方是否选对了底层错误。jsonschema的错误借用被校验的值,不能存成'static的 source,所以它只以文字形式进SourceError,挂在TypeValidation.cause下。TypeValidation只加了上游的value,没有加context(field、entityName、entityId):aimux 现在没有地方填它。