Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 3.8k
fix(deployed-chat): allow non-streaming responses in deployed chat, allow partial failure responses in deployed chat#833
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -14,7 +14,7 @@ import { getBlock } from '@/blocks' | ||
| import { db } from '@/db' | ||
| import { chat, environment as envTable, userStats, workflow } from '@/db/schema' | ||
| import { Executor } from '@/executor' | ||
| import type { BlockLog } from '@/executor/types' | ||
| import type { BlockLog, ExecutionResult } from '@/executor/types' | ||
| import { Serializer } from '@/serializer' | ||
| import { mergeSubblockState } from '@/stores/workflows/server-utils' | ||
| import type { WorkflowState } from '@/stores/workflows/workflow/types' | ||
| @@ -549,6 +549,7 @@ export async function executeWorkflowForChat( | ||
| async start(controller) { | ||
| const encoder = new TextEncoder() | ||
| const streamedContent = new Map<string, string>() | ||
| const streamedBlocks = new Set<string>() // Track which blocks have started streaming | ||
| const onStream = async (streamingExecution: any): Promise<void> => { | ||
| if (!streamingExecution.stream) return | ||
| @@ -557,6 +558,15 @@ export async function executeWorkflowForChat( | ||
| const reader = streamingExecution.stream.getReader() | ||
| if (blockId) { | ||
| streamedContent.set(blockId, '') | ||
| // Add separator if this is not the first block to stream | ||
| if (streamedBlocks.size > 0) { | ||
| // Send separator before the new block starts | ||
| controller.enqueue( | ||
| encoder.encode(`data: ${JSON.stringify({ blockId, chunk: '\n\n' })}\n\n`) | ||
| ) | ||
| } | ||
| streamedBlocks.add(blockId) | ||
| } | ||
| try { | ||
| while (true) { | ||
| @@ -615,25 +625,117 @@ export async function executeWorkflowForChat( | ||
| throw error | ||
| } | ||
| if (result && 'success' in result) { | ||
| // Update streamed content and apply tokenization | ||
| if (result.logs) { | ||
| result.logs.forEach((log: BlockLog) => { | ||
| if (streamedContent.has(log.blockId)) { | ||
| const content = streamedContent.get(log.blockId) | ||
| if (log.output) { | ||
| log.output.content = content | ||
| // Handle both ExecutionResult and StreamingExecution types | ||
| const executionResult = | ||
| result && typeof result === 'object' && 'execution' in result | ||
| ? (result.execution as ExecutionResult) | ||
| : (result as ExecutionResult) | ||
| if (executionResult?.logs) { | ||
| // Update streamed content and apply tokenization - process regardless of overall success | ||
| // This ensures partial successes (some agents succeed, some fail) still return results | ||
| // Add newlines between different agent outputs for better readability | ||
| const processedOutputs = new Set<string>() | ||
| executionResult.logs.forEach((log: BlockLog) => { | ||
| if (streamedContent.has(log.blockId)) { | ||
| const content = streamedContent.get(log.blockId) | ||
| if (log.output && content) { | ||
| // Add newline separation between different outputs (but not before the first one) | ||
| const separator = processedOutputs.size > 0 ? '\n\n' : '' | ||
| log.output.content = separator + content | ||
| processedOutputs.add(log.blockId) | ||
| } | ||
| } | ||
| }) | ||
| // Also process non-streamed outputs from selected blocks (like function blocks) | ||
| // This uses the same logic as the chat panel to ensure identical behavior | ||
| const nonStreamingLogs = executionResult.logs.filter( | ||
| (log: BlockLog) => !streamedContent.has(log.blockId) | ||
| ) | ||
| // Extract the exact same functions used by the chat panel | ||
| const extractBlockIdFromOutputId = (outputId: string): string => { | ||
| return outputId.includes('_') ? outputId.split('_')[0] : outputId.split('.')[0] | ||
| } | ||
| const extractPathFromOutputId = (outputId: string, blockId: string): string => { | ||
| return outputId.substring(blockId.length + 1) | ||
| } | ||
| const parseOutputContentSafely = (output: any): any => { | ||
| if (!output?.content) { | ||
| return output | ||
| } | ||
| if (typeof output.content === 'string') { | ||
| try { | ||
| return JSON.parse(output.content) | ||
| } catch (e) { | ||
| // Fallback to original structure if parsing fails | ||
| return output | ||
| } | ||
| } | ||
| return output | ||
| } | ||
| // Filter outputs that have matching logs (exactly like chat panel) | ||
| const outputsToRender = selectedOutputIds.filter((outputId) => { | ||
| const blockIdForOutput = extractBlockIdFromOutputId(outputId) | ||
| return nonStreamingLogs.some((log) => log.blockId === blockIdForOutput) | ||
| }) | ||
| // Process each selected output (exactly like chat panel) | ||
| for (const outputId of outputsToRender) { | ||
| const blockIdForOutput = extractBlockIdFromOutputId(outputId) | ||
| const path = extractPathFromOutputId(outputId, blockIdForOutput) | ||
| const log = nonStreamingLogs.find((l) => l.blockId === blockIdForOutput) | ||
| if (log) { | ||
| let outputValue: any = log.output | ||
| if (path) { | ||
| // Parse JSON content safely (exactly like chat panel) | ||
| outputValue = parseOutputContentSafely(outputValue) | ||
| const pathParts = path.split('.') | ||
| for (const part of pathParts) { | ||
| if (outputValue && typeof outputValue === 'object' && part in outputValue) { | ||
| outputValue = outputValue[part] | ||
| } else { | ||
| outputValue = undefined | ||
| break | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| // Process all logs for streaming tokenization | ||
| const processedCount = processStreamingBlockLogs(result.logs, streamedContent) | ||
| logger.info(`[CHAT-API] Processed ${processedCount} blocks for streaming tokenization`) | ||
| if (outputValue !== undefined) { | ||
| // Add newline separation between different outputs | ||
| const separator = processedOutputs.size > 0 ? '\n\n' : '' | ||
| // Format the output exactly like the chat panel | ||
| const formattedOutput = | ||
| typeof outputValue === 'string' ? outputValue : JSON.stringify(outputValue, null, 2) | ||
| // Update the log content | ||
| if (!log.output.content) { | ||
| log.output.content = separator + formattedOutput | ||
| } else { | ||
| log.output.content = separator + formattedOutput | ||
| } | ||
waleedlatif1 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| processedOutputs.add(log.blockId) | ||
| } | ||
| } | ||
| } | ||
| const { traceSpans, totalDuration } = buildTraceSpans(result) | ||
| const enrichedResult = { ...result, traceSpans, totalDuration } | ||
| // Process all logs for streaming tokenization | ||
| const processedCount = processStreamingBlockLogs(executionResult.logs, streamedContent) | ||
| logger.info(`Processed ${processedCount} blocks for streaming tokenization`) | ||
| const { traceSpans, totalDuration } = buildTraceSpans(executionResult) | ||
| const enrichedResult = { ...executionResult, traceSpans, totalDuration } | ||
| if (conversationId) { | ||
| if (!enrichedResult.metadata) { | ||
| enrichedResult.metadata = { | ||
| @@ -646,7 +748,7 @@ export async function executeWorkflowForChat( | ||
| const executionId = uuidv4() | ||
| logger.debug(`Generated execution ID for deployed chat: ${executionId}`) | ||
| if (result.success) { | ||
| if (executionResult.success) { | ||
| try { | ||
| await db | ||
| .update(userStats) | ||
| @@ -669,12 +771,12 @@ export async function executeWorkflowForChat( | ||
| } | ||
| // Complete logging session (for both success and failure) | ||
| if (result && 'success' in result) { | ||
| const { traceSpans } = buildTraceSpans(result) | ||
| if (executionResult?.logs) { | ||
| const { traceSpans } = buildTraceSpans(executionResult) | ||
| await loggingSession.safeComplete({ | ||
| endedAt: new Date().toISOString(), | ||
| totalDurationMs: result.metadata?.duration || 0, | ||
| finalOutput: result.output, | ||
| totalDurationMs: executionResult.metadata?.duration || 0, | ||
| finalOutput: executionResult.output, | ||
| traceSpans, | ||
| }) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.