@deepagents/text2sql 0.3.0 → 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.
- package/README.md +167 -0
- package/dist/finetune/convert-to-gguf.d.ts +18 -0
- package/dist/finetune/convert-to-gguf.d.ts.map +1 -0
- package/dist/finetune/run-finetune.d.ts +23 -0
- package/dist/finetune/run-finetune.d.ts.map +1 -0
- package/dist/finetune/run-mlx.d.ts +22 -0
- package/dist/finetune/run-mlx.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1795 -284
- package/dist/index.js.map +4 -4
- package/dist/lib/adapters/adapter.d.ts +8 -3
- package/dist/lib/adapters/adapter.d.ts.map +1 -1
- package/dist/lib/adapters/{grounding.ticket.d.ts → groundings/abstract.grounding.d.ts} +2 -2
- package/dist/lib/adapters/groundings/abstract.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/column-stats.grounding.d.ts +1 -1
- package/dist/lib/adapters/groundings/column-stats.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/groundings/column-values.grounding.d.ts +76 -0
- package/dist/lib/adapters/groundings/column-values.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/constraint.grounding.d.ts +1 -1
- package/dist/lib/adapters/groundings/constraint.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/groundings/context.d.ts +1 -1
- package/dist/lib/adapters/groundings/context.d.ts.map +1 -1
- package/dist/lib/adapters/groundings/{grounding.d.ts → index.d.ts} +8 -5
- package/dist/lib/adapters/groundings/index.d.ts.map +1 -0
- package/dist/lib/adapters/groundings/{grounding.js → index.js} +411 -206
- package/dist/lib/adapters/groundings/index.js.map +7 -0
- package/dist/lib/adapters/groundings/indexes.grounding.d.ts +1 -1
- package/dist/lib/adapters/groundings/indexes.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/groundings/info.grounding.d.ts +1 -1
- package/dist/lib/adapters/groundings/info.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/groundings/report.grounding.d.ts +1 -1
- package/dist/lib/adapters/groundings/report.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/groundings/row-count.grounding.d.ts +1 -1
- package/dist/lib/adapters/groundings/row-count.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/groundings/table.grounding.d.ts +1 -1
- package/dist/lib/adapters/groundings/table.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/groundings/view.grounding.d.ts +1 -1
- package/dist/lib/adapters/groundings/view.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/column-stats.postgres.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/column-values.postgres.grounding.d.ts +17 -0
- package/dist/lib/adapters/postgres/column-values.postgres.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/postgres/index.d.ts +4 -4
- package/dist/lib/adapters/postgres/index.d.ts.map +1 -1
- package/dist/lib/adapters/postgres/index.js +239 -33
- package/dist/lib/adapters/postgres/index.js.map +4 -4
- package/dist/lib/adapters/postgres/postgres.d.ts +1 -0
- package/dist/lib/adapters/postgres/postgres.d.ts.map +1 -1
- package/dist/lib/adapters/sqlite/column-values.sqlite.grounding.d.ts +17 -0
- package/dist/lib/adapters/sqlite/column-values.sqlite.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlite/constraint.sqlite.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlite/index.d.ts +4 -4
- package/dist/lib/adapters/sqlite/index.d.ts.map +1 -1
- package/dist/lib/adapters/sqlite/index.js +218 -46
- package/dist/lib/adapters/sqlite/index.js.map +4 -4
- package/dist/lib/adapters/sqlite/sqlite.d.ts +1 -0
- package/dist/lib/adapters/sqlite/sqlite.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/column-values.sqlserver.grounding.d.ts +17 -0
- package/dist/lib/adapters/sqlserver/column-values.sqlserver.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/sqlserver/index.d.ts +4 -4
- package/dist/lib/adapters/sqlserver/index.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/index.js +185 -32
- package/dist/lib/adapters/sqlserver/index.js.map +4 -4
- package/dist/lib/adapters/sqlserver/sqlserver.d.ts +1 -0
- package/dist/lib/adapters/sqlserver/sqlserver.d.ts.map +1 -1
- package/dist/lib/agents/chat1.agent.d.ts +50 -0
- package/dist/lib/agents/chat1.agent.d.ts.map +1 -0
- package/dist/lib/agents/chat2.agent.d.ts +68 -0
- package/dist/lib/agents/chat2.agent.d.ts.map +1 -0
- package/dist/lib/agents/chat3.agent.d.ts +80 -0
- package/dist/lib/agents/chat3.agent.d.ts.map +1 -0
- package/dist/lib/agents/chat4.agent.d.ts +88 -0
- package/dist/lib/agents/chat4.agent.d.ts.map +1 -0
- package/dist/lib/agents/question.agent.d.ts +23 -0
- package/dist/lib/agents/question.agent.d.ts.map +1 -0
- package/dist/lib/agents/sql.agent.d.ts +62 -0
- package/dist/lib/agents/sql.agent.d.ts.map +1 -0
- package/dist/lib/agents/teachables.agent.d.ts +8 -9
- package/dist/lib/agents/teachables.agent.d.ts.map +1 -1
- package/dist/lib/agents/text2sql.agent.d.ts +0 -1
- package/dist/lib/agents/text2sql.agent.d.ts.map +1 -1
- package/dist/lib/checkpoint.d.ts +99 -0
- package/dist/lib/checkpoint.d.ts.map +1 -0
- package/dist/lib/instructions.js +50 -21
- package/dist/lib/instructions.js.map +2 -2
- package/dist/lib/sql.d.ts +83 -3
- package/dist/lib/sql.d.ts.map +1 -1
- package/dist/lib/syntheize.d.ts +2 -0
- package/dist/lib/syntheize.d.ts.map +1 -0
- package/dist/lib/synthesis/decorators/deduplicated-producer.d.ts +26 -0
- package/dist/lib/synthesis/decorators/deduplicated-producer.d.ts.map +1 -0
- package/dist/lib/synthesis/decorators/filtered-producer.d.ts +26 -0
- package/dist/lib/synthesis/decorators/filtered-producer.d.ts.map +1 -0
- package/dist/lib/synthesis/decorators/index.d.ts +7 -0
- package/dist/lib/synthesis/decorators/index.d.ts.map +1 -0
- package/dist/lib/synthesis/decorators/validated-producer.d.ts +33 -0
- package/dist/lib/synthesis/decorators/validated-producer.d.ts.map +1 -0
- package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts +76 -0
- package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts.map +1 -0
- package/dist/lib/synthesis/extractors/full-context-extractor.d.ts +25 -0
- package/dist/lib/synthesis/extractors/full-context-extractor.d.ts.map +1 -0
- package/dist/lib/synthesis/extractors/index.d.ts +8 -0
- package/dist/lib/synthesis/extractors/index.d.ts.map +1 -0
- package/dist/lib/synthesis/extractors/last-query-extractor.d.ts +30 -0
- package/dist/lib/synthesis/extractors/last-query-extractor.d.ts.map +1 -0
- package/dist/lib/synthesis/extractors/message-extractor.d.ts +27 -0
- package/dist/lib/synthesis/extractors/message-extractor.d.ts.map +1 -0
- package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts +48 -0
- package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts.map +1 -0
- package/dist/lib/synthesis/extractors/sql-extractor.d.ts +27 -0
- package/dist/lib/synthesis/extractors/sql-extractor.d.ts.map +1 -0
- package/dist/lib/synthesis/extractors/windowed-context-extractor.d.ts +30 -0
- package/dist/lib/synthesis/extractors/windowed-context-extractor.d.ts.map +1 -0
- package/dist/lib/synthesis/index.d.ts +6 -0
- package/dist/lib/synthesis/index.d.ts.map +1 -0
- package/dist/lib/synthesis/index.js +2069 -0
- package/dist/lib/synthesis/index.js.map +7 -0
- package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts +34 -0
- package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts.map +1 -0
- package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts +41 -0
- package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts.map +1 -0
- package/dist/lib/synthesis/synthesizers/index.d.ts +7 -0
- package/dist/lib/synthesis/synthesizers/index.d.ts.map +1 -0
- package/dist/lib/synthesis/synthesizers/persona-generator.d.ts +34 -0
- package/dist/lib/synthesis/synthesizers/persona-generator.d.ts.map +1 -0
- package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts +39 -0
- package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts.map +1 -0
- package/dist/lib/synthesis/synthesizers/styles.d.ts +8 -0
- package/dist/lib/synthesis/synthesizers/styles.d.ts.map +1 -0
- package/dist/lib/synthesis/synthesizers/teachings-generator.d.ts +32 -0
- package/dist/lib/synthesis/synthesizers/teachings-generator.d.ts.map +1 -0
- package/dist/lib/synthesis/types.d.ts +26 -0
- package/dist/lib/synthesis/types.d.ts.map +1 -0
- package/dist/lib/teach/teachables.d.ts +18 -3
- package/dist/lib/teach/teachables.d.ts.map +1 -1
- package/dist/lib/teach/teachings.d.ts +9 -2
- package/dist/lib/teach/teachings.d.ts.map +1 -1
- package/package.json +32 -15
- package/dist/lib/adapters/grounding.ticket.d.ts.map +0 -1
- package/dist/lib/adapters/groundings/grounding.d.ts.map +0 -1
- package/dist/lib/adapters/groundings/grounding.js.map +0 -7
- package/dist/lib/adapters/groundings/low-cardinality.grounding.d.ts +0 -35
- package/dist/lib/adapters/groundings/low-cardinality.grounding.d.ts.map +0 -1
- package/dist/lib/adapters/postgres/low-cardinality.postgres.grounding.d.ts +0 -14
- package/dist/lib/adapters/postgres/low-cardinality.postgres.grounding.d.ts.map +0 -1
- package/dist/lib/adapters/sqlite/low-cardinality.sqlite.grounding.d.ts +0 -14
- package/dist/lib/adapters/sqlite/low-cardinality.sqlite.grounding.d.ts.map +0 -1
- package/dist/lib/adapters/sqlserver/low-cardinality.sqlserver.grounding.d.ts +0 -14
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
}
|
|
@@ -566,14 +739,14 @@ var tools = {
|
|
|
566
739
|
limit: z2.number().min(1).max(10).default(3).optional().describe("Number of rows to sample (1-10, default 3).")
|
|
567
740
|
}),
|
|
568
741
|
execute: ({ tableName, columns, limit = 3 }, options) => {
|
|
569
|
-
const sanitize = (name) => name.replace(/[^a-zA-Z0-9_.]/g, "");
|
|
570
|
-
const safeTable = sanitize(tableName);
|
|
571
|
-
const columnList = columns?.length ? columns.map(sanitize).join(", ") : "*";
|
|
572
742
|
const safeLimit = Math.min(Math.max(1, limit), 10);
|
|
573
743
|
const state = toState(options);
|
|
574
|
-
|
|
575
|
-
|
|
744
|
+
const sql = state.adapter.buildSampleRowsQuery(
|
|
745
|
+
tableName,
|
|
746
|
+
columns,
|
|
747
|
+
safeLimit
|
|
576
748
|
);
|
|
749
|
+
return state.adapter.execute(sql);
|
|
577
750
|
}
|
|
578
751
|
}),
|
|
579
752
|
db_query: tool({
|
|
@@ -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/
|
|
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
|
|
736
|
-
this.path = path.join(tmpdir(), `text2sql-${
|
|
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 (
|
|
1057
|
+
if (existsSync2(this.path)) {
|
|
740
1058
|
return readFile(this.path, "utf-8");
|
|
741
1059
|
}
|
|
742
1060
|
return null;
|
|
@@ -961,240 +1279,1000 @@ 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/
|
|
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
|
|
1296
|
+
import { defaultSettingsMiddleware, wrapLanguageModel } from "ai";
|
|
972
1297
|
import z3 from "zod";
|
|
973
|
-
import { agent as agent3 } from "@deepagents/agent";
|
|
974
|
-
var
|
|
975
|
-
|
|
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
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
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 outputs to ~100 rows; aggregate or paginate unless the user explicitly confirms a larger pull."
|
|
1015
|
-
}),
|
|
1016
|
-
guardrail({
|
|
1017
|
-
rule: "Prevent cartesian or guesswork joins.",
|
|
1018
|
-
reason: "Protect correctness and performance.",
|
|
1019
|
-
action: "If join keys are missing or unclear, inspect relationships and ask for the intended join path before executing."
|
|
1020
|
-
}),
|
|
1021
|
-
clarification({
|
|
1022
|
-
when: "The request targets time-based data without a date range.",
|
|
1023
|
-
ask: "Confirm the intended timeframe (e.g., last 30/90 days, YTD, specific year).",
|
|
1024
|
-
reason: "Prevents large scans and irrelevant results."
|
|
1025
|
-
}),
|
|
1026
|
-
clarification({
|
|
1027
|
-
when: 'The request uses ambiguous scoring or ranking language (e.g., "top", "best", "active") without a metric.',
|
|
1028
|
-
ask: "Clarify the ranking metric or definition before writing the query.",
|
|
1029
|
-
reason: "Ensures the correct aggregation/ordering is used."
|
|
1030
|
-
}),
|
|
1031
|
-
workflow({
|
|
1032
|
-
task: "SQL generation plan",
|
|
1033
|
-
steps: [
|
|
1034
|
-
"Translate the question into SQL patterns (aggregation, segmentation, time range, ranking).",
|
|
1035
|
-
"Choose tables/relations that satisfy those patterns; note lookup tables and filter values implied by schema hints.",
|
|
1036
|
-
"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'])).",
|
|
1037
|
-
"Sketch join/filter/aggregation order considering table sizes, indexes, and stats.",
|
|
1038
|
-
"Draft SQL, validate via 'validate_query', then execute via 'db_query' with a short reasoning note."
|
|
1039
|
-
]
|
|
1040
|
-
}),
|
|
1041
|
-
styleGuide({
|
|
1042
|
-
prefer: "Summaries should be concise, business-friendly, highlight key comparisons, and add a short helpful follow-up when useful."
|
|
1043
|
-
}),
|
|
1044
|
-
// Tool usage constraints
|
|
1045
|
-
guardrail({
|
|
1046
|
-
rule: "Never output more than 100 rows of raw data.",
|
|
1047
|
-
action: "Use aggregation or pagination otherwise."
|
|
1048
|
-
}),
|
|
1049
|
-
guardrail({
|
|
1050
|
-
rule: "You must validate your query before final execution.",
|
|
1051
|
-
action: "Follow the pattern: Draft Query \u2192 `validate_query` \u2192 Fix (if needed) \u2192 `db_query`."
|
|
1052
|
-
}),
|
|
1053
|
-
guardrail({
|
|
1054
|
-
rule: "ALWAYS use `get_sample_rows` before writing queries that filter or compare against string columns.",
|
|
1055
|
-
reason: "Prevents SQL errors from wrong value formats.",
|
|
1056
|
-
action: "Target specific columns (e.g., get_sample_rows('table', ['status', 'type']))."
|
|
1057
|
-
}),
|
|
1058
|
-
guardrail({
|
|
1059
|
-
rule: "Do not call `db_query` without first producing and validating a SQL snippet.",
|
|
1060
|
-
action: "First produce the query string, then validate."
|
|
1061
|
-
}),
|
|
1062
|
-
hint(
|
|
1063
|
-
"Use the `scratchpad` tool for strategic reflection during SQL query generation."
|
|
1064
|
-
)
|
|
1065
|
-
];
|
|
1066
|
-
|
|
1067
|
-
// packages/text2sql/src/lib/sql.ts
|
|
1068
|
-
var Text2Sql = class {
|
|
1069
|
-
#config;
|
|
1070
|
-
constructor(config) {
|
|
1071
|
-
this.#config = {
|
|
1072
|
-
adapter: config.adapter,
|
|
1073
|
-
briefCache: new FileCache("brief-" + config.version),
|
|
1074
|
-
history: config.history,
|
|
1075
|
-
instructions: [...teachings_default, ...config.instructions ?? []],
|
|
1076
|
-
tools: config.tools ?? {},
|
|
1077
|
-
model: config.model,
|
|
1078
|
-
memory: config.memory,
|
|
1079
|
-
introspection: new FileCache("introspection-" + config.version)
|
|
1080
|
-
};
|
|
1081
|
-
}
|
|
1082
|
-
async explain(sql) {
|
|
1083
|
-
const { experimental_output } = await generate(
|
|
1084
|
-
explainerAgent,
|
|
1085
|
-
[user("Explain this SQL.")],
|
|
1086
|
-
{ sql }
|
|
1087
|
-
);
|
|
1088
|
-
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
|
+
`;
|
|
1089
1320
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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,
|
|
1101
1353
|
{
|
|
1102
1354
|
teachings: toInstructions(
|
|
1103
1355
|
"instructions",
|
|
1104
1356
|
persona({
|
|
1105
1357
|
name: "Freya",
|
|
1106
|
-
role: "You are an expert SQL query generator
|
|
1107
|
-
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."
|
|
1108
1359
|
}),
|
|
1109
|
-
...
|
|
1360
|
+
...instructions
|
|
1110
1361
|
),
|
|
1111
|
-
adapter: this.#config.adapter,
|
|
1112
1362
|
introspection
|
|
1113
1363
|
}
|
|
1114
1364
|
);
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
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;
|
|
1119
1377
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
];
|
|
1135
|
-
const tools2 = Object.keys({
|
|
1136
|
-
...agent4.handoff.tools,
|
|
1137
|
-
...this.#config.memory ? memoryTools : {},
|
|
1138
|
-
...this.#config.tools
|
|
1139
|
-
});
|
|
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) {
|
|
1140
1392
|
return {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
teachings: toInstructions(
|
|
1145
|
-
"instructions",
|
|
1146
|
-
persona({
|
|
1147
|
-
name: "Freya",
|
|
1148
|
-
role: "You are an expert SQL query generator, answering business questions with accurate queries.",
|
|
1149
|
-
tone: "Your tone should be concise and business-friendly."
|
|
1150
|
-
}),
|
|
1151
|
-
...allInstructions
|
|
1152
|
-
)
|
|
1153
|
-
})
|
|
1393
|
+
ok: false,
|
|
1394
|
+
error: result.error,
|
|
1395
|
+
isUnanswerable: result.isUnanswerable
|
|
1154
1396
|
};
|
|
1155
1397
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
return cached;
|
|
1160
|
-
}
|
|
1161
|
-
const introspection = await this.#config.adapter.introspect();
|
|
1162
|
-
await this.#config.introspection.set(introspection);
|
|
1163
|
-
return introspection;
|
|
1398
|
+
const validationError = await options.adapter.validate(result.sql);
|
|
1399
|
+
if (validationError) {
|
|
1400
|
+
return { ok: false, error: validationError };
|
|
1164
1401
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
// ],
|
|
1177
|
-
// {
|
|
1178
|
-
// },
|
|
1179
|
-
// );
|
|
1180
|
-
// return output.suggestions;
|
|
1181
|
-
// }
|
|
1182
|
-
async chat(messages, params) {
|
|
1183
|
-
const [introspection, userTeachables] = await Promise.all([
|
|
1184
|
-
this.index({ onProgress: console.log }),
|
|
1185
|
-
this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
|
|
1186
|
-
]);
|
|
1187
|
-
const chat = await this.#config.history.upsertChat({
|
|
1188
|
-
id: params.chatId,
|
|
1189
|
-
userId: params.userId,
|
|
1190
|
-
title: "Chat " + params.chatId
|
|
1191
|
-
});
|
|
1192
|
-
const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
|
|
1193
|
-
(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)
|
|
1194
1413
|
);
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
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 ? [
|
|
1198
2276
|
hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
|
|
1199
2277
|
styleGuide({
|
|
1200
2278
|
prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
|
|
@@ -1202,6 +2280,100 @@ var Text2Sql = class {
|
|
|
1202
2280
|
})
|
|
1203
2281
|
] : []
|
|
1204
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",
|
|
2369
|
+
always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
|
|
2370
|
+
})
|
|
2371
|
+
] : []
|
|
2372
|
+
];
|
|
2373
|
+
const originalMessage = [
|
|
2374
|
+
...chat.messages.map((it) => it.content),
|
|
2375
|
+
...messages
|
|
2376
|
+
];
|
|
1205
2377
|
const result = stream(
|
|
1206
2378
|
t_a_g.clone({
|
|
1207
2379
|
model: this.#config.model,
|
|
@@ -1211,7 +2383,7 @@ var Text2Sql = class {
|
|
|
1211
2383
|
...this.#config.tools
|
|
1212
2384
|
}
|
|
1213
2385
|
}),
|
|
1214
|
-
|
|
2386
|
+
originalMessage,
|
|
1215
2387
|
{
|
|
1216
2388
|
teachings: toInstructions(
|
|
1217
2389
|
"instructions",
|
|
@@ -1245,46 +2417,385 @@ var Text2Sql = class {
|
|
|
1245
2417
|
sendFinish: true,
|
|
1246
2418
|
sendReasoning: true,
|
|
1247
2419
|
sendSources: true,
|
|
1248
|
-
originalMessages:
|
|
2420
|
+
originalMessages: originalMessage,
|
|
1249
2421
|
generateMessageId: generateId,
|
|
1250
|
-
onFinish: async ({
|
|
1251
|
-
const userMessage =
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
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
|
+
});
|
|
1255
2435
|
}
|
|
1256
2436
|
await this.#config.history.addMessage({
|
|
1257
2437
|
id: v72(),
|
|
1258
2438
|
chatId: params.chatId,
|
|
1259
|
-
role:
|
|
1260
|
-
content:
|
|
2439
|
+
role: responseMessage.role,
|
|
2440
|
+
content: responseMessage
|
|
1261
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
|
+
}
|
|
1262
2768
|
await this.#config.history.addMessage({
|
|
1263
2769
|
id: v72(),
|
|
1264
2770
|
chatId: params.chatId,
|
|
1265
|
-
role:
|
|
1266
|
-
content:
|
|
2771
|
+
role: responseMessage.role,
|
|
2772
|
+
content: responseMessage
|
|
1267
2773
|
});
|
|
1268
2774
|
}
|
|
1269
2775
|
});
|
|
1270
2776
|
}
|
|
1271
2777
|
};
|
|
1272
|
-
|
|
1273
|
-
// packages/text2sql/src/index.ts
|
|
1274
|
-
if (import.meta.main) {
|
|
1275
|
-
}
|
|
1276
2778
|
export {
|
|
2779
|
+
Adapter,
|
|
2780
|
+
Checkpoint,
|
|
1277
2781
|
FileCache,
|
|
1278
2782
|
History,
|
|
1279
2783
|
InMemoryHistory,
|
|
1280
2784
|
InMemoryTeachablesStore,
|
|
1281
2785
|
JsonCache,
|
|
2786
|
+
Point,
|
|
1282
2787
|
SqliteHistory,
|
|
1283
2788
|
SqliteTeachablesStore,
|
|
1284
2789
|
TeachablesStore,
|
|
1285
2790
|
Text2Sql,
|
|
2791
|
+
applyTablesFilter,
|
|
2792
|
+
filterRelationshipsByTables,
|
|
2793
|
+
filterTablesByName,
|
|
2794
|
+
getTablesWithRelated,
|
|
2795
|
+
guidelines,
|
|
2796
|
+
hashConfig,
|
|
2797
|
+
matchesFilter,
|
|
1286
2798
|
memoryTools,
|
|
1287
|
-
sqlQueryAgent,
|
|
1288
2799
|
suggestionsAgent,
|
|
1289
2800
|
t_a_g
|
|
1290
2801
|
};
|