Skip to content

Commit 4987a28

Browse files
authored
Bugfix/Array Input Variables (#5196)
- Replace manual template variable processing in multiple components with a new utility function `processTemplateVariables`.
1 parent 736c2b1 commit 4987a28

File tree

9 files changed

+199
-81
lines changed

9 files changed

+199
-81
lines changed

packages/components/nodes/agentflow/Agent/Agent.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
replaceBase64ImagesWithFileReferences,
2929
updateFlowState
3030
} from '../utils'
31-
import { convertMultiOptionsToStringArray, getCredentialData, getCredentialParam } from '../../../src/utils'
31+
import { convertMultiOptionsToStringArray, getCredentialData, getCredentialParam, processTemplateVariables } from '../../../src/utils'
3232
import { addSingleFileToStorage } from '../../../src/storageUtils'
3333
import fetch from 'node-fetch'
3434

@@ -1086,13 +1086,7 @@ class Agent_Agentflow implements INode {
10861086
}
10871087

10881088
// Process template variables in state
1089-
if (newState && Object.keys(newState).length > 0) {
1090-
for (const key in newState) {
1091-
if (newState[key].toString().includes('{{ output }}')) {
1092-
newState[key] = newState[key].replaceAll('{{ output }}', finalResponse)
1093-
}
1094-
}
1095-
}
1089+
newState = processTemplateVariables(newState, finalResponse)
10961090

10971091
// Replace the actual messages array with one that includes the file references for images instead of base64 data
10981092
const messagesWithFileReferences = replaceBase64ImagesWithFileReferences(

packages/components/nodes/agentflow/CustomFunction/CustomFunction.ts

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
INodeParams,
99
IServerSideEventStreamer
1010
} from '../../../src/Interface'
11-
import { getVars, executeJavaScriptCode, createCodeExecutionSandbox } from '../../../src/utils'
11+
import { getVars, executeJavaScriptCode, createCodeExecutionSandbox, processTemplateVariables } from '../../../src/utils'
1212
import { updateFlowState } from '../utils'
1313

