@deepagents/text2sql 0.3.1 → 0.6.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 (143) hide show
  1. package/README.md +167 -0
  2. package/dist/finetune/convert-to-gguf.d.ts +18 -0
  3. package/dist/finetune/convert-to-gguf.d.ts.map +1 -0
  4. package/dist/finetune/run-finetune.d.ts +23 -0
  5. package/dist/finetune/run-finetune.d.ts.map +1 -0
  6. package/dist/finetune/run-mlx.d.ts +22 -0
  7. package/dist/finetune/run-mlx.d.ts.map +1 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1793 -279
  11. package/dist/index.js.map +4 -4
  12. package/dist/lib/adapters/adapter.d.ts +3 -3
  13. package/dist/lib/adapters/adapter.d.ts.map +1 -1
  14. package/dist/lib/adapters/{grounding.ticket.d.ts → groundings/abstract.grounding.d.ts} +2 -2
  15. package/dist/lib/adapters/groundings/abstract.grounding.d.ts.map +1 -0
  16. package/dist/lib/adapters/groundings/column-stats.grounding.d.ts +1 -1
  17. package/dist/lib/adapters/groundings/column-stats.grounding.d.ts.map +1 -1
  18. package/dist/lib/adapters/groundings/column-values.grounding.d.ts +76 -0
  19. package/dist/lib/adapters/groundings/column-values.grounding.d.ts.map +1 -0
  20. package/dist/lib/adapters/groundings/constraint.grounding.d.ts +1 -1
  21. package/dist/lib/adapters/groundings/constraint.grounding.d.ts.map +1 -1
  22. package/dist/lib/adapters/groundings/context.d.ts +1 -1
  23. package/dist/lib/adapters/groundings/context.d.ts.map +1 -1
  24. package/dist/lib/adapters/groundings/{grounding.d.ts → index.d.ts} +8 -5
  25. package/dist/lib/adapters/groundings/index.d.ts.map +1 -0
  26. package/dist/lib/adapters/groundings/{grounding.js → index.js} +411 -206
  27. package/dist/lib/adapters/groundings/index.js.map +7 -0
  28. package/dist/lib/adapters/groundings/indexes.grounding.d.ts +1 -1
  29. package/dist/lib/adapters/groundings/indexes.grounding.d.ts.map +1 -1
  30. package/dist/lib/adapters/groundings/info.grounding.d.ts +1 -1
  31. package/dist/lib/adapters/groundings/info.grounding.d.ts.map +1 -1
  32. package/dist/lib/adapters/groundings/report.grounding.d.ts +1 -1
  33. package/dist/lib/adapters/groundings/report.grounding.d.ts.map +1 -1
  34. package/dist/lib/adapters/groundings/row-count.grounding.d.ts +1 -1
  35. package/dist/lib/adapters/groundings/row-count.grounding.d.ts.map +1 -1
  36. package/dist/lib/adapters/groundings/table.grounding.d.ts +1 -1
  37. package/dist/lib/adapters/groundings/table.grounding.d.ts.map +1 -1
  38. package/dist/lib/adapters/groundings/view.grounding.d.ts +1 -1
  39. package/dist/lib/adapters/groundings/view.grounding.d.ts.map +1 -1
  40. package/dist/lib/adapters/postgres/column-stats.postgres.grounding.d.ts.map +1 -1
  41. package/dist/lib/adapters/postgres/column-values.postgres.grounding.d.ts +17 -0
  42. package/dist/lib/adapters/postgres/column-values.postgres.grounding.d.ts.map +1 -0
  43. package/dist/lib/adapters/postgres/index.d.ts +4 -4
  44. package/dist/lib/adapters/postgres/index.d.ts.map +1 -1
  45. package/dist/lib/adapters/postgres/index.js +233 -33
  46. package/dist/lib/adapters/postgres/index.js.map +4 -4
  47. package/dist/lib/adapters/sqlite/column-values.sqlite.grounding.d.ts +17 -0
  48. package/dist/lib/adapters/sqlite/column-values.sqlite.grounding.d.ts.map +1 -0
  49. package/dist/lib/adapters/sqlite/constraint.sqlite.grounding.d.ts.map +1 -1
  50. package/dist/lib/adapters/sqlite/index.d.ts +4 -4
  51. package/dist/lib/adapters/sqlite/index.d.ts.map +1 -1
  52. package/dist/lib/adapters/sqlite/index.js +214 -46
  53. package/dist/lib/adapters/sqlite/index.js.map +4 -4
  54. package/dist/lib/adapters/sqlserver/column-values.sqlserver.grounding.d.ts +17 -0
  55. package/dist/lib/adapters/sqlserver/column-values.sqlserver.grounding.d.ts.map +1 -0
  56. package/dist/lib/adapters/sqlserver/index.d.ts +4 -4
  57. package/dist/lib/adapters/sqlserver/index.d.ts.map +1 -1
  58. package/dist/lib/adapters/sqlserver/index.js +179 -32
  59. package/dist/lib/adapters/sqlserver/index.js.map +4 -4
  60. package/dist/lib/agents/chat1.agent.d.ts +50 -0
  61. package/dist/lib/agents/chat1.agent.d.ts.map +1 -0
  62. package/dist/lib/agents/chat2.agent.d.ts +68 -0
  63. package/dist/lib/agents/chat2.agent.d.ts.map +1 -0
  64. package/dist/lib/agents/chat3.agent.d.ts +80 -0
  65. package/dist/lib/agents/chat3.agent.d.ts.map +1 -0
  66. package/dist/lib/agents/chat4.agent.d.ts +88 -0
  67. package/dist/lib/agents/chat4.agent.d.ts.map +1 -0
  68. package/dist/lib/agents/question.agent.d.ts +23 -0
  69. package/dist/lib/agents/question.agent.d.ts.map +1 -0
  70. package/dist/lib/agents/sql.agent.d.ts +62 -0
  71. package/dist/lib/agents/sql.agent.d.ts.map +1 -0
  72. package/dist/lib/agents/teachables.agent.d.ts +8 -9
  73. package/dist/lib/agents/teachables.agent.d.ts.map +1 -1
  74. package/dist/lib/agents/text2sql.agent.d.ts +0 -1
  75. package/dist/lib/agents/text2sql.agent.d.ts.map +1 -1
  76. package/dist/lib/checkpoint.d.ts +99 -0
  77. package/dist/lib/checkpoint.d.ts.map +1 -0
  78. package/dist/lib/instructions.js +50 -21
  79. package/dist/lib/instructions.js.map +2 -2
  80. package/dist/lib/sql.d.ts +83 -3
  81. package/dist/lib/sql.d.ts.map +1 -1
  82. package/dist/lib/syntheize.d.ts +2 -0
  83. package/dist/lib/syntheize.d.ts.map +1 -0
  84. package/dist/lib/synthesis/decorators/deduplicated-producer.d.ts +26 -0
  85. package/dist/lib/synthesis/decorators/deduplicated-producer.d.ts.map +1 -0
  86. package/dist/lib/synthesis/decorators/filtered-producer.d.ts +26 -0
  87. package/dist/lib/synthesis/decorators/filtered-producer.d.ts.map +1 -0
  88. package/dist/lib/synthesis/decorators/index.d.ts +7 -0
  89. package/dist/lib/synthesis/decorators/index.d.ts.map +1 -0
  90. package/dist/lib/synthesis/decorators/validated-producer.d.ts +33 -0
  91. package/dist/lib/synthesis/decorators/validated-producer.d.ts.map +1 -0
  92. package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts +76 -0
  93. package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts.map +1 -0
  94. package/dist/lib/synthesis/extractors/full-context-extractor.d.ts +25 -0
  95. package/dist/lib/synthesis/extractors/full-context-extractor.d.ts.map +1 -0
  96. package/dist/lib/synthesis/extractors/index.d.ts +8 -0
  97. package/dist/lib/synthesis/extractors/index.d.ts.map +1 -0
  98. package/dist/lib/synthesis/extractors/last-query-extractor.d.ts +30 -0
  99. package/dist/lib/synthesis/extractors/last-query-extractor.d.ts.map +1 -0
  100. package/dist/lib/synthesis/extractors/message-extractor.d.ts +27 -0
  101. package/dist/lib/synthesis/extractors/message-extractor.d.ts.map +1 -0
  102. package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts +48 -0
  103. package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts.map +1 -0
  104. package/dist/lib/synthesis/extractors/sql-extractor.d.ts +27 -0
  105. package/dist/lib/synthesis/extractors/sql-extractor.d.ts.map +1 -0
  106. package/dist/lib/synthesis/extractors/windowed-context-extractor.d.ts +30 -0
  107. package/dist/lib/synthesis/extractors/windowed-context-extractor.d.ts.map +1 -0
  108. package/dist/lib/synthesis/index.d.ts +6 -0
  109. package/dist/lib/synthesis/index.d.ts.map +1 -0
  110. package/dist/lib/synthesis/index.js +2069 -0
  111. package/dist/lib/synthesis/index.js.map +7 -0
  112. package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts +34 -0
  113. package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts.map +1 -0
  114. package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts +41 -0
  115. package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts.map +1 -0
  116. package/dist/lib/synthesis/synthesizers/index.d.ts +7 -0
  117. package/dist/lib/synthesis/synthesizers/index.d.ts.map +1 -0
  118. package/dist/lib/synthesis/synthesizers/persona-generator.d.ts +34 -0
  119. package/dist/lib/synthesis/synthesizers/persona-generator.d.ts.map +1 -0
  120. package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts +39 -0
  121. package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts.map +1 -0
  122. package/dist/lib/synthesis/synthesizers/styles.d.ts +8 -0
  123. package/dist/lib/synthesis/synthesizers/styles.d.ts.map +1 -0
  124. package/dist/lib/synthesis/synthesizers/teachings-generator.d.ts +32 -0
  125. package/dist/lib/synthesis/synthesizers/teachings-generator.d.ts.map +1 -0
  126. package/dist/lib/synthesis/types.d.ts +26 -0
  127. package/dist/lib/synthesis/types.d.ts.map +1 -0
  128. package/dist/lib/teach/teachables.d.ts +18 -3
  129. package/dist/lib/teach/teachables.d.ts.map +1 -1
  130. package/dist/lib/teach/teachings.d.ts +9 -2
  131. package/dist/lib/teach/teachings.d.ts.map +1 -1
  132. package/package.json +32 -15
  133. package/dist/lib/adapters/grounding.ticket.d.ts.map +0 -1
  134. package/dist/lib/adapters/groundings/grounding.d.ts.map +0 -1
  135. package/dist/lib/adapters/groundings/grounding.js.map +0 -7
  136. package/dist/lib/adapters/groundings/low-cardinality.grounding.d.ts +0 -35
  137. package/dist/lib/adapters/groundings/low-cardinality.grounding.d.ts.map +0 -1
  138. package/dist/lib/adapters/postgres/low-cardinality.postgres.grounding.d.ts +0 -14
  139. package/dist/lib/adapters/postgres/low-cardinality.postgres.grounding.d.ts.map +0 -1
  140. package/dist/lib/adapters/sqlite/low-cardinality.sqlite.grounding.d.ts +0 -14
  141. package/dist/lib/adapters/sqlite/low-cardinality.sqlite.grounding.d.ts.map +0 -1
  142. package/dist/lib/adapters/sqlserver/low-cardinality.sqlserver.grounding.d.ts +0 -14
  143. package/dist/lib/adapters/sqlserver/low-cardinality.sqlserver.grounding.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,3 +1,154 @@
