@memberjunction/query-gen 0.0.1 → 2.126.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +34 -0
- package/COORDINATOR.md +768 -0
- package/IMPLEMENTATION_PLAN.md +1753 -0
- package/LLM_ENTITY_GROUPING_PLAN.md +977 -0
- package/README.md +675 -29
- package/dist/cli/commands/export.d.ts +15 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +178 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +19 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +282 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +17 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +193 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/config.d.ts +51 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +142 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +57 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/EntityGrouper.d.ts +74 -0
- package/dist/core/EntityGrouper.d.ts.map +1 -0
- package/dist/core/EntityGrouper.js +246 -0
- package/dist/core/EntityGrouper.js.map +1 -0
- package/dist/core/MetadataExporter.d.ts +59 -0
- package/dist/core/MetadataExporter.d.ts.map +1 -0
- package/dist/core/MetadataExporter.js +151 -0
- package/dist/core/MetadataExporter.js.map +1 -0
- package/dist/core/QueryDatabaseWriter.d.ts +50 -0
- package/dist/core/QueryDatabaseWriter.d.ts.map +1 -0
- package/dist/core/QueryDatabaseWriter.js +152 -0
- package/dist/core/QueryDatabaseWriter.js.map +1 -0
- package/dist/core/QueryFixer.d.ts +48 -0
- package/dist/core/QueryFixer.d.ts.map +1 -0
- package/dist/core/QueryFixer.js +115 -0
- package/dist/core/QueryFixer.js.map +1 -0
- package/dist/core/QueryRefiner.d.ts +94 -0
- package/dist/core/QueryRefiner.d.ts.map +1 -0
- package/dist/core/QueryRefiner.js +267 -0
- package/dist/core/QueryRefiner.js.map +1 -0
- package/dist/core/QueryTester.d.ts +70 -0
- package/dist/core/QueryTester.d.ts.map +1 -0
- package/dist/core/QueryTester.js +243 -0
- package/dist/core/QueryTester.js.map +1 -0
- package/dist/core/QueryWriter.d.ts +57 -0
- package/dist/core/QueryWriter.d.ts.map +1 -0
- package/dist/core/QueryWriter.js +184 -0
- package/dist/core/QueryWriter.js.map +1 -0
- package/dist/core/QuestionGenerator.d.ts +58 -0
- package/dist/core/QuestionGenerator.d.ts.map +1 -0
- package/dist/core/QuestionGenerator.js +145 -0
- package/dist/core/QuestionGenerator.js.map +1 -0
- package/dist/data/schema.d.ts +230 -0
- package/dist/data/schema.d.ts.map +1 -0
- package/dist/data/schema.js +6 -0
- package/dist/data/schema.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/PromptNames.d.ts +32 -0
- package/dist/prompts/PromptNames.d.ts.map +1 -0
- package/dist/prompts/PromptNames.js +35 -0
- package/dist/prompts/PromptNames.js.map +1 -0
- package/dist/utils/category-builder.d.ts +28 -0
- package/dist/utils/category-builder.d.ts.map +1 -0
- package/dist/utils/category-builder.js +90 -0
- package/dist/utils/category-builder.js.map +1 -0
- package/dist/utils/entity-helpers.d.ts +49 -0
- package/dist/utils/entity-helpers.d.ts.map +1 -0
- package/dist/utils/entity-helpers.js +189 -0
- package/dist/utils/entity-helpers.js.map +1 -0
- package/dist/utils/error-handlers.d.ts +19 -0
- package/dist/utils/error-handlers.d.ts.map +1 -0
- package/dist/utils/error-handlers.js +41 -0
- package/dist/utils/error-handlers.js.map +1 -0
- package/dist/utils/graph-helpers.d.ts +51 -0
- package/dist/utils/graph-helpers.d.ts.map +1 -0
- package/dist/utils/graph-helpers.js +82 -0
- package/dist/utils/graph-helpers.js.map +1 -0
- package/dist/utils/prompt-helpers.d.ts +25 -0
- package/dist/utils/prompt-helpers.d.ts.map +1 -0
- package/dist/utils/prompt-helpers.js +66 -0
- package/dist/utils/prompt-helpers.js.map +1 -0
- package/dist/utils/query-helpers.d.ts +23 -0
- package/dist/utils/query-helpers.d.ts.map +1 -0
- package/dist/utils/query-helpers.js +34 -0
- package/dist/utils/query-helpers.js.map +1 -0
- package/dist/utils/user-helpers.d.ts +15 -0
- package/dist/utils/user-helpers.d.ts.map +1 -0
- package/dist/utils/user-helpers.js +32 -0
- package/dist/utils/user-helpers.js.map +1 -0
- package/dist/vectors/EmbeddingService.d.ts +58 -0
- package/dist/vectors/EmbeddingService.d.ts.map +1 -0
- package/dist/vectors/EmbeddingService.js +90 -0
- package/dist/vectors/EmbeddingService.js.map +1 -0
- package/dist/vectors/SimilaritySearch.d.ts +51 -0
- package/dist/vectors/SimilaritySearch.d.ts.map +1 -0
- package/dist/vectors/SimilaritySearch.js +85 -0
- package/dist/vectors/SimilaritySearch.js.map +1 -0
- package/docs/API.md +1040 -0
- package/docs/ARCHITECTURE.md +1120 -0
- package/examples/advanced-usage.ts +401 -0
- package/examples/basic-usage.ts +285 -0
- package/package.json +48 -6
- package/src/cli/commands/export.ts +173 -0
- package/src/cli/commands/generate.ts +330 -0
- package/src/cli/commands/validate.ts +185 -0
- package/src/cli/config.ts +203 -0
- package/src/cli/index.ts +63 -0
- package/src/core/EntityGrouper.ts +318 -0
- package/src/core/MetadataExporter.ts +148 -0
- package/src/core/QueryDatabaseWriter.ts +187 -0
- package/src/core/QueryFixer.ts +153 -0
- package/src/core/QueryRefiner.ts +382 -0
- package/src/core/QueryTester.ts +264 -0
- package/src/core/QueryWriter.ts +239 -0
- package/src/core/QuestionGenerator.ts +199 -0
- package/src/data/golden-queries.json +1371 -0
- package/src/data/schema.ts +252 -0
- package/src/index.ts +49 -0
- package/src/prompts/PromptNames.ts +36 -0
- package/src/utils/category-builder.ts +97 -0
- package/src/utils/entity-helpers.ts +203 -0
- package/src/utils/error-handlers.ts +41 -0
- package/src/utils/graph-helpers.ts +99 -0
- package/src/utils/prompt-helpers.ts +79 -0
- package/src/utils/query-helpers.ts +32 -0
- package/src/utils/user-helpers.ts +39 -0
- package/src/vectors/EmbeddingService.ts +109 -0
- package/src/vectors/SimilaritySearch.ts +108 -0
- package/tsconfig.json +39 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryFixer - Fixes SQL queries that fail execution
|
|
3
|
+
*
|
|
4
|
+
* Uses the SQL Query Fixer AI prompt to analyze errors and generate
|
|
5
|
+
* corrected SQL queries with updated metadata.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AIEngine } from '@memberjunction/aiengine';
|
|
9
|
+
import { AIPromptEntityExtended } from '@memberjunction/core-entities';
|
|
10
|
+
import { UserInfo, LogStatus } from '@memberjunction/core';
|
|
11
|
+
import { extractErrorMessage } from '../utils/error-handlers';
|
|
12
|
+
import {
|
|
13
|
+
GeneratedQuery,
|
|
14
|
+
EntityMetadataForPrompt,
|
|
15
|
+
BusinessQuestion,
|
|
16
|
+
} from '../data/schema';
|
|
17
|
+
import { PROMPT_SQL_QUERY_FIXER } from '../prompts/PromptNames';
|
|
18
|
+
import { QueryGenConfig } from '../cli/config';
|
|
19
|
+
import { executePromptWithOverrides } from '../utils/prompt-helpers';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* QueryFixer class
|
|
23
|
+
* Fixes SQL queries that fail to execute
|
|
24
|
+
*/
|
|
25
|
+
export class QueryFixer {
|
|
26
|
+
constructor(
|
|
27
|
+
private contextUser: UserInfo,
|
|
28
|
+
private config: QueryGenConfig
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Fix a SQL query that failed to execute
|
|
33
|
+
* Uses AI to analyze the error and generate a corrected query
|
|
34
|
+
*
|
|
35
|
+
* @param query - The query that failed
|
|
36
|
+
* @param errorMessage - The error message from execution
|
|
37
|
+
* @param entityMetadata - Entity metadata for context
|
|
38
|
+
* @param businessQuestion - Original business question for context
|
|
39
|
+
* @returns Corrected SQL query with updated metadata
|
|
40
|
+
*/
|
|
41
|
+
async fixQuery(
|
|
42
|
+
query: GeneratedQuery,
|
|
43
|
+
errorMessage: string,
|
|
44
|
+
entityMetadata: EntityMetadataForPrompt[],
|
|
45
|
+
businessQuestion: BusinessQuestion
|
|
46
|
+
): Promise<GeneratedQuery> {
|
|
47
|
+
try {
|
|
48
|
+
// Ensure AIEngine is configured
|
|
49
|
+
const aiEngine = AIEngine.Instance;
|
|
50
|
+
await aiEngine.Config(false, this.contextUser);
|
|
51
|
+
|
|
52
|
+
// Find the SQL Query Fixer prompt
|
|
53
|
+
const prompt = this.findPromptByName(aiEngine, PROMPT_SQL_QUERY_FIXER);
|
|
54
|
+
|
|
55
|
+
// Prepare prompt data
|
|
56
|
+
const promptData = {
|
|
57
|
+
originalSQL: query.sql,
|
|
58
|
+
errorMessage,
|
|
59
|
+
parameters: query.parameters,
|
|
60
|
+
entityMetadata,
|
|
61
|
+
userQuestion: businessQuestion.userQuestion,
|
|
62
|
+
description: businessQuestion.description,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Execute AI prompt to fix the query
|
|
66
|
+
const fixedQuery = await this.executePrompt(prompt, promptData);
|
|
67
|
+
|
|
68
|
+
// Validate the fixed query structure
|
|
69
|
+
this.validateFixedQuery(fixedQuery);
|
|
70
|
+
|
|
71
|
+
return fixedQuery;
|
|
72
|
+
} catch (error: unknown) {
|
|
73
|
+
throw new Error(extractErrorMessage(error, 'QueryFixer.fixQuery'));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Find prompt by name in AIEngine cache
|
|
79
|
+
* Throws if prompt not found
|
|
80
|
+
*/
|
|
81
|
+
private findPromptByName(
|
|
82
|
+
aiEngine: AIEngine,
|
|
83
|
+
promptName: string
|
|
84
|
+
): AIPromptEntityExtended {
|
|
85
|
+
const prompt = aiEngine.Prompts.find((p) => p.Name === promptName);
|
|
86
|
+
if (!prompt) {
|
|
87
|
+
throw new Error(`Prompt '${promptName}' not found in AIEngine cache`);
|
|
88
|
+
}
|
|
89
|
+
return prompt;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Execute the SQL Query Fixer AI prompt
|
|
94
|
+
* Parses JSON response and validates structure
|
|
95
|
+
*/
|
|
96
|
+
private async executePrompt(
|
|
97
|
+
prompt: AIPromptEntityExtended,
|
|
98
|
+
promptData: {
|
|
99
|
+
originalSQL: string;
|
|
100
|
+
errorMessage: string;
|
|
101
|
+
parameters: GeneratedQuery['parameters'];
|
|
102
|
+
entityMetadata: EntityMetadataForPrompt[];
|
|
103
|
+
userQuestion: string;
|
|
104
|
+
description: string;
|
|
105
|
+
}
|
|
106
|
+
): Promise<GeneratedQuery> {
|
|
107
|
+
// The SQL Query Fixer template returns { newSQL, reasoning }
|
|
108
|
+
const result = await executePromptWithOverrides<{
|
|
109
|
+
newSQL: string;
|
|
110
|
+
reasoning: string;
|
|
111
|
+
}>(prompt, promptData, this.contextUser, this.config);
|
|
112
|
+
|
|
113
|
+
if (!result || !result.success) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`AI prompt execution failed: ${result?.errorMessage || 'Unknown error'}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!result.result) {
|
|
120
|
+
throw new Error('AI prompt returned no result');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Log the reasoning
|
|
124
|
+
if (this.config.verbose && result.result.reasoning) {
|
|
125
|
+
LogStatus(`Query fix reasoning: ${result.result.reasoning}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Return GeneratedQuery format, preserving original parameters
|
|
129
|
+
return {
|
|
130
|
+
sql: result.result.newSQL,
|
|
131
|
+
parameters: promptData.parameters,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Validate fixed query structure
|
|
137
|
+
* Ensures query has proper metadata
|
|
138
|
+
*
|
|
139
|
+
* @param query - Fixed query to validate
|
|
140
|
+
* @throws Error if query structure is invalid
|
|
141
|
+
*/
|
|
142
|
+
private validateFixedQuery(query: GeneratedQuery): void {
|
|
143
|
+
// Validate SQL is present
|
|
144
|
+
if (!query.sql || query.sql.trim().length === 0) {
|
|
145
|
+
throw new Error('Fixed query has empty SQL');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Validate parameters array
|
|
149
|
+
if (!Array.isArray(query.parameters)) {
|
|
150
|
+
throw new Error('Fixed query parameters must be an array');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryRefiner - Iteratively improves queries based on evaluation feedback
|
|
3
|
+
*
|
|
4
|
+
* Uses evaluation and refinement AI prompts to assess if queries answer
|
|
5
|
+
* the business question correctly and improve them through iterations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AIEngine } from '@memberjunction/aiengine';
|
|
9
|
+
import { AIPromptEntityExtended } from '@memberjunction/core-entities';
|
|
10
|
+
import { UserInfo, LogStatus } from '@memberjunction/core';
|
|
11
|
+
import { extractErrorMessage } from '../utils/error-handlers';
|
|
12
|
+
import {
|
|
13
|
+
GeneratedQuery,
|
|
14
|
+
BusinessQuestion,
|
|
15
|
+
EntityMetadataForPrompt,
|
|
16
|
+
RefinedQuery,
|
|
17
|
+
QueryEvaluation,
|
|
18
|
+
QueryTestResult,
|
|
19
|
+
} from '../data/schema';
|
|
20
|
+
import { QueryTester } from './QueryTester';
|
|
21
|
+
import { PROMPT_QUERY_EVALUATOR, PROMPT_QUERY_REFINER } from '../prompts/PromptNames';
|
|
22
|
+
import { QueryGenConfig } from '../cli/config';
|
|
23
|
+
import { executePromptWithOverrides } from '../utils/prompt-helpers';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* QueryRefiner class
|
|
27
|
+
* Iteratively refines queries based on evaluation feedback
|
|
28
|
+
*/
|
|
29
|
+
export class QueryRefiner {
|
|
30
|
+
private entityMetadata: EntityMetadataForPrompt[] = [];
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
private tester: QueryTester,
|
|
34
|
+
private contextUser: UserInfo,
|
|
35
|
+
private config: QueryGenConfig
|
|
36
|
+
) {}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Refine a query through evaluation and improvement iterations
|
|
40
|
+
*
|
|
41
|
+
* Loops up to maxRefinements times, testing and evaluating each iteration.
|
|
42
|
+
* Returns when query passes evaluation or max refinements reached.
|
|
43
|
+
*
|
|
44
|
+
* @param query - Initial generated query
|
|
45
|
+
* @param businessQuestion - Original business question
|
|
46
|
+
* @param entityMetadata - Entity metadata for refinement
|
|
47
|
+
* @param maxRefinements - Maximum refinement iterations (default: 3)
|
|
48
|
+
* @returns Refined query with test results and evaluation
|
|
49
|
+
*/
|
|
50
|
+
async refineQuery(
|
|
51
|
+
query: GeneratedQuery,
|
|
52
|
+
businessQuestion: BusinessQuestion,
|
|
53
|
+
entityMetadata: EntityMetadataForPrompt[],
|
|
54
|
+
maxRefinements: number = 3
|
|
55
|
+
): Promise<RefinedQuery> {
|
|
56
|
+
// Store entity metadata for use in evaluation
|
|
57
|
+
this.entityMetadata = entityMetadata;
|
|
58
|
+
|
|
59
|
+
let currentQuery = query;
|
|
60
|
+
let refinementCount = 0;
|
|
61
|
+
|
|
62
|
+
// Track the last successfully tested query and its results
|
|
63
|
+
let lastWorkingQuery = query;
|
|
64
|
+
let lastWorkingTestResult: QueryTestResult | null = null;
|
|
65
|
+
let lastWorkingEvaluation: QueryEvaluation | null = null;
|
|
66
|
+
|
|
67
|
+
// Ensure AIEngine is configured
|
|
68
|
+
await this.configureAIEngine();
|
|
69
|
+
|
|
70
|
+
while (refinementCount < maxRefinements) {
|
|
71
|
+
// 1. Test the current query
|
|
72
|
+
let testResult: QueryTestResult;
|
|
73
|
+
try {
|
|
74
|
+
testResult = await this.testCurrentQuery(currentQuery);
|
|
75
|
+
// Success! Save this as our last working version
|
|
76
|
+
lastWorkingQuery = currentQuery;
|
|
77
|
+
lastWorkingTestResult = testResult;
|
|
78
|
+
} catch (error: unknown) {
|
|
79
|
+
// Query broke during refinement - revert to last working version
|
|
80
|
+
if (this.config.verbose) {
|
|
81
|
+
LogStatus(`Refinement produced broken query: ${extractErrorMessage(error, 'Refinement Test')}. Reverting to last working version.`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// If we have a previous working version, use that
|
|
85
|
+
if (lastWorkingTestResult && lastWorkingEvaluation) {
|
|
86
|
+
return this.buildSuccessResult(
|
|
87
|
+
lastWorkingQuery,
|
|
88
|
+
lastWorkingTestResult,
|
|
89
|
+
lastWorkingEvaluation,
|
|
90
|
+
refinementCount
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// No previous working version - throw the error
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 2. Evaluate if it answers the question
|
|
99
|
+
const evaluation = await this.evaluateQuery(
|
|
100
|
+
currentQuery,
|
|
101
|
+
businessQuestion,
|
|
102
|
+
testResult
|
|
103
|
+
);
|
|
104
|
+
lastWorkingEvaluation = evaluation;
|
|
105
|
+
|
|
106
|
+
// 3. If evaluation passes, we're done!
|
|
107
|
+
if (this.shouldStopRefining(evaluation)) {
|
|
108
|
+
return this.buildSuccessResult(
|
|
109
|
+
currentQuery,
|
|
110
|
+
testResult,
|
|
111
|
+
evaluation,
|
|
112
|
+
refinementCount
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 4. Refine the query based on suggestions
|
|
117
|
+
refinementCount++;
|
|
118
|
+
if (this.config.verbose) {
|
|
119
|
+
LogStatus(`Refinement iteration ${refinementCount}/${maxRefinements}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
currentQuery = await this.performRefinement(
|
|
123
|
+
currentQuery,
|
|
124
|
+
businessQuestion,
|
|
125
|
+
evaluation,
|
|
126
|
+
entityMetadata
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Reached max refinements - return best attempt
|
|
131
|
+
// Use the last successfully tested query
|
|
132
|
+
if (lastWorkingTestResult && lastWorkingEvaluation) {
|
|
133
|
+
return {
|
|
134
|
+
query: lastWorkingQuery,
|
|
135
|
+
testResult: lastWorkingTestResult,
|
|
136
|
+
evaluation: lastWorkingEvaluation,
|
|
137
|
+
refinementCount,
|
|
138
|
+
reachedMaxRefinements: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Fallback: try to build final result with current query
|
|
143
|
+
return await this.buildFinalResult(
|
|
144
|
+
currentQuery,
|
|
145
|
+
businessQuestion,
|
|
146
|
+
refinementCount
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Configure AIEngine for prompt execution
|
|
152
|
+
* Ensures engine is ready before running prompts
|
|
153
|
+
*/
|
|
154
|
+
private async configureAIEngine(): Promise<void> {
|
|
155
|
+
try {
|
|
156
|
+
const aiEngine = AIEngine.Instance;
|
|
157
|
+
await aiEngine.Config(false, this.contextUser);
|
|
158
|
+
} catch (error: unknown) {
|
|
159
|
+
throw new Error(extractErrorMessage(error, 'AIEngine Configuration'));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Test current query using QueryTester
|
|
165
|
+
* Throws if query testing fails
|
|
166
|
+
*/
|
|
167
|
+
private async testCurrentQuery(query: GeneratedQuery): Promise<QueryTestResult> {
|
|
168
|
+
const testResult = await this.tester.testQuery(query);
|
|
169
|
+
|
|
170
|
+
if (!testResult.success) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`Query testing failed after ${testResult.attempts} attempts: ${testResult.error}`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return testResult;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Determine if refinement should stop based on evaluation
|
|
181
|
+
* Stops if query answers question and doesn't need refinement
|
|
182
|
+
*/
|
|
183
|
+
private shouldStopRefining(evaluation: QueryEvaluation): boolean {
|
|
184
|
+
return evaluation.answersQuestion && !evaluation.needsRefinement;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Build success result when refinement loop completes successfully
|
|
189
|
+
*/
|
|
190
|
+
private buildSuccessResult(
|
|
191
|
+
query: GeneratedQuery,
|
|
192
|
+
testResult: QueryTestResult,
|
|
193
|
+
evaluation: QueryEvaluation,
|
|
194
|
+
refinementCount: number
|
|
195
|
+
): RefinedQuery {
|
|
196
|
+
return {
|
|
197
|
+
query,
|
|
198
|
+
testResult,
|
|
199
|
+
evaluation,
|
|
200
|
+
refinementCount,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Build final result when max refinements reached
|
|
206
|
+
* Re-tests and re-evaluates final query
|
|
207
|
+
*/
|
|
208
|
+
private async buildFinalResult(
|
|
209
|
+
query: GeneratedQuery,
|
|
210
|
+
businessQuestion: BusinessQuestion,
|
|
211
|
+
refinementCount: number
|
|
212
|
+
): Promise<RefinedQuery> {
|
|
213
|
+
const testResult = await this.tester.testQuery(query);
|
|
214
|
+
const evaluation = await this.evaluateQuery(
|
|
215
|
+
query,
|
|
216
|
+
businessQuestion,
|
|
217
|
+
testResult
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
query,
|
|
222
|
+
testResult,
|
|
223
|
+
evaluation,
|
|
224
|
+
refinementCount,
|
|
225
|
+
reachedMaxRefinements: true,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Evaluate if query answers the business question correctly
|
|
231
|
+
* Uses Query Result Evaluator AI prompt
|
|
232
|
+
*
|
|
233
|
+
* @param query - Query to evaluate
|
|
234
|
+
* @param businessQuestion - Original business question
|
|
235
|
+
* @param testResult - Test execution results with sample data
|
|
236
|
+
* @returns Evaluation with confidence and suggestions
|
|
237
|
+
*/
|
|
238
|
+
private async evaluateQuery(
|
|
239
|
+
query: GeneratedQuery,
|
|
240
|
+
businessQuestion: BusinessQuestion,
|
|
241
|
+
testResult: QueryTestResult
|
|
242
|
+
): Promise<QueryEvaluation> {
|
|
243
|
+
try {
|
|
244
|
+
const aiEngine = AIEngine.Instance;
|
|
245
|
+
const prompt = this.findPromptByName(aiEngine, PROMPT_QUERY_EVALUATOR);
|
|
246
|
+
|
|
247
|
+
// Limit sample results to first 10 rows for efficiency
|
|
248
|
+
const sampleResults = testResult.sampleRows?.slice(0, 10) || [];
|
|
249
|
+
|
|
250
|
+
const promptData = {
|
|
251
|
+
userQuestion: businessQuestion.userQuestion,
|
|
252
|
+
description: businessQuestion.description,
|
|
253
|
+
technicalDescription: businessQuestion.technicalDescription,
|
|
254
|
+
entityMetadata: this.entityMetadata,
|
|
255
|
+
generatedSQL: query.sql,
|
|
256
|
+
parameters: query.parameters,
|
|
257
|
+
sampleResults,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const evaluation = await this.executePrompt<QueryEvaluation>(
|
|
261
|
+
prompt,
|
|
262
|
+
promptData
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
this.logEvaluation(evaluation);
|
|
266
|
+
return evaluation;
|
|
267
|
+
} catch (error: unknown) {
|
|
268
|
+
throw new Error(extractErrorMessage(error, 'QueryRefiner.evaluateQuery'));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Refine query based on evaluation feedback
|
|
274
|
+
* Uses Query Refiner AI prompt
|
|
275
|
+
*
|
|
276
|
+
* @param query - Current query to refine
|
|
277
|
+
* @param businessQuestion - Original business question
|
|
278
|
+
* @param evaluation - Evaluation feedback
|
|
279
|
+
* @param entityMetadata - Entity metadata for refinement
|
|
280
|
+
* @returns Refined query with improvements
|
|
281
|
+
*/
|
|
282
|
+
private async performRefinement(
|
|
283
|
+
query: GeneratedQuery,
|
|
284
|
+
businessQuestion: BusinessQuestion,
|
|
285
|
+
evaluation: QueryEvaluation,
|
|
286
|
+
entityMetadata: EntityMetadataForPrompt[]
|
|
287
|
+
): Promise<GeneratedQuery> {
|
|
288
|
+
try {
|
|
289
|
+
const aiEngine = AIEngine.Instance;
|
|
290
|
+
const prompt = this.findPromptByName(aiEngine, PROMPT_QUERY_REFINER);
|
|
291
|
+
|
|
292
|
+
const promptData = {
|
|
293
|
+
userQuestion: businessQuestion.userQuestion,
|
|
294
|
+
description: businessQuestion.description,
|
|
295
|
+
currentSQL: query.sql,
|
|
296
|
+
evaluationFeedback: evaluation,
|
|
297
|
+
entityMetadata,
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const refinedQuery = await this.executePrompt<
|
|
301
|
+
GeneratedQuery & { improvementsSummary: string }
|
|
302
|
+
>(prompt, promptData);
|
|
303
|
+
|
|
304
|
+
if (this.config.verbose) {
|
|
305
|
+
LogStatus(`Refinements applied: ${refinedQuery.improvementsSummary}`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
sql: refinedQuery.sql,
|
|
310
|
+
parameters: refinedQuery.parameters,
|
|
311
|
+
};
|
|
312
|
+
} catch (error: unknown) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
extractErrorMessage(error, 'QueryRefiner.performRefinement')
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Find prompt by name in AIEngine cache
|
|
321
|
+
* Throws if prompt not found
|
|
322
|
+
*/
|
|
323
|
+
private findPromptByName(
|
|
324
|
+
aiEngine: AIEngine,
|
|
325
|
+
promptName: string
|
|
326
|
+
): AIPromptEntityExtended {
|
|
327
|
+
const prompt = aiEngine.Prompts.find((p) => p.Name === promptName);
|
|
328
|
+
if (!prompt) {
|
|
329
|
+
throw new Error(`Prompt '${promptName}' not found in AIEngine cache`);
|
|
330
|
+
}
|
|
331
|
+
return prompt;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Execute AI prompt and parse result
|
|
336
|
+
* Generic method for any prompt type
|
|
337
|
+
*/
|
|
338
|
+
private async executePrompt<T>(
|
|
339
|
+
prompt: AIPromptEntityExtended,
|
|
340
|
+
promptData: Record<string, unknown>
|
|
341
|
+
): Promise<T> {
|
|
342
|
+
const result = await executePromptWithOverrides<T>(
|
|
343
|
+
prompt,
|
|
344
|
+
promptData,
|
|
345
|
+
this.contextUser,
|
|
346
|
+
this.config
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
if (!result || !result.success) {
|
|
350
|
+
throw new Error(
|
|
351
|
+
`AI prompt execution failed: ${result?.errorMessage || 'Unknown error'}`
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (!result.result) {
|
|
356
|
+
throw new Error('AI prompt returned no result');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return result.result;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Log evaluation results for debugging
|
|
364
|
+
*/
|
|
365
|
+
private logEvaluation(evaluation: QueryEvaluation): void {
|
|
366
|
+
if (!this.config.verbose) return;
|
|
367
|
+
|
|
368
|
+
LogStatus(
|
|
369
|
+
`Evaluation: answersQuestion=${evaluation.answersQuestion}, ` +
|
|
370
|
+
`confidence=${evaluation.confidence}, ` +
|
|
371
|
+
`needsRefinement=${evaluation.needsRefinement}`
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
if (evaluation.reasoning) {
|
|
375
|
+
LogStatus(`Reasoning: ${evaluation.reasoning}`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (evaluation.suggestions.length > 0) {
|
|
379
|
+
LogStatus(`Suggestions: ${evaluation.suggestions.join('; ')}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|