sequential thinking tie off the request.

This commit is contained in:
dal 2025-08-15 19:10:45 -06:00
parent 4c042a34fa
commit 95b67529ad
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
4 changed files with 80 additions and 15 deletions

View File

@ -343,4 +343,51 @@ describe('extract-values-search-step integration', () => {
expect(Array.isArray(result.values)).toBe(true); 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('');
}
});
}); });

View File

@ -185,7 +185,7 @@ export async function runExtractValuesAndSearchStep(
return { return {
values: extractedValues, values: extractedValues,
valuesMessage: valuesMessage:
extractedValues.length > 0 extractedValues.length > 0 && storedValuesResult.searchResults
? { ? {
role: 'user', role: 'user',
content: storedValuesResult.searchResults, content: storedValuesResult.searchResults,

View File

@ -12,14 +12,14 @@ import type { SequentialThinkingState } from '../sequential-thinking-tool';
*/ */
export function createSequentialThinkingReasoningMessage( export function createSequentialThinkingReasoningMessage(
sequentialThinkingState: SequentialThinkingState, sequentialThinkingState: SequentialThinkingState,
toolCallId?: string, toolCallId: string,
status: ChatMessageReasoning_status = 'loading' status: ChatMessageReasoning_status = 'loading'
): ChatMessageReasoningMessage_Text | null { ): ChatMessageReasoningMessage_Text {
// Use entry_id from state or fallback to provided toolCallId // Use entry_id from state or fallback to provided toolCallId
const id = toolCallId; const id = toolCallId;
if (!id) { if (!id) {
return null; throw new Error('Tool call ID is required');
} }
// Determine title based on status // Determine title based on status
@ -49,11 +49,11 @@ export function createSequentialThinkingReasoningMessage(
export function createSequentialThinkingRawLlmMessageEntry( export function createSequentialThinkingRawLlmMessageEntry(
sequentialThinkingState: SequentialThinkingState, sequentialThinkingState: SequentialThinkingState,
toolCallId?: string toolCallId?: string
): ModelMessage | null { ): ModelMessage {
const id = toolCallId; const id = toolCallId;
if (!id) { if (!id) {
return null; throw new Error('Tool call ID is required');
} }
// Build the input object with available state // Build the input object with available state

View File

@ -1,4 +1,4 @@
import { updateMessageEntries } from '@buster/database'; import { type UpdateMessageEntriesParams, updateMessageEntries } from '@buster/database';
import { wrapTraced } from 'braintrust'; import { wrapTraced } from 'braintrust';
import { normalizeEscapedText } from '../../../utils/streaming/escape-normalizer'; import { normalizeEscapedText } from '../../../utils/streaming/escape-normalizer';
import { createRawToolResultEntry } from '../../shared/create-raw-llm-tool-result-entry'; import { createRawToolResultEntry } from '../../shared/create-raw-llm-tool-result-entry';
@ -16,24 +16,42 @@ import {
// Process sequential thinking execution // Process sequential thinking execution
async function processSequentialThinking( async function processSequentialThinking(
toolCallId: string, state: SequentialThinkingState,
messageId: string context: SequentialThinkingContext
): Promise<SequentialThinkingOutput> { ): Promise<SequentialThinkingOutput> {
const output: SequentialThinkingOutput = { const output: SequentialThinkingOutput = {
success: true, 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( const rawToolResultEntry = createRawToolResultEntry(
toolCallId, context.messageId,
SEQUENTIAL_THINKING_TOOL_NAME, SEQUENTIAL_THINKING_TOOL_NAME,
output output
); );
if (reasoningEntry) {
entries.reasoningMessages = [reasoningEntry];
}
if (rawLlmMessage) {
entries.rawLlmMessages = [rawLlmMessage, rawToolResultEntry];
}
try { try {
await updateMessageEntries({ await updateMessageEntries(entries);
messageId,
rawLlmMessages: [rawToolResultEntry],
});
} catch (error) { } catch (error) {
console.error('[sequential-thinking] Error updating message entries:', 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'); 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 } { name: SEQUENTIAL_THINKING_TOOL_NAME }
); );