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

- 连接池空闲十五秒,陈旧连接复用致重置根治
- 流式发送错误改谓词分类,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
+18 -3
View File
@@ -600,7 +600,9 @@ impl LlmProvider for AnthropicCompatProvider {
source = ?std::error::Error::source(&e),
"Anthropic 同步 send 失败"
);
if is_reqwest_error_retryable(&e) {
// is_decode 补可重试:body 中途断连在 reqwest 归 decode 类,complete 重发
// 幂等安全(受 3 次/30s 预算约束,真正格式错重试同败,预算内止损)。
if is_reqwest_error_retryable(&e) || e.is_decode() {
return AttemptOutcome::Retryable(format!("请求失败(可重试): {}", e));
}
return AttemptOutcome::Fatal(format!(
@@ -626,7 +628,13 @@ impl LlmProvider for AnthropicCompatProvider {
}
let resp: AnthropicResponse = match resp.json().await {
Ok(r) => r,
Err(e) => return AttemptOutcome::Fatal(format!("响应解析失败: {}", e)),
// serde 结构错属 Fatal(重试同败);body 读取中途断连同归 is_decode,可重试。
Err(e) => {
if e.is_decode() {
return AttemptOutcome::Retryable(format!("响应解码失败(可重试): {}", e));
}
return AttemptOutcome::Fatal(format!("响应解析失败: {}", e));
}
};
// content 块中拼接 text,收集 tool_use
let mut text = String::new();
@@ -714,17 +722,24 @@ impl LlmProvider for AnthropicCompatProvider {
let resp = match tokio::time::timeout(Duration::from_secs(60), send_future).await {
Ok(Ok(r)) => r,
Ok(Err(e)) => {
// 谓词结构化分类(对齐 openai 路径,共用 crate::send_error_class)。原 bail 串
// [timeout={} connect={}=...] 的字面词让一切错误(含 builder 错)侥幸可重试。
// 类词改由谓词决定置串首;谓词转储缩写为 t/c/b/r 防误触分类词表,source 链
// 保留真实传输词(reset/broken pipe 等)作串兜底。
let class = crate::send_error_class(crate::SendErrPreds::of(&e));
tracing::error!(
is_timeout = e.is_timeout(),
is_connect = e.is_connect(),
is_body = e.is_body(),
is_request = e.is_request(),
class = class,
url = %self.messages_url(),
source = ?std::error::Error::source(&e),
"Anthropic 流式 send 失败"
);
anyhow::bail!(
"send 失败: {} [timeout={} connect={} body={} request={} src={:?}]",
"send 失败({}): {} [t={} c={} b={} r={} src={:?}]",
class,
e,
e.is_timeout(),
e.is_connect(),