1414
interface ICustomFunctionInputVariables {
@@ -145,19 +145,13 @@ class CustomFunction_Agentflow implements INode {
145145
const appDataSource = options.appDataSource as DataSource
146146
const databaseEntities = options.databaseEntities as IDatabaseEntity
147147

148-
// Update flow state if needed
149-
let newState = { ...state }
150-
if (_customFunctionUpdateState && Array.isArray(_customFunctionUpdateState) && _customFunctionUpdateState.length > 0) {
151-
newState = updateFlowState(state, _customFunctionUpdateState)
152-
}
153-
154148
const variables = await getVars(appDataSource, databaseEntities, nodeData, options)
155149
const flow = {
156150
chatflowId: options.chatflowid,
157151
sessionId: options.sessionId,
158152
chatId: options.chatId,
159153
input,
160-
state: newState
154+
state
161155
}
162156

163157
// Create additional sandbox variables for custom function inputs
@@ -190,15 +184,14 @@ class CustomFunction_Agentflow implements INode {
190184
finalOutput = JSON.stringify(response, null, 2)
191185
}
192186

193-
// Process template variables in state
194-
if (newState && Object.keys(newState).length > 0) {
195-
for (const key in newState) {
196-
if (newState[key].toString().includes('{{ output }}')) {
197-
newState[key] = newState[key].replaceAll('{{ output }}', finalOutput)
198-
}
199-
}
187+
// Update flow state if needed
188+
let newState = { ...state }
189+
if (_customFunctionUpdateState && Array.isArray(_customFunctionUpdateState) && _customFunctionUpdateState.length > 0) {
190+
newState = updateFlowState(state, _customFunctionUpdateState)
200191
}
201192

193+
newState = processTemplateVariables(newState, finalOutput)
194+
202195
const returnOutput = {
203196
id: nodeData.id,
204197
name: this.name,

packages/components/nodes/agentflow/ExecuteFlow/ExecuteFlow.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
IServerSideEventStreamer
99
} from '../../../src/Interface'
1010
import axios, { AxiosRequestConfig } from 'axios'
11-
import { getCredentialData, getCredentialParam } from '../../../src/utils'
11+
import { getCredentialData, getCredentialParam, processTemplateVariables } from '../../../src/utils'
1212
import { DataSource } from 'typeorm'
1313
import { BaseMessageLike } from '@langchain/core/messages'
1414
import { updateFlowState } from '../utils'
@@ -222,13 +222,7 @@ class ExecuteFlow_Agentflow implements INode {
222222
}
223223

224224
// Process template variables in state
225-
if (newState && Object.keys(newState).length > 0) {
226-
for (const key in newState) {
227-
if (newState[key].toString().includes('{{ output }}')) {
228-
newState[key] = newState[key].replaceAll('{{ output }}', resultText)
229-
}
230-
}
231-
}
225+
newState = processTemplateVariables(newState, resultText)
232226

233227
// Only add to runtime chat history if this is the first node
234228
const inputMessages = []

packages/components/nodes/agentflow/LLM/LLM.ts

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
replaceBase64ImagesWithFileReferences,
1313
updateFlowState
1414
} from '../utils'
15-
import { get } from 'lodash'
15+
import { processTemplateVariables } from '../../../src/utils'
1616

1717
class LLM_Agentflow implements INode {
1818
label: string
@@ -529,36 +529,7 @@ class LLM_Agentflow implements INode {
529529
}
530530

531531
// Process template variables in state
532-
if (newState && Object.keys(newState).length > 0) {
533-
for (const key in newState) {
534-
const stateValue = newState[key].toString()
535-
if (stateValue.includes('{{ output')) {
536-
// Handle simple output replacement
537-
if (stateValue === '{{ output }}') {
538-
newState[key] = finalResponse
539-
continue
540-
}
541-
542-
// Handle JSON path expressions like {{ output.item1 }}
543-
// eslint-disable-next-line
544-
const match = stateValue.match(/{{[\s]*output\.([\w\.]+)[\s]*}}/)
545-
if (match) {
546-
try {
547-
// Parse the response if it's JSON
548-
const jsonResponse = typeof finalResponse === 'string' ? JSON.parse(finalResponse) : finalResponse
549-
// Get the value using lodash get
550-
const path = match[1]
551-
const value = get(jsonResponse, path)
552-
newState[key] = value ?? stateValue // Fall back to original if path not found
553-
} catch (e) {
554-
// If JSON parsing fails, keep original template
555-
console.warn(`Failed to parse JSON or find path in output: ${e}`)
556-
newState[key] = stateValue
557-
}
558-
}
559-
}
560-
}
561-
}
532+
newState = processTemplateVariables(newState, finalResponse)
562533

563534
// Replace the actual messages array with one that includes the file references for images instead of base64 data
564535
const messagesWithFileReferences = replaceBase64ImagesWithFileReferences(

packages/components/nodes/agentflow/Retriever/Retriever.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
IServerSideEventStreamer
99
} from '../../../src/Interface'
1010
import { updateFlowState } from '../utils'
11+
import { processTemplateVariables } from '../../../src/utils'
1112
import { DataSource } from 'typeorm'
1213
import { BaseRetriever } from '@langchain/core/retrievers'
1314
import { Document } from '@langchain/core/documents'
@@ -197,14 +198,7 @@ class Retriever_Agentflow implements INode {
197198
sseStreamer.streamTokenEvent(chatId, finalOutput)
198199
}
199200

200-
// Process template variables in state
201-
if (newState && Object.keys(newState).length > 0) {
202-
for (const key in newState) {
203-
if (newState[key].toString().includes('{{ output }}')) {
204-
newState[key] = newState[key].replaceAll('{{ output }}', finalOutput)
205-
}
206-
}
207-
}
201+
newState = processTemplateVariables(newState, finalOutput)
208202

209203
const returnOutput = {
210204
id: nodeData.id,

packages/components/nodes/agentflow/Tool/Tool.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams, IServerSideEventStreamer } from '../../../src/Interface'
22
import { updateFlowState } from '../utils'
3+
import { processTemplateVariables } from '../../../src/utils'
34
import { Tool } from '@langchain/core/tools'
45
import { ARTIFACTS_PREFIX, TOOL_ARGS_PREFIX } from '../../../src/agents'
56
import zodToJsonSchema from 'zod-to-json-schema'
@@ -330,14 +331,7 @@ class Tool_Agentflow implements INode {
330331
sseStreamer.streamTokenEvent(chatId, toolOutput)
331332
}
332333

333-
// Process template variables in state
334-
if (newState && Object.keys(newState).length > 0) {
335-
for (const key in newState) {
336-
if (newState[key].toString().includes('{{ output }}')) {
337-
newState[key] = newState[key].replaceAll('{{ output }}', toolOutput)
338-
}
339-
}
340-
}
334+
newState = processTemplateVariables(newState, toolOutput)
341335

342336
const returnOutput = {
343337
id: nodeData.id,

packages/components/nodes/agentflow/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,9 +459,9 @@ export const getPastChatHistoryImageMessages = async (
459459
/**
460460
* Updates the flow state with new values
461461
*/
462-
export const updateFlowState = (state: ICommonObject, llmUpdateState: IFlowState[]): ICommonObject => {
462+
export const updateFlowState = (state: ICommonObject, updateState: IFlowState[]): ICommonObject => {
463463
let newFlowState: Record<string, any> = {}
464-
for (const state of llmUpdateState) {
464+
for (const state of updateState) {
465465
newFlowState[state.key] = state.value
466466
}
467467

packages/components/src/utils.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import TurndownService from 'turndown'
88
import { DataSource, Equal } from 'typeorm'
99
import { ICommonObject, IDatabaseEntity, IFileUpload, IMessage, INodeData, IVariable, MessageContentImageUrl } from './Interface'
1010
import { AES, enc } from 'crypto-js'
11-
import { omit } from 'lodash'
11+
import { omit, get } from 'lodash'
1212
import { AIMessage, HumanMessage, BaseMessage } from '@langchain/core/messages'
1313
import { Document } from '@langchain/core/documents'
1414
import { getFileFromStorage } from './storageUtils'
@@ -1609,3 +1609,50 @@ export const createCodeExecutionSandbox = (
16091609

16101610
return sandbox
16111611
}
1612+
1613+
/**
1614+
* Process template variables in state object, replacing {{ output }} and {{ output.property }} patterns
1615+
* @param {ICommonObject} state - The state object to process
1616+
* @param {any} finalOutput - The output value to substitute
1617+
* @returns {ICommonObject} - The processed state object
1618+
*/
1619+
export const processTemplateVariables = (state: ICommonObject, finalOutput: any): ICommonObject => {
1620+
if (!state || Object.keys(state).length === 0) {
1621+
return state
1622+
}
1623+
1624+
const newState = { ...state }
1625+
1626+
for (const key in newState) {
1627+
const stateValue = newState[key].toString()
1628+
if (stateValue.includes('{{ output') || stateValue.includes('{{output')) {
1629+
// Handle simple output replacement (with or without spaces)
1630+
if (stateValue === '{{ output }}' || stateValue === '{{output}}') {
1631+
newState[key] = finalOutput
1632+
continue
1633+
}
1634+
1635+
// Handle JSON path expressions like {{ output.updated }} or {{output.updated}}
1636+
// eslint-disable-next-line
1637+
const match = stateValue.match(/\{\{\s*output\.([\w\.]+)\s*\}\}/)
1638+
if (match) {
1639+
try {
1640+
// Parse the response if it's JSON
1641+
const jsonResponse = typeof finalOutput === 'string' ? JSON.parse(finalOutput) : finalOutput
1642+
// Get the value using lodash get
1643+
const path = match[1]
1644+
const value = get(jsonResponse, path)
1645+
newState[key] = value ?? stateValue // Fall back to original if path not found
1646+
} catch (e) {
1647+
// If JSON parsing fails, keep original template
1648+
newState[key] = stateValue
1649+
}
1650+
} else {
1651+
// Handle simple {{ output }} replacement for backward compatibility
1652+
newState[key] = newState[key].replaceAll('{{ output }}', finalOutput)
1653+
}
1654+
}
1655+
}
1656+
1657+
return newState
1658+
}

0 commit comments

Comments
 (0)