From 7c708a6e01165ec527a053515761452eae1b46a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Fri, 19 Jun 2026 02:39:39 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96:=20F-09=20batch5=E8=B0=83?= =?UTF-8?q?=E6=95=B4=20=E5=88=A0global=E4=BC=9A=E8=AF=9D=E7=BA=A7=E9=99=90?= =?UTF-8?q?=E6=B5=81(=E7=94=A8=E6=88=B7=E5=86=B3=E7=AD=96=E4=B8=8D?= =?UTF-8?q?=E8=AE=BE=E5=B9=B6=E5=8F=91=E4=BC=9A=E8=AF=9D=E4=B8=8A=E9=99=90?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - agentic.rs: 删loop入口acquire_global(会话数不限) + per_conv保留(单对话内限流permits=2) - state.rs: global回归LLM调用并发限流原义(5处单次调用点+stream_llm防provider429),非会话数上限 用户决策"不设并发会话上限",token暴增接受;主代兜底cargo 0+test98+grep印证 --- src-tauri/src/commands/ai/agentic.rs | 32 ++++++++++++++-------------- src-tauri/src/state.rs | 22 +++++++++++-------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src-tauri/src/commands/ai/agentic.rs b/src-tauri/src/commands/ai/agentic.rs index de2a039..6ab36dd 100644 --- a/src-tauri/src/commands/ai/agentic.rs +++ b/src-tauri/src/commands/ai/agentic.rs @@ -509,15 +509,15 @@ pub(crate) async fn run_agentic_loop( // 其 token 估算在 loop 外算一次缓存复用,避免每轮/每次重试重复 estimate_text(低收益优化,行为不变)。 let sys_tokens = TokenEstimator::default().estimate_text(&system_prompt); - // F-260616-09 B 批5 / 决策 c-1:global 改「并发会话数上限」,每 loop 入口拿 1 permit 持整个 loop - // 生命周期(含工具执行/审批等待/重试)。同一时刻最多 N 个对话并发跑 loop(N=global permits,默认 3), - // 第 N+1 个对话的 acquire_global await 阻塞排队。permit 绑 guard(函数返回)Drop 自动释放—— - // 各 return 点(save_conversation/stop/conv 删除/收敛/达 MAX)退出即释放槽位。 - // per_conv 同样整 loop 持有:同一对话内主循环 stream_llm + 后台标题/压缩/提炼共享该 conv 的 permits=2, - // 防单对话内并发 LLM 调用失控。**F-260616-12 核验**:retry 同 loop 内,持 per_conv 合理; - // global 是会话级,retry 不再阻塞他对话(原每轮 acquire/drop 语义下 global 短暂释放, - // 现整 loop 持有更贴合"会话级并发上限"语义)。 - let _conv_global_permit = llm_concurrency.acquire_global().await; + // F-09 batch5 修正(用户决策「不设并发会话上限」):删除 loop 入口 acquire_global。 + // 原决策 c-1 把 global 改「会话数上限」(loop 入口持整 loop,N=3 排队第 4 个), + // 现用户取消会话数上限 → 多对话 loop 并发不限,第 N+1 个不再 await 阻塞。 + // global 字段回归原义「LLM 调用并发限流」:仅由 stream_llm 每轮 + 标题/压缩/提炼/项目分析 + // 等单次 LLM 调用点 acquire/drop(防 provider 429),不再绑 loop 生命周期。 + // per_conv(每对话 permits=2)保留 — 单对话内主循环 stream_llm + 后台标题/压缩/提炼共享该 conv 的 + // permits=2,防单对话内并发 LLM 调用失控(单对话内限流,非会话数限制,符合「不设上限」)。 + // permit 绑 guard(函数返回)Drop 自动释放——各 return 点退出即释放槽位。 + // retry 同 loop 内,持 per_conv 合理(F-260616-12 核验)。 let _conv_per_conv_permit = llm_concurrency.acquire_per_conv(&conv_id).await; for iteration in start_iteration..max_iterations { @@ -732,19 +732,19 @@ pub(crate) async fn run_agentic_loop( messages.iter().map(|m| est.estimate_message(m)).sum() }; - // LLM 并发限流(global + per_conv 双层)— F-260616-09 B 批5 已上移到 loop 入口 - // (L520-521 _conv_global_permit/_conv_per_conv_permit),整 loop 持有(含工具执行/审批等待/重试)。 - // 原每轮 acquire + stream 后 drop(L747-748 acquire / L914-915 drop)已移除——会话级并发语义下, - // permit 应绑 loop 生命周期而非单次 stream。工具执行期间占槽是决策 c 的有意行为 - // (会话级并发上限含全部 LLM 相关工作,非仅 stream 调用)。 + // LLM 并发限流 — F-09 batch5 修正后: + // per_conv 由 loop 入口(L521 _conv_per_conv_permit)整 loop 持有(含工具执行/审批等待/重试), + // 防单对话内并发 LLM 调用失控(单对话内 permits=2,非会话数限制)。 + // global 已不再由 loop 入口持有(用户决策「不设并发会话上限」),回归原义「LLM 调用并发限流」: + // 由各单次 LLM 调用点(stream_llm 重试循环内/标题/压缩/提炼/项目分析)各自 acquire/drop 防 429。 // // CR-30-1 / F-260616-07 / 决策 a1: 流前失败(Init Err)重试,流中途失败(MidStream // Partial)不重试保文。重试退避复用 retry::backoff_delay(1s→2s→4s±20% jitter) + // retry::is_status_retryable Fatal 分类(stream_recv classify_status_or_class 镜像, - // 4xx 非429 立即放弃) + 30s 总挂钟预算。重试期间持有 permit 不释放(防新请求挤占)。 + // 4xx 非429 立即放弃) + 30s 总挂钟预算。重试期间持有 per_conv permit 不释放(防新请求挤占)。 // // F-260614-04 / F-260614-04b: per-provider permit 仍在 candidate 循环内取 - // (切换 candidate 时释放旧取新,避免占用未用 provider 的槽);global/per_conv 由 loop 入口持有。 + // (切换 candidate 时释放旧取新,避免占用未用 provider 的槽);per_conv 由 loop 入口持有。 // 重试总预算(挂钟,含 sleep + 各次请求耗时),对齐 retry::MAX_TOTAL_BUDGET 30s。 // F-260614-04b:本轮各 candidate 共享一个 30s 预算(切换 provider 不重置预算, diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index b0bb03f..0d8219d 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -81,22 +81,24 @@ impl Default for KnowledgeConfig { // LLM 调用并发控制(双层 Semaphore + 可选 per-provider 层) // ============================================================ -/// LLM 调用并发控制 — 会话级 global + 单对话内 per_conv 双层 Semaphore + 可选 per-provider 层 +/// LLM 调用并发控制 — 全局 LLM 调用限流 global + 单对话内 per_conv 双层 Semaphore + 可选 per-provider 层 /// /// 限流对象:所有真实 LLM 调用(主循环 stream_llm / 标题生成 / 知识提炼)。 /// 不限流本地工具执行(tools.execute)——本地操作无外部成本、不受 RPM 约束。 /// -/// ## F-260616-09 B 批5 / 决策 c-1:global 改「并发会话数上限」 -/// `global` permits 默认 3(`new(3, 2)`),语义从「全局 LLM 调用并发上限」改为 -/// 「并发会话数上限」:`run_agentic_loop` 入口 `acquire_global()` 拿 1 permit,持有整个 loop -/// 生命周期(含工具执行/审批等待/重试)。同一时刻最多 3 个对话并发跑 loop,第 4 个排队。 -/// 收益:token 暴增护栏(决策 c 原意)。 +/// ## global(permits 默认 3,「LLM 调用并发限流」原义) +/// F-09 batch5 曾把 global 改「并发会话数上限」(loop 入口持整 loop),用户决策修正「不设并发会话上限」后 +/// 已删除 loop 入口 acquire。global 回归原义:由各单次 LLM 调用点(stream_llm 重试循环 / 标题 / 压缩 / +/// 提炼 / 项目分析扫描)各自 `acquire_global()` 拿 permit、调用结束 Drop 释放,防 provider 429。 +/// 多对话 loop 并发不限数(用户接受 token 暴增)。 /// /// ## per_conv 改 HashMap /// 原应用级单信号量(AiSession 单例 + generating 互斥下退化)改为 /// `HashMap>`:每对话一份(permits=2,主循环 + 标题 + 提炼各自限流)。 +/// `run_agentic_loop` 入口 `acquire_per_conv(conv_id)` 拿 1 permit 持整个 loop 生命周期(含工具执行/审批 +/// 等待/重试),防单对话内并发 LLM 调用失控。这是单对话内限流,非会话数限制,符合用户「不设上限」。 /// `acquire_per_conv(conv_id)`:lock HashMap → 无则建(permits=2)→ clone Arc → 释放 lock → acquire_owned。 -/// conv 退出清理(`release_conv(conv_id)`):loop 结束 + 无 pending 审批时 remove 条目(防 HashMap 无限增长)。 +/// conv 退出清理(`release_conv(conv_id)`):conv 删除时 remove 条目(防 HashMap 无限增长)。 /// /// 运行时调整:tokio Semaphore 的 permits 数构造时固定、不可增减, /// 故 `global` 用 `Arc>>` 双层包装——替换内层 Arc 即重建 Semaphore。 @@ -113,7 +115,8 @@ impl Default for KnowledgeConfig { /// (set_provider_caps 传 min(sum, global_cap)),非运行时强约束。 #[derive(Clone)] pub struct LlmConcurrency { - /// 会话级并发上限(F--09 B 批5/决策 c-1):permits=默认 3,run_agentic_loop 入口拿 1 持整 loop。 + /// 全局 LLM 调用并发上限(permits 默认 3):F-09 batch5 修正后回归原义,由各单次 LLM 调用点 + /// (stream_llm/标题/压缩/提炼/项目分析)各自 acquire/drop 防 429,不再由 loop 入口持整 loop。 global: Arc>>, /// 单对话内并发上限(F-09 B 批5):HashMap,每对话 permits=2。 /// acquire 时按 conv_id 取/建;release_conv 在 loop 结束 + 无 pending 时 remove。 @@ -138,7 +141,8 @@ impl LlmConcurrency { } } - /// 取会话级并发 permit(F-09 B 批5/决策 c-1):run_agentic_loop 入口拿 1 持整个 loop 生命周期。 + /// 取全局 LLM 调用并发 permit:由各单次 LLM 调用点(stream_llm/标题/压缩/提炼/项目分析) + /// 各自调用、调用结束 Drop 释放。F-09 batch5 修正后不再由 run_agentic_loop 入口持整 loop。 /// 重建后(set_global)新请求自动走最新 Semaphore。 pub async fn acquire_global(&self) -> tokio::sync::OwnedSemaphorePermit { let sema = self.global.lock().await.clone();