@pgsage/core 0.1.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 (70) hide show
  1. package/README.md +126 -0
  2. package/dist/corrector/index.d.ts +72 -0
  3. package/dist/corrector/index.d.ts.map +1 -0
  4. package/dist/corrector/index.js +113 -0
  5. package/dist/corrector/index.js.map +1 -0
  6. package/dist/db/index.d.ts +3 -0
  7. package/dist/db/index.d.ts.map +1 -0
  8. package/dist/db/index.js +2 -0
  9. package/dist/db/index.js.map +1 -0
  10. package/dist/db/pool.d.ts +46 -0
  11. package/dist/db/pool.d.ts.map +1 -0
  12. package/dist/db/pool.js +46 -0
  13. package/dist/db/pool.js.map +1 -0
  14. package/dist/embeddings/index.d.ts +4 -0
  15. package/dist/embeddings/index.d.ts.map +1 -0
  16. package/dist/embeddings/index.js +2 -0
  17. package/dist/embeddings/index.js.map +1 -0
  18. package/dist/embeddings/types.d.ts +39 -0
  19. package/dist/embeddings/types.d.ts.map +1 -0
  20. package/dist/embeddings/types.js +16 -0
  21. package/dist/embeddings/types.js.map +1 -0
  22. package/dist/embeddings/voyage.d.ts +70 -0
  23. package/dist/embeddings/voyage.d.ts.map +1 -0
  24. package/dist/embeddings/voyage.js +163 -0
  25. package/dist/embeddings/voyage.js.map +1 -0
  26. package/dist/estimator/index.d.ts +53 -0
  27. package/dist/estimator/index.d.ts.map +1 -0
  28. package/dist/estimator/index.js +57 -0
  29. package/dist/estimator/index.js.map +1 -0
  30. package/dist/executor/index.d.ts +56 -0
  31. package/dist/executor/index.d.ts.map +1 -0
  32. package/dist/executor/index.js +86 -0
  33. package/dist/executor/index.js.map +1 -0
  34. package/dist/explainer/index.d.ts +39 -0
  35. package/dist/explainer/index.d.ts.map +1 -0
  36. package/dist/explainer/index.js +79 -0
  37. package/dist/explainer/index.js.map +1 -0
  38. package/dist/explainer/prompt.d.ts +37 -0
  39. package/dist/explainer/prompt.d.ts.map +1 -0
  40. package/dist/explainer/prompt.js +102 -0
  41. package/dist/explainer/prompt.js.map +1 -0
  42. package/dist/index.d.ts +19 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +24 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/introspector/index.d.ts +127 -0
  47. package/dist/introspector/index.d.ts.map +1 -0
  48. package/dist/introspector/index.js +460 -0
  49. package/dist/introspector/index.js.map +1 -0
  50. package/dist/orchestrator/index.d.ts +113 -0
  51. package/dist/orchestrator/index.d.ts.map +1 -0
  52. package/dist/orchestrator/index.js +126 -0
  53. package/dist/orchestrator/index.js.map +1 -0
  54. package/dist/planner/index.d.ts +83 -0
  55. package/dist/planner/index.d.ts.map +1 -0
  56. package/dist/planner/index.js +67 -0
  57. package/dist/planner/index.js.map +1 -0
  58. package/dist/planner/prompt.d.ts +37 -0
  59. package/dist/planner/prompt.d.ts.map +1 -0
  60. package/dist/planner/prompt.js +436 -0
  61. package/dist/planner/prompt.js.map +1 -0
  62. package/dist/retriever/index.d.ts +90 -0
  63. package/dist/retriever/index.d.ts.map +1 -0
  64. package/dist/retriever/index.js +164 -0
  65. package/dist/retriever/index.js.map +1 -0
  66. package/dist/validator/index.d.ts +26 -0
  67. package/dist/validator/index.d.ts.map +1 -0
  68. package/dist/validator/index.js +205 -0
  69. package/dist/validator/index.js.map +1 -0
  70. package/package.json +63 -0
