scorer-wasm.ts 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import { EvalScorer, Trace } from 'braintrust'
  2. import { parse } from 'libpg-query'
  3. import { AssistantEvalInput, AssistantEvalOutput, Expected } from './scorer'
  4. import { getParsedToolSpans } from './trace-utils'
  5. import { executeSqlInputSchema } from '@/lib/ai/tools/studio-tools'
  6. import { extractIdentifiers, isQuotedInSql, needsQuoting } from '@/lib/sql-identifier-quoting'
  7. /** Extracts SQL strings from all `execute_sql` tool spans in the trace. */
  8. async function getSqlQueries(trace: Trace): Promise<string[]> {
  9. const spans = await getParsedToolSpans(trace, 'execute_sql', {
  10. inputSchema: executeSqlInputSchema,
  11. })
  12. return spans.map((s) => s.input.sql)
  13. }
  14. export const sqlSyntaxScorer: EvalScorer<
  15. AssistantEvalInput,
  16. AssistantEvalOutput,
  17. Expected
  18. > = async ({ trace }) => {
  19. if (!trace) return null
  20. const sqlQueries = await getSqlQueries(trace)
  21. if (sqlQueries.length === 0) return null
  22. const errors: string[] = []
  23. let validQueries = 0
  24. for (const sql of sqlQueries) {
  25. try {
  26. await parse(sql)
  27. validQueries++
  28. } catch (error) {
  29. const errorMessage = error instanceof Error ? error.message : String(error)
  30. errors.push(`SQL syntax error: ${errorMessage}`)
  31. }
  32. }
  33. return {
  34. name: 'SQL Validity',
  35. score: validQueries / sqlQueries.length,
  36. metadata: errors.length > 0 ? { errors } : undefined,
  37. }
  38. }
  39. export const sqlIdentifierQuotingScorer: EvalScorer<
  40. AssistantEvalInput,
  41. AssistantEvalOutput,
  42. Expected
  43. > = async ({ trace }) => {
  44. if (!trace) return null
  45. const sqlQueries = await getSqlQueries(trace)
  46. if (sqlQueries.length === 0) return null
  47. const errors: string[] = []
  48. let totalNeedingQuotes = 0
  49. let properlyQuoted = 0
  50. for (const sql of sqlQueries) {
  51. try {
  52. const ast = await parse(sql)
  53. const identifiers = extractIdentifiers(ast)
  54. for (const identifier of identifiers) {
  55. if (needsQuoting(identifier)) {
  56. totalNeedingQuotes++
  57. if (isQuotedInSql(sql, identifier)) {
  58. properlyQuoted++
  59. } else {
  60. const sqlPreview = sql.length > 100 ? `${sql.substring(0, 100)}...` : sql
  61. errors.push(
  62. `Identifier "${identifier}" needs quoting but is not quoted in: ${sqlPreview}`
  63. )
  64. }
  65. }
  66. }
  67. } catch {
  68. // Skip invalid SQL - already handled by sqlSyntaxScorer
  69. }
  70. }
  71. const score = totalNeedingQuotes === 0 ? 1 : properlyQuoted / totalNeedingQuotes
  72. return {
  73. name: 'SQL Identifier Quoting',
  74. score,
  75. metadata: errors.length > 0 ? { errors } : undefined,
  76. }
  77. }