本 issue 落地一个实现类改动:统一 OpenAI-compatible、Anthropic、Gemini 三家 provider 的生成重试与超时模型,形成一套稳定、可配置、可测试的 provider 级生成尝试语义,先补齐 NeoCode 当前生成链路中“首包等待不稳、长流容易被误杀、三家 provider 行为不一致”这三个质量缺口。
保留一个明确原则:
- 本次目标不是“让 agent 更智能”,而是让生成链路的重试与超时行为稳定、一致、可验证。
- 本次不把 provider retry 逻辑重新抬回
runtime。
- 本次不引入 background job、断线恢复、任务持久化等长任务能力。
- 本次不顺手扩张到
gateway / runtime / tui 协议。
- 本次只收敛 provider 生成尝试语义,不重构第三方 chat 端口适配层。
- 本次新增配置字段必须采用“可选字段 + 默认值回填”策略;旧配置缺少新字段时必须仍可直接启动。
目标问题
当前生成链路的重试与超时语义是分裂的:
OpenAI-compatible / Anthropic
-> 主要依赖 SDK 自带 retry
-> request timeout / http.Client.Timeout 承担过多职责
Gemini
-> provider 内部自管 retry
-> 当前只支持“未开始流输出前重试”
-> retry 次数与超时语义单独定义
这导致几个直接问题:
- 首包等待和长流读取被同一个 timeout 粗暴绑定。
- timeout 太短会误杀正常长流,太长又会拖慢首包失败后的重试。
- 三家 provider 对相似错误的处理不一致。
- 当前没有统一定义“什么时候算流已经开始”“什么时候还能重试”。
- timeout / retry 策略散落在不同 provider 中,后续很难统一治理和测试。
相关实现主要在:
internal/provider/openaicompat/*
internal/provider/anthropic/*
internal/provider/gemini/*
internal/config/provider.go
为什么现在做
这不是参数微调,而是一个明确的链路质量问题。
从链路质量看,当前缺口体现在:
- 首包前坏请求不能稳定快速失败。
- 首包后正常长流缺少稳定保护。
- provider 之间 retry 责任不一致,系统行为漂移。
- 用户很难建立“为什么重试 / 为什么没重试 / 为什么被断流”的稳定预期。
从用户感知看,这个改动应直接带来:
- 首包迟迟不来的请求更快失败并进入重试。
- 已开始稳定输出的长流不会因为总时长过长被误杀。
- 相似错误在不同 provider 上更接近相同行为。
- timeout / retry 策略可通过 provider 配置统一调整。
实现设计
1. 统一生成尝试模型
本次统一引入两阶段生成语义:
首包前
-> start timeout
-> 超时则取消本次尝试并重试
首包后
-> 不设固定总时长上限
-> 只要持续有 payload 就允许继续输出
-> 若连续 idle timeout 没有任何新 payload,则判定流卡死并中断
统一规则直接定死:
start timeout 表示“从发请求到收到首个有效流 payload 的最长等待窗口”。
idle timeout 表示“首包后,连续没有任何新 payload 的最长空闲窗口”。
max retries 表示额外重试次数,不含首次尝试。
- 只有在“尚未收到首个有效流 payload”时才允许 retry。
- 一旦收到首个有效流 payload,后续任何流中断或 provider 错误都直接返回,不再 retry。
- backoff 采用指数退避加抖动。
“首个有效流 payload” 统一定义为首次发出的:
- text delta
- tool call start
- tool call delta
不允许各 provider 自己定义“什么算流已开始”。
2. provider 级配置参数
本次新增 provider 级 YAML 字段,不放入 runtime:
generate_max_retries: 5
generate_start_timeout_sec: 60
generate_idle_timeout_sec: 300
对应配置与运行时结构:
type ProviderConfig struct {
...
GenerateMaxRetries int `yaml:"generate_max_retries,omitempty"`
GenerateStartTimeoutSec int `yaml:"generate_start_timeout_sec,omitempty"`
GenerateIdleTimeoutSec int `yaml:"generate_idle_timeout_sec,omitempty"`
}
type provider.RuntimeConfig struct {
...
GenerateMaxRetries int
GenerateStartTimeout time.Duration
GenerateIdleTimeout time.Duration
}
默认值与归一化规则直接定死:
generate_max_retries <= 0 回填为 5
generate_start_timeout_sec <= 0 回填为 60
generate_idle_timeout_sec <= 0 回填为 300
本期不支持通过 0 显式关闭重试或 timeout;0 与未配置都表示“使用默认值”。
3. provider 层统一 attempt runner
三家 provider 不允许各自实现一套 retry / watchdog 主流程;必须共用一套 provider-level attempt runner。
统一职责边界:
provider common
-> attempt lifecycle
-> start watchdog
-> idle watchdog
-> retry loop
-> retryable decision
provider specific
-> 构造请求
-> 消费流
-> 映射错误
统一执行流程:
- 创建一次生成尝试
- 启动
start watchdog
- 若在
GenerateStartTimeout 内未收到首个有效 payload,则取消本次尝试并进入 retry
- 一旦收到首个有效 payload,关闭
start watchdog
- 启动
idle watchdog
- 每收到一个新的有效 payload,就刷新
idle watchdog
- 若
idle watchdog 超时,则终止本次尝试
- 仅当“未收到首包 + 错误可重试”时进入下一次 retry
“流已开始”的判定必须统一收敛在 NeoCode 自己的有效事件发射层,而不是直接基于 SDK 原始事件判定。
4. 三家 provider 的统一策略
OpenAI-compatible
- 显式关闭 SDK 内建 retry,避免双层重试。
- 生成链路由 NeoCode provider 外层统一管理 retry。
- 不再依赖 SDK request timeout 作为生成主控制器。
http.Client.Timeout 如保留,只能作为宽松保底值,不得抢占主控制权。
Anthropic
- 与 OpenAI-compatible 一致,显式关闭 SDK 内建 retry。
- 生成链路由 NeoCode provider 外层统一管理 retry。
- 不再依赖 SDK request timeout 作为生成主控制器。
Gemini
- 保留当前 provider 内部流消费与错误映射结构,但接入统一 attempt runner。
- 删除“固定 2 次重试”的本地语义。
- retry 次数、首包窗口、空闲窗口全部读取统一运行时配置。
5. 第三方 chat 端口适配边界
本次不修改 OpenAI-compatible 现有第三方 chat 端口适配语义。
保持不变的内容:
chat_api_mode
chat_endpoint_path
/chat/completions 与 /responses 路径推断
- weak SSE / compatible stream parser
- typed stream 失败后的 compatible fallback 语义
本次只允许在“完整的一次生成尝试”外层包统一的 start/idle/retry 控制,不允许重构或改写现有端口适配层。
风险与约束
本次最关键的风险点与约束如下:
- 不允许 SDK retry 与 NeoCode retry 双层叠加。
- 不允许三家 provider 各自实现一套 retry/watchdog 主流程。
- 不允许将 SDK 元事件、空 chunk、keepalive 误判为“流已开始”。
- 不允许 fallback 链路被重复包裹统一状态机。
- 不允许把 migration 当作旧配置可启动的唯一条件。
- 不允许让 builtin provider、custom provider、测试默认值出现默认值分叉。
- 首包后失败不 retry 是有意取舍,用于避免重复文本输出、重复 tool call 和重复副作用。
- 第三方 chat 端口适配层保持现有行为,不在本 issue 内重构。
任务清单
测试验证
配置测试
Provider 行为测试
第三方 chat 端口适配回归
总体验证
验收标准
风险与回滚
- 若统一 attempt runner 没有形成清晰真相来源,整体回滚本次 provider 重试统一实现。
- 不保留“OpenAI-compatible / Anthropic 继续主要靠 SDK retry、Gemini 主要靠本地 retry”的长期双轨。
- 若第三方 chat 端口适配回归不稳,优先回滚 OpenAI-compatible 外层统一控制改动,保留现有端口适配语义。
- 若 migration 首版实现不稳,可整体回滚 migration 子集,但不影响“旧配置可直接启动”这一主目标。
目标问题
当前生成链路的重试与超时语义是分裂的:
这导致几个直接问题:
相关实现主要在:
internal/provider/openaicompat/*internal/provider/anthropic/*internal/provider/gemini/*internal/config/provider.go为什么现在做
这不是参数微调,而是一个明确的链路质量问题。
从链路质量看,当前缺口体现在:
从用户感知看,这个改动应直接带来:
实现设计
1. 统一生成尝试模型
本次统一引入两阶段生成语义:
统一规则直接定死:
start timeout表示“从发请求到收到首个有效流 payload 的最长等待窗口”。idle timeout表示“首包后,连续没有任何新 payload 的最长空闲窗口”。max retries表示额外重试次数,不含首次尝试。“首个有效流 payload” 统一定义为首次发出的:
不允许各 provider 自己定义“什么算流已开始”。
2. provider 级配置参数
本次新增 provider 级 YAML 字段,不放入
runtime:对应配置与运行时结构:
默认值与归一化规则直接定死:
generate_max_retries <= 0回填为5generate_start_timeout_sec <= 0回填为60generate_idle_timeout_sec <= 0回填为300本期不支持通过
0显式关闭重试或 timeout;0与未配置都表示“使用默认值”。3. provider 层统一 attempt runner
三家 provider 不允许各自实现一套 retry / watchdog 主流程;必须共用一套 provider-level attempt runner。
统一职责边界:
统一执行流程:
start watchdogGenerateStartTimeout内未收到首个有效 payload,则取消本次尝试并进入 retrystart watchdogidle watchdogidle watchdogidle watchdog超时,则终止本次尝试“流已开始”的判定必须统一收敛在 NeoCode 自己的有效事件发射层,而不是直接基于 SDK 原始事件判定。
4. 三家 provider 的统一策略
OpenAI-compatible
http.Client.Timeout如保留,只能作为宽松保底值,不得抢占主控制权。Anthropic
Gemini
5. 第三方 chat 端口适配边界
本次不修改 OpenAI-compatible 现有第三方 chat 端口适配语义。
保持不变的内容:
chat_api_modechat_endpoint_path/chat/completions与/responses路径推断本次只允许在“完整的一次生成尝试”外层包统一的 start/idle/retry 控制,不允许重构或改写现有端口适配层。
风险与约束
本次最关键的风险点与约束如下:
任务清单
internal/config/provider.go增加generate_max_retries、generate_start_timeout_sec、generate_idle_timeout_sec三个字段。provider.RuntimeConfig中增加对应运行时字段。2次重试语义并接入统一 runner。chat_api_mode、chat_endpoint_path、weak SSE fallback、compatible parser 现有行为不变。测试验证
配置测试
5 / 60 / 300。provider.RuntimeConfig。<=0输入会被归一化到默认值。config.yaml缺少新字段时可直接启动。providers/*/provider.yaml缺少新字段时可直接启动。Provider 行为测试
6。generate_idle_timeout_sec没有任何新 payload,则会被终止。2次重试行为。第三方 chat 端口适配回归
chat_endpoint_path指向非标准/gateway/chat/completions时行为不退化。chat_api_mode=responses+ 自定义路径时行为不退化。generate_start_timeout_sec解决。总体验证
go test ./internal/provider/... ./internal/config/...通过。go test ./...通过。验收标准
风险与回滚