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')}
({ ...item, label: t(`memoryExtractionEngine.${item.label}`) })) : []} /> } {config.control === 'slider' && <>
-{t(`memoryExtractionEngine.${config.label}`)}
{config.min || 0} {t('memoryExtractionEngine.CurrentValue')}: {values?.[config.variableName as keyof ConfigForm]}
} {config.control === 'inputNumber' && <>
-{t(`memoryExtractionEngine.${config.label}`)}
} ))} ))} ))}
{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