修复: 知识工具检索接分词与晋升补嵌入
knowledge_search 整句 LIKE 改分词检索,自然语言查询可命中 晋升(手动/复用达标)后补 spawn 嵌入,进入向量检索面 本地截断改调 audit truncate_chars 配置加载下沉 load_knowledge_config 供工具路径共用
This commit is contained in:
@@ -30,8 +30,11 @@ use df_storage::db::Database;
|
||||
use df_storage::models::KnowledgeRecord;
|
||||
use df_types::types::new_id;
|
||||
|
||||
use crate::commands::ai::audit::truncate_chars;
|
||||
use crate::commands::ai::knowledge_inject::{extract_query_keywords, spawn_embedding_for_record};
|
||||
use crate::commands::knowledge_timeline::KnowledgeTimeline;
|
||||
use crate::commands::now_millis;
|
||||
use crate::state::load_knowledge_config;
|
||||
|
||||
/// search 结果单条 content 的截断长度:知识全文可能很长,原样返回会回灌 prompt
|
||||
/// 挤占上下文;截 500 字给 LLM 足够判断「这条是否相关」,需全文时走 IPC/前端看详情。
|
||||
@@ -49,7 +52,7 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
|
||||
registry,
|
||||
db: Arc<Database>,
|
||||
"knowledge_search",
|
||||
"检索本地知识库(仅已发布知识,按关键词 LIKE 匹配,按复用次数降序)。使用时机:回答前需要过往经验/项目约定/踩坑记录时调用,如用户问「之前怎么解决的」「有没有相关约定」。用户说「记住/记一下/以后注意」时不要用本工具,改用 knowledge_insert。参数:query=知识主题关键词(必填),limit=返回条数(可选,默认 5,上限 10)",
|
||||
"检索本地知识库(仅已发布知识,query 自动分词提取关键词匹配,标题命中优先、复用次数降序)。使用时机:回答前需要过往经验/项目约定/踩坑记录时调用,如用户问「之前怎么解决的」「有没有相关约定」。用户说「记住/记一下/以后注意」时不要用本工具,改用 knowledge_insert。参数:query=知识主题关键词或自然语言描述(必填,自动分词),limit=返回条数(可选,默认 5,上限 10)",
|
||||
RiskLevel::Low,
|
||||
schema: object_schema(vec![("query", "string", true), ("limit", "integer", false)]),
|
||||
args => {
|
||||
@@ -59,7 +62,11 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
|
||||
}
|
||||
let limit = args["limit"].as_u64().unwrap_or(5).clamp(1, 10) as usize;
|
||||
let repo = df_storage::crud::KnowledgeRepo::new(&db);
|
||||
let hits = repo.search(query, None, limit).await?;
|
||||
// 整句 LIKE 对中文自然语言命中率≈0(知识库走查 2026-09-29),与注入检索同源
|
||||
// 分词(ASCII 词 + 中文 2-gram)后逐词匹配,任一关键词命中即入候选。
|
||||
// 仅已发布(include_candidates=false):LLM 检索面与人工审核面一致。
|
||||
let keywords = extract_query_keywords(query);
|
||||
let hits = repo.search_keywords(&keywords, false, limit).await?;
|
||||
// 空结果返回可执行的下一步指引而非裸空数组:弱模型拿到 [] 常直接回「没找到」
|
||||
// 结束,带上「让用户口述 + insert 沉淀」能把检索失败转化为知识积累入口。
|
||||
if hits.is_empty() {
|
||||
@@ -71,21 +78,10 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
|
||||
let results: Vec<serde_json::Value> = hits
|
||||
.iter()
|
||||
.map(|k| {
|
||||
// 按 chars 截断而非字节切片:中文内容按字节切会落在 UTF-8 边界上 panic
|
||||
let snippet = if k.content.chars().count() > SEARCH_CONTENT_SNIPPET_LEN {
|
||||
let truncated: String = k
|
||||
.content
|
||||
.chars()
|
||||
.take(SEARCH_CONTENT_SNIPPET_LEN)
|
||||
.collect();
|
||||
format!("{truncated}…")
|
||||
} else {
|
||||
k.content.clone()
|
||||
};
|
||||
serde_json::json!({
|
||||
"title": k.title,
|
||||
"kind": k.kind,
|
||||
"content": snippet,
|
||||
"content": truncate_chars(&k.content, SEARCH_CONTENT_SNIPPET_LEN),
|
||||
"status": k.status,
|
||||
"reuse_count": k.reuse_count,
|
||||
})
|
||||
@@ -194,6 +190,12 @@ pub fn register(registry: &mut AiToolRegistry, db: &Arc<Database>) {
|
||||
KnowledgeTimeline::new(&db)
|
||||
.record_manual_publish(id, "agent")
|
||||
.await;
|
||||
// 转正即进检索面,后台补嵌入对齐 IPC 发布路径(knowledge_update_status);
|
||||
// 失败标 failed 可补偿重试,非阻断,vector_enabled 关闭时内部直接跳过
|
||||
if let Some(rec) = repo.get_by_id(id).await? {
|
||||
let config = load_knowledge_config(&db).await;
|
||||
spawn_embedding_for_record(&db, &config, &rec).await;
|
||||
}
|
||||
Ok(serde_json::json!({
|
||||
"id": id,
|
||||
"status": "published",
|
||||
|
||||
Reference in New Issue
Block a user