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);
});
});
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 {
values: extractedValues,
valuesMessage:
extractedValues.length > 0
extractedValues.length > 0 && storedValuesResult.searchResults
? {
role: 'user',
content: storedValuesResult.searchResults,

View File

@ -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

View File

@ -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<SequentialThinkingOutput> {
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 }
);