@@ -0,0 +1,436 @@
1
+ /**
2
+ * Planner prompt utilities
3
+ *
4
+ * Contains:
5
+ * - SYSTEM_PROMPT: the base system prompt for the SQL planner
6
+ * - FEW_SHOT_EXAMPLES: representative Q→SQL pairs for the Census schema
7
+ * - formatSchemaContext(): converts RetrievedSchemaChunk[] into a compact,
8
+ * LLM-readable schema block
9
+ */
10
+ // ---------------------------------------------------------------------------
11
+ // System prompt
12
+ // ---------------------------------------------------------------------------
13
+ export const SYSTEM_PROMPT = `\
14
+ You are an expert PostgreSQL query writer for a schema-aware SQL agent.
15
+
16
+ ## Your task
17
+ Given a natural-language question and the relevant schema context, produce:
18
+ 1. A single, valid PostgreSQL SELECT query that answers the question.
19
+ 2. A list of assumptions you made about any ambiguous terms or requirements.
20
+
21
+ ## Rules
22
+ - Output ONLY a SELECT statement. Never produce INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, CREATE, GRANT, REVOKE, COPY, or any other non-SELECT statement.
23
+ - Never produce multiple statements separated by semicolons.
24
+ - Never use EXECUTE, PERFORM, or dynamic SQL.
25
+ - Always use schema-qualified table names (e.g. public.geography, not just geography).
26
+ - Prefer explicit column names over SELECT *.
27
+ - Always write the estimate column as \`e.estimate\` — never alias it away from that name (e.g. do NOT write \`e.estimate AS median_income\`). Other columns may use aliases for readability.
28
+ - Add a LIMIT clause if the question asks for top-N results; default to LIMIT 100 if a row cap is not otherwise specified and the query could return unbounded rows.
29
+ - When sorting on a nullable numeric column, always add NULLS LAST (e.g. ORDER BY e.estimate DESC NULLS LAST) so that NULL values do not appear at the top of the result.
30
+ - Use standard PostgreSQL functions only. Do not use vendor extensions.
31
+ - If you cannot produce a valid SELECT query from the given context, return an empty string for sql and explain the issue in assumptions.
32
+
33
+ ## Variable categories
34
+ The variables table uses exactly these 8 category values. Use the correct one — do not guess:
35
+
36
+ income — household income, per-capita income, Gini index, income brackets
37
+ poverty — population below the poverty line, poverty ratios
38
+ housing — housing units, home values, gross rent, occupancy, rent burden
39
+ employment — labor force participation, unemployment counts, industry, occupation
40
+ demographics — total population, age, sex, race, Hispanic/Latino origin
41
+ education — educational attainment (high school, bachelor's, graduate degrees)
42
+ transportation — commute mode (drove alone, transit, worked from home), travel time
43
+ health_insurance — health insurance coverage by age and sex
44
+
45
+ Common mistakes to avoid:
46
+ - Bachelor's degree → category = 'education' (NOT 'demographics')
47
+ - Poverty / below poverty line → category = 'poverty' (NOT 'income')
48
+ - Commute mode, work from home, travel time → category = 'transportation' (NOT 'employment')
49
+ - Health insurance coverage → category = 'health_insurance'
50
+
51
+ ## Estimates are raw counts, not percentages
52
+ Most estimate values are raw COUNTS (number of people, number of housing units), NOT percentages or rates.
53
+ To express a rate or percentage, you must compute it by dividing a count by its relevant total:
54
+
55
+ homeownership rate = 100.0 * owner_occupied.estimate / NULLIF(total_occupied.estimate, 0)
56
+ poverty rate = 100.0 * below_poverty.estimate / NULLIF(total_for_poverty.estimate, 0)
57
+ drove-alone rate = 100.0 * drove_alone.estimate / NULLIF(total_workers.estimate, 0)
58
+
59
+ Never compare a raw count estimate directly to a percentage threshold. For example:
60
+ WRONG: owner_e.estimate < 60 — this compares a unit count to 60, not 60%
61
+ RIGHT: 100.0 * owner_e.estimate / NULLIF(total_e.estimate, 0) < 60
62
+
63
+ When a question asks for a rate or percentage, always join in the denominator total variable
64
+ and compute the ratio explicitly.
65
+
66
+ ## ILIKE precision
67
+ ILIKE label patterns can match multiple variables. When a single specific metric is needed:
68
+ - Use a tighter pattern (e.g. \`v.label ILIKE '%median household income in the past 12 months%'\` rather than \`'%median household income%'\` which also matches age-group breakdowns).
69
+ - Or add \`AND v.concept ILIKE '...'\` to narrow further.
70
+ - Or filter to a known variable_code directly (e.g. \`e.variable_code = 'B19013_001E'\`) when the code is clear from the question or schema context.
71
+ Ambiguous ILIKE matches combined with ORDER BY will surface whichever matched variable has the highest/lowest value — which may not be the intended one.
72
+
73
+ ## Assumption surfacing
74
+ For every ambiguous term, geographic scope decision, join assumption, or data interpretation, add a plain-English sentence to the assumptions list. Examples:
75
+ - "Interpreting 'large counties' as counties with an estimated population > 100,000."
76
+ - "Using the most recent ACS 5-year estimate (2024 vintage)."
77
+ - "Joining estimates to geography on geo_id to resolve county names."
78
+
79
+ ## Display hint
80
+ Choose the best UI visualization layout for the result:
81
+ - "big" — a single scalar answer: one row, one primary numeric value (e.g. a county's median income or total population).
82
+ - "statrow" — 2–4 related metrics about the same subject in one row (e.g. a housing snapshot with home value, rent, and homeownership rate).
83
+ - "bar" — a ranked list of one metric across many geographies (e.g. top 10 counties by income).
84
+ - "grouped" — a two-series comparison across geographies (e.g. rent in 2020 vs 2024 by state).
85
+ - "table" — default fallback for anything that does not fit the above.
86
+
87
+ ## Output format
88
+ Respond with a JSON object matching this exact shape:
89
+ {
90
+ "sql": "<your SELECT query or empty string>",
91
+ "assumptions": ["<assumption 1>", "<assumption 2>"],
92
+ "displayHint": "<big | statrow | bar | grouped | table>"
93
+ }
94
+ Do not include any text outside the JSON object.`;
95
+ export const DEFAULT_FEW_SHOT_EXAMPLES = [
96
+ // ---------------------------------------------------------------------------
97
+ // Example 1: Simple lookup — single variable, named geography
98
+ // Demonstrates: correct category, e.estimate not aliased, NULLS LAST
99
+ // ---------------------------------------------------------------------------
100
+ {
101
+ question: "What is the median household income in Providence County, Rhode Island?",
102
+ schemaContext: `TABLE public.geography
103
+ geo_id text PK — FIPS-based composite id
104
+ state_name text NOT NULL — e.g. "Rhode Island"
105
+ county_name text — e.g. "Providence County"
106
+ level text NOT NULL — "state" or "county"
107
+
108
+ TABLE public.variables
109
+ variable_code text PK — ACS code e.g. "B19013_001E"
110
+ label text NOT NULL — e.g. "Median household income in the past 12 months (in 2024 inflation-adjusted dollars)"
111
+ concept text
112
+ category text — income | poverty | housing | employment | demographics | education | transportation | health_insurance
113
+
114
+ TABLE public.estimates
115
+ geo_id text FK→geography.geo_id
116
+ variable_code text FK→variables.variable_code
117
+ estimate numeric — the estimated value (raw count or median dollar amount)
118
+ margin_error numeric
119
+ PK (geo_id, variable_code)`,
120
+ result: {
121
+ sql: `SELECT
122
+ g.county_name,
123
+ g.state_name,
124
+ v.label,
125
+ e.estimate,
126
+ e.margin_error
127
+ FROM public.estimates e
128
+ JOIN public.geography g ON g.geo_id = e.geo_id
129
+ JOIN public.variables v ON v.variable_code = e.variable_code
130
+ WHERE g.county_name = 'Providence County'
131
+ AND g.state_name = 'Rhode Island'
132
+ AND g.level = 'county'
133
+ AND v.category = 'income'
134
+ AND v.variable_code = 'B19013_001E'
135
+ ORDER BY e.estimate DESC NULLS LAST
136
+ LIMIT 10;`,
137
+ assumptions: [
138
+ "Filtering to Providence County, Rhode Island by matching county_name and state_name exactly.",
139
+ "Using variable_code = 'B19013_001E' (Median household income in the past 12 months) to avoid matching age-group income breakdowns.",
140
+ "Keeping the column named e.estimate (not aliased) as required.",
141
+ ],
142
+ displayHint: "big",
143
+ },
144
+ },
145
+ // ---------------------------------------------------------------------------
146
+ // Example 2: Ranking — education category (common misclassification target)
147
+ // Demonstrates: education category, NULLS LAST, tight variable_code filter
148
+ // ---------------------------------------------------------------------------
149
+ {
150
+ question: "Which 5 states have the highest percentage of adults with a bachelor's degree?",
151
+ schemaContext: `TABLE public.geography
152
+ geo_id text PK
153
+ state_name text NOT NULL
154
+ level text NOT NULL — "state" or "county"
155
+
156
+ TABLE public.variables
157
+ variable_code text PK
158
+ label text NOT NULL
159
+ category text — income | poverty | housing | employment | demographics | education | transportation | health_insurance
160
+
161
+ TABLE public.estimates
162
+ geo_id text FK→geography.geo_id
163
+ variable_code text FK→variables.variable_code
164
+ estimate numeric`,
165
+ result: {
166
+ sql: `SELECT
167
+ g.state_name,
168
+ ROUND(100.0 * bach.estimate / NULLIF(total.estimate, 0), 1) AS pct_bachelors,
169
+ bach.estimate,
170
+ total.estimate AS total_adults_25_plus
171
+ FROM public.geography g
172
+ JOIN public.estimates bach ON bach.geo_id = g.geo_id
173
+ JOIN public.estimates total ON total.geo_id = g.geo_id
174
+ WHERE g.level = 'state'
175
+ AND bach.variable_code = 'B15003_022E'
176
+ AND total.variable_code = 'B15003_001E'
177
+ ORDER BY pct_bachelors DESC NULLS LAST
178
+ LIMIT 5;`,
179
+ assumptions: [
180
+ "Bachelor's degree variables are in the 'education' category, not 'demographics'.",
181
+ "Using B15003_022E (bachelor's degree count) divided by B15003_001E (total population 25+) to compute the percentage.",
182
+ "Filtering to state-level geographies (level = 'state').",
183
+ "Ordering by percentage descending with NULLS LAST; returning top 5.",
184
+ ],
185
+ displayHint: "bar",
186
+ },
187
+ },
188
+ // ---------------------------------------------------------------------------
189
+ // Example 3: Rate computation — homeownership rate (raw count ÷ total)
190
+ // Demonstrates: estimates are counts not percentages, must divide to get rate
191
+ // ---------------------------------------------------------------------------
192
+ {
193
+ question: "Which 5 states have the lowest homeownership rate?",
194
+ schemaContext: `TABLE public.geography
195
+ geo_id text PK
196
+ state_name text NOT NULL
197
+ level text NOT NULL
198
+
199
+ TABLE public.variables
200
+ variable_code text PK
201
+ label text NOT NULL
202
+ category text
203
+
204
+ TABLE public.estimates
205
+ geo_id text FK→geography.geo_id
206
+ variable_code text FK→variables.variable_code
207
+ estimate numeric — raw COUNT of housing units, not a percentage`,
208
+ result: {
209
+ sql: `SELECT
210
+ g.state_name,
211
+ ROUND(100.0 * owner.estimate / NULLIF(total.estimate, 0), 1) AS homeownership_rate,
212
+ owner.estimate AS owner_occupied_units,
213
+ total.estimate AS total_occupied_units
214
+ FROM public.geography g
215
+ JOIN public.estimates owner ON owner.geo_id = g.geo_id
216
+ JOIN public.estimates total ON total.geo_id = g.geo_id
217
+ WHERE g.level = 'state'
218
+ AND owner.variable_code = 'B25003_002E'
219
+ AND total.variable_code = 'B25003_001E'
220
+ ORDER BY homeownership_rate ASC NULLS LAST
221
+ LIMIT 5;`,
222
+ assumptions: [
223
+ "Homeownership estimates (B25003_002E, B25003_001E) are raw unit COUNTS, not percentages.",
224
+ "Computing the rate as 100 * owner_occupied / total_occupied using NULLIF to avoid division by zero.",
225
+ "B25003_002E = owner-occupied units; B25003_001E = total occupied housing units.",
226
+ "Filtering to state-level geographies; ordering ASC NULLS LAST for lowest rates first.",
227
+ ],
228
+ displayHint: "bar",
229
+ },
230
+ },
231
+ // ---------------------------------------------------------------------------
232
+ // Example 4: Transportation category + worked from home
233
+ // Demonstrates: transportation (not employment), exact label wording
234
+ // ---------------------------------------------------------------------------
235
+ {
236
+ question: "Which 10 counties have the most people with commutes of 90 minutes or more?",
237
+ schemaContext: `TABLE public.geography
238
+ geo_id text PK
239
+ county_name text
240
+ state_name text NOT NULL
241
+ level text NOT NULL
242
+
243
+ TABLE public.variables
244
+ variable_code text PK
245
+ label text NOT NULL
246
+ category text — transportation category covers commute mode and travel time
247
+
248
+ TABLE public.estimates
249
+ geo_id text FK→geography.geo_id
250
+ variable_code text FK→variables.variable_code
251
+ estimate numeric`,
252
+ result: {
253
+ sql: `SELECT
254
+ g.county_name,
255
+ g.state_name,
256
+ e.estimate,
257
+ e.margin_error
258
+ FROM public.estimates e
259
+ JOIN public.geography g ON g.geo_id = e.geo_id
260
+ WHERE g.level = 'county'
261
+ AND e.variable_code = 'B08303_013E'
262
+ ORDER BY e.estimate DESC NULLS LAST
263
+ LIMIT 10;`,
264
+ assumptions: [
265
+ "Commute time variables are in the 'transportation' category, not 'employment'.",
266
+ "Using variable_code B08303_013E (Workers with commute 90 or more minutes) directly — the label wording does not contain '90 minutes or more', so ILIKE would miss it.",
267
+ "Filtering to county-level geographies; ordering by estimate DESC NULLS LAST.",
268
+ ],
269
+ displayHint: "bar",
270
+ },
271
+ },
272
+ // ---------------------------------------------------------------------------
273
+ // Example 5: Cross-category with computed poverty rate
274
+ // Demonstrates: poverty category, rate-from-counts, multi-variable join
275
+ // ---------------------------------------------------------------------------
276
+ {
277
+ question: "Show counties with population over 500,000 where more than 15% of residents live below the poverty line.",
278
+ schemaContext: `TABLE public.geography
279
+ geo_id text PK
280
+ county_name text
281
+ state_name text NOT NULL
282
+ level text NOT NULL
283
+
284
+ TABLE public.variables
285
+ variable_code text PK
286
+ label text NOT NULL
287
+ category text
288
+
289
+ TABLE public.estimates
290
+ geo_id text FK→geography.geo_id
291
+ variable_code text FK→variables.variable_code
292
+ estimate numeric — raw count`,
293
+ result: {
294
+ sql: `SELECT
295
+ g.county_name,
296
+ g.state_name,
297
+ pop.estimate AS total_population,
298
+ pov.estimate AS below_poverty_count,
299
+ ROUND(100.0 * pov.estimate / NULLIF(pov_total.estimate, 0), 1) AS poverty_rate_pct
300
+ FROM public.geography g
301
+ JOIN public.estimates pop ON pop.geo_id = g.geo_id
302
+ JOIN public.estimates pov ON pov.geo_id = g.geo_id
303
+ JOIN public.estimates pov_total ON pov_total.geo_id = g.geo_id
304
+ WHERE g.level = 'county'
305
+ AND pop.variable_code = 'B01003_001E'
306
+ AND pov.variable_code = 'B17001_002E'
307
+ AND pov_total.variable_code = 'B17001_001E'
308
+ AND pop.estimate > 500000
309
+ AND 100.0 * pov.estimate / NULLIF(pov_total.estimate, 0) > 15
310
+ ORDER BY poverty_rate_pct DESC NULLS LAST
311
+ LIMIT 100;`,
312
+ assumptions: [
313
+ "Poverty variables are in the 'poverty' category, not 'income'.",
314
+ "B17001_002E = population with income below poverty level (count); B17001_001E = total population for poverty determination (denominator).",
315
+ "Poverty rate is computed as 100 * below_poverty / total_for_poverty — estimates are raw counts, not percentages.",
316
+ "Population filter uses B01003_001E (total population); poverty rate threshold is > 15 percentage points.",
317
+ "Filtering to county-level geographies; ordering by poverty rate descending.",
318
+ ],
319
+ displayHint: "table",
320
+ },
321
+ },
322
+ ];
323
+ // ---------------------------------------------------------------------------
324
+ // Schema context formatter
325
+ // ---------------------------------------------------------------------------
326
+ /**
327
+ * Converts an array of RetrievedSchemaChunk into a compact, LLM-readable
328
+ * schema context block. Table-level chunks are listed first, followed by
329
+ * column chunks grouped under their parent table.
330
+ */
331
+ export function formatSchemaContext(chunks) {
332
+ if (chunks.length === 0) {
333
+ return "(no schema context retrieved)";
334
+ }
335
+ const tableChunks = chunks.filter((c) => c.objectType === "table");
336
+ const columnChunks = chunks.filter((c) => c.objectType === "column");
337
+ const lines = [];
338
+ // Group column chunks by their parent table reference
339
+ const columnsByTable = new Map();
340
+ for (const col of columnChunks) {
341
+ // objectRef for columns: "{schema}.{table}.{column}"
342
+ const parts = col.objectRef.split(".");
343
+ const tableRef = parts.slice(0, 2).join(".");
344
+ const existing = columnsByTable.get(tableRef) ?? [];
345
+ existing.push(col);
346
+ columnsByTable.set(tableRef, existing);
347
+ }
348
+ // Build output: tables with their columns interleaved
349
+ const renderedTables = new Set();
350
+ for (const tableChunk of tableChunks) {
351
+ lines.push(tableChunk.content);
352
+ renderedTables.add(tableChunk.objectRef);
353
+ const cols = columnsByTable.get(tableChunk.objectRef) ?? [];
354
+ for (const col of cols) {
355
+ // Indent column details under the table
356
+ const indented = col.content
357
+ .split("\n")
358
+ .map((l) => ` ${l}`)
359
+ .join("\n");
360
+ lines.push(indented);
361
+ }
362
+ lines.push(""); // blank line between tables
363
+ }
364
+ // Any column chunks whose parent table wasn't retrieved as a table chunk
365
+ for (const [tableRef, cols] of columnsByTable.entries()) {
366
+ if (renderedTables.has(tableRef))
367
+ continue;
368
+ lines.push(`TABLE ${tableRef} (partial — columns only):`);
369
+ for (const col of cols) {
370
+ const indented = col.content
371
+ .split("\n")
372
+ .map((l) => ` ${l}`)
373
+ .join("\n");
374
+ lines.push(indented);
375
+ }
376
+ lines.push("");
377
+ }
378
+ // If only column chunks exist and no table chunks, handle gracefully above
379
+ if (tableChunks.length === 0 && columnChunks.length === 0) {
380
+ return chunks.map((c) => c.content).join("\n\n");
381
+ }
382
+ return lines.join("\n").trimEnd();
383
+ }
384
+ // ---------------------------------------------------------------------------
385
+ // User message builder
386
+ // ---------------------------------------------------------------------------
387
+ /**
388
+ * Builds the full user message to send to the planning agent.
389
+ * Includes the schema context, optional few-shot examples, optional prior
390
+ * failed attempts (errorContext), and the question.
391
+ */
392
+ export function buildUserMessage(question, context, examples, errorContext) {
393
+ const parts = [];
394
+ // Schema context section
395
+ parts.push("## Relevant Schema Context");
396
+ parts.push(formatSchemaContext(context));
397
+ parts.push("");
398
+ // Few-shot examples section
399
+ if (examples.length > 0) {
400
+ parts.push("## Examples");
401
+ for (const ex of examples) {
402
+ parts.push(`### Question: ${ex.question}`);
403
+ parts.push("Schema context:");
404
+ parts.push("```");
405
+ parts.push(ex.schemaContext);
406
+ parts.push("```");
407
+ parts.push("Response:");
408
+ parts.push("```json");
409
+ parts.push(JSON.stringify(ex.result, null, 2));
410
+ parts.push("```");
411
+ parts.push("");
412
+ }
413
+ }
414
+ // Prior failed attempts (correction context)
415
+ if (errorContext && errorContext.length > 0) {
416
+ parts.push("## Prior Attempts (Failed)");
417
+ parts.push("The following SQL queries were generated previously but failed. " +
418
+ "Study the errors and produce a corrected query that avoids these mistakes.");
419
+ parts.push("");
420
+ for (let i = 0; i < errorContext.length; i++) {
421
+ const attempt = errorContext[i];
422
+ parts.push(`### Attempt ${i + 1}`);
423
+ parts.push("SQL:");
424
+ parts.push("```sql");
425
+ parts.push(attempt.sql);
426
+ parts.push("```");
427
+ parts.push(`Error: ${attempt.error}`);
428
+ parts.push("");
429
+ }
430
+ }
431
+ // The actual question
432
+ parts.push("## Question");
433
+ parts.push(question);
434
+ return parts.join("\n");
435
+ }
436
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/planner/prompt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Retriever
3
+ *
4
+ * Two responsibilities:
5
+ *
6
+ * 1. indexSchema() — converts an IntrospectedSchema into text descriptions,
7
+ * embeds them via an EmbeddingProvider, and upserts the results into the
8
+ * schema_embeddings table. Safe to run repeatedly (idempotent via ON
9
+ * CONFLICT ... DO UPDATE).
10
+ *
11
+ * 2. retrieve() — embeds a natural-language question and performs a pgvector
12
+ * cosine similarity search against schema_embeddings, returning the
13
+ * top-K most relevant schema chunks.
14
+ */
15
+ import type { Pool } from "pg";
16
+ import type { EmbeddingProvider } from "../embeddings/types.js";
17
+ import type { IntrospectedSchema, IntrospectedTable } from "../introspector/index.js";
18
+ export type SchemaObjectType = "table" | "column";
19
+ export interface RetrievedSchemaChunk {
20
+ /** Row id from schema_embeddings. */
21
+ id: string;
22
+ /** The type of schema object this chunk describes. */
23
+ objectType: SchemaObjectType;
24
+ /**
25
+ * Stable reference for the object.
26
+ * Tables: "{schema}.{table}"
27
+ * Columns: "{schema}.{table}.{column}"
28
+ */
29
+ objectRef: string;
30
+ /** The text that was embedded (useful for debugging / prompt building). */
31
+ content: string;
32
+ /** Cosine similarity score in [0, 1]. Higher is more relevant. */
33
+ score: number;
34
+ }
35
+ export interface IndexSchemaResult {
36
+ /** Total rows upserted into schema_embeddings. */
37
+ rowsUpserted: number;
38
+ /** Total tokens consumed by embedding calls. */
39
+ totalTokens: number;
40
+ }
41
+ export interface IndexSchemaOptions {
42
+ /**
43
+ * If true, re-embed and overwrite entries even if they already exist.
44
+ * Default: false (upsert only updates the embedding + content).
45
+ */
46
+ force?: boolean;
47
+ }
48
+ export interface RetrieveOptions {
49
+ /**
50
+ * Maximum number of results to return.
51
+ * Default: 10.
52
+ */
53
+ topK?: number;
54
+ /**
55
+ * Minimum cosine similarity score (0–1).
56
+ * Results below this threshold are filtered out.
57
+ * Default: 0 (no threshold).
58
+ */
59
+ minScore?: number;
60
+ /**
61
+ * Limit results to specific object types.
62
+ * Default: all types.
63
+ */
64
+ objectTypes?: SchemaObjectType[];
65
+ }
66
+ /**
67
+ * Generate a human-readable description of a table suitable for embedding.
68
+ * Includes: schema/table name, type, row estimate, comment, and a column
69
+ * summary with types, nullability, PK/FK annotations.
70
+ */
71
+ export declare function describeTable(table: IntrospectedTable): string;
72
+ /**
73
+ * Generate a human-readable description of a single column suitable for
74
+ * embedding. Includes table context, type info, PK/FK annotations, and
75
+ * comment.
76
+ */
77
+ export declare function describeColumn(table: IntrospectedTable, columnIndex: number): string;
78
+ /**
79
+ * Embed an IntrospectedSchema and upsert the results into schema_embeddings.
80
+ *
81
+ * Generates one chunk per table and one chunk per column, embeds them all
82
+ * in batches via the provider, then bulk-upserts into the DB.
83
+ */
84
+ export declare function indexSchema(pool: Pool, schema: IntrospectedSchema, provider: EmbeddingProvider, _options?: IndexSchemaOptions): Promise<IndexSchemaResult>;
85
+ /**
86
+ * Embed a natural-language question and return the most relevant schema
87
+ * chunks from schema_embeddings, ranked by cosine similarity.
88
+ */
89
+ export declare function retrieve(pool: Pool, question: string, provider: EmbeddingProvider, options?: RetrieveOptions): Promise<RetrievedSchemaChunk[]>;
90
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/retriever/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAMtF,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAElD,MAAM,WAAW,oBAAoB;IACnC,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,sDAAsD;IACtD,UAAU,EAAE,gBAAgB,CAAC;IAC7B;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;CACrB;AAMD,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAClC;AAMD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAsB9D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,iBAAiB,EACxB,WAAW,EAAE,MAAM,GAClB,MAAM,CAoBR;AAMD;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,iBAAiB,EAC3B,QAAQ,GAAE,kBAAuB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CA6D5B;AAcD;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAqCjC"}