From 94a562a1d50fafdd06964107b68d99f84af8accb Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Tue, 10 Feb 2026 10:22:38 +0800 Subject: [PATCH] Add debug knowledge base --- web/pages/KnowledgeBase.tsx | 99 ++++++++++++++++++++++++++++++++++++- web/services/backendApi.ts | 31 ++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/web/pages/KnowledgeBase.tsx b/web/pages/KnowledgeBase.tsx index c2c2902..efddc76 100644 --- a/web/pages/KnowledgeBase.tsx +++ b/web/pages/KnowledgeBase.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState, useRef } from 'react'; import { Search, Plus, FileText, Upload, ArrowLeft, CloudUpload, File as FileIcon, X, Pencil, Trash2, Settings2, MoreHorizontal } from 'lucide-react'; import { Button, Input, TableHeader, TableRow, TableHead, TableCell, Card, Dialog, Badge } from '../components/UI'; import { KnowledgeBase } from '../types'; -import { createKnowledgeBase, deleteKnowledgeBase, deleteKnowledgeDocument, fetchKnowledgeBaseById, fetchKnowledgeBases, fetchLLMModels, updateKnowledgeBase, uploadKnowledgeDocument } from '../services/backendApi'; +import { createKnowledgeBase, deleteKnowledgeBase, deleteKnowledgeDocument, fetchKnowledgeBaseById, fetchKnowledgeBases, fetchLLMModels, searchKnowledgeBase, updateKnowledgeBase, uploadKnowledgeDocument, type KnowledgeSearchResultItem } from '../services/backendApi'; const EMBEDDING_OPTIONS = [ 'text-embedding-3-small', @@ -455,7 +455,39 @@ const KnowledgeBaseDetail: React.FC<{ onDeleteDocument: (docId: string) => void; }> = ({ kb, onBack, onImport, onEdit, onDelete, onDeleteDocument }) => { const [docSearch, setDocSearch] = useState(''); + const [debugQuery, setDebugQuery] = useState(''); + const [debugTopK, setDebugTopK] = useState(5); + const [debugLoading, setDebugLoading] = useState(false); + const [debugResults, setDebugResults] = useState([]); + const [debugError, setDebugError] = useState(''); const filteredDocs = kb.documents.filter((d) => d.name.toLowerCase().includes(docSearch.toLowerCase())); + const docNameById = new Map(kb.documents.map((doc) => [doc.id, doc.name])); + + const runDebugSearch = async () => { + const query = debugQuery.trim(); + if (!query) { + alert('请输入要检索的文本'); + return; + } + try { + setDebugLoading(true); + setDebugError(''); + const response = await searchKnowledgeBase(kb.id, query, Math.min(Math.max(debugTopK, 1), 20)); + setDebugResults(response.results || []); + } catch (error: any) { + console.error(error); + setDebugResults([]); + setDebugError(error?.message || '检索失败,请稍后重试。'); + } finally { + setDebugLoading(false); + } + }; + + const renderMatchPercent = (distance?: number) => { + if (typeof distance !== 'number' || Number.isNaN(distance)) return '-'; + const score = 1 / (1 + Math.max(distance, 0)); + return `${(score * 100).toFixed(2)}%`; + }; return (
@@ -524,6 +556,71 @@ const KnowledgeBaseDetail: React.FC<{ + + +
+

调试检索

+

+ 输入测试文本,使用当前知识库的 embedding 配置检索文档片段并查看匹配度。 +

+
+
+
+ setDebugQuery(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && !debugLoading) void runDebugSearch(); + }} + className="bg-black/20 border-transparent focus:bg-black/40" + /> + setDebugTopK(parseInt(e.target.value || '1', 10))} + className="bg-black/20 border-transparent focus:bg-black/40" + /> + +
+ + {!!debugError && ( +
{debugError}
+ )} + + {!debugLoading && debugResults.length === 0 && !debugError && ( +
暂无结果,输入文本后点击“开始检索”。
+ )} + + {debugResults.length > 0 && ( +
+ {debugResults.map((item, idx) => { + const docId = String(item.metadata?.document_id || ''); + const chunkIndex = item.metadata?.chunk_index; + const docName = docNameById.get(docId); + return ( +
+
+ #{idx + 1} + {docName || docId || 'unknown_doc'} + chunk {chunkIndex ?? '-'} + distance: {typeof item.distance === 'number' ? item.distance.toFixed(6) : '-'} + 匹配度: {renderMatchPercent(item.distance)} +
+

+ {item.content || '(空内容)'} +

+
+ ); + })} +
+ )} +
+
); }; diff --git a/web/services/backendApi.ts b/web/services/backendApi.ts index c99cd92..9ec96f6 100644 --- a/web/services/backendApi.ts +++ b/web/services/backendApi.ts @@ -621,6 +621,37 @@ export const deleteKnowledgeDocument = async (kbId: string, docId: string): Prom await apiRequest(`/knowledge/bases/${kbId}/documents/${docId}`, { method: 'DELETE' }); }; +export type KnowledgeSearchResultItem = { + content: string; + metadata?: { + document_id?: string; + chunk_index?: number; + kb_id?: string; + [key: string]: any; + }; + distance?: number; +}; + +export type KnowledgeSearchResponse = { + query: string; + results: KnowledgeSearchResultItem[]; +}; + +export const searchKnowledgeBase = async ( + kbId: string, + query: string, + nResults: number = 5 +): Promise => { + return apiRequest('/knowledge/search', { + method: 'POST', + body: { + kb_id: kbId, + query, + nResults, + }, + }); +}; + export const fetchHistory = async (): Promise => { const [historyResp, assistantsResp] = await Promise.all([ apiRequest<{ list?: AnyRecord[] } | AnyRecord[]>('/history'),