perf(rag): ui and related apis

This commit is contained in:
Junyan Qin
2025-07-18 00:37:22 +08:00
parent bb672d8f46
commit b61bd36b14
17 changed files with 89 additions and 32 deletions

View File

@@ -20,7 +20,7 @@ class KnowledgeBaseRouterGroup(group.RouterGroup):
@self.route( @self.route(
'/<knowledge_base_uuid>', '/<knowledge_base_uuid>',
methods=['GET', 'DELETE'], methods=['GET', 'DELETE', 'PUT'],
) )
async def handle_specific_knowledge_base(knowledge_base_uuid: str) -> quart.Response: async def handle_specific_knowledge_base(knowledge_base_uuid: str) -> quart.Response:
if quart.request.method == 'GET': if quart.request.method == 'GET':
@@ -34,6 +34,12 @@ class KnowledgeBaseRouterGroup(group.RouterGroup):
'base': knowledge_base, 'base': knowledge_base,
} }
) )
elif quart.request.method == 'PUT':
json_data = await quart.request.json
await self.ap.knowledge_service.update_knowledge_base(knowledge_base_uuid, json_data)
return self.success({})
elif quart.request.method == 'DELETE': elif quart.request.method == 'DELETE':
await self.ap.knowledge_service.delete_knowledge_base(knowledge_base_uuid) await self.ap.knowledge_service.delete_knowledge_base(knowledge_base_uuid)
return self.success({}) return self.success({})

View File

