【功能新增】AI:增加召回测试的实现

This commit is contained in:
YunaiV 2025-03-02 20:53:19 +08:00
parent 1958c2bb9d
commit 45ceacdcac
5 changed files with 215 additions and 17 deletions

View File

@ -30,20 +30,28 @@ export const KnowledgeSegmentApi = {
deleteKnowledgeSegment: async (id: number) => {
return await request.delete({ url: `/ai/knowledge/segment/delete?id=` + id })
},
// 切片内容
splitContent: async (url: string, segmentMaxTokens: number) => {
return await request.get({
url: `/ai/knowledge/segment/split`,
params: { url, segmentMaxTokens }
return await request.get({
url: `/ai/knowledge/segment/split`,
params: { url, segmentMaxTokens }
})
},
// 获取文档处理列表
getKnowledgeSegmentProcessList: async (documentIds: number[]) => {
return await request.get({
url: `/ai/knowledge/segment/get-process-list`,
params: { documentIds: documentIds.join(',') }
return await request.get({
url: `/ai/knowledge/segment/get-process-list`,
params: { documentIds: documentIds.join(',') }
})
},
// 搜索知识库分片
searchKnowledgeSegment: async (params: any) => {
return await request.get({
url: `/ai/knowledge/segment/search`,
params
})
}
}

View File

@ -622,17 +622,18 @@ const remainingRouter: AppRouteRecordRaw[] = [
}
},
{
path: 'console/knowledge/document',
path: 'knowledge/document',
component: () => import('@/views/ai/knowledge/document/index.vue'),
name: 'AiKnowledgeDocument',
meta: {
title: '知识库文档',
icon: 'ep:document',
noCache: false
noCache: false,
activeMenu: '/ai/knowledge'
}
},
{
path: 'console/knowledge/document/create',
path: 'knowledge/document/create',
component: () => import('@/views/ai/knowledge/document/form/index.vue'),
name: 'AiKnowledgeDocumentCreate',
meta: {
@ -640,11 +641,11 @@ const remainingRouter: AppRouteRecordRaw[] = [
icon: 'ep:plus',
noCache: true,
hidden: true,
activeMenu: '/ai/console/knowledge/document'
activeMenu: '/ai/knowledge'
}
},
{
path: 'console/knowledge/document/update',
path: 'knowledge/document/update',
component: () => import('@/views/ai/knowledge/document/form/index.vue'),
name: 'AiKnowledgeDocumentUpdate',
meta: {
@ -652,7 +653,19 @@ const remainingRouter: AppRouteRecordRaw[] = [
icon: 'ep:edit',
noCache: true,
hidden: true,
activeMenu: '/ai/console/knowledge/document'
activeMenu: '/ai/knowledge'
}
},
{
path: 'knowledge/retrieval',
component: () => import('@/views/ai/knowledge/knowledge/retrieval/index.vue'),
name: 'AiKnowledgeRetrieval',
meta: {
title: '文档召回测试',
icon: 'ep:search',
noCache: true,
hidden: true,
activeMenu: '/ai/knowledge'
}
}
]

View File

@ -145,7 +145,6 @@ const splitContent = async (file: any) => {
)
} catch (error) {
console.error('获取分段内容失败:', file, error)
message.error('获取分段内容失败')
} finally {
splitLoading.value = false
}
@ -214,7 +213,6 @@ const handleSave = async () => {
}
} catch (error: any) {
console.error('保存失败:', modelData.value, error)
message.error(error.message)
} finally {
//
submitLoading.value = false

View File

@ -95,6 +95,14 @@
>
文档
</el-button>
<el-button
link
type="primary"
@click="handleRetrieval(scope.row.id)"
v-hasPermi="['ai:knowledge:query']"
>
召回测试
</el-button>
<el-button
link
type="danger"
@ -191,11 +199,19 @@ const handleDelete = async (id: number) => {
const router = useRouter()
const handleDocument = (id: number) => {
router.push({
path: '/ai/console/knowledge/document',
name: 'AiKnowledgeDocument',
query: { knowledgeId: id }
})
}
/** 跳转到文档召回测试页面 */
const handleRetrieval = (id: number) => {
router.push({
name: 'AiKnowledgeRetrieval',
query: { id }
})
}
/** 初始化 **/
onMounted(() => {
getList()

View File

@ -0,0 +1,163 @@
<template>
<div class="flex gap-20px w-full">
<!-- 左侧输入区域 -->
<ContentWrap class="flex-1 min-w-300px">
<div class="mb-15px">
<h3 class="m-0 mb-5px">召回测试</h3>
<div class="text-gray-500 text-14px">根据给定的查询文本测试召回效果</div>
</div>
<div>
<div class="relative mb-10px">
<el-input
v-model="queryParams.content"
type="textarea"
:rows="8"
placeholder="请输入文本"
/>
<div class="absolute bottom-10px right-10px text-gray-400 text-12px">
{{ queryParams.content?.length }} / 200
</div>
</div>
<div class="flex items-center mb-10px">
<span class="w-60px text-gray-500">topK:</span>
<el-input-number v-model="queryParams.topK" :min="1" :max="20" />
</div>
<div class="flex items-center mb-15px">
<span class="w-60px text-gray-500">相似度:</span>
<el-input-number
v-model="queryParams.similarityThreshold"
:min="0"
:max="1"
:precision="2"
:step="0.01"
/>
</div>
<div class="flex justify-end">
<el-button type="primary" @click="getRetrievalResult" :loading="loading">测试</el-button>
</div>
</div>
</ContentWrap>
<!-- 右侧召回结果区域 -->
<ContentWrap class="flex-1 min-w-300px">
<el-empty v-if="loading" description="正在检索中..." />
<div v-else-if="segments.length > 0" class="font-bold mb-15px">
{{ segments.length }} 个召回段落
</div>
<el-empty v-else description="暂无召回结果" />
<div>
<div
v-for="(segment, index) in segments"
:key="index"
class="mb-20px border border-solid border-gray-200 rounded p-15px"
>
<div class="flex justify-between text-12px text-gray-500 mb-5px">
<span>
分段({{ segment.id }}) · {{ segment.contentLength }} 字符数 ·
{{ segment.tokens }} Token
</span>
<span class="px-8px py-4px bg-blue-50 text-blue-500 rounded-full text-12px font-bold">
score: {{ segment.score }}
</span>
</div>
<div
class="bg-gray-50 p-10px rounded mb-10px whitespace-pre-wrap overflow-hidden transition-all duration-100 text-13px"
:class="{
'line-clamp-2 max-h-50px': !segment.expanded,
'max-h-500px': segment.expanded
}"
>
{{ segment.content }}
</div>
<div class="flex justify-between items-center">
<div class="flex items-center text-gray-500 text-13px">
<Icon icon="ep:document" class="mr-5px" />
<span>{{ segment.documentName || '未知文档' }}</span>
</div>
<el-button size="small" @click="toggleExpand(segment)">
{{ segment.expanded ? '收起' : '展开' }}
<Icon :icon="segment.expanded ? 'ep:arrow-up' : 'ep:arrow-down'" />
</el-button>
</div>
</div>
</div>
</ContentWrap>
</div>
</template>
<script setup lang="ts">
import { useMessage } from '@/hooks/web/useMessage'
import { KnowledgeSegmentApi } from '@/api/ai/knowledge/segment'
import { KnowledgeApi } from '@/api/ai/knowledge/knowledge'
/** 文档召回测试 */
defineOptions({ name: 'KnowledgeDocumentRetrieval' })
const message = useMessage() //
const route = useRoute() //
const router = useRouter() //
const loading = ref(false) //
const segments = ref<any[]>([]) //
const queryParams = reactive({
id: undefined,
content: '',
topK: 10,
similarityThreshold: 0.5
})
/** 调用文档召回测试接口 */
const getRetrievalResult = async () => {
if (!queryParams.content) {
message.warning('请输入查询文本')
return
}
loading.value = true
segments.value = []
try {
const data = await KnowledgeSegmentApi.searchKnowledgeSegment({
knowledgeId: queryParams.id,
content: queryParams.content,
topK: queryParams.topK,
similarityThreshold: queryParams.similarityThreshold
})
segments.value = data || []
} catch (error) {
console.error(error)
} finally {
loading.value = false
}
}
/** 展开/收起段落内容 */
const toggleExpand = (segment: any) => {
segment.expanded = !segment.expanded
}
/** 获取知识库信息 */
const getKnowledgeInfo = async (id: number) => {
try {
const knowledge = await KnowledgeApi.getKnowledge(id)
if (knowledge) {
queryParams.topK = knowledge.topK || queryParams.topK
queryParams.similarityThreshold =
knowledge.similarityThreshold || queryParams.similarityThreshold
}
} catch (error) {}
}
/** 初始化 **/
onMounted(() => {
// ID
if (!route.query.id) {
message.error('知识库 ID 不存在,无法进行召回测试')
router.back()
return
}
queryParams.id = route.query.id as any
//
getKnowledgeInfo(queryParams.id as any)
})
</script>