mirror of
https://github.com/supabase/supabase.git
synced 2026-05-09 02:09:50 -04:00
25036af80e
Fix for the LLM occasionally generating MySQL-style `\'` escapes in SQL, which are invalid in PostgreSQL. Example trace where this happened in the wild: ([Braintrust](https://www.braintrust.dev/app/supabase.io/p/Assistant/review?tab=experiment&r=5fcf1b12-8584-455c-9e9a-bdc0fa3ed21c&s=5fcf1b12-8584-455c-9e9a-bdc0fa3ed21c&o=0627ada8-b567-4117-9fe8-49d847cb73a7&review=1)) **Changes** - Adds `fixSqlBackslashEscapes` to convert `\'` → `''` before SQL is executed - Unit tests + adversarial eval dataset case Compare the results of the adversarial test case: - `master`: 0% SQL Validity ([Braintrust](https://www.braintrust.dev/app/supabase.io/p/Dev%20(mattrossman%2FAssistant)/trace?object_type=experiment&object_id=b469cbf7-4d6f-429c-9819-6c4099294123&r=dce5a29b-2fde-44c3-80f8-4e14d1f657c0&s=dce5a29b-2fde-44c3-80f8-4e14d1f657c0)) - This branch: 100% SQL Validity ([Braintrust](https://www.braintrust.dev/app/supabase.io/p/Assistant/trace?object_type=experiment&object_id=160e9ce0-e320-4f6d-8aa7-c5ad7e01fbd2&r=d75ef0e3-90ed-42a7-9ef3-8bf69592f193&s=0eeca492-dbe6-451e-8d81-127caff30320)) Closes AI-400
54 lines
1.5 KiB
TypeScript
54 lines
1.5 KiB
TypeScript
/**
|
|
* LLMs sometimes emit MySQL-style `\'` escapes in SQL. PostgreSQL doesn't
|
|
* treat backslash as an escape character, so replace `\'` → `''`.
|
|
* Dollar-quoted strings (e.g. `$$...$$`) are left untouched.
|
|
*/
|
|
export function fixSqlBackslashEscapes(sql: string): string {
|
|
return sql.replace(/\$([^$]*)\$[\s\S]*?\$\1\$|\\'/g, (match, dollarTag) =>
|
|
dollarTag !== undefined ? match : "''"
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Selects a key from weighted choices using consistent hashing
|
|
* on an input string.
|
|
*
|
|
* The same input always returns the same key, with distribution
|
|
* proportional to the provided weights.
|
|
*
|
|
* @example
|
|
* const region = await selectWeightedKey('my-unique-id', {
|
|
* use1: 40,
|
|
* use2: 10,
|
|
* usw2: 10,
|
|
* euc1: 10,
|
|
* })
|
|
* // Returns one of the keys based on the input and weights
|
|
*/
|
|
export async function selectWeightedKey<T extends string>(
|
|
input: string,
|
|
weights: Record<T, number>
|
|
): Promise<T> {
|
|
const keys = Object.keys(weights) as T[]
|
|
const encoder = new TextEncoder()
|
|
const data = encoder.encode(input)
|
|
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
|
|
|
|
// Use first 4 bytes (32 bit integer)
|
|
const hashInt = new DataView(hashBuffer).getUint32(0)
|
|
|
|
const totalWeight = keys.reduce((sum, key) => sum + weights[key], 0)
|
|
|
|
let cumulativeWeight = 0
|
|
const targetWeight = hashInt % totalWeight
|
|
|
|
for (const key of keys) {
|
|
cumulativeWeight += weights[key]
|
|
if (cumulativeWeight > targetWeight) {
|
|
return key
|
|
}
|
|
}
|
|
|
|
return keys[0]
|
|
}
|