mirror of
https://github.com/langbot-app/LangBot.git
synced 2025-11-26 03:44:58 +08:00
perf(rag): ui and related apis
This commit is contained in:
@@ -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({})
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 = ''
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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.")
|
||||||
|
|||||||
@@ -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: ''
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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} />}
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -255,6 +255,10 @@ const jaJP = {
|
|||||||
embeddingModelDescription:
|
embeddingModelDescription:
|
||||||
'テキストのベクトル化に使用する埋め込みモデルを管理します',
|
'テキストのベクトル化に使用する埋め込みモデルを管理します',
|
||||||
updateTime: '更新日時',
|
updateTime: '更新日時',
|
||||||
|
cannotChangeEmbeddingModel:
|
||||||
|
'知識ベース作成後は埋め込みモデルを変更できません',
|
||||||
|
updateKnowledgeBaseSuccess: '知識ベースの更新に成功しました',
|
||||||
|
updateKnowledgeBaseFailed: '知識ベースの更新に失敗しました',
|
||||||
documentsTab: {
|
documentsTab: {
|
||||||
name: '名前',
|
name: '名前',
|
||||||
status: 'ステータス',
|
status: 'ステータス',
|
||||||
|
|||||||
@@ -247,6 +247,9 @@ const zhHans = {
|
|||||||
selectEmbeddingModel: '选择嵌入模型',
|
selectEmbeddingModel: '选择嵌入模型',
|
||||||
embeddingModelDescription: '用于向量化文本,可在模型配置页面配置',
|
embeddingModelDescription: '用于向量化文本,可在模型配置页面配置',
|
||||||
updateTime: '更新于',
|
updateTime: '更新于',
|
||||||
|
cannotChangeEmbeddingModel: '知识库创建后不可修改嵌入模型',
|
||||||
|
updateKnowledgeBaseSuccess: '知识库更新成功',
|
||||||
|
updateKnowledgeBaseFailed: '知识库更新失败',
|
||||||
documentsTab: {
|
documentsTab: {
|
||||||
name: '名称',
|
name: '名称',
|
||||||
status: '状态',
|
status: '状态',
|
||||||
|
|||||||
Reference in New Issue
Block a user