chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* LLM tool enrichment utilities for table operations.
|
||||
*
|
||||
* Provides functions to enrich tool descriptions and parameter schemas
|
||||
* with table-specific information so LLMs can construct proper queries.
|
||||
*/
|
||||
|
||||
import type { TableSummary } from '@/lib/table/types'
|
||||
|
||||
/**
|
||||
* Operations that use filters and need filter-specific enrichment.
|
||||
*/
|
||||
export const FILTER_OPERATIONS = new Set([
|
||||
'table_query_rows',
|
||||
'table_update_rows_by_filter',
|
||||
'table_delete_rows_by_filter',
|
||||
])
|
||||
|
||||
/**
|
||||
* Operations that need column info for data construction.
|
||||
*/
|
||||
export const DATA_OPERATIONS = new Set([
|
||||
'table_insert_row',
|
||||
'table_batch_insert_rows',
|
||||
'table_upsert_row',
|
||||
'table_update_row',
|
||||
])
|
||||
|
||||
/**
|
||||
* Enriches a table tool description with table information based on the operation type.
|
||||
*/
|
||||
export function enrichTableToolDescription(
|
||||
originalDescription: string,
|
||||
table: TableSummary,
|
||||
toolId: string
|
||||
): string {
|
||||
if (!table.columns || table.columns.length === 0) {
|
||||
return originalDescription
|
||||
}
|
||||
|
||||
const columnList = table.columns.map((col) => ` - ${col.name} (${col.type})`).join('\n')
|
||||
|
||||
if (FILTER_OPERATIONS.has(toolId)) {
|
||||
const stringCols = table.columns.filter((c) => c.type === 'string')
|
||||
const numberCols = table.columns.filter((c) => c.type === 'number')
|
||||
|
||||
let filterExample = ''
|
||||
if (stringCols.length > 0 && numberCols.length > 0) {
|
||||
filterExample = `
|
||||
|
||||
Example filter: {"${stringCols[0].name}": {"$eq": "value"}, "${numberCols[0].name}": {"$lt": 50}}`
|
||||
} else if (stringCols.length > 0) {
|
||||
filterExample = `
|
||||
|
||||
Example filter: {"${stringCols[0].name}": {"$eq": "value"}}`
|
||||
}
|
||||
|
||||
let sortExample = ''
|
||||
if (toolId === 'table_query_rows' && numberCols.length > 0) {
|
||||
sortExample = `
|
||||
Example sort: {"${numberCols[0].name}": "desc"} for highest first, {"${numberCols[0].name}": "asc"} for lowest first`
|
||||
}
|
||||
|
||||
const queryInstructions =
|
||||
toolId === 'table_query_rows'
|
||||
? `
|
||||
INSTRUCTIONS:
|
||||
1. ALWAYS include a filter based on the user's question - queries without filters will fail
|
||||
2. Construct the filter yourself from the user's question - do NOT ask for confirmation
|
||||
3. Use exact match ($eq) by default unless the user specifies otherwise
|
||||
4. For ranking queries (highest, lowest, Nth, top N):
|
||||
- ALWAYS use sort with the relevant column (e.g., {"salary": "desc"} for highest salary)
|
||||
- Use limit to get only the needed rows (e.g., limit=1 for highest, limit=2 for second highest)
|
||||
- For "second highest X", use sort: {"X": "desc"} with limit: 2, then take the second result
|
||||
5. Only use limit=1000 when you need ALL matching rows`
|
||||
: `
|
||||
INSTRUCTIONS:
|
||||
1. ALWAYS include a filter based on the user's question - queries without filters will fail
|
||||
2. Construct the filter yourself from the user's question - do NOT ask for confirmation
|
||||
3. Use exact match ($eq) by default unless the user specifies otherwise`
|
||||
|
||||
return `${originalDescription}
|
||||
${queryInstructions}
|
||||
|
||||
Table "${table.name}" columns:
|
||||
${columnList}
|
||||
${filterExample}${sortExample}`
|
||||
}
|
||||
|
||||
if (DATA_OPERATIONS.has(toolId)) {
|
||||
const exampleCols = table.columns.slice(0, 3)
|
||||
const dataExample = exampleCols.reduce(
|
||||
(obj, col) => {
|
||||
obj[col.name] = col.type === 'number' ? 123 : col.type === 'boolean' ? true : 'example'
|
||||
return obj
|
||||
},
|
||||
{} as Record<string, unknown>
|
||||
)
|
||||
|
||||
if (toolId === 'table_update_row') {
|
||||
return `${originalDescription}
|
||||
|
||||
Table "${table.name}" available columns:
|
||||
${columnList}
|
||||
|
||||
For updates, only include the fields you want to change. Example: {"${exampleCols[0]?.name || 'field'}": "new_value"}`
|
||||
}
|
||||
|
||||
return `${originalDescription}
|
||||
|
||||
Table "${table.name}" available columns:
|
||||
${columnList}
|
||||
|
||||
Pass the "data" parameter with an object like: ${JSON.stringify(dataExample)}`
|
||||
}
|
||||
|
||||
return `${originalDescription}
|
||||
|
||||
Table "${table.name}" columns:
|
||||
${columnList}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Enriches LLM tool parameters with table-specific information.
|
||||
*/
|
||||
export function enrichTableToolParameters(
|
||||
llmSchema: { properties?: Record<string, any>; required?: string[] },
|
||||
table: TableSummary,
|
||||
toolId: string
|
||||
): { properties: Record<string, any>; required: string[] } {
|
||||
if (!table.columns || table.columns.length === 0) {
|
||||
return {
|
||||
properties: llmSchema.properties || {},
|
||||
required: llmSchema.required || [],
|
||||
}
|
||||
}
|
||||
|
||||
const columnNames = table.columns.map((c) => c.name).join(', ')
|
||||
const enrichedProperties = { ...llmSchema.properties }
|
||||
const enrichedRequired = llmSchema.required ? [...llmSchema.required] : []
|
||||
|
||||
if (enrichedProperties.filter && FILTER_OPERATIONS.has(toolId)) {
|
||||
enrichedProperties.filter = {
|
||||
...enrichedProperties.filter,
|
||||
description: `REQUIRED - query will fail without a filter. Construct filter from user's question using columns: ${columnNames}. Syntax: {"column": {"$eq": "value"}}`,
|
||||
}
|
||||
}
|
||||
|
||||
if (FILTER_OPERATIONS.has(toolId) && !enrichedRequired.includes('filter')) {
|
||||
enrichedRequired.push('filter')
|
||||
}
|
||||
|
||||
if (enrichedProperties.sort && toolId === 'table_query_rows') {
|
||||
enrichedProperties.sort = {
|
||||
...enrichedProperties.sort,
|
||||
description: `Sort order as {field: "asc"|"desc"}. REQUIRED for ranking queries (highest, lowest, Nth). Example: {"salary": "desc"} for highest salary first.`,
|
||||
}
|
||||
}
|
||||
|
||||
if (enrichedProperties.limit && toolId === 'table_query_rows') {
|
||||
enrichedProperties.limit = {
|
||||
...enrichedProperties.limit,
|
||||
description: `Maximum rows to return (min: 1, max: 1000, default: 100). For ranking queries: use limit=1 for highest/lowest, limit=2 for second highest, etc.`,
|
||||
}
|
||||
}
|
||||
|
||||
if (enrichedProperties.data && DATA_OPERATIONS.has(toolId)) {
|
||||
const exampleCols = table.columns.slice(0, 2)
|
||||
const exampleData = exampleCols.reduce(
|
||||
(obj: Record<string, unknown>, col: { name: string; type: string }) => {
|
||||
obj[col.name] = col.type === 'number' ? 123 : col.type === 'boolean' ? true : 'value'
|
||||
return obj
|
||||
},
|
||||
{} as Record<string, unknown>
|
||||
)
|
||||
|
||||
if (toolId === 'table_update_row') {
|
||||
enrichedProperties.data = {
|
||||
...enrichedProperties.data,
|
||||
description: `Object containing fields to update. Only include fields you want to change. Available columns: ${columnNames}`,
|
||||
}
|
||||
} else {
|
||||
enrichedProperties.data = {
|
||||
...enrichedProperties.data,
|
||||
description: `REQUIRED object containing row values. Use columns: ${columnNames}. Example value: ${JSON.stringify(exampleData)}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (enrichedProperties.rows && toolId === 'table_batch_insert_rows') {
|
||||
enrichedProperties.rows = {
|
||||
...enrichedProperties.rows,
|
||||
description: `REQUIRED. Array of row objects. Each object uses columns: ${columnNames}`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
properties: enrichedProperties,
|
||||
required: enrichedRequired,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from '@/lib/table/llm/enrichment'
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Wand enricher for table schema context.
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { userTableDefinitions } from '@sim/db/schema'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import type { TableSchema } from '@/lib/table/types'
|
||||
|
||||
const logger = createLogger('TableWandEnricher')
|
||||
|
||||
/**
|
||||
* Wand enricher that provides table schema context.
|
||||
* Used by the wand API to inject table column information into the system prompt.
|
||||
*/
|
||||
export async function enrichTableSchema(
|
||||
workspaceId: string | null,
|
||||
context: Record<string, unknown>
|
||||
): Promise<string | null> {
|
||||
const tableId = context.tableId as string | undefined
|
||||
if (!tableId || !workspaceId) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const [table] = await db
|
||||
.select({
|
||||
name: userTableDefinitions.name,
|
||||
schema: userTableDefinitions.schema,
|
||||
})
|
||||
.from(userTableDefinitions)
|
||||
.where(
|
||||
and(
|
||||
eq(userTableDefinitions.id, tableId),
|
||||
eq(userTableDefinitions.workspaceId, workspaceId),
|
||||
isNull(userTableDefinitions.archivedAt)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
|
||||
if (!table) {
|
||||
return null
|
||||
}
|
||||
|
||||
const schema = table.schema as TableSchema | null
|
||||
if (!schema?.columns?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const columnLines = schema.columns
|
||||
.map((col) => {
|
||||
const flags = [col.type, col.required && 'required', col.unique && 'unique'].filter(Boolean)
|
||||
return `- ${col.name} (${flags.join(', ')})`
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
const label = table.name ? `${table.name} (${tableId})` : tableId
|
||||
return `Table schema for ${label}:\n${columnLines}\nBuilt-in columns: createdAt, updatedAt`
|
||||
} catch (error) {
|
||||
logger.warn('Failed to fetch table schema', { tableId, error })
|
||||
return null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user