From 95b67529ad8131c025eac5ed1e1963755d100925 Mon Sep 17 00:00:00 2001 From: dal Date: Fri, 15 Aug 2025 19:10:45 -0600 Subject: [PATCH] sequential thinking tie off the request. --- .../extract-values-search-step.int.test.ts | 47 +++++++++++++++++++ .../extract-values-search-step.ts | 2 +- ...quential-thinking-tool-transform-helper.ts | 10 ++-- .../sequential-thinking-tool-execute.ts | 36 ++++++++++---- 4 files changed, 80 insertions(+), 15 deletions(-) diff --git a/packages/ai/src/steps/analyst-agent-steps/extract-values-step/extract-values-search-step.int.test.ts b/packages/ai/src/steps/analyst-agent-steps/extract-values-step/extract-values-search-step.int.test.ts index 4e962e2bd..198983ba3 100644 --- a/packages/ai/src/steps/analyst-agent-steps/extract-values-step/extract-values-search-step.int.test.ts +++ b/packages/ai/src/steps/analyst-agent-steps/extract-values-step/extract-values-search-step.int.test.ts @@ -343,4 +343,51 @@ describe('extract-values-search-step integration', () => { expect(Array.isArray(result.values)).toBe(true); }); }); + + it('should not create valuesMessage with empty content when search returns no results', async () => { + const messages: ModelMessage[] = [ + { role: 'user', content: 'Show me sales for Red Bull' }, + ]; + + // Test with a dataSourceId that would trigger search but return empty results + const params = { + messages, + dataSourceId: 'test-datasource-id', + }; + + const result = await runExtractValuesAndSearchStep(params); + + expect(result).toBeDefined(); + expect(result.values).toBeDefined(); + + // If valuesMessage exists, it should have non-empty content + if (result.valuesMessage) { + expect(result.valuesMessage.content).toBeTruthy(); + expect(typeof result.valuesMessage.content).toBe('string'); + expect((result.valuesMessage.content as string).length).toBeGreaterThan(0); + } + }); + + it('should not create valuesMessage when extracted values exist but search results are empty', async () => { + const messages: ModelMessage[] = [ + { role: 'user', content: 'Show me Nike and Adidas products' }, + ]; + + const params = { + messages, + dataSourceId: 'test-datasource-id', + }; + + const result = await runExtractValuesAndSearchStep(params); + + expect(result).toBeDefined(); + expect(result.values).toBeDefined(); + + // Verify that if we have values but no search results, valuesMessage is undefined + // This prevents empty string messages from being created + if (result.values.length > 0 && result.valuesMessage) { + expect(result.valuesMessage.content).toBeTruthy(); + expect((result.valuesMessage.content as string).trim()).not.toBe(''); + } + }); }); diff --git a/packages/ai/src/steps/analyst-agent-steps/extract-values-step/extract-values-search-step.ts b/packages/ai/src/steps/analyst-agent-steps/extract-values-step/extract-values-search-step.ts index 3f06109bc..c83da7b2a 100644 --- a/packages/ai/src/steps/analyst-agent-steps/extract-values-step/extract-values-search-step.ts +++ b/packages/ai/src/steps/analyst-agent-steps/extract-values-step/extract-values-search-step.ts @@ -185,7 +185,7 @@ export async function runExtractValuesAndSearchStep( return { values: extractedValues, valuesMessage: - extractedValues.length > 0 + extractedValues.length > 0 && storedValuesResult.searchResults ? { role: 'user', content: storedValuesResult.searchResults, diff --git a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/helpers/sequential-thinking-tool-transform-helper.ts b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/helpers/sequential-thinking-tool-transform-helper.ts index e2cacc82f..84f0a22b3 100644 --- a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/helpers/sequential-thinking-tool-transform-helper.ts +++ b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/helpers/sequential-thinking-tool-transform-helper.ts @@ -12,14 +12,14 @@ import type { SequentialThinkingState } from '../sequential-thinking-tool'; */ export function createSequentialThinkingReasoningMessage( sequentialThinkingState: SequentialThinkingState, - toolCallId?: string, + toolCallId: string, status: ChatMessageReasoning_status = 'loading' -): ChatMessageReasoningMessage_Text | null { +): ChatMessageReasoningMessage_Text { // Use entry_id from state or fallback to provided toolCallId const id = toolCallId; if (!id) { - return null; + throw new Error('Tool call ID is required'); } // Determine title based on status @@ -49,11 +49,11 @@ export function createSequentialThinkingReasoningMessage( export function createSequentialThinkingRawLlmMessageEntry( sequentialThinkingState: SequentialThinkingState, toolCallId?: string -): ModelMessage | null { +): ModelMessage { const id = toolCallId; if (!id) { - return null; + throw new Error('Tool call ID is required'); } // Build the input object with available state diff --git a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-execute.ts b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-execute.ts index 43e372044..2e7b3b6cf 100644 --- a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-execute.ts +++ b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-execute.ts @@ -1,4 +1,4 @@ -import { updateMessageEntries } from '@buster/database'; +import { type UpdateMessageEntriesParams, updateMessageEntries } from '@buster/database'; import { wrapTraced } from 'braintrust'; import { normalizeEscapedText } from '../../../utils/streaming/escape-normalizer'; import { createRawToolResultEntry } from '../../shared/create-raw-llm-tool-result-entry'; @@ -16,24 +16,42 @@ import { // Process sequential thinking execution async function processSequentialThinking( - toolCallId: string, - messageId: string + state: SequentialThinkingState, + context: SequentialThinkingContext ): Promise { const output: SequentialThinkingOutput = { success: true, }; + const entries: UpdateMessageEntriesParams = { + messageId: context.messageId, + reasoningMessages: [], + rawLlmMessages: [], + }; + + const reasoningEntry = createSequentialThinkingReasoningMessage( + state, + context.messageId, + 'completed' + ); + const rawLlmMessage = createSequentialThinkingRawLlmMessageEntry(state, context.messageId); + const rawToolResultEntry = createRawToolResultEntry( - toolCallId, + context.messageId, SEQUENTIAL_THINKING_TOOL_NAME, output ); + if (reasoningEntry) { + entries.reasoningMessages = [reasoningEntry]; + } + + if (rawLlmMessage) { + entries.rawLlmMessages = [rawLlmMessage, rawToolResultEntry]; + } + try { - await updateMessageEntries({ - messageId, - rawLlmMessages: [rawToolResultEntry], - }); + await updateMessageEntries(entries); } catch (error) { console.error('[sequential-thinking] Error updating message entries:', error); } @@ -54,7 +72,7 @@ export function createSequentialThinkingExecute( throw new Error('Tool call ID is required'); } - return await processSequentialThinking(state.toolCallId, context.messageId); + return await processSequentialThinking(state, context); }, { name: SEQUENTIAL_THINKING_TOOL_NAME } );