mirror of https://github.com/buster-so/buster.git
183 lines
6.9 KiB
TypeScript
183 lines
6.9 KiB
TypeScript
import type { PermissionedDataset } from '@buster/access-controls';
|
|
import { type ModelMessage, hasToolCall, stepCountIs, streamText } from 'ai';
|
|
import { wrapTraced } from 'braintrust';
|
|
import z from 'zod';
|
|
import { Sonnet4 } from '../../llm';
|
|
import { DEFAULT_ANTHROPIC_OPTIONS } from '../../llm/providers/gateway';
|
|
import { createExecuteSqlTool, createSequentialThinkingTool } from '../../tools';
|
|
import {
|
|
MESSAGE_USER_CLARIFYING_QUESTION_TOOL_NAME,
|
|
createMessageUserClarifyingQuestionTool,
|
|
} from '../../tools/communication-tools/message-user-clarifying-question/message-user-clarifying-question';
|
|
import {
|
|
RESPOND_WITHOUT_ASSET_CREATION_TOOL_NAME,
|
|
createRespondWithoutAssetCreationTool,
|
|
} from '../../tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-tool';
|
|
import {
|
|
SUBMIT_THOUGHTS_TOOL_NAME,
|
|
createSubmitThoughtsTool,
|
|
} from '../../tools/communication-tools/submit-thoughts-tool/submit-thoughts-tool';
|
|
import { EXECUTE_SQL_TOOL_NAME } from '../../tools/database-tools/execute-sql/execute-sql';
|
|
import { SEQUENTIAL_THINKING_TOOL_NAME } from '../../tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool';
|
|
import { type AgentContext, repairToolCall } from '../../utils/tool-call-repair';
|
|
import {
|
|
type AnalysisMode,
|
|
getThinkAndPrepAgentSystemPrompt,
|
|
} from './get-think-and-prep-agent-system-prompt';
|
|
|
|
export const THINK_AND_PREP_AGENT_NAME = 'thinkAndPrepAgent';
|
|
|
|
const STOP_CONDITIONS = [
|
|
stepCountIs(25),
|
|
hasToolCall(SUBMIT_THOUGHTS_TOOL_NAME),
|
|
hasToolCall(RESPOND_WITHOUT_ASSET_CREATION_TOOL_NAME),
|
|
hasToolCall(MESSAGE_USER_CLARIFYING_QUESTION_TOOL_NAME),
|
|
];
|
|
|
|
export const ThinkAndPrepAgentOptionsSchema = z.object({
|
|
sql_dialect_guidance: z
|
|
.string()
|
|
.describe('The SQL dialect guidance for the think and prep agent.'),
|
|
messageId: z.string().describe('The message ID for tracking tool execution.'),
|
|
chatId: z.string().describe('The chat ID for tracking tool execution.'),
|
|
organizationId: z.string().describe('The organization ID for tracking tool execution.'),
|
|
dataSourceId: z.string().describe('The data source ID for tracking tool execution.'),
|
|
dataSourceSyntax: z.string().describe('The data source syntax for tracking tool execution.'),
|
|
userId: z.string().describe('The user ID for tracking tool execution.'),
|
|
datasets: z
|
|
.array(z.custom<PermissionedDataset>())
|
|
.describe('The datasets available to the user.'),
|
|
analysisMode: z
|
|
.enum(['standard', 'investigation'])
|
|
.default('standard')
|
|
.describe('The analysis mode to determine which prompt to use.')
|
|
.optional(),
|
|
workflowStartTime: z.number().describe('The start time of the workflow'),
|
|
});
|
|
|
|
export const ThinkAndPrepStreamOptionsSchema = z.object({
|
|
messages: z
|
|
.array(z.custom<ModelMessage>())
|
|
.describe('The messages to send to the think and prep agent.'),
|
|
});
|
|
|
|
export type ThinkAndPrepAgentOptions = z.infer<typeof ThinkAndPrepAgentOptionsSchema>;
|
|
export type ThinkAndPrepStreamOptions = z.infer<typeof ThinkAndPrepStreamOptionsSchema>;
|
|
|
|
export function createThinkAndPrepAgent(thinkAndPrepAgentSchema: ThinkAndPrepAgentOptions) {
|
|
const { messageId, datasets, workflowStartTime } = thinkAndPrepAgentSchema;
|
|
|
|
const systemMessage = {
|
|
role: 'system',
|
|
content: getThinkAndPrepAgentSystemPrompt(
|
|
thinkAndPrepAgentSchema.sql_dialect_guidance,
|
|
(thinkAndPrepAgentSchema.analysisMode || 'standard') as AnalysisMode
|
|
),
|
|
providerOptions: DEFAULT_ANTHROPIC_OPTIONS,
|
|
} as ModelMessage;
|
|
|
|
// Create second system message with datasets information
|
|
const datasetsContent = datasets
|
|
.filter((d) => d.ymlContent)
|
|
.map((d) => d.ymlContent)
|
|
.join('\n\n');
|
|
|
|
const datasetsSystemMessage = {
|
|
role: 'system',
|
|
content: datasetsContent
|
|
? `<database_context>\n${datasetsContent}\n</database_context>`
|
|
: '<database_context>\nNo datasets available\n</database_context>',
|
|
providerOptions: DEFAULT_ANTHROPIC_OPTIONS,
|
|
} as ModelMessage;
|
|
|
|
async function stream({ messages }: ThinkAndPrepStreamOptions) {
|
|
const sequentialThinking = createSequentialThinkingTool({ messageId });
|
|
const executeSqlTool = createExecuteSqlTool({
|
|
messageId,
|
|
dataSourceId: thinkAndPrepAgentSchema.dataSourceId,
|
|
dataSourceSyntax: thinkAndPrepAgentSchema.dataSourceSyntax,
|
|
userId: thinkAndPrepAgentSchema.userId,
|
|
});
|
|
const respondWithoutAssetCreation = createRespondWithoutAssetCreationTool({
|
|
messageId,
|
|
workflowStartTime,
|
|
});
|
|
const submitThoughts = createSubmitThoughtsTool();
|
|
const messageUserClarifyingQuestion = createMessageUserClarifyingQuestionTool({
|
|
messageId,
|
|
workflowStartTime,
|
|
});
|
|
|
|
const availableTools = [
|
|
SEQUENTIAL_THINKING_TOOL_NAME,
|
|
EXECUTE_SQL_TOOL_NAME,
|
|
RESPOND_WITHOUT_ASSET_CREATION_TOOL_NAME,
|
|
SUBMIT_THOUGHTS_TOOL_NAME,
|
|
MESSAGE_USER_CLARIFYING_QUESTION_TOOL_NAME,
|
|
];
|
|
|
|
const agentContext: AgentContext = {
|
|
agentName: THINK_AND_PREP_AGENT_NAME,
|
|
availableTools,
|
|
nextPhaseTools: [
|
|
'createMetrics',
|
|
'modifyMetrics',
|
|
'createDashboards',
|
|
'modifyDashboards',
|
|
'createReports',
|
|
'modifyReports',
|
|
'doneTool',
|
|
],
|
|
};
|
|
|
|
return wrapTraced(
|
|
() =>
|
|
streamText({
|
|
model: Sonnet4,
|
|
headers: {
|
|
'anthropic-beta':
|
|
'fine-grained-tool-streaming-2025-05-14,extended-cache-ttl-2025-04-11',
|
|
anthropic_beta: 'fine-grained-tool-streaming-2025-05-14,extended-cache-ttl-2025-04-11',
|
|
},
|
|
providerOptions: DEFAULT_ANTHROPIC_OPTIONS,
|
|
tools: {
|
|
[SEQUENTIAL_THINKING_TOOL_NAME]: sequentialThinking,
|
|
[EXECUTE_SQL_TOOL_NAME]: executeSqlTool,
|
|
[RESPOND_WITHOUT_ASSET_CREATION_TOOL_NAME]: respondWithoutAssetCreation,
|
|
[SUBMIT_THOUGHTS_TOOL_NAME]: submitThoughts,
|
|
[MESSAGE_USER_CLARIFYING_QUESTION_TOOL_NAME]: messageUserClarifyingQuestion,
|
|
},
|
|
messages: [systemMessage, datasetsSystemMessage, ...messages],
|
|
stopWhen: STOP_CONDITIONS,
|
|
toolChoice: 'required',
|
|
maxOutputTokens: 25000,
|
|
temperature: 0,
|
|
experimental_repairToolCall: async (repairContext) => {
|
|
return repairToolCall({
|
|
toolCall: repairContext.toolCall,
|
|
tools: repairContext.tools,
|
|
error: repairContext.error,
|
|
messages: repairContext.messages,
|
|
...(repairContext.system && { system: repairContext.system }),
|
|
...(repairContext.inputSchema && { inputSchema: repairContext.inputSchema }),
|
|
agentContext,
|
|
});
|
|
},
|
|
onStepFinish: async (event) => {
|
|
console.info('Think and Prep Agent step finished', {
|
|
toolCalls: event.toolCalls?.length || 0,
|
|
hasToolResults: !!event.toolResults,
|
|
});
|
|
},
|
|
}),
|
|
{
|
|
name: 'Think and Prep Agent',
|
|
}
|
|
)();
|
|
}
|
|
|
|
return {
|
|
stream,
|
|
};
|
|
}
|