Skip to content

Commit 62b56d0

Browse files
authored
Bugfix/Missing Reasoning Item (#6237)
* - pass langchain message instance as a whole to avoid missing reasoning item - add `normalizeMessagesForStorage` function to convert LangChain message instances into plain JSON objects, improving database storage efficiency and UI readability * - Fix ChatflowConfigurationDialog for inconsistent behaviour - Added a button in NodeExecutionDetails to toggle visibility of all available tools, improving user experience. * display input data for tool node in execution
1 parent e10a4ca commit 62b56d0

File tree

5 files changed

+113
-40
lines changed

5 files changed

+113
-40
lines changed

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

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
getUniqueImageMessages,
3131
processMessagesWithImages,
3232
revertBase64ImagesToFileRefs,
33+
normalizeMessagesForStorage,
3334
replaceInlineDataWithFileReferences,
3435
updateFlowState
3536
} from '../utils'
@@ -1471,7 +1472,8 @@ class Agent_Agentflow implements INode {
14711472
* This is to avoid storing the actual base64 data into database
14721473
*/
14731474
const messagesToStore = messages.filter((msg: any) => !msg._isTemporaryImageMessage)
1474-
const messagesWithFileReferences = revertBase64ImagesToFileRefs(messagesToStore)
1475+
const normalizedMessagesToStore = normalizeMessagesForStorage(messagesToStore)
1476+
const messagesWithFileReferences = revertBase64ImagesToFileRefs(normalizedMessagesToStore)
14751477

14761478
// Only add to runtime chat history if this is the first node
14771479
const inputMessages = []
@@ -2235,13 +2237,7 @@ class Agent_Agentflow implements INode {
22352237
}
22362238

22372239
// Add LLM response with tool calls to messages
2238-
messages.push({
2239-
id: response.id,
2240-
role: 'assistant',
2241-
content: response.content,
2242-
tool_calls: response.tool_calls,
2243-
usage_metadata: response.usage_metadata
2244-
})
2240+
messages.push(response)
22452241

22462242
// Process each tool call
22472243
for (let i = 0; i < response.tool_calls.length; i++) {
@@ -2622,13 +2618,7 @@ class Agent_Agentflow implements INode {
26222618
}
26232619

26242620
// Add LLM response with tool calls to messages
2625-
messages.push({
2626-
id: response.id,
2627-
role: 'assistant',
2628-
content: response.content,
2629-
tool_calls: response.tool_calls,
2630-
usage_metadata: response.usage_metadata
2631-
})
2621+
messages.push(response)
26322622

26332623
// Process each tool call
26342624
for (let i = 0; i < response.tool_calls.length; i++) {

packages/components/nodes/agentflow/utils.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,47 @@ export const revertBase64ImagesToFileRefs = (messages: BaseMessageLike[]): BaseM
197197
return updatedMessages
198198
}
199199

200+
/**
201+
* Converts LangChain message/chunk instances into plain JSON objects for clean DB storage.
202+
* This avoids persisting large `{ lc, type, kwargs }` blobs and keeps execution-details UI readable.
203+
*/
204+
export const normalizeMessagesForStorage = (messages: BaseMessageLike[]): BaseMessageLike[] => {
205+
return (messages || []).map((msg: any) => {
206+
if (msg?.lc_namespace || typeof msg?._getType === 'function') {
207+
const rawType = typeof msg?._getType === 'function' ? msg._getType() : msg?.type
208+
const role =
209+
rawType === 'ai'
210+
? 'assistant'
211+
: rawType === 'human'
212+
? 'user'
213+
: rawType === 'system'
214+
? 'system'
215+
: rawType === 'tool'
216+
? 'tool'
217+
: msg?.role || 'assistant'
218+
219+
const plain: Record<string, any> = {
220+
role,
221+
content: msg?.content ?? ''
222+
}
223+
224+
if (msg?.name) plain.name = msg.name
225+
if (msg?.tool_call_id) plain.tool_call_id = msg.tool_call_id
226+
if (Array.isArray(msg?.tool_calls) && msg.tool_calls.length > 0) plain.tool_calls = msg.tool_calls
227+
228+
if (msg?.additional_kwargs && Object.keys(msg.additional_kwargs).length > 0) {
229+
plain.additional_kwargs = msg.additional_kwargs
230+
}
231+
232+
if (msg?.usage_metadata) plain.usage_metadata = msg.usage_metadata
233+
if (msg?.id) plain.id = msg.id
234+
235+
return plain
236+
}
237+
return msg
238+
})
239+
}
240+
200241
// ─── Handling new image uploads ──────────────────────────────────────────────
201242

202243
/**

packages/ui/src/ui-component/dialog/ChatflowConfigurationDialog.jsx

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -167,26 +167,46 @@ function getSectionStatus(sectionId, chatflow) {
167167
case 'chatFeedback':
168168
return chatbotConfig?.chatFeedback?.status === true
169169
case 'speechToText': {
170-
const stt = chatbotConfig?.speechToText
171-
if (!stt) return false
172-
return Object.values(stt).some((provider) => provider?.status === true)
170+
if (!chatflow.speechToText) return false
171+
try {
172+
const stt = JSON.parse(chatflow.speechToText)
173+
// "none" with status:true means disabled — ignore it
174+
return Object.entries(stt).some(([key, provider]) => key !== 'none' && provider?.status === true)
175+
} catch {
176+
return false
177+
}
173178
}
174179
case 'textToSpeech': {
175-
const tts = chatbotConfig?.textToSpeech
176-
if (!tts) return false
177-
return Object.values(tts).some((provider) => provider?.status === true)
180+
if (!chatflow.textToSpeech) return false
181+
try {
182+
const tts = JSON.parse(chatflow.textToSpeech)
183+
return Object.entries(tts).some(([key, provider]) => key !== 'none' && provider?.status === true)
184+
} catch {
185+
return false
186+
}
178187
}
179188
case 'fileUpload':
180189
return chatbotConfig?.fullFileUpload?.status === true
181190
case 'analyseChatflow': {
182-
const ap = chatbotConfig?.analyticsProviders
183-
if (!ap) return false
184-
return Object.values(ap).some((provider) => provider?.status === true)
191+
if (!chatflow.analytic) return false
192+
try {
193+
const ap = JSON.parse(chatflow.analytic)
194+
return Object.values(ap).some((provider) => provider?.status === true)
195+
} catch {
196+
return false
197+
}
185198
}
186199
case 'postProcessing':
187-
return chatbotConfig?.postProcessing?.status === true
188-
case 'mcpServer':
189-
return chatbotConfig?.mcpServer?.status === true
200+
return chatbotConfig?.postProcessing?.enabled === true
201+
case 'mcpServer': {
202+
if (!chatflow.mcpServerConfig) return false
203+
try {
204+
const mcp = typeof chatflow.mcpServerConfig === 'string' ? JSON.parse(chatflow.mcpServerConfig) : chatflow.mcpServerConfig
205+
return mcp?.enabled === true
206+
} catch {
207+
return false
208+
}
209+
}
190210
case 'overrideConfig':
191211
return apiConfig?.overrideConfig?.status === true
192212
default:
@@ -206,7 +226,6 @@ const ChatflowConfigurationDialog = ({ show, isAgentCanvas, dialogProps, onCance
206226
const customization = useSelector((state) => state.customization)
207227

208228
const [activeSection, setActiveSection] = useState('rateLimit')
209-
const [mcpServerEnabled, setMcpServerEnabled] = useState(false)
210229

211230
const isDark = theme.palette.mode === 'dark' || customization?.isDarkMode
212231

@@ -253,7 +272,7 @@ const ChatflowConfigurationDialog = ({ show, isAgentCanvas, dialogProps, onCance
253272
case 'postProcessing':
254273
return <PostProcessing {...props} />
255274
case 'mcpServer':
256-
return <McpServer {...props} onStatusChange={(enabled) => setMcpServerEnabled(enabled)} />
275+
return <McpServer {...props} />
257276
case 'overrideConfig':
258277
return <OverrideConfig {...props} hideTitle />
259278
default:
@@ -346,8 +365,7 @@ const ChatflowConfigurationDialog = ({ show, isAgentCanvas, dialogProps, onCance
346365
{/* Section items */}
347366
{group.sections.map((section) => {
348367
const isActive = currentSection === section.id
349-
const isEnabled =
350-
section.id === 'mcpServer' ? mcpServerEnabled : getSectionStatus(section.id, chatflow)
368+
const isEnabled = getSectionStatus(section.id, chatflow)
351369
const SectionIcon = section.icon
352370

353371
return (

packages/ui/src/ui-component/extended/FollowUpPrompts.jsx

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -412,13 +412,25 @@ const FollowUpPrompts = ({ dialogProps }) => {
412412
}
413413

414414
useEffect(() => {
415-
if (dialogProps.chatflow && dialogProps.chatflow.followUpPrompts) {
416-
let chatbotConfig = JSON.parse(dialogProps.chatflow.chatbotConfig)
417-
let followUpPromptsConfig = JSON.parse(dialogProps.chatflow.followUpPrompts)
418-
setChatbotConfig(chatbotConfig || {})
419-
if (followUpPromptsConfig) {
420-
setFollowUpPromptsConfig(followUpPromptsConfig)
421-
setSelectedProvider(followUpPromptsConfig.selectedProvider)
415+
if (!dialogProps.chatflow) return
416+
// Load chatbotConfig unconditionally — otherwise saving follow-up prompts
417+
// writes an empty object and wipes starterPrompts/leads/allowedOrigins/etc.
418+
if (dialogProps.chatflow.chatbotConfig) {
419+
try {
420+
setChatbotConfig(JSON.parse(dialogProps.chatflow.chatbotConfig) || {})
421+
} catch {
422+
setChatbotConfig({})
423+
}
424+
}
425+
if (dialogProps.chatflow.followUpPrompts) {
426+
try {
427+
const followUpPromptsConfig = JSON.parse(dialogProps.chatflow.followUpPrompts)
428+
if (followUpPromptsConfig) {
429+
setFollowUpPromptsConfig(followUpPromptsConfig)
430+
setSelectedProvider(followUpPromptsConfig.selectedProvider)
431+
}
432+
} catch {
433+
// ignore malformed stored config
422434
}
423435
}
424436

packages/ui/src/views/agentexecutions/NodeExecutionDetails.jsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export const NodeExecutionDetails = ({ data, label, status, metadata, isPublic,
4848
const [loadingMessage, setLoadingMessage] = useState('')
4949
const [sourceDialogOpen, setSourceDialogOpen] = useState(false)
5050
const [sourceDialogProps, setSourceDialogProps] = useState({})
51+
const [showAllTools, setShowAllTools] = useState(false)
5152
const customization = useSelector((state) => state.customization)
5253
const theme = useTheme()
5354
const { enqueueSnackbar } = useSnackbar()
@@ -387,7 +388,7 @@ export const NodeExecutionDetails = ({ data, label, status, metadata, isPublic,
387388
<Typography sx={{ mt: 2 }} variant='h5' gutterBottom>
388389
Tools
389390
</Typography>
390-
{data.output.availableTools.map((tool, index) => {
391+
{(showAllTools ? data.output.availableTools : data.output.availableTools.slice(0, 5)).map((tool, index) => {
391392
// Check if this tool is in the usedTools array
392393
const isToolUsed =
393394
data.output.usedTools &&
@@ -412,7 +413,7 @@ export const NodeExecutionDetails = ({ data, label, status, metadata, isPublic,
412413
}}
413414
>
414415
<AccordionSummary
415-
expandIcon={<IconChevronDown />}
416+
expandIcon={<IconChevronDown color={customization.isDarkMode ? '#fff' : undefined} />}
416417
aria-controls={`tool-${index}-content`}
417418
id={`tool-${index}-header`}
418419
>
@@ -486,6 +487,15 @@ export const NodeExecutionDetails = ({ data, label, status, metadata, isPublic,
486487
</Accordion>
487488
)
488489
})}
490+
{data.output.availableTools.length > 5 && (
491+
<Button
492+
size='small'
493+
onClick={() => setShowAllTools((prev) => !prev)}
494+
sx={{ mt: 0.5, textTransform: 'none' }}
495+
>
496+
{showAllTools ? 'Show less' : `Show ${data.output.availableTools.length - 5} more`}
497+
</Button>
498+
)}
489499
</Box>
490500
)}
491501
<Typography sx={{ mt: 2 }} variant='h5' gutterBottom>
@@ -905,6 +915,8 @@ export const NodeExecutionDetails = ({ data, label, status, metadata, isPublic,
905915
)}
906916
</Box>
907917
))
918+
) : data?.name === 'toolAgentflow' && data?.input ? (
919+
<JSONViewer data={data.input} />
908920
) : data?.input?.form || data?.input?.http || data?.input?.conditions ? (
909921
<JSONViewer data={data.input.form || data.input.http || data.input.conditions} />
910922
) : data?.input?.code ? (

0 commit comments

Comments
 (0)