feat(ai): Phase 3A RAG 知识库 — CRUD API + Agent Tool + 向量知识源 + 前端管理页

- 知识库 REST API: 10 个端点 (references/guides CRUD + re-embed)
- search_medical_knowledge Agent Tool: 语义检索参考资料和临床指南
- VectorKnowledgeSource: 实现 KnowledgeSource trait,自动降级
- 沙箱配置: Patient/MedicalStaff 允许使用知识库检索
- 前端 AiKnowledgePage: Tabs(参考资料/临床指南) + Table + Modal CRUD
- 权限码 seed 迁移: ai.knowledge.list + ai.knowledge.manage + 菜单
This commit is contained in:
iven
2026-05-19 09:10:53 +08:00
parent c0570dfbfc
commit 8b88cb4a50
18 changed files with 1389 additions and 5 deletions

View File

@@ -44,6 +44,7 @@ pub fn get_sandbox_config(role: &UserRole) -> SandboxConfig {
"query_patient_vitals".into(),
"query_patient_lab_reports".into(),
"query_patient_medications".into(),
"search_medical_knowledge".into(),
]),
system_prompt_suffix: PATIENT_PROMPT_SUFFIX,
output_filter: OutputFilter {
@@ -59,6 +60,7 @@ pub fn get_sandbox_config(role: &UserRole) -> SandboxConfig {
"query_patient_lab_reports".into(),
"query_patient_appointments".into(),
"query_patient_medications".into(),
"search_medical_knowledge".into(),
]),
system_prompt_suffix: MEDICAL_STAFF_PROMPT_SUFFIX,
output_filter: OutputFilter {

View File

@@ -4,8 +4,10 @@ pub mod query_appointments;
pub mod query_lab_reports;
pub mod query_medications;
pub mod query_vitals;
pub mod search_medical_knowledge;
pub use query_appointments::QueryAppointmentsTool;
pub use query_lab_reports::QueryLabReportsTool;
pub use query_medications::QueryMedicationsTool;
pub use query_vitals::QueryPatientVitalsTool;
pub use search_medical_knowledge::SearchMedicalKnowledgeTool;

View File

@@ -0,0 +1,112 @@
use async_trait::async_trait;
use crate::agent::tool::{AgentTool, ToolContext, ToolResult};
use crate::service::embedding::EmbeddingService;
/// 语义检索医学知识库(参考资料 + 临床指南)
pub struct SearchMedicalKnowledgeTool;
#[async_trait]
impl AgentTool for SearchMedicalKnowledgeTool {
fn name(&self) -> &str {
"search_medical_knowledge"
}
fn description(&self) -> &str {
"在医学知识库中语义检索相关参考资料和临床指南。输入查询文本,返回最相关的知识条目。可选按分析类型过滤(如 trend、lab_report、dialysis_risk"
}
fn parameters_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"required": ["query"],
"properties": {
"query": {
"type": "string",
"description": "搜索查询文本,如'高血压管理指南'或'血红蛋白偏低原因'"
},
"analysis_type": {
"type": "string",
"description": "可选按分析类型过滤trend、lab_report、dialysis_risk 等)"
}
}
})
}
async fn execute(&self, ctx: &ToolContext, params: serde_json::Value) -> ToolResult {
let query_text = match params["query"].as_str() {
Some(q) if !q.trim().is_empty() => q.to_string(),
_ => {
return ToolResult {
output: "请提供搜索查询文本".to_string(),
display_hint: None,
};
}
};
let analysis_type = params["analysis_type"].as_str();
let embedding_svc = EmbeddingService::from_settings(&ctx.db).await;
if !embedding_svc.is_configured() {
return ToolResult {
output: "Embedding API 未配置,无法进行语义搜索".to_string(),
display_hint: None,
};
}
let embedding = match embedding_svc.embed(&query_text).await {
Ok(e) => e,
Err(e) => {
return ToolResult {
output: format!("生成查询向量失败: {}", e),
display_hint: None,
};
}
};
let results = match crate::knowledge::vector_search::KnowledgeSearchRepository::search(
&ctx.db,
ctx.tenant_id,
analysis_type,
&embedding,
5,
0.6,
)
.await
{
Ok(r) => r,
Err(e) => {
return ToolResult {
output: format!("知识库搜索失败: {}", e),
display_hint: None,
};
}
};
if results.is_empty() {
return ToolResult {
output: "未找到相关的医学知识条目".to_string(),
display_hint: None,
};
}
let mut output = String::from("知识库检索结果:\n\n");
for (i, r) in results.iter().enumerate() {
output.push_str(&format!(
"{}. [{}] {}(相似度: {}%\n 来源: {}\n {}\n\n",
i + 1,
r.source_table,
r.title,
(r.similarity * 100.0) as u32,
r.source_name,
r.content.chars().take(200).collect::<String>(),
));
}
ToolResult {
output,
display_hint: None,
}
}
}