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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+201
View File
@@ -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,
}
}
+1
View File
@@ -0,0 +1 @@
export * from '@/lib/table/llm/enrichment'
+64
View File
@@ -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
}
}