wwf
2 天以前 a430284aa21e3ae1f0d5654e55b2ad2852519cc2
app/components/workflow/nodes/_base/components/variable/utils.ts
@@ -3,7 +3,7 @@
import type { CodeNodeType } from '../../../code/types'
import type { EndNodeType } from '../../../end/types'
import type { AnswerNodeType } from '../../../answer/types'
import { type LLMNodeType, type StructuredOutput, Type } from '../../../llm/types'
import type { LLMNodeType } from '../../../llm/types'
import type { KnowledgeRetrievalNodeType } from '../../../knowledge-retrieval/types'
import type { IfElseNodeType } from '../../../if-else/types'
import type { TemplateTransformNodeType } from '../../../template-transform/types'
@@ -13,16 +13,13 @@
import type { ToolNodeType } from '../../../tool/types'
import type { ParameterExtractorNodeType } from '../../../parameter-extractor/types'
import type { IterationNodeType } from '../../../iteration/types'
import type { LoopNodeType } from '../../../loop/types'
import type { ListFilterNodeType } from '../../../list-operator/types'
import { OUTPUT_FILE_SUB_VARIABLES } from '../../../constants'
import { OUTPUT_FILE_SUB_VARIABLES } from '../../../if-else/default'
import type { DocExtractorNodeType } from '../../../document-extractor/types'
import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types'
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
import type { ConversationVariable, EnvironmentVariable, Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/variable-assigner/types'
import type { Field as StructField } from '@/app/components/workflow/nodes/llm/types'
import {
  HTTP_REQUEST_OUTPUT_STRUCT,
  KNOWLEDGE_RETRIEVAL_OUTPUT_STRUCT,
@@ -35,7 +32,6 @@
} from '@/app/components/workflow/constants'
import type { PromptItem } from '@/models/debug'
import { VAR_REGEX } from '@/config'
import type { AgentNodeType } from '../../../agent/types'
export const isSystemVar = (valueSelector: ValueSelector) => {
  return valueSelector[0] === 'sys' || valueSelector[1] === 'sys'
@@ -57,110 +53,21 @@
  } as any)[type] || VarType.string
}
const structTypeToVarType = (type: Type, isArray?: boolean): VarType => {
  if (isArray) {
    return ({
      [Type.string]: VarType.arrayString,
      [Type.number]: VarType.arrayNumber,
      [Type.object]: VarType.arrayObject,
    } as any)[type] || VarType.string
  }
  return ({
    [Type.string]: VarType.string,
    [Type.number]: VarType.number,
    [Type.boolean]: VarType.boolean,
    [Type.object]: VarType.object,
    [Type.array]: VarType.array,
  } as any)[type] || VarType.string
}
export const varTypeToStructType = (type: VarType): Type => {
  return ({
    [VarType.string]: Type.string,
    [VarType.number]: Type.number,
    [VarType.boolean]: Type.boolean,
    [VarType.object]: Type.object,
    [VarType.array]: Type.array,
  } as any)[type] || Type.string
}
const findExceptVarInStructuredProperties = (properties: Record<string, StructField>, filterVar: (payload: Var, selector: ValueSelector) => boolean): Record<string, StructField> => {
  const res = produce(properties, (draft) => {
    Object.keys(properties).forEach((key) => {
      const item = properties[key]
      const isObj = item.type === Type.object
      const isArray = item.type === Type.array
      const arrayType = item.items?.type
      if (!isObj && !filterVar({
        variable: key,
        type: structTypeToVarType(isArray ? arrayType! : item.type, isArray),
      }, [key])) {
        delete properties[key]
        return
      }
      if (item.type === Type.object && item.properties)
        item.properties = findExceptVarInStructuredProperties(item.properties, filterVar)
    })
    return draft
  })
  return res
}
const findExceptVarInStructuredOutput = (structuredOutput: StructuredOutput, filterVar: (payload: Var, selector: ValueSelector) => boolean): StructuredOutput => {
  const res = produce(structuredOutput, (draft) => {
    const properties = draft.schema.properties
    Object.keys(properties).forEach((key) => {
      const item = properties[key]
      const isObj = item.type === Type.object
      const isArray = item.type === Type.array
      const arrayType = item.items?.type
      if (!isObj && !filterVar({
        variable: key,
        type: structTypeToVarType(isArray ? arrayType! : item.type, isArray),
      }, [key])) {
        delete properties[key]
        return
      }
      if (item.type === Type.object && item.properties)
        item.properties = findExceptVarInStructuredProperties(item.properties, filterVar)
    })
    return draft
  })
  return res
}
const findExceptVarInObject = (obj: any, filterVar: (payload: Var, selector: ValueSelector) => boolean, value_selector: ValueSelector, isFile?: boolean): Var => {
  const { children } = obj
  const isStructuredOutput = !!(children as StructuredOutput)?.schema?.properties
  let childrenResult: Var[] | StructuredOutput | undefined
  if (isStructuredOutput) {
    childrenResult = findExceptVarInStructuredOutput(children, filterVar)
  }
 else if (Array.isArray(children)) {
    childrenResult = children.filter((item: Var) => {
      const { children: itemChildren } = item
      const currSelector = [...value_selector, item.variable]
      if (!itemChildren)
        return filterVar(item, currSelector)
      const filteredObj = findExceptVarInObject(item, filterVar, currSelector, false) // File doesn't contain file children
      return filteredObj.children && (filteredObj.children as Var[])?.length > 0
    })
  }
 else {
    childrenResult = []
  }
  const res: Var = {
    variable: obj.variable,
    type: isFile ? VarType.file : VarType.object,
    children: childrenResult,
  }
    children: children.filter((item: Var) => {
      const { children } = item
      const currSelector = [...value_selector, item.variable]
      if (!children)
        return filterVar(item, currSelector)
      const obj = findExceptVarInObject(item, filterVar, currSelector, false) // File doesn't contains file children
      return obj.children && obj.children?.length > 0
    }),
  }
  return res
}
@@ -230,17 +137,10 @@
    }
    case BlockEnum.LLM: {
      res.vars = [...LLM_OUTPUT_STRUCT]
      if (data.structured_output_enabled && data.structured_output?.schema?.properties && Object.keys(data.structured_output.schema.properties).length > 0) {
        res.vars.push({
          variable: 'structured_output',
          type: VarType.object,
          children: data.structured_output,
        })
      }
      res.vars = LLM_OUTPUT_STRUCT
      break
    }
    case BlockEnum.KnowledgeRetrieval: {
      res.vars = KNOWLEDGE_RETRIEVAL_OUTPUT_STRUCT
      break
@@ -305,7 +205,6 @@
      break
    }
    // eslint-disable-next-line sonarjs/no-duplicated-branches
    case BlockEnum.VariableAggregator: {
      const {
        output_type,
@@ -336,36 +235,7 @@
    }
    case BlockEnum.Tool: {
      const {
        output_schema,
      } = data as ToolNodeType
      if (!output_schema) {
        res.vars = TOOL_OUTPUT_STRUCT
      }
      else {
        const outputSchema: any[] = []
        Object.keys(output_schema.properties).forEach((outputKey) => {
          const output = output_schema.properties[outputKey]
          const dataType = output.type
          outputSchema.push({
            variable: outputKey,
            type: dataType === 'array'
              ? `array[${output.items?.type.slice(0, 1).toLocaleLowerCase()}${output.items?.type.slice(1)}]`
              : `${output.type.slice(0, 1).toLocaleLowerCase()}${output.type.slice(1)}`,
            description: output.description,
            children: output.type === 'object' ? {
              schema: {
                type: 'object',
                properties: output.properties,
              },
            } : undefined,
          })
        })
        res.vars = [
          ...TOOL_OUTPUT_STRUCT,
          ...outputSchema,
        ]
      }
      res.vars = TOOL_OUTPUT_STRUCT
      break
    }
@@ -389,21 +259,6 @@
          type: (data as IterationNodeType).output_type || VarType.arrayString,
        },
      ]
      break
    }
    case BlockEnum.Loop: {
      const { loop_variables } = data as LoopNodeType
      res.isLoop = true
      res.vars = loop_variables?.map((v) => {
        return {
          variable: v.label,
          type: v.var_type,
          isLoopVariable: true,
          nodeId: res.nodeId,
        }
      }) || []
      break
    }
