well this at least allows for streaming responses when no tool calls are made

This commit is contained in:
perf3ct 2025-04-13 17:56:57 +00:00
parent 263c869091
commit d1edf59f97
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
4 changed files with 85 additions and 26 deletions

View File

@ -87,9 +87,13 @@ export async function setupStreamingResponse(
// Handle content updates
if (message.content) {
receivedAnyContent = true;
console.log(`[${responseId}] Received content chunk of length ${message.content.length}, preview: "${message.content.substring(0, 50)}${message.content.length > 50 ? '...' : ''}"`);
// Add to our accumulated response
assistantResponse += message.content;
// Update the UI immediately
// Update the UI immediately with each chunk
onContentUpdate(assistantResponse);
// Reset timeout since we got content

View File

@ -430,13 +430,27 @@ export class OllamaService extends BaseAIService {
// Call the callback with the current chunk content
if (opts.streamCallback) {
await StreamProcessor.sendChunkToCallback(
opts.streamCallback,
chunk.message?.content || '',
false, // Never mark as done during processing
chunk,
chunkCount
);
// For chunks with content, send the content directly
if (chunk.message?.content) {
log.info(`Sending direct chunk #${chunkCount} with content: "${chunk.message.content.substring(0, 50)}${chunk.message.content.length > 50 ? '...' : ''}"`);
await StreamProcessor.sendChunkToCallback(
opts.streamCallback,
chunk.message.content,
!!chunk.done, // Mark as done if done flag is set
chunk,
chunkCount
);
} else if (chunk.done) {
// Send empty done message for final chunk with no content
await StreamProcessor.sendChunkToCallback(
opts.streamCallback,
'',
true,
chunk,
chunkCount
);
}
}
// If this is the done chunk, log it
@ -446,7 +460,9 @@ export class OllamaService extends BaseAIService {
}
// Send one final callback with done=true after all chunks have been processed
if (opts.streamCallback) {
// Only send this if the last chunk didn't already have done=true
if (opts.streamCallback && (!finalChunk || !finalChunk.done)) {
log.info(`Sending explicit final callback with done=true flag after all chunks processed`);
await StreamProcessor.sendFinalCallback(opts.streamCallback, completeText);
}

View File

@ -40,9 +40,9 @@ export class StreamProcessor {
let textToAdd = '';
let logged = false;
// Log first chunk and periodic updates
if (chunkCount === 1 || chunkCount % 10 === 0) {
log.info(`Processing ${options.providerName} stream chunk #${chunkCount}, done=${!!chunk.done}, has content=${!!chunk.message?.content}`);
// Enhanced logging for content chunks and completion status
if (chunkCount === 1 || chunkCount % 10 === 0 || chunk.done) {
log.info(`Processing ${options.providerName} stream chunk #${chunkCount}, done=${!!chunk.done}, has content=${!!chunk.message?.content}, content length=${chunk.message?.content?.length || 0}`);
logged = true;
}
@ -52,10 +52,19 @@ export class StreamProcessor {
const newCompleteText = completeText + textToAdd;
if (chunkCount === 1) {
log.info(`First content chunk: "${textToAdd.substring(0, 50)}${textToAdd.length > 50 ? '...' : ''}"`);
// Log the first chunk more verbosely for debugging
log.info(`First content chunk [${chunk.message.content.length} chars]: "${textToAdd.substring(0, 100)}${textToAdd.length > 100 ? '...' : ''}"`);
}
// For final chunks with done=true, log more information
if (chunk.done) {
log.info(`Final content chunk received with done=true flag. Length: ${chunk.message.content.length}`);
}
return { completeText: newCompleteText, logged };
} else if (chunk.done) {
// If it's the final chunk with no content, log this case
log.info(`Empty final chunk received with done=true flag`);
}
return { completeText, logged };
@ -72,7 +81,15 @@ export class StreamProcessor {
chunkNumber: number
): Promise<void> {
try {
const result = callback(content || '', done, chunk);
// Log all done=true callbacks and first chunk for debugging
if (done || chunkNumber === 1) {
log.info(`Sending chunk to callback: chunkNumber=${chunkNumber}, contentLength=${content?.length || 0}, done=${done}`);
}
// Always make sure we have a string for content
const safeContent = content || '';
const result = callback(safeContent, done, chunk);
// Handle both Promise and void return types
if (result instanceof Promise) {
await result;
@ -81,6 +98,10 @@ export class StreamProcessor {
if (chunkNumber === 1) {
log.info(`Successfully called streamCallback with first chunk`);
}
if (done) {
log.info(`Successfully called streamCallback with done=true flag`);
}
} catch (callbackError) {
log.error(`Error in streamCallback: ${callbackError}`);
}
@ -94,12 +115,18 @@ export class StreamProcessor {
completeText: string
): Promise<void> {
try {
log.info(`Sending final done=true callback after processing all chunks`);
const result = callback('', true, { done: true });
log.info(`Sending explicit final done=true callback after processing all chunks. Complete text length: ${completeText?.length || 0}`);
// Pass the complete text instead of empty string for better UX
// The client will know it's done based on the done=true flag
const result = callback(completeText || '', true, { done: true, complete: true });
// Handle both Promise and void return types
if (result instanceof Promise) {
await result;
}
log.info(`Final callback sent successfully with done=true flag`);
} catch (finalCallbackError) {
log.error(`Error in final streamCallback: ${finalCallbackError}`);
}

View File

@ -1163,18 +1163,22 @@ class RestChatService {
if (chunk.text) {
messageContent += chunk.text;
// Send the chunk content via WebSocket
// Enhanced logging for each chunk
log.info(`Received stream chunk from ${service.getName()} with ${chunk.text.length} chars of text, done=${!!chunk.done}`);
// Send each individual chunk via WebSocket as it arrives
wsService.sendMessageToAllClients({
type: 'llm-stream',
sessionId,
content: chunk.text,
done: !!chunk.done, // Include done flag with each chunk
// Include any raw data from the provider that might contain thinking/tool info
...(chunk.raw ? { raw: chunk.raw } : {})
} as LLMStreamMessage);
// Log the first chunk (useful for debugging)
if (messageContent.length === chunk.text.length) {
log.info(`First stream chunk received from ${service.getName()}`);
log.info(`First stream chunk received from ${service.getName()}: "${chunk.text.substring(0, 50)}${chunk.text.length > 50 ? '...' : ''}"`);
}
}
@ -1198,15 +1202,23 @@ class RestChatService {
// Signal completion when done
if (chunk.done) {
log.info(`Stream completed from ${service.getName()}`);
log.info(`Stream completed from ${service.getName()}, total content: ${messageContent.length} chars`);
// Send the final message with both content and done flag together
wsService.sendMessageToAllClients({
type: 'llm-stream',
sessionId,
content: messageContent, // Send the accumulated content
done: true
} as LLMStreamMessage);
// Only send final done message if it wasn't already sent with content
// This ensures we don't duplicate the content but still mark completion
if (!chunk.text) {
// Send final message with both content and done flag together
wsService.sendMessageToAllClients({
type: 'llm-stream',
sessionId,
content: messageContent, // Send the accumulated content
done: true
} as LLMStreamMessage);
log.info(`Sent explicit final completion message with accumulated content`);
} else {
log.info(`Final done flag was already sent with content chunk, no need for extra message`);
}
}
});