【功能新增】AI:增加召回测试的实现
This commit is contained in:
parent
1958c2bb9d
commit
45ceacdcac
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
163
src/views/ai/knowledge/knowledge/retrieval/index.vue
Normal file
163
src/views/ai/knowledge/knowledge/retrieval/index.vue
Normal 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>
|
Loading…
Reference in New Issue
Block a user