@deepagents/text2sql 0.3.1 → 0.7.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 +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2500 -318
- package/dist/index.js.map +4 -4
- package/dist/lib/adapters/adapter.d.ts +3 -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/mysql/column-stats.mysql.grounding.d.ts +14 -0
- package/dist/lib/adapters/mysql/column-stats.mysql.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/mysql/column-values.mysql.grounding.d.ts +22 -0
- package/dist/lib/adapters/mysql/column-values.mysql.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/mysql/constraint.mysql.grounding.d.ts +13 -0
- package/dist/lib/adapters/mysql/constraint.mysql.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/mysql/index.d.ts +44 -0
- package/dist/lib/adapters/mysql/index.d.ts.map +1 -0
- package/dist/lib/adapters/mysql/indexes.mysql.grounding.d.ts +13 -0
- package/dist/lib/adapters/mysql/indexes.mysql.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/mysql/info.mysql.grounding.d.ts +13 -0
- package/dist/lib/adapters/mysql/info.mysql.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/mysql/mysql.d.ts +33 -0
- package/dist/lib/adapters/mysql/mysql.d.ts.map +1 -0
- package/dist/lib/adapters/mysql/row-count.mysql.grounding.d.ts +13 -0
- package/dist/lib/adapters/mysql/row-count.mysql.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/mysql/table.mysql.grounding.d.ts +21 -0
- package/dist/lib/adapters/mysql/table.mysql.grounding.d.ts.map +1 -0
- package/dist/lib/adapters/mysql/view.mysql.grounding.d.ts +18 -0
- package/dist/lib/adapters/mysql/view.mysql.grounding.d.ts.map +1 -0
- 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 +233 -33
- package/dist/lib/adapters/postgres/index.js.map +4 -4
- 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 +214 -46
- package/dist/lib/adapters/sqlite/index.js.map +4 -4
- 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 +179 -32
- package/dist/lib/adapters/sqlserver/index.js.map +4 -4
- package/dist/lib/agents/bi.agent.d.ts +14 -0
- package/dist/lib/agents/bi.agent.d.ts.map +1 -0
- 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/developer.agent.d.ts +31 -0
- package/dist/lib/agents/developer.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 +44 -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 +125 -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 +2172 -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 +38 -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,64 +1,206 @@
|
|
|
1
|
-
// packages/text2sql/src/lib/
|
|
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
|
+
|
|
152
|
+
// packages/text2sql/src/lib/agents/developer.agent.ts
|
|
153
|
+
import { groq as groq3 } from "@ai-sdk/groq";
|
|
154
|
+
import { tool } from "ai";
|
|
155
|
+
import dedent2 from "dedent";
|
|
156
|
+
import z3 from "zod";
|
|
157
|
+
import { agent as agent3, generate as generate2, toState } from "@deepagents/agent";
|
|
158
|
+
import { scratchpad_tool } from "@deepagents/toolbox";
|
|
159
|
+
|
|
160
|
+
// packages/text2sql/src/lib/agents/explainer.agent.ts
|
|
2
161
|
import { groq } from "@ai-sdk/groq";
|
|
3
162
|
import dedent from "dedent";
|
|
4
163
|
import z from "zod";
|
|
5
|
-
import { agent
|
|
6
|
-
var
|
|
7
|
-
name: "
|
|
164
|
+
import { agent } from "@deepagents/agent";
|
|
165
|
+
var explainerAgent = agent({
|
|
166
|
+
name: "explainer",
|
|
8
167
|
model: groq("openai/gpt-oss-20b"),
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
sql: z.string().describe("The SQL statement needed to answer the question."),
|
|
14
|
-
businessValue: z.string().describe("Why the question matters to stakeholders.")
|
|
15
|
-
})
|
|
16
|
-
).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
|
|
17
|
-
}),
|
|
18
|
-
prompt: (state) => {
|
|
19
|
-
return dedent`
|
|
20
|
-
${thirdPersonPrompt()}
|
|
21
|
-
|
|
22
|
-
<identity>
|
|
23
|
-
You are a senior analytics strategist who proposes ambitious business questions
|
|
24
|
-
and drafts the SQL needed to answer them. You specialize in identifying ideas
|
|
25
|
-
that combine multiple tables, apply segmentation or time analysis, and surface
|
|
26
|
-
metrics that drive executive decisions.
|
|
27
|
-
</identity>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<instructions>
|
|
31
|
-
- Recommend one or two UNIQUE questions that go beyond simple counts or listings.
|
|
32
|
-
- Favor questions that require joins, aggregates, time comparisons, cohort analysis,
|
|
33
|
-
or window functions.
|
|
34
|
-
- For each question, explain the business reason stakeholders care about it.
|
|
35
|
-
- Provide the complete SQL query that could answer the question in the given schema.
|
|
36
|
-
- Keep result sets scoped with LIMIT clauses (max 50 rows) when returning raw rows.
|
|
37
|
-
- Ensure table/column names match the provided schema exactly.
|
|
38
|
-
- Use columns marked [LowCardinality: ...] to identify meaningful categorical filters or segmentations.
|
|
39
|
-
- Leverage table [rows / size] hints to determine whether to aggregate (large tables) or inspect detailed data (tiny tables).
|
|
40
|
-
- Reference PK/Indexed annotations and the Indexes list to recommend queries that use efficient join/filter paths.
|
|
41
|
-
- Column annotations may expose ranges/null percentages—use them to suggest realistic thresholds or quality checks.
|
|
42
|
-
- Consult <relationship_examples> to anchor your recommendations in the actual join paths between tables.
|
|
43
|
-
- Output only information grounded in the schema/context provided.
|
|
44
|
-
</instructions>
|
|
168
|
+
prompt: (state) => dedent`
|
|
169
|
+
You are an expert SQL tutor.
|
|
170
|
+
Explain the following SQL query in plain English to a non-technical user.
|
|
171
|
+
Focus on the intent and logic, not the syntax.
|
|
45
172
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
173
|
+
<sql>
|
|
174
|
+
${state?.sql}
|
|
175
|
+
</sql>
|
|
176
|
+
`,
|
|
177
|
+
output: z.object({
|
|
178
|
+
explanation: z.string().describe("The explanation of the SQL query.")
|
|
179
|
+
})
|
|
51
180
|
});
|
|
52
181
|
|
|
53
|
-
// packages/text2sql/src/lib/agents/
|
|
182
|
+
// packages/text2sql/src/lib/agents/sql.agent.ts
|
|
54
183
|
import { groq as groq2 } from "@ai-sdk/groq";
|
|
55
|
-
import {
|
|
184
|
+
import {
|
|
185
|
+
APICallError,
|
|
186
|
+
JSONParseError,
|
|
187
|
+
NoContentGeneratedError,
|
|
188
|
+
NoObjectGeneratedError,
|
|
189
|
+
NoOutputGeneratedError,
|
|
190
|
+
TypeValidationError,
|
|
191
|
+
defaultSettingsMiddleware,
|
|
192
|
+
wrapLanguageModel
|
|
193
|
+
} from "ai";
|
|
194
|
+
import { Console } from "node:console";
|
|
195
|
+
import { createWriteStream } from "node:fs";
|
|
196
|
+
import pRetry from "p-retry";
|
|
56
197
|
import z2 from "zod";
|
|
57
198
|
import {
|
|
58
199
|
agent as agent2,
|
|
59
|
-
|
|
200
|
+
generate,
|
|
201
|
+
toOutput,
|
|
202
|
+
user
|
|
60
203
|
} from "@deepagents/agent";
|
|
61
|
-
import { scratchpad_tool } from "@deepagents/toolbox";
|
|
62
204
|
|
|
63
205
|
// packages/text2sql/src/lib/teach/xml.ts
|
|
64
206
|
function wrapBlock(tag, children) {
|
|
@@ -106,20 +248,23 @@ function escapeXml(value) {
|
|
|
106
248
|
function term(name, definition) {
|
|
107
249
|
return {
|
|
108
250
|
type: "term",
|
|
109
|
-
|
|
251
|
+
encode: () => ({ type: "term", name, definition }),
|
|
252
|
+
decode: () => wrapBlock("term", [leaf("name", name), leaf("definition", definition)])
|
|
110
253
|
};
|
|
111
254
|
}
|
|
112
255
|
function hint(text) {
|
|
113
256
|
return {
|
|
114
257
|
type: "hint",
|
|
115
|
-
|
|
258
|
+
encode: () => ({ type: "hint", text }),
|
|
259
|
+
decode: () => leaf("hint", text)
|
|
116
260
|
};
|
|
117
261
|
}
|
|
118
262
|
function guardrail(input) {
|
|
119
263
|
const { rule, reason, action } = input;
|
|
120
264
|
return {
|
|
121
265
|
type: "guardrail",
|
|
122
|
-
|
|
266
|
+
encode: () => ({ type: "guardrail", rule, reason, action }),
|
|
267
|
+
decode: () => wrapBlock("guardrail", [
|
|
123
268
|
leaf("rule", rule),
|
|
124
269
|
reason ? leaf("reason", reason) : "",
|
|
125
270
|
action ? leaf("action", action) : ""
|
|
@@ -130,7 +275,8 @@ function explain(input) {
|
|
|
130
275
|
const { concept, explanation, therefore } = input;
|
|
131
276
|
return {
|
|
132
277
|
type: "explain",
|
|
133
|
-
|
|
278
|
+
encode: () => ({ type: "explain", concept, explanation, therefore }),
|
|
279
|
+
decode: () => wrapBlock("explanation", [
|
|
134
280
|
leaf("concept", concept),
|
|
135
281
|
leaf("details", explanation),
|
|
136
282
|
therefore ? leaf("therefore", therefore) : ""
|
|
@@ -141,7 +287,8 @@ function example(input) {
|
|
|
141
287
|
const { question, answer, note } = input;
|
|
142
288
|
return {
|
|
143
289
|
type: "example",
|
|
144
|
-
|
|
290
|
+
encode: () => ({ type: "example", question, answer, note }),
|
|
291
|
+
decode: () => wrapBlock("example", [
|
|
145
292
|
leaf("question", question),
|
|
146
293
|
leaf("answer", answer),
|
|
147
294
|
note ? leaf("note", note) : ""
|
|
@@ -152,7 +299,8 @@ function clarification(input) {
|
|
|
152
299
|
const { when, ask, reason } = input;
|
|
153
300
|
return {
|
|
154
301
|
type: "clarification",
|
|
155
|
-
|
|
302
|
+
encode: () => ({ type: "clarification", when, ask, reason }),
|
|
303
|
+
decode: () => wrapBlock("clarification", [
|
|
156
304
|
leaf("when", when),
|
|
157
305
|
leaf("ask", ask),
|
|
158
306
|
leaf("reason", reason)
|
|
@@ -163,7 +311,8 @@ function workflow(input) {
|
|
|
163
311
|
const { task, steps, triggers, notes } = input;
|
|
164
312
|
return {
|
|
165
313
|
type: "workflow",
|
|
166
|
-
|
|
314
|
+
encode: () => ({ type: "workflow", task, steps, triggers, notes }),
|
|
315
|
+
decode: () => wrapBlock("workflow", [
|
|
167
316
|
leaf("task", task),
|
|
168
317
|
triggers?.length ? list("triggers", triggers, "trigger") : "",
|
|
169
318
|
list("steps", steps, "step"),
|
|
@@ -175,7 +324,8 @@ function quirk(input) {
|
|
|
175
324
|
const { issue, workaround } = input;
|
|
176
325
|
return {
|
|
177
326
|
type: "quirk",
|
|
178
|
-
|
|
327
|
+
encode: () => ({ type: "quirk", issue, workaround }),
|
|
328
|
+
decode: () => wrapBlock("quirk", [
|
|
179
329
|
leaf("issue", issue),
|
|
180
330
|
leaf("workaround", workaround)
|
|
181
331
|
])
|
|
@@ -185,7 +335,8 @@ function styleGuide(input) {
|
|
|
185
335
|
const { prefer, never, always } = input;
|
|
186
336
|
return {
|
|
187
337
|
type: "styleGuide",
|
|
188
|
-
|
|
338
|
+
encode: () => ({ type: "styleGuide", prefer, never, always }),
|
|
339
|
+
decode: () => wrapBlock("style_guide", [
|
|
189
340
|
leaf("prefer", prefer),
|
|
190
341
|
always ? leaf("always", always) : "",
|
|
191
342
|
never ? leaf("never", never) : ""
|
|
@@ -196,7 +347,15 @@ function analogy(input) {
|
|
|
196
347
|
const { concept, relationship, insight, therefore, pitfall } = input;
|
|
197
348
|
return {
|
|
198
349
|
type: "analogy",
|
|
199
|
-
|
|
350
|
+
encode: () => ({
|
|
351
|
+
type: "analogy",
|
|
352
|
+
concept,
|
|
353
|
+
relationship,
|
|
354
|
+
insight,
|
|
355
|
+
therefore,
|
|
356
|
+
pitfall
|
|
357
|
+
}),
|
|
358
|
+
decode: () => wrapBlock("analogy", [
|
|
200
359
|
list("concepts", concept, "concept"),
|
|
201
360
|
leaf("relationship", relationship),
|
|
202
361
|
insight ? leaf("insight", insight) : "",
|
|
@@ -208,7 +367,8 @@ function analogy(input) {
|
|
|
208
367
|
function glossary(entries) {
|
|
209
368
|
return {
|
|
210
369
|
type: "glossary",
|
|
211
|
-
|
|
370
|
+
encode: () => ({ type: "glossary", entries }),
|
|
371
|
+
decode: () => wrapBlock(
|
|
212
372
|
"glossary",
|
|
213
373
|
Object.entries(entries).map(
|
|
214
374
|
([term2, sql]) => wrapBlock("entry", [leaf("term", term2), leaf("sql", sql)])
|
|
@@ -220,7 +380,8 @@ function identity(input) {
|
|
|
220
380
|
const { name, role } = input;
|
|
221
381
|
return {
|
|
222
382
|
type: "identity",
|
|
223
|
-
|
|
383
|
+
encode: () => ({ type: "identity", name, role }),
|
|
384
|
+
decode: () => wrapBlock("identity", [
|
|
224
385
|
name ? leaf("name", name) : "",
|
|
225
386
|
role ? leaf("role", role) : ""
|
|
226
387
|
])
|
|
@@ -230,35 +391,40 @@ function persona(input) {
|
|
|
230
391
|
const { name, role, tone } = input;
|
|
231
392
|
return {
|
|
232
393
|
type: "persona",
|
|
233
|
-
|
|
394
|
+
encode: () => ({ type: "persona", name, role, tone: tone ?? "" }),
|
|
395
|
+
decode: () => wrapBlock("persona", [
|
|
234
396
|
leaf("name", name),
|
|
235
397
|
leaf("role", role),
|
|
236
|
-
leaf("tone", tone)
|
|
398
|
+
tone ? leaf("tone", tone) : ""
|
|
237
399
|
])
|
|
238
400
|
};
|
|
239
401
|
}
|
|
240
402
|
function alias(termName, meaning) {
|
|
241
403
|
return {
|
|
242
404
|
type: "alias",
|
|
243
|
-
|
|
405
|
+
encode: () => ({ type: "alias", term: termName, meaning }),
|
|
406
|
+
decode: () => wrapBlock("alias", [leaf("term", termName), leaf("meaning", meaning)])
|
|
244
407
|
};
|
|
245
408
|
}
|
|
246
409
|
function preference(aspect, value) {
|
|
247
410
|
return {
|
|
248
411
|
type: "preference",
|
|
249
|
-
|
|
412
|
+
encode: () => ({ type: "preference", aspect, value }),
|
|
413
|
+
decode: () => wrapBlock("preference", [leaf("aspect", aspect), leaf("value", value)])
|
|
250
414
|
};
|
|
251
415
|
}
|
|
252
416
|
function context(description) {
|
|
253
417
|
return {
|
|
254
418
|
type: "context",
|
|
255
|
-
|
|
419
|
+
encode: () => ({ type: "context", description }),
|
|
420
|
+
decode: () => leaf("context", description)
|
|
256
421
|
};
|
|
257
422
|
}
|
|
258
423
|
function correction(subject, clarification2) {
|
|
259
424
|
return {
|
|
260
425
|
type: "correction",
|
|
261
|
-
|
|
426
|
+
encode: () => ({ type: "correction", subject, clarification: clarification2 }),
|
|
427
|
+
decode: () => wrapBlock("correction", [
|
|
262
428
|
leaf("subject", subject),
|
|
263
429
|
leaf("clarification", clarification2)
|
|
264
430
|
])
|
|
@@ -267,7 +433,8 @@ function correction(subject, clarification2) {
|
|
|
267
433
|
function teachable(tag, ...teachables) {
|
|
268
434
|
return {
|
|
269
435
|
type: "user_profile",
|
|
270
|
-
|
|
436
|
+
encode: () => teachables[0]?.encode() ?? { type: "context", description: "" },
|
|
437
|
+
decode: () => toInstructions(tag, ...teachables)
|
|
271
438
|
};
|
|
272
439
|
}
|
|
273
440
|
function toInstructions(tag, ...teachables) {
|
|
@@ -286,7 +453,7 @@ function toInstructions(tag, ...teachables) {
|
|
|
286
453
|
if (!items?.length) {
|
|
287
454
|
return "";
|
|
288
455
|
}
|
|
289
|
-
const renderedItems = items.map((item) => item.
|
|
456
|
+
const renderedItems = items.map((item) => item.decode().trim()).filter(Boolean).map((item) => indentBlock(item, 2)).join("\n");
|
|
290
457
|
if (!renderedItems.length) {
|
|
291
458
|
return "";
|
|
292
459
|
}
|
|
@@ -298,7 +465,7 @@ ${renderedItems}
|
|
|
298
465
|
if (definedTypes.has(type)) {
|
|
299
466
|
continue;
|
|
300
467
|
}
|
|
301
|
-
const renderedItems = items.map((item) => item.
|
|
468
|
+
const renderedItems = items.map((item) => item.decode().trim()).filter(Boolean).map((item) => indentBlock(item, 2)).join("\n");
|
|
302
469
|
if (renderedItems.length) {
|
|
303
470
|
sections.push(renderedItems);
|
|
304
471
|
}
|
|
@@ -408,6 +575,384 @@ function toTeachables(generated) {
|
|
|
408
575
|
});
|
|
409
576
|
}
|
|
410
577
|
|
|
578
|
+
// packages/text2sql/src/lib/agents/sql.agent.ts
|
|
579
|
+
var logger = new Console({
|
|
580
|
+
stdout: createWriteStream("./sql-agent.log", { flags: "a" }),
|
|
581
|
+
stderr: createWriteStream("./sql-agent-error.log", { flags: "a" }),
|
|
582
|
+
inspectOptions: { depth: null }
|
|
583
|
+
});
|
|
584
|
+
var RETRY_TEMPERATURES = [0, 0.2, 0.3];
|
|
585
|
+
var sqlQueryAgent = agent2({
|
|
586
|
+
name: "text2sql",
|
|
587
|
+
model: groq2("openai/gpt-oss-20b"),
|
|
588
|
+
logging: process.env.AGENT_LOGGING === "true",
|
|
589
|
+
output: z2.union([
|
|
590
|
+
z2.object({
|
|
591
|
+
sql: z2.string().describe("The SQL query that answers the question"),
|
|
592
|
+
reasoning: z2.string().optional().describe("The reasoning steps taken to generate the SQL")
|
|
593
|
+
}),
|
|
594
|
+
z2.object({
|
|
595
|
+
error: z2.string().describe(
|
|
596
|
+
"Error message explaining why the question cannot be answered with the given schema"
|
|
597
|
+
)
|
|
598
|
+
})
|
|
599
|
+
]),
|
|
600
|
+
prompt: (state) => {
|
|
601
|
+
return `
|
|
602
|
+
${state?.teachings || ""}
|
|
603
|
+
${state?.introspection || ""}
|
|
604
|
+
`;
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
function extractSql(output) {
|
|
608
|
+
const match = output.match(/```sql\n?([\s\S]*?)```/);
|
|
609
|
+
return match ? match[1].trim() : output.trim();
|
|
610
|
+
}
|
|
611
|
+
var marker = Symbol("SQLValidationError");
|
|
612
|
+
var SQLValidationError = class _SQLValidationError extends Error {
|
|
613
|
+
[marker];
|
|
614
|
+
constructor(message) {
|
|
615
|
+
super(message);
|
|
616
|
+
this.name = "SQLValidationError";
|
|
617
|
+
this[marker] = true;
|
|
618
|
+
}
|
|
619
|
+
static isInstance(error) {
|
|
620
|
+
return error instanceof _SQLValidationError && error[marker] === true;
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
|
|
624
|
+
constructor(message) {
|
|
625
|
+
super(message);
|
|
626
|
+
this.name = "UnanswerableSQLError";
|
|
627
|
+
}
|
|
628
|
+
static isInstance(error) {
|
|
629
|
+
return error instanceof _UnanswerableSQLError;
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
async function toSql(options) {
|
|
633
|
+
const { maxRetries = 3 } = options;
|
|
634
|
+
return withRetry(
|
|
635
|
+
async (attemptNumber, errors, attempts) => {
|
|
636
|
+
const agentInstance = sqlQueryAgent.clone({
|
|
637
|
+
model: wrapLanguageModel({
|
|
638
|
+
model: options.model ?? sqlQueryAgent.model,
|
|
639
|
+
middleware: defaultSettingsMiddleware({
|
|
640
|
+
settings: {
|
|
641
|
+
temperature: RETRY_TEMPERATURES[attemptNumber - 1] ?? 0.3,
|
|
642
|
+
topP: 1
|
|
643
|
+
}
|
|
644
|
+
})
|
|
645
|
+
})
|
|
646
|
+
});
|
|
647
|
+
const messages = errors.length ? [
|
|
648
|
+
user(options.input),
|
|
649
|
+
user(
|
|
650
|
+
`<validation_error>Your previous SQL query had the following error: ${errors.at(-1)?.message}. Please fix the query.</validation_error>`
|
|
651
|
+
)
|
|
652
|
+
] : [user(options.input)];
|
|
653
|
+
const output = await toOutput(
|
|
654
|
+
generate(agentInstance, messages, {
|
|
655
|
+
introspection: options.introspection,
|
|
656
|
+
teachings: toInstructions(
|
|
657
|
+
"instructions",
|
|
658
|
+
persona({
|
|
659
|
+
name: "Freya",
|
|
660
|
+
role: "You are an expert SQL query generator. You translate natural language questions into precise, efficient SQL queries based on the provided database schema."
|
|
661
|
+
}),
|
|
662
|
+
...options.instructions
|
|
663
|
+
)
|
|
664
|
+
})
|
|
665
|
+
);
|
|
666
|
+
if ("error" in output) {
|
|
667
|
+
throw new UnanswerableSQLError(output.error);
|
|
668
|
+
}
|
|
669
|
+
const sql = extractSql(output.sql);
|
|
670
|
+
const validationError = await options.adapter.validate(sql);
|
|
671
|
+
if (validationError) {
|
|
672
|
+
throw new SQLValidationError(validationError);
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
attempts,
|
|
676
|
+
sql,
|
|
677
|
+
errors: errors.length ? errors.map(formatErrorMessage) : void 0
|
|
678
|
+
};
|
|
679
|
+
},
|
|
680
|
+
{ retries: maxRetries - 1 }
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
function formatErrorMessage(error) {
|
|
684
|
+
if (APICallError.isInstance(error)) {
|
|
685
|
+
if (error.message.startsWith("Failed to validate JSON")) {
|
|
686
|
+
return `Schema validation failed: ${error.message}`;
|
|
687
|
+
}
|
|
688
|
+
return error.message;
|
|
689
|
+
}
|
|
690
|
+
if (SQLValidationError.isInstance(error)) {
|
|
691
|
+
return `SQL Validation Error: ${error.message}`;
|
|
692
|
+
}
|
|
693
|
+
return error.message;
|
|
694
|
+
}
|
|
695
|
+
async function withRetry(computation, options = { retries: 3 }) {
|
|
696
|
+
const errors = [];
|
|
697
|
+
let attempts = 0;
|
|
698
|
+
return pRetry(
|
|
699
|
+
(attemptNumber) => {
|
|
700
|
+
return computation(attemptNumber, errors, ++attempts);
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
retries: options.retries,
|
|
704
|
+
shouldRetry: (context2) => {
|
|
705
|
+
if (UnanswerableSQLError.isInstance(context2.error)) {
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
if (SQLValidationError.isInstance(context2.error)) {
|
|
709
|
+
return true;
|
|
710
|
+
}
|
|
711
|
+
console.log({
|
|
712
|
+
NoObjectGeneratedError: NoObjectGeneratedError.isInstance(
|
|
713
|
+
context2.error
|
|
714
|
+
),
|
|
715
|
+
NoOutputGeneratedError: NoOutputGeneratedError.isInstance(
|
|
716
|
+
context2.error
|
|
717
|
+
),
|
|
718
|
+
APICallError: APICallError.isInstance(context2.error),
|
|
719
|
+
JSONParseError: JSONParseError.isInstance(context2.error),
|
|
720
|
+
TypeValidationError: TypeValidationError.isInstance(context2.error),
|
|
721
|
+
NoContentGeneratedError: NoContentGeneratedError.isInstance(
|
|
722
|
+
context2.error
|
|
723
|
+
)
|
|
724
|
+
});
|
|
725
|
+
return APICallError.isInstance(context2.error) || JSONParseError.isInstance(context2.error) || TypeValidationError.isInstance(context2.error) || NoObjectGeneratedError.isInstance(context2.error) || NoOutputGeneratedError.isInstance(context2.error) || NoContentGeneratedError.isInstance(context2.error);
|
|
726
|
+
},
|
|
727
|
+
onFailedAttempt(context2) {
|
|
728
|
+
logger.error(`toSQL`, context2.error);
|
|
729
|
+
console.log(
|
|
730
|
+
`Attempt ${context2.attemptNumber} failed. There are ${context2.retriesLeft} retries left.`
|
|
731
|
+
);
|
|
732
|
+
errors.push(context2.error);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// packages/text2sql/src/lib/agents/developer.agent.ts
|
|
739
|
+
var tools = {
|
|
740
|
+
/**
|
|
741
|
+
* Generate SQL from natural language question.
|
|
742
|
+
* Uses the toSql function with retry logic and validation.
|
|
743
|
+
*/
|
|
744
|
+
generate_sql: tool({
|
|
745
|
+
description: dedent2`
|
|
746
|
+
Generate a validated SQL query from a natural language question.
|
|
747
|
+
The query is automatically validated against the database schema.
|
|
748
|
+
Use this when the user asks a question that requires data retrieval.
|
|
749
|
+
|
|
750
|
+
Returns the SQL query along with generation metadata (attempts, any errors encountered).
|
|
751
|
+
`,
|
|
752
|
+
inputSchema: z3.object({
|
|
753
|
+
question: z3.string().min(1).describe("The natural language question to convert to SQL")
|
|
754
|
+
}),
|
|
755
|
+
execute: async ({ question }, options) => {
|
|
756
|
+
const state = toState(options);
|
|
757
|
+
try {
|
|
758
|
+
const result = await toSql({
|
|
759
|
+
input: question,
|
|
760
|
+
adapter: state.adapter,
|
|
761
|
+
introspection: state.introspection,
|
|
762
|
+
instructions: state.instructions
|
|
763
|
+
});
|
|
764
|
+
return {
|
|
765
|
+
success: true,
|
|
766
|
+
sql: result.sql,
|
|
767
|
+
attempts: result.attempts,
|
|
768
|
+
errors: result.errors
|
|
769
|
+
};
|
|
770
|
+
} catch (error) {
|
|
771
|
+
return {
|
|
772
|
+
success: false,
|
|
773
|
+
error: error instanceof Error ? error.message : String(error)
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}),
|
|
778
|
+
/**
|
|
779
|
+
* Get plain-English explanation of a SQL query.
|
|
780
|
+
*/
|
|
781
|
+
explain_sql: tool({
|
|
782
|
+
description: dedent2`
|
|
783
|
+
Get a plain-English explanation of a SQL query.
|
|
784
|
+
Use this to help the user understand what a query does.
|
|
785
|
+
|
|
786
|
+
The explanation focuses on intent and logic, not syntax.
|
|
787
|
+
`,
|
|
788
|
+
inputSchema: z3.object({
|
|
789
|
+
sql: z3.string().min(1).describe("The SQL query to explain")
|
|
790
|
+
}),
|
|
791
|
+
execute: async ({ sql }) => {
|
|
792
|
+
const { experimental_output } = await generate2(explainerAgent, [], {
|
|
793
|
+
sql
|
|
794
|
+
});
|
|
795
|
+
return { explanation: experimental_output.explanation };
|
|
796
|
+
}
|
|
797
|
+
}),
|
|
798
|
+
/**
|
|
799
|
+
* Show database schema introspection.
|
|
800
|
+
*/
|
|
801
|
+
show_schema: tool({
|
|
802
|
+
description: dedent2`
|
|
803
|
+
Display the database schema introspection.
|
|
804
|
+
Use this when the user wants to see available tables, columns, or relationships.
|
|
805
|
+
|
|
806
|
+
Optionally filter by table name to reduce output.
|
|
807
|
+
`,
|
|
808
|
+
inputSchema: z3.object({
|
|
809
|
+
table: z3.string().optional().describe(
|
|
810
|
+
"Optional: filter to show only a specific table. If omitted, shows full schema."
|
|
811
|
+
)
|
|
812
|
+
}),
|
|
813
|
+
execute: async ({ table }, options) => {
|
|
814
|
+
const state = toState(options);
|
|
815
|
+
if (!table) {
|
|
816
|
+
return { schema: state.introspection };
|
|
817
|
+
}
|
|
818
|
+
const lines = state.introspection.split("\n");
|
|
819
|
+
const tableLines = [];
|
|
820
|
+
let inTable = false;
|
|
821
|
+
let depth = 0;
|
|
822
|
+
for (const line of lines) {
|
|
823
|
+
const lowerLine = line.toLowerCase();
|
|
824
|
+
if (lowerLine.includes(`name="${table.toLowerCase()}"`) || lowerLine.includes(`table="${table.toLowerCase()}"`)) {
|
|
825
|
+
inTable = true;
|
|
826
|
+
depth = 1;
|
|
827
|
+
tableLines.push(line);
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (inTable) {
|
|
831
|
+
tableLines.push(line);
|
|
832
|
+
if (line.includes("</")) {
|
|
833
|
+
depth--;
|
|
834
|
+
}
|
|
835
|
+
if (line.includes("<") && !line.includes("</") && !line.includes("/>")) {
|
|
836
|
+
depth++;
|
|
837
|
+
}
|
|
838
|
+
if (depth <= 0) {
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (tableLines.length === 0) {
|
|
844
|
+
return {
|
|
845
|
+
schema: `Table "${table}" not found in schema. Use show_schema without a table filter to see all available tables.`
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
return { schema: tableLines.join("\n") };
|
|
849
|
+
}
|
|
850
|
+
}),
|
|
851
|
+
/**
|
|
852
|
+
* Developer scratchpad for notes and reasoning.
|
|
853
|
+
*/
|
|
854
|
+
scratchpad: scratchpad_tool
|
|
855
|
+
};
|
|
856
|
+
var developerAgent = agent3({
|
|
857
|
+
model: groq3("gpt-oss-20b"),
|
|
858
|
+
tools,
|
|
859
|
+
name: "developer_agent",
|
|
860
|
+
prompt: (state) => {
|
|
861
|
+
return dedent2`
|
|
862
|
+
You are an expert SQL developer assistant helping power users build and refine queries.
|
|
863
|
+
|
|
864
|
+
## Your Capabilities
|
|
865
|
+
|
|
866
|
+
You have access to the following tools:
|
|
867
|
+
|
|
868
|
+
1. **generate_sql**: Convert natural language questions to validated SQL queries
|
|
869
|
+
- Automatically validates against the database schema
|
|
870
|
+
- Returns generation metadata (attempts, errors if any)
|
|
871
|
+
|
|
872
|
+
2. **explain_sql**: Get a plain-English explanation of any SQL query
|
|
873
|
+
- Helps users understand complex queries
|
|
874
|
+
- Focuses on intent and logic, not syntax
|
|
875
|
+
|
|
876
|
+
3. **show_schema**: Display database schema information
|
|
877
|
+
- Can show full schema or filter by table name
|
|
878
|
+
- Use to explore available tables and columns
|
|
879
|
+
|
|
880
|
+
4. **scratchpad**: Record your reasoning and notes
|
|
881
|
+
|
|
882
|
+
## Guidelines
|
|
883
|
+
|
|
884
|
+
- Be transparent: show the SQL you generate before explaining it
|
|
885
|
+
- Be precise: provide exact column names and table references
|
|
886
|
+
- Be helpful: suggest refinements and alternatives when appropriate
|
|
887
|
+
- Support both natural language questions AND raw SQL input
|
|
888
|
+
- When validating user SQL, explain any errors clearly
|
|
889
|
+
- Use show_schema proactively when you need to verify table/column names
|
|
890
|
+
|
|
891
|
+
${state?.teachings || ""}
|
|
892
|
+
${state?.introspection || ""}
|
|
893
|
+
`;
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
// packages/text2sql/src/lib/agents/suggestions.agents.ts
|
|
898
|
+
import { groq as groq4 } from "@ai-sdk/groq";
|
|
899
|
+
import dedent3 from "dedent";
|
|
900
|
+
import z4 from "zod";
|
|
901
|
+
import { agent as agent4, thirdPersonPrompt } from "@deepagents/agent";
|
|
902
|
+
var suggestionsAgent = agent4({
|
|
903
|
+
name: "text2sql-suggestions",
|
|
904
|
+
model: groq4("openai/gpt-oss-20b"),
|
|
905
|
+
output: z4.object({
|
|
906
|
+
suggestions: z4.array(
|
|
907
|
+
z4.object({
|
|
908
|
+
question: z4.string().describe("A complex, high-impact business question."),
|
|
909
|
+
sql: z4.string().describe("The SQL statement needed to answer the question."),
|
|
910
|
+
businessValue: z4.string().describe("Why the question matters to stakeholders.")
|
|
911
|
+
})
|
|
912
|
+
).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
|
|
913
|
+
}),
|
|
914
|
+
prompt: (state) => {
|
|
915
|
+
return dedent3`
|
|
916
|
+
${thirdPersonPrompt()}
|
|
917
|
+
|
|
918
|
+
<identity>
|
|
919
|
+
You are a senior analytics strategist who proposes ambitious business questions
|
|
920
|
+
and drafts the SQL needed to answer them. You specialize in identifying ideas
|
|
921
|
+
that combine multiple tables, apply segmentation or time analysis, and surface
|
|
922
|
+
metrics that drive executive decisions.
|
|
923
|
+
</identity>
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
<instructions>
|
|
927
|
+
- Recommend one or two UNIQUE questions that go beyond simple counts or listings.
|
|
928
|
+
- Favor questions that require joins, aggregates, time comparisons, cohort analysis,
|
|
929
|
+
or window functions.
|
|
930
|
+
- For each question, explain the business reason stakeholders care about it.
|
|
931
|
+
- Provide the complete SQL query that could answer the question in the given schema.
|
|
932
|
+
- Keep result sets scoped with LIMIT clauses (max 50 rows) when returning raw rows.
|
|
933
|
+
- Ensure table/column names match the provided schema exactly.
|
|
934
|
+
- Use columns marked [LowCardinality: ...] to identify meaningful categorical filters or segmentations.
|
|
935
|
+
- Leverage table [rows / size] hints to determine whether to aggregate (large tables) or inspect detailed data (tiny tables).
|
|
936
|
+
- Reference PK/Indexed annotations and the Indexes list to recommend queries that use efficient join/filter paths.
|
|
937
|
+
- Column annotations may expose ranges/null percentages—use them to suggest realistic thresholds or quality checks.
|
|
938
|
+
- Consult <relationship_examples> to anchor your recommendations in the actual join paths between tables.
|
|
939
|
+
- Output only information grounded in the schema/context provided.
|
|
940
|
+
</instructions>
|
|
941
|
+
|
|
942
|
+
<response-format>
|
|
943
|
+
Return valid JSON that satisfies the defined output schema.
|
|
944
|
+
</response-format>
|
|
945
|
+
`;
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
// packages/text2sql/src/lib/agents/text2sql.agent.ts
|
|
950
|
+
import { groq as groq5 } from "@ai-sdk/groq";
|
|
951
|
+
import { tool as tool2 } from "ai";
|
|
952
|
+
import z5 from "zod";
|
|
953
|
+
import { agent as agent5, toState as toState2 } from "@deepagents/agent";
|
|
954
|
+
import { scratchpad_tool as scratchpad_tool2 } from "@deepagents/toolbox";
|
|
955
|
+
|
|
411
956
|
// packages/text2sql/src/lib/memory/memory.prompt.ts
|
|
412
957
|
var memory_prompt_default = toInstructions(
|
|
413
958
|
"memory_guidelines",
|
|
@@ -537,14 +1082,14 @@ var memory_prompt_default = toInstructions(
|
|
|
537
1082
|
);
|
|
538
1083
|
|
|
539
1084
|
// packages/text2sql/src/lib/agents/text2sql.agent.ts
|
|
540
|
-
var
|
|
541
|
-
validate_query:
|
|
1085
|
+
var tools2 = {
|
|
1086
|
+
validate_query: tool2({
|
|
542
1087
|
description: `Validate SQL query syntax before execution. Use this to check if your SQL is valid before running db_query. This helps catch errors early and allows you to correct the query if needed.`,
|
|
543
|
-
inputSchema:
|
|
544
|
-
sql:
|
|
1088
|
+
inputSchema: z5.object({
|
|
1089
|
+
sql: z5.string().describe("The SQL query to validate.")
|
|
545
1090
|
}),
|
|
546
1091
|
execute: async ({ sql }, options) => {
|
|
547
|
-
const state =
|
|
1092
|
+
const state = toState2(options);
|
|
548
1093
|
const result = await state.adapter.validate(sql);
|
|
549
1094
|
if (typeof result === "string") {
|
|
550
1095
|
return `Validation Error: ${result}`;
|
|
@@ -552,22 +1097,22 @@ var tools = {
|
|
|
552
1097
|
return "Query is valid.";
|
|
553
1098
|
}
|
|
554
1099
|
}),
|
|
555
|
-
get_sample_rows:
|
|
1100
|
+
get_sample_rows: tool2({
|
|
556
1101
|
description: `Sample rows from a table to understand data formatting, codes, and value patterns. Use BEFORE writing queries when:
|
|
557
1102
|
- Column types in schema don't reveal format (e.g., "status" could be 'active'/'inactive' or 1/0)
|
|
558
1103
|
- Date/time formats are unclear (ISO, Unix timestamp, locale-specific)
|
|
559
1104
|
- You need to understand lookup table codes or enum values
|
|
560
1105
|
- Column names are ambiguous (e.g., "type", "category", "code")`,
|
|
561
|
-
inputSchema:
|
|
562
|
-
tableName:
|
|
563
|
-
columns:
|
|
1106
|
+
inputSchema: z5.object({
|
|
1107
|
+
tableName: z5.string().describe("The name of the table to sample."),
|
|
1108
|
+
columns: z5.array(z5.string()).optional().describe(
|
|
564
1109
|
"Specific columns to sample. If omitted, samples all columns."
|
|
565
1110
|
),
|
|
566
|
-
limit:
|
|
1111
|
+
limit: z5.number().min(1).max(10).default(3).optional().describe("Number of rows to sample (1-10, default 3).")
|
|
567
1112
|
}),
|
|
568
1113
|
execute: ({ tableName, columns, limit = 3 }, options) => {
|
|
569
1114
|
const safeLimit = Math.min(Math.max(1, limit), 10);
|
|
570
|
-
const state =
|
|
1115
|
+
const state = toState2(options);
|
|
571
1116
|
const sql = state.adapter.buildSampleRowsQuery(
|
|
572
1117
|
tableName,
|
|
573
1118
|
columns,
|
|
@@ -576,13 +1121,13 @@ var tools = {
|
|
|
576
1121
|
return state.adapter.execute(sql);
|
|
577
1122
|
}
|
|
578
1123
|
}),
|
|
579
|
-
db_query:
|
|
1124
|
+
db_query: tool2({
|
|
580
1125
|
description: `Internal tool to fetch data from the store's database. Write a SQL query to retrieve the information needed to answer the user's question. The results will be returned as data that you can then present to the user in natural language.`,
|
|
581
|
-
inputSchema:
|
|
582
|
-
reasoning:
|
|
1126
|
+
inputSchema: z5.object({
|
|
1127
|
+
reasoning: z5.string().describe(
|
|
583
1128
|
"Your reasoning for why this SQL query is relevant to the user request."
|
|
584
1129
|
),
|
|
585
|
-
sql:
|
|
1130
|
+
sql: z5.string().min(1, { message: "SQL query cannot be empty." }).refine(
|
|
586
1131
|
(sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
|
|
587
1132
|
{
|
|
588
1133
|
message: "Only read-only SELECT or WITH queries are allowed."
|
|
@@ -590,11 +1135,11 @@ var tools = {
|
|
|
590
1135
|
).describe("The SQL query to execute against the database.")
|
|
591
1136
|
}),
|
|
592
1137
|
execute: ({ sql }, options) => {
|
|
593
|
-
const state =
|
|
1138
|
+
const state = toState2(options);
|
|
594
1139
|
return state.adapter.execute(sql);
|
|
595
1140
|
}
|
|
596
1141
|
}),
|
|
597
|
-
scratchpad:
|
|
1142
|
+
scratchpad: scratchpad_tool2
|
|
598
1143
|
};
|
|
599
1144
|
var userMemoryTypes = [
|
|
600
1145
|
"identity",
|
|
@@ -603,61 +1148,61 @@ var userMemoryTypes = [
|
|
|
603
1148
|
"context",
|
|
604
1149
|
"correction"
|
|
605
1150
|
];
|
|
606
|
-
var userMemorySchema =
|
|
607
|
-
|
|
608
|
-
type:
|
|
609
|
-
description:
|
|
1151
|
+
var userMemorySchema = z5.discriminatedUnion("type", [
|
|
1152
|
+
z5.object({
|
|
1153
|
+
type: z5.literal("identity"),
|
|
1154
|
+
description: z5.string().describe("The user's identity: role or/and name")
|
|
610
1155
|
}),
|
|
611
|
-
|
|
612
|
-
type:
|
|
613
|
-
term:
|
|
614
|
-
meaning:
|
|
1156
|
+
z5.object({
|
|
1157
|
+
type: z5.literal("alias"),
|
|
1158
|
+
term: z5.string().describe("The term the user uses"),
|
|
1159
|
+
meaning: z5.string().describe("What the user means by this term")
|
|
615
1160
|
}),
|
|
616
|
-
|
|
617
|
-
type:
|
|
618
|
-
aspect:
|
|
619
|
-
value:
|
|
1161
|
+
z5.object({
|
|
1162
|
+
type: z5.literal("preference"),
|
|
1163
|
+
aspect: z5.string().describe("What aspect of output this preference applies to"),
|
|
1164
|
+
value: z5.string().describe("The user's preference")
|
|
620
1165
|
}),
|
|
621
|
-
|
|
622
|
-
type:
|
|
623
|
-
description:
|
|
1166
|
+
z5.object({
|
|
1167
|
+
type: z5.literal("context"),
|
|
1168
|
+
description: z5.string().describe("What the user is currently working on")
|
|
624
1169
|
}),
|
|
625
|
-
|
|
626
|
-
type:
|
|
627
|
-
subject:
|
|
628
|
-
clarification:
|
|
1170
|
+
z5.object({
|
|
1171
|
+
type: z5.literal("correction"),
|
|
1172
|
+
subject: z5.string().describe("What was misunderstood"),
|
|
1173
|
+
clarification: z5.string().describe("The correct understanding")
|
|
629
1174
|
})
|
|
630
1175
|
]);
|
|
631
1176
|
var memoryTools = {
|
|
632
|
-
remember_memory:
|
|
1177
|
+
remember_memory: tool2({
|
|
633
1178
|
description: "Store something about the user for future conversations. Use silently when user shares facts, preferences, vocabulary, corrections, or context.",
|
|
634
|
-
inputSchema:
|
|
1179
|
+
inputSchema: z5.object({ memory: userMemorySchema }),
|
|
635
1180
|
execute: async ({ memory }, options) => {
|
|
636
|
-
const state =
|
|
1181
|
+
const state = toState2(
|
|
637
1182
|
options
|
|
638
1183
|
);
|
|
639
1184
|
await state.memory.remember(state.userId, memory);
|
|
640
1185
|
return "Remembered.";
|
|
641
1186
|
}
|
|
642
1187
|
}),
|
|
643
|
-
forget_memory:
|
|
1188
|
+
forget_memory: tool2({
|
|
644
1189
|
description: "Forget a specific memory. Use when user asks to remove something.",
|
|
645
|
-
inputSchema:
|
|
646
|
-
id:
|
|
1190
|
+
inputSchema: z5.object({
|
|
1191
|
+
id: z5.string().describe("The ID of the teachable to forget")
|
|
647
1192
|
}),
|
|
648
1193
|
execute: async ({ id }, options) => {
|
|
649
|
-
const state =
|
|
1194
|
+
const state = toState2(options);
|
|
650
1195
|
await state.memory.forget(id);
|
|
651
1196
|
return "Forgotten.";
|
|
652
1197
|
}
|
|
653
1198
|
}),
|
|
654
|
-
recall_memory:
|
|
1199
|
+
recall_memory: tool2({
|
|
655
1200
|
description: "List stored memories for the current user. Use when user asks what you remember about them or wants to see their stored preferences.",
|
|
656
|
-
inputSchema:
|
|
657
|
-
type:
|
|
1201
|
+
inputSchema: z5.object({
|
|
1202
|
+
type: z5.enum(userMemoryTypes).optional().catch(void 0).describe("Optional: filter by memory type")
|
|
658
1203
|
}),
|
|
659
1204
|
execute: async ({ type }, options) => {
|
|
660
|
-
const state =
|
|
1205
|
+
const state = toState2(
|
|
661
1206
|
options
|
|
662
1207
|
);
|
|
663
1208
|
const memories = await state.memory.recall(state.userId, type);
|
|
@@ -672,71 +1217,301 @@ var memoryTools = {
|
|
|
672
1217
|
}));
|
|
673
1218
|
}
|
|
674
1219
|
}),
|
|
675
|
-
update_memory:
|
|
1220
|
+
update_memory: tool2({
|
|
676
1221
|
description: "Update an existing memory. Use when user wants to modify something you previously stored.",
|
|
677
|
-
inputSchema:
|
|
1222
|
+
inputSchema: z5.object({
|
|
678
1223
|
memory: userMemorySchema,
|
|
679
|
-
id:
|
|
1224
|
+
id: z5.string().describe("The ID of the memory to update")
|
|
680
1225
|
}),
|
|
681
1226
|
execute: async ({ id, memory }, options) => {
|
|
682
|
-
const state =
|
|
1227
|
+
const state = toState2(options);
|
|
683
1228
|
await state.memory.update(id, memory);
|
|
684
1229
|
return "Updated.";
|
|
685
1230
|
}
|
|
686
1231
|
})
|
|
687
1232
|
};
|
|
688
|
-
var
|
|
1233
|
+
var chainOfThoughtPrompt = `
|
|
1234
|
+
## Query Reasoning Process
|
|
1235
|
+
|
|
1236
|
+
Let's think step by step before writing SQL:
|
|
1237
|
+
|
|
1238
|
+
1. **Schema Link**: Which tables and columns are relevant? Verify they exist in the schema.
|
|
1239
|
+
2. **Join Path**: If multiple tables, what relationships connect them?
|
|
1240
|
+
3. **Filters**: What WHERE conditions are needed?
|
|
1241
|
+
4. **Aggregation**: Is COUNT, SUM, AVG, GROUP BY, or HAVING required?
|
|
1242
|
+
5. **Output**: What columns to SELECT and any ORDER BY or LIMIT?
|
|
1243
|
+
6. **Verify**: Do all referenced tables and columns exist in the schema above?
|
|
1244
|
+
|
|
1245
|
+
For simple queries, steps 2-4 may not apply\u2014skip them.
|
|
1246
|
+
|
|
1247
|
+
For complex queries requiring multiple data points, decompose into sub-questions:
|
|
1248
|
+
- Break the question into simpler parts (Q1, Q2, ...)
|
|
1249
|
+
- Determine if each part needs a subquery or CTE
|
|
1250
|
+
- Combine the parts into the final query
|
|
1251
|
+
|
|
1252
|
+
Keep reasoning brief. Verbose explanations cause errors.
|
|
1253
|
+
`;
|
|
1254
|
+
var fewShotExamples = `
|
|
1255
|
+
## Examples
|
|
1256
|
+
|
|
1257
|
+
### Example 1: Simple Filter
|
|
1258
|
+
Question: "How many records in table_a have column_x equal to 'value'?"
|
|
1259
|
+
Reasoning:
|
|
1260
|
+
- Schema Link: table_a, column_x
|
|
1261
|
+
- Filters: column_x = 'value'
|
|
1262
|
+
- Aggregation: COUNT(*)
|
|
1263
|
+
SQL: SELECT COUNT(*) FROM table_a WHERE column_x = 'value'
|
|
1264
|
+
|
|
1265
|
+
### Example 2: JOIN Query
|
|
1266
|
+
Question: "Show column_y from table_b for each record in table_a where column_x is 'value'"
|
|
1267
|
+
Reasoning:
|
|
1268
|
+
- Schema Link: table_a (column_x, id), table_b (column_y, fk_a)
|
|
1269
|
+
- Join Path: table_a.id \u2192 table_b.fk_a
|
|
1270
|
+
- Filters: column_x = 'value'
|
|
1271
|
+
- Output: column_y from table_b
|
|
1272
|
+
SQL: SELECT b.column_y FROM table_a a JOIN table_b b ON b.fk_a = a.id WHERE a.column_x = 'value'
|
|
1273
|
+
|
|
1274
|
+
### Example 3: Aggregation with GROUP BY
|
|
1275
|
+
Question: "What is the total of column_y grouped by column_x?"
|
|
1276
|
+
Reasoning:
|
|
1277
|
+
- Schema Link: table_a (column_x, column_y)
|
|
1278
|
+
- Aggregation: SUM(column_y), GROUP BY column_x
|
|
1279
|
+
- Output: column_x, sum
|
|
1280
|
+
SQL: SELECT column_x, SUM(column_y) as total FROM table_a GROUP BY column_x
|
|
1281
|
+
|
|
1282
|
+
### Example 4: Complex Aggregation
|
|
1283
|
+
Question: "Which values of column_x have more than 10 records, sorted by count descending?"
|
|
1284
|
+
Reasoning:
|
|
1285
|
+
- Schema Link: table_a (column_x)
|
|
1286
|
+
- Aggregation: COUNT(*), GROUP BY column_x, HAVING > 10
|
|
1287
|
+
- Output: column_x, count, ORDER BY count DESC
|
|
1288
|
+
SQL: SELECT column_x, COUNT(*) as cnt FROM table_a GROUP BY column_x HAVING COUNT(*) > 10 ORDER BY cnt DESC
|
|
1289
|
+
|
|
1290
|
+
### Example 5: Subquery (Decomposition)
|
|
1291
|
+
Question: "Show records from table_a where column_y is above average"
|
|
1292
|
+
Reasoning:
|
|
1293
|
+
- Decompose:
|
|
1294
|
+
- Q1: What is the average of column_y?
|
|
1295
|
+
- Q2: Which records have column_y above that value?
|
|
1296
|
+
- Schema Link: table_a (column_y)
|
|
1297
|
+
- Filters: column_y > (result of Q1)
|
|
1298
|
+
- Output: all columns from matching records
|
|
1299
|
+
- Verify: table_a and column_y exist \u2713
|
|
1300
|
+
SQL: SELECT * FROM table_a WHERE column_y > (SELECT AVG(column_y) FROM table_a)
|
|
1301
|
+
|
|
1302
|
+
### Example 6: Complex Multi-Join (Decomposition)
|
|
1303
|
+
Question: "Find the top 3 categories by total sales amount for orders placed last month"
|
|
1304
|
+
Reasoning:
|
|
1305
|
+
- Decompose:
|
|
1306
|
+
- Q1: Which orders were placed last month?
|
|
1307
|
+
- Q2: What is the total sales per category for those orders?
|
|
1308
|
+
- Q3: Which 3 categories have the highest totals?
|
|
1309
|
+
- Schema Link: orders (order_date, id), order_items (order_id, amount, product_id), products (id, category_id), categories (id, name)
|
|
1310
|
+
- Join Path: orders \u2192 order_items \u2192 products \u2192 categories
|
|
1311
|
+
- Filters: order_date within last month
|
|
1312
|
+
- Aggregation: SUM(amount), GROUP BY category
|
|
1313
|
+
- Output: category name, total, ORDER BY total DESC, LIMIT 3
|
|
1314
|
+
- Verify: all tables and columns exist \u2713
|
|
1315
|
+
SQL: SELECT c.name, SUM(oi.amount) as total FROM orders o JOIN order_items oi ON oi.order_id = o.id JOIN products p ON p.id = oi.product_id JOIN categories c ON c.id = p.category_id WHERE o.order_date >= DATE('now', '-1 month') GROUP BY c.id, c.name ORDER BY total DESC LIMIT 3
|
|
1316
|
+
`;
|
|
1317
|
+
var t_a_g = agent5({
|
|
1318
|
+
model: groq5("openai/gpt-oss-20b"),
|
|
1319
|
+
tools: tools2,
|
|
689
1320
|
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
1321
|
prompt: (state) => {
|
|
1322
|
+
const hasMemory = !!state?.memory;
|
|
698
1323
|
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
1324
|
${state?.teachings || ""}
|
|
705
1325
|
${state?.introspection || ""}
|
|
706
|
-
|
|
1326
|
+
|
|
1327
|
+
${chainOfThoughtPrompt}
|
|
1328
|
+
|
|
1329
|
+
${fewShotExamples}
|
|
1330
|
+
|
|
1331
|
+
${hasMemory ? memory_prompt_default : ""}
|
|
707
1332
|
`;
|
|
708
1333
|
}
|
|
709
1334
|
});
|
|
710
|
-
var t_a_g = agent2({
|
|
711
|
-
model: groq2("openai/gpt-oss-20b"),
|
|
712
|
-
tools,
|
|
713
|
-
name: "text2sql",
|
|
714
|
-
prompt: (state) => {
|
|
715
|
-
const hasMemory = !!state?.memory;
|
|
716
|
-
return `
|
|
717
1335
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1336
|
+
// packages/text2sql/src/lib/checkpoint.ts
|
|
1337
|
+
import { createHash } from "node:crypto";
|
|
1338
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
1339
|
+
import pLimit from "p-limit";
|
|
1340
|
+
var Checkpoint = class _Checkpoint {
|
|
1341
|
+
constructor(path2, configHash, points) {
|
|
1342
|
+
this.path = path2;
|
|
1343
|
+
this.configHash = configHash;
|
|
1344
|
+
this.points = points;
|
|
723
1345
|
}
|
|
724
|
-
|
|
1346
|
+
points;
|
|
1347
|
+
/**
|
|
1348
|
+
* Load checkpoint from file, or return empty checkpoint if none exists.
|
|
1349
|
+
* Handles corrupted files and config changes gracefully.
|
|
1350
|
+
*/
|
|
1351
|
+
static async load(options) {
|
|
1352
|
+
const { path: path2, configHash } = options;
|
|
1353
|
+
if (existsSync(path2)) {
|
|
1354
|
+
try {
|
|
1355
|
+
const content = readFileSync(path2, "utf-8");
|
|
1356
|
+
const file = JSON.parse(content);
|
|
1357
|
+
if (configHash && file.configHash && file.configHash !== configHash) {
|
|
1358
|
+
console.log("\u26A0 Config changed, starting fresh");
|
|
1359
|
+
return new _Checkpoint(path2, configHash, {});
|
|
1360
|
+
}
|
|
1361
|
+
const points = file.points ?? {};
|
|
1362
|
+
const totalEntries = Object.values(points).reduce(
|
|
1363
|
+
(sum, p) => sum + p.entries.length,
|
|
1364
|
+
0
|
|
1365
|
+
);
|
|
1366
|
+
console.log(`\u2713 Resuming from checkpoint (${totalEntries} entries)`);
|
|
1367
|
+
return new _Checkpoint(path2, configHash, points);
|
|
1368
|
+
} catch {
|
|
1369
|
+
console.log("\u26A0 Checkpoint corrupted, starting fresh");
|
|
1370
|
+
return new _Checkpoint(path2, configHash, {});
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
console.log("Starting new checkpoint");
|
|
1374
|
+
return new _Checkpoint(path2, configHash, {});
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Run a single computation with checkpointing.
|
|
1378
|
+
* If already completed, returns cached value.
|
|
1379
|
+
*
|
|
1380
|
+
* @param key - Unique identifier for this computation
|
|
1381
|
+
* @param computation - Async function that produces the value
|
|
1382
|
+
* @param codec - Optional codec for encoding/decoding non-primitive values
|
|
1383
|
+
*/
|
|
1384
|
+
async run(key, computation, codec) {
|
|
1385
|
+
const point = this.point(key);
|
|
1386
|
+
return point.through(
|
|
1387
|
+
"single",
|
|
1388
|
+
async () => {
|
|
1389
|
+
const result = await computation();
|
|
1390
|
+
return codec ? codec.encode(result) : result;
|
|
1391
|
+
},
|
|
1392
|
+
codec
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Create a resumable checkpoint point for iterative operations.
|
|
1397
|
+
*
|
|
1398
|
+
* @param step - Unique identifier for this checkpoint point
|
|
1399
|
+
*/
|
|
1400
|
+
point(step) {
|
|
1401
|
+
this.points[step] ??= { committed: false, entries: [] };
|
|
1402
|
+
return new Point(this.points[step], () => this.save());
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Process each input with automatic checkpointing and concurrency.
|
|
1406
|
+
*
|
|
1407
|
+
* @param step - Unique identifier for this checkpoint
|
|
1408
|
+
* @param inputs - Items to process
|
|
1409
|
+
* @param process - Function to process each input
|
|
1410
|
+
* @param options - Optional settings like concurrency
|
|
1411
|
+
* @returns All outputs (use `.flat()` if outputs are arrays)
|
|
1412
|
+
*/
|
|
1413
|
+
async each(step, inputs, process2, options) {
|
|
1414
|
+
const point = this.point(step);
|
|
1415
|
+
const limit = pLimit(options?.concurrency ?? 1);
|
|
1416
|
+
const inputArray = Array.from(inputs);
|
|
1417
|
+
await Promise.all(
|
|
1418
|
+
inputArray.map(
|
|
1419
|
+
(input) => limit(() => point.through(input, () => process2(input)))
|
|
1420
|
+
)
|
|
1421
|
+
);
|
|
1422
|
+
await point.commit();
|
|
1423
|
+
return point.values();
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Get clean output from all completed points.
|
|
1427
|
+
* Single-entry points return the value directly, multi-entry return arrays.
|
|
1428
|
+
*/
|
|
1429
|
+
getOutput() {
|
|
1430
|
+
const output = {};
|
|
1431
|
+
for (const [key, pointData] of Object.entries(this.points)) {
|
|
1432
|
+
if (pointData.entries.length === 1) {
|
|
1433
|
+
output[key] = pointData.entries[0].output;
|
|
1434
|
+
} else {
|
|
1435
|
+
output[key] = pointData.entries.map((e) => e.output);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
return output;
|
|
1439
|
+
}
|
|
1440
|
+
/** Get the file path where checkpoint is stored */
|
|
1441
|
+
getPath() {
|
|
1442
|
+
return this.path;
|
|
1443
|
+
}
|
|
1444
|
+
async save() {
|
|
1445
|
+
const file = {
|
|
1446
|
+
configHash: this.configHash,
|
|
1447
|
+
points: this.points
|
|
1448
|
+
};
|
|
1449
|
+
const content = JSON.stringify(file, null, 2);
|
|
1450
|
+
const tempPath = `${this.path}.tmp`;
|
|
1451
|
+
writeFileSync(tempPath, content);
|
|
1452
|
+
renameSync(tempPath, this.path);
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
function hash(value) {
|
|
1456
|
+
return createHash("md5").update(JSON.stringify(value)).digest("hex");
|
|
1457
|
+
}
|
|
1458
|
+
var Point = class {
|
|
1459
|
+
constructor(data, persist) {
|
|
1460
|
+
this.data = data;
|
|
1461
|
+
this.persist = persist;
|
|
1462
|
+
this.#cache = new Map(
|
|
1463
|
+
data.entries.map((e) => [e.inputHash, e.output])
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
#cache;
|
|
1467
|
+
/**
|
|
1468
|
+
* Execute computation if input wasn't processed before.
|
|
1469
|
+
* Returns cached output if input hash exists, otherwise executes, saves, and returns.
|
|
1470
|
+
*/
|
|
1471
|
+
async through(input, compute, codec) {
|
|
1472
|
+
const inputHash = hash(input);
|
|
1473
|
+
if (this.#cache.has(inputHash)) {
|
|
1474
|
+
const cached = this.#cache.get(inputHash);
|
|
1475
|
+
return codec ? codec.decode(cached) : cached;
|
|
1476
|
+
}
|
|
1477
|
+
const output = await compute();
|
|
1478
|
+
this.data.entries.push({ inputHash, output });
|
|
1479
|
+
this.#cache.set(inputHash, output);
|
|
1480
|
+
await this.persist();
|
|
1481
|
+
return codec ? codec.decode(output) : output;
|
|
1482
|
+
}
|
|
1483
|
+
/** Mark this point as complete. */
|
|
1484
|
+
async commit() {
|
|
1485
|
+
this.data.committed = true;
|
|
1486
|
+
await this.persist();
|
|
1487
|
+
}
|
|
1488
|
+
/** Check if this point has been committed. */
|
|
1489
|
+
isCommitted() {
|
|
1490
|
+
return this.data.committed;
|
|
1491
|
+
}
|
|
1492
|
+
/** Get all outputs from this point. */
|
|
1493
|
+
values() {
|
|
1494
|
+
return this.data.entries.map((e) => e.output);
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
function hashConfig(config) {
|
|
1498
|
+
return createHash("md5").update(JSON.stringify(config)).digest("hex");
|
|
1499
|
+
}
|
|
725
1500
|
|
|
726
1501
|
// packages/text2sql/src/lib/file-cache.ts
|
|
727
|
-
import { createHash } from "node:crypto";
|
|
728
|
-
import { existsSync } from "node:fs";
|
|
1502
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1503
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
729
1504
|
import { readFile, writeFile } from "node:fs/promises";
|
|
730
1505
|
import { tmpdir } from "node:os";
|
|
731
1506
|
import path from "node:path";
|
|
732
1507
|
var FileCache = class {
|
|
733
1508
|
path;
|
|
734
1509
|
constructor(watermark, extension = ".txt") {
|
|
735
|
-
const
|
|
736
|
-
this.path = path.join(tmpdir(), `text2sql-${
|
|
1510
|
+
const hash2 = createHash2("md5").update(watermark).digest("hex");
|
|
1511
|
+
this.path = path.join(tmpdir(), `text2sql-${hash2}${extension}`);
|
|
737
1512
|
}
|
|
738
1513
|
async get() {
|
|
739
|
-
if (
|
|
1514
|
+
if (existsSync2(this.path)) {
|
|
740
1515
|
return readFile(this.path, "utf-8");
|
|
741
1516
|
}
|
|
742
1517
|
return null;
|
|
@@ -961,166 +1736,1156 @@ import {
|
|
|
961
1736
|
} from "ai";
|
|
962
1737
|
import { v7 as v72 } from "uuid";
|
|
963
1738
|
import {
|
|
964
|
-
generate,
|
|
1739
|
+
generate as generate5,
|
|
965
1740
|
stream,
|
|
966
|
-
user
|
|
1741
|
+
user as user4
|
|
967
1742
|
} from "@deepagents/agent";
|
|
968
1743
|
|
|
969
|
-
// packages/text2sql/src/lib/agents/
|
|
970
|
-
import { groq as
|
|
971
|
-
import
|
|
972
|
-
import
|
|
973
|
-
import
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1744
|
+
// packages/text2sql/src/lib/agents/bi.agent.ts
|
|
1745
|
+
import { groq as groq6 } from "@ai-sdk/groq";
|
|
1746
|
+
import { tool as tool3 } from "ai";
|
|
1747
|
+
import dedent4 from "dedent";
|
|
1748
|
+
import z6 from "zod";
|
|
1749
|
+
import { agent as agent6, toState as toState3 } from "@deepagents/agent";
|
|
1750
|
+
import { scratchpad_tool as scratchpad_tool3 } from "@deepagents/toolbox";
|
|
1751
|
+
var tools3 = {
|
|
1752
|
+
/**
|
|
1753
|
+
* Validate SQL query syntax without executing.
|
|
1754
|
+
* Use this to verify queries are correct before embedding in dashboard components.
|
|
1755
|
+
*/
|
|
1756
|
+
validate_query: tool3({
|
|
1757
|
+
description: dedent4`
|
|
1758
|
+
Validate SQL query syntax before embedding in dashboard components.
|
|
1759
|
+
Use this to verify your queries are syntactically correct and reference valid tables/columns.
|
|
981
1760
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1761
|
+
This tool does NOT execute the query or return data.
|
|
1762
|
+
Only SELECT or WITH statements are allowed.
|
|
1763
|
+
`,
|
|
1764
|
+
inputSchema: z6.object({
|
|
1765
|
+
reasoning: z6.string().describe(
|
|
1766
|
+
"Why this query helps understand the data for dashboard design."
|
|
1767
|
+
),
|
|
1768
|
+
sql: z6.string().min(1, { message: "SQL query cannot be empty." }).refine(
|
|
1769
|
+
(sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
|
|
1770
|
+
{
|
|
1771
|
+
message: "Only read-only SELECT or WITH queries are allowed."
|
|
1772
|
+
}
|
|
1773
|
+
).describe("The SQL query to validate.")
|
|
1774
|
+
}),
|
|
1775
|
+
execute: async ({ sql }, options) => {
|
|
1776
|
+
const state = toState3(options);
|
|
1777
|
+
const result = await state.adapter.validate(sql);
|
|
1778
|
+
if (typeof result === "string") {
|
|
1779
|
+
return { valid: false, error: result };
|
|
1780
|
+
}
|
|
1781
|
+
return { valid: true };
|
|
1782
|
+
}
|
|
1783
|
+
}),
|
|
1784
|
+
/**
|
|
1785
|
+
* Record insights and reasoning during schema analysis and dashboard design.
|
|
1786
|
+
*/
|
|
1787
|
+
scratchpad: scratchpad_tool3
|
|
1788
|
+
};
|
|
1789
|
+
var COMPONENTS_DOC = dedent4`
|
|
1790
|
+
## Available Components
|
|
1791
|
+
|
|
1792
|
+
You output markdown with embedded HTML custom elements. Use kebab-case tags with closing tags.
|
|
1793
|
+
|
|
1794
|
+
### Chart Components
|
|
1795
|
+
|
|
1796
|
+
#### Area Charts
|
|
1797
|
+
| Component | Required Props | Optional Props | Use Case |
|
|
1798
|
+
|-----------|----------------|----------------|----------|
|
|
1799
|
+
| \`<area-chart>\` | \`title\`, \`sql\` | \`x-key\`, \`y-key\`, \`variant\` | Cumulative values, trends with volume |
|
|
1800
|
+
|
|
1801
|
+
**Variants** (use \`variant\` prop):
|
|
1802
|
+
- \`default\` - Basic area with smooth curves
|
|
1803
|
+
- \`linear\` - Sharp-edged lines showing precise changes
|
|
1804
|
+
- \`step\` - Step-based segments for discrete data
|
|
1805
|
+
- \`stacked\` - Multiple series stacked on top of each other
|
|
1806
|
+
- \`stacked-expand\` - Normalized to 100% showing percentage contribution
|
|
1807
|
+
- \`gradient\` - Filled with gradient for visual depth
|
|
1808
|
+
|
|
1809
|
+
#### Bar Charts
|
|
1810
|
+
| Component | Required Props | Optional Props | Use Case |
|
|
1811
|
+
|-----------|----------------|----------------|----------|
|
|
1812
|
+
| \`<bar-chart>\` | \`title\`, \`sql\` | \`x-key\`, \`y-key\`, \`variant\`, \`orientation\` | Categorical comparisons, grouped data |
|
|
1813
|
+
|
|
1814
|
+
**Variants** (use \`variant\` prop):
|
|
1815
|
+
- \`default\` - Basic vertical bars
|
|
1816
|
+
- \`multiple\` - Multiple series side by side
|
|
1817
|
+
- \`stacked\` - Multiple series stacked
|
|
1818
|
+
- \`labeled\` - With value labels on bars
|
|
1819
|
+
- \`negative\` - Supports positive/negative values with conditional coloring
|
|
1820
|
+
- \`mixed\` - Different colors per category
|
|
1821
|
+
|
|
1822
|
+
**Orientation** (use \`orientation\` prop):
|
|
1823
|
+
- \`vertical\` (default) - Vertical bars
|
|
1824
|
+
- \`horizontal\` - Horizontal bars (good for long category names)
|
|
1825
|
+
|
|
1826
|
+
#### Line Charts
|
|
1827
|
+
| Component | Required Props | Optional Props | Use Case |
|
|
1828
|
+
|-----------|----------------|----------------|----------|
|
|
1829
|
+
| \`<line-chart>\` | \`title\`, \`sql\` | \`x-key\`, \`y-key\`, \`variant\` | Trends over time, continuous data |
|
|
1830
|
+
|
|
1831
|
+
**Variants** (use \`variant\` prop):
|
|
1832
|
+
- \`default\` - Smooth curved lines
|
|
1833
|
+
- \`linear\` - Straight lines between points
|
|
1834
|
+
- \`step\` - Step-based transitions
|
|
1835
|
+
- \`dots\` - Lines with visible data point markers
|
|
1836
|
+
- \`multiple\` - Multiple series for comparisons (A/B testing, etc.)
|
|
1837
|
+
- \`interactive\` - With metric switching capability
|
|
1838
|
+
- \`labeled\` - With value labels at each point
|
|
1839
|
+
|
|
1840
|
+
#### Pie & Donut Charts
|
|
1841
|
+
| Component | Required Props | Optional Props | Use Case |
|
|
1842
|
+
|-----------|----------------|----------------|----------|
|
|
1843
|
+
| \`<pie-chart>\` | \`title\`, \`sql\` | \`label-key\`, \`value-key\`, \`variant\` | Part-to-whole relationships, distributions |
|
|
1844
|
+
| \`<donut-chart>\` | \`title\`, \`sql\` | \`label-key\`, \`value-key\`, \`variant\` | Same as pie but with center space for text/KPI |
|
|
1845
|
+
|
|
1846
|
+
**Variants** (use \`variant\` prop):
|
|
1847
|
+
- \`default\` - Basic pie/donut
|
|
1848
|
+
- \`labeled\` - With labels on segments
|
|
1849
|
+
- \`legend\` - With external legend
|
|
1850
|
+
- \`interactive\` - With hover highlighting and selection
|
|
1851
|
+
- \`stacked\` - Multiple concentric rings for comparison
|
|
1852
|
+
|
|
1853
|
+
#### Radar Charts
|
|
1854
|
+
| Component | Required Props | Optional Props | Use Case |
|
|
1855
|
+
|-----------|----------------|----------------|----------|
|
|
1856
|
+
| \`<radar-chart>\` | \`title\`, \`sql\` | \`label-key\`, \`value-key\`, \`variant\` | Multi-dimensional comparisons, skill assessments |
|
|
1857
|
+
|
|
1858
|
+
**Variants** (use \`variant\` prop):
|
|
1859
|
+
- \`default\` - Basic radar with polygon grid
|
|
1860
|
+
- \`dots\` - With visible data point markers
|
|
1861
|
+
- \`filled\` - With filled area
|
|
1862
|
+
- \`multiple\` - Multiple series overlapping
|
|
1863
|
+
- \`circle\` - Circular grid instead of polygon
|
|
1864
|
+
- \`legend\` - With integrated legend
|
|
1865
|
+
|
|
1866
|
+
#### Radial Charts
|
|
1867
|
+
| Component | Required Props | Optional Props | Use Case |
|
|
1868
|
+
|-----------|----------------|----------------|----------|
|
|
1869
|
+
| \`<radial-chart>\` | \`title\`, \`sql\` | \`value-key\`, \`variant\` | Progress indicators, gauges, circular metrics |
|
|
1870
|
+
|
|
1871
|
+
**Variants** (use \`variant\` prop):
|
|
1872
|
+
- \`default\` - Basic radial bars from center outward
|
|
1873
|
+
- \`text\` - With centered value/caption text
|
|
1874
|
+
- \`shape\` - Gauge-style arc (not full circle)
|
|
1875
|
+
- \`stacked\` - Concentric arcs for multiple metrics
|
|
1876
|
+
- \`grid\` - With background grid rings
|
|
1877
|
+
|
|
1878
|
+
#### KPI Component
|
|
1879
|
+
| Component | Required Props | Optional Props | Use Case |
|
|
1880
|
+
|-----------|----------------|----------------|----------|
|
|
1881
|
+
| \`<kpi>\` | \`title\`, \`sql\` | \`variant\`, \`format\`, \`trend-sql\`, \`target\`, \`icon\`, \`description\`, \`color\` | Rich metric displays with trends, progress, sparklines |
|
|
1882
|
+
|
|
1883
|
+
**Variants** (use \`variant\` prop):
|
|
1884
|
+
- \`default\` - Simple value card
|
|
1885
|
+
- \`trend\` - Value with change indicator (↑12.5% or ↓3.2%)
|
|
1886
|
+
- \`comparison\` - Value with previous period value shown
|
|
1887
|
+
- \`progress\` - Value with horizontal progress bar toward target
|
|
1888
|
+
- \`ring\` - Value with circular progress gauge toward target
|
|
1889
|
+
- \`sparkline\` - Value with mini area chart showing recent trend
|
|
1890
|
+
|
|
1891
|
+
**Props Reference**:
|
|
1892
|
+
- \`title\` (required) - Display label
|
|
1893
|
+
- \`sql\` (required) - Query returning \`{ value: number }\`
|
|
1894
|
+
- \`variant\` - Display style (see above)
|
|
1895
|
+
- \`format\` - Value format: \`currency\`, \`percent\`, \`number\`, \`compact\`, \`duration\`
|
|
1896
|
+
- \`trend-sql\` - Query for trend data:
|
|
1897
|
+
- For \`trend\`/\`comparison\`: returns \`{ change: number }\` or \`{ previous: number }\`
|
|
1898
|
+
- For \`sparkline\`: returns time-series \`[{ date, value }]\`
|
|
1899
|
+
- \`target\` - Target value for \`progress\`/\`ring\` variants
|
|
1900
|
+
- \`icon\` - Icon identifier: \`dollar\`, \`users\`, \`cart\`, \`chart\`, \`percent\`, \`clock\`
|
|
1901
|
+
- \`description\` - Subtitle/context text
|
|
1902
|
+
- \`color\` - Accent color: \`positive\` (green), \`negative\` (red), \`neutral\`, \`primary\`
|
|
1903
|
+
|
|
1904
|
+
#### Data Table
|
|
1905
|
+
| Component | Required Props | Optional Props | Use Case |
|
|
1906
|
+
|-----------|----------------|----------------|----------|
|
|
1907
|
+
| \`<data-table>\` | \`title\`, \`sql\` | \`columns\` | Detailed data, lists, rankings |
|
|
1908
|
+
|
|
1909
|
+
### Layout Components
|
|
1910
|
+
| Component | Props | Description |
|
|
1911
|
+
|-----------|-------|-------------|
|
|
1912
|
+
| \`<row>\` | \`gap?\` | Horizontal flex container (small, medium, or large) |
|
|
1913
|
+
| \`<column>\` | \`span\` (1-12) | Column within row, 12-column grid |
|
|
1914
|
+
| \`<grid>\` | \`cols\`, \`gap?\` | CSS Grid container |
|
|
1915
|
+
|
|
1916
|
+
### Chart Selection Guide
|
|
1917
|
+
- **Time series / Trends with volume**: Use \`<area-chart>\` (shows magnitude over time)
|
|
1918
|
+
- **Time series / Precise trends**: Use \`<line-chart>\` (clean trend lines)
|
|
1919
|
+
- **Categories / Comparisons**: Use \`<bar-chart>\`
|
|
1920
|
+
- **Part-to-whole / Proportions**: Use \`<pie-chart>\` or \`<donut-chart>\`
|
|
1921
|
+
- **Multi-dimensional comparisons**: Use \`<radar-chart>\` (e.g., comparing skills, features)
|
|
1922
|
+
- **Progress / Gauges**: Use \`<radial-chart>\` (circular progress indicators)
|
|
1923
|
+
- **Detailed data / Rankings**: Use \`<data-table>\`
|
|
1924
|
+
- **Single metrics**: Use \`<kpi>\` with appropriate variant:
|
|
1925
|
+
- Simple value → \`default\`
|
|
1926
|
+
- Value with change indicator → \`trend\`
|
|
1927
|
+
- Value vs previous period → \`comparison\`
|
|
1928
|
+
- Value toward goal → \`progress\` or \`ring\`
|
|
1929
|
+
- Value with recent history → \`sparkline\`
|
|
1930
|
+
|
|
1931
|
+
### Example Output
|
|
1932
|
+
\`\`\`markdown
|
|
1933
|
+
## Sales Dashboard
|
|
1934
|
+
|
|
1935
|
+
<row>
|
|
1936
|
+
<column span="3">
|
|
1937
|
+
<kpi
|
|
1938
|
+
title="Total Revenue"
|
|
1939
|
+
sql="SELECT SUM(amount) as value FROM orders"
|
|
1940
|
+
trend-sql="SELECT ((SUM(CASE WHEN created_at >= NOW() - INTERVAL '30 days' THEN amount END) - SUM(CASE WHEN created_at >= NOW() - INTERVAL '60 days' AND created_at < NOW() - INTERVAL '30 days' THEN amount END)) / NULLIF(SUM(CASE WHEN created_at >= NOW() - INTERVAL '60 days' AND created_at < NOW() - INTERVAL '30 days' THEN amount END), 0) * 100) as change FROM orders"
|
|
1941
|
+
variant="trend"
|
|
1942
|
+
format="currency"
|
|
1943
|
+
icon="dollar">
|
|
1944
|
+
</kpi>
|
|
1945
|
+
</column>
|
|
1946
|
+
<column span="3">
|
|
1947
|
+
<kpi
|
|
1948
|
+
title="Orders Today"
|
|
1949
|
+
sql="SELECT COUNT(*) as value FROM orders WHERE DATE(created_at) = CURRENT_DATE"
|
|
1950
|
+
trend-sql="SELECT DATE(created_at) as date, COUNT(*) as value FROM orders WHERE created_at >= NOW() - INTERVAL '7 days' GROUP BY 1 ORDER BY 1"
|
|
1951
|
+
variant="sparkline"
|
|
1952
|
+
icon="cart">
|
|
1953
|
+
</kpi>
|
|
1954
|
+
</column>
|
|
1955
|
+
<column span="3">
|
|
1956
|
+
<kpi
|
|
1957
|
+
title="Sales Target"
|
|
1958
|
+
sql="SELECT SUM(amount) as value FROM orders WHERE EXTRACT(QUARTER FROM created_at) = EXTRACT(QUARTER FROM NOW())"
|
|
1959
|
+
target="100000"
|
|
1960
|
+
variant="progress"
|
|
1961
|
+
format="currency"
|
|
1962
|
+
description="Q4 2024 Goal">
|
|
1963
|
+
</kpi>
|
|
1964
|
+
</column>
|
|
1965
|
+
<column span="3">
|
|
1966
|
+
<kpi
|
|
1967
|
+
title="Conversion Rate"
|
|
1968
|
+
sql="SELECT (COUNT(DISTINCT buyer_id)::float / COUNT(DISTINCT visitor_id) * 100) as value FROM sessions"
|
|
1969
|
+
variant="ring"
|
|
1970
|
+
target="100"
|
|
1971
|
+
format="percent"
|
|
1972
|
+
color="primary">
|
|
1973
|
+
</kpi>
|
|
1974
|
+
</column>
|
|
1975
|
+
</row>
|
|
1976
|
+
|
|
1977
|
+
<row>
|
|
1978
|
+
<column span="8">
|
|
1979
|
+
<area-chart
|
|
1980
|
+
title="Revenue Over Time"
|
|
1981
|
+
sql="SELECT DATE_TRUNC('month', created_at) as month, SUM(amount) as revenue FROM orders GROUP BY 1 ORDER BY 1"
|
|
1982
|
+
x-key="month"
|
|
1983
|
+
y-key="revenue"
|
|
1984
|
+
variant="gradient">
|
|
1985
|
+
</area-chart>
|
|
1986
|
+
</column>
|
|
1987
|
+
<column span="4">
|
|
1988
|
+
<donut-chart
|
|
1989
|
+
title="Revenue by Category"
|
|
1990
|
+
sql="SELECT category, SUM(amount) as revenue FROM orders GROUP BY category"
|
|
1991
|
+
label-key="category"
|
|
1992
|
+
value-key="revenue"
|
|
1993
|
+
variant="interactive">
|
|
1994
|
+
</donut-chart>
|
|
1995
|
+
</column>
|
|
1996
|
+
</row>
|
|
1997
|
+
|
|
1998
|
+
<row>
|
|
1999
|
+
<column span="6">
|
|
2000
|
+
<bar-chart
|
|
2001
|
+
title="Monthly Comparison"
|
|
2002
|
+
sql="SELECT DATE_TRUNC('month', created_at) as month, SUM(CASE WHEN EXTRACT(YEAR FROM created_at) = 2024 THEN amount END) as this_year, SUM(CASE WHEN EXTRACT(YEAR FROM created_at) = 2023 THEN amount END) as last_year FROM orders GROUP BY 1"
|
|
2003
|
+
variant="multiple"
|
|
2004
|
+
x-key="month">
|
|
2005
|
+
</bar-chart>
|
|
2006
|
+
</column>
|
|
2007
|
+
<column span="6">
|
|
2008
|
+
<radar-chart
|
|
2009
|
+
title="Product Performance"
|
|
2010
|
+
sql="SELECT metric, score FROM product_metrics WHERE product_id = 1"
|
|
2011
|
+
label-key="metric"
|
|
2012
|
+
value-key="score"
|
|
2013
|
+
variant="filled">
|
|
2014
|
+
</radar-chart>
|
|
2015
|
+
</column>
|
|
2016
|
+
</row>
|
|
2017
|
+
|
|
2018
|
+
<data-table
|
|
2019
|
+
title="Top 10 Products"
|
|
2020
|
+
sql="SELECT name, SUM(quantity) as sold, SUM(amount) as revenue FROM order_items GROUP BY name ORDER BY revenue DESC LIMIT 10">
|
|
2021
|
+
</data-table>
|
|
2022
|
+
\`\`\`
|
|
2023
|
+
`;
|
|
2024
|
+
var biAgent = agent6({
|
|
2025
|
+
model: groq6("gpt-oss-20b"),
|
|
2026
|
+
tools: tools3,
|
|
2027
|
+
name: "bi_agent",
|
|
2028
|
+
prompt: (state) => {
|
|
2029
|
+
return dedent4`
|
|
2030
|
+
You are an expert BI analyst that creates dashboard specifications using HTML custom elements.
|
|
2031
|
+
|
|
2032
|
+
${COMPONENTS_DOC}
|
|
2033
|
+
|
|
2034
|
+
## Your Workflow
|
|
2035
|
+
|
|
2036
|
+
1. **PLAN**: Analyze the request and schema to determine what metrics/visualizations to create
|
|
2037
|
+
2. **VALIDATE**: Use \`validate_query\` to verify SQL syntax is correct before embedding
|
|
2038
|
+
3. **OUTPUT**: Generate the dashboard using layout and chart components
|
|
2039
|
+
|
|
2040
|
+
## Critical Rules
|
|
2041
|
+
|
|
2042
|
+
- **Design from schema**: Use the provided schema introspection to understand available tables, columns, and relationships
|
|
2043
|
+
- **Validate all queries**: Use \`validate_query\` to ensure SQL is syntactically correct before embedding in components
|
|
2044
|
+
- **Use kebab-case HTML tags** with closing tags (e.g., \`<bar-chart></bar-chart>\`)
|
|
2045
|
+
- Use \`scratchpad\` to record schema analysis insights and design decisions
|
|
2046
|
+
- Choose chart types based on column types (dates → line/area, categories → bar/pie, numbers → KPI)
|
|
2047
|
+
- Use layout components (row, column, grid) to organize the dashboard
|
|
2048
|
+
- Include a text introduction explaining what the dashboard shows
|
|
2049
|
+
|
|
2050
|
+
## SQL Rules
|
|
2051
|
+
|
|
2052
|
+
- Only SELECT or WITH statements
|
|
2053
|
+
- Use proper date/time functions for the database
|
|
2054
|
+
- Include appropriate GROUP BY, ORDER BY clauses
|
|
2055
|
+
- Use aliases for calculated columns
|
|
2056
|
+
|
|
2057
|
+
${state?.teachings || ""}
|
|
2058
|
+
${state?.introspection || ""}
|
|
2059
|
+
`;
|
|
2060
|
+
}
|
|
989
2061
|
});
|
|
990
2062
|
|
|
991
|
-
// packages/text2sql/src/lib/
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
2063
|
+
// packages/text2sql/src/lib/agents/chat1.agent.ts
|
|
2064
|
+
import { groq as groq7 } from "@ai-sdk/groq";
|
|
2065
|
+
import { tool as tool4 } from "ai";
|
|
2066
|
+
import z7 from "zod";
|
|
2067
|
+
import { agent as agent7, toState as toState4 } from "@deepagents/agent";
|
|
2068
|
+
import { scratchpad_tool as scratchpad_tool4 } from "@deepagents/toolbox";
|
|
2069
|
+
var tools4 = {
|
|
2070
|
+
query_database: tool4({
|
|
2071
|
+
description: `Query the database to answer a question. Provide your question in natural language and this tool will:
|
|
2072
|
+
1. Generate the appropriate SQL query
|
|
2073
|
+
2. Validate the SQL syntax
|
|
2074
|
+
3. Execute the query
|
|
2075
|
+
4. Return the results
|
|
2076
|
+
|
|
2077
|
+
Use this tool when you need to retrieve data to answer the user's question.`,
|
|
2078
|
+
inputSchema: z7.object({
|
|
2079
|
+
question: z7.string().min(1).describe(
|
|
2080
|
+
"The question to answer, expressed in natural language. Be specific about what data you need."
|
|
2081
|
+
),
|
|
2082
|
+
reasoning: z7.string().optional().describe(
|
|
2083
|
+
"Your reasoning for why this query is needed to answer the user."
|
|
2084
|
+
)
|
|
2085
|
+
}),
|
|
2086
|
+
execute: async ({ question }, options) => {
|
|
2087
|
+
const state = toState4(options);
|
|
2088
|
+
try {
|
|
2089
|
+
const sqlResult = await toSql({
|
|
2090
|
+
input: question,
|
|
2091
|
+
adapter: state.adapter,
|
|
2092
|
+
introspection: state.introspection,
|
|
2093
|
+
instructions: state.instructions
|
|
2094
|
+
});
|
|
2095
|
+
if (!sqlResult.sql) {
|
|
2096
|
+
return {
|
|
2097
|
+
success: false,
|
|
2098
|
+
error: sqlResult.errors?.join("; ") || "Failed to generate SQL"
|
|
2099
|
+
};
|
|
2100
|
+
}
|
|
2101
|
+
const data = await state.adapter.execute(sqlResult.sql);
|
|
2102
|
+
return {
|
|
2103
|
+
success: true,
|
|
2104
|
+
sql: sqlResult.sql,
|
|
2105
|
+
data
|
|
2106
|
+
};
|
|
2107
|
+
} catch (error) {
|
|
2108
|
+
return {
|
|
2109
|
+
success: false,
|
|
2110
|
+
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
1011
2114
|
}),
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
2115
|
+
scratchpad: scratchpad_tool4
|
|
2116
|
+
};
|
|
2117
|
+
var chat1Agent = agent7({
|
|
2118
|
+
name: "chat1-combined",
|
|
2119
|
+
model: groq7("openai/gpt-oss-20b"),
|
|
2120
|
+
tools: tools4,
|
|
2121
|
+
prompt: (state) => {
|
|
2122
|
+
return `
|
|
2123
|
+
${state?.teachings || ""}
|
|
2124
|
+
${state?.introspection || ""}
|
|
2125
|
+
`;
|
|
2126
|
+
}
|
|
2127
|
+
});
|
|
2128
|
+
|
|
2129
|
+
// packages/text2sql/src/lib/agents/chat2.agent.ts
|
|
2130
|
+
import { groq as groq8 } from "@ai-sdk/groq";
|
|
2131
|
+
import { tool as tool5 } from "ai";
|
|
2132
|
+
import z8 from "zod";
|
|
2133
|
+
import { agent as agent8, toState as toState5 } from "@deepagents/agent";
|
|
2134
|
+
import { scratchpad_tool as scratchpad_tool5 } from "@deepagents/toolbox";
|
|
2135
|
+
var tools5 = {
|
|
2136
|
+
generate_sql: tool5({
|
|
2137
|
+
description: `Generate a SQL query from a natural language question. This tool will:
|
|
2138
|
+
1. Translate your question into SQL
|
|
2139
|
+
2. Validate the SQL syntax
|
|
2140
|
+
3. Retry with corrections if validation fails
|
|
2141
|
+
4. Return the validated SQL for your review
|
|
2142
|
+
|
|
2143
|
+
Use this BEFORE execute_sql to see what query will be run. You can then:
|
|
2144
|
+
- Explain the approach to the user
|
|
2145
|
+
- Decide if the SQL looks correct
|
|
2146
|
+
- Refine your question and regenerate if needed`,
|
|
2147
|
+
inputSchema: z8.object({
|
|
2148
|
+
question: z8.string().min(1).describe(
|
|
2149
|
+
"The question to translate into SQL. Be specific about what data you need."
|
|
2150
|
+
),
|
|
2151
|
+
reasoning: z8.string().optional().describe("Your reasoning for why this data is needed.")
|
|
2152
|
+
}),
|
|
2153
|
+
execute: async ({ question }, options) => {
|
|
2154
|
+
const state = toState5(options);
|
|
2155
|
+
try {
|
|
2156
|
+
const sqlResult = await toSql({
|
|
2157
|
+
input: question,
|
|
2158
|
+
adapter: state.adapter,
|
|
2159
|
+
introspection: state.introspection,
|
|
2160
|
+
instructions: state.instructions
|
|
2161
|
+
});
|
|
2162
|
+
if (!sqlResult.sql) {
|
|
2163
|
+
return {
|
|
2164
|
+
success: false,
|
|
2165
|
+
error: sqlResult.errors?.join("; ") || "Failed to generate SQL",
|
|
2166
|
+
validationErrors: sqlResult.errors
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
return {
|
|
2170
|
+
success: true,
|
|
2171
|
+
sql: sqlResult.sql,
|
|
2172
|
+
validationErrors: sqlResult.errors
|
|
2173
|
+
};
|
|
2174
|
+
} catch (error) {
|
|
2175
|
+
return {
|
|
2176
|
+
success: false,
|
|
2177
|
+
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
1016
2181
|
}),
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
2182
|
+
execute_sql: tool5({
|
|
2183
|
+
description: `Execute a SQL query and return the results. Use this AFTER generate_sql to run the query.
|
|
2184
|
+
|
|
2185
|
+
Only SELECT and WITH (CTE) queries are allowed - no data modification.`,
|
|
2186
|
+
inputSchema: z8.object({
|
|
2187
|
+
sql: z8.string().min(1).refine(
|
|
2188
|
+
(sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
|
|
2189
|
+
{
|
|
2190
|
+
message: "Only read-only SELECT or WITH queries are allowed."
|
|
2191
|
+
}
|
|
2192
|
+
).describe("The SQL query to execute (must be SELECT or WITH)."),
|
|
2193
|
+
reasoning: z8.string().optional().describe("Brief explanation of what this query retrieves.")
|
|
2194
|
+
}),
|
|
2195
|
+
execute: async ({ sql }, options) => {
|
|
2196
|
+
const state = toState5(options);
|
|
2197
|
+
try {
|
|
2198
|
+
const data = await state.adapter.execute(sql);
|
|
2199
|
+
return {
|
|
2200
|
+
success: true,
|
|
2201
|
+
data,
|
|
2202
|
+
rowCount: Array.isArray(data) ? data.length : void 0
|
|
2203
|
+
};
|
|
2204
|
+
} catch (error) {
|
|
2205
|
+
return {
|
|
2206
|
+
success: false,
|
|
2207
|
+
error: error instanceof Error ? error.message : "Query execution failed"
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
1021
2211
|
}),
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
2212
|
+
scratchpad: scratchpad_tool5
|
|
2213
|
+
};
|
|
2214
|
+
var chat2Agent = agent8({
|
|
2215
|
+
name: "chat2-with-peek",
|
|
2216
|
+
model: groq8("openai/gpt-oss-20b"),
|
|
2217
|
+
tools: tools5,
|
|
2218
|
+
prompt: (state) => {
|
|
2219
|
+
return `
|
|
2220
|
+
${state?.teachings || ""}
|
|
2221
|
+
${state?.introspection || ""}
|
|
2222
|
+
|
|
2223
|
+
When answering questions that require database queries:
|
|
2224
|
+
1. First use generate_sql to create the SQL query
|
|
2225
|
+
2. Review the generated SQL to ensure it matches the user's intent
|
|
2226
|
+
3. Use execute_sql to run the query
|
|
2227
|
+
4. Present the results to the user
|
|
2228
|
+
|
|
2229
|
+
If the generated SQL doesn't look right, you can refine your question and regenerate.
|
|
2230
|
+
`;
|
|
2231
|
+
}
|
|
2232
|
+
});
|
|
2233
|
+
|
|
2234
|
+
// packages/text2sql/src/lib/agents/chat3.agent.ts
|
|
2235
|
+
import { groq as groq9 } from "@ai-sdk/groq";
|
|
2236
|
+
import { defaultSettingsMiddleware as defaultSettingsMiddleware2, tool as tool6, wrapLanguageModel as wrapLanguageModel2 } from "ai";
|
|
2237
|
+
import z9 from "zod";
|
|
2238
|
+
import { agent as agent9, generate as generate3, toState as toState6, user as user2 } from "@deepagents/agent";
|
|
2239
|
+
import { scratchpad_tool as scratchpad_tool6 } from "@deepagents/toolbox";
|
|
2240
|
+
var collaborativeSqlOutputSchema = z9.discriminatedUnion("status", [
|
|
2241
|
+
z9.object({
|
|
2242
|
+
status: z9.literal("success"),
|
|
2243
|
+
sql: z9.string().describe("The generated SQL query"),
|
|
2244
|
+
confidence: z9.enum(["high", "medium", "low"]).describe("Confidence level in this SQL being correct"),
|
|
2245
|
+
assumptions: z9.array(z9.string()).optional().describe("Assumptions made during SQL generation"),
|
|
2246
|
+
reasoning: z9.string().optional().describe("Brief explanation of the query approach")
|
|
1026
2247
|
}),
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
2248
|
+
z9.object({
|
|
2249
|
+
status: z9.literal("clarification_needed"),
|
|
2250
|
+
question: z9.string().describe("Question to clarify the request"),
|
|
2251
|
+
context: z9.string().optional().describe("Why this clarification is needed"),
|
|
2252
|
+
options: z9.array(z9.string()).optional().describe("Possible options if applicable")
|
|
1031
2253
|
}),
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
2254
|
+
z9.object({
|
|
2255
|
+
status: z9.literal("unanswerable"),
|
|
2256
|
+
reason: z9.string().describe("Why this question cannot be answered"),
|
|
2257
|
+
suggestions: z9.array(z9.string()).optional().describe("Alternative questions that could be answered")
|
|
2258
|
+
})
|
|
2259
|
+
]);
|
|
2260
|
+
var collaborativeSqlAgent = agent9({
|
|
2261
|
+
name: "collaborative-sql",
|
|
2262
|
+
model: groq9("openai/gpt-oss-20b"),
|
|
2263
|
+
output: collaborativeSqlOutputSchema,
|
|
2264
|
+
prompt: (state) => {
|
|
2265
|
+
return `
|
|
2266
|
+
${toInstructions(
|
|
2267
|
+
"instructions",
|
|
2268
|
+
persona({
|
|
2269
|
+
name: "SQLCollab",
|
|
2270
|
+
role: "You are an expert SQL query generator that collaborates with the user to ensure accuracy."
|
|
2271
|
+
}),
|
|
2272
|
+
...state?.instructions || []
|
|
2273
|
+
)}
|
|
2274
|
+
${state?.introspection || ""}
|
|
2275
|
+
|
|
2276
|
+
IMPORTANT: You have three response options:
|
|
2277
|
+
|
|
2278
|
+
1. SUCCESS - When you can confidently generate SQL:
|
|
2279
|
+
- Provide the SQL query
|
|
2280
|
+
- Rate your confidence (high/medium/low)
|
|
2281
|
+
- List any assumptions you made
|
|
2282
|
+
|
|
2283
|
+
2. CLARIFICATION_NEEDED - When the question is ambiguous:
|
|
2284
|
+
- Ask a specific clarifying question
|
|
2285
|
+
- Explain why clarification is needed
|
|
2286
|
+
- Provide options if applicable
|
|
2287
|
+
|
|
2288
|
+
3. UNANSWERABLE - When the question cannot be answered with available data:
|
|
2289
|
+
- Explain why
|
|
2290
|
+
- Suggest alternative questions that could be answered
|
|
2291
|
+
|
|
2292
|
+
Prefer asking for clarification over making low-confidence guesses.
|
|
2293
|
+
`;
|
|
2294
|
+
}
|
|
2295
|
+
});
|
|
2296
|
+
var tools6 = {
|
|
2297
|
+
consult_sql_agent: tool6({
|
|
2298
|
+
description: `Consult the SQL specialist agent to generate a query. The SQL agent may:
|
|
2299
|
+
- Return a SQL query with confidence level and assumptions
|
|
2300
|
+
- Ask for clarification if the question is ambiguous
|
|
2301
|
+
- Indicate if the question cannot be answered with available data
|
|
2302
|
+
|
|
2303
|
+
Based on the response:
|
|
2304
|
+
- If clarification is needed, you can provide context or ask the user
|
|
2305
|
+
- If assumptions were made, verify them with the user for important queries
|
|
2306
|
+
- If unanswerable, relay the suggestions to the user`,
|
|
2307
|
+
inputSchema: z9.object({
|
|
2308
|
+
question: z9.string().min(1).describe("The question to translate into SQL."),
|
|
2309
|
+
context: z9.string().optional().describe("Additional context from the conversation that might help."),
|
|
2310
|
+
previousClarification: z9.string().optional().describe(
|
|
2311
|
+
"Answer to a previous clarification question from the SQL agent."
|
|
2312
|
+
)
|
|
2313
|
+
}),
|
|
2314
|
+
execute: async ({ question, context: context2, previousClarification }, options) => {
|
|
2315
|
+
const state = toState6(options);
|
|
2316
|
+
try {
|
|
2317
|
+
let fullQuestion = question;
|
|
2318
|
+
if (context2) {
|
|
2319
|
+
fullQuestion = `${question}
|
|
2320
|
+
|
|
2321
|
+
Additional context: ${context2}`;
|
|
2322
|
+
}
|
|
2323
|
+
if (previousClarification) {
|
|
2324
|
+
fullQuestion = `${fullQuestion}
|
|
2325
|
+
|
|
2326
|
+
Clarification provided: ${previousClarification}`;
|
|
2327
|
+
}
|
|
2328
|
+
const agentInstance = collaborativeSqlAgent.clone({
|
|
2329
|
+
model: wrapLanguageModel2({
|
|
2330
|
+
model: collaborativeSqlAgent.model,
|
|
2331
|
+
middleware: defaultSettingsMiddleware2({
|
|
2332
|
+
settings: { temperature: 0.1 }
|
|
2333
|
+
})
|
|
2334
|
+
})
|
|
2335
|
+
});
|
|
2336
|
+
const { experimental_output: output } = await generate3(
|
|
2337
|
+
agentInstance,
|
|
2338
|
+
[user2(fullQuestion)],
|
|
2339
|
+
state
|
|
2340
|
+
);
|
|
2341
|
+
if (output.status === "success") {
|
|
2342
|
+
const validationError = await state.adapter.validate(output.sql);
|
|
2343
|
+
if (validationError) {
|
|
2344
|
+
return {
|
|
2345
|
+
success: false,
|
|
2346
|
+
error: `SQL validation failed: ${validationError}`
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
const data = await state.adapter.execute(output.sql);
|
|
2350
|
+
return {
|
|
2351
|
+
success: true,
|
|
2352
|
+
sql: output.sql,
|
|
2353
|
+
data,
|
|
2354
|
+
confidence: output.confidence,
|
|
2355
|
+
assumptions: output.assumptions
|
|
2356
|
+
};
|
|
2357
|
+
}
|
|
2358
|
+
if (output.status === "clarification_needed") {
|
|
2359
|
+
return {
|
|
2360
|
+
success: false,
|
|
2361
|
+
clarificationNeeded: output.question,
|
|
2362
|
+
clarificationContext: output.context,
|
|
2363
|
+
clarificationOptions: output.options
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
if (output.status === "unanswerable") {
|
|
2367
|
+
return {
|
|
2368
|
+
success: false,
|
|
2369
|
+
unanswerableReason: output.reason,
|
|
2370
|
+
suggestions: output.suggestions
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
return {
|
|
2374
|
+
success: false,
|
|
2375
|
+
error: "Unexpected response from SQL agent"
|
|
2376
|
+
};
|
|
2377
|
+
} catch (error) {
|
|
2378
|
+
return {
|
|
2379
|
+
success: false,
|
|
2380
|
+
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
1041
2384
|
}),
|
|
1042
|
-
|
|
1043
|
-
|
|
2385
|
+
execute_sql: tool6({
|
|
2386
|
+
description: `Execute a SQL query directly. Use this when you have SQL that you want to run
|
|
2387
|
+
(e.g., after receiving SQL from consult_sql_agent or for follow-up queries).`,
|
|
2388
|
+
inputSchema: z9.object({
|
|
2389
|
+
sql: z9.string().min(1).refine(
|
|
2390
|
+
(sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
|
|
2391
|
+
{
|
|
2392
|
+
message: "Only read-only SELECT or WITH queries are allowed."
|
|
2393
|
+
}
|
|
2394
|
+
).describe("The SQL query to execute.")
|
|
2395
|
+
}),
|
|
2396
|
+
execute: async ({ sql }, options) => {
|
|
2397
|
+
const state = toState6(options);
|
|
2398
|
+
try {
|
|
2399
|
+
const validationError = await state.adapter.validate(sql);
|
|
2400
|
+
if (validationError) {
|
|
2401
|
+
return {
|
|
2402
|
+
success: false,
|
|
2403
|
+
error: `Validation failed: ${validationError}`
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
const data = await state.adapter.execute(sql);
|
|
2407
|
+
return {
|
|
2408
|
+
success: true,
|
|
2409
|
+
data,
|
|
2410
|
+
rowCount: Array.isArray(data) ? data.length : void 0
|
|
2411
|
+
};
|
|
2412
|
+
} catch (error) {
|
|
2413
|
+
return {
|
|
2414
|
+
success: false,
|
|
2415
|
+
error: error instanceof Error ? error.message : "Execution failed"
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
1044
2419
|
}),
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
2420
|
+
scratchpad: scratchpad_tool6
|
|
2421
|
+
};
|
|
2422
|
+
var chat3Agent = agent9({
|
|
2423
|
+
name: "chat3-collaborative",
|
|
2424
|
+
model: groq9("openai/gpt-oss-20b"),
|
|
2425
|
+
tools: tools6,
|
|
2426
|
+
prompt: (state) => {
|
|
2427
|
+
return `
|
|
2428
|
+
${state?.teachings || ""}
|
|
2429
|
+
${state?.introspection || ""}
|
|
2430
|
+
|
|
2431
|
+
When answering questions that require database queries, use the consult_sql_agent tool.
|
|
2432
|
+
|
|
2433
|
+
The SQL agent may respond in three ways:
|
|
2434
|
+
1. SUCCESS with SQL, confidence, and assumptions - review the confidence and assumptions
|
|
2435
|
+
2. CLARIFICATION_NEEDED with a question - either answer from context or ask the user
|
|
2436
|
+
3. UNANSWERABLE with reason and suggestions - relay this to the user helpfully
|
|
2437
|
+
|
|
2438
|
+
For medium/low confidence results, consider mentioning the assumptions to the user.
|
|
2439
|
+
For clarification requests, try to answer from conversation context first before asking the user.
|
|
2440
|
+
`;
|
|
2441
|
+
}
|
|
2442
|
+
});
|
|
2443
|
+
|
|
2444
|
+
// packages/text2sql/src/lib/agents/chat4.agent.ts
|
|
2445
|
+
import { groq as groq10 } from "@ai-sdk/groq";
|
|
2446
|
+
import { defaultSettingsMiddleware as defaultSettingsMiddleware3, tool as tool7, wrapLanguageModel as wrapLanguageModel3 } from "ai";
|
|
2447
|
+
import z10 from "zod";
|
|
2448
|
+
import { agent as agent10, generate as generate4, toState as toState7, user as user3 } from "@deepagents/agent";
|
|
2449
|
+
import { scratchpad_tool as scratchpad_tool7 } from "@deepagents/toolbox";
|
|
2450
|
+
var questionDecompositionSchema = z10.object({
|
|
2451
|
+
originalQuestion: z10.string().describe("The original question being decomposed"),
|
|
2452
|
+
breakdown: z10.array(z10.string()).min(1).describe(
|
|
2453
|
+
"Semantic breakdown of the question into its component parts. Each part describes an aspect of what is being asked, NOT how to implement it."
|
|
2454
|
+
),
|
|
2455
|
+
entities: z10.array(z10.string()).optional().describe(
|
|
2456
|
+
"Key entities/concepts mentioned (e.g., customers, orders, products)"
|
|
2457
|
+
),
|
|
2458
|
+
filters: z10.array(z10.string()).optional().describe(
|
|
2459
|
+
'Filtering criteria mentioned (e.g., "last quarter", "above $100")'
|
|
2460
|
+
),
|
|
2461
|
+
aggregation: z10.string().optional().describe(
|
|
2462
|
+
'Type of aggregation if any (e.g., "count", "sum", "average", "top N")'
|
|
2463
|
+
),
|
|
2464
|
+
ambiguities: z10.array(z10.string()).optional().describe("Any ambiguous parts that might need clarification")
|
|
2465
|
+
});
|
|
2466
|
+
var decompositionSqlOutputSchema = z10.union([
|
|
2467
|
+
z10.object({
|
|
2468
|
+
sql: z10.string().describe("The SQL query that answers the decomposed question"),
|
|
2469
|
+
reasoning: z10.string().optional().describe("How each breakdown component was addressed")
|
|
1049
2470
|
}),
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
2471
|
+
z10.object({
|
|
2472
|
+
error: z10.string().describe("Error message if the question cannot be answered")
|
|
2473
|
+
})
|
|
2474
|
+
]);
|
|
2475
|
+
var decompositionSqlAgent = agent10({
|
|
2476
|
+
name: "decomposition-sql",
|
|
2477
|
+
model: groq10("openai/gpt-oss-20b"),
|
|
2478
|
+
output: decompositionSqlOutputSchema,
|
|
2479
|
+
prompt: (state) => {
|
|
2480
|
+
return `
|
|
2481
|
+
${toInstructions(
|
|
2482
|
+
"instructions",
|
|
2483
|
+
persona({
|
|
2484
|
+
name: "SQLDecomp",
|
|
2485
|
+
role: "You are an expert SQL query generator. You receive questions broken down into semantic components and generate precise SQL."
|
|
2486
|
+
}),
|
|
2487
|
+
...state?.instructions || []
|
|
2488
|
+
)}
|
|
2489
|
+
${state?.introspection || ""}
|
|
2490
|
+
|
|
2491
|
+
You will receive questions in a decomposed format with:
|
|
2492
|
+
- breakdown: Semantic parts of the question
|
|
2493
|
+
- entities: Key concepts mentioned
|
|
2494
|
+
- filters: Filtering criteria
|
|
2495
|
+
- aggregation: Type of aggregation needed
|
|
2496
|
+
- ambiguities: Potentially unclear parts
|
|
2497
|
+
|
|
2498
|
+
Address each component of the breakdown in your SQL.
|
|
2499
|
+
If there are ambiguities, make reasonable assumptions and note them in your reasoning.
|
|
2500
|
+
`;
|
|
2501
|
+
}
|
|
2502
|
+
});
|
|
2503
|
+
var RETRY_TEMPERATURES2 = [0, 0.2, 0.3];
|
|
2504
|
+
var tools7 = {
|
|
2505
|
+
query_with_decomposition: tool7({
|
|
2506
|
+
description: `Query the database using question decomposition. This tool:
|
|
2507
|
+
1. Breaks down your question into semantic components (entities, filters, aggregations)
|
|
2508
|
+
2. Passes the decomposition to the SQL specialist
|
|
2509
|
+
3. Generates and validates SQL
|
|
2510
|
+
4. Executes and returns results
|
|
2511
|
+
|
|
2512
|
+
This approach helps ensure all aspects of the question are addressed in the query.`,
|
|
2513
|
+
inputSchema: z10.object({
|
|
2514
|
+
question: z10.string().min(1).describe("The question to answer."),
|
|
2515
|
+
breakdown: z10.array(z10.string()).min(1).describe(
|
|
2516
|
+
'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"]'
|
|
2517
|
+
),
|
|
2518
|
+
entities: z10.array(z10.string()).optional().describe(
|
|
2519
|
+
'Key entities mentioned (e.g., ["customers", "orders", "products"])'
|
|
2520
|
+
),
|
|
2521
|
+
filters: z10.array(z10.string()).optional().describe('Filter criteria (e.g., ["last month", "status = active"])'),
|
|
2522
|
+
aggregation: z10.string().optional().describe(
|
|
2523
|
+
'Aggregation type if any (e.g., "sum revenue", "count orders", "top 10")'
|
|
2524
|
+
),
|
|
2525
|
+
ambiguities: z10.array(z10.string()).optional().describe("Note any ambiguous parts you identified")
|
|
2526
|
+
}),
|
|
2527
|
+
execute: async ({ question, breakdown, entities, filters, aggregation, ambiguities }, options) => {
|
|
2528
|
+
const state = toState7(options);
|
|
2529
|
+
const decomposition = {
|
|
2530
|
+
originalQuestion: question,
|
|
2531
|
+
breakdown,
|
|
2532
|
+
entities,
|
|
2533
|
+
filters,
|
|
2534
|
+
aggregation,
|
|
2535
|
+
ambiguities
|
|
2536
|
+
};
|
|
2537
|
+
const decomposedPrompt = formatDecomposition(decomposition);
|
|
2538
|
+
try {
|
|
2539
|
+
let lastError;
|
|
2540
|
+
for (let attempt = 0; attempt < RETRY_TEMPERATURES2.length; attempt++) {
|
|
2541
|
+
const temperature = RETRY_TEMPERATURES2[attempt];
|
|
2542
|
+
const agentInstance = decompositionSqlAgent.clone({
|
|
2543
|
+
model: wrapLanguageModel3({
|
|
2544
|
+
model: decompositionSqlAgent.model,
|
|
2545
|
+
middleware: defaultSettingsMiddleware3({
|
|
2546
|
+
settings: { temperature }
|
|
2547
|
+
})
|
|
2548
|
+
})
|
|
2549
|
+
});
|
|
2550
|
+
const prompt = lastError ? `${decomposedPrompt}
|
|
2551
|
+
|
|
2552
|
+
Previous attempt failed with: ${lastError}. Please fix the query.` : decomposedPrompt;
|
|
2553
|
+
const { experimental_output: output } = await generate4(
|
|
2554
|
+
agentInstance,
|
|
2555
|
+
[user3(prompt)],
|
|
2556
|
+
state
|
|
2557
|
+
);
|
|
2558
|
+
if ("error" in output) {
|
|
2559
|
+
return {
|
|
2560
|
+
success: false,
|
|
2561
|
+
question,
|
|
2562
|
+
decomposition,
|
|
2563
|
+
error: output.error,
|
|
2564
|
+
attempts: attempt + 1
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
const validationError = await state.adapter.validate(output.sql);
|
|
2568
|
+
if (validationError) {
|
|
2569
|
+
lastError = validationError;
|
|
2570
|
+
continue;
|
|
2571
|
+
}
|
|
2572
|
+
const data = await state.adapter.execute(output.sql);
|
|
2573
|
+
return {
|
|
2574
|
+
success: true,
|
|
2575
|
+
question,
|
|
2576
|
+
decomposition,
|
|
2577
|
+
sql: output.sql,
|
|
2578
|
+
data,
|
|
2579
|
+
reasoning: output.reasoning,
|
|
2580
|
+
attempts: attempt + 1
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
return {
|
|
2584
|
+
success: false,
|
|
2585
|
+
question,
|
|
2586
|
+
decomposition,
|
|
2587
|
+
error: `Failed after ${RETRY_TEMPERATURES2.length} attempts. Last error: ${lastError}`,
|
|
2588
|
+
attempts: RETRY_TEMPERATURES2.length
|
|
2589
|
+
};
|
|
2590
|
+
} catch (error) {
|
|
2591
|
+
return {
|
|
2592
|
+
success: false,
|
|
2593
|
+
question,
|
|
2594
|
+
decomposition,
|
|
2595
|
+
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
1054
2599
|
}),
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
2600
|
+
execute_sql: tool7({
|
|
2601
|
+
description: `Execute a SQL query directly. Use for follow-up queries or when you already have SQL.`,
|
|
2602
|
+
inputSchema: z10.object({
|
|
2603
|
+
sql: z10.string().min(1).refine(
|
|
2604
|
+
(sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
|
|
2605
|
+
{
|
|
2606
|
+
message: "Only read-only SELECT or WITH queries are allowed."
|
|
2607
|
+
}
|
|
2608
|
+
).describe("The SQL query to execute.")
|
|
2609
|
+
}),
|
|
2610
|
+
execute: async ({ sql }, options) => {
|
|
2611
|
+
const state = toState7(options);
|
|
2612
|
+
try {
|
|
2613
|
+
const validationError = await state.adapter.validate(sql);
|
|
2614
|
+
if (validationError) {
|
|
2615
|
+
return {
|
|
2616
|
+
success: false,
|
|
2617
|
+
error: `Validation failed: ${validationError}`
|
|
2618
|
+
};
|
|
2619
|
+
}
|
|
2620
|
+
const data = await state.adapter.execute(sql);
|
|
2621
|
+
return {
|
|
2622
|
+
success: true,
|
|
2623
|
+
data,
|
|
2624
|
+
rowCount: Array.isArray(data) ? data.length : void 0
|
|
2625
|
+
};
|
|
2626
|
+
} catch (error) {
|
|
2627
|
+
return {
|
|
2628
|
+
success: false,
|
|
2629
|
+
error: error instanceof Error ? error.message : "Execution failed"
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
1058
2633
|
}),
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
briefCache: new FileCache("brief-" + config.version),
|
|
1071
|
-
history: config.history,
|
|
1072
|
-
instructions: [...teachings_default, ...config.instructions ?? []],
|
|
1073
|
-
tools: config.tools ?? {},
|
|
1074
|
-
model: config.model,
|
|
1075
|
-
memory: config.memory,
|
|
1076
|
-
introspection: new FileCache("introspection-" + config.version)
|
|
1077
|
-
};
|
|
2634
|
+
scratchpad: scratchpad_tool7
|
|
2635
|
+
};
|
|
2636
|
+
function formatDecomposition(decomposition) {
|
|
2637
|
+
const parts = [
|
|
2638
|
+
`Original Question: ${decomposition.originalQuestion}`,
|
|
2639
|
+
"",
|
|
2640
|
+
"Question Breakdown:",
|
|
2641
|
+
...decomposition.breakdown.map((part, i) => ` ${i + 1}. ${part}`)
|
|
2642
|
+
];
|
|
2643
|
+
if (decomposition.entities?.length) {
|
|
2644
|
+
parts.push("", `Entities: ${decomposition.entities.join(", ")}`);
|
|
1078
2645
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
explainerAgent,
|
|
1082
|
-
[user("Explain this SQL.")],
|
|
1083
|
-
{ sql }
|
|
1084
|
-
);
|
|
1085
|
-
return experimental_output.explanation;
|
|
2646
|
+
if (decomposition.filters?.length) {
|
|
2647
|
+
parts.push("", `Filters: ${decomposition.filters.join(", ")}`);
|
|
1086
2648
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
}
|
|
1096
|
-
}),
|
|
1097
|
-
[user(query)],
|
|
1098
|
-
{
|
|
1099
|
-
teachings: toInstructions(
|
|
1100
|
-
"instructions",
|
|
1101
|
-
persona({
|
|
1102
|
-
name: "Freya",
|
|
1103
|
-
role: "You are an expert SQL query generator, answering business questions with accurate queries.",
|
|
1104
|
-
tone: "Your tone should be concise and business-friendly."
|
|
1105
|
-
}),
|
|
1106
|
-
...this.#config.instructions
|
|
1107
|
-
),
|
|
1108
|
-
adapter: this.#config.adapter,
|
|
1109
|
-
introspection
|
|
1110
|
-
}
|
|
2649
|
+
if (decomposition.aggregation) {
|
|
2650
|
+
parts.push("", `Aggregation: ${decomposition.aggregation}`);
|
|
2651
|
+
}
|
|
2652
|
+
if (decomposition.ambiguities?.length) {
|
|
2653
|
+
parts.push(
|
|
2654
|
+
"",
|
|
2655
|
+
"Potential Ambiguities:",
|
|
2656
|
+
...decomposition.ambiguities.map((a) => ` - ${a}`)
|
|
1111
2657
|
);
|
|
1112
|
-
|
|
2658
|
+
}
|
|
2659
|
+
parts.push(
|
|
2660
|
+
"",
|
|
2661
|
+
"Generate SQL that addresses each component of the breakdown."
|
|
2662
|
+
);
|
|
2663
|
+
return parts.join("\n");
|
|
2664
|
+
}
|
|
2665
|
+
var chat4Agent = agent10({
|
|
2666
|
+
name: "chat4-decomposition",
|
|
2667
|
+
model: groq10("openai/gpt-oss-20b"),
|
|
2668
|
+
tools: tools7,
|
|
2669
|
+
prompt: (state) => {
|
|
2670
|
+
return `
|
|
2671
|
+
${state?.teachings || ""}
|
|
2672
|
+
${state?.introspection || ""}
|
|
2673
|
+
|
|
2674
|
+
When answering questions that require database queries, use the query_with_decomposition tool.
|
|
2675
|
+
|
|
2676
|
+
IMPORTANT: You must break down the question into semantic parts - describe WHAT is being asked, not HOW to implement it.
|
|
2677
|
+
|
|
2678
|
+
Good breakdown example for "Which customers bought the most expensive products last quarter?":
|
|
2679
|
+
- "customers who made purchases" (entity relationship)
|
|
2680
|
+
- "products they purchased" (what products)
|
|
2681
|
+
- "expensive products - need definition" (filter criteria - note ambiguity)
|
|
2682
|
+
- "last quarter" (time filter)
|
|
2683
|
+
- "most - ranking by count or value?" (aggregation - note ambiguity)
|
|
2684
|
+
|
|
2685
|
+
Bad breakdown (too instructional):
|
|
2686
|
+
- "JOIN customers with orders" (this is HOW, not WHAT)
|
|
2687
|
+
- "Use ORDER BY and LIMIT" (this is implementation)
|
|
2688
|
+
|
|
2689
|
+
Break the question into its semantic aspects, and let the SQL specialist figure out the implementation.
|
|
2690
|
+
`;
|
|
2691
|
+
}
|
|
2692
|
+
});
|
|
2693
|
+
|
|
2694
|
+
// packages/text2sql/src/lib/synthesis/types.ts
|
|
2695
|
+
async function toPairs(producer) {
|
|
2696
|
+
const pairs = [];
|
|
2697
|
+
for await (const chunk of producer.produce()) {
|
|
2698
|
+
pairs.push(...chunk);
|
|
2699
|
+
}
|
|
2700
|
+
return pairs;
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
// packages/text2sql/src/lib/teach/teachings.ts
|
|
2704
|
+
function guidelines(options = {}) {
|
|
2705
|
+
const { date = "strict" } = options;
|
|
2706
|
+
const baseTeachings = [
|
|
2707
|
+
// Schema adherence
|
|
2708
|
+
hint(
|
|
2709
|
+
"Use only tables and columns that exist in the schema. Never reference non-existent entities."
|
|
2710
|
+
),
|
|
2711
|
+
hint(
|
|
2712
|
+
"If the user asks to show a table or entity without specifying columns, use SELECT *."
|
|
2713
|
+
),
|
|
2714
|
+
hint(
|
|
2715
|
+
"When showing items associated with another entity, include the item ID and the related details requested."
|
|
2716
|
+
),
|
|
2717
|
+
hint(
|
|
2718
|
+
'When asked to "show" items, list them unless the user explicitly asks to count or total.'
|
|
2719
|
+
),
|
|
2720
|
+
hint(
|
|
2721
|
+
"Use canonical/LowCardinality values verbatim for filtering; [rows/size] hints suggest when to aggregate instead of listing."
|
|
2722
|
+
),
|
|
2723
|
+
// Joins and relationships
|
|
2724
|
+
hint(
|
|
2725
|
+
"Use appropriate JOINs based on the relationships defined in the schema."
|
|
2726
|
+
),
|
|
2727
|
+
hint(
|
|
2728
|
+
"Favor PK/indexed columns for joins and filters; follow relationship metadata for join direction and cardinality."
|
|
2729
|
+
),
|
|
2730
|
+
// Aggregations and calculations
|
|
2731
|
+
hint(
|
|
2732
|
+
"Apply proper aggregations (COUNT, SUM, AVG, etc.) when the question implies summarization."
|
|
2733
|
+
),
|
|
2734
|
+
hint(
|
|
2735
|
+
'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.'
|
|
2736
|
+
),
|
|
2737
|
+
hint(
|
|
2738
|
+
"Use window functions when the question requires ranking, running totals, or comparisons across rows."
|
|
2739
|
+
),
|
|
2740
|
+
// Query semantics
|
|
2741
|
+
hint(
|
|
2742
|
+
'Words like "reach", "reached", "hit" with a value (e.g., "temperature reach 80") mean >= (greater than or equal), not = (exact match).'
|
|
2743
|
+
),
|
|
2744
|
+
hint(
|
|
2745
|
+
'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.'
|
|
2746
|
+
),
|
|
2747
|
+
hint(
|
|
2748
|
+
'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.'
|
|
2749
|
+
),
|
|
2750
|
+
hint(
|
|
2751
|
+
"Handle NULL values appropriately using IS NULL, IS NOT NULL, or COALESCE."
|
|
2752
|
+
),
|
|
2753
|
+
// Style and readability
|
|
2754
|
+
styleGuide({
|
|
2755
|
+
prefer: 'For table aliases, use the full table name (e.g., "FROM users AS users", "JOIN order_items AS order_items"). For column aliases, use descriptive names that reflect the data (e.g., "COUNT(*) AS total_orders").',
|
|
2756
|
+
never: "Use abbreviated table aliases (u, oi, ca) or generic positional aliases (t1, t2, a, b)."
|
|
2757
|
+
}),
|
|
2758
|
+
styleGuide({
|
|
2759
|
+
prefer: "Summaries should be concise, business-friendly, highlight key comparisons, and add a short helpful follow-up when useful."
|
|
2760
|
+
}),
|
|
2761
|
+
// Guardrails - Query safety
|
|
2762
|
+
guardrail({
|
|
2763
|
+
rule: "Generate ONLY valid, executable SQL.",
|
|
2764
|
+
reason: "Invalid SQL wastes resources and confuses users.",
|
|
2765
|
+
action: "Validate syntax and schema references before returning."
|
|
2766
|
+
}),
|
|
2767
|
+
guardrail({
|
|
2768
|
+
rule: "Only generate SELECT/WITH statements (read-only queries).",
|
|
2769
|
+
reason: "Prevents accidental data modification.",
|
|
2770
|
+
action: "Never generate INSERT, UPDATE, DELETE, DROP, or other DDL/DML statements."
|
|
2771
|
+
}),
|
|
2772
|
+
guardrail({
|
|
2773
|
+
rule: "Avoid unbounded scans on large tables.",
|
|
2774
|
+
reason: "Protects performance and prevents runaway queries.",
|
|
2775
|
+
action: "Ensure filters are applied on indexed columns before querying broad fact tables."
|
|
2776
|
+
}),
|
|
2777
|
+
guardrail({
|
|
2778
|
+
rule: "Do not add LIMIT unless explicitly requested.",
|
|
2779
|
+
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.',
|
|
2780
|
+
reason: "Adding arbitrary limits changes query semantics."
|
|
2781
|
+
}),
|
|
2782
|
+
guardrail({
|
|
2783
|
+
rule: "Add ORDER BY where appropriate for deterministic results.",
|
|
2784
|
+
reason: "Ensures consistent query output.",
|
|
2785
|
+
action: "Include ORDER BY when results have a natural ordering or when combined with LIMIT."
|
|
2786
|
+
}),
|
|
2787
|
+
guardrail({
|
|
2788
|
+
rule: "Prevent cartesian or guesswork joins.",
|
|
2789
|
+
reason: "Protect correctness and performance.",
|
|
2790
|
+
action: "If join keys are missing or unclear, inspect relationships and ask for the intended join path before executing."
|
|
2791
|
+
}),
|
|
2792
|
+
guardrail({
|
|
2793
|
+
rule: "Ensure the query is optimized for the schema.",
|
|
2794
|
+
reason: "Better performance and resource usage.",
|
|
2795
|
+
action: "Use indexed columns for filtering, avoid SELECT * on large joins, prefer specific column selection when appropriate."
|
|
2796
|
+
}),
|
|
2797
|
+
guardrail({
|
|
2798
|
+
rule: "When facing genuine ambiguity with multiple valid interpretations, seek clarification.",
|
|
2799
|
+
reason: "Prevents incorrect assumptions in edge cases.",
|
|
2800
|
+
action: "Ask a focused clarifying question before proceeding with a guess."
|
|
2801
|
+
}),
|
|
2802
|
+
// Clarifications
|
|
2803
|
+
clarification({
|
|
2804
|
+
when: 'The request uses ambiguous scoring or ranking language (e.g., "top", "best", "active") without a metric.',
|
|
2805
|
+
ask: "Clarify the ranking metric or definition before writing the query.",
|
|
2806
|
+
reason: "Ensures the correct aggregation/ordering is used."
|
|
2807
|
+
}),
|
|
2808
|
+
// Workflow
|
|
2809
|
+
workflow({
|
|
2810
|
+
task: "SQL generation plan",
|
|
2811
|
+
steps: [
|
|
2812
|
+
'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.',
|
|
2813
|
+
"Translate the question into SQL patterns (aggregation, segmentation, time range, ranking) only if no column name match.",
|
|
2814
|
+
"Choose tables/relations that satisfy those patterns; note lookup tables and filter values implied by schema hints.",
|
|
2815
|
+
"Sketch join/filter/aggregation order considering table sizes, indexes, and stats.",
|
|
2816
|
+
"Generate precise, validated SQL that answers the question."
|
|
2817
|
+
]
|
|
2818
|
+
})
|
|
2819
|
+
];
|
|
2820
|
+
if (date === "strict") {
|
|
2821
|
+
baseTeachings.push(
|
|
2822
|
+
clarification({
|
|
2823
|
+
when: "The request targets time-based data without a date range.",
|
|
2824
|
+
ask: "Confirm the intended timeframe (e.g., last 30/90 days, YTD, specific year).",
|
|
2825
|
+
reason: "Prevents large scans and irrelevant results."
|
|
2826
|
+
})
|
|
2827
|
+
);
|
|
2828
|
+
} else {
|
|
2829
|
+
baseTeachings.push(
|
|
2830
|
+
hint(
|
|
2831
|
+
'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.'
|
|
2832
|
+
)
|
|
2833
|
+
);
|
|
2834
|
+
}
|
|
2835
|
+
return baseTeachings;
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
// packages/text2sql/src/lib/sql.ts
|
|
2839
|
+
var Text2Sql = class {
|
|
2840
|
+
#config;
|
|
2841
|
+
constructor(config) {
|
|
2842
|
+
this.#config = {
|
|
2843
|
+
adapter: config.adapter,
|
|
2844
|
+
history: config.history,
|
|
2845
|
+
instructions: [
|
|
2846
|
+
...guidelines(config.teachingsOptions),
|
|
2847
|
+
...config.instructions ?? []
|
|
2848
|
+
],
|
|
2849
|
+
tools: config.tools ?? {},
|
|
2850
|
+
model: config.model,
|
|
2851
|
+
memory: config.memory,
|
|
2852
|
+
introspection: new FileCache("introspection-" + config.version)
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
async explain(sql) {
|
|
2856
|
+
const { experimental_output } = await generate5(
|
|
2857
|
+
explainerAgent,
|
|
2858
|
+
[user4("Explain this SQL.")],
|
|
2859
|
+
{ sql }
|
|
2860
|
+
);
|
|
2861
|
+
return experimental_output.explanation;
|
|
2862
|
+
}
|
|
2863
|
+
async toSql(input) {
|
|
2864
|
+
const introspection = await this.index();
|
|
2865
|
+
const result = await toSql({
|
|
2866
|
+
input,
|
|
2867
|
+
adapter: this.#config.adapter,
|
|
2868
|
+
introspection,
|
|
2869
|
+
instructions: this.#config.instructions,
|
|
2870
|
+
model: this.#config.model
|
|
2871
|
+
});
|
|
2872
|
+
return result.sql;
|
|
1113
2873
|
}
|
|
1114
2874
|
instruct(...dataset) {
|
|
1115
2875
|
this.#config.instructions.push(...dataset);
|
|
1116
2876
|
}
|
|
1117
|
-
async inspect(
|
|
2877
|
+
async inspect(agent11) {
|
|
1118
2878
|
const [grounding] = await Promise.all([this.index()]);
|
|
1119
2879
|
const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
|
|
1120
2880
|
(name) => name.startsWith("render_")
|
|
1121
2881
|
);
|
|
1122
2882
|
const allInstructions = [
|
|
1123
2883
|
...this.#config.instructions,
|
|
2884
|
+
guardrail({
|
|
2885
|
+
rule: "ALWAYS use `get_sample_rows` before writing queries that filter or compare against string columns.",
|
|
2886
|
+
reason: "Prevents SQL errors from wrong value formats.",
|
|
2887
|
+
action: "Target specific columns (e.g., get_sample_rows('table', ['status', 'type']))."
|
|
2888
|
+
}),
|
|
1124
2889
|
...renderToolNames.length ? [
|
|
1125
2890
|
hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
|
|
1126
2891
|
styleGuide({
|
|
@@ -1129,24 +2894,16 @@ var Text2Sql = class {
|
|
|
1129
2894
|
})
|
|
1130
2895
|
] : []
|
|
1131
2896
|
];
|
|
1132
|
-
const
|
|
1133
|
-
...
|
|
2897
|
+
const tools8 = Object.keys({
|
|
2898
|
+
...agent11.handoff.tools,
|
|
1134
2899
|
...this.#config.memory ? memoryTools : {},
|
|
1135
2900
|
...this.#config.tools
|
|
1136
2901
|
});
|
|
1137
2902
|
return {
|
|
1138
|
-
tools:
|
|
1139
|
-
prompt:
|
|
2903
|
+
tools: tools8,
|
|
2904
|
+
prompt: agent11.instructions({
|
|
1140
2905
|
introspection: grounding,
|
|
1141
|
-
teachings: toInstructions(
|
|
1142
|
-
"instructions",
|
|
1143
|
-
persona({
|
|
1144
|
-
name: "Freya",
|
|
1145
|
-
role: "You are an expert SQL query generator, answering business questions with accurate queries.",
|
|
1146
|
-
tone: "Your tone should be concise and business-friendly."
|
|
1147
|
-
}),
|
|
1148
|
-
...allInstructions
|
|
1149
|
-
)
|
|
2906
|
+
teachings: toInstructions("instructions", ...allInstructions)
|
|
1150
2907
|
})
|
|
1151
2908
|
};
|
|
1152
2909
|
}
|
|
@@ -1159,6 +2916,29 @@ var Text2Sql = class {
|
|
|
1159
2916
|
await this.#config.introspection.set(introspection);
|
|
1160
2917
|
return introspection;
|
|
1161
2918
|
}
|
|
2919
|
+
/**
|
|
2920
|
+
* Generate training data pairs using a producer factory.
|
|
2921
|
+
* The factory receives the configured adapter, so users don't need to pass it manually.
|
|
2922
|
+
*
|
|
2923
|
+
* @example
|
|
2924
|
+
* // Generate questions for existing SQL
|
|
2925
|
+
* const pairs = await text2sql.toPairs(
|
|
2926
|
+
* (adapter) => new SqlExtractor(sqls, adapter, { validateSql: true })
|
|
2927
|
+
* );
|
|
2928
|
+
*
|
|
2929
|
+
* @example
|
|
2930
|
+
* // Extract from chat history with validation
|
|
2931
|
+
* const pairs = await text2sql.toPairs(
|
|
2932
|
+
* (adapter) => new ValidatedProducer(
|
|
2933
|
+
* new MessageExtractor(messages),
|
|
2934
|
+
* adapter
|
|
2935
|
+
* )
|
|
2936
|
+
* );
|
|
2937
|
+
*/
|
|
2938
|
+
async toPairs(factory) {
|
|
2939
|
+
const producer = factory(this.#config.adapter);
|
|
2940
|
+
return toPairs(producer);
|
|
2941
|
+
}
|
|
1162
2942
|
// public async suggest() {
|
|
1163
2943
|
// const [introspection, adapterInfo] = await Promise.all([
|
|
1164
2944
|
// this.index(),
|
|
@@ -1191,6 +2971,11 @@ var Text2Sql = class {
|
|
|
1191
2971
|
);
|
|
1192
2972
|
const instructions = [
|
|
1193
2973
|
...this.#config.instructions,
|
|
2974
|
+
guardrail({
|
|
2975
|
+
rule: "ALWAYS use `get_sample_rows` before writing queries that filter or compare against string columns.",
|
|
2976
|
+
reason: "Prevents SQL errors from wrong value formats.",
|
|
2977
|
+
action: "Target specific columns (e.g., get_sample_rows('table', ['status', 'type']))."
|
|
2978
|
+
}),
|
|
1194
2979
|
...renderToolNames.length ? [
|
|
1195
2980
|
hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
|
|
1196
2981
|
styleGuide({
|
|
@@ -1199,6 +2984,10 @@ var Text2Sql = class {
|
|
|
1199
2984
|
})
|
|
1200
2985
|
] : []
|
|
1201
2986
|
];
|
|
2987
|
+
const originalMessage = [
|
|
2988
|
+
...chat.messages.map((it) => it.content),
|
|
2989
|
+
...messages
|
|
2990
|
+
];
|
|
1202
2991
|
const result = stream(
|
|
1203
2992
|
t_a_g.clone({
|
|
1204
2993
|
model: this.#config.model,
|
|
@@ -1208,7 +2997,7 @@ var Text2Sql = class {
|
|
|
1208
2997
|
...this.#config.tools
|
|
1209
2998
|
}
|
|
1210
2999
|
}),
|
|
1211
|
-
|
|
3000
|
+
originalMessage,
|
|
1212
3001
|
{
|
|
1213
3002
|
teachings: toInstructions(
|
|
1214
3003
|
"instructions",
|
|
@@ -1226,10 +3015,395 @@ var Text2Sql = class {
|
|
|
1226
3015
|
userId: params.userId
|
|
1227
3016
|
}
|
|
1228
3017
|
);
|
|
3018
|
+
return this.#createUIMessageStream(
|
|
3019
|
+
result,
|
|
3020
|
+
messages,
|
|
3021
|
+
params,
|
|
3022
|
+
originalMessage
|
|
3023
|
+
);
|
|
3024
|
+
}
|
|
3025
|
+
/**
|
|
3026
|
+
* Chat1 - Combined tool, no peek.
|
|
3027
|
+
*
|
|
3028
|
+
* Uses a single `query_database` tool that:
|
|
3029
|
+
* 1. Takes a natural language question
|
|
3030
|
+
* 2. Internally calls toSql() to generate validated SQL
|
|
3031
|
+
* 3. Executes the SQL
|
|
3032
|
+
* 4. Returns both SQL and results
|
|
3033
|
+
*
|
|
3034
|
+
* The agent does NOT see the SQL before execution.
|
|
3035
|
+
*/
|
|
3036
|
+
async chat1(messages, params) {
|
|
3037
|
+
const [introspection, userTeachables] = await Promise.all([
|
|
3038
|
+
this.index({ onProgress: console.log }),
|
|
3039
|
+
this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
|
|
3040
|
+
]);
|
|
3041
|
+
const chat = await this.#config.history.upsertChat({
|
|
3042
|
+
id: params.chatId,
|
|
3043
|
+
userId: params.userId,
|
|
3044
|
+
title: "Chat " + params.chatId
|
|
3045
|
+
});
|
|
3046
|
+
const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
|
|
3047
|
+
(name) => name.startsWith("render_")
|
|
3048
|
+
);
|
|
3049
|
+
const instructions = [
|
|
3050
|
+
...this.#config.instructions,
|
|
3051
|
+
...renderToolNames.length ? [
|
|
3052
|
+
hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
|
|
3053
|
+
styleGuide({
|
|
3054
|
+
prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
|
|
3055
|
+
always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
|
|
3056
|
+
})
|
|
3057
|
+
] : []
|
|
3058
|
+
];
|
|
3059
|
+
const originalMessage = [
|
|
3060
|
+
...chat.messages.map((it) => it.content),
|
|
3061
|
+
...messages
|
|
3062
|
+
];
|
|
3063
|
+
const result = stream(
|
|
3064
|
+
chat1Agent.clone({
|
|
3065
|
+
model: this.#config.model,
|
|
3066
|
+
tools: {
|
|
3067
|
+
...tools4,
|
|
3068
|
+
...this.#config.memory ? memoryTools : {},
|
|
3069
|
+
...this.#config.tools
|
|
3070
|
+
}
|
|
3071
|
+
}),
|
|
3072
|
+
originalMessage,
|
|
3073
|
+
{
|
|
3074
|
+
teachings: toInstructions(
|
|
3075
|
+
"instructions",
|
|
3076
|
+
...instructions,
|
|
3077
|
+
teachable("user_profile", ...userTeachables)
|
|
3078
|
+
),
|
|
3079
|
+
adapter: this.#config.adapter,
|
|
3080
|
+
introspection,
|
|
3081
|
+
instructions: this.#config.instructions,
|
|
3082
|
+
memory: this.#config.memory,
|
|
3083
|
+
userId: params.userId
|
|
3084
|
+
}
|
|
3085
|
+
);
|
|
3086
|
+
return this.#createUIMessageStream(
|
|
3087
|
+
result,
|
|
3088
|
+
messages,
|
|
3089
|
+
params,
|
|
3090
|
+
originalMessage
|
|
3091
|
+
);
|
|
3092
|
+
}
|
|
3093
|
+
/**
|
|
3094
|
+
* Chat2 - Separate generate + execute tools (with peek).
|
|
3095
|
+
*
|
|
3096
|
+
* Uses two separate tools:
|
|
3097
|
+
* 1. `generate_sql` - Takes a question, returns validated SQL
|
|
3098
|
+
* 2. `execute_sql` - Takes SQL, executes it
|
|
3099
|
+
*
|
|
3100
|
+
* The agent sees the SQL before execution and can review/refine.
|
|
3101
|
+
*/
|
|
3102
|
+
async chat2(messages, params) {
|
|
3103
|
+
const [introspection, userTeachables] = await Promise.all([
|
|
3104
|
+
this.index({ onProgress: console.log }),
|
|
3105
|
+
this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
|
|
3106
|
+
]);
|
|
3107
|
+
const chat = await this.#config.history.upsertChat({
|
|
3108
|
+
id: params.chatId,
|
|
3109
|
+
userId: params.userId,
|
|
3110
|
+
title: "Chat " + params.chatId
|
|
3111
|
+
});
|
|
3112
|
+
const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
|
|
3113
|
+
(name) => name.startsWith("render_")
|
|
3114
|
+
);
|
|
3115
|
+
const instructions = [
|
|
3116
|
+
...this.#config.instructions,
|
|
3117
|
+
...renderToolNames.length ? [
|
|
3118
|
+
hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
|
|
3119
|
+
styleGuide({
|
|
3120
|
+
prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
|
|
3121
|
+
always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
|
|
3122
|
+
})
|
|
3123
|
+
] : []
|
|
3124
|
+
];
|
|
3125
|
+
const originalMessage = [
|
|
3126
|
+
...chat.messages.map((it) => it.content),
|
|
3127
|
+
...messages
|
|
3128
|
+
];
|
|
3129
|
+
const result = stream(
|
|
3130
|
+
chat2Agent.clone({
|
|
3131
|
+
model: this.#config.model,
|
|
3132
|
+
tools: {
|
|
3133
|
+
...tools5,
|
|
3134
|
+
...this.#config.memory ? memoryTools : {},
|
|
3135
|
+
...this.#config.tools
|
|
3136
|
+
}
|
|
3137
|
+
}),
|
|
3138
|
+
originalMessage,
|
|
3139
|
+
{
|
|
3140
|
+
teachings: toInstructions(
|
|
3141
|
+
"instructions",
|
|
3142
|
+
...instructions,
|
|
3143
|
+
teachable("user_profile", ...userTeachables)
|
|
3144
|
+
),
|
|
3145
|
+
adapter: this.#config.adapter,
|
|
3146
|
+
introspection,
|
|
3147
|
+
instructions: this.#config.instructions,
|
|
3148
|
+
memory: this.#config.memory,
|
|
3149
|
+
userId: params.userId
|
|
3150
|
+
}
|
|
3151
|
+
);
|
|
3152
|
+
return this.#createUIMessageStream(
|
|
3153
|
+
result,
|
|
3154
|
+
messages,
|
|
3155
|
+
params,
|
|
3156
|
+
originalMessage
|
|
3157
|
+
);
|
|
3158
|
+
}
|
|
3159
|
+
/**
|
|
3160
|
+
* Chat3 - Agent conversation/collaboration.
|
|
3161
|
+
*
|
|
3162
|
+
* Enables richer interaction where the SQL agent can:
|
|
3163
|
+
* - Surface confidence levels
|
|
3164
|
+
* - State assumptions
|
|
3165
|
+
* - Request clarification when uncertain
|
|
3166
|
+
*/
|
|
3167
|
+
async chat3(messages, params) {
|
|
3168
|
+
const [introspection, userTeachables] = await Promise.all([
|
|
3169
|
+
this.index({ onProgress: console.log }),
|
|
3170
|
+
this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
|
|
3171
|
+
]);
|
|
3172
|
+
const chat = await this.#config.history.upsertChat({
|
|
3173
|
+
id: params.chatId,
|
|
3174
|
+
userId: params.userId,
|
|
3175
|
+
title: "Chat " + params.chatId
|
|
3176
|
+
});
|
|
3177
|
+
const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
|
|
3178
|
+
(name) => name.startsWith("render_")
|
|
3179
|
+
);
|
|
3180
|
+
const instructions = [
|
|
3181
|
+
...this.#config.instructions,
|
|
3182
|
+
...renderToolNames.length ? [
|
|
3183
|
+
hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
|
|
3184
|
+
styleGuide({
|
|
3185
|
+
prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
|
|
3186
|
+
always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
|
|
3187
|
+
})
|
|
3188
|
+
] : []
|
|
3189
|
+
];
|
|
3190
|
+
const originalMessage = [
|
|
3191
|
+
...chat.messages.map((it) => it.content),
|
|
3192
|
+
...messages
|
|
3193
|
+
];
|
|
3194
|
+
const result = stream(
|
|
3195
|
+
chat3Agent.clone({
|
|
3196
|
+
model: this.#config.model,
|
|
3197
|
+
tools: {
|
|
3198
|
+
...tools6,
|
|
3199
|
+
...this.#config.memory ? memoryTools : {},
|
|
3200
|
+
...this.#config.tools
|
|
3201
|
+
}
|
|
3202
|
+
}),
|
|
3203
|
+
originalMessage,
|
|
3204
|
+
{
|
|
3205
|
+
teachings: toInstructions(
|
|
3206
|
+
"instructions",
|
|
3207
|
+
...instructions,
|
|
3208
|
+
teachable("user_profile", ...userTeachables)
|
|
3209
|
+
),
|
|
3210
|
+
adapter: this.#config.adapter,
|
|
3211
|
+
introspection,
|
|
3212
|
+
instructions: this.#config.instructions,
|
|
3213
|
+
memory: this.#config.memory,
|
|
3214
|
+
userId: params.userId
|
|
3215
|
+
}
|
|
3216
|
+
);
|
|
3217
|
+
return this.#createUIMessageStream(
|
|
3218
|
+
result,
|
|
3219
|
+
messages,
|
|
3220
|
+
params,
|
|
3221
|
+
originalMessage
|
|
3222
|
+
);
|
|
3223
|
+
}
|
|
3224
|
+
/**
|
|
3225
|
+
* Chat4 - Question decomposition approach.
|
|
3226
|
+
*
|
|
3227
|
+
* Breaks down questions into semantic components before SQL generation:
|
|
3228
|
+
* - entities: Key concepts mentioned
|
|
3229
|
+
* - filters: Filtering criteria
|
|
3230
|
+
* - aggregation: Type of aggregation
|
|
3231
|
+
* - breakdown: Semantic parts of the question
|
|
3232
|
+
*
|
|
3233
|
+
* This helps ensure all aspects of the question are addressed.
|
|
3234
|
+
*/
|
|
3235
|
+
async chat4(messages, params) {
|
|
3236
|
+
const [introspection, userTeachables] = await Promise.all([
|
|
3237
|
+
this.index({ onProgress: console.log }),
|
|
3238
|
+
this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
|
|
3239
|
+
]);
|
|
3240
|
+
const chat = await this.#config.history.upsertChat({
|
|
3241
|
+
id: params.chatId,
|
|
3242
|
+
userId: params.userId,
|
|
3243
|
+
title: "Chat " + params.chatId
|
|
3244
|
+
});
|
|
3245
|
+
const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
|
|
3246
|
+
(name) => name.startsWith("render_")
|
|
3247
|
+
);
|
|
3248
|
+
const instructions = [
|
|
3249
|
+
...this.#config.instructions,
|
|
3250
|
+
...renderToolNames.length ? [
|
|
3251
|
+
hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
|
|
3252
|
+
styleGuide({
|
|
3253
|
+
prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
|
|
3254
|
+
always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
|
|
3255
|
+
})
|
|
3256
|
+
] : []
|
|
3257
|
+
];
|
|
3258
|
+
const originalMessage = [
|
|
3259
|
+
...chat.messages.map((it) => it.content),
|
|
3260
|
+
...messages
|
|
3261
|
+
];
|
|
3262
|
+
const result = stream(
|
|
3263
|
+
chat4Agent.clone({
|
|
3264
|
+
model: this.#config.model,
|
|
3265
|
+
tools: {
|
|
3266
|
+
...tools7,
|
|
3267
|
+
...this.#config.memory ? memoryTools : {},
|
|
3268
|
+
...this.#config.tools
|
|
3269
|
+
}
|
|
3270
|
+
}),
|
|
3271
|
+
originalMessage,
|
|
3272
|
+
{
|
|
3273
|
+
teachings: toInstructions(
|
|
3274
|
+
"instructions",
|
|
3275
|
+
...instructions,
|
|
3276
|
+
teachable("user_profile", ...userTeachables)
|
|
3277
|
+
),
|
|
3278
|
+
adapter: this.#config.adapter,
|
|
3279
|
+
introspection,
|
|
3280
|
+
instructions: this.#config.instructions,
|
|
3281
|
+
memory: this.#config.memory,
|
|
3282
|
+
userId: params.userId
|
|
3283
|
+
}
|
|
3284
|
+
);
|
|
3285
|
+
return this.#createUIMessageStream(
|
|
3286
|
+
result,
|
|
3287
|
+
messages,
|
|
3288
|
+
params,
|
|
3289
|
+
originalMessage
|
|
3290
|
+
);
|
|
3291
|
+
}
|
|
3292
|
+
/**
|
|
3293
|
+
* Business intelligence focused chat agent.
|
|
3294
|
+
*
|
|
3295
|
+
* Creates dashboards using MDX components with embedded SQL queries.
|
|
3296
|
+
* The agent explores data, validates SQL, and outputs markdown with
|
|
3297
|
+
* JSX chart components that the frontend renders via MDX.
|
|
3298
|
+
*
|
|
3299
|
+
* @example
|
|
3300
|
+
* ```typescript
|
|
3301
|
+
* const result = await text2sql.bi(
|
|
3302
|
+
* [user("Show me a sales dashboard for last 30 days")],
|
|
3303
|
+
* { chatId: 'dashboard-1', userId: 'user-1' }
|
|
3304
|
+
* );
|
|
3305
|
+
* // Result contains markdown with <BarChart sql="..." />, <KPI sql="..." />, etc.
|
|
3306
|
+
* ```
|
|
3307
|
+
*/
|
|
3308
|
+
async bi(messages, params) {
|
|
3309
|
+
const [introspection, userTeachables] = await Promise.all([
|
|
3310
|
+
this.index({ onProgress: console.log }),
|
|
3311
|
+
this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
|
|
3312
|
+
]);
|
|
3313
|
+
const chat = await this.#config.history.upsertChat({
|
|
3314
|
+
id: params.chatId,
|
|
3315
|
+
userId: params.userId,
|
|
3316
|
+
title: "Chat " + params.chatId
|
|
3317
|
+
});
|
|
3318
|
+
const originalMessages = [
|
|
3319
|
+
...chat.messages.map((it) => it.content),
|
|
3320
|
+
...messages
|
|
3321
|
+
];
|
|
3322
|
+
const result = stream(
|
|
3323
|
+
biAgent.clone({
|
|
3324
|
+
model: this.#config.model
|
|
3325
|
+
}),
|
|
3326
|
+
originalMessages,
|
|
3327
|
+
{
|
|
3328
|
+
teachings: toInstructions(
|
|
3329
|
+
"instructions",
|
|
3330
|
+
...this.#config.instructions,
|
|
3331
|
+
teachable("user_profile", ...userTeachables)
|
|
3332
|
+
),
|
|
3333
|
+
adapter: this.#config.adapter,
|
|
3334
|
+
introspection
|
|
3335
|
+
}
|
|
3336
|
+
);
|
|
3337
|
+
return this.#createUIMessageStream(
|
|
3338
|
+
result,
|
|
3339
|
+
messages,
|
|
3340
|
+
params,
|
|
3341
|
+
originalMessages
|
|
3342
|
+
);
|
|
3343
|
+
}
|
|
3344
|
+
/**
|
|
3345
|
+
* Developer-focused conversational interface for SQL generation.
|
|
3346
|
+
*
|
|
3347
|
+
* Provides power-user tools for query building without execution:
|
|
3348
|
+
* - generate_sql: Convert natural language to validated SQL
|
|
3349
|
+
* - validate_sql: Check SQL syntax
|
|
3350
|
+
* - explain_sql: Get plain-English explanations
|
|
3351
|
+
* - show_schema: Explore database schema on demand
|
|
3352
|
+
*
|
|
3353
|
+
* @example
|
|
3354
|
+
* ```typescript
|
|
3355
|
+
* const result = await text2sql.developer(
|
|
3356
|
+
* [user("Generate a query to find top customers by revenue")],
|
|
3357
|
+
* { chatId: 'dev-session-1', userId: 'dev-1' }
|
|
3358
|
+
* );
|
|
3359
|
+
* // Agent responds with SQL, can validate, explain, or refine iteratively
|
|
3360
|
+
* ```
|
|
3361
|
+
*/
|
|
3362
|
+
async developer(messages, params) {
|
|
3363
|
+
const [introspection, userTeachables] = await Promise.all([
|
|
3364
|
+
this.index({ onProgress: console.log }),
|
|
3365
|
+
this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
|
|
3366
|
+
]);
|
|
3367
|
+
const chat = await this.#config.history.upsertChat({
|
|
3368
|
+
id: params.chatId,
|
|
3369
|
+
userId: params.userId,
|
|
3370
|
+
title: "Chat " + params.chatId
|
|
3371
|
+
});
|
|
3372
|
+
const originalMessages = [
|
|
3373
|
+
...chat.messages.map((it) => it.content),
|
|
3374
|
+
...messages
|
|
3375
|
+
];
|
|
3376
|
+
const result = stream(
|
|
3377
|
+
developerAgent.clone({
|
|
3378
|
+
model: this.#config.model
|
|
3379
|
+
}),
|
|
3380
|
+
originalMessages,
|
|
3381
|
+
{
|
|
3382
|
+
teachings: toInstructions(
|
|
3383
|
+
"instructions",
|
|
3384
|
+
...this.#config.instructions,
|
|
3385
|
+
teachable("user_profile", ...userTeachables)
|
|
3386
|
+
),
|
|
3387
|
+
adapter: this.#config.adapter,
|
|
3388
|
+
introspection,
|
|
3389
|
+
instructions: this.#config.instructions
|
|
3390
|
+
}
|
|
3391
|
+
);
|
|
3392
|
+
return this.#createUIMessageStream(
|
|
3393
|
+
result,
|
|
3394
|
+
messages,
|
|
3395
|
+
params,
|
|
3396
|
+
originalMessages
|
|
3397
|
+
);
|
|
3398
|
+
}
|
|
3399
|
+
/**
|
|
3400
|
+
* Helper to create UI message stream with common error handling and persistence.
|
|
3401
|
+
*/
|
|
3402
|
+
#createUIMessageStream(result, messages, params, originalMessages) {
|
|
1229
3403
|
return result.toUIMessageStream({
|
|
1230
3404
|
onError: (error) => {
|
|
1231
3405
|
if (NoSuchToolError.isInstance(error)) {
|
|
1232
|
-
return "The model tried to call
|
|
3406
|
+
return "The model tried to call an unknown tool.";
|
|
1233
3407
|
} else if (InvalidToolInputError.isInstance(error)) {
|
|
1234
3408
|
return "The model called a tool with invalid arguments.";
|
|
1235
3409
|
} else if (ToolCallRepairError.isInstance(error)) {
|
|
@@ -1242,46 +3416,54 @@ var Text2Sql = class {
|
|
|
1242
3416
|
sendFinish: true,
|
|
1243
3417
|
sendReasoning: true,
|
|
1244
3418
|
sendSources: true,
|
|
1245
|
-
originalMessages
|
|
3419
|
+
originalMessages,
|
|
1246
3420
|
generateMessageId: generateId,
|
|
1247
|
-
onFinish: async ({
|
|
1248
|
-
const userMessage =
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
3421
|
+
onFinish: async ({ responseMessage, isContinuation }) => {
|
|
3422
|
+
const userMessage = messages.at(-1);
|
|
3423
|
+
if (!isContinuation && userMessage) {
|
|
3424
|
+
console.log(
|
|
3425
|
+
"Saving user message to history:",
|
|
3426
|
+
JSON.stringify(userMessage)
|
|
3427
|
+
);
|
|
3428
|
+
await this.#config.history.addMessage({
|
|
3429
|
+
id: v72(),
|
|
3430
|
+
chatId: params.chatId,
|
|
3431
|
+
role: userMessage.role,
|
|
3432
|
+
content: userMessage
|
|
3433
|
+
});
|
|
1252
3434
|
}
|
|
1253
3435
|
await this.#config.history.addMessage({
|
|
1254
3436
|
id: v72(),
|
|
1255
3437
|
chatId: params.chatId,
|
|
1256
|
-
role:
|
|
1257
|
-
content:
|
|
1258
|
-
});
|
|
1259
|
-
await this.#config.history.addMessage({
|
|
1260
|
-
id: v72(),
|
|
1261
|
-
chatId: params.chatId,
|
|
1262
|
-
role: botMessage.role,
|
|
1263
|
-
content: botMessage
|
|
3438
|
+
role: responseMessage.role,
|
|
3439
|
+
content: responseMessage
|
|
1264
3440
|
});
|
|
1265
3441
|
}
|
|
1266
3442
|
});
|
|
1267
3443
|
}
|
|
1268
3444
|
};
|
|
1269
|
-
|
|
1270
|
-
// packages/text2sql/src/index.ts
|
|
1271
|
-
if (import.meta.main) {
|
|
1272
|
-
}
|
|
1273
3445
|
export {
|
|
3446
|
+
Adapter,
|
|
3447
|
+
Checkpoint,
|
|
1274
3448
|
FileCache,
|
|
1275
3449
|
History,
|
|
1276
3450
|
InMemoryHistory,
|
|
1277
3451
|
InMemoryTeachablesStore,
|
|
1278
3452
|
JsonCache,
|
|
3453
|
+
Point,
|
|
1279
3454
|
SqliteHistory,
|
|
1280
3455
|
SqliteTeachablesStore,
|
|
1281
3456
|
TeachablesStore,
|
|
1282
3457
|
Text2Sql,
|
|
3458
|
+
applyTablesFilter,
|
|
3459
|
+
developerAgent,
|
|
3460
|
+
filterRelationshipsByTables,
|
|
3461
|
+
filterTablesByName,
|
|
3462
|
+
getTablesWithRelated,
|
|
3463
|
+
guidelines,
|
|
3464
|
+
hashConfig,
|
|
3465
|
+
matchesFilter,
|
|
1283
3466
|
memoryTools,
|
|
1284
|
-
sqlQueryAgent,
|
|
1285
3467
|
suggestionsAgent,
|
|
1286
3468
|
t_a_g
|
|
1287
3469
|
};
|