streaming fix on non done tool responses

This commit is contained in:
dal 2025-09-26 10:47:04 -06:00
parent f0686fa54e
commit aa6fbfd437
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
5 changed files with 47 additions and 28 deletions

View File

@ -1,4 +1,4 @@
import { updateMessageEntries } from '@buster/database/queries'; import { updateMessage, updateMessageEntries } from '@buster/database/queries';
import { wrapTraced } from 'braintrust'; import { wrapTraced } from 'braintrust';
import { cleanupState } from '../../shared/cleanup-state'; import { cleanupState } from '../../shared/cleanup-state';
import { createRawToolResultEntry } from '../../shared/create-raw-llm-tool-result-entry'; import { createRawToolResultEntry } from '../../shared/create-raw-llm-tool-result-entry';
@ -39,6 +39,11 @@ async function processMessageUserClarifyingQuestion(
messageId, messageId,
rawLlmMessages, rawLlmMessages,
}); });
// Mark the message as completed
await updateMessage(messageId, {
isCompleted: true,
});
} catch (error) { } catch (error) {
console.error('[message-user-clarifying-question] Error updating message entries:', error); console.error('[message-user-clarifying-question] Error updating message entries:', error);
} }
@ -53,15 +58,18 @@ export function createMessageUserClarifyingQuestionExecute(
) { ) {
return wrapTraced( return wrapTraced(
async ( async (
_input: MessageUserClarifyingQuestionInput _input: MessageUserClarifyingQuestionInput,
options?: { toolCallId?: string }
): Promise<MessageUserClarifyingQuestionOutput> => { ): Promise<MessageUserClarifyingQuestionOutput> => {
if (!state.toolCallId) { // Use toolCallId from state if available, otherwise from options
const toolCallId = state.toolCallId || options?.toolCallId;
if (!toolCallId) {
throw new Error('Tool call ID is required'); throw new Error('Tool call ID is required');
} }
const result = await processMessageUserClarifyingQuestion( const result = await processMessageUserClarifyingQuestion(
state, state,
state.toolCallId, toolCallId,
context.messageId context.messageId
); );
cleanupState(state); cleanupState(state);

View File

@ -66,7 +66,6 @@ export function createMessageUserClarifyingQuestionStart(
} }
await updateMessage(context.messageId, { await updateMessage(context.messageId, {
isCompleted: true,
finalReasoningMessage: `Reasoned for ${timeString}`, finalReasoningMessage: `Reasoned for ${timeString}`,
}); });
} }

View File

@ -1,4 +1,4 @@
import { updateMessageEntries } from '@buster/database/queries'; import { updateMessage, updateMessageEntries } from '@buster/database/queries';
import { wrapTraced } from 'braintrust'; import { wrapTraced } from 'braintrust';
import { cleanupState } from '../../shared/cleanup-state'; import { cleanupState } from '../../shared/cleanup-state';
import { createRawToolResultEntry } from '../../shared/create-raw-llm-tool-result-entry'; import { createRawToolResultEntry } from '../../shared/create-raw-llm-tool-result-entry';
@ -38,6 +38,11 @@ async function processRespondWithoutAssetCreation(
messageId, messageId,
rawLlmMessages, rawLlmMessages,
}); });
// Mark the message as completed
await updateMessage(messageId, {
isCompleted: true,
});
} catch (error) { } catch (error) {
console.error('[respond-without-asset-creation] Error updating message entries:', error); console.error('[respond-without-asset-creation] Error updating message entries:', error);
} }
@ -52,17 +57,16 @@ export function createRespondWithoutAssetCreationExecute(
) { ) {
return wrapTraced( return wrapTraced(
async ( async (
_input: RespondWithoutAssetCreationInput _input: RespondWithoutAssetCreationInput,
options?: { toolCallId?: string }
): Promise<RespondWithoutAssetCreationOutput> => { ): Promise<RespondWithoutAssetCreationOutput> => {
if (!state.toolCallId) { // Use toolCallId from state if available, otherwise from options
const toolCallId = state.toolCallId || options?.toolCallId;
if (!toolCallId) {
throw new Error('Tool call ID is required'); throw new Error('Tool call ID is required');
} }
const result = await processRespondWithoutAssetCreation( const result = await processRespondWithoutAssetCreation(state, toolCallId, context.messageId);
state,
state.toolCallId,
context.messageId
);
cleanupState(state); cleanupState(state);
return result; return result;
}, },

View File

@ -64,7 +64,6 @@ export function createRespondWithoutAssetCreationStart(
} }
await updateMessage(context.messageId, { await updateMessage(context.messageId, {
isCompleted: true,
finalReasoningMessage: `Reasoned for ${timeString}`, finalReasoningMessage: `Reasoned for ${timeString}`,
}); });
} }

