feat(pool): 引入 pro_first 调度预设、Pool 候选持久化跳过与诊断信息优化

- 新增 pro_first 调度预设(Pro 优先),更新 plus_first 仅针对 Plus 计划,移除 free_team_first
- Pool 内部候选(pool_key_index 不为空)跳过 DB 持久化(available/skipped/unused 均适用)
- LRU 排序新增 catalog_lru_score 回退:runtime 无记录时使用 last_used_at_unix_secs
- 执行路径 miss 诊断消息细化为中文,按 reason 分类输出可读说明
- build_local_request_candidate_status_record 补充 extra_data 和 created_at_unix_ms 字段
- OpenAI CLI 计划构建流程补充候选评估进度跟踪与 terminal reason 设置
- 前端 PoolSchedulingDialog 增加 pro_first 预设展示,修复 LRU 默认预设检测逻辑
This commit is contained in:
fawney19
2026-04-24 13:29:05 +08:00
parent f29649e3a8
commit f3c9835759
27 changed files with 950 additions and 153 deletions

View File

@@ -10,6 +10,23 @@ use super::{
};
use crate::DataLayerError;
fn merge_extra_data(
existing: Option<serde_json::Value>,
overlay: Option<serde_json::Value>,
) -> Option<serde_json::Value> {
match (existing, overlay) {
(
Some(serde_json::Value::Object(mut existing_object)),
Some(serde_json::Value::Object(overlay_object)),
) => {
existing_object.extend(overlay_object);
Some(serde_json::Value::Object(existing_object))
}
(_existing, Some(overlay)) => Some(overlay),
(existing, None) => existing,
}
}
#[derive(Debug, Default)]
pub struct InMemoryRequestCandidateRepository {
by_id: RwLock<BTreeMap<String, StoredRequestCandidate>>,
@@ -353,9 +370,10 @@ impl RequestCandidateWriteRepository for InMemoryRequestCandidateRepository {
concurrent_requests: candidate
.concurrent_requests
.or_else(|| existing.as_ref().and_then(|row| row.concurrent_requests)),
extra_data: candidate
.extra_data
.or_else(|| existing.as_ref().and_then(|row| row.extra_data.clone())),
extra_data: merge_extra_data(
existing.as_ref().and_then(|row| row.extra_data.clone()),
candidate.extra_data,
),
required_capabilities: candidate.required_capabilities.or_else(|| {
existing
.as_ref()
@@ -411,6 +429,7 @@ mod tests {
RequestCandidateReadRepository, RequestCandidateStatus, RequestCandidateWriteRepository,
StoredRequestCandidate, UpsertRequestCandidateRecord,
};
use serde_json::json;
fn sample_candidate(
id: &str,
@@ -540,7 +559,10 @@ mod tests {
error_message: None,
latency_ms: None,
concurrent_requests: None,
extra_data: None,
extra_data: Some(json!({
"execution_strategy": "local_cross_format",
"provider_name": "primary",
})),
required_capabilities: None,
created_at_unix_ms: Some(100),
started_at_unix_ms: None,
@@ -572,7 +594,10 @@ mod tests {
error_message: None,
latency_ms: Some(25),
concurrent_requests: Some(2),
extra_data: None,
extra_data: Some(json!({
"provider_api_format": "openai:cli",
"provider_name": "updated",
})),
required_capabilities: None,
created_at_unix_ms: None,
started_at_unix_ms: Some(101),
@@ -584,6 +609,27 @@ mod tests {
assert_eq!(updated.status, RequestCandidateStatus::Success);
assert_eq!(updated.status_code, Some(200));
assert_eq!(updated.latency_ms, Some(25));
assert_eq!(
updated
.extra_data
.as_ref()
.and_then(|value| value.get("execution_strategy")),
Some(&json!("local_cross_format"))
);
assert_eq!(
updated
.extra_data
.as_ref()
.and_then(|value| value.get("provider_api_format")),
Some(&json!("openai:cli"))
);
assert_eq!(
updated
.extra_data
.as_ref()
.and_then(|value| value.get("provider_name")),
Some(&json!("updated"))
);
assert_eq!(updated.started_at_unix_ms, Some(101));
}

View File

@@ -240,7 +240,14 @@ DO UPDATE SET
error_message = COALESCE(EXCLUDED.error_message, request_candidates.error_message),
latency_ms = COALESCE(EXCLUDED.latency_ms, request_candidates.latency_ms),
concurrent_requests = COALESCE(EXCLUDED.concurrent_requests, request_candidates.concurrent_requests),
extra_data = COALESCE(EXCLUDED.extra_data, request_candidates.extra_data),
extra_data = CASE
WHEN request_candidates.extra_data IS NULL THEN EXCLUDED.extra_data
WHEN EXCLUDED.extra_data IS NULL THEN request_candidates.extra_data
WHEN json_typeof(request_candidates.extra_data) = 'object'
AND json_typeof(EXCLUDED.extra_data) = 'object'
THEN (request_candidates.extra_data::jsonb || EXCLUDED.extra_data::jsonb)::json
ELSE EXCLUDED.extra_data
END,
required_capabilities = COALESCE(EXCLUDED.required_capabilities, request_candidates.required_capabilities),
started_at = COALESCE(EXCLUDED.started_at, request_candidates.started_at),
finished_at = COALESCE(EXCLUDED.finished_at, request_candidates.finished_at)