import { type FC, useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { useParams } from 'react-router-dom'
import { Row, Col, Space, Switch, Select, InputNumber, Slider, Button, App, Skeleton, Form } from 'antd'
import { ExclamationCircleFilled, CheckCircleFilled } from '@ant-design/icons'
import clsx from 'clsx'
import Card from './components/Card'
import RbCard from '@/components/RbCard/Card'
import RbAlert from '@/components/RbAlert'
import Empty from '@/components/Empty'
import type { ConfigForm, ConfigVo, Variable, TestResult } from './types'
import { getMemoryExtractionConfig, updateMemoryExtractionConfig, pilotRunMemoryExtractionConfig } from '@/api/memory'
import Markdown from '@/components/Markdown'
import { getModelList } from '@/api/models';
import type { Model } from '@/views/ModelManagement/types'
const keys = [
// 'example',
'storageLayerModule',
'arrangementLayerModule'
]
const configList: ConfigVo[] = [
{
type: 'storageLayerModule',
data: [
{
title: 'entityDeduplicationDisambiguation',
list: [
{
label: 'enableLlmDedupBlockwise',
variableName: 'enable_llm_dedup_blockwise',
control: 'button', // switch
type: 'tinyint',
},
{
label: 'enableLlmDisambiguation',
variableName: 'enable_llm_disambiguation',
control: 'button',
type: 'tinyint',
},
{
label: 'tNameStrict',
control: 'slider',
variableName: 't_name_strict',
type: 'decimal',
},
{
label: 'tTypeStrict',
control: 'slider',
variableName: 't_type_strict',
type: 'decimal',
},
{
label: 'tOverall',
control: 'slider',
variableName: 't_overall',
type: 'decimal',
},
]
},
// 语义锚点标注
{
title: 'semanticAnchorAnnotationModule',
list: [
// 句子提取颗粒度
{
label: 'statementGranularity',
variableName: 'statement_granularity',
control: 'slider',
type: 'decimal',
max: 3,
min: 1,
step: 1,
meaning: 'statementGranularityDesc',
},
// 是否包含对话上下文
{
label: 'includeDialogueContext',
variableName: 'include_dialogue_context',
control: 'button', // switch
type: 'tinyint',
meaning: 'includeDialogueContextDesc'
},
// 上下文文字上限
{
label: 'maxDialogueContextChars',
variableName: 'max_context',
control: 'inputNumber',
min: 100,
type: 'decimal',
meaning: 'maxDialogueContextCharsDesc',
},
]
},
]
},
{
type: 'arrangementLayerModule',
data: [
{
title: 'queryMode',
list: [
{
label: 'deepRetrieval',
variableName: 'deep_retrieval',
control: 'button',
type: 'tinyint',
meaning: 'deepRetrievalMeaning',
},
]
},
{
title: 'dataPreprocessing',
list: [
{
label: 'chunkerStrategy',
variableName: 'chunker_strategy',
control: 'select',
type: 'enum',
options: [
{ label: 'recursiveChunker', value: 'RecursiveChunker' }, // 递归分块
{ label: 'tokenChunker', value: 'TokenChunker' }, // token 分块
{ label: 'semanticChunker', value: 'SemanticChunker' }, // 语义分块
{ label: 'neuralChunker', value: 'NeuralChunker' }, // 神经网络分块
{ label: 'hybridChunker', value: 'HybridChunker' }, // 混合分块
{ label: 'llmChunker', value: 'LLMChunker' }, // LLM 分块
{ label: 'sentenceChunker', value: 'SentenceChunker' }, // 句子分块
{ label: 'lateChunker', value: 'LateChunker' }, // 延迟分块
],
meaning: 'chunkerStrategyDesc',
},
]
},
// 智能语义剪枝
{
title: 'intelligentSemanticPruning',
list: [
// 智能语义剪枝功能
{
label: 'intelligentSemanticPruningFunction',
variableName: 'pruning_enabled',
control: 'button',
type: 'tinyint',
meaning: 'intelligentSemanticPruningFunctionDesc',
},
// 智能语义剪枝场景
{
label: 'intelligentSemanticPruningScene',
variableName: 'pruning_scene',
control: 'select',
type: 'enum',
options: [
{ label: 'education', value: 'education' },
{ label: 'online_service', value: 'online_service' },
{ label: 'outbound', value: 'outbound' },
],
meaning: 'intelligentSemanticPruningSceneDesc',
},
// 智能语义剪枝阈值
{
label: 'intelligentSemanticPruningThreshold',
control: 'slider',
variableName: 'pruning_threshold',
type: 'decimal',
max: 0.9,
min: 0,
step: 0.1,
meaning: 'intelligentSemanticPruningThresholdDesc',
},
]
},
// 自我反思引擎
{
title: 'selfReflexionEngine',
list: [
// 是否启用反思引擎
{
label: 'enableSelfReflexion',
variableName: 'enable_self_reflexion',
control: 'button',
type: 'tinyint',
},
// 迭代周期
{
label: 'iterationPeriod',
variableName: 'iteration_period',
control: 'select',
type: 'enum',
options: [
{ label: 'oneHour', value: '1' },
{ label: 'threeHours', value: '3' },
{ label: 'sixHours', value: '6' },
{ label: 'twelveHours', value: '12' },
{ label: 'daily', value: '24' },
],
meaning: 'iterationPeriodDesc',
},
// 反思范围
{
label: 'reflexionRange',
variableName: 'reflexion_range',
control: 'select',
type: 'enum',
options: [
{ label: 'retrieval', value: 'retrieval' },
{ label: 'database', value: 'database' },
],
meaning: 'reflexionRangeDesc',
},
// 反思基线
{
label: 'reflectOnTheBaseline',
variableName: 'baseline',
control: 'select',
type: 'enum',
options: [
{ label: 'basedOnTime', value: 'TIME' },
{ label: 'basedOnFacts', value: 'FACT' },
{ label: 'basedOnFactsAndTime', value: 'TIME-FACT' },
],
},
]
},
]
}
]
const resultObj = {
extractTheNumberOfEntities: 'entities.extracted_count',
numberOfEntityDisambiguation: 'disambiguation.block_count',
memoryFragments: 'memory.chunks',
numberOfRelationalTriples: 'triplets.count'
}
const ConfigDesc: FC<{ config: Variable, className?: string }> = ({config, className}) => {
const { t } = useTranslation();
return (
{config.variableName && {t('memoryExtractionEngine.variableName')}: {config.variableName}}
{config.control && {t('memoryExtractionEngine.control')}: {t(`memoryExtractionEngine.${config.control}`)}}
{config.type && {t('memoryExtractionEngine.type')}: {config.type}}
{config.meaning &&
{t('memoryExtractionEngine.Meaning')}: {t(`memoryExtractionEngine.${config.meaning}`)}
}
)
}
const MemoryExtractionEngine: FC = () => {
const { t } = useTranslation();
const { message } = App.useApp();
const { id } = useParams()
const [expandedKeys, setExpandedKeys] = useState(keys)
const [form] = Form.useForm()
const [modelForm] = Form.useForm()
// const [data, setData] = useState()
const modelValues = Form.useWatch([], modelForm)
const values = Form.useWatch([], form)
const [testResult, setTestResult] = useState(null)
const [loading, setLoading] = useState(false)
const [runLoading, setRunLoading] = useState(false)
const [iterationPeriodDisabled, setIterationPeriodDisabled] = useState(false)
const [modelList, setModelList] = useState([])
useEffect(() => {
if (values?.reflexion_range === 'database') {
form.setFieldValue('iteration_period', 24)
setIterationPeriodDisabled(true)
} else {
setIterationPeriodDisabled(false)
}
}, [values])
const getModels = () => {
const requests = [getModelList({ type: 'llm', pagesize: 100, page: 1 }), getModelList({ type: 'chat', pagesize: 100, page: 1 })]
Promise.all(requests)
.then(responses => {
const [chatRes, modelRes] = responses as { items: Model[] }[]
const chatList = chatRes.items || []
const modelList = modelRes.items || []
setModelList([...chatList, ...modelList])
})
}
const getConfig = () => {
if (!id) {
return
}
getMemoryExtractionConfig(id).then(res => {
const response = res as ConfigForm
const initialValues: ConfigForm = {
...response,
t_name_strict: Number(response.t_name_strict || 0),
t_type_strict: Number(response.t_type_strict || 0),
t_overall: Number(response.t_overall || 0),
}
// setData(initialValues)
form.setFieldsValue(initialValues)
modelForm.setFieldsValue({
llm_id: response.llm_id,
})
})
}
useEffect(() => {
if (id) {
getConfig()
getModels()
const lastResult = localStorage.getItem(`${id}_testResult`)
setTestResult(lastResult ? JSON.parse(lastResult) : null)
}
}, [id])
const handleExpand = (key: string) => {
const newKeys = expandedKeys.includes(key) ? expandedKeys.filter(item => item !== key) : [...expandedKeys, key]
setExpandedKeys(newKeys)
}
const handleSave = () => {
if (!id) {
return
}
console.log('values', values)
setLoading(true)
updateMemoryExtractionConfig({
...values,
...modelValues,
config_id: id,
}).then(() => {
message.success(t('common.saveSuccess'))
})
.finally(() => {
setLoading(false)
})
}
const handleRun = () => {
if (!id) {
return
}
setRunLoading(true)
updateMemoryExtractionConfig({
...values,
...modelValues,
config_id: id,
}).then(() => {
pilotRunMemoryExtractionConfig({
config_id: id,
dialogue_text: t('memoryExtractionEngine.exampleText'),
}).then((res) => {
message.success(t('common.testSuccess'))
const response = res as { extracted_result: TestResult }
setTestResult(response.extracted_result || {})
localStorage.setItem(`${id}_testResult`, JSON.stringify(response.extracted_result || {}))
})
.finally(() => {
setRunLoading(false)
})
})
}
return (
<>
{t('memoryExtractionEngine.title')}
{t('memoryExtractionEngine.subTitle')}
{expandedKeys.includes('example') &&
}
{testResult && Object.keys(testResult).length > 0
? <>
} className="rb:mb-[14px]">
{t('memoryExtractionEngine.warning')}
{resultObj && Object.keys(resultObj).length > 0 &&
{Object.keys(resultObj).map(key => {
const keys = (resultObj as Record
)[key].split('.')
return (
{testResult?.[keys[0] as keyof TestResult]?.[keys[1]]}
{t(`memoryExtractionEngine.${key}`)}
{}
{key === 'extractTheNumberOfEntities'
? t(`memoryExtractionEngine.${key}Desc`, {
num: testResult.dedup.total_merged_count,
exact: testResult.dedup.breakdown.exact,
fuzzy: testResult.dedup.breakdown.fuzzy,
llm: testResult.dedup.breakdown.llm,
})
: key === 'numberOfEntityDisambiguation'
? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.disambiguation.effects?.length, block_count: testResult.disambiguation.block_count })
: key === 'numberOfRelationalTriples'
? t(`memoryExtractionEngine.${key}Desc`, { num: testResult.triplets.count })
:t(`memoryExtractionEngine.${key}Desc`)
}
)})}
}
{testResult?.dedup?.impact && testResult.dedup.impact?.length > 0 &&
{t('memoryExtractionEngine.identifyDuplicates')}
{testResult.dedup.impact.map((item, index) => (
-{t('memoryExtractionEngine.identifyDuplicatesDesc', { ...item })}
))}
} className="rb:mt-[12px]">
{t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })}
}
{testResult?.disambiguation && testResult.disambiguation?.effects?.length > 0 &&
{testResult.disambiguation.effects.map((item, index) => (
0,
})}>
Disagreement Case {index +1}:
-{item.left.name}({item.left.type}) vs {item.right.name}({item.right.type}) →
{item.result}
))}
} className="rb:mt-[12px]">
{t('memoryExtractionEngine.entityDeduplicationImpactDesc', { count: testResult.dedup.impact.length })}
}
{testResult?.core_entities && testResult?.core_entities.length > 0 &&
{testResult.core_entities.map(item => (
{item.type}({item.count})
{item.entities.map((entity, index) => (
-{entity}
))}
))}
}
{testResult?.triplet_samples && testResult?.triplet_samples.length > 0 &&
{testResult.triplet_samples.map((item, index) => (
-({item.subject}, {item.predicate}, {item.object})
))}
} className="rb:mt-[12px]">
{t('memoryExtractionEngine.extractRelationalTriplesDesc', { count: testResult.triplet_samples.length })}
}
>
: loading
?
:
}
>
)
}
export default MemoryExtractionEngine