diff --git a/app/api/scheduled/execute/route.ts b/app/api/scheduled/execute/route.ts index b851239229..a982e2b760 100644 --- a/app/api/scheduled/execute/route.ts +++ b/app/api/scheduled/execute/route.ts @@ -272,9 +272,43 @@ export async function GET(req: NextRequest) { workflowId: schedule.workflowId, } + // Process the block states to ensure response formats are properly parsed + // This is crucial for agent blocks with response format + const processedBlockStates = Object.entries(currentBlockStates).reduce( + (acc, [blockId, blockState]) => { + // Check if this block has a responseFormat that needs to be parsed + if (blockState.responseFormat && typeof blockState.responseFormat === 'string') { + try { + console.log( + `[Schedule Debug] Block ${blockId} has responseFormat as string:`, + blockState.responseFormat + ) + // Attempt to parse the responseFormat if it's a string + const parsedResponseFormat = JSON.parse(blockState.responseFormat) + console.log( + `[Schedule Debug] Successfully parsed responseFormat for block ${blockId}:`, + parsedResponseFormat + ) + + acc[blockId] = { + ...blockState, + responseFormat: parsedResponseFormat, + } + } catch (error) { + console.warn(`Failed to parse responseFormat for block ${blockId}:`, error) + acc[blockId] = blockState + } + } else { + acc[blockId] = blockState + } + return acc + }, + {} as Record> + ) + const executor = new Executor( serializedWorkflow, - currentBlockStates, + processedBlockStates, // Use the processed block states decryptedEnvVars, input ) diff --git a/app/api/webhooks/trigger/[path]/route.ts b/app/api/webhooks/trigger/[path]/route.ts index a6b731dfb8..c516932371 100644 --- a/app/api/webhooks/trigger/[path]/route.ts +++ b/app/api/webhooks/trigger/[path]/route.ts @@ -277,9 +277,43 @@ export async function POST( console.log('Executing workflow with workflowId:', foundWorkflow.id) + // Process the block states to ensure response formats are properly parsed + // This is crucial for agent blocks with response format + const processedBlockStates = Object.entries(currentBlockStates).reduce( + (acc, [blockId, blockState]) => { + // Check if this block has a responseFormat that needs to be parsed + if (blockState.responseFormat && typeof blockState.responseFormat === 'string') { + try { + console.log( + `[Webhook Debug] Block ${blockId} has responseFormat as string:`, + blockState.responseFormat + ) + // Attempt to parse the responseFormat if it's a string + const parsedResponseFormat = JSON.parse(blockState.responseFormat) + console.log( + `[Webhook Debug] Successfully parsed responseFormat for block ${blockId}:`, + parsedResponseFormat + ) + + acc[blockId] = { + ...blockState, + responseFormat: parsedResponseFormat, + } + } catch (error) { + console.warn(`Failed to parse responseFormat for block ${blockId}:`, error) + acc[blockId] = blockState + } + } else { + acc[blockId] = blockState + } + return acc + }, + {} as Record> + ) + const executor = new Executor( serializedWorkflow, - currentBlockStates, + processedBlockStates, // Use the processed block states decryptedEnvVars, enrichedInput ) diff --git a/app/api/workflow/[id]/execute/route.ts b/app/api/workflow/[id]/execute/route.ts index 7cd4ee620f..9f77f1b817 100644 --- a/app/api/workflow/[id]/execute/route.ts +++ b/app/api/workflow/[id]/execute/route.ts @@ -111,9 +111,43 @@ async function executeWorkflow(workflow: any, input?: any) { } } + // Process the block states to ensure response formats are properly parsed + // This is crucial for agent blocks with response format + const processedBlockStates = Object.entries(currentBlockStates).reduce( + (acc, [blockId, blockState]) => { + // Check if this block has a responseFormat that needs to be parsed + if (blockState.responseFormat && typeof blockState.responseFormat === 'string') { + try { + console.log( + `[API Debug] Block ${blockId} has responseFormat as string:`, + blockState.responseFormat + ) + // Attempt to parse the responseFormat if it's a string + const parsedResponseFormat = JSON.parse(blockState.responseFormat) + console.log( + `[API Debug] Successfully parsed responseFormat for block ${blockId}:`, + parsedResponseFormat + ) + + acc[blockId] = { + ...blockState, + responseFormat: parsedResponseFormat, + } + } catch (error) { + console.warn(`Failed to parse responseFormat for block ${blockId}:`, error) + acc[blockId] = blockState + } + } else { + acc[blockId] = blockState + } + return acc + }, + {} as Record> + ) + // Serialize and execute the workflow const serializedWorkflow = new Serializer().serializeWorkflow(mergedStates, edges, loops) - const executor = new Executor(serializedWorkflow, currentBlockStates, decryptedEnvVars, input) + const executor = new Executor(serializedWorkflow, processedBlockStates, decryptedEnvVars, input) const result = await executor.execute(workflowId) // Log each execution step and the final result diff --git a/app/w/[id]/hooks/use-workflow-execution.ts b/app/w/[id]/hooks/use-workflow-execution.ts index 3391d0ecd3..39d2ddfbb1 100644 --- a/app/w/[id]/hooks/use-workflow-execution.ts +++ b/app/w/[id]/hooks/use-workflow-execution.ts @@ -120,6 +120,20 @@ export function useWorkflowExecution() { useSubBlockStore.getState().workflowValues[activeWorkflowId] ) console.log('Merged Block States for Execution:', currentBlockStates) + + // Debug any responseFormat fields + const blockWithResponseFormat = Object.entries(currentBlockStates).find( + ([_id, state]) => state.responseFormat + ) + if (blockWithResponseFormat) { + console.log( + 'ResponseFormat found:', + blockWithResponseFormat[0], + typeof blockWithResponseFormat[1].responseFormat, + blockWithResponseFormat[1].responseFormat + ) + } + console.groupEnd() // Get environment variables diff --git a/executor/handlers.ts b/executor/handlers.ts index 5ae0b8b888..d70cb0986f 100644 --- a/executor/handlers.ts +++ b/executor/handlers.ts @@ -106,21 +106,42 @@ export class AgentBlockHandler implements BlockHandler { inputs: Record, context: ExecutionContext ): Promise { + console.log(`[AgentBlockHandler Debug] Executing agent block: ${block.id}`) + console.log(`[AgentBlockHandler Debug] Block inputs:`, JSON.stringify(inputs, null, 2)) + // Parse response format if provided let responseFormat: any = undefined if (inputs.responseFormat) { - try { - responseFormat = - typeof inputs.responseFormat === 'string' - ? JSON.parse(inputs.responseFormat) - : inputs.responseFormat - } catch (error: any) { - throw new Error(`Invalid response format: ${error.message}`) + // Handle empty string case - treat it as no response format + if (inputs.responseFormat === '') { + console.log( + `[AgentBlockHandler Debug] Response format is an empty string, treating as undefined` + ) + responseFormat = undefined + } else { + try { + console.log( + `[AgentBlockHandler Debug] Response format before parsing:`, + typeof inputs.responseFormat, + inputs.responseFormat + ) + + responseFormat = + typeof inputs.responseFormat === 'string' + ? JSON.parse(inputs.responseFormat) + : inputs.responseFormat + + console.log(`[AgentBlockHandler Debug] Response format after parsing:`, responseFormat) + } catch (error: any) { + console.error(`[AgentBlockHandler Error] Failed to parse response format:`, error) + throw new Error(`Invalid response format: ${error.message}`) + } } } const model = inputs.model || 'gpt-4o' const providerId = getProviderFromModel(model) + console.log(`[AgentBlockHandler Debug] Using provider: ${providerId}, model: ${model}`) // Format tools for provider API const formattedTools = Array.isArray(inputs.tools) @@ -208,10 +229,27 @@ export class AgentBlockHandler implements BlockHandler { responseFormat, }) - // Return structured or standard response based on responseFormat - return responseFormat - ? { - ...JSON.parse(response.content), + console.log(`[AgentBlockHandler Debug] Provider response:`, { + content: response.content, + contentType: typeof response.content, + contentLength: response.content ? response.content.length : 0, + model: response.model, + hasTokens: !!response.tokens, + hasToolCalls: !!response.toolCalls, + }) + + // For structured responses, try to parse the content + if (responseFormat) { + try { + console.log( + `[AgentBlockHandler Debug] Attempting to parse response content as JSON:`, + response.content + ) + const parsedContent = JSON.parse(response.content) + console.log(`[AgentBlockHandler Debug] Successfully parsed content:`, parsedContent) + + return { + ...parsedContent, tokens: response.tokens || { prompt: 0, completion: 0, @@ -224,7 +262,12 @@ export class AgentBlockHandler implements BlockHandler { } : undefined, } - : { + } catch (error) { + console.error(`[AgentBlockHandler Error] Failed to parse response content:`, error) + console.log(`[AgentBlockHandler Debug] Falling back to standard response format`) + + // Fall back to standard response if parsing fails + return { response: { content: response.content, model: response.model, @@ -239,6 +282,25 @@ export class AgentBlockHandler implements BlockHandler { }, }, } + } + } + + // Return standard response if no responseFormat + return { + response: { + content: response.content, + model: response.model, + tokens: response.tokens || { + prompt: 0, + completion: 0, + total: 0, + }, + toolCalls: { + list: response.toolCalls || [], + count: response.toolCalls?.length || 0, + }, + }, + } } } diff --git a/providers/index.ts b/providers/index.ts index 595b070a10..3ffd05f638 100644 --- a/providers/index.ts +++ b/providers/index.ts @@ -5,6 +5,9 @@ export async function executeProviderRequest( providerId: string, request: ProviderRequest ): Promise { + console.log(`[Provider Debug] Executing request with provider: ${providerId}`) + console.log(`[Provider Debug] Request has responseFormat:`, !!request.responseFormat) + const provider = getProvider(providerId) if (!provider) { throw new Error(`Provider not found: ${providerId}`) @@ -16,17 +19,39 @@ export async function executeProviderRequest( // If responseFormat is provided, modify the system prompt to enforce structured output if (request.responseFormat) { - const structuredOutputInstructions = generateStructuredOutputInstructions( - request.responseFormat - ) + // Handle empty string case + if (typeof request.responseFormat === 'string' && request.responseFormat === '') { + console.log(`[Provider Debug] Response format is an empty string, ignoring it`) + request.responseFormat = undefined + } else { + console.log(`[Provider Debug] Response format:`, request.responseFormat) - // Only add additional instructions if they're not empty - if (structuredOutputInstructions.trim()) { - request.systemPrompt = - `${request.systemPrompt || ''}\n\n${structuredOutputInstructions}`.trim() + const structuredOutputInstructions = generateStructuredOutputInstructions( + request.responseFormat + ) + + console.log(`[Provider Debug] Generated instructions:`, structuredOutputInstructions) + + // Only add additional instructions if they're not empty + if (structuredOutputInstructions.trim()) { + const originalPrompt = request.systemPrompt || '' + request.systemPrompt = `${originalPrompt}\n\n${structuredOutputInstructions}`.trim() + + console.log(`[Provider Debug] Updated system prompt with instructions`) + } } } // Execute the request using the provider's implementation - return await provider.executeRequest(request) + const response = await provider.executeRequest(request) + + console.log(`[Provider Debug] Provider response:`, { + contentType: typeof response.content, + contentLength: response.content ? response.content.length : 0, + model: response.model, + hasTokens: !!response.tokens, + hasToolCalls: !!response.toolCalls, + }) + + return response }