@@ -10,6 +10,85 @@ import type { TableSummary } from '../types'
1010
1111const logger = createLogger ( 'TableLLMEnrichment' )
1212
13+ /**
14+ * Cache for in-flight and recently fetched table schemas.
15+ * Key: tableId, Value: { promise, timestamp }
16+ * This deduplicates concurrent requests for the same table schema.
17+ */
18+ const schemaCache = new Map <
19+ string ,
20+ {
21+ promise : Promise < TableSummary | null >
22+ timestamp : number
23+ }
24+ > ( )
25+
26+ /** Schema cache TTL in milliseconds (5 seconds) */
27+ const SCHEMA_CACHE_TTL_MS = 5000
28+
29+ /**
30+ * Clears expired entries from the schema cache.
31+ */
32+ function cleanupSchemaCache ( ) : void {
33+ const now = Date . now ( )
34+ for ( const [ key , entry ] of schemaCache . entries ( ) ) {
35+ if ( now - entry . timestamp > SCHEMA_CACHE_TTL_MS ) {
36+ schemaCache . delete ( key )
37+ }
38+ }
39+ }
40+
41+ /**
42+ * Fetches table schema with caching and request deduplication.
43+ * If a request for the same table is already in flight, returns the same promise.
44+ */
45+ async function fetchTableSchemaWithCache (
46+ tableId : string ,
47+ context : TableEnrichmentContext
48+ ) : Promise < TableSummary | null > {
49+ // Clean up old entries periodically
50+ if ( schemaCache . size > 50 ) {
51+ cleanupSchemaCache ( )
52+ }
53+
54+ const cacheKey = `${ context . workspaceId } :${ tableId } `
55+ const cached = schemaCache . get ( cacheKey )
56+
57+ // If we have a cached entry that's still valid, return it
58+ if ( cached && Date . now ( ) - cached . timestamp < SCHEMA_CACHE_TTL_MS ) {
59+ return cached . promise
60+ }
61+
62+ // Create a new fetch promise
63+ const fetchPromise = ( async ( ) : Promise < TableSummary | null > => {
64+ const schemaResult = await context . executeTool ( 'table_get_schema' , {
65+ tableId,
66+ _context : {
67+ workspaceId : context . workspaceId ,
68+ workflowId : context . workflowId ,
69+ } ,
70+ } )
71+
72+ if ( ! schemaResult . success || ! schemaResult . output ) {
73+ logger . warn ( `Failed to fetch table schema: ${ schemaResult . error } ` )
74+ return null
75+ }
76+
77+ return {
78+ name : schemaResult . output . name ,
79+ columns : schemaResult . output . columns || [ ] ,
80+ }
81+ } ) ( )
82+
83+ // Cache the promise immediately to deduplicate concurrent requests
84+ schemaCache . set ( cacheKey , {
85+ promise : fetchPromise ,
86+ timestamp : Date . now ( ) ,
87+ } )
88+
89+ return fetchPromise
90+ }
91+
1392export interface TableEnrichmentContext {
1493 workspaceId : string
1594 workflowId : string
@@ -50,33 +129,17 @@ export async function enrichTableToolForLLM(
50129 }
51130
52131 try {
53- logger . info ( `Fetching schema for table ${ tableId } ` )
54-
55- const schemaResult = await context . executeTool ( 'table_get_schema' , {
56- tableId,
57- _context : {
58- workspaceId : context . workspaceId ,
59- workflowId : context . workflowId ,
60- } ,
61- } )
132+ // Use cached schema fetch to deduplicate concurrent requests for the same table
133+ const tableSchema = await fetchTableSchemaWithCache ( tableId , context )
62134
63- if ( ! schemaResult . success || ! schemaResult . output ) {
64- logger . warn ( `Failed to fetch table schema: ${ schemaResult . error } ` )
135+ if ( ! tableSchema ) {
65136 return null
66137 }
67138
68- const tableSchema : TableSummary = {
69- name : schemaResult . output . name ,
70- columns : schemaResult . output . columns || [ ] ,
71- }
72-
73139 // Apply enrichment using the existing utility functions
74140 const enrichedDescription = enrichTableToolDescription ( originalDescription , tableSchema , toolId )
75-
76141 const enrichedParams = enrichTableToolParameters ( llmSchema , tableSchema , toolId )
77142
78- logger . info ( `Enriched ${ toolId } with ${ tableSchema . columns . length } columns` )
79-
80143 return {
81144 description : enrichedDescription ,
82145 parameters : {
@@ -86,7 +149,7 @@ export async function enrichTableToolForLLM(
86149 } ,
87150 }
88151 } catch ( error ) {
89- logger . warn ( ` Error fetching table schema:` , error )
152+ logger . warn ( ' Error fetching table schema:' , error )
90153 return null
91154 }
92155}
@@ -190,6 +253,16 @@ ${filterExample}${sortExample}`
190253 { } as Record < string , unknown >
191254 )
192255
256+ // Update operations support partial updates
257+ if ( toolId === 'table_update_row' ) {
258+ return `${ originalDescription }
259+
260+ Table "${ table . name } " available columns:
261+ ${ columnList }
262+
263+ For updates, only include the fields you want to change. Example: {"${ exampleCols [ 0 ] ?. name || 'field' } ": "new_value"}`
264+ }
265+
193266 return `${ originalDescription }
194267
195268Table "${ table . name } " available columns:
@@ -268,9 +341,18 @@ export function enrichTableToolParameters(
268341 } ,
269342 { } as Record < string , unknown >
270343 )
271- enrichedProperties . data = {
272- ...enrichedProperties . data ,
273- description : `REQUIRED object containing row values. Use columns: ${ columnNames } . Example value: ${ JSON . stringify ( exampleData ) } ` ,
344+
345+ // Update operations support partial updates - only include fields to change
346+ if ( toolId === 'table_update_row' ) {
347+ enrichedProperties . data = {
348+ ...enrichedProperties . data ,
349+ description : `Object containing fields to update. Only include fields you want to change. Available columns: ${ columnNames } ` ,
350+ }
351+ } else {
352+ enrichedProperties . data = {
353+ ...enrichedProperties . data ,
354+ description : `REQUIRED object containing row values. Use columns: ${ columnNames } . Example value: ${ JSON . stringify ( exampleData ) } ` ,
355+ }
274356 }
275357 }
276358
0 commit comments