@@ -25,6 +25,73 @@ const costParametersSchema = z.object({
2525// Create a type from the schema
2626type 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
2895export 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+ } ;
0 commit comments