@@ -434,25 +289,6 @@
          variable: 'last_record',
          type: (data as ListFilterNodeType).item_var_type,
        },
      ]
      break
    }
    case BlockEnum.Agent: {
      const payload = data as AgentNodeType
      const outputs: Var[] = []
      Object.keys(payload.output_schema?.properties || {}).forEach((outputKey) => {
        const output = payload.output_schema.properties[outputKey]
        outputs.push({
          variable: outputKey,
          type: output.type === 'array'
            ? `Array[${output.items?.type.slice(0, 1).toLocaleUpperCase()}${output.items?.type.slice(1)}]` as VarType
            : `${output.type.slice(0, 1).toLocaleUpperCase()}${output.type.slice(1)}` as VarType,
        })
      })
      res.vars = [
        ...outputs,
        ...TOOL_OUTPUT_STRUCT,
      ]
      break
    }
@@ -501,7 +337,7 @@
  res.vars = res.vars.filter((v) => {
    const isCurrentMatched = filterVar(v, (() => {
      const variableArr = v.variable.split('.')
      const [first] = variableArr
      const [first, ..._other] = variableArr
      if (first === 'sys' || first === 'env' || first === 'conversation')
        return variableArr
@@ -526,7 +362,7 @@
      return false
    const obj = findExceptVarInObject(isFile ? { ...v, children } : v, filterVar, selector, isFile)
    return obj?.children && ((obj?.children as Var[]).length > 0 || Object.keys((obj?.children as StructuredOutput)?.schema?.properties || {}).length > 0)
    return obj?.children && obj?.children.length > 0
  }).map((v) => {
    const isFile = v.type === VarType.file
@@ -577,20 +413,8 @@
      chatVarList: conversationVariables,
    },
  }
  // Sort nodes in reverse chronological order (most recent first)
  const sortedNodes = [...nodes].sort((a, b) => {
    if (a.data.type === BlockEnum.Start) return 1
    if (b.data.type === BlockEnum.Start) return -1
    if (a.data.type === 'env') return 1
    if (b.data.type === 'env') return -1
    if (a.data.type === 'conversation') return 1
    if (b.data.type === 'conversation') return -1
    // sort nodes by x position
    return (b.position?.x || 0) - (a.position?.x || 0)
  })
  const res = [
    ...sortedNodes.filter(node => SUPPORT_OUTPUT_VARS_NODE.includes(node?.data?.type)),
    ...nodes.filter(node => SUPPORT_OUTPUT_VARS_NODE.includes(node.data.type)),
    ...(environmentVariables.length > 0 ? [ENV_NODE] : []),
    ...((isChatMode && conversationVariables.length > 0) ? [CHAT_VAR_NODE] : []),
  ].map((node) => {
@@ -608,57 +432,6 @@
}: {
  valueSelector: ValueSelector
  beforeNodesOutputVars: NodeOutPutVar[]
}): VarType => {
  const outputVarNodeId = valueSelector[0]
  const isSystem = isSystemVar(valueSelector)
  const targetVar = isSystem ? beforeNodesOutputVars.find(v => v.isStartNode) : beforeNodesOutputVars.find(v => v.nodeId === outputVarNodeId)
  if (!targetVar)
    return VarType.string
  let arrayType: VarType = VarType.string
  let curr: any = targetVar.vars
  if (isSystem) {
    arrayType = curr.find((v: any) => v.variable === (valueSelector).join('.'))?.type
  }
  else {
    for (let i = 1; i < valueSelector.length; i++) {
      const key = valueSelector[i]
      const isLast = i === valueSelector.length - 1
      curr = Array.isArray(curr) ? curr.find(v => v.variable === key) : []
      if (isLast)
      arrayType = curr?.type
      else if (curr?.type === VarType.object || curr?.type === VarType.file)
      curr = curr.children || []
    }
  }
  switch (arrayType as VarType) {
    case VarType.arrayString:
      return VarType.string
    case VarType.arrayNumber:
      return VarType.number
    case VarType.arrayObject:
      return VarType.object
    case VarType.array:
      return VarType.any
    case VarType.arrayFile:
      return VarType.file
    default:
      return VarType.string
  }
}
const getLoopItemType = ({
  valueSelector,
  beforeNodesOutputVars,
}: {
  valueSelector: ValueSelector
  beforeNodesOutputVars: NodeOutPutVar[]
}): VarType => {
  const outputVarNodeId = valueSelector[0]
  const isSystem = isSystemVar(valueSelector)
@@ -707,17 +480,16 @@
  parentNode,
  valueSelector,
  isIterationItem,
  isLoopItem,
  availableNodes,
  isChatMode,
  isConstant,
  environmentVariables = [],
  conversationVariables = [],
}: {
}:
{
  valueSelector: ValueSelector
  parentNode?: Node | null
  isIterationItem?: boolean
  isLoopItem?: boolean
  availableNodes: any[]
  isChatMode: boolean
  isConstant?: boolean
@@ -753,31 +525,11 @@
    if (valueSelector[1] === 'index')
      return VarType.number
  }
  const isLoopInnerVar = parentNode?.data.type === BlockEnum.Loop
  if (isLoopItem) {
    return getLoopItemType({
      valueSelector,
      beforeNodesOutputVars,
    })
  }
  if (isLoopInnerVar) {
    if (valueSelector[1] === 'item') {
      const itemType = getLoopItemType({
        valueSelector: (parentNode?.data as any).iterator_selector || [],
        beforeNodesOutputVars,
      })
      return itemType
    }
    if (valueSelector[1] === 'index')
      return VarType.number
  }
  const isSystem = isSystemVar(valueSelector)
  const isEnv = isENV(valueSelector)
  const isChatVar = isConversationVar(valueSelector)
  const startNode = availableNodes.find((node: any) => {
    return node?.data.type === BlockEnum.Start
    return node.data.type === BlockEnum.Start
  })
  const targetVarNodeId = isSystem ? startNode?.id : valueSelector[0]
@@ -788,33 +540,10 @@
  let type: VarType = VarType.string
  let curr: any = targetVar.vars
  if (isSystem || isEnv || isChatVar) {
    return curr.find((v: any) => v.variable === (valueSelector as ValueSelector).join('.'))?.type
  }
  else {
    const targetVar = curr.find((v: any) => v.variable === valueSelector[1])
    if (!targetVar)
      return VarType.string
    const isStructuredOutputVar = !!targetVar.children?.schema?.properties
    if (isStructuredOutputVar) {
      if (valueSelector.length === 2) { // root
        return VarType.object
      }
      let currProperties = targetVar.children.schema;
      (valueSelector as ValueSelector).slice(2).forEach((key, i) => {
        const isLast = i === valueSelector.length - 3
        if (!currProperties)
          return
        currProperties = currProperties.properties[key]
        if (isLast)
          type = structTypeToVarType(currProperties?.type)
      })
      return type
    }
    (valueSelector as ValueSelector).slice(1).forEach((key, i) => {
      const isLast = i === valueSelector.length - 2
      if (Array.isArray(curr))
@@ -897,9 +626,6 @@
        },
      ],
    }
    const iterationIndex = beforeNodesOutputVars.findIndex(v => v.nodeId === iterationNode?.id)
    if (iterationIndex > -1)
      beforeNodesOutputVars.splice(iterationIndex, 1)
    beforeNodesOutputVars.unshift(iterationVar)
  }
  return beforeNodesOutputVars
@@ -954,7 +680,7 @@
      break
    }
    case BlockEnum.LLM: {
      const payload = data as LLMNodeType
      const payload = (data as LLMNodeType)
      const isChatModel = payload.model?.mode === 'chat'
      let prompts: string[] = []
      if (isChatModel) {
@@ -992,19 +718,19 @@
      break
    }
    case BlockEnum.QuestionClassifier: {
      const payload = data as QuestionClassifierNodeType
      const payload = (data as QuestionClassifierNodeType)
      res = [payload.query_variable_selector]
      const varInInstructions = matchNotSystemVars([payload.instruction || ''])
      res.push(...varInInstructions)
      break
    }
    case BlockEnum.HttpRequest: {
      const payload = data as HttpNodeType
      const payload = (data as HttpNodeType)
      res = matchNotSystemVars([payload.url, payload.headers, payload.params, typeof payload.body.data === 'string' ? payload.body.data : payload.body.data.map(d => d.value).join('')])
      break
    }
    case BlockEnum.Tool: {
      const payload = data as ToolNodeType
      const payload = (data as ToolNodeType)
      const mixVars = matchNotSystemVars(Object.keys(payload.tool_parameters)?.filter(key => payload.tool_parameters[key].type === ToolVarType.mixed).map(key => payload.tool_parameters[key].value) as string[])
      const vars = Object.keys(payload.tool_parameters).filter(key => payload.tool_parameters[key].type === ToolVarType.variable).map(key => payload.tool_parameters[key].value as string) || []
      res = [...(mixVars as ValueSelector[]), ...(vars as any)]
@@ -1022,7 +748,7 @@
    }
    case BlockEnum.ParameterExtractor: {
      const payload = data as ParameterExtractorNodeType
      const payload = (data as ParameterExtractorNodeType)
      res = [payload.query]
      const varInInstructions = matchNotSystemVars([payload.instruction || ''])
      res.push(...varInInstructions)
@@ -1034,31 +760,8 @@
      break
    }
    case BlockEnum.Loop: {
      const payload = data as LoopNodeType
      res = payload.break_conditions?.map((c) => {
        return c.variable_selector || []
      }) || []
      break
    }
    case BlockEnum.ListFilter: {
      res = [(data as ListFilterNodeType).variable]
      break
    }
    case BlockEnum.Agent: {
      const payload = data as AgentNodeType
      const valueSelectors: ValueSelector[] = []
      if (!payload.agent_parameters)
        break
      Object.keys(payload.agent_parameters || {}).forEach((key) => {
        const { value } = payload.agent_parameters![key]
        if (typeof value === 'string')
          valueSelectors.push(...matchNotSystemVars([value]))
      })
      res = valueSelectors
      break
    }
  }
@@ -1072,7 +775,7 @@
  let res: string | string[] = ''
  switch (type) {
    case BlockEnum.LLM: {
      const payload = data as LLMNodeType
      const payload = (data as LLMNodeType)
      res = [`#${valueSelector.join('.')}#`]
      if (payload.context?.variable_selector.join('.') === valueSelector.join('.'))
        res.push('#context#')
@@ -1294,7 +997,6 @@
        }
        break
      }
      // eslint-disable-next-line sonarjs/no-duplicated-branches
      case BlockEnum.VariableAggregator: {
        const payload = data as VariableAssignerNodeType
        if (payload.variables) {
@@ -1320,17 +1022,6 @@
        break
      }
      case BlockEnum.Loop: {
        const payload = data as LoopNodeType
        if (payload.break_conditions) {
          payload.break_conditions = payload.break_conditions.map((c) => {
            if (c.variable_selector?.join('.') === oldVarSelector.join('.'))
              c.variable_selector = newVarSelector
            return c
          })
        }
        break
      }
      case BlockEnum.ListFilter: {
        const payload = data as ListFilterNodeType
        if (payload.variable.join('.') === oldVarSelector.join('.'))
@@ -1341,28 +1032,15 @@
  })
  return newNode
}
const varToValueSelectorList = (v: Var, parentValueSelector: ValueSelector, res: ValueSelector[]) => {
  if (!v.variable)
    return
  res.push([...parentValueSelector, v.variable])
  const isStructuredOutput = !!(v.children as StructuredOutput)?.schema?.properties
  if ((v.children as Var[])?.length > 0) {
    (v.children as Var[]).forEach((child) => {
  if (v.children && v.children.length > 0) {
    v.children.forEach((child) => {
      varToValueSelectorList(child, [...parentValueSelector, v.variable], res)
    })
  }
  if (isStructuredOutput) {
    Object.keys((v.children as StructuredOutput)?.schema?.properties || {}).forEach((key) => {
      const type = (v.children as StructuredOutput)?.schema?.properties[key].type
      const isArray = type === Type.array
      const arrayType = (v.children as StructuredOutput)?.schema?.properties[key].items?.type
      varToValueSelectorList({
        variable: key,
        type: structTypeToVarType(isArray ? arrayType! : type, isArray),
      }, [...parentValueSelector, v.variable], res)
    })
  }
}
@@ -1398,16 +1076,7 @@
    }
    case BlockEnum.LLM: {
      const vars = [...LLM_OUTPUT_STRUCT]
      const llmNodeData = data as LLMNodeType
      if (llmNodeData.structured_output_enabled && llmNodeData.structured_output?.schema?.properties && Object.keys(llmNodeData.structured_output.schema.properties).length > 0) {
        vars.push({
          variable: 'structured_output',
          type: VarType.object,
          children: llmNodeData.structured_output,
        })
      }
      varsToValueSelectorList(vars, [id], res)
      varsToValueSelectorList(LLM_OUTPUT_STRUCT, [id], res)
      break
    }
@@ -1470,11 +1139,6 @@
    }
    case BlockEnum.Iteration: {
      res.push([id, 'output'])
      break
    }
    case BlockEnum.Loop: {
      res.push([id, 'output'])
      break
    }