@@ -47,12 +47,18 @@ class KnowledgeService:
async def update_knowledge_base(self, kb_uuid: str, kb_data: dict) -> None: async def update_knowledge_base(self, kb_uuid: str, kb_data: dict) -> None:
"""更新知识库""" """更新知识库"""
if 'uuid' in kb_data:
del kb_data['uuid']
if 'embedding_model_uuid' in kb_data:
del kb_data['embedding_model_uuid']
await self.ap.persistence_mgr.execute_async( await self.ap.persistence_mgr.execute_async(
sqlalchemy.update(persistence_rag.KnowledgeBase) sqlalchemy.update(persistence_rag.KnowledgeBase)
.values(kb_data) .values(kb_data)
.where(persistence_rag.KnowledgeBase.uuid == kb_uuid) .where(persistence_rag.KnowledgeBase.uuid == kb_uuid)
) )
await self.ap.rag_mgr.remove_knowledge_base(kb_uuid) await self.ap.rag_mgr.remove_knowledge_base_from_runtime(kb_uuid)
kb = await self.get_knowledge_base(kb_uuid) kb = await self.get_knowledge_base(kb_uuid)
@@ -91,7 +97,7 @@ class KnowledgeService:
async def delete_knowledge_base(self, kb_uuid: str) -> None: async def delete_knowledge_base(self, kb_uuid: str) -> None:
"""删除知识库""" """删除知识库"""
await self.ap.rag_mgr.remove_knowledge_base(kb_uuid) await self.ap.rag_mgr.delete_knowledge_base(kb_uuid)
await self.ap.persistence_mgr.execute_async( await self.ap.persistence_mgr.execute_async(
sqlalchemy.delete(persistence_rag.KnowledgeBase).where(persistence_rag.KnowledgeBase.uuid == kb_uuid) sqlalchemy.delete(persistence_rag.KnowledgeBase).where(persistence_rag.KnowledgeBase.uuid == kb_uuid)

View File

@@ -33,6 +33,9 @@ class LocalAgentRunner(runner.RequestRunner):
kb_uuid = query.pipeline_config['ai']['local-agent']['knowledge-base'] kb_uuid = query.pipeline_config['ai']['local-agent']['knowledge-base']
if kb_uuid == '__none__':
kb_uuid = None
user_message = copy.deepcopy(query.user_message) user_message = copy.deepcopy(query.user_message)
user_message_text = '' user_message_text = ''

View File

@@ -198,7 +198,13 @@ class RAGManager:
return kb return kb
return None return None
async def remove_knowledge_base(self, kb_uuid: str): async def remove_knowledge_base_from_runtime(self, kb_uuid: str):
for kb in self.knowledge_bases:
if kb.knowledge_base_entity.uuid == kb_uuid:
self.knowledge_bases.remove(kb)
return
async def delete_knowledge_base(self, kb_uuid: str):
for kb in self.knowledge_bases: for kb in self.knowledge_bases:
if kb.knowledge_base_entity.uuid == kb_uuid: if kb.knowledge_base_entity.uuid == kb_uuid:
await kb.dispose() await kb.dispose()

View File

@@ -5,6 +5,7 @@ from chromadb import PersistentClient
from pkg.vector.vdb import VectorDatabase from pkg.vector.vdb import VectorDatabase
from pkg.core import app from pkg.core import app
import chromadb import chromadb
import chromadb.errors
class ChromaVectorDatabase(VectorDatabase): class ChromaVectorDatabase(VectorDatabase):
@@ -51,5 +52,10 @@ class ChromaVectorDatabase(VectorDatabase):
async def delete_collection(self, collection: str): async def delete_collection(self, collection: str):
if collection in self._collections: if collection in self._collections:
del self._collections[collection] del self._collections[collection]
await asyncio.to_thread(self.client.delete_collection, name=collection)
try:
await asyncio.to_thread(self.client.delete_collection, name=collection)
except chromadb.errors.NotFoundError:
self.ap.logger.warning(f"Chroma collection '{collection}' not found.")
return
self.ap.logger.info(f"Chroma collection '{collection}' deleted.") self.ap.logger.info(f"Chroma collection '{collection}' deleted.")

View File

@@ -72,6 +72,9 @@ stages:
label: label:
en_US: Knowledge Base en_US: Knowledge Base
zh_Hans: 知识库 zh_Hans: 知识库
description:
en_US: Configure the knowledge base to use for the agent, if not selected, the agent will directly use the LLM to reply
zh_Hans: 配置用于提升回复质量的知识库,若不选择,则直接使用大模型回复
type: knowledge-base-selector type: knowledge-base-selector
required: false required: false
default: '' default: ''

View File

@@ -51,7 +51,7 @@ export default function DynamicFormItemComponent({
}); });
} }
}, [config.type]); }, [config.type]);
useEffect(() => { useEffect(() => {
if (config.type === DynamicFormItemType.KNOWLEDGE_BASE_SELECTOR) { if (config.type === DynamicFormItemType.KNOWLEDGE_BASE_SELECTOR) {
httpClient httpClient
@@ -272,28 +272,11 @@ export default function DynamicFormItemComponent({
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
{/* <SelectItem value="">{t('knowledge.empty')}</SelectItem> */} <SelectItem value="__none__">{t('knowledge.empty')}</SelectItem>
{knowledgeBases.map((base) => ( {knowledgeBases.map((base) => (
<HoverCard key={base.uuid} openDelay={0} closeDelay={0}> <SelectItem key={base.uuid} value={base.uuid ?? ''}>
<HoverCardTrigger asChild> {base.name}
<SelectItem value={base.uuid ?? ''}>{base.name}</SelectItem> </SelectItem>
</HoverCardTrigger>
<HoverCardContent
className="w-80 data-[state=open]:animate-none data-[state=closed]:animate-none"
align="end"
side="right"
sideOffset={10}
>
<div className="space-y-2">
<div className="flex items-center gap-2">
<h4 className="font-medium">{base.name}</h4>
</div>
<p className="text-sm text-muted-foreground">
{base.description}
</p>
</div>
</HoverCardContent>
</HoverCard>
))} ))}
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>

View File

@@ -36,6 +36,7 @@ interface KBDetailDialogProps {
onFormCancel: () => void; onFormCancel: () => void;
onKbDeleted: () => void; onKbDeleted: () => void;
onNewKbCreated: (kbId: string) => void; onNewKbCreated: (kbId: string) => void;
onKbUpdated: (kbId: string) => void;
} }
export default function KBDetailDialog({ export default function KBDetailDialog({
@@ -46,6 +47,7 @@ export default function KBDetailDialog({
onFormCancel, onFormCancel,
onKbDeleted, onKbDeleted,
onNewKbCreated, onNewKbCreated,
onKbUpdated,
}: KBDetailDialogProps) { }: KBDetailDialogProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [kbId, setKbId] = useState<string | undefined>(propKbId); const [kbId, setKbId] = useState<string | undefined>(propKbId);
@@ -111,6 +113,7 @@ export default function KBDetailDialog({
onFormCancel={onFormCancel} onFormCancel={onFormCancel}
onKbDeleted={onKbDeleted} onKbDeleted={onKbDeleted}
onNewKbCreated={onNewKbCreated} onNewKbCreated={onNewKbCreated}
onKbUpdated={onKbUpdated}
/> />
)} )}
{activeMenu === 'documents' && <div>documents</div>} {activeMenu === 'documents' && <div>documents</div>}
@@ -185,6 +188,7 @@ export default function KBDetailDialog({
onFormCancel={onFormCancel} onFormCancel={onFormCancel}
onKbDeleted={onKbDeleted} onKbDeleted={onKbDeleted}
onNewKbCreated={onNewKbCreated} onNewKbCreated={onNewKbCreated}
onKbUpdated={onKbUpdated}
/> />
)} )}
{activeMenu === 'documents' && <KBDoc kbId={kbId} />} {activeMenu === 'documents' && <KBDoc kbId={kbId} />}

View File

@@ -28,7 +28,7 @@ export default function KBDoc({ kbId }: { kbId: string }) {
setDocumentsList( setDocumentsList(
resp.files.map((file: KnowledgeBaseFile) => { resp.files.map((file: KnowledgeBaseFile) => {
return { return {
id: file.id, uuid: file.uuid,
name: file.file_name, name: file.file_name,
status: file.status, status: file.status,
}; };

View File

@@ -14,7 +14,7 @@ import {
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export type DocumentFile = { export type DocumentFile = {
id: string; uuid: string;
name: string; name: string;
status: string; status: string;
}; };
@@ -52,7 +52,7 @@ export const columns = (
{t('knowledge.documentsTab.actions')} {t('knowledge.documentsTab.actions')}
</DropdownMenuLabel> </DropdownMenuLabel>
<DropdownMenuItem onClick={() => onDelete(document.id)}> <DropdownMenuItem onClick={() => onDelete(document.uuid)}>
{t('knowledge.documentsTab.delete')} {t('knowledge.documentsTab.delete')}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View File

@@ -24,6 +24,7 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { KnowledgeBase } from '@/app/infra/entities/api'; import { KnowledgeBase } from '@/app/infra/entities/api';
import { toast } from 'sonner';
const getFormSchema = (t: (key: string) => string) => const getFormSchema = (t: (key: string) => string) =>
z.object({ z.object({
@@ -42,6 +43,7 @@ export default function KBForm({
onFormCancel, onFormCancel,
onKbDeleted, onKbDeleted,
onNewKbCreated, onNewKbCreated,
onKbUpdated,
}: { }: {
initKbId?: string; initKbId?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -49,6 +51,7 @@ export default function KBForm({
onFormCancel: () => void; onFormCancel: () => void;
onKbDeleted: () => void; onKbDeleted: () => void;
onNewKbCreated: (kbId: string) => void; onNewKbCreated: (kbId: string) => void;
onKbUpdated: (kbId: string) => void;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const formSchema = getFormSchema(t); const formSchema = getFormSchema(t);
@@ -114,6 +117,17 @@ export default function KBForm({
description: data.description, description: data.description,
embedding_model_uuid: data.embeddingModelUUID, embedding_model_uuid: data.embeddingModelUUID,
}; };
httpClient
.updateKnowledgeBase(initKbId, updateKb)
.then((res) => {
console.log('update knowledge base success', res);
onKbUpdated(res.uuid);
toast.success(t('knowledge.updateKnowledgeBaseSuccess'));
})
.catch((err) => {
console.error('update knowledge base failed', err);
toast.error(t('knowledge.updateKnowledgeBaseFailed'));
});
} else { } else {
// create knowledge base // create knowledge base
const newKb: KnowledgeBase = { const newKb: KnowledgeBase = {
@@ -186,6 +200,7 @@ export default function KBForm({
<FormControl> <FormControl>
<div className="relative"> <div className="relative">
<Select <Select
disabled={!!initKbId}
onValueChange={(value) => { onValueChange={(value) => {
field.onChange(value); field.onChange(value);
console.log('value', value); console.log('value', value);
@@ -210,7 +225,9 @@ export default function KBForm({
</div> </div>
</FormControl> </FormControl>
<FormDescription> <FormDescription>
{t('knowledge.embeddingModelDescription')} {initKbId
? t('knowledge.cannotChangeEmbeddingModel')
: t('knowledge.embeddingModelDescription')}
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>

View File

@@ -81,6 +81,10 @@ export default function KnowledgePage() {
setDetailDialogOpen(false); setDetailDialogOpen(false);
}; };
const handleKbUpdated = (kbId: string) => {
getKnowledgeBaseList();
};
return ( return (
<div> <div>
<KBDetailDialog <KBDetailDialog
@@ -91,6 +95,7 @@ export default function KnowledgePage() {
onFormCancel={handleFormCancel} onFormCancel={handleFormCancel}
onKbDeleted={handleKbDeleted} onKbDeleted={handleKbDeleted}
onNewKbCreated={handleNewKbCreated} onNewKbCreated={handleNewKbCreated}
onKbUpdated={handleKbUpdated}
/> />
<div className={styles.knowledgeListContainer}> <div className={styles.knowledgeListContainer}>

View File

@@ -164,7 +164,7 @@ export interface ApiRespKnowledgeBaseFiles {
} }
export interface KnowledgeBaseFile { export interface KnowledgeBaseFile {
id: string; uuid: string;
file_name: string; file_name: string;
status: string; status: string;
} }

View File

@@ -459,6 +459,13 @@ class HttpClient {
return this.post('/api/v1/knowledge/bases', base); return this.post('/api/v1/knowledge/bases', base);
} }
public updateKnowledgeBase(
uuid: string,
base: KnowledgeBase,
): Promise<{ uuid: string }> {
return this.put(`/api/v1/knowledge/bases/${uuid}`, base);
}
public uploadKnowledgeBaseFile( public uploadKnowledgeBaseFile(
uuid: string, uuid: string,
file_id: string, file_id: string,

View File

@@ -253,6 +253,10 @@ const enUS = {
embeddingModelDescription: embeddingModelDescription:
'Used to vectorize the text, you can configure it in the Models page', 'Used to vectorize the text, you can configure it in the Models page',
updateTime: 'Updated ', updateTime: 'Updated ',
cannotChangeEmbeddingModel:
'Knowledge base created cannot be modified embedding model',
updateKnowledgeBaseSuccess: 'Knowledge base updated successfully',
updateKnowledgeBaseFailed: 'Knowledge base update failed',
documentsTab: { documentsTab: {
name: 'Name', name: 'Name',
status: 'Status', status: 'Status',

View File

@@ -255,6 +255,10 @@ const jaJP = {
embeddingModelDescription: embeddingModelDescription:
'テキストのベクトル化に使用する埋め込みモデルを管理します', 'テキストのベクトル化に使用する埋め込みモデルを管理します',
updateTime: '更新日時', updateTime: '更新日時',
cannotChangeEmbeddingModel:
'知識ベース作成後は埋め込みモデルを変更できません',
updateKnowledgeBaseSuccess: '知識ベースの更新に成功しました',
updateKnowledgeBaseFailed: '知識ベースの更新に失敗しました',
documentsTab: { documentsTab: {
name: '名前', name: '名前',
status: 'ステータス', status: 'ステータス',

View File

@@ -247,6 +247,9 @@ const zhHans = {
selectEmbeddingModel: '选择嵌入模型', selectEmbeddingModel: '选择嵌入模型',
embeddingModelDescription: '用于向量化文本,可在模型配置页面配置', embeddingModelDescription: '用于向量化文本,可在模型配置页面配置',
updateTime: '更新于', updateTime: '更新于',
cannotChangeEmbeddingModel: '知识库创建后不可修改嵌入模型',
updateKnowledgeBaseSuccess: '知识库更新成功',
updateKnowledgeBaseFailed: '知识库更新失败',
documentsTab: { documentsTab: {
name: '名称', name: '名称',
status: '状态', status: '状态',