From b6e63e83295a4174af23a048760ec6ec2a79261e Mon Sep 17 00:00:00 2001 From: kenwoodjw Date: Tue, 3 Jun 2025 15:23:50 +0800 Subject: [PATCH] remove debug log and add type check Signed-off-by: kenwoodjw --- api/core/rag/retrieval/dataset_retrieval.py | 29 ++- .../knowledge_retrieval_node.py | 32 +++- api/pyproject.toml | 2 +- api/uv.lock | 8 +- .../condition-list/condition-array.tsx | 174 ++++++++++++------ .../condition-list/condition-item.tsx | 2 + 6 files changed, 170 insertions(+), 77 deletions(-) diff --git a/api/core/rag/retrieval/dataset_retrieval.py b/api/core/rag/retrieval/dataset_retrieval.py index 45f41d522f..dd24227f6c 100644 --- a/api/core/rag/retrieval/dataset_retrieval.py +++ b/api/core/rag/retrieval/dataset_retrieval.py @@ -1048,18 +1048,22 @@ class DatasetRetrieval: filters.append(sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) != value) case "in": if isinstance(value, list | tuple): - # For arrays: check if metadata field (single value) is in the input array + if not value: + return filters + or_conditions = [] for i, v in enumerate(value): - param_key = f"{key_value}_{i}" if isinstance(v, str): - # For string type: exact match with quoted string or_conditions.append(DatasetDocument.doc_metadata[metadata_name] == f'"{v}"') - else: - # For number type: exact match as numeric value + elif isinstance(v, int | float): or_conditions.append( sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) == v ) + or_conditions.append(DatasetDocument.doc_metadata[metadata_name] == str(v)) + else: + v_str = str(v) + or_conditions.append(DatasetDocument.doc_metadata[metadata_name] == f'"{v_str}"') + if or_conditions: filters.append(or_(*or_conditions)) else: @@ -1072,18 +1076,23 @@ class DatasetRetrieval: ) case "not in": if isinstance(value, list | tuple): - # For arrays: check if metadata field (single value) is not in the input array + if not value: + return filters + and_conditions = [] for i, v in enumerate(value): - param_key = f"{key_value}_{i}" if isinstance(v, str): - # For string type: not equal to quoted string and_conditions.append(DatasetDocument.doc_metadata[metadata_name] != f'"{v}"') - else: - # For number type: not equal to numeric value + elif isinstance(v, int | float): + and_conditions.append( sqlalchemy_cast(DatasetDocument.doc_metadata[metadata_name].astext, Float) != v ) + and_conditions.append(DatasetDocument.doc_metadata[metadata_name] != str(v)) + else: + v_str = str(v) + and_conditions.append(DatasetDocument.doc_metadata[metadata_name] != f'"{v_str}"') + if and_conditions: filters.append(and_(*and_conditions)) else: diff --git a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py index 9f633c39db..083b0579bb 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py +++ b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py @@ -527,18 +527,24 @@ class KnowledgeRetrievalNode(LLMNode): filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) != value) case "in": if isinstance(value, list | tuple): - # For arrays: check if metadata field (single value) is in the input array + if not value: + return filters + + # Generate matching conditions for each value, supporting both number and string matching or_conditions = [] for i, v in enumerate(value): - param_key = f"{key_value}_{i}" if isinstance(v, str): - # For string type: exact match with quoted string or_conditions.append(Document.doc_metadata[metadata_name] == f'"{v}"') - else: - # For number type: exact match as numeric value + elif isinstance(v, int | float): + or_conditions.append( sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) == v ) + or_conditions.append(Document.doc_metadata[metadata_name] == str(v)) + else: + v_str = str(v) + or_conditions.append(Document.doc_metadata[metadata_name] == f'"{v_str}"') + if or_conditions: filters.append(or_(*or_conditions)) else: @@ -549,18 +555,24 @@ class KnowledgeRetrievalNode(LLMNode): filters.append(sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) == value) case "not in": if isinstance(value, list | tuple): - # For arrays: check if metadata field (single value) is not in the input array + if not value: # 空数组 + return filters + + # 为每个值生成不匹配条件 and_conditions = [] for i, v in enumerate(value): - param_key = f"{key_value}_{i}" if isinstance(v, str): - # For string type: not equal to quoted string and_conditions.append(Document.doc_metadata[metadata_name] != f'"{v}"') - else: - # For number type: not equal to numeric value + elif isinstance(v, int | float): + and_conditions.append( sqlalchemy_cast(Document.doc_metadata[metadata_name].astext, Float) != v ) + and_conditions.append(Document.doc_metadata[metadata_name] != str(v)) + else: + v_str = str(v) + and_conditions.append(Document.doc_metadata[metadata_name] != f'"{v_str}"') + if and_conditions: filters.append(and_(*and_conditions)) else: diff --git a/api/pyproject.toml b/api/pyproject.toml index 50a765c0e1..204b449eaa 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "chardet~=5.1.0", "flask~=3.1.0", "flask-compress~=1.17", - "flask-cors~=5.0.0", + "flask-cors~=6.0.0", "flask-login~=0.6.3", "flask-migrate~=4.0.7", "flask-restful~=0.3.10", diff --git a/api/uv.lock b/api/uv.lock index a1e1d6146a..596ee35f85 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1392,7 +1392,7 @@ requires-dist = [ { name = "chardet", specifier = "~=5.1.0" }, { name = "flask", specifier = "~=3.1.0" }, { name = "flask-compress", specifier = "~=1.17" }, - { name = "flask-cors", specifier = "~=5.0.0" }, + { name = "flask-cors", specifier = "~=6.0.0" }, { name = "flask-login", specifier = "~=0.6.3" }, { name = "flask-migrate", specifier = "~=4.0.7" }, { name = "flask-restful", specifier = "~=0.3.10" }, @@ -1765,15 +1765,15 @@ wheels = [ [[package]] name = "flask-cors" -version = "5.0.1" +version = "6.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flask" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/d8/667bd90d1ee41c96e938bafe81052494e70b7abd9498c4a0215c103b9667/flask_cors-5.0.1.tar.gz", hash = "sha256:6ccb38d16d6b72bbc156c1c3f192bc435bfcc3c2bc864b2df1eb9b2d97b2403c", size = 11643 } +sdist = { url = "https://files.pythonhosted.org/packages/20/e7/b3c6afdd984672b55dff07482699c688af6c01bd7fd5dd55f9c9d1a88d1c/flask_cors-6.0.0.tar.gz", hash = "sha256:4592c1570246bf7beee96b74bc0adbbfcb1b0318f6ba05c412e8909eceec3393", size = 11875 } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/61/4aea5fb55be1b6f95e604627dc6c50c47d693e39cab2ac086ee0155a0abd/flask_cors-5.0.1-py3-none-any.whl", hash = "sha256:fa5cb364ead54bbf401a26dbf03030c6b18fb2fcaf70408096a572b409586b0c", size = 11296 }, + { url = "https://files.pythonhosted.org/packages/ba/f0/0ee29090016345938f016ee98aa8b5de1c500ee93491dc0c76495848fca1/flask_cors-6.0.0-py3-none-any.whl", hash = "sha256:6332073356452343a8ccddbfec7befdc3fdd040141fe776ec9b94c262f058657", size = 11549 }, ] [[package]] diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-array.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-array.tsx index fe13a4b17e..e7aa1d8b5a 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-array.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-array.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect } from 'react' +import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import ConditionValueMethod from './condition-value-method' import type { ConditionValueMethodProps } from './condition-value-method' @@ -11,14 +11,17 @@ import type { } from '@/app/components/workflow/types' import { VarType } from '@/app/components/workflow/types' import Input from '@/app/components/base/input' +import type { MetadataFilteringVariableType } from '@/app/components/workflow/nodes/knowledge-retrieval/types' type ConditionArrayProps = { - value?: string | string[] | (string | number)[] + value?: string | string[] | (string | number)[] | number onChange: (value?: string | string[] | (string | number)[]) => void nodesOutputVars: NodeOutPutVar[] availableNodes: Node[] isCommonVariable?: boolean commonVariables: { name: string, type: string }[] + fieldType?: MetadataFilteringVariableType + strictTypeChecking?: boolean } & ConditionValueMethodProps const ConditionArray = ({ @@ -30,22 +33,24 @@ const ConditionArray = ({ availableNodes, isCommonVariable, commonVariables, + fieldType, + strictTypeChecking = false, }: ConditionArrayProps) => { const { t } = useTranslation() - const parseValueSelector = useCallback((value?: string | string[] | (string | number)[]): string[] => { + const parseValueSelector = useCallback((value?: string | string[] | (string | number)[] | number): string[] => { if (typeof value !== 'string') return [] - // 支持多种格式: - // 1. {{#nodeId.variable#}} 格式 + // Support multiple formats: + // 1. {{#nodeId.variable#}} format if (value.includes('#')) { const match = value.match(/\{\{#([^#]+)#\}\}/) if (match && match[1]) return match[1].split('.') } - // 2. nodeId.variable 格式(直接格式) + // 2. nodeId.variable format (direct format) if (value.includes('.')) return value.split('.') @@ -54,27 +59,55 @@ const ConditionArray = ({ const currentValueSelector = parseValueSelector(value) - useEffect(() => { - console.log('🔍 ConditionArray Debug:') - console.log(' - valueMethod:', valueMethod) - console.log(' - isCommonVariable:', isCommonVariable) - console.log(' - value:', value) - console.log(' - currentValueSelector:', currentValueSelector) - console.log(' - nodesOutputVars (数组变量):', nodesOutputVars) - console.log(' - availableNodes:', availableNodes) - console.log(' - commonVariables (通用数组变量):', commonVariables) - }, [valueMethod, isCommonVariable, value, currentValueSelector, nodesOutputVars, availableNodes, commonVariables]) - const handleVariableValueChange = useCallback((v: ValueSelector) => { - console.log('🔧 数组变量被选择:', v) onChange(`{{#${v.join('.')}#}}`) }, [onChange]) const handleCommonVariableValueChange = useCallback((v: string) => { - console.log('🔧 通用数组变量被选择:', v) onChange(`{{${v}}}`) }, [onChange]) + // Type compatibility check + const checkTypeCompatibility = useCallback((selectedVariable: any) => { + if (!fieldType || !selectedVariable) return null + + // Get variable type + const variableType = selectedVariable.type || selectedVariable.value_type + + // Define compatibility rules + const compatibilityRules: Record = { + string: ['array[string]', 'array', 'string'], + number: ['array[number]', 'array', 'number'], + select: ['array[string]', 'array', 'string'], + array: ['array[string]', 'array[number]', 'array[object]', 'array'], + time: ['array[string]', 'array', 'string'], // Time field compatibility + } + + const compatibleTypes = compatibilityRules[fieldType as string] || [] + + if (!compatibleTypes.includes(variableType)) { + return { + warning: true, + message: `⚠️ Type mismatch: ${fieldType} field is not recommended to use ${variableType} type variables`, + } + } + + return null + }, [fieldType]) + + // Check if currently selected variable is compatible + const typeCompatibilityCheck = useMemo(() => { + if (valueMethod === 'variable' && currentValueSelector.length > 0) { + // Find currently selected variable information + const selectedVar = nodesOutputVars.find(nodeVar => + nodeVar.nodeId === currentValueSelector[0], + )?.vars.find(v => v.variable === currentValueSelector[1]) + + return checkTypeCompatibility(selectedVar) + } + return null + }, [valueMethod, currentValueSelector, nodesOutputVars, checkTypeCompatibility]) + const handleConstantValueChange = useCallback((inputValue: string) => { // Parse comma-separated values into array if (inputValue.trim() === '') { @@ -87,40 +120,63 @@ const ConditionArray = ({ const trimmed = item.trim() if (trimmed === '') return null - // Try to convert to number if it's a valid number (only if it looks like a pure numeric value) + // Keep natural type detection: pure numbers auto-convert to numbers, otherwise keep as strings if (/^-?\d+(\.\d+)?$/.test(trimmed)) { const numericValue = Number(trimmed) if (!isNaN(numericValue) && isFinite(numericValue)) return numericValue } - // Otherwise keep as string (remove quotes if present) + // Remove quotes (if any) and keep as string return trimmed.replace(/^["']|["']$/g, '') }).filter(item => item !== null) - console.log('🔧 常量数组值被设置:', arrayValues) - console.log('🔧 数组类型检测:', arrayValues.map(v => typeof v)) onChange(arrayValues) }, [onChange]) const displayValue = Array.isArray(value) ? value.map(v => String(v)).join(', ') : (value || '') - // Filter available variables to show only array types - const filteredNodesOutputVars = nodesOutputVars.filter(nodeVar => - nodeVar.vars.some(v => - v.type === VarType.arrayString - || v.type === VarType.arrayNumber - || v.type === VarType.arrayObject - || v.type === VarType.arrayFile - || v.type === VarType.array - || v.type.toString().startsWith('array'), - ), - ) - - const filteredCommonVariables = commonVariables.filter(v => - v.type === 'array' - || v.type.startsWith('array'), - ) + // Filter variables based on strict mode + const filteredNodesOutputVars = useMemo(() => { + const basicFilter = nodesOutputVars.filter(nodeVar => + nodeVar.vars.some(v => + v.type === VarType.arrayString + || v.type === VarType.arrayNumber + || v.type === VarType.arrayObject + || v.type === VarType.arrayFile + || v.type === VarType.array + || v.type.toString().startsWith('array'), + ), + ) + + if (!strictTypeChecking || !fieldType) + return basicFilter + + // Strict mode: only show type-compatible variables + return basicFilter.map(nodeVar => ({ + ...nodeVar, + vars: nodeVar.vars.filter((v) => { + const typeCheck = checkTypeCompatibility(v) + return !typeCheck?.warning + }), + })).filter(nodeVar => nodeVar.vars.length > 0) + }, [nodesOutputVars, strictTypeChecking, fieldType, checkTypeCompatibility]) + + const filteredCommonVariables = useMemo(() => { + const basicFilter = commonVariables.filter(v => + v.type === 'array' + || v.type.startsWith('array'), + ) + + if (!strictTypeChecking || !fieldType) + return basicFilter + + // Strict mode: only show type-compatible variables + return basicFilter.filter((v) => { + const typeCheck = checkTypeCompatibility(v) + return !typeCheck?.warning + }) + }, [commonVariables, strictTypeChecking, fieldType, checkTypeCompatibility]) return (
@@ -131,23 +187,37 @@ const ConditionArray = ({
{ valueMethod === 'variable' && !isCommonVariable && ( - +
+ + {typeCompatibilityCheck?.warning && ( +
+ {typeCompatibilityCheck.message} +
+ )} +
) } { valueMethod === 'variable' && isCommonVariable && ( - +
+ + {typeCompatibilityCheck?.warning && ( +
+ {typeCompatibilityCheck.message} +
+ )} +
) } { diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx index 4675c13834..1876e3358f 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-item.tsx @@ -218,6 +218,8 @@ const ConditionItem = ({ onChange={handleValueChange} isCommonVariable={isCommonVariable} commonVariables={availableCommonArrayVars} + fieldType={currentMetadata?.type} + strictTypeChecking={false} /> ) }