@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,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* QueryRefiner - Iteratively improves queries based on evaluation feedback
|
|
4
|
+
*
|
|
5
|
+
* Uses evaluation and refinement AI prompts to assess if queries answer
|
|
6
|
+
* the business question correctly and improve them through iterations.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.QueryRefiner = void 0;
|
|
10
|
+
const aiengine_1 = require("@memberjunction/aiengine");
|
|
11
|
+
const core_1 = require("@memberjunction/core");
|
|
12
|
+
const error_handlers_1 = require("../utils/error-handlers");
|
|
13
|
+
const PromptNames_1 = require("../prompts/PromptNames");
|
|
14
|
+
const prompt_helpers_1 = require("../utils/prompt-helpers");
|
|
15
|
+
/**
|
|
16
|
+
* QueryRefiner class
|
|
17
|
+
* Iteratively refines queries based on evaluation feedback
|
|
18
|
+
*/
|
|
19
|
+
class QueryRefiner {
|
|
20
|
+
tester;
|
|
21
|
+
contextUser;
|
|
22
|
+
config;
|
|
23
|
+
entityMetadata = [];
|
|
24
|
+
constructor(tester, contextUser, config) {
|
|
25
|
+
this.tester = tester;
|
|
26
|
+
this.contextUser = contextUser;
|
|
27
|
+
this.config = config;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Refine a query through evaluation and improvement iterations
|
|
31
|
+
*
|
|
32
|
+
* Loops up to maxRefinements times, testing and evaluating each iteration.
|
|
33
|
+
* Returns when query passes evaluation or max refinements reached.
|
|
34
|
+
*
|
|
35
|
+
* @param query - Initial generated query
|
|
36
|
+
* @param businessQuestion - Original business question
|
|
37
|
+
* @param entityMetadata - Entity metadata for refinement
|
|
38
|
+
* @param maxRefinements - Maximum refinement iterations (default: 3)
|
|
39
|
+
* @returns Refined query with test results and evaluation
|
|
40
|
+
*/
|
|
41
|
+
async refineQuery(query, businessQuestion, entityMetadata, maxRefinements = 3) {
|
|
42
|
+
// Store entity metadata for use in evaluation
|
|
43
|
+
this.entityMetadata = entityMetadata;
|
|
44
|
+
let currentQuery = query;
|
|
45
|
+
let refinementCount = 0;
|
|
46
|
+
// Track the last successfully tested query and its results
|
|
47
|
+
let lastWorkingQuery = query;
|
|
48
|
+
let lastWorkingTestResult = null;
|
|
49
|
+
let lastWorkingEvaluation = null;
|
|
50
|
+
// Ensure AIEngine is configured
|
|
51
|
+
await this.configureAIEngine();
|
|
52
|
+
while (refinementCount < maxRefinements) {
|
|
53
|
+
// 1. Test the current query
|
|
54
|
+
let testResult;
|
|
55
|
+
try {
|
|
56
|
+
testResult = await this.testCurrentQuery(currentQuery);
|
|
57
|
+
// Success! Save this as our last working version
|
|
58
|
+
lastWorkingQuery = currentQuery;
|
|
59
|
+
lastWorkingTestResult = testResult;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// Query broke during refinement - revert to last working version
|
|
63
|
+
if (this.config.verbose) {
|
|
64
|
+
(0, core_1.LogStatus)(`Refinement produced broken query: ${(0, error_handlers_1.extractErrorMessage)(error, 'Refinement Test')}. Reverting to last working version.`);
|
|
65
|
+
}
|
|
66
|
+
// If we have a previous working version, use that
|
|
67
|
+
if (lastWorkingTestResult && lastWorkingEvaluation) {
|
|
68
|
+
return this.buildSuccessResult(lastWorkingQuery, lastWorkingTestResult, lastWorkingEvaluation, refinementCount);
|
|
69
|
+
}
|
|
70
|
+
// No previous working version - throw the error
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
// 2. Evaluate if it answers the question
|
|
74
|
+
const evaluation = await this.evaluateQuery(currentQuery, businessQuestion, testResult);
|
|
75
|
+
lastWorkingEvaluation = evaluation;
|
|
76
|
+
// 3. If evaluation passes, we're done!
|
|
77
|
+
if (this.shouldStopRefining(evaluation)) {
|
|
78
|
+
return this.buildSuccessResult(currentQuery, testResult, evaluation, refinementCount);
|
|
79
|
+
}
|
|
80
|
+
// 4. Refine the query based on suggestions
|
|
81
|
+
refinementCount++;
|
|
82
|
+
if (this.config.verbose) {
|
|
83
|
+
(0, core_1.LogStatus)(`Refinement iteration ${refinementCount}/${maxRefinements}`);
|
|
84
|
+
}
|
|
85
|
+
currentQuery = await this.performRefinement(currentQuery, businessQuestion, evaluation, entityMetadata);
|
|
86
|
+
}
|
|
87
|
+
// Reached max refinements - return best attempt
|
|
88
|
+
// Use the last successfully tested query
|
|
89
|
+
if (lastWorkingTestResult && lastWorkingEvaluation) {
|
|
90
|
+
return {
|
|
91
|
+
query: lastWorkingQuery,
|
|
92
|
+
testResult: lastWorkingTestResult,
|
|
93
|
+
evaluation: lastWorkingEvaluation,
|
|
94
|
+
refinementCount,
|
|
95
|
+
reachedMaxRefinements: true,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Fallback: try to build final result with current query
|
|
99
|
+
return await this.buildFinalResult(currentQuery, businessQuestion, refinementCount);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Configure AIEngine for prompt execution
|
|
103
|
+
* Ensures engine is ready before running prompts
|
|
104
|
+
*/
|
|
105
|
+
async configureAIEngine() {
|
|
106
|
+
try {
|
|
107
|
+
const aiEngine = aiengine_1.AIEngine.Instance;
|
|
108
|
+
await aiEngine.Config(false, this.contextUser);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
throw new Error((0, error_handlers_1.extractErrorMessage)(error, 'AIEngine Configuration'));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Test current query using QueryTester
|
|
116
|
+
* Throws if query testing fails
|
|
117
|
+
*/
|
|
118
|
+
async testCurrentQuery(query) {
|
|
119
|
+
const testResult = await this.tester.testQuery(query);
|
|
120
|
+
if (!testResult.success) {
|
|
121
|
+
throw new Error(`Query testing failed after ${testResult.attempts} attempts: ${testResult.error}`);
|
|
122
|
+
}
|
|
123
|
+
return testResult;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Determine if refinement should stop based on evaluation
|
|
127
|
+
* Stops if query answers question and doesn't need refinement
|
|
128
|
+
*/
|
|
129
|
+
shouldStopRefining(evaluation) {
|
|
130
|
+
return evaluation.answersQuestion && !evaluation.needsRefinement;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Build success result when refinement loop completes successfully
|
|
134
|
+
*/
|
|
135
|
+
buildSuccessResult(query, testResult, evaluation, refinementCount) {
|
|
136
|
+
return {
|
|
137
|
+
query,
|
|
138
|
+
testResult,
|
|
139
|
+
evaluation,
|
|
140
|
+
refinementCount,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Build final result when max refinements reached
|
|
145
|
+
* Re-tests and re-evaluates final query
|
|
146
|
+
*/
|
|
147
|
+
async buildFinalResult(query, businessQuestion, refinementCount) {
|
|
148
|
+
const testResult = await this.tester.testQuery(query);
|
|
149
|
+
const evaluation = await this.evaluateQuery(query, businessQuestion, testResult);
|
|
150
|
+
return {
|
|
151
|
+
query,
|
|
152
|
+
testResult,
|
|
153
|
+
evaluation,
|
|
154
|
+
refinementCount,
|
|
155
|
+
reachedMaxRefinements: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Evaluate if query answers the business question correctly
|
|
160
|
+
* Uses Query Result Evaluator AI prompt
|
|
161
|
+
*
|
|
162
|
+
* @param query - Query to evaluate
|
|
163
|
+
* @param businessQuestion - Original business question
|
|
164
|
+
* @param testResult - Test execution results with sample data
|
|
165
|
+
* @returns Evaluation with confidence and suggestions
|
|
166
|
+
*/
|
|
167
|
+
async evaluateQuery(query, businessQuestion, testResult) {
|
|
168
|
+
try {
|
|
169
|
+
const aiEngine = aiengine_1.AIEngine.Instance;
|
|
170
|
+
const prompt = this.findPromptByName(aiEngine, PromptNames_1.PROMPT_QUERY_EVALUATOR);
|
|
171
|
+
// Limit sample results to first 10 rows for efficiency
|
|
172
|
+
const sampleResults = testResult.sampleRows?.slice(0, 10) || [];
|
|
173
|
+
const promptData = {
|
|
174
|
+
userQuestion: businessQuestion.userQuestion,
|
|
175
|
+
description: businessQuestion.description,
|
|
176
|
+
technicalDescription: businessQuestion.technicalDescription,
|
|
177
|
+
entityMetadata: this.entityMetadata,
|
|
178
|
+
generatedSQL: query.sql,
|
|
179
|
+
parameters: query.parameters,
|
|
180
|
+
sampleResults,
|
|
181
|
+
};
|
|
182
|
+
const evaluation = await this.executePrompt(prompt, promptData);
|
|
183
|
+
this.logEvaluation(evaluation);
|
|
184
|
+
return evaluation;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
throw new Error((0, error_handlers_1.extractErrorMessage)(error, 'QueryRefiner.evaluateQuery'));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Refine query based on evaluation feedback
|
|
192
|
+
* Uses Query Refiner AI prompt
|
|
193
|
+
*
|
|
194
|
+
* @param query - Current query to refine
|
|
195
|
+
* @param businessQuestion - Original business question
|
|
196
|
+
* @param evaluation - Evaluation feedback
|
|
197
|
+
* @param entityMetadata - Entity metadata for refinement
|
|
198
|
+
* @returns Refined query with improvements
|
|
199
|
+
*/
|
|
200
|
+
async performRefinement(query, businessQuestion, evaluation, entityMetadata) {
|
|
201
|
+
try {
|
|
202
|
+
const aiEngine = aiengine_1.AIEngine.Instance;
|
|
203
|
+
const prompt = this.findPromptByName(aiEngine, PromptNames_1.PROMPT_QUERY_REFINER);
|
|
204
|
+
const promptData = {
|
|
205
|
+
userQuestion: businessQuestion.userQuestion,
|
|
206
|
+
description: businessQuestion.description,
|
|
207
|
+
currentSQL: query.sql,
|
|
208
|
+
evaluationFeedback: evaluation,
|
|
209
|
+
entityMetadata,
|
|
210
|
+
};
|
|
211
|
+
const refinedQuery = await this.executePrompt(prompt, promptData);
|
|
212
|
+
if (this.config.verbose) {
|
|
213
|
+
(0, core_1.LogStatus)(`Refinements applied: ${refinedQuery.improvementsSummary}`);
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
sql: refinedQuery.sql,
|
|
217
|
+
parameters: refinedQuery.parameters,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
throw new Error((0, error_handlers_1.extractErrorMessage)(error, 'QueryRefiner.performRefinement'));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Find prompt by name in AIEngine cache
|
|
226
|
+
* Throws if prompt not found
|
|
227
|
+
*/
|
|
228
|
+
findPromptByName(aiEngine, promptName) {
|
|
229
|
+
const prompt = aiEngine.Prompts.find((p) => p.Name === promptName);
|
|
230
|
+
if (!prompt) {
|
|
231
|
+
throw new Error(`Prompt '${promptName}' not found in AIEngine cache`);
|
|
232
|
+
}
|
|
233
|
+
return prompt;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Execute AI prompt and parse result
|
|
237
|
+
* Generic method for any prompt type
|
|
238
|
+
*/
|
|
239
|
+
async executePrompt(prompt, promptData) {
|
|
240
|
+
const result = await (0, prompt_helpers_1.executePromptWithOverrides)(prompt, promptData, this.contextUser, this.config);
|
|
241
|
+
if (!result || !result.success) {
|
|
242
|
+
throw new Error(`AI prompt execution failed: ${result?.errorMessage || 'Unknown error'}`);
|
|
243
|
+
}
|
|
244
|
+
if (!result.result) {
|
|
245
|
+
throw new Error('AI prompt returned no result');
|
|
246
|
+
}
|
|
247
|
+
return result.result;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Log evaluation results for debugging
|
|
251
|
+
*/
|
|
252
|
+
logEvaluation(evaluation) {
|
|
253
|
+
if (!this.config.verbose)
|
|
254
|
+
return;
|
|
255
|
+
(0, core_1.LogStatus)(`Evaluation: answersQuestion=${evaluation.answersQuestion}, ` +
|
|
256
|
+
`confidence=${evaluation.confidence}, ` +
|
|
257
|
+
`needsRefinement=${evaluation.needsRefinement}`);
|
|
258
|
+
if (evaluation.reasoning) {
|
|
259
|
+
(0, core_1.LogStatus)(`Reasoning: ${evaluation.reasoning}`);
|
|
260
|
+
}
|
|
261
|
+
if (evaluation.suggestions.length > 0) {
|
|
262
|
+
(0, core_1.LogStatus)(`Suggestions: ${evaluation.suggestions.join('; ')}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
exports.QueryRefiner = QueryRefiner;
|
|
267
|
+
//# sourceMappingURL=QueryRefiner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QueryRefiner.js","sourceRoot":"","sources":["../../src/core/QueryRefiner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryTester - Tests and validates SQL queries
|
|
3
|
+
*
|
|
4
|
+
* Renders Nunjucks templates with sample parameter values and executes
|
|
5
|
+
* queries against the database. Handles error fixing with retry loop.
|
|
6
|
+
*/
|
|
7
|
+
import { DatabaseProviderBase, UserInfo } from '@memberjunction/core';
|
|
8
|
+
import { GeneratedQuery, QueryTestResult, EntityMetadataForPrompt, BusinessQuestion } from '../data/schema';
|
|
9
|
+
import { QueryGenConfig } from '../cli/config';
|
|
10
|
+
/**
|
|
11
|
+
* QueryTester class
|
|
12
|
+
* Tests SQL queries by rendering templates and executing against database
|
|
13
|
+
*/
|
|
14
|
+
export declare class QueryTester {
|
|
15
|
+
private dataProvider;
|
|
16
|
+
private entityMetadata;
|
|
17
|
+
private businessQuestion;
|
|
18
|
+
private contextUser;
|
|
19
|
+
private config;
|
|
20
|
+
private nunjucksEnv;
|
|
21
|
+
constructor(dataProvider: DatabaseProviderBase, entityMetadata: EntityMetadataForPrompt[], businessQuestion: BusinessQuestion, contextUser: UserInfo, config: QueryGenConfig);
|
|
22
|
+
/**
|
|
23
|
+
* Test a query by rendering template with sample values and executing it
|
|
24
|
+
* Retries up to maxAttempts times, calling QueryFixer on failures
|
|
25
|
+
*
|
|
26
|
+
* @param query - Generated query to test
|
|
27
|
+
* @param maxAttempts - Maximum number of retry attempts (default: 5)
|
|
28
|
+
* @returns Test result with success status, SQL, and sample data
|
|
29
|
+
*/
|
|
30
|
+
testQuery(query: GeneratedQuery, maxAttempts?: number): Promise<QueryTestResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Render query template with sample parameter values
|
|
33
|
+
* Uses QueryParameterProcessor for proper type handling
|
|
34
|
+
*
|
|
35
|
+
* @param query - Generated query with parameters
|
|
36
|
+
* @returns Rendered SQL string ready for execution
|
|
37
|
+
*/
|
|
38
|
+
private renderQueryTemplate;
|
|
39
|
+
/**
|
|
40
|
+
* Processes a raw parameter value based on its type, handling special cases like arrays.
|
|
41
|
+
* Follows Skip-Brain pattern for parameter processing.
|
|
42
|
+
* For array types, this function will:
|
|
43
|
+
* - Parse JSON arrays if the value is a JSON string
|
|
44
|
+
* - Split comma-separated strings as a fallback
|
|
45
|
+
* - Return as-is for sqlIn filter to handle
|
|
46
|
+
*
|
|
47
|
+
* @param rawValue - The raw parameter value (from sampleValue)
|
|
48
|
+
* @param paramType - The parameter type ('string', 'number', 'date', 'boolean', 'array')
|
|
49
|
+
* @returns Processed value ready for use in Nunjucks template
|
|
50
|
+
*/
|
|
51
|
+
private processParameterValue;
|
|
52
|
+
/**
|
|
53
|
+
* Execute SQL query against database
|
|
54
|
+
* Uses DataProvider to run query with contextUser
|
|
55
|
+
*
|
|
56
|
+
* @param sql - Rendered SQL query
|
|
57
|
+
* @returns Array of result rows
|
|
58
|
+
*/
|
|
59
|
+
private executeSQLQuery;
|
|
60
|
+
/**
|
|
61
|
+
* Fix a query that failed to execute
|
|
62
|
+
* Uses QueryFixer with AI to analyze error and generate correction
|
|
63
|
+
*
|
|
64
|
+
* @param query - Query that failed
|
|
65
|
+
* @param errorMessage - Error message from execution
|
|
66
|
+
* @returns Corrected query
|
|
67
|
+
*/
|
|
68
|
+
private fixQuery;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=QueryTester.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QueryTester.d.ts","sourceRoot":"","sources":["../../src/core/QueryTester.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,oBAAoB,EAEpB,QAAQ,EAET,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,cAAc,EACd,eAAe,EACf,uBAAuB,EACvB,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C;;;GAGG;AACH,qBAAa,WAAW;IAIpB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,gBAAgB;IACxB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM;IAPhB,OAAO,CAAC,WAAW,CAAuB;gBAGhC,YAAY,EAAE,oBAAoB,EAClC,cAAc,EAAE,uBAAuB,EAAE,EACzC,gBAAgB,EAAE,gBAAgB,EAClC,WAAW,EAAE,QAAQ,EACrB,MAAM,EAAE,cAAc;IAqBhC;;;;;;;OAOG;IACG,SAAS,CACb,KAAK,EAAE,cAAc,EACrB,WAAW,GAAE,MAAU,GACtB,OAAO,CAAC,eAAe,CAAC;IAgD3B;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IAsB3B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,qBAAqB;IA0D7B;;;;;;OAMG;YACW,eAAe;IAqB7B;;;;;;;OAOG;YACW,QAAQ;CAYvB"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* QueryTester - Tests and validates SQL queries
|
|
4
|
+
*
|
|
5
|
+
* Renders Nunjucks templates with sample parameter values and executes
|
|
6
|
+
* queries against the database. Handles error fixing with retry loop.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
25
|
+
if (mod && mod.__esModule) return mod;
|
|
26
|
+
var result = {};
|
|
27
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
28
|
+
__setModuleDefault(result, mod);
|
|
29
|
+
return result;
|
|
30
|
+
};
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.QueryTester = void 0;
|
|
33
|
+
const nunjucks = __importStar(require("nunjucks"));
|
|
34
|
+
const core_1 = require("@memberjunction/core");
|
|
35
|
+
const error_handlers_1 = require("../utils/error-handlers");
|
|
36
|
+
const QueryFixer_1 = require("./QueryFixer");
|
|
37
|
+
/**
|
|
38
|
+
* QueryTester class
|
|
39
|
+
* Tests SQL queries by rendering templates and executing against database
|
|
40
|
+
*/
|
|
41
|
+
class QueryTester {
|
|
42
|
+
dataProvider;
|
|
43
|
+
entityMetadata;
|
|
44
|
+
businessQuestion;
|
|
45
|
+
contextUser;
|
|
46
|
+
config;
|
|
47
|
+
nunjucksEnv;
|
|
48
|
+
constructor(dataProvider, entityMetadata, businessQuestion, contextUser, config) {
|
|
49
|
+
this.dataProvider = dataProvider;
|
|
50
|
+
this.entityMetadata = entityMetadata;
|
|
51
|
+
this.businessQuestion = businessQuestion;
|
|
52
|
+
this.contextUser = contextUser;
|
|
53
|
+
this.config = config;
|
|
54
|
+
// Initialize Nunjucks environment with SQL-safe filters
|
|
55
|
+
this.nunjucksEnv = new nunjucks.Environment(null, {
|
|
56
|
+
autoescape: false,
|
|
57
|
+
throwOnUndefined: true,
|
|
58
|
+
trimBlocks: true,
|
|
59
|
+
lstripBlocks: true,
|
|
60
|
+
});
|
|
61
|
+
// Add custom SQL-safe filters from RunQuerySQLFilterManager
|
|
62
|
+
const filterManager = core_1.RunQuerySQLFilterManager.Instance;
|
|
63
|
+
const filters = filterManager.getAllFilters();
|
|
64
|
+
for (const filter of filters) {
|
|
65
|
+
if (filter.implementation) {
|
|
66
|
+
this.nunjucksEnv.addFilter(filter.name, filter.implementation);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Test a query by rendering template with sample values and executing it
|
|
72
|
+
* Retries up to maxAttempts times, calling QueryFixer on failures
|
|
73
|
+
*
|
|
74
|
+
* @param query - Generated query to test
|
|
75
|
+
* @param maxAttempts - Maximum number of retry attempts (default: 5)
|
|
76
|
+
* @returns Test result with success status, SQL, and sample data
|
|
77
|
+
*/
|
|
78
|
+
async testQuery(query, maxAttempts = 5) {
|
|
79
|
+
let attempt = 0;
|
|
80
|
+
let lastError;
|
|
81
|
+
let currentQuery = query;
|
|
82
|
+
while (attempt < maxAttempts) {
|
|
83
|
+
attempt++;
|
|
84
|
+
try {
|
|
85
|
+
// 1. Render template with sample parameter values
|
|
86
|
+
const renderedSQL = this.renderQueryTemplate(currentQuery);
|
|
87
|
+
// 2. Execute SQL on database
|
|
88
|
+
const results = await this.executeSQLQuery(renderedSQL);
|
|
89
|
+
// 3. Success! (Empty results are valid - query executed without errors)
|
|
90
|
+
// Note: We don't validate rowCount because:
|
|
91
|
+
// - Empty results may indicate no data in database (not a query error)
|
|
92
|
+
// - Query structure can be correct even with zero rows returned
|
|
93
|
+
// - Testing should focus on SQL syntax/execution, not data presence
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
renderedSQL,
|
|
97
|
+
rowCount: results.length,
|
|
98
|
+
sampleRows: results.slice(0, 10), // Return first 10 rows
|
|
99
|
+
attempts: attempt,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
lastError = (0, error_handlers_1.extractErrorMessage)(error, 'Query Testing');
|
|
104
|
+
if (this.config.verbose) {
|
|
105
|
+
(0, core_1.LogError)(`Attempt ${attempt}/${maxAttempts} failed: ${lastError}`);
|
|
106
|
+
}
|
|
107
|
+
// 5. If not last attempt, try to fix the query
|
|
108
|
+
if (attempt < maxAttempts) {
|
|
109
|
+
currentQuery = await this.fixQuery(currentQuery, lastError);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Failed after max attempts
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
error: lastError,
|
|
117
|
+
attempts: maxAttempts,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Render query template with sample parameter values
|
|
122
|
+
* Uses QueryParameterProcessor for proper type handling
|
|
123
|
+
*
|
|
124
|
+
* @param query - Generated query with parameters
|
|
125
|
+
* @returns Rendered SQL string ready for execution
|
|
126
|
+
*/
|
|
127
|
+
renderQueryTemplate(query) {
|
|
128
|
+
// Build parameter values object
|
|
129
|
+
const paramValues = {};
|
|
130
|
+
for (const param of query.parameters) {
|
|
131
|
+
const rawValue = param.sampleValue;
|
|
132
|
+
if (rawValue !== undefined && rawValue !== null) {
|
|
133
|
+
paramValues[param.name] = this.processParameterValue(rawValue, param.type);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
// Render template using Nunjucks with SQL-safe filters
|
|
138
|
+
const renderedSQL = this.nunjucksEnv.renderString(query.sql, paramValues);
|
|
139
|
+
return renderedSQL;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw new Error(`Template rendering failed: ${(0, error_handlers_1.extractErrorMessage)(error, 'Nunjucks')}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Processes a raw parameter value based on its type, handling special cases like arrays.
|
|
147
|
+
* Follows Skip-Brain pattern for parameter processing.
|
|
148
|
+
* For array types, this function will:
|
|
149
|
+
* - Parse JSON arrays if the value is a JSON string
|
|
150
|
+
* - Split comma-separated strings as a fallback
|
|
151
|
+
* - Return as-is for sqlIn filter to handle
|
|
152
|
+
*
|
|
153
|
+
* @param rawValue - The raw parameter value (from sampleValue)
|
|
154
|
+
* @param paramType - The parameter type ('string', 'number', 'date', 'boolean', 'array')
|
|
155
|
+
* @returns Processed value ready for use in Nunjucks template
|
|
156
|
+
*/
|
|
157
|
+
processParameterValue(rawValue, paramType) {
|
|
158
|
+
if (rawValue === undefined || rawValue === null) {
|
|
159
|
+
return rawValue;
|
|
160
|
+
}
|
|
161
|
+
// For array type parameters, ensure value is compatible with sqlIn filter
|
|
162
|
+
if (paramType === 'array' && typeof rawValue === 'string') {
|
|
163
|
+
try {
|
|
164
|
+
// Try to parse as JSON array
|
|
165
|
+
const parsed = JSON.parse(rawValue);
|
|
166
|
+
if (Array.isArray(parsed)) {
|
|
167
|
+
return parsed;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Not valid JSON - return as-is for sqlIn filter to handle comma-separated strings
|
|
172
|
+
}
|
|
173
|
+
// Return comma-separated string as-is - sqlIn filter handles this
|
|
174
|
+
return rawValue;
|
|
175
|
+
}
|
|
176
|
+
// For non-array types, convert as needed
|
|
177
|
+
switch (paramType) {
|
|
178
|
+
case 'number':
|
|
179
|
+
if (typeof rawValue === 'string') {
|
|
180
|
+
const num = Number(rawValue);
|
|
181
|
+
if (isNaN(num)) {
|
|
182
|
+
throw new Error(`Invalid number sample value: ${rawValue}`);
|
|
183
|
+
}
|
|
184
|
+
return num;
|
|
185
|
+
}
|
|
186
|
+
return rawValue;
|
|
187
|
+
case 'boolean':
|
|
188
|
+
if (typeof rawValue === 'string') {
|
|
189
|
+
const lower = rawValue.toLowerCase();
|
|
190
|
+
if (lower !== 'true' && lower !== 'false') {
|
|
191
|
+
throw new Error(`Invalid boolean sample value: ${rawValue}`);
|
|
192
|
+
}
|
|
193
|
+
return lower === 'true';
|
|
194
|
+
}
|
|
195
|
+
return rawValue;
|
|
196
|
+
case 'date':
|
|
197
|
+
if (typeof rawValue === 'string') {
|
|
198
|
+
const date = new Date(rawValue);
|
|
199
|
+
if (isNaN(date.getTime())) {
|
|
200
|
+
throw new Error(`Invalid date sample value: ${rawValue}`);
|
|
201
|
+
}
|
|
202
|
+
return date;
|
|
203
|
+
}
|
|
204
|
+
return rawValue;
|
|
205
|
+
case 'string':
|
|
206
|
+
default:
|
|
207
|
+
return rawValue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Execute SQL query against database
|
|
212
|
+
* Uses DataProvider to run query with contextUser
|
|
213
|
+
*
|
|
214
|
+
* @param sql - Rendered SQL query
|
|
215
|
+
* @returns Array of result rows
|
|
216
|
+
*/
|
|
217
|
+
async executeSQLQuery(sql) {
|
|
218
|
+
try {
|
|
219
|
+
const result = await this.dataProvider.ExecuteSQL(sql, undefined, undefined, this.contextUser);
|
|
220
|
+
if (!result || !Array.isArray(result)) {
|
|
221
|
+
throw new Error('ExecuteSQL returned invalid result format');
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
throw new Error(`SQL execution failed: ${(0, error_handlers_1.extractErrorMessage)(error, 'Database')}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Fix a query that failed to execute
|
|
231
|
+
* Uses QueryFixer with AI to analyze error and generate correction
|
|
232
|
+
*
|
|
233
|
+
* @param query - Query that failed
|
|
234
|
+
* @param errorMessage - Error message from execution
|
|
235
|
+
* @returns Corrected query
|
|
236
|
+
*/
|
|
237
|
+
async fixQuery(query, errorMessage) {
|
|
238
|
+
const fixer = new QueryFixer_1.QueryFixer(this.contextUser, this.config);
|
|
239
|
+
return await fixer.fixQuery(query, errorMessage, this.entityMetadata, this.businessQuestion);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
exports.QueryTester = QueryTester;
|
|
243
|
+
//# sourceMappingURL=QueryTester.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QueryTester.js","sourceRoot":"","sources":["../../src/core/QueryTester.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryWriter - Generates SQL query templates using AI with few-shot learning
|
|
3
|
+
*
|
|
4
|
+
* Uses the SQL Query Writer AI prompt to generate Nunjucks SQL templates
|
|
5
|
+
* based on business questions and similar golden query examples.
|
|
6
|
+
*/
|
|
7
|
+
import { UserInfo } from '@memberjunction/core';
|
|
8
|
+
import { QueryGenConfig } from '../cli/config';
|
|
9
|
+
import { BusinessQuestion, GeneratedQuery, EntityMetadataForPrompt, GoldenQuery } from '../data/schema';
|
|
10
|
+
/**
|
|
11
|
+
* QueryWriter class
|
|
12
|
+
* Generates Nunjucks SQL query templates using AI with few-shot learning
|
|
13
|
+
*/
|
|
14
|
+
export declare class QueryWriter {
|
|
15
|
+
private contextUser;
|
|
16
|
+
private config;
|
|
17
|
+
constructor(contextUser: UserInfo, config: QueryGenConfig);
|
|
18
|
+
/**
|
|
19
|
+
* Generate SQL query template for a business question
|
|
20
|
+
* Uses few-shot learning with similar golden query examples
|
|
21
|
+
*
|
|
22
|
+
* @param businessQuestion - Business question to answer with SQL
|
|
23
|
+
* @param entityMetadata - Available entity metadata for query
|
|
24
|
+
* @param fewShotExamples - Similar golden queries for few-shot learning
|
|
25
|
+
* @returns Generated SQL query with parameters and output schema
|
|
26
|
+
*/
|
|
27
|
+
generateQuery(businessQuestion: BusinessQuestion, entityMetadata: EntityMetadataForPrompt[], fewShotExamples: GoldenQuery[]): Promise<GeneratedQuery>;
|
|
28
|
+
/**
|
|
29
|
+
* Find prompt by name in AIEngine cache
|
|
30
|
+
* Throws if prompt not found
|
|
31
|
+
*/
|
|
32
|
+
private findPromptByName;
|
|
33
|
+
/**
|
|
34
|
+
* Execute the SQL Query Writer AI prompt with retry logic for validation failures
|
|
35
|
+
* Parses JSON response and validates structure, retrying with feedback if validation fails
|
|
36
|
+
*/
|
|
37
|
+
private executePrompt;
|
|
38
|
+
/**
|
|
39
|
+
* Validate generated query structure
|
|
40
|
+
* Ensures query has proper SQL template syntax and valid metadata
|
|
41
|
+
*
|
|
42
|
+
* @param query - Generated query to validate
|
|
43
|
+
* @throws Error if query structure is invalid
|
|
44
|
+
*/
|
|
45
|
+
private validateGeneratedQuery;
|
|
46
|
+
/**
|
|
47
|
+
* Check if SQL uses base views (vw* pattern)
|
|
48
|
+
* Basic heuristic: looks for view names in FROM and JOIN clauses
|
|
49
|
+
*/
|
|
50
|
+
private usesBaseViews;
|
|
51
|
+
/**
|
|
52
|
+
* Validate a single query parameter
|
|
53
|
+
* Ensures all required fields are present and valid
|
|
54
|
+
*/
|
|
55
|
+
private validateParameter;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=QueryWriter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QueryWriter.d.ts","sourceRoot":"","sources":["../../src/core/QueryWriter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,QAAQ,EAAa,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGxG;;;GAGG;AACH,qBAAa,WAAW;IAEpB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM;gBADN,WAAW,EAAE,QAAQ,EACrB,MAAM,EAAE,cAAc;IAGhC;;;;;;;;OAQG;IACG,aAAa,CACjB,gBAAgB,EAAE,gBAAgB,EAClC,cAAc,EAAE,uBAAuB,EAAE,EACzC,eAAe,EAAE,WAAW,EAAE,GAC7B,OAAO,CAAC,cAAc,CAAC;IAgC1B;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAWxB;;;OAGG;YACW,aAAa;IAwE3B;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;IAsB9B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAMrB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;CAiC1B"}
|