1
+ // packages/text2sql/src/lib/adapters/groundings/context.ts
2
+ function createGroundingContext() {
3
+ return {
4
+ tables: [],
5
+ views: [],
6
+ relationships: [],
7
+ info: void 0
8
+ };
9
+ }
10
+
11
+ // packages/text2sql/src/lib/adapters/adapter.ts
12
+ var Adapter = class {
13
+ async introspect(ctx = createGroundingContext()) {
14
+ const lines = [];
15
+ for (const fn of this.grounding) {
16
+ const grounding = fn(this);
17
+ lines.push({
18
+ tag: grounding.tag,
19
+ fn: await grounding.execute(ctx)
20
+ });
21
+ }
22
+ return lines.map(({ fn, tag }) => {
23
+ const description = fn();
24
+ if (description === null) {
25
+ return "";
26
+ }
27
+ return `<${tag}>
28
+ ${description}
29
+ </${tag}>`;
30
+ }).join("\n");
31
+ }
32
+ /**
33
+ * Convert unknown database value to number.
34
+ * Handles number, bigint, and string types.
35
+ */
36
+ toNumber(value) {
37
+ if (typeof value === "number" && Number.isFinite(value)) {
38
+ return value;
39
+ }
40
+ if (typeof value === "bigint") {
41
+ return Number(value);
42
+ }
43
+ if (typeof value === "string" && value.trim() !== "") {
44
+ const parsed = Number(value);
45
+ return Number.isFinite(parsed) ? parsed : void 0;
46
+ }
47
+ return void 0;
48
+ }
49
+ /**
50
+ * Parse a potentially qualified table name into schema and table parts.
51
+ */
52
+ parseTableName(name) {
53
+ if (name.includes(".")) {
54
+ const [schema, ...rest] = name.split(".");
55
+ return { schema, table: rest.join(".") };
56
+ }
57
+ return { schema: this.defaultSchema ?? "", table: name };
58
+ }
59
+ /**
60
+ * Escape a string value for use in SQL string literals (single quotes).
61
+ * Used in WHERE clauses like: WHERE name = '${escapeString(value)}'
62
+ */
63
+ escapeString(value) {
64
+ return value.replace(/'/g, "''");
65
+ }
66
+ /**
67
+ * Build a SQL filter clause to include/exclude schemas.
68
+ * @param columnName - The schema column name (e.g., 'TABLE_SCHEMA')
69
+ * @param allowedSchemas - If provided, filter to these schemas only
70
+ */
71
+ buildSchemaFilter(columnName, allowedSchemas) {
72
+ if (allowedSchemas && allowedSchemas.length > 0) {
73
+ const values = allowedSchemas.map((s) => `'${this.escapeString(s)}'`).join(", ");
74
+ return `AND ${columnName} IN (${values})`;
75
+ }
76
+ if (this.systemSchemas.length > 0) {
77
+ const values = this.systemSchemas.map((s) => `'${this.escapeString(s)}'`).join(", ");
78
+ return `AND ${columnName} NOT IN (${values})`;
79
+ }
80
+ return "";
81
+ }
82
+ };
83
+ function filterTablesByName(tables, filter) {
84
+ if (!filter) return tables;
85
+ return tables.filter((table) => matchesFilter(table.name, filter));
86
+ }
87
+ function filterRelationshipsByTables(relationships, tableNames) {
88
+ if (tableNames === void 0) {
89
+ return relationships;
90
+ }
91
+ if (tableNames.size === 0) {
92
+ return [];
93
+ }
94
+ return relationships.filter(
95
+ (it) => tableNames.has(it.table) || tableNames.has(it.referenced_table)
96
+ );
97
+ }
98
+ function applyTablesFilter(tables, relationships, filter) {
99
+ if (!filter) {
100
+ return { tables, relationships };
101
+ }
102
+ const allowedNames = new Set(
103
+ getTablesWithRelated(tables, relationships, filter)
104
+ );
105
+ return {
106
+ tables: tables.filter((table) => allowedNames.has(table.name)),
107
+ relationships: filterRelationshipsByTables(relationships, allowedNames)
108
+ };
109
+ }
110
+ function matchesFilter(tableName, filter) {
111
+ if (Array.isArray(filter)) {
112
+ return filter.includes(tableName);
113
+ }
114
+ return filter.test(tableName);
115
+ }
116
+ function getTablesWithRelated(allTables, relationships, filter) {
117
+ const matchedTables = filterTablesByName(allTables, filter).map(
118
+ (it) => it.name
119
+ );
120
+ if (matchedTables.length === 0) {
121
+ return [];
122
+ }
123
+ const adjacency = /* @__PURE__ */ new Map();
124
+ for (const rel of relationships) {
125
+ if (!adjacency.has(rel.table)) {
126
+ adjacency.set(rel.table, /* @__PURE__ */ new Set());
127
+ }
128
+ if (!adjacency.has(rel.referenced_table)) {
129
+ adjacency.set(rel.referenced_table, /* @__PURE__ */ new Set());
130
+ }
131
+ adjacency.get(rel.table).add(rel.referenced_table);
132
+ adjacency.get(rel.referenced_table).add(rel.table);
133
+ }
134
+ const result = new Set(matchedTables);
135
+ const queue = [...matchedTables];
136
+ while (queue.length > 0) {
137
+ const current = queue.shift();
138
+ const neighbors = adjacency.get(current);
139
+ if (!neighbors) {
140
+ continue;
141
+ }
142
+ for (const neighbor of neighbors) {
143
+ if (!result.has(neighbor)) {
144
+ result.add(neighbor);
145
+ queue.push(neighbor);
146
+ }
147
+ }
148
+ }
149
+ return Array.from(result);
150
+ }
151
+
1
152
  // packages/text2sql/src/lib/agents/suggestions.agents.ts
2
153
  import { groq } from "@ai-sdk/groq";
3
154
  import dedent from "dedent";
@@ -54,10 +205,7 @@ var suggestionsAgent = agent({
54
205
  import { groq as groq2 } from "@ai-sdk/groq";
55
206
  import { tool } from "ai";
56
207
  import z2 from "zod";
57
- import {
58
- agent as agent2,
59
- toState
60
- } from "@deepagents/agent";
208
+ import { agent as agent2, toState } from "@deepagents/agent";
61
209
  import { scratchpad_tool } from "@deepagents/toolbox";
62
210
 
63
211
  // packages/text2sql/src/lib/teach/xml.ts
@@ -106,20 +254,23 @@ function escapeXml(value) {
106
254
  function term(name, definition) {
107
255
  return {
108
256
  type: "term",
109
- format: () => wrapBlock("term", [leaf("name", name), leaf("definition", definition)])
257
+ encode: () => ({ type: "term", name, definition }),
258
+ decode: () => wrapBlock("term", [leaf("name", name), leaf("definition", definition)])
110
259
  };
111
260
  }
112
261
  function hint(text) {
113
262
  return {
114
263
  type: "hint",
115
- format: () => leaf("hint", text)
264
+ encode: () => ({ type: "hint", text }),
265
+ decode: () => leaf("hint", text)
116
266
  };
117
267
  }
118
268
  function guardrail(input) {
119
269
  const { rule, reason, action } = input;
120
270
  return {
121
271
  type: "guardrail",
122
- format: () => wrapBlock("guardrail", [
272
+ encode: () => ({ type: "guardrail", rule, reason, action }),
273
+ decode: () => wrapBlock("guardrail", [
123
274
  leaf("rule", rule),
124
275
  reason ? leaf("reason", reason) : "",
125
276
  action ? leaf("action", action) : ""
@@ -130,7 +281,8 @@ function explain(input) {
130
281
  const { concept, explanation, therefore } = input;
131
282
  return {
132
283
  type: "explain",
133
- format: () => wrapBlock("explanation", [
284
+ encode: () => ({ type: "explain", concept, explanation, therefore }),
285
+ decode: () => wrapBlock("explanation", [
134
286
  leaf("concept", concept),
135
287
  leaf("details", explanation),
136
288
  therefore ? leaf("therefore", therefore) : ""
@@ -141,7 +293,8 @@ function example(input) {
141
293
  const { question, answer, note } = input;
142
294
  return {
143
295
  type: "example",
144
- format: () => wrapBlock("example", [
296
+ encode: () => ({ type: "example", question, answer, note }),
297
+ decode: () => wrapBlock("example", [
145
298
  leaf("question", question),
146
299
  leaf("answer", answer),
147
300
  note ? leaf("note", note) : ""
@@ -152,7 +305,8 @@ function clarification(input) {
152
305
  const { when, ask, reason } = input;
153
306
  return {
154
307
  type: "clarification",
155
- format: () => wrapBlock("clarification", [
308
+ encode: () => ({ type: "clarification", when, ask, reason }),
309
+ decode: () => wrapBlock("clarification", [
156
310
  leaf("when", when),
157
311
  leaf("ask", ask),
158
312
  leaf("reason", reason)
@@ -163,7 +317,8 @@ function workflow(input) {
163
317
  const { task, steps, triggers, notes } = input;
164
318
  return {
165
319
  type: "workflow",
166
- format: () => wrapBlock("workflow", [
320
+ encode: () => ({ type: "workflow", task, steps, triggers, notes }),
321
+ decode: () => wrapBlock("workflow", [
167
322
  leaf("task", task),
168
323
  triggers?.length ? list("triggers", triggers, "trigger") : "",
169
324
  list("steps", steps, "step"),
@@ -175,7 +330,8 @@ function quirk(input) {
175
330
  const { issue, workaround } = input;
176
331
  return {
177
332
  type: "quirk",
178
- format: () => wrapBlock("quirk", [
333
+ encode: () => ({ type: "quirk", issue, workaround }),
334
+ decode: () => wrapBlock("quirk", [
179
335
  leaf("issue", issue),
180
336
  leaf("workaround", workaround)
181
337
  ])
@@ -185,7 +341,8 @@ function styleGuide(input) {
185
341
  const { prefer, never, always } = input;
186
342
  return {
187
343
  type: "styleGuide",
188
- format: () => wrapBlock("style_guide", [
344
+ encode: () => ({ type: "styleGuide", prefer, never, always }),
345
+ decode: () => wrapBlock("style_guide", [
189
346
  leaf("prefer", prefer),
190
347
  always ? leaf("always", always) : "",
191
348
  never ? leaf("never", never) : ""
@@ -196,7 +353,15 @@ function analogy(input) {
196
353
  const { concept, relationship, insight, therefore, pitfall } = input;
197
354
  return {
198
355
  type: "analogy",
199
- format: () => wrapBlock("analogy", [
356
+ encode: () => ({
357
+ type: "analogy",
358
+ concept,
359
+ relationship,
360
+ insight,
361
+ therefore,
362
+ pitfall
363
+ }),
364
+ decode: () => wrapBlock("analogy", [
200
365
  list("concepts", concept, "concept"),
201
366
  leaf("relationship", relationship),
202
367
  insight ? leaf("insight", insight) : "",
@@ -208,7 +373,8 @@ function analogy(input) {
208
373
  function glossary(entries) {
209
374
  return {
210
375
  type: "glossary",
211
- format: () => wrapBlock(
376
+ encode: () => ({ type: "glossary", entries }),
377
+ decode: () => wrapBlock(
212
378
  "glossary",
213
379
  Object.entries(entries).map(
214
380
  ([term2, sql]) => wrapBlock("entry", [leaf("term", term2), leaf("sql", sql)])
@@ -220,7 +386,8 @@ function identity(input) {
220
386
  const { name, role } = input;
221
387
  return {
222
388
  type: "identity",
223
- format: () => wrapBlock("identity", [
389
+ encode: () => ({ type: "identity", name, role }),
390
+ decode: () => wrapBlock("identity", [
224
391
  name ? leaf("name", name) : "",
225
392
  role ? leaf("role", role) : ""
226
393
  ])
@@ -230,35 +397,40 @@ function persona(input) {
230
397
  const { name, role, tone } = input;
231
398
  return {
232
399
  type: "persona",
233
- format: () => wrapBlock("persona", [
400
+ encode: () => ({ type: "persona", name, role, tone: tone ?? "" }),
401
+ decode: () => wrapBlock("persona", [
234
402
  leaf("name", name),
235
403
  leaf("role", role),
236
- leaf("tone", tone)
404
+ tone ? leaf("tone", tone) : ""
237
405
  ])
238
406
  };
239
407
  }
240
408
  function alias(termName, meaning) {
241
409
  return {
242
410
  type: "alias",
243
- format: () => wrapBlock("alias", [leaf("term", termName), leaf("meaning", meaning)])
411
+ encode: () => ({ type: "alias", term: termName, meaning }),
412
+ decode: () => wrapBlock("alias", [leaf("term", termName), leaf("meaning", meaning)])
244
413
  };
245
414
  }
246
415
  function preference(aspect, value) {
247
416
  return {
248
417
  type: "preference",
249
- format: () => wrapBlock("preference", [leaf("aspect", aspect), leaf("value", value)])
418
+ encode: () => ({ type: "preference", aspect, value }),
419
+ decode: () => wrapBlock("preference", [leaf("aspect", aspect), leaf("value", value)])
250
420
  };
251
421
  }
252
422
  function context(description) {
253
423
  return {
254
424
  type: "context",
255
- format: () => leaf("context", description)
425
+ encode: () => ({ type: "context", description }),
426
+ decode: () => leaf("context", description)
256
427
  };
257
428
  }
258
429
  function correction(subject, clarification2) {
259
430
  return {
260
431
  type: "correction",
261
- format: () => wrapBlock("correction", [
432
+ encode: () => ({ type: "correction", subject, clarification: clarification2 }),
433
+ decode: () => wrapBlock("correction", [
262
434
  leaf("subject", subject),
263
435
  leaf("clarification", clarification2)
264
436
  ])
@@ -267,7 +439,8 @@ function correction(subject, clarification2) {
267
439
  function teachable(tag, ...teachables) {
268
440
  return {
269
441
  type: "user_profile",
270
- format: () => toInstructions(tag, ...teachables)
442
+ encode: () => teachables[0]?.encode() ?? { type: "context", description: "" },
443
+ decode: () => toInstructions(tag, ...teachables)
271
444
  };
272
445
  }
273
446
  function toInstructions(tag, ...teachables) {
@@ -286,7 +459,7 @@ function toInstructions(tag, ...teachables) {
286
459
  if (!items?.length) {
287
460
  return "";
288
461
  }
289
- const renderedItems = items.map((item) => item.format().trim()).filter(Boolean).map((item) => indentBlock(item, 2)).join("\n");
462
+ const renderedItems = items.map((item) => item.decode().trim()).filter(Boolean).map((item) => indentBlock(item, 2)).join("\n");
290
463
  if (!renderedItems.length) {
291
464
  return "";
292
465
  }
@@ -298,7 +471,7 @@ ${renderedItems}
298
471
  if (definedTypes.has(type)) {
299
472
  continue;
300
473
  }
301
- const renderedItems = items.map((item) => item.format().trim()).filter(Boolean).map((item) => indentBlock(item, 2)).join("\n");
474
+ const renderedItems = items.map((item) => item.decode().trim()).filter(Boolean).map((item) => indentBlock(item, 2)).join("\n");
302
475
  if (renderedItems.length) {
303
476
  sections.push(renderedItems);
304
477
  }
@@ -685,28 +858,6 @@ var memoryTools = {
685
858
  }
686
859
  })
687
860
  };
688
- var sqlQueryAgent = agent2({
689
- name: "text2sql",
690
- model: groq2("openai/gpt-oss-20b"),
691
- tools,
692
- // output: z.object({
693
- // sql: z
694
- // .string()
695
- // .describe('The SQL query generated to answer the user question.'),
696
- // }),
697
- prompt: (state) => {
698
- return `
699
- <agent>
700
- <name>Freya</name>
701
- <role>You are an expert SQL query generator, answering business questions with accurate queries.</role>
702
- <tone>Your tone should be concise and business-friendly.</tone>
703
- </agent>
704
- ${state?.teachings || ""}
705
- ${state?.introspection || ""}
706
- <output>SQL query that can run directly without prose whatsoever</output>
707
- `;
708
- }
709
- });
710
861
  var t_a_g = agent2({
711
862
  model: groq2("openai/gpt-oss-20b"),
712
863
  tools,
@@ -723,20 +874,187 @@ var t_a_g = agent2({
723
874
  }
724
875
  });
725
876
 
726
- // packages/text2sql/src/lib/file-cache.ts
877
+ // packages/text2sql/src/lib/checkpoint.ts
727
878
  import { createHash } from "node:crypto";
728
- import { existsSync } from "node:fs";
879
+ import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
880
+ import pLimit from "p-limit";
881
+ var Checkpoint = class _Checkpoint {
882
+ constructor(path2, configHash, points) {
883
+ this.path = path2;
884
+ this.configHash = configHash;
885
+ this.points = points;
886
+ }
887
+ points;
888
+ /**
889
+ * Load checkpoint from file, or return empty checkpoint if none exists.
890
+ * Handles corrupted files and config changes gracefully.
891
+ */
892
+ static async load(options) {
893
+ const { path: path2, configHash } = options;
894
+ if (existsSync(path2)) {
895
+ try {
896
+ const content = readFileSync(path2, "utf-8");
897
+ const file = JSON.parse(content);
898
+ if (configHash && file.configHash && file.configHash !== configHash) {
899
+ console.log("\u26A0 Config changed, starting fresh");
900
+ return new _Checkpoint(path2, configHash, {});
901
+ }
902
+ const points = file.points ?? {};
903
+ const totalEntries = Object.values(points).reduce(
904
+ (sum, p) => sum + p.entries.length,
905
+ 0
906
+ );
907
+ console.log(`\u2713 Resuming from checkpoint (${totalEntries} entries)`);
908
+ return new _Checkpoint(path2, configHash, points);
909
+ } catch {
910
+ console.log("\u26A0 Checkpoint corrupted, starting fresh");
911
+ return new _Checkpoint(path2, configHash, {});
912
+ }
913
+ }
914
+ console.log("Starting new checkpoint");
915
+ return new _Checkpoint(path2, configHash, {});
916
+ }
917
+ /**
918
+ * Run a single computation with checkpointing.
919
+ * If already completed, returns cached value.
920
+ *
921
+ * @param key - Unique identifier for this computation
922
+ * @param computation - Async function that produces the value
923
+ * @param codec - Optional codec for encoding/decoding non-primitive values
924
+ */
925
+ async run(key, computation, codec) {
926
+ const point = this.point(key);
927
+ return point.through(
928
+ "single",
929
+ async () => {
930
+ const result = await computation();
931
+ return codec ? codec.encode(result) : result;
932
+ },
933
+ codec
934
+ );
935
+ }
936
+ /**
937
+ * Create a resumable checkpoint point for iterative operations.
938
+ *
939
+ * @param step - Unique identifier for this checkpoint point
940
+ */
941
+ point(step) {
942
+ if (!this.points[step]) {
943
+ this.points[step] = { committed: false, entries: [] };
944
+ }
945
+ return new Point(this.points[step], () => this.save());
946
+ }
947
+ /**
948
+ * Process each input with automatic checkpointing and concurrency.
949
+ *
950
+ * @param step - Unique identifier for this checkpoint
951
+ * @param inputs - Items to process
952
+ * @param process - Function to process each input
953
+ * @param options - Optional settings like concurrency
954
+ * @returns All outputs (use `.flat()` if outputs are arrays)
955
+ */
956
+ async each(step, inputs, process2, options) {
957
+ const point = this.point(step);
958
+ const limit = pLimit(options?.concurrency ?? 1);
959
+ const inputArray = Array.from(inputs);
960
+ await Promise.all(
961
+ inputArray.map(
962
+ (input) => limit(() => point.through(input, () => process2(input)))
963
+ )
964
+ );
965
+ await point.commit();
966
+ return point.values();
967
+ }
968
+ /**
969
+ * Get clean output from all completed points.
970
+ * Single-entry points return the value directly, multi-entry return arrays.
971
+ */
972
+ getOutput() {
973
+ const output = {};
974
+ for (const [key, pointData] of Object.entries(this.points)) {
975
+ if (pointData.entries.length === 1) {
976
+ output[key] = pointData.entries[0].output;
977
+ } else {
978
+ output[key] = pointData.entries.map((e) => e.output);
979
+ }
980
+ }
981
+ return output;
982
+ }
983
+ /** Get the file path where checkpoint is stored */
984
+ getPath() {
985
+ return this.path;
986
+ }
987
+ async save() {
988
+ const file = {
989
+ configHash: this.configHash,
990
+ points: this.points
991
+ };
992
+ const content = JSON.stringify(file, null, 2);
993
+ const tempPath = `${this.path}.tmp`;
994
+ writeFileSync(tempPath, content);
995
+ renameSync(tempPath, this.path);
996
+ }
997
+ };
998
+ function hash(value) {
999
+ return createHash("md5").update(JSON.stringify(value)).digest("hex");
1000
+ }
1001
+ var Point = class {
1002
+ constructor(data, persist) {
1003
+ this.data = data;
1004
+ this.persist = persist;
1005
+ this.#cache = new Map(
1006
+ data.entries.map((e) => [e.inputHash, e.output])
1007
+ );
1008
+ }
1009
+ #cache;
1010
+ /**
1011
+ * Execute computation if input wasn't processed before.
1012
+ * Returns cached output if input hash exists, otherwise executes, saves, and returns.
1013
+ */
1014
+ async through(input, compute, codec) {
1015
+ const inputHash = hash(input);
1016
+ if (this.#cache.has(inputHash)) {
1017
+ const cached = this.#cache.get(inputHash);
1018
+ return codec ? codec.decode(cached) : cached;
1019
+ }
1020
+ const output = await compute();
1021
+ this.data.entries.push({ inputHash, output });
1022
+ this.#cache.set(inputHash, output);
1023
+ await this.persist();
1024
+ return codec ? codec.decode(output) : output;
1025
+ }
1026
+ /** Mark this point as complete. */
1027
+ async commit() {
1028
+ this.data.committed = true;
1029
+ await this.persist();
1030
+ }
1031
+ /** Check if this point has been committed. */
1032
+ isCommitted() {
1033
+ return this.data.committed;
1034
+ }
1035
+ /** Get all outputs from this point. */
1036
+ values() {
1037
+ return this.data.entries.map((e) => e.output);
1038
+ }
1039
+ };
1040
+ function hashConfig(config) {
1041
+ return createHash("md5").update(JSON.stringify(config)).digest("hex");
1042
+ }
1043
+
1044
+ // packages/text2sql/src/lib/file-cache.ts
1045
+ import { createHash as createHash2 } from "node:crypto";
1046
+ import { existsSync as existsSync2 } from "node:fs";
729
1047
  import { readFile, writeFile } from "node:fs/promises";
730
1048
  import { tmpdir } from "node:os";
731
1049
  import path from "node:path";
732
1050
  var FileCache = class {
733
1051
  path;
734
1052
  constructor(watermark, extension = ".txt") {
735
- const hash = createHash("md5").update(watermark).digest("hex");
736
- this.path = path.join(tmpdir(), `text2sql-${hash}${extension}`);
1053
+ const hash2 = createHash2("md5").update(watermark).digest("hex");
1054
+ this.path = path.join(tmpdir(), `text2sql-${hash2}${extension}`);
737
1055
  }
738
1056
  async get() {
739
- if (existsSync(this.path)) {
1057
+ if (existsSync2(this.path)) {
740
1058
  return readFile(this.path, "utf-8");
741
1059
  }
742
1060
  return null;
@@ -961,244 +1279,1101 @@ import {
961
1279
  } from "ai";
962
1280
  import { v7 as v72 } from "uuid";
963
1281
  import {
964
- generate,
1282
+ generate as generate4,
965
1283
  stream,
966
- user
1284
+ user as user4
967
1285
  } from "@deepagents/agent";
968
1286
 
969
- // packages/text2sql/src/lib/agents/explainer.agent.ts
1287
+ // packages/text2sql/src/lib/agents/chat1.agent.ts
1288
+ import { groq as groq4 } from "@ai-sdk/groq";
1289
+ import { tool as tool2 } from "ai";
1290
+ import z4 from "zod";
1291
+ import { agent as agent4, toState as toState2 } from "@deepagents/agent";
1292
+ import { scratchpad_tool as scratchpad_tool2 } from "@deepagents/toolbox";
1293
+
1294
+ // packages/text2sql/src/lib/agents/sql.agent.ts
970
1295
  import { groq as groq3 } from "@ai-sdk/groq";
971
- import dedent2 from "dedent";
1296
+ import { defaultSettingsMiddleware, wrapLanguageModel } from "ai";
972
1297
  import z3 from "zod";
973
- import { agent as agent3 } from "@deepagents/agent";
974
- var explainerAgent = agent3({
975
- name: "explainer",
1298
+ import { agent as agent3, generate, user } from "@deepagents/agent";
1299
+ var RETRY_TEMPERATURES = [0, 0.2, 0.3];
1300
+ var sqlQueryAgent = agent3({
1301
+ name: "text2sql",
976
1302
  model: groq3("openai/gpt-oss-20b"),
977
- prompt: (state) => dedent2`
978
- You are an expert SQL tutor.
979
- Explain the following SQL query in plain English to a non-technical user.
980
- Focus on the intent and logic, not the syntax.
981
-
982
- <sql>
983
- ${state?.sql}
984
- </sql>
985
- `,
986
- output: z3.object({
987
- explanation: z3.string().describe("The explanation of the SQL query.")
988
- })
989
- });
990
-
991
- // packages/text2sql/src/lib/teach/teachings.ts
992
- var teachings_default = [
993
- hint(
994
- "If the user asks to show a table or entity without specifying columns, use SELECT *."
995
- ),
996
- hint(
997
- "When showing items associated with another entity, include the item ID and the related details requested."
998
- ),
999
- hint(
1000
- 'When asked to "show" items, list them unless the user explicitly asks to count or total.'
1001
- ),
1002
- hint(
1003
- "Use canonical/LowCardinality values verbatim for filtering; [rows/size] hints suggest when to aggregate instead of listing."
1004
- ),
1005
- hint(
1006
- "Favor PK/indexed columns for joins and filters; follow relationship metadata for join direction and cardinality."
1007
- ),
1008
- guardrail({
1009
- rule: "Avoid unbounded scans on large/time-based tables.",
1010
- action: "Ask for or apply a reasonable recent date range before querying broad fact tables."
1011
- }),
1012
- guardrail({
1013
- rule: "Do not return oversized raw result sets.",
1014
- action: "Keep raw limit strictly to ~100 rows even if users request more or coearced by hints.",
1015
- reason: "Browser will time out or crash on huge datasets. Data overload harms usability."
1016
- }),
1017
- guardrail({
1018
- rule: "Prevent cartesian or guesswork joins.",
1019
- reason: "Protect correctness and performance.",
1020
- action: "If join keys are missing or unclear, inspect relationships and ask for the intended join path before executing."
1021
- }),
1022
- clarification({
1023
- when: "The request targets time-based data without a date range.",
1024
- ask: "Confirm the intended timeframe (e.g., last 30/90 days, YTD, specific year).",
1025
- reason: "Prevents large scans and irrelevant results."
1026
- }),
1027
- clarification({
1028
- when: 'The request uses ambiguous scoring or ranking language (e.g., "top", "best", "active") without a metric.',
1029
- ask: "Clarify the ranking metric or definition before writing the query.",
1030
- reason: "Ensures the correct aggregation/ordering is used."
1031
- }),
1032
- workflow({
1033
- task: "SQL generation plan",
1034
- steps: [
1035
- "Translate the question into SQL patterns (aggregation, segmentation, time range, ranking).",
1036
- "Choose tables/relations that satisfy those patterns; note lookup tables and filter values implied by schema hints.",
1037
- "Inspect samples with 'get_sample_rows' for any column you'll use in WHERE/JOIN conditions - target just those columns (e.g., get_sample_rows('orders', ['status', 'order_type'])).",
1038
- "Sketch join/filter/aggregation order considering table sizes, indexes, and stats.",
1039
- "Draft SQL, validate via 'validate_query', then execute via 'db_query' with a short reasoning note."
1040
- ]
1041
- }),
1042
- styleGuide({
1043
- prefer: "Summaries should be concise, business-friendly, highlight key comparisons, and add a short helpful follow-up when useful."
1044
- }),
1045
- // Tool usage constraints
1046
- guardrail({
1047
- rule: "You must validate your query before final execution.",
1048
- action: "Follow the pattern: Draft Query \u2192 `validate_query` \u2192 Fix (if needed) \u2192 `db_query`."
1049
- }),
1050
- guardrail({
1051
- rule: "ALWAYS use `get_sample_rows` before writing queries that filter or compare against string columns.",
1052
- reason: "Prevents SQL errors from wrong value formats.",
1053
- action: "Target specific columns (e.g., get_sample_rows('table', ['status', 'type']))."
1054
- }),
1055
- guardrail({
1056
- rule: "Do not call `db_query` without first producing and validating a SQL snippet.",
1057
- action: "First produce the query string, then validate."
1058
- }),
1059
- hint(
1060
- "Use the `scratchpad` tool for strategic reflection during SQL query generation."
1061
- )
1062
- ];
1063
-
1064
- // packages/text2sql/src/lib/sql.ts
1065
- var Text2Sql = class {
1066
- #config;
1067
- constructor(config) {
1068
- this.#config = {
1069
- adapter: config.adapter,
1070
- briefCache: new FileCache("brief-" + config.version),
1071
- history: config.history,
1072
- instructions: [...teachings_default, ...config.instructions ?? []],
1073
- tools: config.tools ?? {},
1074
- model: config.model,
1075
- memory: config.memory,
1076
- introspection: new FileCache("introspection-" + config.version)
1077
- };
1078
- }
1079
- async explain(sql) {
1080
- const { experimental_output } = await generate(
1081
- explainerAgent,
1082
- [user("Explain this SQL.")],
1083
- { sql }
1084
- );
1085
- return experimental_output.explanation;
1303
+ logging: process.env.AGENT_LOGGING === "true",
1304
+ output: z3.union([
1305
+ z3.object({
1306
+ sql: z3.string().describe("The SQL query that answers the question"),
1307
+ reasoning: z3.string().optional().describe("The reasoning steps taken to generate the SQL")
1308
+ }),
1309
+ z3.object({
1310
+ error: z3.string().describe(
1311
+ "Error message explaining why the question cannot be answered with the given schema"
1312
+ )
1313
+ })
1314
+ ]),
1315
+ prompt: (state) => {
1316
+ return `
1317
+ ${state?.teachings || ""}
1318
+ ${state?.introspection || ""}
1319
+ `;
1086
1320
  }
1087
- async toSql(query, options) {
1088
- const introspection = await this.index();
1089
- const { text } = await generate(
1090
- sqlQueryAgent.clone({
1091
- model: this.#config.model,
1092
- tools: {
1093
- ...t_a_g.handoff.tools,
1094
- ...options?.tools ?? this.#config.tools
1095
- }
1096
- }),
1097
- [user(query)],
1321
+ });
1322
+ function extractSql(output) {
1323
+ const match = output.match(/```sql\n?([\s\S]*?)```/);
1324
+ return match ? match[1].trim() : output.trim();
1325
+ }
1326
+ async function generateSql(params) {
1327
+ const {
1328
+ input,
1329
+ model,
1330
+ temperature,
1331
+ introspection,
1332
+ instructions,
1333
+ previousError
1334
+ } = params;
1335
+ const agentInstance = sqlQueryAgent.clone({
1336
+ model: wrapLanguageModel({
1337
+ model,
1338
+ middleware: defaultSettingsMiddleware({
1339
+ settings: { temperature, topP: 1 }
1340
+ })
1341
+ })
1342
+ });
1343
+ const messages = previousError ? [
1344
+ user(input),
1345
+ user(
1346
+ `<validation_error>Your previous SQL query had the following error: ${previousError}. Please fix the query.</validation_error>`
1347
+ )
1348
+ ] : [user(input)];
1349
+ try {
1350
+ const { experimental_output: output } = await generate(
1351
+ agentInstance,
1352
+ messages,
1098
1353
  {
1099
1354
  teachings: toInstructions(
1100
1355
  "instructions",
1101
1356
  persona({
1102
1357
  name: "Freya",
1103
- role: "You are an expert SQL query generator, answering business questions with accurate queries.",
1104
- tone: "Your tone should be concise and business-friendly."
1358
+ role: "You are an expert SQL query generator. You translate natural language questions into precise, efficient SQL queries based on the provided database schema."
1105
1359
  }),
1106
- ...this.#config.instructions
1360
+ ...instructions
1107
1361
  ),
1108
- adapter: this.#config.adapter,
1109
1362
  introspection
1110
1363
  }
1111
1364
  );
1112
- return text;
1113
- }
1114
- instruct(...dataset) {
1115
- this.#config.instructions.push(...dataset);
1365
+ if ("error" in output) {
1366
+ return { success: false, error: output.error, isUnanswerable: true };
1367
+ }
1368
+ return { success: true, sql: extractSql(output.sql) };
1369
+ } catch (error) {
1370
+ if (error instanceof Error && (error.message.includes("Failed to validate JSON") || error.message.includes("response did not match schema"))) {
1371
+ return {
1372
+ success: false,
1373
+ error: `Schema validation failed: ${error.message}`
1374
+ };
1375
+ }
1376
+ throw error;
1116
1377
  }
1117
- async inspect(agent4) {
1118
- const [grounding] = await Promise.all([this.index()]);
1119
- const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
1120
- (name) => name.startsWith("render_")
1121
- );
1122
- const allInstructions = [
1123
- ...this.#config.instructions,
1124
- ...renderToolNames.length ? [
1125
- hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
1126
- styleGuide({
1127
- prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
1128
- always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
1129
- })
1130
- ] : []
1131
- ];
1132
- const tools2 = Object.keys({
1133
- ...agent4.handoff.tools,
1134
- ...this.#config.memory ? memoryTools : {},
1135
- ...this.#config.tools
1136
- });
1378
+ }
1379
+ var sqlGenerators = {
1380
+ generateSql
1381
+ };
1382
+ async function generateAndValidate(options, temperature, previousError) {
1383
+ const result = await sqlGenerators.generateSql({
1384
+ input: options.input,
1385
+ model: options.model ?? sqlQueryAgent.model,
1386
+ temperature,
1387
+ introspection: options.introspection,
1388
+ instructions: options.instructions,
1389
+ previousError
1390
+ });
1391
+ if (!result.success) {
1137
1392
  return {
1138
- tools: tools2,
1139
- prompt: agent4.instructions({
1140
- introspection: grounding,
1141
- teachings: toInstructions(
1142
- "instructions",
1143
- persona({
1144
- name: "Freya",
1145
- role: "You are an expert SQL query generator, answering business questions with accurate queries.",
1146
- tone: "Your tone should be concise and business-friendly."
1147
- }),
1148
- ...allInstructions
1149
- )
1150
- })
1393
+ ok: false,
1394
+ error: result.error,
1395
+ isUnanswerable: result.isUnanswerable
1151
1396
  };
1152
1397
  }
1153
- async index(options) {
1154
- const cached = await this.#config.introspection.get();
1155
- if (cached) {
1156
- return cached;
1157
- }
1158
- const introspection = await this.#config.adapter.introspect();
1159
- await this.#config.introspection.set(introspection);
1160
- return introspection;
1398
+ const validationError = await options.adapter.validate(result.sql);
1399
+ if (validationError) {
1400
+ return { ok: false, error: validationError };
1161
1401
  }
1162
- // public async suggest() {
1163
- // const [introspection, adapterInfo] = await Promise.all([
1164
- // this.index(),
1165
- // this.#config.adapter.introspect(),
1166
- // ]);
1167
- // const { experimental_output: output } = await generate(
1168
- // suggestionsAgent,
1169
- // [
1170
- // user(
1171
- // 'Suggest high-impact business questions and matching SQL queries for this database.',
1172
- // ),
1173
- // ],
1174
- // {
1175
- // },
1176
- // );
1177
- // return output.suggestions;
1178
- // }
1179
- async chat(messages, params) {
1180
- const [introspection, userTeachables] = await Promise.all([
1181
- this.index({ onProgress: console.log }),
1182
- this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
1183
- ]);
1184
- const chat = await this.#config.history.upsertChat({
1185
- id: params.chatId,
1186
- userId: params.userId,
1187
- title: "Chat " + params.chatId
1188
- });
1189
- const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
1190
- (name) => name.startsWith("render_")
1402
+ return { ok: true, sql: result.sql };
1403
+ }
1404
+ async function toSql(options) {
1405
+ const { maxRetries = 3 } = options;
1406
+ const errors = [];
1407
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1408
+ const temperature = RETRY_TEMPERATURES[attempt - 1] ?? 0.3;
1409
+ const result = await generateAndValidate(
1410
+ options,
1411
+ temperature,
1412
+ errors.at(-1)
1191
1413
  );
1192
- const instructions = [
1193
- ...this.#config.instructions,
1194
- ...renderToolNames.length ? [
1195
- hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
1196
- styleGuide({
1197
- prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
1414
+ if (result.ok) {
1415
+ return {
1416
+ sql: result.sql,
1417
+ attempts: attempt,
1418
+ errors: errors.length ? errors : void 0
1419
+ };
1420
+ }
1421
+ if (result.isUnanswerable) {
1422
+ return { sql: "", attempts: attempt, errors: [result.error] };
1423
+ }
1424
+ errors.push(result.error);
1425
+ }
1426
+ return { sql: "", attempts: maxRetries, errors };
1427
+ }
1428
+
1429
+ // packages/text2sql/src/lib/agents/chat1.agent.ts
1430
+ var tools2 = {
1431
+ query_database: tool2({
1432
+ description: `Query the database to answer a question. Provide your question in natural language and this tool will:
1433
+ 1. Generate the appropriate SQL query
1434
+ 2. Validate the SQL syntax
1435
+ 3. Execute the query
1436
+ 4. Return the results
1437
+
1438
+ Use this tool when you need to retrieve data to answer the user's question.`,
1439
+ inputSchema: z4.object({
1440
+ question: z4.string().min(1).describe(
1441
+ "The question to answer, expressed in natural language. Be specific about what data you need."
1442
+ ),
1443
+ reasoning: z4.string().optional().describe(
1444
+ "Your reasoning for why this query is needed to answer the user."
1445
+ )
1446
+ }),
1447
+ execute: async ({ question }, options) => {
1448
+ const state = toState2(options);
1449
+ try {
1450
+ const sqlResult = await toSql({
1451
+ input: question,
1452
+ adapter: state.adapter,
1453
+ introspection: state.introspection,
1454
+ instructions: state.instructions
1455
+ });
1456
+ if (!sqlResult.sql) {
1457
+ return {
1458
+ success: false,
1459
+ error: sqlResult.errors?.join("; ") || "Failed to generate SQL",
1460
+ attempts: sqlResult.attempts
1461
+ };
1462
+ }
1463
+ const data = await state.adapter.execute(sqlResult.sql);
1464
+ return {
1465
+ success: true,
1466
+ sql: sqlResult.sql,
1467
+ data,
1468
+ attempts: sqlResult.attempts
1469
+ };
1470
+ } catch (error) {
1471
+ return {
1472
+ success: false,
1473
+ error: error instanceof Error ? error.message : "Unknown error occurred"
1474
+ };
1475
+ }
1476
+ }
1477
+ }),
1478
+ scratchpad: scratchpad_tool2
1479
+ };
1480
+ var chat1Agent = agent4({
1481
+ name: "chat1-combined",
1482
+ model: groq4("openai/gpt-oss-20b"),
1483
+ tools: tools2,
1484
+ prompt: (state) => {
1485
+ return `
1486
+ ${state?.teachings || ""}
1487
+ ${state?.introspection || ""}
1488
+ `;
1489
+ }
1490
+ });
1491
+
1492
+ // packages/text2sql/src/lib/agents/chat2.agent.ts
1493
+ import { groq as groq5 } from "@ai-sdk/groq";
1494
+ import { tool as tool3 } from "ai";
1495
+ import z5 from "zod";
1496
+ import { agent as agent5, toState as toState3 } from "@deepagents/agent";
1497
+ import { scratchpad_tool as scratchpad_tool3 } from "@deepagents/toolbox";
1498
+ var tools3 = {
1499
+ generate_sql: tool3({
1500
+ description: `Generate a SQL query from a natural language question. This tool will:
1501
+ 1. Translate your question into SQL
1502
+ 2. Validate the SQL syntax
1503
+ 3. Retry with corrections if validation fails
1504
+ 4. Return the validated SQL for your review
1505
+
1506
+ Use this BEFORE execute_sql to see what query will be run. You can then:
1507
+ - Explain the approach to the user
1508
+ - Decide if the SQL looks correct
1509
+ - Refine your question and regenerate if needed`,
1510
+ inputSchema: z5.object({
1511
+ question: z5.string().min(1).describe(
1512
+ "The question to translate into SQL. Be specific about what data you need."
1513
+ ),
1514
+ reasoning: z5.string().optional().describe("Your reasoning for why this data is needed.")
1515
+ }),
1516
+ execute: async ({ question }, options) => {
1517
+ const state = toState3(options);
1518
+ try {
1519
+ const sqlResult = await toSql({
1520
+ input: question,
1521
+ adapter: state.adapter,
1522
+ introspection: state.introspection,
1523
+ instructions: state.instructions
1524
+ });
1525
+ if (!sqlResult.sql) {
1526
+ return {
1527
+ success: false,
1528
+ error: sqlResult.errors?.join("; ") || "Failed to generate SQL",
1529
+ attempts: sqlResult.attempts,
1530
+ validationErrors: sqlResult.errors
1531
+ };
1532
+ }
1533
+ return {
1534
+ success: true,
1535
+ sql: sqlResult.sql,
1536
+ attempts: sqlResult.attempts,
1537
+ validationErrors: sqlResult.errors
1538
+ };
1539
+ } catch (error) {
1540
+ return {
1541
+ success: false,
1542
+ error: error instanceof Error ? error.message : "Unknown error occurred"
1543
+ };
1544
+ }
1545
+ }
1546
+ }),
1547
+ execute_sql: tool3({
1548
+ description: `Execute a SQL query and return the results. Use this AFTER generate_sql to run the query.
1549
+
1550
+ Only SELECT and WITH (CTE) queries are allowed - no data modification.`,
1551
+ inputSchema: z5.object({
1552
+ sql: z5.string().min(1).refine(
1553
+ (sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
1554
+ {
1555
+ message: "Only read-only SELECT or WITH queries are allowed."
1556
+ }
1557
+ ).describe("The SQL query to execute (must be SELECT or WITH)."),
1558
+ reasoning: z5.string().optional().describe("Brief explanation of what this query retrieves.")
1559
+ }),
1560
+ execute: async ({ sql }, options) => {
1561
+ const state = toState3(options);
1562
+ try {
1563
+ const data = await state.adapter.execute(sql);
1564
+ return {
1565
+ success: true,
1566
+ data,
1567
+ rowCount: Array.isArray(data) ? data.length : void 0
1568
+ };
1569
+ } catch (error) {
1570
+ return {
1571
+ success: false,
1572
+ error: error instanceof Error ? error.message : "Query execution failed"
1573
+ };
1574
+ }
1575
+ }
1576
+ }),
1577
+ scratchpad: scratchpad_tool3
1578
+ };
1579
+ var chat2Agent = agent5({
1580
+ name: "chat2-with-peek",
1581
+ model: groq5("openai/gpt-oss-20b"),
1582
+ tools: tools3,
1583
+ prompt: (state) => {
1584
+ return `
1585
+ ${state?.teachings || ""}
1586
+ ${state?.introspection || ""}
1587
+
1588
+ When answering questions that require database queries:
1589
+ 1. First use generate_sql to create the SQL query
1590
+ 2. Review the generated SQL to ensure it matches the user's intent
1591
+ 3. Use execute_sql to run the query
1592
+ 4. Present the results to the user
1593
+
1594
+ If the generated SQL doesn't look right, you can refine your question and regenerate.
1595
+ `;
1596
+ }
1597
+ });
1598
+
1599
+ // packages/text2sql/src/lib/agents/chat3.agent.ts
1600
+ import { groq as groq6 } from "@ai-sdk/groq";
1601
+ import { defaultSettingsMiddleware as defaultSettingsMiddleware2, tool as tool4, wrapLanguageModel as wrapLanguageModel2 } from "ai";
1602
+ import z6 from "zod";
1603
+ import { agent as agent6, generate as generate2, toState as toState4, user as user2 } from "@deepagents/agent";
1604
+ import { scratchpad_tool as scratchpad_tool4 } from "@deepagents/toolbox";
1605
+ var collaborativeSqlOutputSchema = z6.discriminatedUnion("status", [
1606
+ z6.object({
1607
+ status: z6.literal("success"),
1608
+ sql: z6.string().describe("The generated SQL query"),
1609
+ confidence: z6.enum(["high", "medium", "low"]).describe("Confidence level in this SQL being correct"),
1610
+ assumptions: z6.array(z6.string()).optional().describe("Assumptions made during SQL generation"),
1611
+ reasoning: z6.string().optional().describe("Brief explanation of the query approach")
1612
+ }),
1613
+ z6.object({
1614
+ status: z6.literal("clarification_needed"),
1615
+ question: z6.string().describe("Question to clarify the request"),
1616
+ context: z6.string().optional().describe("Why this clarification is needed"),
1617
+ options: z6.array(z6.string()).optional().describe("Possible options if applicable")
1618
+ }),
1619
+ z6.object({
1620
+ status: z6.literal("unanswerable"),
1621
+ reason: z6.string().describe("Why this question cannot be answered"),
1622
+ suggestions: z6.array(z6.string()).optional().describe("Alternative questions that could be answered")
1623
+ })
1624
+ ]);
1625
+ var collaborativeSqlAgent = agent6({
1626
+ name: "collaborative-sql",
1627
+ model: groq6("openai/gpt-oss-20b"),
1628
+ output: collaborativeSqlOutputSchema,
1629
+ prompt: (state) => {
1630
+ return `
1631
+ ${toInstructions(
1632
+ "instructions",
1633
+ persona({
1634
+ name: "SQLCollab",
1635
+ role: "You are an expert SQL query generator that collaborates with the user to ensure accuracy."
1636
+ }),
1637
+ ...state?.instructions || []
1638
+ )}
1639
+ ${state?.introspection || ""}
1640
+
1641
+ IMPORTANT: You have three response options:
1642
+
1643
+ 1. SUCCESS - When you can confidently generate SQL:
1644
+ - Provide the SQL query
1645
+ - Rate your confidence (high/medium/low)
1646
+ - List any assumptions you made
1647
+
1648
+ 2. CLARIFICATION_NEEDED - When the question is ambiguous:
1649
+ - Ask a specific clarifying question
1650
+ - Explain why clarification is needed
1651
+ - Provide options if applicable
1652
+
1653
+ 3. UNANSWERABLE - When the question cannot be answered with available data:
1654
+ - Explain why
1655
+ - Suggest alternative questions that could be answered
1656
+
1657
+ Prefer asking for clarification over making low-confidence guesses.
1658
+ `;
1659
+ }
1660
+ });
1661
+ var tools4 = {
1662
+ consult_sql_agent: tool4({
1663
+ description: `Consult the SQL specialist agent to generate a query. The SQL agent may:
1664
+ - Return a SQL query with confidence level and assumptions
1665
+ - Ask for clarification if the question is ambiguous
1666
+ - Indicate if the question cannot be answered with available data
1667
+
1668
+ Based on the response:
1669
+ - If clarification is needed, you can provide context or ask the user
1670
+ - If assumptions were made, verify them with the user for important queries
1671
+ - If unanswerable, relay the suggestions to the user`,
1672
+ inputSchema: z6.object({
1673
+ question: z6.string().min(1).describe("The question to translate into SQL."),
1674
+ context: z6.string().optional().describe("Additional context from the conversation that might help."),
1675
+ previousClarification: z6.string().optional().describe(
1676
+ "Answer to a previous clarification question from the SQL agent."
1677
+ )
1678
+ }),
1679
+ execute: async ({ question, context: context2, previousClarification }, options) => {
1680
+ const state = toState4(options);
1681
+ try {
1682
+ let fullQuestion = question;
1683
+ if (context2) {
1684
+ fullQuestion = `${question}
1685
+
1686
+ Additional context: ${context2}`;
1687
+ }
1688
+ if (previousClarification) {
1689
+ fullQuestion = `${fullQuestion}
1690
+
1691
+ Clarification provided: ${previousClarification}`;
1692
+ }
1693
+ const agentInstance = collaborativeSqlAgent.clone({
1694
+ model: wrapLanguageModel2({
1695
+ model: collaborativeSqlAgent.model,
1696
+ middleware: defaultSettingsMiddleware2({
1697
+ settings: { temperature: 0.1 }
1698
+ })
1699
+ })
1700
+ });
1701
+ const { experimental_output: output } = await generate2(
1702
+ agentInstance,
1703
+ [user2(fullQuestion)],
1704
+ state
1705
+ );
1706
+ if (output.status === "success") {
1707
+ const validationError = await state.adapter.validate(output.sql);
1708
+ if (validationError) {
1709
+ return {
1710
+ success: false,
1711
+ error: `SQL validation failed: ${validationError}`
1712
+ };
1713
+ }
1714
+ const data = await state.adapter.execute(output.sql);
1715
+ return {
1716
+ success: true,
1717
+ sql: output.sql,
1718
+ data,
1719
+ confidence: output.confidence,
1720
+ assumptions: output.assumptions
1721
+ };
1722
+ }
1723
+ if (output.status === "clarification_needed") {
1724
+ return {
1725
+ success: false,
1726
+ clarificationNeeded: output.question,
1727
+ clarificationContext: output.context,
1728
+ clarificationOptions: output.options
1729
+ };
1730
+ }
1731
+ if (output.status === "unanswerable") {
1732
+ return {
1733
+ success: false,
1734
+ unanswerableReason: output.reason,
1735
+ suggestions: output.suggestions
1736
+ };
1737
+ }
1738
+ return {
1739
+ success: false,
1740
+ error: "Unexpected response from SQL agent"
1741
+ };
1742
+ } catch (error) {
1743
+ return {
1744
+ success: false,
1745
+ error: error instanceof Error ? error.message : "Unknown error occurred"
1746
+ };
1747
+ }
1748
+ }
1749
+ }),
1750
+ execute_sql: tool4({
1751
+ description: `Execute a SQL query directly. Use this when you have SQL that you want to run
1752
+ (e.g., after receiving SQL from consult_sql_agent or for follow-up queries).`,
1753
+ inputSchema: z6.object({
1754
+ sql: z6.string().min(1).refine(
1755
+ (sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
1756
+ {
1757
+ message: "Only read-only SELECT or WITH queries are allowed."
1758
+ }
1759
+ ).describe("The SQL query to execute.")
1760
+ }),
1761
+ execute: async ({ sql }, options) => {
1762
+ const state = toState4(options);
1763
+ try {
1764
+ const validationError = await state.adapter.validate(sql);
1765
+ if (validationError) {
1766
+ return {
1767
+ success: false,
1768
+ error: `Validation failed: ${validationError}`
1769
+ };
1770
+ }
1771
+ const data = await state.adapter.execute(sql);
1772
+ return {
1773
+ success: true,
1774
+ data,
1775
+ rowCount: Array.isArray(data) ? data.length : void 0
1776
+ };
1777
+ } catch (error) {
1778
+ return {
1779
+ success: false,
1780
+ error: error instanceof Error ? error.message : "Execution failed"
1781
+ };
1782
+ }
1783
+ }
1784
+ }),
1785
+ scratchpad: scratchpad_tool4
1786
+ };
1787
+ var chat3Agent = agent6({
1788
+ name: "chat3-collaborative",
1789
+ model: groq6("openai/gpt-oss-20b"),
1790
+ tools: tools4,
1791
+ prompt: (state) => {
1792
+ return `
1793
+ ${state?.teachings || ""}
1794
+ ${state?.introspection || ""}
1795
+
1796
+ When answering questions that require database queries, use the consult_sql_agent tool.
1797
+
1798
+ The SQL agent may respond in three ways:
1799
+ 1. SUCCESS with SQL, confidence, and assumptions - review the confidence and assumptions
1800
+ 2. CLARIFICATION_NEEDED with a question - either answer from context or ask the user
1801
+ 3. UNANSWERABLE with reason and suggestions - relay this to the user helpfully
1802
+
1803
+ For medium/low confidence results, consider mentioning the assumptions to the user.
1804
+ For clarification requests, try to answer from conversation context first before asking the user.
1805
+ `;
1806
+ }
1807
+ });
1808
+
1809
+ // packages/text2sql/src/lib/agents/chat4.agent.ts
1810
+ import { groq as groq7 } from "@ai-sdk/groq";
1811
+ import { defaultSettingsMiddleware as defaultSettingsMiddleware3, tool as tool5, wrapLanguageModel as wrapLanguageModel3 } from "ai";
1812
+ import z7 from "zod";
1813
+ import { agent as agent7, generate as generate3, toState as toState5, user as user3 } from "@deepagents/agent";
1814
+ import { scratchpad_tool as scratchpad_tool5 } from "@deepagents/toolbox";
1815
+ var questionDecompositionSchema = z7.object({
1816
+ originalQuestion: z7.string().describe("The original question being decomposed"),
1817
+ breakdown: z7.array(z7.string()).min(1).describe(
1818
+ "Semantic breakdown of the question into its component parts. Each part describes an aspect of what is being asked, NOT how to implement it."
1819
+ ),
1820
+ entities: z7.array(z7.string()).optional().describe(
1821
+ "Key entities/concepts mentioned (e.g., customers, orders, products)"
1822
+ ),
1823
+ filters: z7.array(z7.string()).optional().describe(
1824
+ 'Filtering criteria mentioned (e.g., "last quarter", "above $100")'
1825
+ ),
1826
+ aggregation: z7.string().optional().describe(
1827
+ 'Type of aggregation if any (e.g., "count", "sum", "average", "top N")'
1828
+ ),
1829
+ ambiguities: z7.array(z7.string()).optional().describe("Any ambiguous parts that might need clarification")
1830
+ });
1831
+ var decompositionSqlOutputSchema = z7.union([
1832
+ z7.object({
1833
+ sql: z7.string().describe("The SQL query that answers the decomposed question"),
1834
+ reasoning: z7.string().optional().describe("How each breakdown component was addressed")
1835
+ }),
1836
+ z7.object({
1837
+ error: z7.string().describe("Error message if the question cannot be answered")
1838
+ })
1839
+ ]);
1840
+ var decompositionSqlAgent = agent7({
1841
+ name: "decomposition-sql",
1842
+ model: groq7("openai/gpt-oss-20b"),
1843
+ output: decompositionSqlOutputSchema,
1844
+ prompt: (state) => {
1845
+ return `
1846
+ ${toInstructions(
1847
+ "instructions",
1848
+ persona({
1849
+ name: "SQLDecomp",
1850
+ role: "You are an expert SQL query generator. You receive questions broken down into semantic components and generate precise SQL."
1851
+ }),
1852
+ ...state?.instructions || []
1853
+ )}
1854
+ ${state?.introspection || ""}
1855
+
1856
+ You will receive questions in a decomposed format with:
1857
+ - breakdown: Semantic parts of the question
1858
+ - entities: Key concepts mentioned
1859
+ - filters: Filtering criteria
1860
+ - aggregation: Type of aggregation needed
1861
+ - ambiguities: Potentially unclear parts
1862
+
1863
+ Address each component of the breakdown in your SQL.
1864
+ If there are ambiguities, make reasonable assumptions and note them in your reasoning.
1865
+ `;
1866
+ }
1867
+ });
1868
+ var RETRY_TEMPERATURES2 = [0, 0.2, 0.3];
1869
+ var tools5 = {
1870
+ query_with_decomposition: tool5({
1871
+ description: `Query the database using question decomposition. This tool:
1872
+ 1. Breaks down your question into semantic components (entities, filters, aggregations)
1873
+ 2. Passes the decomposition to the SQL specialist
1874
+ 3. Generates and validates SQL
1875
+ 4. Executes and returns results
1876
+
1877
+ This approach helps ensure all aspects of the question are addressed in the query.`,
1878
+ inputSchema: z7.object({
1879
+ question: z7.string().min(1).describe("The question to answer."),
1880
+ breakdown: z7.array(z7.string()).min(1).describe(
1881
+ 'Break down the question into its semantic parts. Each part should describe an ASPECT of what is being asked, not instructions. Example for "top customers by revenue last month": ["customers who made purchases", "revenue from those purchases", "time period: last month", "ranking: top by total revenue"]'
1882
+ ),
1883
+ entities: z7.array(z7.string()).optional().describe(
1884
+ 'Key entities mentioned (e.g., ["customers", "orders", "products"])'
1885
+ ),
1886
+ filters: z7.array(z7.string()).optional().describe('Filter criteria (e.g., ["last month", "status = active"])'),
1887
+ aggregation: z7.string().optional().describe(
1888
+ 'Aggregation type if any (e.g., "sum revenue", "count orders", "top 10")'
1889
+ ),
1890
+ ambiguities: z7.array(z7.string()).optional().describe("Note any ambiguous parts you identified")
1891
+ }),
1892
+ execute: async ({ question, breakdown, entities, filters, aggregation, ambiguities }, options) => {
1893
+ const state = toState5(options);
1894
+ const decomposition = {
1895
+ originalQuestion: question,
1896
+ breakdown,
1897
+ entities,
1898
+ filters,
1899
+ aggregation,
1900
+ ambiguities
1901
+ };
1902
+ const decomposedPrompt = formatDecomposition(decomposition);
1903
+ try {
1904
+ let lastError;
1905
+ for (let attempt = 0; attempt < RETRY_TEMPERATURES2.length; attempt++) {
1906
+ const temperature = RETRY_TEMPERATURES2[attempt];
1907
+ const agentInstance = decompositionSqlAgent.clone({
1908
+ model: wrapLanguageModel3({
1909
+ model: decompositionSqlAgent.model,
1910
+ middleware: defaultSettingsMiddleware3({
1911
+ settings: { temperature }
1912
+ })
1913
+ })
1914
+ });
1915
+ const prompt = lastError ? `${decomposedPrompt}
1916
+
1917
+ Previous attempt failed with: ${lastError}. Please fix the query.` : decomposedPrompt;
1918
+ const { experimental_output: output } = await generate3(
1919
+ agentInstance,
1920
+ [user3(prompt)],
1921
+ state
1922
+ );
1923
+ if ("error" in output) {
1924
+ return {
1925
+ success: false,
1926
+ question,
1927
+ decomposition,
1928
+ error: output.error,
1929
+ attempts: attempt + 1
1930
+ };
1931
+ }
1932
+ const validationError = await state.adapter.validate(output.sql);
1933
+ if (validationError) {
1934
+ lastError = validationError;
1935
+ continue;
1936
+ }
1937
+ const data = await state.adapter.execute(output.sql);
1938
+ return {
1939
+ success: true,
1940
+ question,
1941
+ decomposition,
1942
+ sql: output.sql,
1943
+ data,
1944
+ reasoning: output.reasoning,
1945
+ attempts: attempt + 1
1946
+ };
1947
+ }
1948
+ return {
1949
+ success: false,
1950
+ question,
1951
+ decomposition,
1952
+ error: `Failed after ${RETRY_TEMPERATURES2.length} attempts. Last error: ${lastError}`,
1953
+ attempts: RETRY_TEMPERATURES2.length
1954
+ };
1955
+ } catch (error) {
1956
+ return {
1957
+ success: false,
1958
+ question,
1959
+ decomposition,
1960
+ error: error instanceof Error ? error.message : "Unknown error occurred"
1961
+ };
1962
+ }
1963
+ }
1964
+ }),
1965
+ execute_sql: tool5({
1966
+ description: `Execute a SQL query directly. Use for follow-up queries or when you already have SQL.`,
1967
+ inputSchema: z7.object({
1968
+ sql: z7.string().min(1).refine(
1969
+ (sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
1970
+ {
1971
+ message: "Only read-only SELECT or WITH queries are allowed."
1972
+ }
1973
+ ).describe("The SQL query to execute.")
1974
+ }),
1975
+ execute: async ({ sql }, options) => {
1976
+ const state = toState5(options);
1977
+ try {
1978
+ const validationError = await state.adapter.validate(sql);
1979
+ if (validationError) {
1980
+ return {
1981
+ success: false,
1982
+ error: `Validation failed: ${validationError}`
1983
+ };
1984
+ }
1985
+ const data = await state.adapter.execute(sql);
1986
+ return {
1987
+ success: true,
1988
+ data,
1989
+ rowCount: Array.isArray(data) ? data.length : void 0
1990
+ };
1991
+ } catch (error) {
1992
+ return {
1993
+ success: false,
1994
+ error: error instanceof Error ? error.message : "Execution failed"
1995
+ };
1996
+ }
1997
+ }
1998
+ }),
1999
+ scratchpad: scratchpad_tool5
2000
+ };
2001
+ function formatDecomposition(decomposition) {
2002
+ const parts = [
2003
+ `Original Question: ${decomposition.originalQuestion}`,
2004
+ "",
2005
+ "Question Breakdown:",
2006
+ ...decomposition.breakdown.map((part, i) => ` ${i + 1}. ${part}`)
2007
+ ];
2008
+ if (decomposition.entities?.length) {
2009
+ parts.push("", `Entities: ${decomposition.entities.join(", ")}`);
2010
+ }
2011
+ if (decomposition.filters?.length) {
2012
+ parts.push("", `Filters: ${decomposition.filters.join(", ")}`);
2013
+ }
2014
+ if (decomposition.aggregation) {
2015
+ parts.push("", `Aggregation: ${decomposition.aggregation}`);
2016
+ }
2017
+ if (decomposition.ambiguities?.length) {
2018
+ parts.push(
2019
+ "",
2020
+ "Potential Ambiguities:",
2021
+ ...decomposition.ambiguities.map((a) => ` - ${a}`)
2022
+ );
2023
+ }
2024
+ parts.push(
2025
+ "",
2026
+ "Generate SQL that addresses each component of the breakdown."
2027
+ );
2028
+ return parts.join("\n");
2029
+ }
2030
+ var chat4Agent = agent7({
2031
+ name: "chat4-decomposition",
2032
+ model: groq7("openai/gpt-oss-20b"),
2033
+ tools: tools5,
2034
+ prompt: (state) => {
2035
+ return `
2036
+ ${state?.teachings || ""}
2037
+ ${state?.introspection || ""}
2038
+
2039
+ When answering questions that require database queries, use the query_with_decomposition tool.
2040
+
2041
+ IMPORTANT: You must break down the question into semantic parts - describe WHAT is being asked, not HOW to implement it.
2042
+
2043
+ Good breakdown example for "Which customers bought the most expensive products last quarter?":
2044
+ - "customers who made purchases" (entity relationship)
2045
+ - "products they purchased" (what products)
2046
+ - "expensive products - need definition" (filter criteria - note ambiguity)
2047
+ - "last quarter" (time filter)
2048
+ - "most - ranking by count or value?" (aggregation - note ambiguity)
2049
+
2050
+ Bad breakdown (too instructional):
2051
+ - "JOIN customers with orders" (this is HOW, not WHAT)
2052
+ - "Use ORDER BY and LIMIT" (this is implementation)
2053
+
2054
+ Break the question into its semantic aspects, and let the SQL specialist figure out the implementation.
2055
+ `;
2056
+ }
2057
+ });
2058
+
2059
+ // packages/text2sql/src/lib/agents/explainer.agent.ts
2060
+ import { groq as groq8 } from "@ai-sdk/groq";
2061
+ import dedent2 from "dedent";
2062
+ import z8 from "zod";
2063
+ import { agent as agent8 } from "@deepagents/agent";
2064
+ var explainerAgent = agent8({
2065
+ name: "explainer",
2066
+ model: groq8("openai/gpt-oss-20b"),
2067
+ prompt: (state) => dedent2`
2068
+ You are an expert SQL tutor.
2069
+ Explain the following SQL query in plain English to a non-technical user.
2070
+ Focus on the intent and logic, not the syntax.
2071
+
2072
+ <sql>
2073
+ ${state?.sql}
2074
+ </sql>
2075
+ `,
2076
+ output: z8.object({
2077
+ explanation: z8.string().describe("The explanation of the SQL query.")
2078
+ })
2079
+ });
2080
+
2081
+ // packages/text2sql/src/lib/synthesis/types.ts
2082
+ async function toPairs(producer) {
2083
+ const pairs = [];
2084
+ for await (const chunk of producer.produce()) {
2085
+ pairs.push(...chunk);
2086
+ }
2087
+ return pairs;
2088
+ }
2089
+
2090
+ // packages/text2sql/src/lib/teach/teachings.ts
2091
+ function guidelines(options = {}) {
2092
+ const { date = "strict" } = options;
2093
+ const baseTeachings = [
2094
+ // Schema adherence
2095
+ hint(
2096
+ "Use only tables and columns that exist in the schema. Never reference non-existent entities."
2097
+ ),
2098
+ hint(
2099
+ "If the user asks to show a table or entity without specifying columns, use SELECT *."
2100
+ ),
2101
+ hint(
2102
+ "When showing items associated with another entity, include the item ID and the related details requested."
2103
+ ),
2104
+ hint(
2105
+ 'When asked to "show" items, list them unless the user explicitly asks to count or total.'
2106
+ ),
2107
+ hint(
2108
+ "Use canonical/LowCardinality values verbatim for filtering; [rows/size] hints suggest when to aggregate instead of listing."
2109
+ ),
2110
+ // Joins and relationships
2111
+ hint(
2112
+ "Use appropriate JOINs based on the relationships defined in the schema."
2113
+ ),
2114
+ hint(
2115
+ "Favor PK/indexed columns for joins and filters; follow relationship metadata for join direction and cardinality."
2116
+ ),
2117
+ // Aggregations and calculations
2118
+ hint(
2119
+ "Apply proper aggregations (COUNT, SUM, AVG, etc.) when the question implies summarization."
2120
+ ),
2121
+ hint(
2122
+ 'When asked "how many X are there" about types/categories/statuses (e.g., "how many statuses are there?"), use COUNT(DISTINCT column). This asks about variety, not row count.'
2123
+ ),
2124
+ hint(
2125
+ "Use window functions when the question requires ranking, running totals, or comparisons across rows."
2126
+ ),
2127
+ // Query semantics
2128
+ hint(
2129
+ 'Words like "reach", "reached", "hit" with a value (e.g., "temperature reach 80") mean >= (greater than or equal), not = (exact match).'
2130
+ ),
2131
+ hint(
2132
+ 'For "shared by" two groups or mutually exclusive conditions (e.g., population > 1500 AND < 500), use INTERSECT between separate queries. A single WHERE with contradictory AND returns nothing.'
2133
+ ),
2134
+ hint(
2135
+ 'When filtering by a specific value from a joined table (e.g., "students who registered course statistics"), always include that WHERE condition. Do not omit mentioned filters.'
2136
+ ),
2137
+ hint(
2138
+ "Handle NULL values appropriately using IS NULL, IS NOT NULL, or COALESCE."
2139
+ ),
2140
+ // Style and readability
2141
+ styleGuide({
2142
+ prefer: "Use meaningful aliases for tables and columns to improve readability."
2143
+ }),
2144
+ styleGuide({
2145
+ prefer: "Summaries should be concise, business-friendly, highlight key comparisons, and add a short helpful follow-up when useful."
2146
+ }),
2147
+ // Guardrails - Query safety
2148
+ guardrail({
2149
+ rule: "Generate ONLY valid, executable SQL.",
2150
+ reason: "Invalid SQL wastes resources and confuses users.",
2151
+ action: "Validate syntax and schema references before returning."
2152
+ }),
2153
+ guardrail({
2154
+ rule: "Only generate SELECT statements (read-only queries).",
2155
+ reason: "Prevents accidental data modification.",
2156
+ action: "Never generate INSERT, UPDATE, DELETE, DROP, or other DDL/DML statements."
2157
+ }),
2158
+ guardrail({
2159
+ rule: "Avoid unbounded scans on large tables.",
2160
+ reason: "Protects performance and prevents runaway queries.",
2161
+ action: "Ensure filters are applied on indexed columns before querying broad fact tables."
2162
+ }),
2163
+ guardrail({
2164
+ rule: "Do not add LIMIT unless explicitly requested.",
2165
+ action: 'Only add LIMIT when user explicitly asks for "top N", "first N", or similar. Do NOT add LIMIT for "list all", "show all", or simple "list" queries.',
2166
+ reason: "Adding arbitrary limits changes query semantics."
2167
+ }),
2168
+ guardrail({
2169
+ rule: "Add ORDER BY where appropriate for deterministic results.",
2170
+ reason: "Ensures consistent query output.",
2171
+ action: "Include ORDER BY when results have a natural ordering or when combined with LIMIT."
2172
+ }),
2173
+ guardrail({
2174
+ rule: "Prevent cartesian or guesswork joins.",
2175
+ reason: "Protect correctness and performance.",
2176
+ action: "If join keys are missing or unclear, inspect relationships and ask for the intended join path before executing."
2177
+ }),
2178
+ guardrail({
2179
+ rule: "Ensure the query is optimized for the schema.",
2180
+ reason: "Better performance and resource usage.",
2181
+ action: "Use indexed columns for filtering, avoid SELECT * on large joins, prefer specific column selection when appropriate."
2182
+ }),
2183
+ guardrail({
2184
+ rule: "When facing genuine ambiguity with multiple valid interpretations, seek clarification.",
2185
+ reason: "Prevents incorrect assumptions in edge cases.",
2186
+ action: "Ask a focused clarifying question before proceeding with a guess."
2187
+ }),
2188
+ // Clarifications
2189
+ clarification({
2190
+ when: 'The request uses ambiguous scoring or ranking language (e.g., "top", "best", "active") without a metric.',
2191
+ ask: "Clarify the ranking metric or definition before writing the query.",
2192
+ reason: "Ensures the correct aggregation/ordering is used."
2193
+ }),
2194
+ // Workflow
2195
+ workflow({
2196
+ task: "SQL generation plan",
2197
+ steps: [
2198
+ 'Scan column names for terms matching the question. If a phrase like "total X" or "number of Y" matches a column name (e.g., Total_X, Num_Y), select that column directly instead of aggregating.',
2199
+ "Translate the question into SQL patterns (aggregation, segmentation, time range, ranking) only if no column name match.",
2200
+ "Choose tables/relations that satisfy those patterns; note lookup tables and filter values implied by schema hints.",
2201
+ "Sketch join/filter/aggregation order considering table sizes, indexes, and stats.",
2202
+ "Generate precise, validated SQL that answers the question."
2203
+ ]
2204
+ })
2205
+ ];
2206
+ if (date === "strict") {
2207
+ baseTeachings.push(
2208
+ clarification({
2209
+ when: "The request targets time-based data without a date range.",
2210
+ ask: "Confirm the intended timeframe (e.g., last 30/90 days, YTD, specific year).",
2211
+ reason: "Prevents large scans and irrelevant results."
2212
+ })
2213
+ );
2214
+ } else {
2215
+ baseTeachings.push(
2216
+ hint(
2217
+ 'When a month, day, or time period is mentioned without a year (e.g., "in August", "on Monday"), assume ALL occurrences of that period in the data. Do not ask for year clarification.'
2218
+ )
2219
+ );
2220
+ }
2221
+ return baseTeachings;
2222
+ }
2223
+
2224
+ // packages/text2sql/src/lib/sql.ts
2225
+ var Text2Sql = class {
2226
+ #config;
2227
+ constructor(config) {
2228
+ this.#config = {
2229
+ adapter: config.adapter,
2230
+ history: config.history,
2231
+ instructions: [
2232
+ ...guidelines(config.teachingsOptions),
2233
+ ...config.instructions ?? []
2234
+ ],
2235
+ tools: config.tools ?? {},
2236
+ model: config.model,
2237
+ memory: config.memory,
2238
+ introspection: new FileCache("introspection-" + config.version)
2239
+ };
2240
+ }
2241
+ async explain(sql) {
2242
+ const { experimental_output } = await generate4(
2243
+ explainerAgent,
2244
+ [user4("Explain this SQL.")],
2245
+ { sql }
2246
+ );
2247
+ return experimental_output.explanation;
2248
+ }
2249
+ async toSql(input) {
2250
+ const introspection = await this.index();
2251
+ const result = await toSql({
2252
+ input,
2253
+ adapter: this.#config.adapter,
2254
+ introspection,
2255
+ instructions: this.#config.instructions,
2256
+ model: this.#config.model
2257
+ });
2258
+ return result.sql;
2259
+ }
2260
+ instruct(...dataset) {
2261
+ this.#config.instructions.push(...dataset);
2262
+ }
2263
+ async inspect(agent9) {
2264
+ const [grounding] = await Promise.all([this.index()]);
2265
+ const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
2266
+ (name) => name.startsWith("render_")
2267
+ );
2268
+ const allInstructions = [
2269
+ ...this.#config.instructions,
2270
+ guardrail({
2271
+ rule: "ALWAYS use `get_sample_rows` before writing queries that filter or compare against string columns.",
2272
+ reason: "Prevents SQL errors from wrong value formats.",
2273
+ action: "Target specific columns (e.g., get_sample_rows('table', ['status', 'type']))."
2274
+ }),
2275
+ ...renderToolNames.length ? [
2276
+ hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2277
+ styleGuide({
2278
+ prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
2279
+ always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
2280
+ })
2281
+ ] : []
2282
+ ];
2283
+ const tools6 = Object.keys({
2284
+ ...agent9.handoff.tools,
2285
+ ...this.#config.memory ? memoryTools : {},
2286
+ ...this.#config.tools
2287
+ });
2288
+ return {
2289
+ tools: tools6,
2290
+ prompt: agent9.instructions({
2291
+ introspection: grounding,
2292
+ teachings: toInstructions("instructions", ...allInstructions)
2293
+ })
2294
+ };
2295
+ }
2296
+ async index(options) {
2297
+ const cached = await this.#config.introspection.get();
2298
+ if (cached) {
2299
+ return cached;
2300
+ }
2301
+ const introspection = await this.#config.adapter.introspect();
2302
+ await this.#config.introspection.set(introspection);
2303
+ return introspection;
2304
+ }
2305
+ /**
2306
+ * Generate training data pairs using a producer factory.
2307
+ * The factory receives the configured adapter, so users don't need to pass it manually.
2308
+ *
2309
+ * @example
2310
+ * // Generate questions for existing SQL
2311
+ * const pairs = await text2sql.toPairs(
2312
+ * (adapter) => new SqlExtractor(sqls, adapter, { validateSql: true })
2313
+ * );
2314
+ *
2315
+ * @example
2316
+ * // Extract from chat history with validation
2317
+ * const pairs = await text2sql.toPairs(
2318
+ * (adapter) => new ValidatedProducer(
2319
+ * new MessageExtractor(messages),
2320
+ * adapter
2321
+ * )
2322
+ * );
2323
+ */
2324
+ async toPairs(factory) {
2325
+ const producer = factory(this.#config.adapter);
2326
+ return toPairs(producer);
2327
+ }
2328
+ // public async suggest() {
2329
+ // const [introspection, adapterInfo] = await Promise.all([
2330
+ // this.index(),
2331
+ // this.#config.adapter.introspect(),
2332
+ // ]);
2333
+ // const { experimental_output: output } = await generate(
2334
+ // suggestionsAgent,
2335
+ // [
2336
+ // user(
2337
+ // 'Suggest high-impact business questions and matching SQL queries for this database.',
2338
+ // ),
2339
+ // ],
2340
+ // {
2341
+ // },
2342
+ // );
2343
+ // return output.suggestions;
2344
+ // }
2345
+ async chat(messages, params) {
2346
+ const [introspection, userTeachables] = await Promise.all([
2347
+ this.index({ onProgress: console.log }),
2348
+ this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
2349
+ ]);
2350
+ const chat = await this.#config.history.upsertChat({
2351
+ id: params.chatId,
2352
+ userId: params.userId,
2353
+ title: "Chat " + params.chatId
2354
+ });
2355
+ const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
2356
+ (name) => name.startsWith("render_")
2357
+ );
2358
+ const instructions = [
2359
+ ...this.#config.instructions,
2360
+ guardrail({
2361
+ rule: "ALWAYS use `get_sample_rows` before writing queries that filter or compare against string columns.",
2362
+ reason: "Prevents SQL errors from wrong value formats.",
2363
+ action: "Target specific columns (e.g., get_sample_rows('table', ['status', 'type']))."
2364
+ }),
2365
+ ...renderToolNames.length ? [
2366
+ hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2367
+ styleGuide({
2368
+ prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
1198
2369
  always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
1199
2370
  })
1200
2371
  ] : []
1201
2372
  ];
2373
+ const originalMessage = [
2374
+ ...chat.messages.map((it) => it.content),
2375
+ ...messages
2376
+ ];
1202
2377
  const result = stream(
1203
2378
  t_a_g.clone({
1204
2379
  model: this.#config.model,
@@ -1208,7 +2383,7 @@ var Text2Sql = class {
1208
2383
  ...this.#config.tools
1209
2384
  }
1210
2385
  }),
1211
- [...chat.messages.map((it) => it.content), ...messages],
2386
+ originalMessage,
1212
2387
  {
1213
2388
  teachings: toInstructions(
1214
2389
  "instructions",
@@ -1242,46 +2417,385 @@ var Text2Sql = class {
1242
2417
  sendFinish: true,
1243
2418
  sendReasoning: true,
1244
2419
  sendSources: true,
1245
- originalMessages: messages,
2420
+ originalMessages: originalMessage,
1246
2421
  generateMessageId: generateId,
1247
- onFinish: async ({ messages: messages2 }) => {
1248
- const userMessage = messages2.at(-2);
1249
- const botMessage = messages2.at(-1);
1250
- if (!userMessage || !botMessage) {
1251
- throw new Error("Not implemented yet");
2422
+ onFinish: async ({ responseMessage, isContinuation }) => {
2423
+ const userMessage = messages.at(-1);
2424
+ if (!isContinuation && userMessage) {
2425
+ console.log(
2426
+ "Saving user message to history:",
2427
+ JSON.stringify(userMessage)
2428
+ );
2429
+ await this.#config.history.addMessage({
2430
+ id: v72(),
2431
+ chatId: params.chatId,
2432
+ role: userMessage.role,
2433
+ content: userMessage
2434
+ });
1252
2435
  }
1253
2436
  await this.#config.history.addMessage({
1254
2437
  id: v72(),
1255
2438
  chatId: params.chatId,
1256
- role: userMessage.role,
1257
- content: userMessage
2439
+ role: responseMessage.role,
2440
+ content: responseMessage
1258
2441
  });
2442
+ }
2443
+ });
2444
+ }
2445
+ /**
2446
+ * Chat1 - Combined tool, no peek.
2447
+ *
2448
+ * Uses a single `query_database` tool that:
2449
+ * 1. Takes a natural language question
2450
+ * 2. Internally calls toSql() to generate validated SQL
2451
+ * 3. Executes the SQL
2452
+ * 4. Returns both SQL and results
2453
+ *
2454
+ * The agent does NOT see the SQL before execution.
2455
+ */
2456
+ async chat1(messages, params) {
2457
+ const [introspection, userTeachables] = await Promise.all([
2458
+ this.index({ onProgress: console.log }),
2459
+ this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
2460
+ ]);
2461
+ const chat = await this.#config.history.upsertChat({
2462
+ id: params.chatId,
2463
+ userId: params.userId,
2464
+ title: "Chat " + params.chatId
2465
+ });
2466
+ const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
2467
+ (name) => name.startsWith("render_")
2468
+ );
2469
+ const instructions = [
2470
+ ...this.#config.instructions,
2471
+ ...renderToolNames.length ? [
2472
+ hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2473
+ styleGuide({
2474
+ prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
2475
+ always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
2476
+ })
2477
+ ] : []
2478
+ ];
2479
+ const originalMessage = [
2480
+ ...chat.messages.map((it) => it.content),
2481
+ ...messages
2482
+ ];
2483
+ const result = stream(
2484
+ chat1Agent.clone({
2485
+ model: this.#config.model,
2486
+ tools: {
2487
+ ...tools2,
2488
+ ...this.#config.memory ? memoryTools : {},
2489
+ ...this.#config.tools
2490
+ }
2491
+ }),
2492
+ originalMessage,
2493
+ {
2494
+ teachings: toInstructions(
2495
+ "instructions",
2496
+ persona({
2497
+ name: "Freya",
2498
+ role: "You are an expert SQL query generator, answering business questions with accurate queries.",
2499
+ tone: "Your tone should be concise and business-friendly."
2500
+ }),
2501
+ ...instructions,
2502
+ teachable("user_profile", ...userTeachables)
2503
+ ),
2504
+ adapter: this.#config.adapter,
2505
+ introspection,
2506
+ instructions: this.#config.instructions,
2507
+ memory: this.#config.memory,
2508
+ userId: params.userId
2509
+ }
2510
+ );
2511
+ return this.#createUIMessageStream(
2512
+ result,
2513
+ messages,
2514
+ params,
2515
+ originalMessage
2516
+ );
2517
+ }
2518
+ /**
2519
+ * Chat2 - Separate generate + execute tools (with peek).
2520
+ *
2521
+ * Uses two separate tools:
2522
+ * 1. `generate_sql` - Takes a question, returns validated SQL
2523
+ * 2. `execute_sql` - Takes SQL, executes it
2524
+ *
2525
+ * The agent sees the SQL before execution and can review/refine.
2526
+ */
2527
+ async chat2(messages, params) {
2528
+ const [introspection, userTeachables] = await Promise.all([
2529
+ this.index({ onProgress: console.log }),
2530
+ this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
2531
+ ]);
2532
+ const chat = await this.#config.history.upsertChat({
2533
+ id: params.chatId,
2534
+ userId: params.userId,
2535
+ title: "Chat " + params.chatId
2536
+ });
2537
+ const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
2538
+ (name) => name.startsWith("render_")
2539
+ );
2540
+ const instructions = [
2541
+ ...this.#config.instructions,
2542
+ ...renderToolNames.length ? [
2543
+ hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2544
+ styleGuide({
2545
+ prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
2546
+ always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
2547
+ })
2548
+ ] : []
2549
+ ];
2550
+ const originalMessage = [
2551
+ ...chat.messages.map((it) => it.content),
2552
+ ...messages
2553
+ ];
2554
+ const result = stream(
2555
+ chat2Agent.clone({
2556
+ model: this.#config.model,
2557
+ tools: {
2558
+ ...tools3,
2559
+ ...this.#config.memory ? memoryTools : {},
2560
+ ...this.#config.tools
2561
+ }
2562
+ }),
2563
+ originalMessage,
2564
+ {
2565
+ teachings: toInstructions(
2566
+ "instructions",
2567
+ persona({
2568
+ name: "Freya",
2569
+ role: "You are an expert SQL query generator, answering business questions with accurate queries.",
2570
+ tone: "Your tone should be concise and business-friendly."
2571
+ }),
2572
+ ...instructions,
2573
+ teachable("user_profile", ...userTeachables)
2574
+ ),
2575
+ adapter: this.#config.adapter,
2576
+ introspection,
2577
+ instructions: this.#config.instructions,
2578
+ memory: this.#config.memory,
2579
+ userId: params.userId
2580
+ }
2581
+ );
2582
+ return this.#createUIMessageStream(
2583
+ result,
2584
+ messages,
2585
+ params,
2586
+ originalMessage
2587
+ );
2588
+ }
2589
+ /**
2590
+ * Chat3 - Agent conversation/collaboration.
2591
+ *
2592
+ * Enables richer interaction where the SQL agent can:
2593
+ * - Surface confidence levels
2594
+ * - State assumptions
2595
+ * - Request clarification when uncertain
2596
+ */
2597
+ async chat3(messages, params) {
2598
+ const [introspection, userTeachables] = await Promise.all([
2599
+ this.index({ onProgress: console.log }),
2600
+ this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
2601
+ ]);
2602
+ const chat = await this.#config.history.upsertChat({
2603
+ id: params.chatId,
2604
+ userId: params.userId,
2605
+ title: "Chat " + params.chatId
2606
+ });
2607
+ const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
2608
+ (name) => name.startsWith("render_")
2609
+ );
2610
+ const instructions = [
2611
+ ...this.#config.instructions,
2612
+ ...renderToolNames.length ? [
2613
+ hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2614
+ styleGuide({
2615
+ prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
2616
+ always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
2617
+ })
2618
+ ] : []
2619
+ ];
2620
+ const originalMessage = [
2621
+ ...chat.messages.map((it) => it.content),
2622
+ ...messages
2623
+ ];
2624
+ const result = stream(
2625
+ chat3Agent.clone({
2626
+ model: this.#config.model,
2627
+ tools: {
2628
+ ...tools4,
2629
+ ...this.#config.memory ? memoryTools : {},
2630
+ ...this.#config.tools
2631
+ }
2632
+ }),
2633
+ originalMessage,
2634
+ {
2635
+ teachings: toInstructions(
2636
+ "instructions",
2637
+ persona({
2638
+ name: "Freya",
2639
+ role: "You are an expert SQL query generator, answering business questions with accurate queries.",
2640
+ tone: "Your tone should be concise and business-friendly."
2641
+ }),
2642
+ ...instructions,
2643
+ teachable("user_profile", ...userTeachables)
2644
+ ),
2645
+ adapter: this.#config.adapter,
2646
+ introspection,
2647
+ instructions: this.#config.instructions,
2648
+ memory: this.#config.memory,
2649
+ userId: params.userId
2650
+ }
2651
+ );
2652
+ return this.#createUIMessageStream(
2653
+ result,
2654
+ messages,
2655
+ params,
2656
+ originalMessage
2657
+ );
2658
+ }
2659
+ /**
2660
+ * Chat4 - Question decomposition approach.
2661
+ *
2662
+ * Breaks down questions into semantic components before SQL generation:
2663
+ * - entities: Key concepts mentioned
2664
+ * - filters: Filtering criteria
2665
+ * - aggregation: Type of aggregation
2666
+ * - breakdown: Semantic parts of the question
2667
+ *
2668
+ * This helps ensure all aspects of the question are addressed.
2669
+ */
2670
+ async chat4(messages, params) {
2671
+ const [introspection, userTeachables] = await Promise.all([
2672
+ this.index({ onProgress: console.log }),
2673
+ this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
2674
+ ]);
2675
+ const chat = await this.#config.history.upsertChat({
2676
+ id: params.chatId,
2677
+ userId: params.userId,
2678
+ title: "Chat " + params.chatId
2679
+ });
2680
+ const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
2681
+ (name) => name.startsWith("render_")
2682
+ );
2683
+ const instructions = [
2684
+ ...this.#config.instructions,
2685
+ ...renderToolNames.length ? [
2686
+ hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
2687
+ styleGuide({
2688
+ prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
2689
+ always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
2690
+ })
2691
+ ] : []
2692
+ ];
2693
+ const originalMessage = [
2694
+ ...chat.messages.map((it) => it.content),
2695
+ ...messages
2696
+ ];
2697
+ const result = stream(
2698
+ chat4Agent.clone({
2699
+ model: this.#config.model,
2700
+ tools: {
2701
+ ...tools5,
2702
+ ...this.#config.memory ? memoryTools : {},
2703
+ ...this.#config.tools
2704
+ }
2705
+ }),
2706
+ originalMessage,
2707
+ {
2708
+ teachings: toInstructions(
2709
+ "instructions",
2710
+ persona({
2711
+ name: "Freya",
2712
+ role: "You are an expert SQL query generator, answering business questions with accurate queries.",
2713
+ tone: "Your tone should be concise and business-friendly."
2714
+ }),
2715
+ ...instructions,
2716
+ teachable("user_profile", ...userTeachables)
2717
+ ),
2718
+ adapter: this.#config.adapter,
2719
+ introspection,
2720
+ instructions: this.#config.instructions,
2721
+ memory: this.#config.memory,
2722
+ userId: params.userId
2723
+ }
2724
+ );
2725
+ return this.#createUIMessageStream(
2726
+ result,
2727
+ messages,
2728
+ params,
2729
+ originalMessage
2730
+ );
2731
+ }
2732
+ /**
2733
+ * Helper to create UI message stream with common error handling and persistence.
2734
+ */
2735
+ #createUIMessageStream(result, messages, params, originalMessage) {
2736
+ return result.toUIMessageStream({
2737
+ onError: (error) => {
2738
+ if (NoSuchToolError.isInstance(error)) {
2739
+ return "The model tried to call an unknown tool.";
2740
+ } else if (InvalidToolInputError.isInstance(error)) {
2741
+ return "The model called a tool with invalid arguments.";
2742
+ } else if (ToolCallRepairError.isInstance(error)) {
2743
+ return "The model tried to call a tool with invalid arguments, but it was repaired.";
2744
+ } else {
2745
+ return "An unknown error occurred.";
2746
+ }
2747
+ },
2748
+ sendStart: true,
2749
+ sendFinish: true,
2750
+ sendReasoning: true,
2751
+ sendSources: true,
2752
+ originalMessages: originalMessage,
2753
+ generateMessageId: generateId,
2754
+ onFinish: async ({ responseMessage, isContinuation }) => {
2755
+ const userMessage = messages.at(-1);
2756
+ if (!isContinuation && userMessage) {
2757
+ console.log(
2758
+ "Saving user message to history:",
2759
+ JSON.stringify(userMessage)
2760
+ );
2761
+ await this.#config.history.addMessage({
2762
+ id: v72(),
2763
+ chatId: params.chatId,
2764
+ role: userMessage.role,
2765
+ content: userMessage
2766
+ });
2767
+ }
1259
2768
  await this.#config.history.addMessage({
1260
2769
  id: v72(),
1261
2770
  chatId: params.chatId,
1262
- role: botMessage.role,
1263
- content: botMessage
2771
+ role: responseMessage.role,
2772
+ content: responseMessage
1264
2773
  });
1265
2774
  }
1266
2775
  });
1267
2776
  }
1268
2777
  };
1269
-
1270
- // packages/text2sql/src/index.ts
1271
- if (import.meta.main) {
1272
- }
1273
2778
  export {
2779
+ Adapter,
2780
+ Checkpoint,
1274
2781
  FileCache,
1275
2782
  History,
1276
2783
  InMemoryHistory,
1277
2784
  InMemoryTeachablesStore,
1278
2785
  JsonCache,
2786
+ Point,
1279
2787
  SqliteHistory,
1280
2788
  SqliteTeachablesStore,
1281
2789
  TeachablesStore,
1282
2790
  Text2Sql,
2791
+ applyTablesFilter,
2792
+ filterRelationshipsByTables,
2793
+ filterTablesByName,
2794
+ getTablesWithRelated,
2795
+ guidelines,
2796
+ hashConfig,
2797
+ matchesFilter,
1283
2798
  memoryTools,
1284
- sqlQueryAgent,
1285
2799
  suggestionsAgent,
1286
2800
  t_a_g
1287
2801
  };