View File

@ -11,6 +11,12 @@ vi.mock('braintrust', () => ({
wrapTraced: (fn: unknown) => fn, wrapTraced: (fn: unknown) => fn,
})); }));
// Mock database operations for tests
vi.mock('@buster/database/queries', () => ({
updateMessage: vi.fn().mockResolvedValue({ success: true }),
updateMessageEntries: vi.fn().mockResolvedValue({ success: true }),
}));
describe('Respond Without Asset Creation Tool Integration Tests', () => { describe('Respond Without Asset Creation Tool Integration Tests', () => {
describe('Tool Creation and Configuration', () => { describe('Tool Creation and Configuration', () => {
test('should create tool with minimal context', () => { test('should create tool with minimal context', () => {
@ -130,7 +136,7 @@ describe('Respond Without Asset Creation Tool Integration Tests', () => {
} as ToolCallOptions); } as ToolCallOptions);
expect(result).toBeDefined(); expect(result).toBeDefined();
expect(result).toEqual({}); expect(result).toEqual({ success: true });
} }
}); });
@ -152,7 +158,7 @@ describe('Respond Without Asset Creation Tool Integration Tests', () => {
} as ToolCallOptions); } as ToolCallOptions);
expect(result).toBeDefined(); expect(result).toBeDefined();
expect(result).toEqual({}); expect(result).toEqual({ success: true });
} }
}); });
@ -177,7 +183,7 @@ Escaped: \n \t \\ \" \'
} as ToolCallOptions); } as ToolCallOptions);
expect(result).toBeDefined(); expect(result).toBeDefined();
expect(result).toEqual({}); expect(result).toEqual({ success: true });
} }
}); });
@ -198,7 +204,7 @@ Escaped: \n \t \\ \" \'
} as ToolCallOptions); } as ToolCallOptions);
expect(result).toBeDefined(); expect(result).toBeDefined();
expect(result).toEqual({}); expect(result).toEqual({ success: true });
} }
}); });
}); });
@ -238,28 +244,31 @@ Escaped: \n \t \\ \" \'
tool2.execute(input2, { toolCallId: 'tc-2', messages: [] } as ToolCallOptions), tool2.execute(input2, { toolCallId: 'tc-2', messages: [] } as ToolCallOptions),
]); ]);
expect(result1).toEqual({}); expect(result1).toEqual({ success: true });
expect(result2).toEqual({}); expect(result2).toEqual({ success: true });
} }
}); });
}); });
describe('Output Schema Validation', () => { describe('Output Schema Validation', () => {
test('should have empty output schema', () => { test('should validate output schema with success field', () => {
const outputSchema = RespondWithoutAssetCreationOutputSchema; const outputSchema = RespondWithoutAssetCreationOutputSchema;
const emptyOutput = {}; const validOutput = { success: true };
expect(() => outputSchema.parse(emptyOutput)).not.toThrow(); expect(() => outputSchema.parse(validOutput)).not.toThrow();
}); });
test('should accept empty output only', () => { test('should require success field in output', () => {
const outputSchema = RespondWithoutAssetCreationOutputSchema; const outputSchema = RespondWithoutAssetCreationOutputSchema;
// The output schema is z.object({}) which accepts empty objects // The output schema requires a success boolean field
// Additional properties are allowed in Zod by default unless strict() is used const validOutput = { success: true };
const result = outputSchema.parse(validOutput);
expect(result).toEqual({ success: true });
// Should throw for empty object
const emptyOutput = {}; const emptyOutput = {};
const result = outputSchema.parse(emptyOutput); expect(() => outputSchema.parse(emptyOutput)).toThrow();
expect(result).toEqual({});
}); });
}); });