mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 10:25:06 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Feature and bug fix. ## What is the current behavior? The assistant can call `query_logs`, but it is not given the ClickHouse schema and query-writing guidance it needs. It also lacks a current UTC reference for producing the absolute timestamps required by the tool, which can lead to valid queries being run against the wrong time range and reported as returning zero rows. ## What is the new behavior? - Adds a dedicated `logs` knowledge topic backed by the shared ClickHouse schema and query guidance. - Requires the assistant to load that knowledge before using `query_logs`. - Includes the current UTC time in project context so relative requests can be converted to correct absolute tool parameters. - Covers the new knowledge flow and context with focused tests and updates the assistant eval expectation. ## How to test 1. Check out this PR and run Studio against a project that has recent logs. Generate some project activity first, such as an API request, if needed. 2. Open the AI Assistant and ask: `Show log counts by minute for the last 15 minutes and summarize any spikes.` 3. Expand the assistant's tool activity and verify it loads the `logs` knowledge topic before calling `query_logs`. 4. Inspect the `query_logs` input and verify: - `iso_timestamp_start` and `iso_timestamp_end` are absolute UTC timestamps ending in `Z`. - The timestamps cover approximately the requested 15-minute window. - The SQL uses ClickHouse syntax, includes a `LIMIT`, and does not put the time range in the SQL `WHERE` clause. 5. Verify the assistant's summary reflects the rows returned by `query_logs` instead of reporting zero rows when results are present. ## Additional context This is the bottom PR in stack #49294. The front-end visualization is added separately in #49293. Verified with 59 focused tests across assistant context, Studio/MCP tools, query display, and logs result parsing. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added AI-assisted project log querying through the `query_logs` tool. - Added logs knowledge guidance for time ranges, schema discovery, query limits, and concise result summaries. - Project context now includes the current UTC timestamp to improve relative time-range interpretation. - Improved notebook assistance with safer table verification and appropriate handling of log queries. - **Bug Fixes** - Prevented incorrect SQL timestamp filtering and enabled cross-service searches without requiring a source filter. - Added validation for supported knowledge topics. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
132 lines
4.9 KiB
TypeScript
132 lines
4.9 KiB
TypeScript
import { acceptUntrustedSql, untrustedSql } from '@supabase/pg-meta'
|
|
import { tool } from 'ai'
|
|
import { z } from 'zod'
|
|
|
|
import { deployEdgeFunction } from '@/data/edge-functions/edge-functions-deploy-mutation'
|
|
import { executeSql } from '@/data/sql/execute-sql-mutation'
|
|
import type { AiOptInLevel } from '@/hooks/misc/useOrgOptedIntoAi'
|
|
import {
|
|
EDGE_FUNCTION_PROMPT,
|
|
LOGS_PROMPT,
|
|
PG_BEST_PRACTICES,
|
|
REALTIME_PROMPT,
|
|
RLS_PROMPT,
|
|
STORAGE_PROMPT,
|
|
} from '@/lib/ai/prompts'
|
|
import { NO_DATA_PERMISSIONS } from '@/lib/ai/tools/tool-sanitizer'
|
|
import { fixSqlBackslashEscapes } from '@/lib/ai/util'
|
|
|
|
const KNOWLEDGE = {
|
|
pg_best_practices: PG_BEST_PRACTICES,
|
|
rls: RLS_PROMPT,
|
|
storage: STORAGE_PROMPT,
|
|
edge_functions: EDGE_FUNCTION_PROMPT,
|
|
realtime: REALTIME_PROMPT,
|
|
logs: LOGS_PROMPT,
|
|
} as const
|
|
|
|
type KnowledgeName = keyof typeof KNOWLEDGE
|
|
|
|
export const executeSqlInputSchema = z.object({
|
|
// Transform at parse time so the corrected SQL is what gets stored in
|
|
// toolCall.input — ensuring evals and logs reflect what actually runs.
|
|
sql: z.string().describe('The SQL statement to execute.').transform(fixSqlBackslashEscapes),
|
|
label: z.string().describe('A short 2-4 word label for the SQL statement.'),
|
|
chartConfig: z
|
|
.object({
|
|
view: z.enum(['table', 'chart']).describe('How to render the results after execution'),
|
|
xAxis: z.string().optional().describe('The column to use for the x-axis of the chart.'),
|
|
yAxis: z.string().optional().describe('The column to use for the y-axis of the chart.'),
|
|
})
|
|
.describe('Chart configuration for rendering the results'),
|
|
isWriteQuery: z
|
|
.boolean()
|
|
.default(false)
|
|
.describe(
|
|
'Whether the SQL statement performs a write operation or has side effects. Set true for INSERT/UPDATE/DELETE/DDL and for SELECT statements that call side-effecting functions, such as select cron.schedule(...), cron.unschedule(...), or functions that create, modify, schedule, enqueue, notify, or trigger work.'
|
|
),
|
|
})
|
|
|
|
export const loadKnowledgeInputSchema = z.object({
|
|
name: z
|
|
.enum(Object.keys(KNOWLEDGE) as [KnowledgeName, ...KnowledgeName[]])
|
|
.describe('The knowledge to load'),
|
|
})
|
|
|
|
export type StudioToolsContext = {
|
|
projectRef?: string
|
|
connectionString?: string
|
|
authorization?: string
|
|
aiOptInLevel?: AiOptInLevel
|
|
}
|
|
|
|
export const getStudioTools = (ctx: StudioToolsContext = {}) => {
|
|
const { projectRef, connectionString, authorization, aiOptInLevel = 'schema' } = ctx
|
|
const authHeaders = authorization
|
|
? { 'Content-Type': 'application/json', Authorization: authorization }
|
|
: undefined
|
|
|
|
return {
|
|
execute_sql: tool({
|
|
description:
|
|
'Asks the user to execute a SQL statement and return the results. Requires user approval before executing.',
|
|
inputSchema: executeSqlInputSchema,
|
|
needsApproval: true,
|
|
execute: async ({ sql }) => {
|
|
// The `needsApproval: true` gate on this tool means the user has
|
|
// explicitly approved this AI-generated SQL before execute runs —
|
|
// that approval is the user gesture that promotes untrusted to safe.
|
|
const { result } = await executeSql(
|
|
{ projectRef, connectionString, sql: acceptUntrustedSql(untrustedSql(sql)) },
|
|
undefined,
|
|
authHeaders
|
|
)
|
|
return result
|
|
},
|
|
toModelOutput: ({ output }) => {
|
|
return aiOptInLevel === 'schema_and_log_and_data'
|
|
? { type: 'json', value: output }
|
|
: { type: 'text', value: NO_DATA_PERMISSIONS }
|
|
},
|
|
}),
|
|
deploy_edge_function: tool({
|
|
description:
|
|
'Asks the user to deploy a Supabase Edge Function from provided code. Requires user approval before deploying.',
|
|
inputSchema: z.object({
|
|
name: z.string().describe('The URL-friendly name/slug of the Edge Function.'),
|
|
code: z.string().describe('The TypeScript code for the Edge Function.'),
|
|
}),
|
|
needsApproval: true,
|
|
execute: async ({ name, code }) => {
|
|
await deployEdgeFunction({
|
|
projectRef: projectRef ?? '',
|
|
slug: name,
|
|
metadata: {
|
|
entrypoint_path: 'index.ts',
|
|
name,
|
|
verify_jwt: true,
|
|
},
|
|
files: [{ name: 'index.ts', content: code }],
|
|
authorization,
|
|
})
|
|
return { success: true }
|
|
},
|
|
}),
|
|
rename_chat: tool({
|
|
description: `Rename the current chat session when the current chat name doesn't describe the conversation topic.`,
|
|
inputSchema: z.object({
|
|
newName: z.string().describe('The new name for the chat session. Five words or less.'),
|
|
}),
|
|
execute: async () => {
|
|
return { status: 'Chat request sent to client' }
|
|
},
|
|
}),
|
|
load_knowledge: tool({
|
|
description:
|
|
'Load detailed knowledge about a Supabase topic before answering questions about it.',
|
|
inputSchema: loadKnowledgeInputSchema,
|
|
execute: ({ name }) => KNOWLEDGE[name],
|
|
}),
|
|
}
|
|
}
|