Files
simstudioai--sim/apps/sim/lib/table/llm/enrichment.ts
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

202 lines
6.6 KiB
TypeScript

/**
* 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,
}
}