新增: F-04多Provider负载均衡池(数据层+选择器+并发原语)+CR-52白项

This commit is contained in:
2026-06-17 02:04:58 +08:00
parent 31ea151bb2
commit 79b6a43095
12 changed files with 463 additions and 16 deletions

View File

@@ -130,6 +130,32 @@ pub(crate) async fn run_agentic_loop(
// B-260615-09: generating 状态由 RAII guard 收敛复位(正常 exit 显式 reset;panic/异常 Drop 兜底)
let mut guard = GeneratingGuard::new(session_arc.clone());
// F-260614-04: 多 Provider 负载均衡池 — 选主候选 provider。
//
// 流程:list_all → ProviderPool::select(按 模型亲和 > weight > is_default 排序)→ 取首位。
// 单 provider 场景:池仅 1 enabled provider → select 返回单元素 Vec → 首位 = 唯一 provider,
// 行为同 F-01 前(零变化)。空池(0 enabled)→ fallback 入参 provider_config(保启动行为)。
//
// 主候选的 model_configs 用于路由(F-01),其 provider_config 用于 build_provider。
// 当前批次仅选主,fallback(主失败切备用 provider)见后续批次(需重构流式重试块,单独评估)。
//
// AiProviderRepo::new 仅 clone Arc<Database>(廉价),不复用 AppState.ai_providers
// (run_agentic_loop 签名只传 Arc<Database>,改签名会牵动 3 调用点 + try_continue)。
let provider_repo = df_storage::crud::AiProviderRepo::new(&db);
let pool_providers: Vec<AiProviderRecord> = provider_repo.list_all().await.unwrap_or_default();
let primary_provider: AiProviderRecord = match super::provider_pool::ProviderPool::select(
&pool_providers,
None, // 模型亲和此时尚未确定(router 选模型需 provider_config,见下方)
)
.into_iter()
.next()
{
Some(p) => p,
None => provider_config.clone(), // 空池兜底:用调用方传入的 provider_config(启动行为不变)
};
// 用主候选覆盖入参 provider_config(下游 build_provider / 路由 / 日志均用此)。
let provider_config = primary_provider;
// FR-S1: resolve→ensure_resolved_key(空 key 早失败)→build_provider 三步统一走工厂
// 空 key 早失败(逻辑见 secret::ensure_resolved_key 单测):避免空 key 发请求吃 401,错误伪装成"API Key 无效"
//
@@ -400,8 +426,13 @@ pub(crate) async fn run_agentic_loop(
// Partial)不重试保文。重试退避复用 retry::backoff_delay(1s→2s→4s±20% jitter) +
// retry::is_status_retryable Fatal 分类(stream_recv classify_status_or_class 镜像,
// 4xx 非429 立即放弃) + 30s 总挂钟预算。重试期间持有 permit 不释放(防新请求挤占)。
//
// F-260614-04: per-provider permit(可选)。set_provider_caps 未配置时返回 None
// (单 provider 场景零变化);配置后取额外 permit 防单 provider 被打满(限流 429)。
// 三 permit 均 Drop 释放(L595-597 显式 drop _global/_per_conv;_provider_permit 绑块尾 Drop)。
let _global_permit = llm_concurrency.acquire_global().await;
let _per_conv_permit = llm_concurrency.acquire_per_conv().await;
let _provider_permit = llm_concurrency.acquire_for_provider(&provider_config.id).await;
// 重试总预算(挂钟,含 sleep + 各次请求耗时),对齐 retry::MAX_TOTAL_BUDGET 30s。
// 超预算直接放弃重试交最终错误/保文路径。
@@ -569,6 +600,8 @@ pub(crate) async fn run_agentic_loop(
// stream 结束立即释放 permit,后续工具执行不受限流(本地操作无 RPM 成本)
drop(_global_permit);
drop(_per_conv_permit);
// F-260614-04: per-provider permit 为 None 时 drop None 无副作用;Some 时释放槽。
drop(_provider_permit);
// 累加本轮 token:provider 流式 usage 的 prompt_tokens 为 0 时(GLM 等),用预估输入兜底
let round_prompt = if round_usage.prompt_tokens == 0 { estimated_prompt } else { round_usage.prompt_tokens };