Skip to content

Commit df39f79

Browse files
authored
Merge pull request #19 from tinybirdco/explorer-02
Explorer 02
2 parents 15ce514 + f76fd73 commit df39f79

File tree

14 files changed

+617
-296
lines changed

14 files changed

+617
-296
lines changed

dashboard/ai-analytics/src/app/api/extract-cost-parameters/route.ts

Lines changed: 154 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,73 @@ const costParametersSchema = z.object({
2525
// Create a type from the schema
2626
type CostParameters = z.infer<typeof costParametersSchema>;
2727

28+
// Add a new function to fetch available dimensions
29+
const fetchAvailableDimensions = async () => {
30+
const TINYBIRD_API_URL = process.env.NEXT_PUBLIC_TINYBIRD_API_URL || 'http://localhost:7181';
31+
const TINYBIRD_API_KEY = process.env.NEXT_PUBLIC_TINYBIRD_API_KEY;
32+
33+
if (!TINYBIRD_API_KEY) {
34+
console.error('No Tinybird API key available');
35+
return null;
36+
}
37+
38+
try {
39+
// SQL query to get all unique values for each dimension
40+
const query = `
41+
SELECT
42+
groupUniqArray(organization) as organizations,
43+
groupUniqArray(project) as projects,
44+
groupUniqArray(environment) as environments,
45+
groupUniqArray(model) as models,
46+
groupUniqArray(provider) as providers
47+
FROM llm_events FORMAT JSON
48+
`;
49+
50+
// URL encode the query
51+
const encodedQuery = encodeURIComponent(query);
52+
const url = `${TINYBIRD_API_URL}/v0/sql?q=${encodedQuery}`;
53+
54+
console.log('Fetching available dimensions from:', url);
55+
56+
const response = await fetch(url, {
57+
headers: {
58+
Authorization: `Bearer ${TINYBIRD_API_KEY}`,
59+
},
60+
});
61+
62+
if (!response.ok) {
63+
const error = await response.text();
64+
console.error('Error fetching dimensions:', error);
65+
throw new Error('Network response was not ok');
66+
}
67+
68+
const data = await response.json();
69+
console.log('Available dimensions:', data);
70+
return data;
71+
} catch (error) {
72+
console.error('Error fetching dimensions:', error);
73+
return null;
74+
}
75+
};
76+
77+
// Format today's date with time in yyyy-MM-dd HH:mm:ss format
78+
const formatDate = (date: Date): string => {
79+
const pad = (num: number): string => num.toString().padStart(2, '0');
80+
81+
const year = date.getFullYear();
82+
const month = pad(date.getMonth() + 1); // getMonth() is 0-indexed
83+
const day = pad(date.getDate());
84+
const hours = pad(date.getHours());
85+
const minutes = pad(date.getMinutes());
86+
const seconds = pad(date.getSeconds());
87+
88+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
89+
};
90+
91+
const today = formatDate(new Date());
92+
const todayDateOnly = today.split(' ')[0]; // Just the date part: yyyy-MM-dd
93+
94+
// Update the POST function to properly map meta with data
2895
export async function POST(req: Request) {
2996
try {
3097
const { query } = await req.json();
@@ -33,13 +100,39 @@ export async function POST(req: Request) {
33100
return NextResponse.json({ error: 'Query is required' }, { status: 400 });
34101
}
35102

36-
// Get today's date for the prompt
37-
const today = new Date().toISOString().split('T')[0]; // Format: YYYY-MM-DD
103+
// Fetch pipe definition and available dimensions in parallel
104+
const [pipeDefinition, availableDimensions] = await Promise.all([
105+
fetchPipeDefinition(),
106+
fetchAvailableDimensions()
107+
]);
108+
109+
console.log('Pipe definition:', pipeDefinition);
110+
console.log('Available dimensions:', availableDimensions);
38111

112+
// Extract dimension values for the system prompt
113+
const dimensionValues: Record<string, { type: string, values: string[] }> = {};
114+
115+
// Map meta with data for a more structured representation
116+
if (availableDimensions && availableDimensions.meta && availableDimensions.data && availableDimensions.data.length > 0) {
117+
const metaInfo = availableDimensions.meta;
118+
const dataValues = availableDimensions.data[0];
119+
120+
// Create a structured object with meta information and data values
121+
metaInfo.forEach((meta: { name: string, type: string }) => {
122+
dimensionValues[meta.name] = {
123+
type: meta.type,
124+
values: dataValues[meta.name] || []
125+
};
126+
});
127+
}
128+
129+
console.log('Structured dimension values:', dimensionValues);
130+
131+
// Update system prompt to include formatted timestamp
39132
const systemPromptText = `
40-
You are a cost prediction parameter extractor. Extract parameters from natural language queries about AI model cost predictions.
133+
You are an LLM cost calculator parameter extractor. Extract parameters from natural language queries about AI model cost predictions.
41134
42-
Today's date is ${today}.
135+
Today's date and time is ${today}.
43136
44137
Return values for these parameters:
45138
- model: The AI model mentioned (e.g., "gpt-4", "claude-sonnet")
@@ -49,15 +142,23 @@ export async function POST(req: Request) {
49142
- timeframe: Time period for analysis (e.g., "last month", "last week", "last 3 months", "last year")
50143
- volumeChange: Any volume change percentage mentioned (can be positive or negative)
51144
- start_date: The start date in YYYY-MM-DD format based on the timeframe
52-
- end_date: The end date in YYYY-MM-DD format (usually today: ${today})
145+
- end_date: The end date in YYYY-MM-DD format (usually today: ${todayDateOnly})
53146
- group_by: If the user wants to group by a specific dimension (e.g., "model", "provider", "organization", "project", "environment", "user")
54147
55-
Filter parameters (extract if mentioned):
56-
- organization: Organization name to filter by (e.g., "quantum_systems", "acme_corp")
57-
- project: Project name to filter by (e.g., "chatbot", "recommendation_engine")
58-
- environment: Environment to filter by (e.g., "production", "staging", "development")
59-
- provider: Provider to filter by (e.g., "OpenAI", "Anthropic", "Cohere")
60-
- user: User to filter by (e.g., "john.doe", "api_user")
148+
Also extract filter parameters if mentioned:
149+
${availableDimensions?.meta?.map((meta: { name: string, type: string }) => {
150+
const name = meta.name;
151+
// Convert to singular form for the parameter name (remove trailing 's')
152+
const paramName = name.endsWith('s') ? name.slice(0, -1) : name;
153+
return `- ${paramName}: ${name.charAt(0).toUpperCase() + name.slice(1)} to filter by`;
154+
}).join('\n ') || ''}
155+
156+
These are the available values in the database, extract them if mentioned no matter if the parameter name is provided or not:
157+
${availableDimensions?.meta?.map((meta: { name: string, type: string }) => {
158+
const name = meta.name;
159+
const values = dimensionValues[name]?.values || [];
160+
return `- ${name.charAt(0).toUpperCase() + name.slice(1)}: ${JSON.stringify(values)}`;
161+
}).join('\n ') || ''}
61162
62163
For timeframes, calculate the appropriate start_date:
63164
- "last week" = 7 days before today
@@ -69,12 +170,7 @@ export async function POST(req: Request) {
69170
Always include start_date and end_date based on the timeframe (default to "last month" if not specified).
70171
71172
Context-based filter extraction rules:
72-
1. When a model name is mentioned (e.g., "gpt-4", "claude-3-opus"), set model filter to that value
73-
2. When a provider name is mentioned (e.g., "OpenAI", "Anthropic"), set provider filter to that value.
74-
3. When an environment is mentioned (e.g., "production", "staging", "development"), set environment filter
75-
4. When a project or organization name is mentioned, set the appropriate filter
76-
5. When a username is mentioned, set the user filter
77-
6. Never use a model name as a provider name and vice-versa, model=anthropic is invalid, provider=anthropic is valid.
173+
- Only use values that exist in the database (see "Available values" above).
78174
79175
Look for phrases like "filter by", "for", "in", "with", etc. to identify filter parameters.
80176
Examples:
@@ -127,7 +223,9 @@ export async function POST(req: Request) {
127223
project: extractedParams.project || null,
128224
environment: extractedParams.environment || null,
129225
provider: extractedParams.provider || null,
130-
user: extractedParams.user || null
226+
user: extractedParams.user || null,
227+
pipeDefinition: pipeDefinition,
228+
availableDimensions: dimensionValues
131229
};
132230

133231
return NextResponse.json(processedResult);
@@ -163,5 +261,41 @@ function getDefaultStartDate(timeframe: string): string {
163261
startDate.setMonth(now.getMonth() - 1);
164262
}
165263

166-
return startDate.toISOString().split('T')[0];
167-
}
264+
// Format the date with the current time
265+
return formatDate(startDate);
266+
}
267+
268+
// Fetch the llm_usage pipe definition
269+
const fetchPipeDefinition = async () => {
270+
const TINYBIRD_API_URL = process.env.NEXT_PUBLIC_TINYBIRD_API_URL || 'http://localhost:7181';
271+
const TINYBIRD_API_KEY = process.env.NEXT_PUBLIC_TINYBIRD_API_KEY;
272+
273+
if (!TINYBIRD_API_KEY) {
274+
console.error('No Tinybird API key available');
275+
return null;
276+
}
277+
278+
try {
279+
const url = `${TINYBIRD_API_URL}/v0/pipes/llm_usage`;
280+
console.log('Fetching pipe definition from:', url);
281+
282+
const response = await fetch(url, {
283+
headers: {
284+
Authorization: `Bearer ${TINYBIRD_API_KEY}`,
285+
},
286+
});
287+
288+
if (!response.ok) {
289+
const error = await response.text();
290+
console.error('Error fetching pipe definition:', error);
291+
throw new Error('Network response was not ok');
292+
}
293+
294+
const data = await response.json();
295+
console.log('Pipe definition:', data);
296+
return data;
297+
} catch (error) {
298+
console.error('Error fetching pipe definition:', error);
299+
return null;
300+
}
301+
};

dashboard/ai-analytics/src/app/components/BarList.tsx

Lines changed: 0 additions & 136 deletions
This file was deleted.

0 commit comments

Comments
 (0)