优化: AI Chat全栈多批审查修复与架构清理(risk_level清理/路由解耦/工具渲染/测试补测/死代码)
This commit is contained in:
@@ -10,7 +10,7 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use df_ai::df_ai_core::model::{IntelligenceTier, Modality, ModelConfig};
|
||||
use df_ai::df_ai_core::model::{Modality, ModelConfig};
|
||||
use df_ai::provider::{ChatMessage, CompletionRequest, LlmProvider};
|
||||
// F-01 阶段5: AiNode 路由 — 节点 config.model_id 优先;否则按 TaskRequirements 路由
|
||||
// (默认 Standard + needs_tool_use=true)。池空/无匹配兜底 record.default_model。
|
||||
@@ -66,6 +66,27 @@ struct ResolvedProvider {
|
||||
/// 无任何 provider → 友好错误「未配置 AI Provider」。
|
||||
///
|
||||
/// model:config["model"] 非空用之,否则 record.default_model,再否则 "gpt-4o-mini" 占位。
|
||||
/// SW-260618-09: provider 三件套 DRY —— resolve_provider + parse_params 合并
|
||||
/// (AiNode/AiSelfReviewNode execute 逐字重复)。
|
||||
async fn resolve_and_parse(
|
||||
db: &Arc<Database>,
|
||||
config: &serde_json::Value,
|
||||
inputs: &HashMap<String, NodeOutput>,
|
||||
) -> anyhow::Result<AiNodeParams> {
|
||||
let provider_cfg = resolve_provider(db, config).await?;
|
||||
parse_params(config, inputs, provider_cfg)
|
||||
}
|
||||
|
||||
/// SW-260618-09: build_provider 5 行封装 DRY(AiNode/AiSelfReviewNode execute 逐字重复)。
|
||||
fn provider_from_params(p: &AiNodeParams) -> Box<dyn LlmProvider> {
|
||||
df_ai::build_provider(
|
||||
&p.provider.protocol,
|
||||
&p.provider.base_url,
|
||||
&p.provider.api_key,
|
||||
&p.provider.default_model,
|
||||
)
|
||||
}
|
||||
|
||||
async fn resolve_provider(
|
||||
db: &Arc<Database>,
|
||||
config: &serde_json::Value,
|
||||
@@ -90,23 +111,16 @@ async fn resolve_provider(
|
||||
}
|
||||
|
||||
// ── 路径 2:老明文路径兼容(过渡,warn) ──
|
||||
let has_plain_base = config.get("base_url").and_then(|v| v.as_str()).is_some();
|
||||
let has_plain_key = config.get("api_key").and_then(|v| v.as_str()).is_some();
|
||||
if has_plain_base && has_plain_key {
|
||||
// base_url / api_key 各解析一次(复用于 has 判定与取值,避免 DRY 重复解析同字段)。
|
||||
let plain_base = config.get("base_url").and_then(|v| v.as_str());
|
||||
let plain_key = config.get("api_key").and_then(|v| v.as_str());
|
||||
if let (Some(base_url_str), Some(api_key_str)) = (plain_base, plain_key) {
|
||||
tracing::warn!(
|
||||
"AiNode 明文 api_key/base_url 经 config 注入已废弃, 改用 provider_id (FR-S1). \
|
||||
老路径将在后续版本移除"
|
||||
);
|
||||
let base_url = config
|
||||
.get("base_url")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let api_key = config
|
||||
.get("api_key")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let base_url = base_url_str.to_string();
|
||||
let api_key = api_key_str.to_string();
|
||||
ensure_resolved_key("(明文注入)", &api_key)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let protocol = config
|
||||
@@ -201,14 +215,12 @@ fn parse_params(
|
||||
let model = if !config_model.is_empty() {
|
||||
config_model
|
||||
} else {
|
||||
// F-01 阶段5: AiNode 默认路由 — Standard + needs_tool_use=true(工作流无人值守 AI 步骤
|
||||
// F-01 阶段5: AiNode 默认路由 — needs_tool_use=true(工作流无人值守 AI 步骤
|
||||
// 常含工具调用,如检索/生成;无需工具的节点应在 config 显式指定 model)。
|
||||
// select_model_id None(池空/无匹配)→ 空串(由 provider impl 回填 default_model)。
|
||||
let node_req = TaskRequirements {
|
||||
modalities: vec![Modality::Text],
|
||||
needs_tool_use: true,
|
||||
min_intelligence: IntelligenceTier::Standard,
|
||||
max_cost: None,
|
||||
estimated_context: 0,
|
||||
};
|
||||
select_model_id(&node_req, &provider.model_pool).unwrap_or_default()
|
||||
@@ -258,15 +270,8 @@ impl Node for AiNode {
|
||||
tracing::info!("AiNode 执行: node_id={}", ctx.node_id);
|
||||
|
||||
// FR-S1 注入链:provider 经 df_storage::secret 在 AiNode 内存解析,api_key 不进 config。
|
||||
let provider_cfg = resolve_provider(&self.db, &ctx.config).await?;
|
||||
let p = parse_params(&ctx.config, &ctx.inputs, provider_cfg)?;
|
||||
|
||||
let provider: Box<dyn LlmProvider> = df_ai::build_provider(
|
||||
&p.provider.protocol,
|
||||
&p.provider.base_url,
|
||||
&p.provider.api_key,
|
||||
&p.provider.default_model,
|
||||
);
|
||||
let p = resolve_and_parse(&self.db, &ctx.config, &ctx.inputs).await?;
|
||||
let provider: Box<dyn LlmProvider> = provider_from_params(&p);
|
||||
|
||||
// ── 构建消息 ──
|
||||
let mut messages = Vec::with_capacity(2);
|
||||
@@ -343,7 +348,8 @@ impl Node for AiNode {
|
||||
"base_url": { "type": "string", "description": "(已废弃过渡)明文 API 地址,改用 provider_id" },
|
||||
"api_key": { "type": "string", "description": "(已废弃过渡)明文 API 密钥,改用 provider_id;FR-S1 下经 secret 解析" }
|
||||
},
|
||||
"required": ["provider_id"]
|
||||
// SW-260618-15: prompt/provider_id 均"留空走兜底"(prompt 取上游、provider_id 走默认 provider),与 required 矛盾。改 required=[] 对齐 execute 运行时,防前端按 schema 误拒合法配置。
|
||||
"required": []
|
||||
}),
|
||||
output: serde_json::json!({
|
||||
"type": "object",
|
||||
@@ -468,8 +474,7 @@ impl Node for AiSelfReviewNode {
|
||||
tracing::info!("AiSelfReviewNode 执行: node_id={}", ctx.node_id);
|
||||
|
||||
// FR-S1 注入链:provider 经 df_storage::secret 在 AiNode 内存解析,api_key 不进 config。
|
||||
let provider_cfg = resolve_provider(&self.db, &ctx.config).await?;
|
||||
let p = parse_params(&ctx.config, &ctx.inputs, provider_cfg)?;
|
||||
let p = resolve_and_parse(&self.db, &ctx.config, &ctx.inputs).await?;
|
||||
|
||||
// ── 读任务(需求 + 产出) ──
|
||||
// task_id 必填(自审对象是具体任务的产出);缺失报错而非静默跳过。
|
||||
@@ -496,12 +501,7 @@ impl Node for AiSelfReviewNode {
|
||||
|
||||
let prompt = Self::build_review_prompt(&task.description, &output_text);
|
||||
|
||||
let provider: Box<dyn LlmProvider> = df_ai::build_provider(
|
||||
&p.provider.protocol,
|
||||
&p.provider.base_url,
|
||||
&p.provider.api_key,
|
||||
&p.provider.default_model,
|
||||
);
|
||||
let provider: Box<dyn LlmProvider> = provider_from_params(&p);
|
||||
|
||||
let request = CompletionRequest {
|
||||
model: p.model.clone(),
|
||||
|
||||
Reference in New Issue
Block a user