import { EvalCase, EvalScorer } from 'braintrust' import { LLMClassifierFromTemplate } from 'autoevals' import { stripIndent } from 'common-tags' import { parse } from 'libpg-query' const LLM_AS_A_JUDGE_MODEL = 'gpt-5.2-2025-12-11' type Input = string type Output = { stepsSerialized: string toolNames: string[] sqlQueries: string[] docs: string[] } export type Expected = { requiredTools?: string[] } // Based on categories in the AssistantMessageRatingSubmittedEvent export type AssistantEvalCaseCategory = | 'sql_generation' | 'schema_design' | 'rls_policies' | 'edge_functions' | 'database_optimization' | 'debugging' | 'general_help' | 'other' export type AssistantEvalCaseMetadata = { category?: AssistantEvalCaseCategory[] } export type AssistantEvalCase = EvalCase export const toolUsageScorer: EvalScorer = async ({ output, expected, }) => { if (!expected.requiredTools) return null const presentCount = expected.requiredTools.filter((tool) => output.toolNames.includes(tool) ).length const totalCount = expected.requiredTools.length const ratio = totalCount === 0 ? 1 : presentCount / totalCount return { name: 'Tool Usage', score: ratio, } } export const sqlSyntaxScorer: EvalScorer = async ({ output }) => { if (output.sqlQueries === undefined || output.sqlQueries.length === 0) { return null } const errors: string[] = [] let validQueries = 0 for (const sql of output.sqlQueries) { try { await parse(sql) validQueries++ } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) errors.push(`SQL syntax error: ${errorMessage}`) } } return { name: 'SQL Validity', score: validQueries / output.sqlQueries.length, metadata: errors.length > 0 ? { errors } : undefined, } } const concisenessEvaluator = LLMClassifierFromTemplate<{ input: string }>({ name: 'Conciseness', promptTemplate: stripIndent` Evaluate the conciseness of this response. Input: {{input}} Output: {{output}} Is the response concise and free of unnecessary words? a) Very concise - no wasted words b) Acceptable verbosity - some verbosity but acceptable c) Too verbose - contains superfluous wording or overly verbose `, choiceScores: { a: 1, b: 0.5, c: 0 }, useCoT: true, model: LLM_AS_A_JUDGE_MODEL, }) export const concisenessScorer: EvalScorer = async ({ input, output }) => { return await concisenessEvaluator({ input, output: output.stepsSerialized, }) } const completenessEvaluator = LLMClassifierFromTemplate<{ input: string }>({ name: 'Completeness', promptTemplate: stripIndent` Evaluate whether this response is complete and finished, or if it appears cut off or incomplete. Input: {{input}} Output: {{output}} Does the response appear complete and finished? a) Complete - response is complete and finished b) Incomplete - response appears cut off, missing parts, or severely incomplete `, choiceScores: { a: 1, b: 0 }, useCoT: true, model: LLM_AS_A_JUDGE_MODEL, }) export const completenessScorer: EvalScorer = async ({ input, output, }) => { return await completenessEvaluator({ input, output: output.stepsSerialized, }) } const goalCompletionEvaluator = LLMClassifierFromTemplate<{ input: string }>({ name: 'Goal Completion', promptTemplate: stripIndent` Evaluate whether this response addresses what the user asked. Input: {{input}} Output: {{output}} Does the response address what the user asked? a) Fully addresses - completely answers the question or fulfills the request b) Partially addresses - addresses some aspects but misses key parts c) Doesn't address - off-topic or fails to address the request `, choiceScores: { a: 1, b: 0.5, c: 0 }, useCoT: true, model: LLM_AS_A_JUDGE_MODEL, }) export const goalCompletionScorer: EvalScorer = async ({ input, output, }) => { return await goalCompletionEvaluator({ input, output: output.stepsSerialized, }) }