feat(ai): 对接本地 Ollama qwen3:4b 模型
- default_provider 从 claude 切换到 ollama - main.rs 支持 ollama/openai/claude 三种 provider 动态选择 - 新增 [ai.providers.ollama] 配置段(base_url/model/temperature) - 前端 SSE AI 分析全链路验证通过
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1411,10 +1411,12 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"dashmap",
|
||||||
"erp-core",
|
"erp-core",
|
||||||
"futures",
|
"futures",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"hex",
|
"hex",
|
||||||
|
"redis",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -40,15 +40,23 @@ hmac_key = "__MUST_SET_VIA_ENV__"
|
|||||||
kek = "__MUST_SET_VIA_ENV__"
|
kek = "__MUST_SET_VIA_ENV__"
|
||||||
|
|
||||||
[ai]
|
[ai]
|
||||||
default_provider = "claude"
|
default_provider = "ollama"
|
||||||
# AI API 密钥。留空则禁用 AI 功能;生产环境必须通过 ERP__AI__API_KEY 设置。
|
# AI API 密钥。留空则禁用 AI 功能;生产环境必须通过 ERP__AI__API_KEY 设置。
|
||||||
api_key = ""
|
api_key = ""
|
||||||
model = "claude-sonnet-4-6"
|
model = "qwen3:4b"
|
||||||
max_tokens = 2048
|
max_tokens = 2048
|
||||||
temperature = 0.3
|
temperature = 0.3
|
||||||
cache_ttl_seconds = 604800
|
cache_ttl_seconds = 604800
|
||||||
rate_limit_patient_daily = 10
|
rate_limit_patient_daily = 10
|
||||||
|
|
||||||
|
[ai.providers.ollama]
|
||||||
|
provider_type = "ollama"
|
||||||
|
base_url = "http://localhost:11434"
|
||||||
|
default_model = "qwen3:4b"
|
||||||
|
max_tokens = 2048
|
||||||
|
temperature = 0.3
|
||||||
|
is_enabled = true
|
||||||
|
|
||||||
[storage]
|
[storage]
|
||||||
upload_dir = "./uploads"
|
upload_dir = "./uploads"
|
||||||
max_file_size = "10MB"
|
max_file_size = "10MB"
|
||||||
|
|||||||
@@ -511,16 +511,43 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
tracing::info!(providers = ?registry.provider_names(), "AI Provider 注册完成");
|
tracing::info!(providers = ?registry.provider_names(), "AI Provider 注册完成");
|
||||||
|
|
||||||
// 构建默认 provider 用于 AnalysisService(保持 Box<dyn AiProvider> 签名)
|
// 根据 default_provider 配置构建 AnalysisService 的默认 provider
|
||||||
let mut default_claude = erp_ai::provider::claude::ClaudeProvider::new(
|
let default_provider: Box<dyn erp_ai::provider::AiProvider> =
|
||||||
|
match config.ai.default_provider.as_str() {
|
||||||
|
"ollama" => {
|
||||||
|
let pcfg = config.ai.providers.get("ollama");
|
||||||
|
let base_url = pcfg.and_then(|c| c.base_url.clone())
|
||||||
|
.unwrap_or_else(|| "http://localhost:11434".to_string());
|
||||||
|
let model = pcfg.map(|c| c.default_model.clone())
|
||||||
|
.unwrap_or_else(|| config.ai.model.clone());
|
||||||
|
tracing::info!(base_url = %base_url, model = %model, "AnalysisService 使用 Ollama 提供商");
|
||||||
|
Box::new(erp_ai::provider::ollama::OllamaProvider::new(base_url, model))
|
||||||
|
}
|
||||||
|
"openai" => {
|
||||||
|
let pcfg = config.ai.providers.get("openai");
|
||||||
|
let api_key = pcfg.and_then(|c| c.api_key_env.as_ref())
|
||||||
|
.and_then(|env| std::env::var(env).ok())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let base_url = pcfg.and_then(|c| c.base_url.clone())
|
||||||
|
.unwrap_or_else(|| "https://api.openai.com".to_string());
|
||||||
|
let model = pcfg.map(|c| c.default_model.clone())
|
||||||
|
.unwrap_or_else(|| config.ai.model.clone());
|
||||||
|
Box::new(erp_ai::provider::openai::OpenAIProvider::new(api_key, base_url, model))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// 默认 Claude
|
||||||
|
let mut claude = erp_ai::provider::claude::ClaudeProvider::new(
|
||||||
config.ai.api_key.clone(),
|
config.ai.api_key.clone(),
|
||||||
);
|
);
|
||||||
if let Some(ref base_url) = config.ai.base_url {
|
if let Some(ref base_url) = config.ai.base_url {
|
||||||
default_claude = default_claude.with_base_url(base_url.clone());
|
claude = claude.with_base_url(base_url.clone());
|
||||||
}
|
}
|
||||||
|
Box::new(claude)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let analysis_svc = erp_ai::service::analysis::AnalysisService::new(
|
let analysis_svc = erp_ai::service::analysis::AnalysisService::new(
|
||||||
Box::new(default_claude),
|
default_provider,
|
||||||
db.clone(),
|
db.clone(),
|
||||||
).with_knowledge_source(std::sync::Arc::new(
|
).with_knowledge_source(std::sync::Arc::new(
|
||||||
erp_ai::knowledge::structured_source::StructuredKnowledgeSource::new(db.clone()),
|
erp_ai::knowledge::structured_source::StructuredKnowledgeSource::new(db.clone()),
|
||||||
|
|||||||
Reference in New Issue
Block a user