mirror of
https://github.com/langgenius/dify.git
synced 2026-11-13 23:06:40 +08:00
255 lines
8.8 KiB
TypeScript
255 lines
8.8 KiB
TypeScript
import type { AgentKnowledgeRetrievalItem } from './form-state'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { comparisonOperatorNotRequireValue } from '@/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/utils'
|
|
import { MetadataFilteringModeEnum } from '@/app/components/workflow/nodes/knowledge-retrieval/types'
|
|
import { checkoutRerankModelConfiguredInRetrievalSettings } from '@/app/components/workflow/nodes/knowledge-retrieval/utils'
|
|
import { RETRIEVE_TYPE } from '@/types/app'
|
|
|
|
export type KnowledgeValidationIssueCode =
|
|
| 'name_required'
|
|
| 'name_duplicate'
|
|
| 'datasets_required'
|
|
| 'custom_query_required'
|
|
| 'single_model_required'
|
|
| 'rerank_model_required'
|
|
| 'metadata_model_required'
|
|
| 'metadata_conditions_required'
|
|
| 'metadata_value_required'
|
|
| 'space_unavailable'
|
|
| 'binding_invalid'
|
|
|
|
type KnowledgeValidationField = 'name' | 'datasets' | 'query' | 'retrieval' | 'metadata'
|
|
|
|
type KnowledgeValidationIssue = {
|
|
itemId: string
|
|
code: KnowledgeValidationIssueCode
|
|
field: KnowledgeValidationField
|
|
}
|
|
|
|
export type KnowledgeValidationResult = {
|
|
byId: Record<string, Partial<Record<KnowledgeValidationField, KnowledgeValidationIssueCode>>>
|
|
firstIssue?: KnowledgeValidationIssue
|
|
isValid: boolean
|
|
}
|
|
|
|
const getKnowledgeRetrievalConcreteName = (item: AgentKnowledgeRetrievalItem) =>
|
|
item.name?.trim() ?? ''
|
|
|
|
export const getKnowledgeRetrievalSetName = (item: AgentKnowledgeRetrievalItem) =>
|
|
getKnowledgeRetrievalConcreteName(item) || item.id
|
|
|
|
export const useKnowledgeValidationMessage = () => {
|
|
const { t } = useTranslation(['agentV2'])
|
|
const { t: tCommon } = useTranslation(['common', 'modelProvider'])
|
|
const { t: tAppDebug } = useTranslation(['appDebug'])
|
|
const { t: tWorkflow } = useTranslation(['workflowModels'])
|
|
|
|
return (issueCode?: KnowledgeValidationIssueCode, metadataFieldNames?: string) => {
|
|
switch (issueCode) {
|
|
case 'space_unavailable':
|
|
return t(($) => $['agentDetail.configure.knowledgeFs.unavailable'])
|
|
case 'binding_invalid':
|
|
return t(($) => $['agentDetail.configure.knowledgeFs.invalid'])
|
|
case 'name_required':
|
|
return tCommon(($) => $['errorMsg.fieldRequired'], {
|
|
field: t(($) => $['agentDetail.configure.knowledgeRetrieval.dialog.nameLabel']),
|
|
})
|
|
case 'name_duplicate':
|
|
return tAppDebug(($) => $['varKeyError.keyAlreadyExists'], {
|
|
key: t(($) => $['agentDetail.configure.knowledgeRetrieval.dialog.nameLabel']),
|
|
})
|
|
case 'datasets_required':
|
|
return tCommon(($) => $['errorMsg.fieldRequired'], {
|
|
field: t(($) => $['agentDetail.configure.knowledgeRetrieval.dialog.knowledge.label']),
|
|
})
|
|
case 'custom_query_required':
|
|
return tCommon(($) => $['errorMsg.fieldRequired'], {
|
|
field: t(
|
|
($) => $['agentDetail.configure.knowledgeRetrieval.dialog.query.customInputLabel'],
|
|
),
|
|
})
|
|
case 'single_model_required':
|
|
return tCommon(($) => $['errorMsg.fieldRequired'], {
|
|
field: tCommon(($) => $['modelProvider.systemReasoningModel.key'], {
|
|
ns: 'modelProvider',
|
|
}),
|
|
})
|
|
case 'metadata_model_required':
|
|
return t(
|
|
($) => $['agentDetail.configure.knowledgeRetrieval.validation.metadataModelRequired'],
|
|
)
|
|
case 'rerank_model_required':
|
|
return tCommon(($) => $['errorMsg.fieldRequired'], {
|
|
field: tCommon(($) => $['modelProvider.rerankModel.key'], { ns: 'modelProvider' }),
|
|
})
|
|
case 'metadata_conditions_required':
|
|
return tCommon(($) => $['errorMsg.fieldRequired'], {
|
|
field: tWorkflow(($) => $['nodes.knowledgeRetrieval.metadata.panel.conditions'], {
|
|
ns: 'workflowModels',
|
|
}),
|
|
})
|
|
case 'metadata_value_required':
|
|
return tAppDebug(($) => $['errorMessage.valueOfVarRequired'], {
|
|
key:
|
|
metadataFieldNames ??
|
|
tWorkflow(($) => $['nodes.knowledgeRetrieval.metadata.panel.conditions']),
|
|
})
|
|
default:
|
|
return undefined
|
|
}
|
|
}
|
|
}
|
|
|
|
const getKnowledgeDatasetCount = (item: AgentKnowledgeRetrievalItem) =>
|
|
item.selectedDatasets?.length ?? item.datasetRefs?.length ?? 0
|
|
|
|
export const getMetadataConditionsMissingValues = (item: AgentKnowledgeRetrievalItem) => {
|
|
if (item.metadataFilterMode !== MetadataFilteringModeEnum.manual) return []
|
|
return (item.metadataFilteringConditions?.conditions ?? []).filter(
|
|
({ comparison_operator, value }) =>
|
|
!comparisonOperatorNotRequireValue(comparison_operator) &&
|
|
(value === undefined ||
|
|
value === null ||
|
|
(typeof value === 'string' && !value.trim()) ||
|
|
(Array.isArray(value) && value.length === 0)),
|
|
)
|
|
}
|
|
|
|
const getNormalizedKnowledgeName = (item: AgentKnowledgeRetrievalItem) =>
|
|
getKnowledgeRetrievalConcreteName(item).toLowerCase()
|
|
|
|
export const validateKnowledgeRetrievals = (
|
|
retrievals: AgentKnowledgeRetrievalItem[],
|
|
nameScope: AgentKnowledgeRetrievalItem[] = retrievals,
|
|
): KnowledgeValidationResult => {
|
|
const byId: KnowledgeValidationResult['byId'] = {}
|
|
const issues: KnowledgeValidationIssue[] = []
|
|
const nameCounts = new Map<string, number>()
|
|
const spaceCount = retrievals.filter((item) => item.controlSpaceId).length
|
|
|
|
// Hidden bindings still reserve their names, without requiring field validation.
|
|
nameScope.forEach((item) => {
|
|
const normalizedName = getNormalizedKnowledgeName(item)
|
|
if (!normalizedName) return
|
|
|
|
nameCounts.set(normalizedName, (nameCounts.get(normalizedName) ?? 0) + 1)
|
|
})
|
|
|
|
const pushIssue = (issue: KnowledgeValidationIssue) => {
|
|
byId[issue.itemId] ??= {}
|
|
const itemIssues = byId[issue.itemId]
|
|
if (itemIssues) itemIssues[issue.field] ??= issue.code
|
|
issues.push(issue)
|
|
}
|
|
|
|
retrievals.forEach((item) => {
|
|
const setName = getKnowledgeRetrievalConcreteName(item)
|
|
const normalizedName = setName.toLowerCase()
|
|
|
|
if (!setName) {
|
|
pushIssue({
|
|
itemId: item.id,
|
|
code: 'name_required',
|
|
field: 'name',
|
|
})
|
|
} else if ((nameCounts.get(normalizedName) ?? 0) > 1) {
|
|
pushIssue({
|
|
itemId: item.id,
|
|
code: 'name_duplicate',
|
|
field: 'name',
|
|
})
|
|
}
|
|
|
|
if (item.controlSpaceId) {
|
|
if (item.isMissing)
|
|
pushIssue({ itemId: item.id, code: 'space_unavailable', field: 'datasets' })
|
|
if (
|
|
spaceCount > 10 ||
|
|
setName.length > 120 ||
|
|
/[\u0000-\u001F\u007F]/u.test(setName) ||
|
|
(item.description?.length ?? 0) > 2000 ||
|
|
!/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/i.test(item.controlSpaceId) ||
|
|
retrievals.some(
|
|
(other) =>
|
|
other.id !== item.id &&
|
|
(other.controlSpaceId === item.controlSpaceId ||
|
|
other.id.toLowerCase() === normalizedName),
|
|
)
|
|
)
|
|
pushIssue({ itemId: item.id, code: 'binding_invalid', field: 'datasets' })
|
|
return
|
|
}
|
|
|
|
if (!getKnowledgeDatasetCount(item)) {
|
|
pushIssue({
|
|
itemId: item.id,
|
|
code: 'datasets_required',
|
|
field: 'datasets',
|
|
})
|
|
}
|
|
|
|
if (item.queryMode === 'custom' && !item.customQuery?.trim()) {
|
|
pushIssue({
|
|
itemId: item.id,
|
|
code: 'custom_query_required',
|
|
field: 'query',
|
|
})
|
|
}
|
|
|
|
if (
|
|
item.retrievalMode === RETRIEVE_TYPE.oneWay &&
|
|
(!item.singleRetrievalConfig?.model?.provider || !item.singleRetrievalConfig.model.name)
|
|
) {
|
|
pushIssue({
|
|
itemId: item.id,
|
|
code: 'single_model_required',
|
|
field: 'retrieval',
|
|
})
|
|
}
|
|
|
|
if (
|
|
item.metadataFilterMode === MetadataFilteringModeEnum.automatic &&
|
|
(!item.metadataModelConfig?.provider || !item.metadataModelConfig.name)
|
|
) {
|
|
pushIssue({
|
|
itemId: item.id,
|
|
code: 'metadata_model_required',
|
|
field: 'metadata',
|
|
})
|
|
}
|
|
|
|
if (
|
|
item.retrievalMode !== RETRIEVE_TYPE.oneWay &&
|
|
// Saved bindings may have only dataset refs. Without catalog details, an
|
|
// explicitly enabled reranker still requires a model; disabled ones do not.
|
|
(item.selectedDatasets || item.multipleRetrievalConfig?.reranking_enable) &&
|
|
!checkoutRerankModelConfiguredInRetrievalSettings(
|
|
item.selectedDatasets ?? [],
|
|
item.multipleRetrievalConfig,
|
|
)
|
|
) {
|
|
pushIssue({ itemId: item.id, code: 'rerank_model_required', field: 'retrieval' })
|
|
}
|
|
|
|
if (
|
|
item.metadataFilterMode === MetadataFilteringModeEnum.manual &&
|
|
!item.metadataFilteringConditions?.conditions.length
|
|
) {
|
|
pushIssue({
|
|
itemId: item.id,
|
|
code: 'metadata_conditions_required',
|
|
field: 'metadata',
|
|
})
|
|
}
|
|
if (getMetadataConditionsMissingValues(item).length) {
|
|
pushIssue({ itemId: item.id, code: 'metadata_value_required', field: 'metadata' })
|
|
}
|
|
})
|
|
|
|
return {
|
|
byId,
|
|
firstIssue: issues[0],
|
|
isValid: issues.length === 0,
|
|
}
|
|
}
|