mirror of https://github.com/buster-so/buster.git
refactor: enhance dashboard and metrics tools with improved descriptions and type handling
- Added the `GPT5` import to the analyst agent for enhanced functionality. - Updated the `createModifyDashboardsStart` function to utilize `ToolCallOptions` for better input handling. - Refactored dashboard and metrics tools to use helper functions for descriptions, improving maintainability. - Ensured consistent type handling and removed redundant code in metrics delta processing. These changes streamline the tools and improve the overall code quality.
This commit is contained in:
parent
74ae581f73
commit
e93a82a3f5
|
@ -1,6 +1,7 @@
|
|||
import { type ModelMessage, NoSuchToolError, hasToolCall, stepCountIs, streamText } from 'ai';
|
||||
import { wrapTraced } from 'braintrust';
|
||||
import z from 'zod';
|
||||
import { GPT5 } from '../../llm/gpt-5';
|
||||
import {
|
||||
createCreateDashboardsTool,
|
||||
createCreateMetricsTool,
|
||||
|
@ -10,7 +11,6 @@ import {
|
|||
} from '../../tools';
|
||||
import { healToolWithLlm } from '../../utils/tool-call-repair';
|
||||
import { getAnalystAgentSystemPrompt } from './get-analyst-agent-system-prompt';
|
||||
import { GPT5 } from '../../llm/gpt-5';
|
||||
|
||||
const DEFAULT_CACHE_OPTIONS = {
|
||||
anthropic: { cacheControl: { type: 'ephemeral', ttl: '1h' } },
|
||||
|
|
|
@ -113,4 +113,4 @@ export async function extractValuesWithLLM(
|
|||
// Continue with empty values instead of failing
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { tool } from 'ai';
|
||||
import { z } from 'zod';
|
||||
import { getDashboardToolDescription } from '../helpers/get-dashboard-tool-descripton';
|
||||
import { createCreateDashboardsDelta } from './create-dashboards-delta';
|
||||
import { createCreateDashboardsExecute } from './create-dashboards-execute';
|
||||
import { createCreateDashboardsFinish } from './create-dashboards-finish';
|
||||
import { createCreateDashboardsStart } from './create-dashboards-start';
|
||||
import { getDashboardToolDescription } from '../helpers/get-dashboard-tool-descripton';
|
||||
|
||||
// Input schema for the create dashboards tool
|
||||
const CreateDashboardsInputSchema = z.object({
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { updateMessageEntries } from '@buster/database';
|
||||
import type { ModelMessage } from 'ai';
|
||||
import type { ModelMessage, ToolCallOptions } from 'ai';
|
||||
import { wrapTraced } from 'braintrust';
|
||||
import { createDashboardsReasoningMessage } from './helpers/modify-dashboards-transform-helper';
|
||||
import type {
|
||||
|
@ -14,7 +14,11 @@ export function createModifyDashboardsStart(
|
|||
state: ModifyDashboardsState
|
||||
) {
|
||||
return wrapTraced(
|
||||
async (input: ModifyDashboardsInput) => {
|
||||
async (options: ToolCallOptions) => {
|
||||
state.toolCallId = options.toolCallId;
|
||||
|
||||
const input = options.input as ModifyDashboardsInput;
|
||||
|
||||
// Log the start of dashboard modification
|
||||
const fileCount = input.files?.length || 0;
|
||||
const messageId = context.messageId;
|
||||
|
@ -25,12 +29,6 @@ export function createModifyDashboardsStart(
|
|||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Initialize state
|
||||
state.processingStartTime = Date.now();
|
||||
state.toolCallId = `modify-dashboards-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
state.parsedArgs = input;
|
||||
|
||||
// Initialize files in state (with IDs from input)
|
||||
state.files = input.files.map((file) => ({
|
||||
id: file.id,
|
||||
yml_content: file.yml_content,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { tool } from 'ai';
|
||||
import { z } from 'zod';
|
||||
import { getDashboardToolDescription } from '../helpers/get-dashboard-tool-descripton';
|
||||
import { createModifyDashboardsDelta } from './modify-dashboards-delta';
|
||||
import { createModifyDashboardsExecute } from './modify-dashboards-execute';
|
||||
import { createModifyDashboardsFinish } from './modify-dashboards-finish';
|
||||
|
@ -98,46 +99,8 @@ export function createModifyDashboardsTool(context: ModifyDashboardsContext) {
|
|||
const onInputDelta = createModifyDashboardsDelta(context, state);
|
||||
const onInputAvailable = createModifyDashboardsFinish(context, state);
|
||||
|
||||
// Get the description from the original tool
|
||||
const description = `Updates existing dashboard configuration files with new YAML content. Provide the complete YAML content for each dashboard, replacing the entire existing file. This tool is ideal for bulk modifications when you need to update multiple dashboards simultaneously. The system will preserve version history and perform all necessary validations on the new content. For each dashboard, you need its UUID and the complete updated YAML content. **Prefer modifying dashboards in bulk using this tool rather than one by one.**
|
||||
|
||||
## COMPLETE DASHBOARD YAML SCHEMA
|
||||
|
||||
# DASHBOARD CONFIGURATION - YML STRUCTURE
|
||||
# ----------------------------------------
|
||||
# Required fields:
|
||||
#
|
||||
# name: Your Dashboard Title # Do NOT use quotes for string values
|
||||
# description: A description of the dashboard, its metrics, and its purpose. # NO quotes
|
||||
# rows:
|
||||
# - id: 1 # Required row ID (integer)
|
||||
# items:
|
||||
# - id: metric-uuid-1 # UUIDv4 of an existing metric, NO quotes
|
||||
# column_sizes: [12] # Required - must sum to exactly 12
|
||||
# - id: 2 # REQUIRED
|
||||
# items:
|
||||
# - id: metric-uuid-2
|
||||
# - id: metric-uuid-3
|
||||
# column_sizes:
|
||||
# - 6
|
||||
# - 6
|
||||
#
|
||||
# Rules:
|
||||
# 1. Each row can have up to 4 items
|
||||
# 2. Each row must have a unique ID
|
||||
# 3. column_sizes is required and must specify the width for each item
|
||||
# 4. Sum of column_sizes in a row must be exactly 12
|
||||
# 5. Each column size must be at least 3
|
||||
# 6. All arrays should follow the YML array syntax using \`-\` and should NOT USE \`[]\` formatting.
|
||||
# 7. Don't use comments. The ones in the example are just for explanation
|
||||
# 8. String values generally should NOT use quotes unless they contain special characters (like :, {, }, [, ], ,, &, *, #, ?, |, -, <, >, =, !, %, @, \`) or start/end with whitespace.
|
||||
# 9. If a string contains special characters or needs to preserve leading/trailing whitespace, enclose it in double quotes (\`"\`). Example: \`name: "Sales & Marketing Dashboard"\`
|
||||
# 10. Avoid special characters in names and descriptions where possible, but if needed, use quotes as described in rule 9. UUIDs should NEVER be quoted.
|
||||
|
||||
**CRITICAL:** Follow the schema exactly - all metric IDs must reference existing metrics, column sizes must sum to 12, and row IDs must be unique. The tool will validate all metric references against the database.`;
|
||||
|
||||
return tool({
|
||||
description,
|
||||
description: getDashboardToolDescription(),
|
||||
inputSchema: ModifyDashboardsInputSchema,
|
||||
outputSchema: ModifyDashboardsOutputSchema,
|
||||
execute,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { updateMessageEntries } from '@buster/database';
|
||||
import type { ChatMessageReasoningMessage } from '@buster/server-shared/chats';
|
||||
import type { ToolCallOptions } from 'ai';
|
||||
import {
|
||||
OptimisticJsonParser,
|
||||
getOptimisticValue,
|
||||
|
@ -17,7 +18,7 @@ import {
|
|||
|
||||
// Factory function for onInputDelta callback
|
||||
export function createCreateMetricsDelta(context: CreateMetricsContext, state: CreateMetricsState) {
|
||||
return async (delta: string | Partial<CreateMetricsInput>) => {
|
||||
return async (options: { inputTextDelta: string } & ToolCallOptions) => {
|
||||
const messageId = context.messageId;
|
||||
|
||||
// Initialize files array if not already initialized
|
||||
|
@ -26,8 +27,8 @@ export function createCreateMetricsDelta(context: CreateMetricsContext, state: C
|
|||
}
|
||||
|
||||
// Handle string deltas (streaming JSON)
|
||||
if (typeof delta === 'string') {
|
||||
state.argsText = (state.argsText || '') + delta;
|
||||
if (typeof options.inputTextDelta === 'string') {
|
||||
state.argsText = (state.argsText || '') + options.inputTextDelta;
|
||||
|
||||
// Use optimistic parsing to extract values even from incomplete JSON
|
||||
const parseResult = OptimisticJsonParser.parse(state.argsText);
|
||||
|
@ -58,21 +59,21 @@ export function createCreateMetricsDelta(context: CreateMetricsContext, state: C
|
|||
const ymlContent = file[CREATE_METRICS_KEYS.yml_content] as string;
|
||||
|
||||
// Check if file already exists in state
|
||||
if (state.files![index]) {
|
||||
if (state.files?.[index]) {
|
||||
// Update existing file
|
||||
state.files![index].name = name;
|
||||
state.files![index].yml_content = ymlContent;
|
||||
state.files[index].name = name;
|
||||
state.files[index].yml_content = ymlContent;
|
||||
} else {
|
||||
// Add new file
|
||||
state.files![index] = {
|
||||
state.files[index] = {
|
||||
name,
|
||||
yml_content: ymlContent,
|
||||
status: 'processing',
|
||||
};
|
||||
}
|
||||
} else if (hasName && !state.files![index]) {
|
||||
} else if (hasName && !state.files?.[index]) {
|
||||
// Add placeholder with just name
|
||||
state.files![index] = {
|
||||
state.files[index] = {
|
||||
name: file[CREATE_METRICS_KEYS.name] as string,
|
||||
yml_content: '',
|
||||
status: 'processing',
|
||||
|
@ -117,20 +118,20 @@ export function createCreateMetricsDelta(context: CreateMetricsContext, state: C
|
|||
}
|
||||
} else {
|
||||
// Handle object deltas (complete input)
|
||||
if (delta.files) {
|
||||
state.parsedArgs = delta as CreateMetricsInput;
|
||||
state.files = delta.files.map((file) => ({
|
||||
if (options.input.files) {
|
||||
state.parsedArgs = options.input as CreateMetricsInput;
|
||||
state.files = options.input.files.map((file) => ({
|
||||
name: file.name,
|
||||
yml_content: file.yml_content,
|
||||
status: 'processing' as const,
|
||||
status: 'processing',
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
console.info('[create-metrics] Input delta processed', {
|
||||
hasFiles: !!state.files.filter((f) => f).length,
|
||||
fileCount: state.files.filter((f) => f).length,
|
||||
processedCount: state.files.filter((f) => f?.yml_content).length,
|
||||
hasFiles: !!state.files?.filter((f) => f).length,
|
||||
fileCount: state.files?.filter((f) => f).length,
|
||||
processedCount: state.files?.filter((f) => f?.yml_content).length,
|
||||
messageId,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { tool } from 'ai';
|
||||
import { z } from 'zod';
|
||||
import { getMetricToolDescription } from '../helpers/get-metric-tool-description';
|
||||
import { createCreateMetricsDelta } from './create-metrics-delta';
|
||||
import { createCreateMetricsExecute } from './create-metrics-execute';
|
||||
import { createCreateMetricsFinish } from './create-metrics-finish';
|
||||
import { createCreateMetricsStart } from './create-metrics-start';
|
||||
import { getMetricToolDescription } from '../helpers/get-metric-tool-description';
|
||||
|
||||
const CreateMetricsInputSchema = z.object({
|
||||
files: z
|
||||
|
|
|
@ -77,4 +77,4 @@ describe('Metric Tool Description Instructions', () => {
|
|||
getMetricToolDescriptionPrompt(' '); // whitespace only
|
||||
}).toThrow('SQL dialect guidance is required');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { ensureTimeFrameQuoted } from './time-frame-helper';
|
||||
|
||||
describe('ensureTimeFrameQuoted', () => {
|
||||
|
@ -87,4 +87,4 @@ timeFrame: '90d'
|
|||
timeFrame: "1y"`;
|
||||
expect(ensureTimeFrameQuoted(input)).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue