From d9423d8773919e2b6d041c87971cea16eb7d9780 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Fri, 22 May 2026 17:17:25 +0200 Subject: [PATCH] fix(scan): keep batched description failure bounded --- .../cli/src/context/scan/description-generation.test.ts | 7 ++++--- packages/cli/src/context/scan/description-generation.ts | 9 +++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/context/scan/description-generation.test.ts b/packages/cli/src/context/scan/description-generation.test.ts index a8678c81..bc7b1e25 100644 --- a/packages/cli/src/context/scan/description-generation.test.ts +++ b/packages/cli/src/context/scan/description-generation.test.ts @@ -461,7 +461,7 @@ describe('KtxDescriptionGenerator', () => { expect(llmRuntime.generateText).toHaveBeenCalledTimes(1); }); - it('tolerates structured object failures and falls back to prepared column values', async () => { + it('does not run per-column fallback when structured object generation throws', async () => { const llmRuntime = createLlmProvider('Fallback description'); llmRuntime.generateObject = vi.fn(async () => { throw new Error('object output unavailable'); @@ -488,9 +488,10 @@ describe('KtxDescriptionGenerator', () => { }); expect(result.tableDescription).toBeNull(); - expect(Object.fromEntries(result.columnDescriptions)).toEqual({ status: 'Fallback description' }); + expect(Object.fromEntries(result.columnDescriptions)).toEqual({ status: null }); expect(warnings).toContain('enrichment_failed'); - expect(llmRuntime.generateText).toHaveBeenCalledTimes(1); + expect(llmRuntime.generateObject).toHaveBeenCalledTimes(1); + expect(llmRuntime.generateText).not.toHaveBeenCalled(); }); }); diff --git a/packages/cli/src/context/scan/description-generation.ts b/packages/cli/src/context/scan/description-generation.ts index 485f2b21..beddfe71 100644 --- a/packages/cli/src/context/scan/description-generation.ts +++ b/packages/cli/src/context/scan/description-generation.ts @@ -755,6 +755,7 @@ export class KtxDescriptionGenerator { const sampleValues = sampleValuesByColumn(input.table.columns, sampleData); const descriptions = new Map(); let tableDescription: string | null = null; + let structuredGenerationSucceeded = false; try { const prompt = batchedPrompt({ @@ -774,6 +775,7 @@ export class KtxDescriptionGenerator { schema: batchedTableDescriptionSchema, temperature: this.settings.temperature, }); + structuredGenerationSucceeded = true; tableDescription = generated.tableDescription.trim() || null; const generatedColumns = new Map( generated.columns.map((column) => [column.name.toLowerCase(), column.description.trim() || null]), @@ -805,6 +807,13 @@ export class KtxDescriptionGenerator { }); } + if (!structuredGenerationSucceeded) { + for (const column of input.table.columns) { + descriptions.set(column.name, null); + } + return { tableDescription, columnDescriptions: descriptions }; + } + const tableContext = `Table: ${input.table.name} | Columns: ${input.table.columns.map((column) => column.name).join(', ')} | Data source: ${input.dataSourceType}`; for (const column of input.table.columns) { if (descriptions.get(column.name)) {