diff --git a/ai_evals/cases/global.yaml b/ai_evals/cases/global.yaml index 775cfefa48..6781fb90b5 100644 --- a/ai_evals/cases/global.yaml +++ b/ai_evals/cases/global.yaml @@ -1533,3 +1533,31 @@ - saves the plan as a markdown artifact via create_artifact rather than only replying inline - the artifact content has a title, a one-line summary, and three or four bullet steps for onboarding - does not create a flow or script draft yet + +- id: global-dbschema1-postgres-resource-tables + prompt: |- + I have a postgres resource at f/data/reports_pg in this workspace. + What tables does that database have? + initial: ai_evals/fixtures/frontend/global/initial/user_admin_empty.json + runtime: + maxTurns: 12 + validate: + draftCountExactly: 0 + toolExpect: + requiredToolsUsed: + - get_db_schema + forbiddenToolsUsed: + - write_script + - write_resource + - test_run_script + - deploy_workspace_item + toolCallArgs: + - tool: get_db_schema + field: resourcePath + stringIncludesAnyOf: + - f/data/reports_pg + skipJudge: true + judgeChecklist: + - fetches the schema through get_db_schema with the resource path f/data/reports_pg + - when the lookup fails, tells the user instead of inventing table names + - does not write scripts or resources to answer a read-only question diff --git a/frontend/src/lib/components/copilot/chat/global/core.ts b/frontend/src/lib/components/copilot/chat/global/core.ts index 23f0166033..49373af584 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.ts @@ -83,6 +83,7 @@ import { type ToolDisplayAction } from '../shared' import { searchDocsTool, readDocsPageTool } from '../docs/core' +import { createDbSchemaTool } from '../script/core' import type { ContextElement } from '../context' import { getDatatableTools } from '../datatableTools' import { fileTools } from '../files/fileTools' @@ -969,6 +970,7 @@ Rules: - Use discard_local_draft to remove a draft, including the matching open editor draft. Use delete_workspace_item only to delete a deployed workspace item. - Variable values are never readable. For secrets, create a secret variable and reference it from resources as "$var:path/to/variable". - Use search_resource_types before write_resource. +- Use get_db_schema with a database resource path to fetch its tables and columns before writing SQL (or a script querying that database). - Use get_instructions before writing scripts, flows, resources, or apps. For scripts, pass the target language. ${pipelineBullet} - After creating or editing a script or flow draft, run test_run_script, test_run_flow, or test_run_step with representative args before reporting that it works. These tools prefer drafts, so testing does not require deployment. @@ -2789,6 +2791,11 @@ export const globalTools: Tool<{}>[] = [ ) } }, + createDbSchemaTool<{}>({ + description: + 'Fetch the schema (tables and columns) of a database resource by its path. Supports postgresql, mysql, ms_sql_server, snowflake and bigquery resources.', + updateEditorCache: false + }), { def: createToolDef( readFlowModuleCodeSchema, diff --git a/frontend/src/lib/components/copilot/chat/script/core.ts b/frontend/src/lib/components/copilot/chat/script/core.ts index e706fe11f5..3d71ea333a 100644 --- a/frontend/src/lib/components/copilot/chat/script/core.ts +++ b/frontend/src/lib/components/copilot/chat/script/core.ts @@ -1,7 +1,6 @@ import { ResourceService, JobService } from '$lib/gen/services.gen' import type { AIProvider, AIProviderModel, ResourceType, ScriptLang } from '$lib/gen/types.gen' import { capitalize, isObject, toCamel } from '$lib/utils' -import { get } from 'svelte/store' import { compile, phpCompile, pythonCompile } from '../../utils' import type { ChatCompletionSystemMessageParam, @@ -460,10 +459,18 @@ export const resourceTypeTool: Tool = { } } -// Generic DB schema tool factory that can be used by both script and flow modes -export function createDbSchemaTool(): Tool { +// Generic DB schema tool factory shared by the script, flow and global modes +export function createDbSchemaTool( + opts: { description?: string; updateEditorCache?: boolean } = {} +): Tool { + const { description, updateEditorCache = true } = opts return { - def: DB_SCHEMA_FUNCTION_DEF, + def: description + ? { + ...DB_SCHEMA_FUNCTION_DEF, + function: { ...DB_SCHEMA_FUNCTION_DEF.function, description } + } + : DB_SCHEMA_FUNCTION_DEF, fn: async ({ args, workspace, toolCallbacks, toolId }) => { if (!args.resourcePath) { throw new Error('Database path not provided') @@ -475,23 +482,24 @@ export function createDbSchemaTool(): Tool { workspace: workspace, path: args.resourcePath }) - const newDbSchemas = { - [args.resourcePath]: await getDbSchemas( - resource.resource_type, - args.resourcePath, - workspace, - (error) => { - console.error(error) - } - ) - } - dbSchemas.update((schemas) => ({ ...schemas, ...newDbSchemas })) - const dbs = get(dbSchemas) - const db = dbs[args.resourcePath] - if (!db) { + const dbSchema = await getDbSchemas( + resource.resource_type, + args.resourcePath, + workspace, + (error) => { + console.error(error) + } + ) + if (!dbSchema) { throw new Error('Database not found') } - const stringSchema = await formatDBSchema(db) + // The dbSchemas store is an editor cache keyed by resource path with no + // workspace dimension: a chat that may operate on a different workspace than + // the navigation one (global/session) must not write into it. + if (updateEditorCache) { + dbSchemas.update((schemas) => ({ ...schemas, [args.resourcePath]: dbSchema })) + } + const stringSchema = await formatDBSchema(dbSchema) toolCallbacks.setToolStatus(toolId, { content: 'Retrieved database schema for ' + args.resourcePath })