修复: 连接池收敛与传输错误分类重试

- 连接池空闲十五秒,陈旧连接复用致重置根治
- 流式发送错误改谓词分类,openai 路径不再裸奔
- decode 类失败纳入重试,正文中断可自愈
- 状态分类合并响应体,激活休眠词表分支
- 手动压缩失败降级兜底,不再弹红卡
This commit is contained in:
lxy
2026-10-01 19:35:22 +08:00
parent 4417b3ddd3
commit 7dcbdb1feb
5 changed files with 343 additions and 21 deletions
+57
View File
@@ -64,6 +64,11 @@ pub use df_ai_core;
pub(crate) fn build_provider_client() -> Client {
Client::builder()
.connect_timeout(std::time::Duration::from_secs(30))
// 连接池收敛:idle 连接 15s 弃用 + 每主机最多存 2 条。reqwest 默认 idle 90s 且
// 无上限,服务端/前置 keepalive 更短时复用陈旧连接首包即被 RST(ConnectionReset
// 10054),呈现「隔几十秒一发就失败」的间歇形态;15s 保守低于常见网关 60s keepalive。
.pool_idle_timeout(std::time::Duration::from_secs(15))
.pool_max_idle_per_host(2)
.build()
.unwrap_or_else(|e| {
tracing::warn!("reqwest builder 失败,回退默认 client: {}", e);
@@ -71,6 +76,58 @@ pub(crate) fn build_provider_client() -> Client {
})
}
/// 流式 send 阶段 reqwest 错误的谓词快照。
///
/// reqwest::Error 无公开构造器,抽谓词结构体过纯函数,单测才能锁定分类
/// (send 阶段 ConnectionReset → 可重试类词)。
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct SendErrPreds {
pub is_timeout: bool,
pub is_connect: bool,
pub is_decode: bool,
pub is_body: bool,
pub is_request: bool,
pub is_builder: bool,
}
impl SendErrPreds {
pub(crate) fn of(e: &reqwest::Error) -> Self {
Self {
is_timeout: e.is_timeout(),
is_connect: e.is_connect(),
is_decode: e.is_decode(),
is_body: e.is_body(),
is_request: e.is_request(),
is_builder: e.is_builder(),
}
}
}
/// 流式 send 阶段错误分类,返回类词(置于 bail 串首位供下游按串分类)。
///
/// reqwest 0.12 的 Display 只给 "error sending request for url",不含 source 关键词,
/// 纯 Display 串分类必得 unknown → Fatal 零重试。此处用谓词确定性给类词:
/// timeout/connect/decode/body 各归本类;is_request(非 builder)覆盖陈旧池化连接
/// 首包 RST(ConnectionReset 10054)等传输层发送错,归 connect(与 retry 的
/// is_reqwest_error_retryable 对 is_request 可重试口径一致);builder 构造错归
/// request(不可重试)。类词须落在 stream_recv classify_status_or_class 传输词表内,
/// 改任一侧需同步。
pub(crate) fn send_error_class(p: SendErrPreds) -> &'static str {
if p.is_timeout {
"timeout"
} else if p.is_connect {
"connect"
} else if p.is_decode {
"decode"
} else if p.is_body {
"body"
} else if p.is_request && !p.is_builder {
"connect"
} else {
"request"
}
}
/// 按 provider_type 构建 LLM Provider 实例(统一选择逻辑,消除调用方重复 match)
///
/// `anthropic` 协议走 AnthropicCompatProvider(GLM 订阅端点 / Claude 官方),