nttp 1.0.0

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.
Files changed (54) hide show
  1. package/README.md +245 -0
  2. package/dist/NTTP.d.ts +121 -0
  3. package/dist/NTTP.d.ts.map +1 -0
  4. package/dist/NTTP.js +229 -0
  5. package/dist/NTTP.js.map +1 -0
  6. package/dist/cache/exact-cache.d.ts +46 -0
  7. package/dist/cache/exact-cache.d.ts.map +1 -0
  8. package/dist/cache/exact-cache.js +104 -0
  9. package/dist/cache/exact-cache.js.map +1 -0
  10. package/dist/cache/index.d.ts +8 -0
  11. package/dist/cache/index.d.ts.map +1 -0
  12. package/dist/cache/index.js +7 -0
  13. package/dist/cache/index.js.map +1 -0
  14. package/dist/cache/semantic-cache.d.ts +82 -0
  15. package/dist/cache/semantic-cache.d.ts.map +1 -0
  16. package/dist/cache/semantic-cache.js +243 -0
  17. package/dist/cache/semantic-cache.js.map +1 -0
  18. package/dist/cache/types.d.ts +135 -0
  19. package/dist/cache/types.d.ts.map +1 -0
  20. package/dist/cache/types.js +5 -0
  21. package/dist/cache/types.js.map +1 -0
  22. package/dist/cache.d.ts +71 -0
  23. package/dist/cache.d.ts.map +1 -0
  24. package/dist/cache.js +129 -0
  25. package/dist/cache.js.map +1 -0
  26. package/dist/errors.d.ts +35 -0
  27. package/dist/errors.d.ts.map +1 -0
  28. package/dist/errors.js +55 -0
  29. package/dist/errors.js.map +1 -0
  30. package/dist/executor.d.ts +82 -0
  31. package/dist/executor.d.ts.map +1 -0
  32. package/dist/executor.js +395 -0
  33. package/dist/executor.js.map +1 -0
  34. package/dist/index.d.ts +8 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +7 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/intent.d.ts +55 -0
  39. package/dist/intent.d.ts.map +1 -0
  40. package/dist/intent.js +183 -0
  41. package/dist/intent.js.map +1 -0
  42. package/dist/llm.d.ts +60 -0
  43. package/dist/llm.d.ts.map +1 -0
  44. package/dist/llm.js +171 -0
  45. package/dist/llm.js.map +1 -0
  46. package/dist/types.d.ts +280 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +5 -0
  49. package/dist/types.js.map +1 -0
  50. package/dist/utils.d.ts +61 -0
  51. package/dist/utils.d.ts.map +1 -0
  52. package/dist/utils.js +19 -0
  53. package/dist/utils.js.map +1 -0
  54. package/package.json +94 -0
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Query execution, SQL generation, and schema inference.
3
+ * Main orchestration pipeline for NTTP.
4
+ */
5
+ import { SQLGenerationError, SQLExecutionError, LLMError, } from './errors.js';
6
+ /**
7
+ * JSON Schema for SQL generation (for structured outputs).
8
+ */
9
+ const SQL_GENERATION_JSON_SCHEMA = {
10
+ type: 'object',
11
+ properties: {
12
+ sql: {
13
+ type: 'string',
14
+ description: 'The SQL query to execute',
15
+ },
16
+ params: {
17
+ type: 'array',
18
+ items: {
19
+ type: ['string', 'integer', 'number', 'boolean', 'null'],
20
+ },
21
+ description: 'Parameters for the SQL query',
22
+ },
23
+ },
24
+ required: ['sql', 'params'],
25
+ additionalProperties: false,
26
+ };
27
+ /**
28
+ * System prompt for SQL generation.
29
+ */
30
+ const SQL_GENERATION_SYSTEM_PROMPT = `You are an expert SQL generator.
31
+ Generate safe, read-only SQL queries from structured intents.
32
+
33
+ {schema}
34
+
35
+ Rules:
36
+ - Use parameterized queries with ? placeholders for values
37
+ - Add LIMIT clause (max 1000)
38
+ - Only SELECT queries - no UPDATE, DELETE, DROP, ALTER, INSERT
39
+ - Use proper JOINs for relationships between tables
40
+ - Handle filters intelligently (e.g., status='active', created_at > date)
41
+
42
+ Return JSON:
43
+ {
44
+ "sql": "SELECT ... FROM ... WHERE ... LIMIT ?",
45
+ "params": [value1, value2, ...]
46
+ }
47
+
48
+ Examples:
49
+ Intent: {"entity": "users", "operation": "list", "filters": {"status": "active"}, "limit": 10}
50
+ Response: {"sql": "SELECT * FROM users WHERE status = ? LIMIT ?", "params": ["active", 10]}
51
+
52
+ Intent: {"entity": "orders", "operation": "count", "filters": {"status": "pending"}}
53
+ Response: {"sql": "SELECT COUNT(*) as count FROM orders WHERE status = ?", "params": ["pending"]}
54
+ `;
55
+ /**
56
+ * Service for executing queries and managing SQL generation.
57
+ */
58
+ export class QueryExecutor {
59
+ db;
60
+ llm;
61
+ cache;
62
+ l1Cache;
63
+ l2Cache;
64
+ defaultLimit = 100;
65
+ constructor(db, llm, cache, l1Cache, l2Cache) {
66
+ this.db = db;
67
+ this.llm = llm;
68
+ this.cache = cache;
69
+ this.l1Cache = l1Cache;
70
+ this.l2Cache = l2Cache;
71
+ }
72
+ /**
73
+ * Execute query with v2 3-layer caching.
74
+ */
75
+ async execute(options) {
76
+ const startTime = Date.now();
77
+ const { query, intent, useCache, forceNewSchema } = options;
78
+ // If v2 not enabled, fall back to v1 behavior
79
+ if (!this.l1Cache && !this.l2Cache) {
80
+ return this.executeV1(options);
81
+ }
82
+ // ─────────────────────────────────────────────────────────
83
+ // L1: EXACT MATCH (hash-based cache)
84
+ // Cost: $0.00 | Latency: <1ms
85
+ // ─────────────────────────────────────────────────────────
86
+ if (this.l1Cache && useCache && !forceNewSchema) {
87
+ const l1Hit = this.l1Cache.get(query);
88
+ if (l1Hit) {
89
+ const data = await this.executeRaw(l1Hit.sql, l1Hit.params);
90
+ const meta = {
91
+ cacheLayer: 1,
92
+ cost: 0,
93
+ latency: Date.now() - startTime,
94
+ };
95
+ return {
96
+ query,
97
+ data,
98
+ schemaId: l1Hit.schemaId,
99
+ cacheHit: true,
100
+ intent,
101
+ sql: l1Hit.sql,
102
+ params: l1Hit.params,
103
+ meta,
104
+ };
105
+ }
106
+ }
107
+ // ─────────────────────────────────────────────────────────
108
+ // L2: SEMANTIC MATCH (embedding-based similarity)
109
+ // Cost: ~$0.0001 | Latency: 50-100ms
110
+ // ─────────────────────────────────────────────────────────
111
+ let l2Embedding;
112
+ if (this.l2Cache && useCache && !forceNewSchema) {
113
+ const { match, embedding } = await this.l2Cache.find(query);
114
+ l2Embedding = embedding; // Save for L3 if needed
115
+ if (match) {
116
+ const data = await this.executeRaw(match.result.sql, match.result.params);
117
+ // Promote to L1 for future exact matches
118
+ if (this.l1Cache) {
119
+ this.l1Cache.set(query, match.result);
120
+ }
121
+ const meta = {
122
+ cacheLayer: 2,
123
+ cost: 0.0001,
124
+ latency: Date.now() - startTime,
125
+ similarity: match.similarity,
126
+ };
127
+ return {
128
+ query,
129
+ data,
130
+ schemaId: match.result.schemaId,
131
+ cacheHit: true,
132
+ intent,
133
+ sql: match.result.sql,
134
+ params: match.result.params,
135
+ meta,
136
+ };
137
+ }
138
+ // L2 miss - embedding saved for reuse in L3 to prevent double API billing
139
+ }
140
+ // ─────────────────────────────────────────────────────────
141
+ // L3: LLM FALLBACK (full pipeline)
142
+ // Cost: ~$0.01 | Latency: 2-3s
143
+ // ─────────────────────────────────────────────────────────
144
+ const schemaId = this.generateSchemaId(intent);
145
+ const { sql, params } = await this.generateSql(intent);
146
+ const data = await this.executeRaw(sql, params);
147
+ const result = {
148
+ schemaId,
149
+ sql,
150
+ params,
151
+ hitCount: 1,
152
+ createdAt: new Date(),
153
+ lastUsedAt: new Date(),
154
+ };
155
+ // Populate both L1 and L2 caches
156
+ if (this.l1Cache) {
157
+ this.l1Cache.set(query, result);
158
+ }
159
+ if (this.l2Cache) {
160
+ // Reuse embedding from L2 miss to prevent double API billing
161
+ if (l2Embedding) {
162
+ this.l2Cache.addWithEmbedding(query, l2Embedding, result);
163
+ }
164
+ else {
165
+ // L2 not checked (cache disabled or forced new schema)
166
+ await this.l2Cache.add(query, result);
167
+ }
168
+ }
169
+ // Also populate v1 schema cache (for backward compat)
170
+ const resultSchema = this.inferSchemaFromResults(data);
171
+ const schemaDefinition = {
172
+ schema_id: schemaId,
173
+ intent_pattern: intent.normalized_text,
174
+ generated_sql: sql,
175
+ sql_params: params,
176
+ result_schema: resultSchema,
177
+ use_count: 1,
178
+ created_at: new Date(),
179
+ last_used_at: new Date(),
180
+ example_queries: [query],
181
+ pinned: false,
182
+ };
183
+ await this.cache.set(schemaId, schemaDefinition);
184
+ const meta = {
185
+ cacheLayer: 3,
186
+ cost: 0.01,
187
+ latency: Date.now() - startTime,
188
+ };
189
+ return {
190
+ query,
191
+ data,
192
+ schemaId,
193
+ cacheHit: false,
194
+ intent,
195
+ sql,
196
+ params,
197
+ meta,
198
+ };
199
+ }
200
+ /**
201
+ * Execute query with v1 caching (legacy).
202
+ */
203
+ async executeV1(options) {
204
+ const { query, intent, useCache, forceNewSchema } = options;
205
+ // Generate schema ID from intent
206
+ const schemaId = this.generateSchemaId(intent);
207
+ // Check cache if enabled and not forcing new schema
208
+ if (useCache && !forceNewSchema) {
209
+ const cachedSchema = await this.cache.get(schemaId);
210
+ if (cachedSchema) {
211
+ // Cache hit - use cached SQL
212
+ const data = await this.executeRaw(cachedSchema.generated_sql, cachedSchema.sql_params);
213
+ // Update cache usage
214
+ await this.cache.updateUsage(schemaId);
215
+ await this.cache.addExampleQuery(schemaId, query);
216
+ return {
217
+ query,
218
+ data,
219
+ schemaId,
220
+ cacheHit: true,
221
+ intent,
222
+ sql: cachedSchema.generated_sql,
223
+ params: cachedSchema.sql_params,
224
+ };
225
+ }
226
+ }
227
+ // Cache miss - generate and execute SQL
228
+ const { sql, params } = await this.generateSql(intent);
229
+ const data = await this.executeRaw(sql, params);
230
+ // Infer schema from results
231
+ const resultSchema = this.inferSchemaFromResults(data);
232
+ // Store in cache
233
+ const schemaDefinition = {
234
+ schema_id: schemaId,
235
+ intent_pattern: intent.normalized_text,
236
+ generated_sql: sql,
237
+ sql_params: params,
238
+ result_schema: resultSchema,
239
+ use_count: 1,
240
+ created_at: new Date(),
241
+ last_used_at: new Date(),
242
+ example_queries: [query],
243
+ pinned: false,
244
+ };
245
+ await this.cache.set(schemaId, schemaDefinition);
246
+ return {
247
+ query,
248
+ data,
249
+ schemaId,
250
+ cacheHit: false,
251
+ intent,
252
+ sql,
253
+ params,
254
+ };
255
+ }
256
+ /**
257
+ * Generate SQL query from intent.
258
+ */
259
+ async generateSql(intent) {
260
+ try {
261
+ // Prepare intent for LLM
262
+ const intentDict = {
263
+ entity: intent.entity,
264
+ operation: intent.operation,
265
+ filters: intent.filters,
266
+ limit: intent.limit || this.defaultLimit,
267
+ fields: intent.fields,
268
+ sort: intent.sort,
269
+ };
270
+ const schemaDescription = await this.getSchemaDescription();
271
+ const systemPrompt = SQL_GENERATION_SYSTEM_PROMPT.replace('{schema}', schemaDescription);
272
+ // Call LLM to generate SQL
273
+ const result = await this.llm.callStructured(`Generate SQL for this intent:\n${JSON.stringify(intentDict, null, 2)}`, systemPrompt, SQL_GENERATION_JSON_SCHEMA, 0.0);
274
+ // Validate SQL safety
275
+ this.validateSqlSafety(result.sql);
276
+ return result;
277
+ }
278
+ catch (error) {
279
+ if (error instanceof LLMError) {
280
+ throw new SQLGenerationError(`Failed to generate SQL: ${error}`);
281
+ }
282
+ throw new SQLGenerationError(`SQL generation failed: ${error}`);
283
+ }
284
+ }
285
+ /**
286
+ * Execute raw SQL query.
287
+ */
288
+ async executeRaw(sql, params = []) {
289
+ try {
290
+ const result = await this.db.raw(sql, params);
291
+ // Normalize different dialect return formats
292
+ if (Array.isArray(result)) {
293
+ return result;
294
+ }
295
+ if (result.rows)
296
+ return result.rows; // PostgreSQL
297
+ if (Array.isArray(result[0]))
298
+ return result[0]; // MySQL
299
+ if (result.recordset)
300
+ return result.recordset; // SQL Server
301
+ return [];
302
+ }
303
+ catch (error) {
304
+ throw new SQLExecutionError(`Query failed: ${error}`);
305
+ }
306
+ }
307
+ /**
308
+ * Validate SQL safety (read-only).
309
+ */
310
+ validateSqlSafety(sql) {
311
+ const dangerous = [
312
+ /\bUPDATE\b/i,
313
+ /\bDELETE\b/i,
314
+ /\bDROP\b/i,
315
+ /\bALTER\b/i,
316
+ /\bINSERT\b/i,
317
+ /\bCREATE\b/i,
318
+ ];
319
+ for (const pattern of dangerous) {
320
+ if (pattern.test(sql)) {
321
+ throw new SQLGenerationError(`Dangerous SQL operation detected: ${pattern.source}`);
322
+ }
323
+ }
324
+ if (!sql.trim().match(/^(SELECT|WITH)\b/i)) {
325
+ throw new SQLGenerationError('SQL must start with SELECT or WITH');
326
+ }
327
+ }
328
+ /**
329
+ * Infer JSON schema from query results.
330
+ */
331
+ inferSchemaFromResults(results) {
332
+ if (results.length === 0) {
333
+ return {};
334
+ }
335
+ const schema = {};
336
+ const sample = results[0];
337
+ for (const [key, value] of Object.entries(sample)) {
338
+ schema[key] = { type: this.inferFieldType(value) };
339
+ }
340
+ return schema;
341
+ }
342
+ /**
343
+ * Infer type of a field value.
344
+ */
345
+ inferFieldType(value) {
346
+ if (value === null)
347
+ return 'null';
348
+ if (typeof value === 'boolean')
349
+ return 'boolean';
350
+ if (typeof value === 'number') {
351
+ return Number.isInteger(value) ? 'integer' : 'number';
352
+ }
353
+ if (typeof value === 'string') {
354
+ // Detect dates
355
+ if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
356
+ return 'string'; // Keep as string, but could be 'date'
357
+ }
358
+ return 'string';
359
+ }
360
+ if (Array.isArray(value))
361
+ return 'array';
362
+ if (typeof value === 'object')
363
+ return 'object';
364
+ return 'string';
365
+ }
366
+ /**
367
+ * Get schema description for LLM.
368
+ */
369
+ async getSchemaDescription() {
370
+ const tables = await this.db
371
+ .select('name')
372
+ .from('sqlite_master')
373
+ .where('type', 'table')
374
+ .andWhere('name', 'not like', 'sqlite_%');
375
+ const lines = ['Database schema:'];
376
+ for (const { name } of tables) {
377
+ const columns = await this.db(name).columnInfo();
378
+ const columnNames = Object.keys(columns).join(', ');
379
+ lines.push(`- ${name} (${columnNames})`);
380
+ }
381
+ return lines.join('\n');
382
+ }
383
+ /**
384
+ * Generate schema ID from intent (SHA256 hash).
385
+ */
386
+ generateSchemaId(intent) {
387
+ const crypto = require('crypto');
388
+ const hash = crypto
389
+ .createHash('sha256')
390
+ .update(intent.normalized_text)
391
+ .digest('hex');
392
+ return hash.substring(0, 16);
393
+ }
394
+ }
395
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * NTTP - Natural Text Transfer Protocol
3
+ * Query databases with natural language using Claude AI
4
+ */
5
+ export { NTTP } from './NTTP.js';
6
+ export type { NTTPConfig, QueryOptions, QueryResult, Intent, SchemaDefinition, CacheStats, } from './types.js';
7
+ export { IntentParseError, SQLGenerationError, SQLExecutionError, LLMError, CacheError, } from './errors.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EACV,UAAU,EACV,YAAY,EACZ,WAAW,EACX,MAAM,EACN,gBAAgB,EAChB,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,UAAU,GACX,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * NTTP - Natural Text Transfer Protocol
3
+ * Query databases with natural language using Claude AI
4
+ */
5
+ export { NTTP } from './NTTP.js';
6
+ export { IntentParseError, SQLGenerationError, SQLExecutionError, LLMError, CacheError, } from './errors.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AASjC,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,UAAU,GACX,MAAM,aAAa,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Intent parsing and normalization for cache key generation.
3
+ */
4
+ import type { Intent, OperationType } from './types.js';
5
+ import type { LLMService } from './llm.js';
6
+ import type { FilterConditions, SortSpec } from './utils.js';
7
+ /**
8
+ * Raw intent response from LLM before normalization.
9
+ */
10
+ interface RawIntent {
11
+ entity: string;
12
+ operation: OperationType;
13
+ filters?: FilterConditions;
14
+ limit?: number | null;
15
+ fields?: string[] | null;
16
+ sort?: SortSpec | null;
17
+ }
18
+ /**
19
+ * Service for parsing natural language into structured intents.
20
+ */
21
+ export declare class IntentParser {
22
+ private llm;
23
+ constructor(llm: LLMService);
24
+ /**
25
+ * Parse natural language query into structured intent.
26
+ *
27
+ * @param query Natural language query
28
+ * @param schemaDescription Database schema description
29
+ * @returns Structured Intent object
30
+ * @throws IntentParseError if parsing fails
31
+ */
32
+ parse(query: string, schemaDescription: string): Promise<Intent>;
33
+ /**
34
+ * Normalize intent dictionary to canonical form for cache key generation.
35
+ *
36
+ * CRITICAL: This algorithm must match Python exactly to ensure
37
+ * cache key compatibility between implementations.
38
+ *
39
+ * @param intentData Intent dictionary from LLM
40
+ * @returns Normalized string representation
41
+ */
42
+ normalizeIntentDict(intentData: RawIntent): string;
43
+ /**
44
+ * Generate unique schema ID from intent.
45
+ *
46
+ * CRITICAL: Must use exact same algorithm as Python (SHA256, first 16 chars)
47
+ * to ensure cache compatibility.
48
+ *
49
+ * @param intent Intent object
50
+ * @returns Schema ID (16-char hash)
51
+ */
52
+ generateSchemaId(intent: Intent): string;
53
+ }
54
+ export {};
55
+ //# sourceMappingURL=intent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAExD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAyB,gBAAgB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAyCpF;;GAEG;AACH,UAAU,SAAS;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,aAAa,CAAC;IACzB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CACvB;AAkCD;;GAEG;AACH,qBAAa,YAAY;IACX,OAAO,CAAC,GAAG;gBAAH,GAAG,EAAE,UAAU;IAEnC;;;;;;;OAOG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA4CtE;;;;;;;;OAQG;IACH,mBAAmB,CAAC,UAAU,EAAE,SAAS,GAAG,MAAM;IAkDlD;;;;;;;;OAQG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;CAKzC"}
package/dist/intent.js ADDED
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Intent parsing and normalization for cache key generation.
3
+ */
4
+ import crypto from 'crypto';
5
+ import { IntentParseError, LLMError } from './errors.js';
6
+ /**
7
+ * JSON Schema for intent parsing (for structured outputs).
8
+ */
9
+ const INTENT_JSON_SCHEMA = {
10
+ type: 'object',
11
+ properties: {
12
+ entity: {
13
+ type: 'string',
14
+ description: 'Target table name (users, products, orders, or order_items)',
15
+ },
16
+ operation: {
17
+ type: 'string',
18
+ enum: ['list', 'count', 'aggregate', 'filter'],
19
+ description: 'Operation type',
20
+ },
21
+ filters: {
22
+ type: 'object',
23
+ description: 'Filter conditions as key-value pairs',
24
+ additionalProperties: false,
25
+ },
26
+ limit: {
27
+ type: ['integer', 'null'],
28
+ description: 'Result limit, or null for default',
29
+ },
30
+ fields: {
31
+ type: ['array', 'null'],
32
+ items: { type: 'string' },
33
+ description: 'Specific fields to return, or null for all fields',
34
+ },
35
+ sort: {
36
+ type: ['string', 'null'],
37
+ description: "Sort specification (e.g., 'created_at:desc'), or null",
38
+ },
39
+ },
40
+ required: ['entity', 'operation'],
41
+ additionalProperties: false,
42
+ };
43
+ /**
44
+ * System prompt for intent parsing.
45
+ */
46
+ const INTENT_PARSE_SYSTEM_PROMPT = `You are an expert at parsing natural language database queries.
47
+ Extract structured intent from user queries about an e-commerce database.
48
+
49
+ {schema}
50
+
51
+ Return JSON with this exact structure:
52
+ {
53
+ "entity": "<table_name>",
54
+ "operation": "<list|count|aggregate|filter>",
55
+ "filters": {"<field>": "<value>"},
56
+ "limit": <number or null>,
57
+ "fields": [<field names>] or null,
58
+ "sort": "<field:asc|desc>" or null
59
+ }
60
+
61
+ Rules:
62
+ - entity must be a valid table name from the schema above
63
+ - operation: "list" for SELECT, "count" for COUNT, "aggregate" for SUM/AVG/etc
64
+ - filters: extract conditions (e.g., "active users" -> {"status": "active"})
65
+ - limit: extract limit if specified, otherwise null
66
+ - fields: specific fields requested, or null for all fields
67
+ - sort: sorting specification if mentioned
68
+
69
+ Examples:
70
+ - "get all active users" -> {"entity": "users", "operation": "list", "filters": {"status": "active"}, "limit": null}
71
+ - "show me 10 products" -> {"entity": "products", "operation": "list", "filters": {}, "limit": 10}
72
+ - "count pending orders" -> {"entity": "orders", "operation": "count", "filters": {"status": "pending"}, "limit": null}
73
+ `;
74
+ /**
75
+ * Service for parsing natural language into structured intents.
76
+ */
77
+ export class IntentParser {
78
+ llm;
79
+ constructor(llm) {
80
+ this.llm = llm;
81
+ }
82
+ /**
83
+ * Parse natural language query into structured intent.
84
+ *
85
+ * @param query Natural language query
86
+ * @param schemaDescription Database schema description
87
+ * @returns Structured Intent object
88
+ * @throws IntentParseError if parsing fails
89
+ */
90
+ async parse(query, schemaDescription) {
91
+ try {
92
+ // Prepare system prompt with schema
93
+ const systemPrompt = INTENT_PARSE_SYSTEM_PROMPT.replace('{schema}', schemaDescription);
94
+ // Call LLM to parse intent with structured outputs (guaranteed schema compliance)
95
+ const result = await this.llm.callStructured(query, systemPrompt, INTENT_JSON_SCHEMA, 0.0);
96
+ // Ensure filters is a dict (guaranteed by schema, but defensive programming)
97
+ if (!result.filters) {
98
+ result.filters = {};
99
+ }
100
+ // Generate normalized text for cache key
101
+ const normalized = this.normalizeIntentDict(result);
102
+ // Create Intent object
103
+ const intent = {
104
+ entity: result.entity,
105
+ operation: result.operation,
106
+ filters: result.filters || {},
107
+ limit: result.limit,
108
+ fields: result.fields,
109
+ sort: result.sort,
110
+ normalized_text: normalized,
111
+ };
112
+ return intent;
113
+ }
114
+ catch (error) {
115
+ if (error instanceof LLMError) {
116
+ throw new IntentParseError(`Failed to understand query: ${error}`);
117
+ }
118
+ throw new IntentParseError(`Failed to parse query: ${error}`);
119
+ }
120
+ }
121
+ /**
122
+ * Normalize intent dictionary to canonical form for cache key generation.
123
+ *
124
+ * CRITICAL: This algorithm must match Python exactly to ensure
125
+ * cache key compatibility between implementations.
126
+ *
127
+ * @param intentData Intent dictionary from LLM
128
+ * @returns Normalized string representation
129
+ */
130
+ normalizeIntentDict(intentData) {
131
+ // Extract key components
132
+ const entity = (intentData.entity || '').toLowerCase().trim();
133
+ const operation = (intentData.operation || '').toLowerCase().trim();
134
+ const filters = intentData.filters || {};
135
+ const limit = intentData.limit;
136
+ const fields = intentData.fields || [];
137
+ const sort = intentData.sort;
138
+ // Normalize filters: sort keys and convert values to lowercase strings
139
+ const normalizedFilters = {};
140
+ for (const [key, value] of Object.entries(filters)) {
141
+ const keyClean = key.toLowerCase().trim();
142
+ const valueClean = typeof value === 'string' ? value.toLowerCase().trim() : String(value);
143
+ normalizedFilters[keyClean] = valueClean;
144
+ }
145
+ // Sort filter keys for consistency
146
+ const sortedFilters = Object.entries(normalizedFilters).sort(([a], [b]) => a.localeCompare(b));
147
+ // Build normalized representation
148
+ const parts = [`entity:${entity}`, `operation:${operation}`];
149
+ if (sortedFilters.length > 0) {
150
+ const filtersStr = sortedFilters.map(([k, v]) => `${k}=${v}`).join(',');
151
+ parts.push(`filters:${filtersStr}`);
152
+ }
153
+ if (limit) {
154
+ parts.push(`limit:${limit}`);
155
+ }
156
+ if (fields && fields.length > 0) {
157
+ const fieldsStr = fields
158
+ .map((f) => String(f).toLowerCase())
159
+ .sort()
160
+ .join(',');
161
+ parts.push(`fields:${fieldsStr}`);
162
+ }
163
+ if (sort) {
164
+ parts.push(`sort:${String(sort).toLowerCase()}`);
165
+ }
166
+ return parts.join('|');
167
+ }
168
+ /**
169
+ * Generate unique schema ID from intent.
170
+ *
171
+ * CRITICAL: Must use exact same algorithm as Python (SHA256, first 16 chars)
172
+ * to ensure cache compatibility.
173
+ *
174
+ * @param intent Intent object
175
+ * @returns Schema ID (16-char hash)
176
+ */
177
+ generateSchemaId(intent) {
178
+ const normalized = intent.normalized_text;
179
+ const hash = crypto.createHash('sha256').update(normalized).digest('hex');
180
+ return hash.substring(0, 16);
181
+ }
182
+ }
183
+ //# sourceMappingURL=intent.js.map