mirror of https://github.com/buster-so/buster.git
232 lines
7.7 KiB
TypeScript
232 lines
7.7 KiB
TypeScript
import { RuntimeContext } from '@mastra/core/runtime-context';
|
|
import { initLogger } from 'braintrust';
|
|
import { afterAll, beforeAll, describe, expect, test } from 'vitest';
|
|
import { thinkAndPrepStep } from '../../../src/steps/think-and-prep-step';
|
|
import type { AnalystRuntimeContext } from '../../../src/workflows/analyst-workflow';
|
|
|
|
describe('Think and Prep Step - Todos in Message History Integration', { timeout: 30000 }, () => {
|
|
beforeAll(() => {
|
|
initLogger({
|
|
apiKey: process.env.BRAINTRUST_KEY || '',
|
|
projectName: 'THINK-AND-PREP-TODOS',
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
});
|
|
|
|
test('should inject todos into message history as tool calls', async () => {
|
|
const runtimeContext = new RuntimeContext<AnalystRuntimeContext>([
|
|
['userId', '550e8400-e29b-41d4-a716-446655440000'],
|
|
['chatId', '550e8400-e29b-41d4-a716-446655440001'],
|
|
['dataSourceId', '550e8400-e29b-41d4-a716-446655440002'],
|
|
['dataSourceSyntax', 'postgres'],
|
|
['organizationId', '550e8400-e29b-41d4-a716-446655440003'],
|
|
]);
|
|
|
|
const todos = `[ ] Determine how "sales" is identified
|
|
[ ] Determine the time frame for analysis
|
|
[ ] Determine the visualization type and axes`;
|
|
|
|
const inputData = {
|
|
'create-todos': { todos },
|
|
'extract-values-search': {
|
|
values: [],
|
|
searchResults: '',
|
|
foundValues: {},
|
|
searchPerformed: false,
|
|
},
|
|
'generate-chat-title': { chatTitle: 'Test Sales Analysis' },
|
|
prompt: 'Show me sales data for the last quarter',
|
|
conversationHistory: [],
|
|
};
|
|
|
|
const getInitData = async () => inputData;
|
|
|
|
// Execute the step
|
|
const result = await thinkAndPrepStep.execute({
|
|
inputData,
|
|
getInitData,
|
|
runtimeContext,
|
|
});
|
|
|
|
// Verify the output messages contain the todos
|
|
expect(result.outputMessages).toBeDefined();
|
|
expect(Array.isArray(result.outputMessages)).toBe(true);
|
|
|
|
// Find the assistant message with tool call
|
|
const todoToolCallMessage = result.outputMessages.find(
|
|
(msg) =>
|
|
msg.role === 'assistant' &&
|
|
Array.isArray(msg.content) &&
|
|
msg.content.some((c) => c.type === 'tool-call' && c.toolName === 'createTodos')
|
|
);
|
|
|
|
expect(todoToolCallMessage).toBeDefined();
|
|
|
|
// Verify the tool call contains the todos
|
|
if (todoToolCallMessage && Array.isArray(todoToolCallMessage.content)) {
|
|
const toolCall = todoToolCallMessage.content.find(
|
|
(c): c is any => c.type === 'tool-call' && (c as any).toolName === 'createTodos'
|
|
);
|
|
|
|
expect(toolCall).toBeDefined();
|
|
if (toolCall) {
|
|
expect(toolCall.args?.todos).toBe(todos);
|
|
expect(toolCall.toolCallId).toBe('create-todos-call');
|
|
}
|
|
}
|
|
|
|
// Find the tool result message
|
|
const todoResultMessage = result.outputMessages.find(
|
|
(msg) =>
|
|
msg.role === 'tool' &&
|
|
Array.isArray(msg.content) &&
|
|
msg.content.some((c) => c.type === 'tool-result' && c.toolName === 'createTodos')
|
|
);
|
|
|
|
expect(todoResultMessage).toBeDefined();
|
|
|
|
// Verify the tool result contains success
|
|
if (todoResultMessage && Array.isArray(todoResultMessage.content)) {
|
|
const toolResult = todoResultMessage.content.find(
|
|
(c): c is any => c.type === 'tool-result' && (c as any).toolName === 'createTodos'
|
|
);
|
|
expect(toolResult).toBeDefined();
|
|
if (toolResult) {
|
|
expect(toolResult.result?.success).toBe(true);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('should handle conversation history with injected todos', async () => {
|
|
const runtimeContext = new RuntimeContext<AnalystRuntimeContext>([
|
|
['userId', '550e8400-e29b-41d4-a716-446655440000'],
|
|
['chatId', '550e8400-e29b-41d4-a716-446655440001'],
|
|
['dataSourceId', '550e8400-e29b-41d4-a716-446655440002'],
|
|
['dataSourceSyntax', 'postgres'],
|
|
['organizationId', '550e8400-e29b-41d4-a716-446655440003'],
|
|
]);
|
|
|
|
const todos = `[ ] Determine metric for "top customers"
|
|
[ ] Determine sorting criteria`;
|
|
|
|
const existingHistory = [
|
|
{ role: 'user' as const, content: 'Show me our revenue' },
|
|
{ role: 'assistant' as const, content: 'Here is your revenue data...' },
|
|
];
|
|
|
|
const inputData = {
|
|
'create-todos': { todos },
|
|
'extract-values-search': {
|
|
values: [],
|
|
searchResults: '',
|
|
foundValues: {},
|
|
searchPerformed: false,
|
|
},
|
|
'generate-chat-title': { chatTitle: 'Top Customers Analysis' },
|
|
prompt: 'Now show me our top customers',
|
|
conversationHistory: existingHistory,
|
|
};
|
|
|
|
const getInitData = async () => inputData;
|
|
|
|
// Execute the step
|
|
const result = await thinkAndPrepStep.execute({
|
|
inputData,
|
|
getInitData,
|
|
runtimeContext,
|
|
});
|
|
|
|
// Verify conversation history is preserved
|
|
expect(result.outputMessages.length).toBeGreaterThan(existingHistory.length);
|
|
|
|
// Verify original messages are preserved
|
|
const firstMessage = result.outputMessages[0];
|
|
const secondMessage = result.outputMessages[1];
|
|
const firstHistoryMessage = existingHistory[0];
|
|
const secondHistoryMessage = existingHistory[1];
|
|
|
|
if (firstMessage && firstHistoryMessage) {
|
|
expect(firstMessage).toEqual(firstHistoryMessage);
|
|
}
|
|
if (secondMessage && secondHistoryMessage) {
|
|
expect(secondMessage).toEqual(secondHistoryMessage);
|
|
}
|
|
|
|
// Verify new user message is added
|
|
const newUserMessage = result.outputMessages.find(
|
|
(msg, index) =>
|
|
index > 1 && msg.role === 'user' && msg.content === 'Now show me our top customers'
|
|
);
|
|
expect(newUserMessage).toBeDefined();
|
|
|
|
// Verify todos are injected after the conversation
|
|
const todoMessages = result.outputMessages.filter(
|
|
(msg) =>
|
|
(msg.role === 'assistant' &&
|
|
Array.isArray(msg.content) &&
|
|
msg.content.some(
|
|
(c) => c.type === 'tool-call' && (c as any).toolName === 'createTodos'
|
|
)) ||
|
|
(msg.role === 'tool' &&
|
|
Array.isArray(msg.content) &&
|
|
msg.content.some(
|
|
(c) => c.type === 'tool-result' && (c as any).toolName === 'createTodos'
|
|
))
|
|
);
|
|
|
|
expect(todoMessages).toHaveLength(2); // One tool call, one tool result
|
|
});
|
|
|
|
test('should properly format empty todos', async () => {
|
|
const runtimeContext = new RuntimeContext<AnalystRuntimeContext>([
|
|
['userId', '550e8400-e29b-41d4-a716-446655440000'],
|
|
['chatId', '550e8400-e29b-41d4-a716-446655440001'],
|
|
['dataSourceId', '550e8400-e29b-41d4-a716-446655440002'],
|
|
['dataSourceSyntax', 'postgres'],
|
|
['organizationId', '550e8400-e29b-41d4-a716-446655440003'],
|
|
]);
|
|
|
|
const inputData = {
|
|
'create-todos': { todos: '' },
|
|
'extract-values-search': {
|
|
values: [],
|
|
searchResults: '',
|
|
foundValues: {},
|
|
searchPerformed: false,
|
|
},
|
|
'generate-chat-title': { chatTitle: 'Empty Todos Test' },
|
|
prompt: 'Simple request',
|
|
conversationHistory: [],
|
|
};
|
|
|
|
const getInitData = async () => inputData;
|
|
|
|
// Execute the step
|
|
const result = await thinkAndPrepStep.execute({
|
|
inputData,
|
|
getInitData,
|
|
runtimeContext,
|
|
});
|
|
|
|
// Verify empty todos are still injected
|
|
const todoMessages = result.outputMessages.filter(
|
|
(msg) =>
|
|
(msg.role === 'assistant' &&
|
|
Array.isArray(msg.content) &&
|
|
msg.content.some(
|
|
(c) => c.type === 'tool-call' && (c as any).toolName === 'createTodos'
|
|
)) ||
|
|
(msg.role === 'tool' &&
|
|
Array.isArray(msg.content) &&
|
|
msg.content.some(
|
|
(c) => c.type === 'tool-result' && (c as any).toolName === 'createTodos'
|
|
))
|
|
);
|
|
|
|
expect(todoMessages).toHaveLength(2);
|
|
});
|
|
});
|