新增: F-04c后端(provider池enabled/weight更新IPC+set_caps启动接入)
This commit is contained in:
@@ -926,23 +926,26 @@ pub async fn ai_save_provider(
|
||||
provider_type: String,
|
||||
) -> Result<String, String> {
|
||||
// 编辑已有提供商时保留原 created_at,避免被覆盖
|
||||
let created_at = match &id {
|
||||
Some(pid) => state.ai_providers.get_by_id(pid).await
|
||||
.map_err(err_str)?
|
||||
.map(|p| p.created_at)
|
||||
.unwrap_or_else(now_millis),
|
||||
None => now_millis(),
|
||||
// F-260614-04c: 编辑路径同时保留原 enabled/weight(负载均衡池可编辑层)。
|
||||
// 前端经此 IPC 改 enabled/weight 落库;新建走默认 enabled=true/weight=50。
|
||||
let existing = match &id {
|
||||
Some(pid) => state.ai_providers.get_by_id(pid).await.map_err(err_str)?,
|
||||
None => None,
|
||||
};
|
||||
let created_at = existing.as_ref().map(|p| p.created_at.clone()).unwrap_or_else(now_millis);
|
||||
// is_default:编辑保留原值;新建时若全表尚无默认则设为默认(首个自动默认,避免无默认可用)
|
||||
let is_default = match &id {
|
||||
Some(pid) => state.ai_providers.get_by_id(pid).await
|
||||
.map_err(err_str)?
|
||||
.map(|p| p.is_default)
|
||||
.unwrap_or(false),
|
||||
let is_default = match &existing {
|
||||
Some(p) => p.is_default,
|
||||
None => !state.ai_providers.list_all().await
|
||||
.map_err(err_str)?
|
||||
.iter().any(|p| p.is_default),
|
||||
};
|
||||
// F-260614-04c: enabled/weight 编辑保留原值(前端 Settings 改值经此落库);
|
||||
// 新建默认进池(enabled=true,weight=50)。
|
||||
let (enabled, weight) = match &existing {
|
||||
Some(p) => (p.enabled, p.weight),
|
||||
None => (true, 50),
|
||||
};
|
||||
// FR-S1:密钥存 OS keyring,DB api_key 列恒空(不入明文)。
|
||||
// api_key 非空 = 新/改密钥 → 写 keyring;空 = 编辑不改 → 保留原 keyring 密钥不动。
|
||||
let provider_id = id.clone().unwrap_or_else(new_id);
|
||||
@@ -993,10 +996,9 @@ pub async fn ai_save_provider(
|
||||
config: None,
|
||||
created_at,
|
||||
updated_at: now_millis(),
|
||||
// F-260614-04: 新建 provider 默认进入负载均衡池(enabled=true,weight=50)。
|
||||
// 单 provider 路径零变化(enabled=true 即与迁移前行为等价)。
|
||||
enabled: true,
|
||||
weight: 50,
|
||||
// F-260614-04c: enabled/weight 编辑保留原值(负载均衡池可编辑层),新建走默认。
|
||||
enabled,
|
||||
weight,
|
||||
};
|
||||
let id = record.id.clone();
|
||||
state
|
||||
@@ -1007,7 +1009,52 @@ pub async fn ai_save_provider(
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
/// 设置活跃提供商(互斥落库:目标置默认、其余清默认,重启不丢)
|
||||
/// F-260614-04c: 轻量更新 provider 池配置(enabled/weight),不改其它字段、不触密钥迁移。
|
||||
///
|
||||
/// 与 `ai_save_provider` 的区别:
|
||||
/// - ai_save_provider 是全量保存(name/base_url/api_key/model...),编辑路径会走 R-PD-1 密钥
|
||||
/// 迁移 + INSERT OR REPLACE 全字段;前端 Settings「负载均衡池」开关/权重滑块仅需改这俩字段,
|
||||
/// 不应重发整张表(尤其避免空 api_key 触发密钥迁移分支)。
|
||||
/// - 本 IPC 仅 UPDATE enabled/weight(经 update_field 或 update_full),重建 caps 表。
|
||||
///
|
||||
/// 落库后立即重建 per_provider caps(set_provider_caps),保证开关/权重变更对 agentic loop
|
||||
/// 即时生效(下条消息即按新配置 acquire)。caps 重建非强一致(软收敛:已持 permit 不回收)。
|
||||
#[tauri::command]
|
||||
pub async fn ai_update_provider_pool(
|
||||
state: State<'_, AppState>,
|
||||
provider_id: String,
|
||||
enabled: bool,
|
||||
weight: u32,
|
||||
) -> Result<(), String> {
|
||||
// 验证 provider 存在(防前端传错 id 静默无操作)
|
||||
let mut record = state
|
||||
.ai_providers
|
||||
.get_by_id(&provider_id)
|
||||
.await
|
||||
.map_err(err_str)?
|
||||
.ok_or_else(|| format!("提供商不存在: {}", provider_id))?;
|
||||
// weight 落库前 clamp 到 [0,100](对齐 crud update 的 weight.min(100),防越界)。
|
||||
let weight = weight.min(100);
|
||||
if record.enabled == enabled && record.weight == weight {
|
||||
// 无变化:跳过 DB 写 + caps 重建(幂等,防前端重复点击触发不必要的 IO)。
|
||||
return Ok(());
|
||||
}
|
||||
record.enabled = enabled;
|
||||
record.weight = weight;
|
||||
record.updated_at = now_millis();
|
||||
// update_full 走 UPDATE 全字段(含 enabled/weight,波12已加);api_key 不变(DB 恒空)。
|
||||
state
|
||||
.ai_providers
|
||||
.update_full(&record)
|
||||
.await
|
||||
.map_err(err_str)?;
|
||||
// 重建 per_provider caps:本 provider 被禁用/weight=0 → 不入新表 → acquire_for_provider
|
||||
// 对其返 None(无限流,但 provider_pool::select 已把它移出候选,实际不会被 acquire)。
|
||||
// caps 重建逻辑收敛到 AppState::reload_provider_caps(单点真理,启动 + 变更共用)。
|
||||
state.reload_provider_caps().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn ai_set_provider(
|
||||
state: State<'_, AppState>,
|
||||
|
||||
@@ -125,6 +125,8 @@ pub fn run() {
|
||||
commands::ai::ai_save_provider,
|
||||
commands::ai::ai_set_provider,
|
||||
commands::ai::ai_delete_provider,
|
||||
// F-260614-04c: 负载均衡池可编辑层(enabled/weight 轻量更新 + caps 重建)
|
||||
commands::ai::ai_update_provider_pool,
|
||||
// F-01 阶段5:测试连接拉取模型列表 + 单模型探测
|
||||
commands::ai::ai_fetch_models,
|
||||
commands::ai::ai_probe_model,
|
||||
|
||||
@@ -294,8 +294,38 @@ impl AppState {
|
||||
// 启动恢复:重启前卡 pending 的工具审批(内存 pending_approvals 已丢)从审计表重建,
|
||||
// 使重启后待审批不丢。前端经 ai_pending_tool_calls + switchConversation 恢复 toolCard 态。
|
||||
crate::commands::ai::restore_pending_approvals(&state).await;
|
||||
// F-260614-04c: 启动一次性初始化 per-provider caps 表。
|
||||
// 根据 DB enabled providers 建 HashMap<provider_id, global_cap>,让 agentic loop 的
|
||||
// acquire_for_provider 从 None(无限流)切换到 Some(按配置限流)。disabled / weight=0
|
||||
// 的 provider 不入表(其被 provider_pool::select 过滤出候选,不会被 acquire)。
|
||||
// 单 provider 场景:该 provider cap=global_cap → acquire_global+acquire_for_provider
|
||||
// 串联,min(3,3)=3,有效上限同未配置 → 行为零变化。
|
||||
state.reload_provider_caps().await;
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
/// F-260614-04c: 从 DB enabled providers 重建 per_provider caps 并 set_provider_caps。
|
||||
///
|
||||
/// 启动 + provider 变更(ai_update_provider_pool / 删除 / 新增)后调用。caps 策略见
|
||||
/// AppState::init 注释(本轮每 provider cap = global_cap,F-04d 配差异化上限时仅改此)。
|
||||
pub async fn reload_provider_caps(&self) {
|
||||
let providers = match self.ai_providers.list_all().await {
|
||||
Ok(ps) => ps,
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
"[F-04c] 读取 providers 重建 caps 失败,保持当前 caps 表不变: {}", e
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let global_cap = 3; // 与 LlmConcurrency::new(3, 2) 的 global 上限对齐。
|
||||
let caps: HashMap<String, usize> = providers
|
||||
.into_iter()
|
||||
.filter(|p| p.enabled && p.weight > 0)
|
||||
.map(|p| (p.id, global_cap))
|
||||
.collect();
|
||||
self.llm_concurrency.set_provider_caps(caps).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// 构建节点注册表 — 注册内置节点
|
||||
|
||||
Reference in New Issue
Block a user