Skip to content

pref:增加错误时重试功能#108

Merged
minorcell merged 4 commits into
1024XEngineer:mainfrom
phantom5099:main
Apr 1, 2026
Merged

pref:增加错误时重试功能#108
minorcell merged 4 commits into
1024XEngineer:mainfrom
phantom5099:main

Conversation

@phantom5099

@phantom5099 phantom5099 commented Apr 1, 2026

Copy link
Copy Markdown
Collaborator

变更说明

本次 PR 建立了 NeoCode provider 层的统一错误处理体系,并在 HTTP 传输层和 Runtime 层分别引入了两级互补的自动重试机制,同时保证了 SSE 流式传输中途断连时的优雅降级。


主要改动

1. 统一 ProviderError 错误体系

新增文件: internal/provider/errors.gointernal/provider/errors_test.go

  • 定义 ProviderError 结构体,包含 StatusCodeCode(语义分类)、MessageRetryable 字段,实现 error 接口
  • parseErroropenai.go)基于 HTTP 状态码 + 可选的 body JSON message 做简单映射, 追求覆盖 OpenAI 私有错误码(如 invalid_api_keymodel_not_found),只处理通用 HTTP 语义
  • classifyStatus 将标准 HTTP 状态码映射为 9 种 ProviderErrorCodeauth_failedforbiddennot_foundclient_errorrate_limitedserver_errortimeoutnetwork_errorunknown
  • IsRetryableStatus 判定 429 和 5xx 为可重试,上层通过 errors.As(err, &pErr) 类型断言获取 Retryable 标记
  • 辅助构造函数:NewProviderErrorFromStatusNewNetworkProviderErrorNewTimeoutProviderError

2. HTTP 层重试 — RetryTransport

新增文件: internal/provider/transport/retry.go

  • 实现 http.RoundTripper 接口的中间件 RetryTransport,在首次请求阶段自动重试可恢复错误
  • 默认配置:最大重试 2 次,初始等待 500ms,最大等待 5s
  • DefaultRetryableFuncProviderError.Retryable == true 或网络层错误(连接拒绝、超时、DNS 失败)时触发重试
  • 指数退避 + 随机抖动([0.5, 1.5) × base),避免惊群效应
  • 重试前自动 drain 前一次 response body 防止连接泄漏,支持 seeker body 重新读取
  • context 取消时立即停止重试

3. OpenAI Provider 集成

修改文件: internal/provider/openai/openai.go

  • New(cfg, opts ...BuildOption) 引入 functional option 模式,WithTransport(rt) 支持注入任意 http.RoundTripper
  • Driver()Build 默认注入 RetryTransportDefaultRetryConfig()
  • SSE 流式传输中途断连不在 Transport 重试范围内consumeStreamio.EOF 时 flush 已缓冲数据,通过 finalizeResponse 返回已累积的内容、tool calls 和 usage,保证"有部分结果总比没有好"的降级行为

4. Runtime 层重试

修改文件: internal/runtime/runtime.gointernal/runtime/events.gointernal/runtime/runtime_test.go

  • 新增 callProviderWithRetry 方法:在 Transport 层重试耗尽后,Runtime 层仍可重试整个 provider.Chat() 调用(默认最多 2 次)
  • 每次重试重新构建 provider 实例和流式事件转发管道
  • 新增 EventProviderRetry 事件类型,向 TUI 通知重试进度
  • 通过 ProviderError.Retryable 判断是否值得重试,非可重试错误直接返回
  • 指数退避策略与 Transport 层一致

5. 两级重试协作关系

请求失败
  ├── Transport 层重试(RetryTransport, HTTP 级别)
  │     ├── 重试成功 → 返回结果
  │     └── 重试耗尽 → 返回最后一个 error
  └── Runtime 层重试(callProviderWithRetry, 业务级别)
        ├── ProviderError.Retryable == true → 重试
        ├── 重试成功 → 继续主循环
        └── 重试耗尽 → 发送 EventError,终止 Run

预期收益

收益 说明
统一错误语义 ProviderError 收敛所有 provider 错误,上层(runtime、TUI)无需关心底层厂商差异,通过 errors.As 类型断言即可获取结构化错误信息
自动容错 两级重试互补:HTTP 层处理瞬时网络抖动和 429/5xx,Runtime 层处理更上层的可恢复失败(如 provider 连接池耗尽后重建),大幅降低用户感知到的错误率
优雅降级 SSE 流式传输中途断连时,consumeStream 返回已接收的部分内容,而不是丢弃已有进度报错,用户体验从"请求失败"降级为"结果不完整"
可扩展的 Transport WithTransport functional option 支持注入任意 http.RoundTripper,便于未来接入日志、metrics、mock 等中间件
可观测性 EventProviderRetry 事件让 TUI 能展示重试进度,用户可以感知到系统在自动恢复

Fixes #100

@codecov

codecov Bot commented Apr 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.60104% with 22 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/provider/openai/openai.go 44.44% 9 Missing and 1 partial ⚠️
internal/provider/transport/retry.go 92.77% 3 Missing and 3 partials ⚠️
internal/runtime/runtime.go 88.00% 4 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@minorcell minorcell merged commit 94cbf56 into 1024XEngineer:main Apr 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pref(provider):标准化错误码,并添加重试机制

2 participants