mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
updates
This commit is contained in:
@@ -711,6 +711,9 @@ export class AgentBlockHandler implements BlockHandler {
|
||||
getAllBlocks,
|
||||
getToolAsync: (toolId: string) => getToolAsync(toolId, ctx.workflowId),
|
||||
getTool,
|
||||
workspaceId: ctx.workspaceId,
|
||||
workflowId: ctx.workflowId,
|
||||
executeTool,
|
||||
})
|
||||
|
||||
if (transformedTool) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
export * from './constants'
|
||||
export * from './filters'
|
||||
export * from './llm-enrichment'
|
||||
export * from './query-builder'
|
||||
export * from './service'
|
||||
export * from './types'
|
||||
|
||||
184
apps/sim/lib/table/llm-enrichment.ts
Normal file
184
apps/sim/lib/table/llm-enrichment.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @module lib/table/llm-enrichment
|
||||
*/
|
||||
|
||||
/**
|
||||
* Table schema information used for LLM enrichment.
|
||||
*/
|
||||
export interface TableSchemaInfo {
|
||||
name: string
|
||||
columns: Array<{ name: string; type: string }>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 schema information based on the operation type.
|
||||
*
|
||||
* @param originalDescription - The original tool description
|
||||
* @param tableSchema - The table schema with name and columns
|
||||
* @param toolId - The tool identifier to determine operation type
|
||||
* @returns Enriched description with table-specific instructions
|
||||
*/
|
||||
export function enrichTableToolDescription(
|
||||
originalDescription: string,
|
||||
tableSchema: TableSchemaInfo,
|
||||
toolId: string
|
||||
): string {
|
||||
if (!tableSchema.columns || tableSchema.columns.length === 0) {
|
||||
return originalDescription
|
||||
}
|
||||
|
||||
const columnList = tableSchema.columns.map((col) => ` - ${col.name} (${col.type})`).join('\n')
|
||||
|
||||
// Filter-based operations: emphasize filter usage
|
||||
if (FILTER_OPERATIONS.has(toolId)) {
|
||||
const stringCols = tableSchema.columns.filter((c) => c.type === 'string')
|
||||
const numberCols = tableSchema.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"}}`
|
||||
}
|
||||
|
||||
return `${originalDescription}
|
||||
|
||||
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. Use limit=1000 to fetch matching rows, then count or process them as needed
|
||||
|
||||
Table "${tableSchema.name}" columns:
|
||||
${columnList}
|
||||
${filterExample}`
|
||||
}
|
||||
|
||||
// Data operations: show columns for data construction
|
||||
if (DATA_OPERATIONS.has(toolId)) {
|
||||
const exampleCols = tableSchema.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>
|
||||
)
|
||||
|
||||
return `${originalDescription}
|
||||
|
||||
Table "${tableSchema.name}" available columns:
|
||||
${columnList}
|
||||
|
||||
Pass the "data" parameter with an object like: ${JSON.stringify(dataExample)}`
|
||||
}
|
||||
|
||||
// Default: just show columns
|
||||
return `${originalDescription}
|
||||
|
||||
Table "${tableSchema.name}" columns:
|
||||
${columnList}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Enriches LLM tool parameters with table-specific information.
|
||||
*
|
||||
* @param llmSchema - The original LLM schema with properties and required fields
|
||||
* @param tableSchema - The table schema with name and columns
|
||||
* @param toolId - The tool identifier to determine operation type
|
||||
* @returns Enriched schema with updated property descriptions and required fields
|
||||
*/
|
||||
export function enrichTableToolParameters(
|
||||
llmSchema: { properties?: Record<string, any>; required?: string[] },
|
||||
tableSchema: TableSchemaInfo,
|
||||
toolId: string
|
||||
): { properties: Record<string, any>; required: string[] } {
|
||||
if (!tableSchema.columns || tableSchema.columns.length === 0) {
|
||||
return {
|
||||
properties: llmSchema.properties || {},
|
||||
required: llmSchema.required || [],
|
||||
}
|
||||
}
|
||||
|
||||
const columnNames = tableSchema.columns.map((c) => c.name).join(', ')
|
||||
const enrichedProperties = { ...llmSchema.properties }
|
||||
const enrichedRequired = llmSchema.required ? [...llmSchema.required] : []
|
||||
|
||||
// Enrich filter parameter for filter-based operations
|
||||
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"}}`,
|
||||
}
|
||||
}
|
||||
|
||||
// Mark filter as required in schema for query operations
|
||||
if (FILTER_OPERATIONS.has(toolId) && !enrichedRequired.includes('filter')) {
|
||||
enrichedRequired.push('filter')
|
||||
}
|
||||
|
||||
// Enrich limit parameter for query operations
|
||||
if (enrichedProperties.limit && toolId === 'table_query_rows') {
|
||||
enrichedProperties.limit = {
|
||||
...enrichedProperties.limit,
|
||||
description: `Maximum rows to return (min: 1, max: 1000, default: 100). Use limit=1000 to fetch all matching rows.`,
|
||||
}
|
||||
}
|
||||
|
||||
// Enrich data parameter for insert/update operations
|
||||
if (enrichedProperties.data && DATA_OPERATIONS.has(toolId)) {
|
||||
const exampleCols = tableSchema.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>
|
||||
)
|
||||
enrichedProperties.data = {
|
||||
...enrichedProperties.data,
|
||||
description: `REQUIRED object containing row values. Use columns: ${columnNames}. Example value: ${JSON.stringify(exampleData)}`,
|
||||
}
|
||||
}
|
||||
|
||||
// Enrich rows parameter for batch insert
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,33 @@ export const openaiProvider: ProviderConfig = {
|
||||
}))
|
||||
: undefined
|
||||
|
||||
// === DEBUG: Log full request details ===
|
||||
logger.info('[OpenAIProvider] === FULL REQUEST DEBUG ===')
|
||||
logger.info(`[OpenAIProvider] Messages: ${allMessages.length} total`)
|
||||
for (const [i, m] of allMessages.entries()) {
|
||||
const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content)
|
||||
logger.info(`[OpenAIProvider] [${i}] ${m.role}: ${content?.substring(0, 150)}...`)
|
||||
}
|
||||
|
||||
// Log tool definitions with formatted JSON
|
||||
if (tools?.length) {
|
||||
for (const tool of tools) {
|
||||
logger.info(`[OpenAIProvider] Tool: ${tool.function.name}`)
|
||||
logger.info(
|
||||
`[OpenAIProvider] Description: ${tool.function.description?.substring(0, 200)}...`
|
||||
)
|
||||
logger.info(`[OpenAIProvider] Parameters:`)
|
||||
const params = tool.function.parameters as any
|
||||
if (params?.properties) {
|
||||
for (const [key, val] of Object.entries(params.properties)) {
|
||||
const desc = (val as any).description || ''
|
||||
logger.info(`[OpenAIProvider] - ${key}: ${desc.substring(0, 100)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info('[OpenAIProvider] === END REQUEST DEBUG ===')
|
||||
|
||||
const payload: any = {
|
||||
model: request.model,
|
||||
messages: allMessages,
|
||||
@@ -293,6 +320,17 @@ export const openaiProvider: ProviderConfig = {
|
||||
|
||||
try {
|
||||
const toolArgs = JSON.parse(toolCall.function.arguments)
|
||||
|
||||
// === DEBUG: Log LLM tool call details ===
|
||||
logger.info('[OpenAIProvider] === LLM TOOL CALL ===')
|
||||
logger.info(`[OpenAIProvider] Tool: ${toolName}`)
|
||||
logger.info(`[OpenAIProvider] Arguments: ${JSON.stringify(toolArgs, null, 2)}`)
|
||||
if (toolName.startsWith('table_')) {
|
||||
const filterStr = toolArgs.filter ? JSON.stringify(toolArgs.filter, null, 2) : 'NONE'
|
||||
logger.info(`[OpenAIProvider] Filter: ${filterStr}`)
|
||||
logger.info(`[OpenAIProvider] Limit: ${toolArgs.limit || 'default'}`)
|
||||
}
|
||||
|
||||
const tool = request.tools?.find((t) => t.id === toolName)
|
||||
|
||||
if (!tool) {
|
||||
@@ -300,9 +338,22 @@ export const openaiProvider: ProviderConfig = {
|
||||
}
|
||||
|
||||
const { toolParams, executionParams } = prepareToolExecution(tool, toolArgs, request)
|
||||
|
||||
logger.info(`[OpenAIProvider] Executing ${toolName}...`)
|
||||
const result = await executeTool(toolName, executionParams, true)
|
||||
const toolCallEndTime = Date.now()
|
||||
|
||||
// === DEBUG: Log tool result ===
|
||||
const resultString = JSON.stringify(result)
|
||||
const sizeKB = Math.round(resultString.length / 1024)
|
||||
logger.info(`[OpenAIProvider] === TOOL RESULT ===`)
|
||||
logger.info(`[OpenAIProvider] Success: ${result.success}, Size: ${sizeKB}KB`)
|
||||
if (result.output?.rows) {
|
||||
logger.info(
|
||||
`[OpenAIProvider] Rows returned: ${result.output.rows.length}, Total matching: ${result.output.totalCount}`
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
toolCall,
|
||||
toolName,
|
||||
@@ -383,10 +434,17 @@ export const openaiProvider: ProviderConfig = {
|
||||
success: result.success,
|
||||
})
|
||||
|
||||
const toolMessageContent = JSON.stringify(resultContent)
|
||||
const msgSizeKB = Math.round(toolMessageContent.length / 1024)
|
||||
const estTokens = Math.round(toolMessageContent.length / 4)
|
||||
logger.info(
|
||||
`[OpenAIProvider] Adding ${toolName} result to conversation: ${msgSizeKB}KB (~${estTokens} tokens)`
|
||||
)
|
||||
|
||||
currentMessages.push({
|
||||
role: 'tool',
|
||||
tool_call_id: toolCall.id,
|
||||
content: JSON.stringify(resultContent),
|
||||
content: toolMessageContent,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { ChatCompletionChunk } from 'openai/resources/chat/completions'
|
||||
import type { CompletionUsage } from 'openai/resources/completions'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { isHosted } from '@/lib/core/config/feature-flags'
|
||||
import { enrichTableToolDescription, enrichTableToolParameters } from '@/lib/table/llm-enrichment'
|
||||
import { isCustomTool } from '@/executor/constants'
|
||||
import {
|
||||
getComputerUseModels,
|
||||
@@ -420,9 +421,20 @@ export async function transformBlockTool(
|
||||
getAllBlocks: () => any[]
|
||||
getTool: (toolId: string) => any
|
||||
getToolAsync?: (toolId: string) => Promise<any>
|
||||
workspaceId?: string
|
||||
workflowId?: string
|
||||
executeTool?: (toolId: string, params: Record<string, any>) => Promise<any>
|
||||
}
|
||||
): Promise<ProviderToolConfig | null> {
|
||||
const { selectedOperation, getAllBlocks, getTool, getToolAsync } = options
|
||||
const {
|
||||
selectedOperation,
|
||||
getAllBlocks,
|
||||
getTool,
|
||||
getToolAsync,
|
||||
workspaceId,
|
||||
workflowId,
|
||||
executeTool,
|
||||
} = options
|
||||
|
||||
const blockDef = getAllBlocks().find((b: any) => b.type === block.type)
|
||||
if (!blockDef) {
|
||||
@@ -485,12 +497,60 @@ export async function transformBlockTool(
|
||||
uniqueToolId = `${toolConfig.id}_${userProvidedParams.knowledgeBaseId}`
|
||||
}
|
||||
|
||||
// Enrich table tool descriptions with schema information
|
||||
let enrichedDescription = toolConfig.description
|
||||
let enrichedLlmSchema = llmSchema
|
||||
if (
|
||||
toolId.startsWith('table_') &&
|
||||
userProvidedParams.tableId &&
|
||||
workspaceId &&
|
||||
workflowId &&
|
||||
executeTool
|
||||
) {
|
||||
try {
|
||||
logger.info(`[transformBlockTool] Fetching schema for table ${userProvidedParams.tableId}`)
|
||||
const schemaResult = await executeTool('table_get_schema', {
|
||||
tableId: userProvidedParams.tableId,
|
||||
_context: { workspaceId, workflowId },
|
||||
})
|
||||
|
||||
if (schemaResult.success && schemaResult.output) {
|
||||
const tableSchema = {
|
||||
name: schemaResult.output.name,
|
||||
columns: schemaResult.output.columns || [],
|
||||
}
|
||||
|
||||
// Enrich description and parameters using lib/table utilities
|
||||
enrichedDescription = enrichTableToolDescription(
|
||||
toolConfig.description,
|
||||
tableSchema,
|
||||
toolId
|
||||
)
|
||||
const enrichedParams = enrichTableToolParameters(llmSchema, tableSchema, toolId)
|
||||
enrichedLlmSchema = {
|
||||
...llmSchema,
|
||||
properties: enrichedParams.properties,
|
||||
required:
|
||||
enrichedParams.required.length > 0 ? enrichedParams.required : llmSchema.required,
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[transformBlockTool] Enriched ${toolId} with ${tableSchema.columns.length} columns`
|
||||
)
|
||||
} else {
|
||||
logger.warn(`[transformBlockTool] Failed to fetch table schema: ${schemaResult.error}`)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`[transformBlockTool] Error fetching table schema:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: uniqueToolId,
|
||||
name: toolConfig.name,
|
||||
description: toolConfig.description,
|
||||
description: enrichedDescription,
|
||||
params: userProvidedParams,
|
||||
parameters: llmSchema,
|
||||
parameters: enrichedLlmSchema,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user