mirror of
https://github.com/langgenius/dify.git
synced 2026-02-22 05:01:56 -05:00
refactor: Update variable syntax to support agent context markers
Extend variable pattern matching to support both `#` and `@` markers, with `@` specifically used for agent context variables. Update regex patterns, text processing logic, and add sub-graph persistence for agent variable handling.
This commit is contained in:
@@ -14,7 +14,6 @@ import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import {
|
||||
|
||||
useAppTriggers,
|
||||
useInvalidateAppTriggers,
|
||||
useUpdateTriggerStatus,
|
||||
|
||||
@@ -38,13 +38,16 @@ export const getInputVars = (text: string): ValueSelector[] => {
|
||||
if (!text || typeof text !== 'string')
|
||||
return []
|
||||
|
||||
const allVars = text.match(/\{\{#([^#]*)#\}\}/g)
|
||||
const allVars = text.match(/\{\{[@#]([^@#]*)[@#]\}\}/g)
|
||||
if (allVars && allVars?.length > 0) {
|
||||
// {{#context#}}, {{#query#}} is not input vars
|
||||
const inputVars = allVars
|
||||
.filter(item => item.includes('.'))
|
||||
.map((item) => {
|
||||
const valueSelector = item.replace('{{#', '').replace('#}}', '').split('.')
|
||||
const valueSelector = item
|
||||
.replace(/^\{\{[@#]/, '')
|
||||
.replace(/[@#]\}\}$/, '')
|
||||
.split('.')
|
||||
if (valueSelector[1] === 'sys' && /^\d+$/.test(valueSelector[0]))
|
||||
return valueSelector.slice(1)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { LexicalNode, NodeKey, SerializedLexicalNode } from 'lexical'
|
||||
import type { GetVarType, WorkflowVariableBlockType } from '../../types'
|
||||
import type { Var } from '@/app/components/workflow/types'
|
||||
import { DecoratorNode } from 'lexical'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import WorkflowVariableBlockComponent from './component'
|
||||
|
||||
export type WorkflowNodesMap = WorkflowVariableBlockType['workflowNodesMap']
|
||||
@@ -120,7 +121,11 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
|
||||
}
|
||||
|
||||
getTextContent(): string {
|
||||
return `{{#${this.getVariables().join('.')}#}}`
|
||||
const variables = this.getVariables()
|
||||
const node = this.getWorkflowNodesMap()?.[variables[0]]
|
||||
const isAgentContextVariable = node?.type === BlockEnum.Agent && variables[variables.length - 1] === 'context'
|
||||
const marker = isAgentContextVariable ? '@' : '#'
|
||||
return `{{${marker}${variables.join('.')}${marker}}}`
|
||||
}
|
||||
}
|
||||
export function $createWorkflowVariableBlockNode(variables: string[], workflowNodesMap: WorkflowNodesMap, getVarType?: GetVarType, environmentVariables?: Var[], conversationVariables?: Var[], ragVariables?: Var[]): WorkflowVariableBlockNode {
|
||||
|
||||
@@ -1,86 +1,97 @@
|
||||
import type { SubGraphProps } from '../types'
|
||||
import type { LLMNodeType } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
|
||||
import type { Edge, Node } from '@/app/components/workflow/types'
|
||||
import type { Edge, Node, ValueSelector } from '@/app/components/workflow/types'
|
||||
import { useMemo } from 'react'
|
||||
import { BlockEnum, PromptRole } from '@/app/components/workflow/types'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
|
||||
const SUBGRAPH_SOURCE_NODE_ID = 'subgraph-source'
|
||||
const SUBGRAPH_LLM_NODE_ID = 'subgraph-llm'
|
||||
export const SUBGRAPH_SOURCE_NODE_ID = 'subgraph-source'
|
||||
export const SUBGRAPH_LLM_NODE_ID = 'subgraph-llm'
|
||||
|
||||
export const getSubGraphInitialNodes = (
|
||||
sourceVariable: ValueSelector,
|
||||
agentName: string,
|
||||
): Node[] => {
|
||||
const sourceVarName = sourceVariable.length > 1
|
||||
? sourceVariable.slice(1).join('.')
|
||||
: 'output'
|
||||
|
||||
const startNode: Node<StartNodeType> = {
|
||||
id: SUBGRAPH_SOURCE_NODE_ID,
|
||||
type: 'custom',
|
||||
position: { x: 100, y: 150 },
|
||||
data: {
|
||||
type: BlockEnum.Start,
|
||||
title: `${agentName}: ${sourceVarName}`,
|
||||
desc: 'Source variable from agent',
|
||||
_connectedSourceHandleIds: ['source'],
|
||||
_connectedTargetHandleIds: [],
|
||||
variables: [],
|
||||
},
|
||||
}
|
||||
|
||||
const llmNode: Node<LLMNodeType> = {
|
||||
id: SUBGRAPH_LLM_NODE_ID,
|
||||
type: 'custom',
|
||||
position: { x: 450, y: 150 },
|
||||
data: {
|
||||
type: BlockEnum.LLM,
|
||||
title: 'LLM',
|
||||
desc: 'Transform the output',
|
||||
_connectedSourceHandleIds: [],
|
||||
_connectedTargetHandleIds: ['target'],
|
||||
model: {
|
||||
provider: '',
|
||||
name: '',
|
||||
mode: AppModeEnum.CHAT,
|
||||
completion_params: {
|
||||
temperature: 0.7,
|
||||
},
|
||||
},
|
||||
prompt_template: [{
|
||||
role: PromptRole.system,
|
||||
text: '',
|
||||
}],
|
||||
context: {
|
||||
enabled: false,
|
||||
variable_selector: [],
|
||||
},
|
||||
vision: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return [startNode, llmNode]
|
||||
}
|
||||
|
||||
export const getSubGraphInitialEdges = (): Edge[] => {
|
||||
return [
|
||||
{
|
||||
id: `${SUBGRAPH_SOURCE_NODE_ID}-${SUBGRAPH_LLM_NODE_ID}`,
|
||||
source: SUBGRAPH_SOURCE_NODE_ID,
|
||||
sourceHandle: 'source',
|
||||
target: SUBGRAPH_LLM_NODE_ID,
|
||||
targetHandle: 'target',
|
||||
type: 'custom',
|
||||
data: {
|
||||
sourceType: BlockEnum.Start,
|
||||
targetType: BlockEnum.LLM,
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export const useSubGraphInit = (props: SubGraphProps) => {
|
||||
const { sourceVariable, agentName } = props
|
||||
|
||||
const initialNodes = useMemo((): Node[] => {
|
||||
const sourceVarName = sourceVariable.length > 1
|
||||
? sourceVariable.slice(1).join('.')
|
||||
: 'output'
|
||||
|
||||
const startNode: Node<StartNodeType> = {
|
||||
id: SUBGRAPH_SOURCE_NODE_ID,
|
||||
type: 'custom',
|
||||
position: { x: 100, y: 150 },
|
||||
data: {
|
||||
type: BlockEnum.Start,
|
||||
title: `${agentName}: ${sourceVarName}`,
|
||||
desc: 'Source variable from agent',
|
||||
_connectedSourceHandleIds: ['source'],
|
||||
_connectedTargetHandleIds: [],
|
||||
variables: [],
|
||||
},
|
||||
}
|
||||
|
||||
const llmNode: Node<LLMNodeType> = {
|
||||
id: SUBGRAPH_LLM_NODE_ID,
|
||||
type: 'custom',
|
||||
position: { x: 450, y: 150 },
|
||||
data: {
|
||||
type: BlockEnum.LLM,
|
||||
title: 'LLM',
|
||||
desc: 'Transform the output',
|
||||
_connectedSourceHandleIds: [],
|
||||
_connectedTargetHandleIds: ['target'],
|
||||
model: {
|
||||
provider: '',
|
||||
name: '',
|
||||
mode: AppModeEnum.CHAT,
|
||||
completion_params: {
|
||||
temperature: 0.7,
|
||||
},
|
||||
},
|
||||
prompt_template: [{
|
||||
role: PromptRole.system,
|
||||
text: '',
|
||||
}],
|
||||
context: {
|
||||
enabled: false,
|
||||
variable_selector: [],
|
||||
},
|
||||
vision: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return [startNode, llmNode]
|
||||
return getSubGraphInitialNodes(sourceVariable, agentName)
|
||||
}, [sourceVariable, agentName])
|
||||
|
||||
const initialEdges = useMemo((): Edge[] => {
|
||||
return [
|
||||
{
|
||||
id: `${SUBGRAPH_SOURCE_NODE_ID}-${SUBGRAPH_LLM_NODE_ID}`,
|
||||
source: SUBGRAPH_SOURCE_NODE_ID,
|
||||
sourceHandle: 'source',
|
||||
target: SUBGRAPH_LLM_NODE_ID,
|
||||
targetHandle: 'target',
|
||||
type: 'custom',
|
||||
data: {
|
||||
sourceType: BlockEnum.Start,
|
||||
targetType: BlockEnum.LLM,
|
||||
},
|
||||
},
|
||||
]
|
||||
return getSubGraphInitialEdges()
|
||||
}, [])
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { ResourceVarInputs } from '../types'
|
||||
import type { MentionConfig, ResourceVarInputs } from '../types'
|
||||
import type { CredentialFormSchema, FormOption } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { Event, Tool } from '@/app/components/tools/types'
|
||||
import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
|
||||
@@ -233,13 +233,25 @@ const FormInputItem: FC<Props> = ({
|
||||
}
|
||||
}
|
||||
|
||||
const handleValueChange = (newValue: any, newType?: VarKindType) => {
|
||||
const handleValueChange = (newValue: any, newType?: VarKindType, mentionConfig?: MentionConfig | null) => {
|
||||
const normalizedValue = isNumber ? Number.parseFloat(newValue) : newValue
|
||||
const resolvedType = newType ?? (varInput?.type === VarKindType.mention ? VarKindType.mention : getVarKindType())
|
||||
const resolvedMentionConfig = resolvedType === VarKindType.mention
|
||||
? (mentionConfig ?? varInput?.mention_config ?? {
|
||||
extractor_node_id: '',
|
||||
output_selector: [],
|
||||
null_strategy: 'use_default',
|
||||
default_value: '',
|
||||
})
|
||||
: undefined
|
||||
|
||||
onChange({
|
||||
...value,
|
||||
[variable]: {
|
||||
...varInput,
|
||||
type: newType ?? getVarKindType(),
|
||||
value: isNumber ? Number.parseFloat(newValue) : newValue,
|
||||
type: resolvedType,
|
||||
value: normalizedValue,
|
||||
mention_config: resolvedMentionConfig,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,10 +8,18 @@ export enum VarKindType {
|
||||
mention = 'mention',
|
||||
}
|
||||
|
||||
export type MentionConfig = {
|
||||
extractor_node_id: string
|
||||
output_selector: ValueSelector
|
||||
null_strategy: 'raise_error' | 'use_default'
|
||||
default_value: unknown
|
||||
}
|
||||
|
||||
// Generic resource variable inputs
|
||||
export type ResourceVarInputs = Record<string, {
|
||||
type: VarKindType
|
||||
value?: string | ValueSelector | any
|
||||
mention_config?: MentionConfig
|
||||
}>
|
||||
|
||||
// Base resource interface
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AgentNode } from '@/app/components/base/prompt-editor/types'
|
||||
import type { VarKindType } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type { MentionConfig, VarKindType } from '@/app/components/workflow/nodes/_base/types'
|
||||
import type {
|
||||
Node,
|
||||
NodeOutPutVar,
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import PromptEditor from '@/app/components/base/prompt-editor'
|
||||
import { useSubGraphPersistence } from '@/app/components/sub-graph/hooks'
|
||||
import { getSubGraphInitialEdges, getSubGraphInitialNodes } from '@/app/components/sub-graph/hooks/use-sub-graph-init'
|
||||
import { VarKindType as VarKindTypeEnum } from '@/app/components/workflow/nodes/_base/types'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
@@ -22,17 +24,24 @@ import AgentHeaderBar from './agent-header-bar'
|
||||
import Placeholder from './placeholder'
|
||||
|
||||
/**
|
||||
* Matches agent context variable syntax: {{#nodeId.context#}}
|
||||
* Example: {{#agent-123.context#}} -> captures "agent-123"
|
||||
* Matches agent context variable syntax: {{@nodeId.context@}}
|
||||
* Example: {{@agent-123.context@}} -> captures "agent-123"
|
||||
*/
|
||||
const AGENT_CONTEXT_VAR_PATTERN = /\{\{#([^.#]+)\.context#\}\}/g
|
||||
const AGENT_CONTEXT_VAR_PATTERN = /\{\{[@#]([^.@#]+)\.context[@#]\}\}/g
|
||||
|
||||
const DEFAULT_MENTION_CONFIG: MentionConfig = {
|
||||
extractor_node_id: '',
|
||||
output_selector: [],
|
||||
null_strategy: 'use_default',
|
||||
default_value: '',
|
||||
}
|
||||
|
||||
type MixedVariableTextInputProps = {
|
||||
readOnly?: boolean
|
||||
nodesOutputVars?: NodeOutPutVar[]
|
||||
availableNodes?: Node[]
|
||||
value?: string
|
||||
onChange?: (text: string, type?: VarKindType) => void
|
||||
onChange?: (text: string, type?: VarKindType, mentionConfig?: MentionConfig | null) => void
|
||||
showManageInputField?: boolean
|
||||
onManageInputField?: () => void
|
||||
disableVariableInsertion?: boolean
|
||||
@@ -57,6 +66,15 @@ const MixedVariableTextInput = ({
|
||||
const setControlPromptEditorRerenderKey = useStore(s => s.setControlPromptEditorRerenderKey)
|
||||
const [isSubGraphModalOpen, setIsSubGraphModalOpen] = useState(false)
|
||||
|
||||
const {
|
||||
loadSubGraphData,
|
||||
updateSubGraphNodes,
|
||||
clearSubGraphData,
|
||||
} = useSubGraphPersistence({
|
||||
toolNodeId: toolNodeId || '',
|
||||
paramKey: paramKey || '',
|
||||
})
|
||||
|
||||
const nodesByIdMap = useMemo(() => {
|
||||
return availableNodes.reduce((acc, node) => {
|
||||
acc[node.id] = node
|
||||
@@ -107,20 +125,28 @@ const MixedVariableTextInput = ({
|
||||
return nodeId === agentNodeId ? '' : match
|
||||
}).trim()
|
||||
|
||||
onChange(valueWithoutAgentVars, VarKindTypeEnum.mixed)
|
||||
onChange(valueWithoutAgentVars, VarKindTypeEnum.mixed, null)
|
||||
if (toolNodeId && paramKey)
|
||||
clearSubGraphData()
|
||||
setControlPromptEditorRerenderKey(Date.now())
|
||||
}, [detectedAgentFromValue?.nodeId, value, onChange, setControlPromptEditorRerenderKey])
|
||||
}, [clearSubGraphData, detectedAgentFromValue?.nodeId, onChange, paramKey, setControlPromptEditorRerenderKey, toolNodeId, value])
|
||||
|
||||
const handleAgentSelect = useCallback((agent: AgentNode) => {
|
||||
if (!onChange)
|
||||
return
|
||||
|
||||
const valueWithoutTrigger = value.replace(/@$/, '')
|
||||
const newValue = `{{#${agent.id}.context#}}${valueWithoutTrigger}`
|
||||
const newValue = `{{@${agent.id}.context@}}${valueWithoutTrigger}`
|
||||
|
||||
onChange(newValue, VarKindTypeEnum.mention)
|
||||
if (toolNodeId && paramKey && !loadSubGraphData()) {
|
||||
const initialNodes = getSubGraphInitialNodes([agent.id, 'context'], agent.title)
|
||||
const initialEdges = getSubGraphInitialEdges()
|
||||
updateSubGraphNodes(initialNodes, initialEdges)
|
||||
}
|
||||
|
||||
onChange(newValue, VarKindTypeEnum.mention, DEFAULT_MENTION_CONFIG)
|
||||
setControlPromptEditorRerenderKey(Date.now())
|
||||
}, [value, onChange, setControlPromptEditorRerenderKey])
|
||||
}, [loadSubGraphData, onChange, paramKey, setControlPromptEditorRerenderKey, toolNodeId, updateSubGraphNodes, value])
|
||||
|
||||
const handleOpenSubGraphModal = useCallback(() => {
|
||||
setIsSubGraphModalOpen(true)
|
||||
@@ -179,7 +205,7 @@ const MixedVariableTextInput = ({
|
||||
onSelect: handleAgentSelect,
|
||||
}}
|
||||
placeholder={<Placeholder disableVariableInsertion={disableVariableInsertion} hasSelectedAgent={!!detectedAgentFromValue} />}
|
||||
onChange={onChange}
|
||||
onChange={text => onChange?.(text)}
|
||||
/>
|
||||
{toolNodeId && detectedAgentFromValue && sourceVariable && (
|
||||
<SubGraphModal
|
||||
|
||||
@@ -32,7 +32,7 @@ const useSingleRunFormParams = ({
|
||||
const { inputs } = useNodeCrud<ToolNodeType>(id, payload)
|
||||
|
||||
const hadVarParams = Object.keys(inputs.tool_parameters)
|
||||
.filter(key => inputs.tool_parameters[key].type !== VarType.constant)
|
||||
.filter(key => ![VarType.constant, VarType.mention].includes(inputs.tool_parameters[key].type))
|
||||
.map(k => inputs.tool_parameters[k])
|
||||
|
||||
const hadVarSettings = Object.keys(inputs.tool_configurations)
|
||||
|
||||
@@ -70,6 +70,8 @@ describe('config test', () => {
|
||||
// rag variables
|
||||
'{{#rag.1748945155129.a#}}',
|
||||
'{{#rag.shared.bbb#}}',
|
||||
'{{@1749783300519.llm.a@}}',
|
||||
'{{@sys.query@}}',
|
||||
]
|
||||
vars.forEach((variable) => {
|
||||
expect(VAR_REGEX.test(variable)).toBe(true)
|
||||
|
||||
@@ -340,7 +340,7 @@ Thought: {{agent_scratchpad}}
|
||||
}
|
||||
|
||||
export const VAR_REGEX
|
||||
= /\{\{(#[\w-]{1,50}(\.\d+)?(\.[a-z_]\w{0,29}){1,10}#)\}\}/gi
|
||||
= /\{\{([#@])[\w-]{1,50}(\.\d+)?(\.[a-z_]\w{0,29}){1,10}\1\}\}/gi
|
||||
|
||||
export const resetReg = () => (VAR_REGEX.lastIndex = 0)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user