mirror of
https://github.com/supabase/supabase.git
synced 2026-10-12 04:45:08 +03:00
* chore: bump `supabase` CLI * chore: stricter message types in `generate-v4.ts` * feat: tutorial eval https://www.braintrust.dev/docs/evaluation * feat: project ID for eval * refactor: `generateAssistantResponse` out of `handlePost` * refactor: generateAssistantResponse to lib/ai * feat: factuality eval with assistant response * chore: upgrade braintrust to v1.0.1 * chore: silence tsconfig warning * feat: assertion scorer * fix: aggregate tools across all steps * refactor: strict tool names, remove need for `as const` * refactor: generic tool name type in assertions * feat: transfer mocks from `feature/braintrust` * feat: LLM criteria assertion * feat: braintrust evals workflow * fix: BRAINTRUST_PROJECT_ID * feat: `sql_similar` assertion * fix: `OPENAI_API_KEY` in workflow env * feat: split AssertionScorer into separate scorers * feat: remove tutorial eval * feat: 20 minute CI timeout * feat: category in test case metadata * feat: score with gpt-5 * refactor: dataset to own file, colocate scorers * feat: "gpt-5.2-2025-12-11" for llm as a judge * feat: SQL syntax scorer with `libpg-query` * feat: `evals:setup` and `evals:run` scripts * feat: `evals:setup` in CI * feat: human readable scorer names * chore: rename to "SQL Validity" * feat: add 2 "sql_generation" test cases * feat: update requiredTools in test cases * chore: ignore Cursor MCP config * feat: "Conciseness" score * feat: "Completeness" scorer * fix: generate-v4 test mocks * feat: serialize "steps" for scorer inputs * updated node mem options for typecheck * updated runner * remove ram update as actions handle this * feat: read `BRAINTRUST_PROJECT_ID` from secrets * feat: score helpfulness, remove old scorers * feat: separate `evals:run` and `evals:upload` scripts * feat: passthrough entire classifier result * feat: use live `search_docs` impl, store docs result in metadata * feat: reduce classifier options * feat: filter workflow by `run-evals` PR label or `master` branch * chore: cleanup stubbed mock tools * fix: checkout actual branch with `ref:` * fix: capture search_docs results from all content parts * feat: simplify sql syntax score calculation * feat: use AI SDK's UI message validator * docs: justification for relative `extends` * fix: cleanup leftover validatedMessages * doc: note mock token isn't secret for snyk * fix: mock ui message to pass validation * feat: revert ignoring Cursor MCP config Using `.git/info/exclude` instead until we have an opinion on this * feat: add "tsconfig" as shared-data devDependency, revert relative path in tsconfig * refactor: tool call parsing into function * Update apps/studio/evals/assistant.eval.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * refactor: organize mock schemas and tool factories --------- Co-authored-by: Ali Waseem <waseema393@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
160 lines
4.2 KiB
TypeScript
160 lines
4.2 KiB
TypeScript
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<Input, Expected, AssistantEvalCaseMetadata>
|
|
|
|
export const toolUsageScorer: EvalScorer<Input, Output, Expected> = 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<Input, Output, Expected> = 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<Input, Output, Expected> = 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<Input, Output, Expected> = 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<Input, Output, Expected> = async ({
|
|
input,
|
|
output,
|
|
}) => {
|
|
return await goalCompletionEvaluator({
|
|
input,
|
|
output: output.stepsSerialized,
|
|
})
|
|
}
|