feat(knowledge): 阶段三 知识治理 + 阶段四 图谱增强

阶段三(治理):
- sensitive.py:写入前脱敏 token/密钥/手机号/身份证/邮箱(sensitive_redaction_enabled)
- 版本快照:更新前写 knowledge_note_versions;GET /notes/{id}/versions
- dedup.py:标题/摘要 Jaccard + 内容哈希查重;GET /duplicates、POST /merge(来源并入主条目、重复项归档)
- auto_ingest.py:独立后台队列/线程沉淀,不阻塞问答;中间件 after_agent 在 auto_ingest_enabled 时入队;价值判断(最小字符)+ skip_existing 去重;自动沉淀落 draft 待审

阶段四(图谱):
- LLM 抽取 entities/relations;service 存 knowledge_entities/knowledge_relations 并连边
- exporter.build_graph 支持实体节点 + note→entity/entity→entity 边;graph.html/json 增强
- GET /api/knowledge/graph 返回产品图谱数据

前端:知识库页「查重合并」对话框 + 相关 api/hooks
配置:sensitive_redaction_enabled / dedup_similarity_threshold / entity_extraction_enabled
测试:tests/test_knowledge.py 20 passed

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
AdministratorandClaude Opus 4.8 committed 2026-06-08 18:31:33 +08:00
1 parent 683cc6d76e
commit 81e4fd92ce
16 files changed
+691 -7

No files matched your search

+58
View File
@@ -131,3 +131,61 @@ export async function exportKnowledgeGraph() {
export function knowledgeExportFileUrl(filename: string) {
return `${baseUrl()}/export/files/${encodeURIComponent(filename)}`;
}
// --- phase 2/3/4 ---
export interface KnowledgeVersion {
id: string;
note_id: string;
title: string;
summary?: string | null;
content_md: string;
status: string;
tags: string[];
edited_by?: string | null;
created_at?: string | null;
}
export async function listNoteVersions(noteId: string) {
const res = await parseResponse<{ items: KnowledgeVersion[] }>(
await apiFetch(`${baseUrl()}/notes/${encodeURIComponent(noteId)}/versions`),
);
return res.items;
}
export interface DuplicateGroup {
primary: { id: string; title: string | null };
duplicates: Array<{ id: string; title: string | null; score: number }>;
}
export async function listDuplicates() {
const res = await parseResponse<{ groups: DuplicateGroup[] }>(
await apiFetch(`${baseUrl()}/duplicates`),
);
return res.groups;
}
export async function mergeKnowledgeNotes(primaryId: string, duplicateIds: string[]) {
return parseResponse<{ note: KnowledgeNote }>(
await apiFetch(`${baseUrl()}/merge`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ primary_id: primaryId, duplicate_ids: duplicateIds }),
}),
);
}
export async function reindexKnowledge() {
return parseResponse<{ reindexed: number; embedding_enabled: number }>(
await apiFetch(`${baseUrl()}/reindex`, { method: "POST" }),
);
}
export interface KnowledgeGraph {
nodes: Array<{ id: string; type: string; label: string; category?: string | null }>;
edges: Array<{ id: string; source: string; target: string; label: string; weight: number }>;
}
export async function getKnowledgeGraph() {
return parseResponse<KnowledgeGraph>(await apiFetch(`${baseUrl()}/graph`));
}
+16
View File
@@ -12,7 +12,9 @@ import {
getKnowledgeNote,
ingestThread,
ingestThreadsBatch,
listDuplicates,
listKnowledgeNotes,
mergeKnowledgeNotes,
searchKnowledge,
updateKnowledgeNote,
} from "./api";
@@ -85,5 +87,19 @@ export function useKnowledgeActions() {
onSuccess: invalidate,
}),
exportGraph: useMutation({ mutationFn: exportKnowledgeGraph }),
merge: useMutation({
mutationFn: ({ primaryId, duplicateIds }: { primaryId: string; duplicateIds: string[] }) =>
mergeKnowledgeNotes(primaryId, duplicateIds),
onSuccess: invalidate,
}),
};
}
export function useKnowledgeDuplicates(enabled: boolean) {
return useQuery({
queryKey: [...ROOT_KEY, "duplicates"],
queryFn: listDuplicates,
enabled,
refetchOnWindowFocus: false,
});
}