新增: 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,
|
provider_type: String,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
// 编辑已有提供商时保留原 created_at,避免被覆盖
|
// 编辑已有提供商时保留原 created_at,避免被覆盖
|
||||||
let created_at = match &id {
|
// F-260614-04c: 编辑路径同时保留原 enabled/weight(负载均衡池可编辑层)。
|
||||||
Some(pid) => state.ai_providers.get_by_id(pid).await
|
// 前端经此 IPC 改 enabled/weight 落库;新建走默认 enabled=true/weight=50。
|
||||||
.map_err(err_str)?
|
let existing = match &id {
|
||||||
.map(|p| p.created_at)
|
Some(pid) => state.ai_providers.get_by_id(pid).await.map_err(err_str)?,
|
||||||
.unwrap_or_else(now_millis),
|
None => None,
|
||||||
None => now_millis(),
|
|
||||||
};
|
};
|
||||||
|
let created_at = existing.as_ref().map(|p| p.created_at.clone()).unwrap_or_else(now_millis);
|
||||||
// is_default:编辑保留原值;新建时若全表尚无默认则设为默认(首个自动默认,避免无默认可用)
|
// is_default:编辑保留原值;新建时若全表尚无默认则设为默认(首个自动默认,避免无默认可用)
|
||||||
let is_default = match &id {
|
let is_default = match &existing {
|
||||||
Some(pid) => state.ai_providers.get_by_id(pid).await
|
Some(p) => p.is_default,
|
||||||
.map_err(err_str)?
|
|
||||||
.map(|p| p.is_default)
|
|
||||||
.unwrap_or(false),
|
|
||||||
None => !state.ai_providers.list_all().await
|
None => !state.ai_providers.list_all().await
|
||||||
.map_err(err_str)?
|
.map_err(err_str)?
|
||||||
.iter().any(|p| p.is_default),
|
.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 列恒空(不入明文)。
|
// FR-S1:密钥存 OS keyring,DB api_key 列恒空(不入明文)。
|
||||||
// api_key 非空 = 新/改密钥 → 写 keyring;空 = 编辑不改 → 保留原 keyring 密钥不动。
|
// api_key 非空 = 新/改密钥 → 写 keyring;空 = 编辑不改 → 保留原 keyring 密钥不动。
|
||||||
let provider_id = id.clone().unwrap_or_else(new_id);
|
let provider_id = id.clone().unwrap_or_else(new_id);
|
||||||
@@ -993,10 +996,9 @@ pub async fn ai_save_provider(
|
|||||||
config: None,
|
config: None,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at: now_millis(),
|
updated_at: now_millis(),
|
||||||
// F-260614-04: 新建 provider 默认进入负载均衡池(enabled=true,weight=50)。
|
// F-260614-04c: enabled/weight 编辑保留原值(负载均衡池可编辑层),新建走默认。
|
||||||
// 单 provider 路径零变化(enabled=true 即与迁移前行为等价)。
|
enabled,
|
||||||
enabled: true,
|
weight,
|
||||||
weight: 50,
|
|
||||||
};
|
};
|
||||||
let id = record.id.clone();
|
let id = record.id.clone();
|
||||||
state
|
state
|
||||||
@@ -1007,7 +1009,52 @@ pub async fn ai_save_provider(
|
|||||||
Ok(id)
|
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]
|
#[tauri::command]
|
||||||
pub async fn ai_set_provider(
|
pub async fn ai_set_provider(
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
|
|||||||
@@ -125,6 +125,8 @@ pub fn run() {
|
|||||||
commands::ai::ai_save_provider,
|
commands::ai::ai_save_provider,
|
||||||
commands::ai::ai_set_provider,
|
commands::ai::ai_set_provider,
|
||||||
commands::ai::ai_delete_provider,
|
commands::ai::ai_delete_provider,
|
||||||
|
// F-260614-04c: 负载均衡池可编辑层(enabled/weight 轻量更新 + caps 重建)
|
||||||
|
commands::ai::ai_update_provider_pool,
|
||||||
// F-01 阶段5:测试连接拉取模型列表 + 单模型探测
|
// F-01 阶段5:测试连接拉取模型列表 + 单模型探测
|
||||||
commands::ai::ai_fetch_models,
|
commands::ai::ai_fetch_models,
|
||||||
commands::ai::ai_probe_model,
|
commands::ai::ai_probe_model,
|
||||||
|
|||||||
@@ -294,8 +294,38 @@ impl AppState {
|
|||||||
// 启动恢复:重启前卡 pending 的工具审批(内存 pending_approvals 已丢)从审计表重建,
|
// 启动恢复:重启前卡 pending 的工具审批(内存 pending_approvals 已丢)从审计表重建,
|
||||||
// 使重启后待审批不丢。前端经 ai_pending_tool_calls + switchConversation 恢复 toolCard 态。
|
// 使重启后待审批不丢。前端经 ai_pending_tool_calls + switchConversation 恢复 toolCard 态。
|
||||||
crate::commands::ai::restore_pending_approvals(&state).await;
|
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)
|
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