diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/code.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/code.tsx index 8306e1e8f8..fb70307ea4 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/code.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/code.tsx @@ -29,6 +29,7 @@ interface CodeProps { isPreview?: boolean previewValue?: string | null disabled?: boolean + onValidationChange?: (isValid: boolean) => void } if (typeof document !== 'undefined') { @@ -60,6 +61,7 @@ export function Code({ isPreview = false, previewValue, disabled = false, + onValidationChange, }: CodeProps) { // Determine the AI prompt placeholder based on language const aiPromptPlaceholder = useMemo(() => { @@ -90,6 +92,27 @@ export function Code({ const showCollapseButton = (subBlockId === 'responseFormat' || subBlockId === 'code') && code.split('\n').length > 5 + const isValidJson = useMemo(() => { + if (subBlockId !== 'responseFormat' || !code.trim()) { + return true + } + try { + JSON.parse(code) + return true + } catch { + return false + } + }, [subBlockId, code]) + + useEffect(() => { + if (onValidationChange && subBlockId === 'responseFormat') { + const timeoutId = setTimeout(() => { + onValidationChange(isValidJson) + }, 150) // Match debounce time from setStoreValue + return () => clearTimeout(timeoutId) + } + }, [isValidJson, onValidationChange, subBlockId]) + const editorRef = useRef(null) // Function to toggle collapsed state @@ -343,9 +366,11 @@ export function Code({
e.preventDefault()} onDrop={handleDrop} > diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/sub-block.tsx index e821aedd96..54efcb75b8 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/sub-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/sub-block.tsx @@ -1,4 +1,5 @@ -import { Info } from 'lucide-react' +import { useState } from 'react' +import { AlertTriangle, Info } from 'lucide-react' import { Label } from '@/components/ui/label' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { getBlock } from '@/blocks/index' @@ -48,10 +49,16 @@ export function SubBlock({ subBlockValues, disabled = false, }: SubBlockProps) { + const [isValidJson, setIsValidJson] = useState(true) + const handleMouseDown = (e: React.MouseEvent) => { e.stopPropagation() } + const handleValidationChange = (isValid: boolean) => { + setIsValidJson(isValid) + } + const isFieldRequired = () => { const blockType = useWorkflowStore.getState().blocks[blockId]?.type if (!blockType) return false @@ -169,6 +176,7 @@ export function SubBlock({ isPreview={isPreview} previewValue={previewValue} disabled={isDisabled} + onValidationChange={handleValidationChange} /> ) case 'switch': @@ -406,6 +414,16 @@ export function SubBlock({ )} + {config.id === 'responseFormat' && !isValidJson && ( + + + + + +

Invalid JSON

+
+
+ )} {config.description && ( diff --git a/apps/sim/executor/handlers/agent/agent-handler.ts b/apps/sim/executor/handlers/agent/agent-handler.ts index 80382f5fff..880b96419a 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.ts @@ -487,6 +487,7 @@ export class AgentBlockHandler implements BlockHandler { const contentType = response.headers.get('Content-Type') if (contentType?.includes('text/event-stream')) { // Handle streaming response + logger.info('Received streaming response') return this.handleStreamingResponse(response, block) } @@ -703,15 +704,31 @@ export class AgentBlockHandler implements BlockHandler { } private processStructuredResponse(result: any, responseFormat: any): BlockOutput { + const content = result.content + try { - const parsedContent = JSON.parse(result.content) + const extractedJson = JSON.parse(content.trim()) + logger.info('Successfully parsed structured response content') return { - ...parsedContent, + ...extractedJson, ...this.createResponseMetadata(result), } } catch (error) { - logger.error('Failed to parse response content:', { error }) - return this.processStandardResponse(result) + logger.info('JSON parsing failed', { + error: error instanceof Error ? error.message : 'Unknown error', + }) + + // LLM did not adhere to structured response format + logger.error('LLM did not adhere to structured response format:', { + content: content.substring(0, 200) + (content.length > 200 ? '...' : ''), + responseFormat: responseFormat, + }) + + const standardResponse = this.processStandardResponse(result) + return Object.assign(standardResponse, { + _responseFormatWarning: + 'LLM did not adhere to the specified structured response format. Expected valid JSON but received malformed content. Falling back to standard format.', + }) } }