@deepagents/text2sql 0.10.2 → 0.11.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 +32 -41
- package/dist/index.d.ts +1 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2338 -2398
- package/dist/index.js.map +4 -4
- package/dist/lib/adapters/adapter.d.ts +13 -1
- package/dist/lib/adapters/adapter.d.ts.map +1 -1
- package/dist/lib/adapters/groundings/abstract.grounding.d.ts +19 -3
- package/dist/lib/adapters/groundings/abstract.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/groundings/column-stats.grounding.d.ts +1 -2
- package/dist/lib/adapters/groundings/column-stats.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/groundings/column-values.grounding.d.ts +1 -2
- package/dist/lib/adapters/groundings/column-values.grounding.d.ts.map +1 -1
- 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/index.js +15 -222
- package/dist/lib/adapters/groundings/index.js.map +3 -3
- 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 +3 -3
- 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/index.js +343 -315
- package/dist/lib/adapters/mysql/index.js.map +4 -4
- package/dist/lib/adapters/postgres/index.js +385 -357
- package/dist/lib/adapters/postgres/index.js.map +4 -4
- package/dist/lib/adapters/spreadsheet/index.js +290 -223
- package/dist/lib/adapters/spreadsheet/index.js.map +4 -4
- package/dist/lib/adapters/sqlite/index.js +307 -279
- package/dist/lib/adapters/sqlite/index.js.map +4 -4
- package/dist/lib/adapters/sqlserver/index.js +383 -355
- package/dist/lib/adapters/sqlserver/index.js.map +4 -4
- package/dist/lib/agents/developer.agent.d.ts +33 -23
- package/dist/lib/agents/developer.agent.d.ts.map +1 -1
- package/dist/lib/agents/sql.agent.d.ts +4 -4
- package/dist/lib/agents/sql.agent.d.ts.map +1 -1
- package/dist/lib/agents/teachables.agent.d.ts +2 -2
- package/dist/lib/agents/teachables.agent.d.ts.map +1 -1
- package/dist/lib/agents/text2sql.agent.d.ts +18 -71
- package/dist/lib/agents/text2sql.agent.d.ts.map +1 -1
- package/dist/lib/fragments/schema.d.ts +214 -0
- package/dist/lib/fragments/schema.d.ts.map +1 -0
- package/dist/lib/instructions.d.ts +29 -2
- package/dist/lib/instructions.d.ts.map +1 -1
- package/dist/lib/instructions.js +336 -319
- package/dist/lib/instructions.js.map +4 -4
- package/dist/lib/sql.d.ts +13 -103
- package/dist/lib/sql.d.ts.map +1 -1
- package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts +2 -2
- package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts.map +1 -1
- package/dist/lib/synthesis/extractors/message-extractor.d.ts +1 -2
- package/dist/lib/synthesis/extractors/message-extractor.d.ts.map +1 -1
- package/dist/lib/synthesis/extractors/sql-extractor.d.ts.map +1 -1
- package/dist/lib/synthesis/index.js +1794 -572
- package/dist/lib/synthesis/index.js.map +4 -4
- package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts.map +1 -1
- package/dist/lib/synthesis/synthesizers/persona-generator.d.ts +7 -17
- package/dist/lib/synthesis/synthesizers/persona-generator.d.ts.map +1 -1
- package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts +2 -2
- package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts.map +1 -1
- package/dist/lib/synthesis/synthesizers/teachings-generator.d.ts +8 -20
- package/dist/lib/synthesis/synthesizers/teachings-generator.d.ts.map +1 -1
- package/dist/lib/teach/teachings.d.ts +2 -2
- package/dist/lib/teach/teachings.d.ts.map +1 -1
- package/package.json +4 -3
- package/dist/lib/agents/chat1.agent.d.ts +0 -50
- package/dist/lib/agents/chat1.agent.d.ts.map +0 -1
- package/dist/lib/agents/chat2.agent.d.ts +0 -68
- package/dist/lib/agents/chat2.agent.d.ts.map +0 -1
- package/dist/lib/agents/chat3.agent.d.ts +0 -80
- package/dist/lib/agents/chat3.agent.d.ts.map +0 -1
- package/dist/lib/agents/chat4.agent.d.ts +0 -88
- package/dist/lib/agents/chat4.agent.d.ts.map +0 -1
- package/dist/lib/history/history.d.ts +0 -41
- package/dist/lib/history/history.d.ts.map +0 -1
- package/dist/lib/history/memory.history.d.ts +0 -5
- package/dist/lib/history/memory.history.d.ts.map +0 -1
- package/dist/lib/history/sqlite.history.d.ts +0 -15
- package/dist/lib/history/sqlite.history.d.ts.map +0 -1
- package/dist/lib/memory/memory.prompt.d.ts +0 -3
- package/dist/lib/memory/memory.prompt.d.ts.map +0 -1
- package/dist/lib/memory/memory.store.d.ts +0 -5
- package/dist/lib/memory/memory.store.d.ts.map +0 -1
- package/dist/lib/memory/sqlite.store.d.ts +0 -14
- package/dist/lib/memory/sqlite.store.d.ts.map +0 -1
- package/dist/lib/memory/store.d.ts +0 -40
- package/dist/lib/memory/store.d.ts.map +0 -1
- package/dist/lib/teach/teachables.d.ts +0 -648
- package/dist/lib/teach/teachables.d.ts.map +0 -1
- package/dist/lib/teach/xml.d.ts +0 -6
- package/dist/lib/teach/xml.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,94 @@
|
|
|
1
|
+
// packages/text2sql/src/lib/fragments/schema.ts
|
|
2
|
+
function dialectInfo(input) {
|
|
3
|
+
return {
|
|
4
|
+
name: "dialectInfo",
|
|
5
|
+
data: {
|
|
6
|
+
dialect: input.dialect,
|
|
7
|
+
...input.version && { version: input.version },
|
|
8
|
+
...input.database && { database: input.database }
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function table(input) {
|
|
13
|
+
return {
|
|
14
|
+
name: "table",
|
|
15
|
+
data: {
|
|
16
|
+
name: input.name,
|
|
17
|
+
...input.schema && { schema: input.schema },
|
|
18
|
+
...input.rowCount != null && { rowCount: input.rowCount },
|
|
19
|
+
...input.sizeHint && { sizeHint: input.sizeHint },
|
|
20
|
+
columns: input.columns,
|
|
21
|
+
...input.indexes?.length && { indexes: input.indexes },
|
|
22
|
+
...input.constraints?.length && { constraints: input.constraints }
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function column(input) {
|
|
27
|
+
return {
|
|
28
|
+
name: "column",
|
|
29
|
+
data: {
|
|
30
|
+
name: input.name,
|
|
31
|
+
type: input.type,
|
|
32
|
+
...input.pk && { pk: true },
|
|
33
|
+
...input.fk && { fk: input.fk },
|
|
34
|
+
...input.unique && { unique: true },
|
|
35
|
+
...input.notNull && { notNull: true },
|
|
36
|
+
...input.default && { default: input.default },
|
|
37
|
+
...input.indexed && { indexed: true },
|
|
38
|
+
...input.values?.length && { values: input.values },
|
|
39
|
+
...input.stats && { stats: input.stats }
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function index(input) {
|
|
44
|
+
return {
|
|
45
|
+
name: "index",
|
|
46
|
+
data: {
|
|
47
|
+
name: input.name,
|
|
48
|
+
columns: input.columns,
|
|
49
|
+
...input.unique && { unique: true },
|
|
50
|
+
...input.type && { type: input.type }
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function constraint(input) {
|
|
55
|
+
return {
|
|
56
|
+
name: "constraint",
|
|
57
|
+
data: {
|
|
58
|
+
name: input.name,
|
|
59
|
+
type: input.type,
|
|
60
|
+
...input.columns?.length && { columns: input.columns },
|
|
61
|
+
...input.definition && { definition: input.definition },
|
|
62
|
+
...input.defaultValue && { defaultValue: input.defaultValue },
|
|
63
|
+
...input.referencedTable && { referencedTable: input.referencedTable },
|
|
64
|
+
...input.referencedColumns?.length && {
|
|
65
|
+
referencedColumns: input.referencedColumns
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function view(input) {
|
|
71
|
+
return {
|
|
72
|
+
name: "view",
|
|
73
|
+
data: {
|
|
74
|
+
name: input.name,
|
|
75
|
+
...input.schema && { schema: input.schema },
|
|
76
|
+
columns: input.columns,
|
|
77
|
+
...input.definition && { definition: input.definition }
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function relationship(input) {
|
|
82
|
+
return {
|
|
83
|
+
name: "relationship",
|
|
84
|
+
data: {
|
|
85
|
+
from: input.from,
|
|
86
|
+
to: input.to,
|
|
87
|
+
...input.cardinality && { cardinality: input.cardinality }
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
1
92
|
// packages/text2sql/src/lib/adapters/groundings/context.ts
|
|
2
93
|
function createGroundingContext() {
|
|
3
94
|
return {
|
|
@@ -10,24 +101,169 @@ function createGroundingContext() {
|
|
|
10
101
|
|
|
11
102
|
// packages/text2sql/src/lib/adapters/adapter.ts
|
|
12
103
|
var Adapter = class {
|
|
104
|
+
/**
|
|
105
|
+
* Introspect the database schema and return context fragments.
|
|
106
|
+
*
|
|
107
|
+
* Executes all configured groundings to populate the context, then
|
|
108
|
+
* generates fragments from the complete context data.
|
|
109
|
+
*
|
|
110
|
+
* @param ctx - Optional grounding context for sharing state between groundings
|
|
111
|
+
* @returns Array of context fragments representing the database schema
|
|
112
|
+
*/
|
|
13
113
|
async introspect(ctx = createGroundingContext()) {
|
|
14
|
-
const lines = [];
|
|
15
114
|
for (const fn of this.grounding) {
|
|
16
115
|
const grounding = fn(this);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
116
|
+
await grounding.execute(ctx);
|
|
117
|
+
}
|
|
118
|
+
return this.#toSchemaFragments(ctx);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Convert complete grounding context to schema fragments.
|
|
122
|
+
* Called after all groundings have populated ctx with data.
|
|
123
|
+
*/
|
|
124
|
+
#toSchemaFragments(ctx) {
|
|
125
|
+
const fragments2 = [];
|
|
126
|
+
if (ctx.info) {
|
|
127
|
+
fragments2.push(
|
|
128
|
+
dialectInfo({
|
|
129
|
+
dialect: ctx.info.dialect,
|
|
130
|
+
version: ctx.info.version,
|
|
131
|
+
database: ctx.info.database
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
for (const t of ctx.tables) {
|
|
136
|
+
fragments2.push(this.#tableToFragment(t));
|
|
137
|
+
}
|
|
138
|
+
for (const v of ctx.views) {
|
|
139
|
+
fragments2.push(this.#viewToFragment(v));
|
|
140
|
+
}
|
|
141
|
+
const tableMap = new Map(ctx.tables.map((t) => [t.name, t]));
|
|
142
|
+
for (const rel of ctx.relationships) {
|
|
143
|
+
const sourceTable = tableMap.get(rel.table);
|
|
144
|
+
const targetTable = tableMap.get(rel.referenced_table);
|
|
145
|
+
fragments2.push(
|
|
146
|
+
this.#relationshipToFragment(rel, sourceTable, targetTable)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (ctx.report) {
|
|
150
|
+
fragments2.push({ name: "businessContext", data: ctx.report });
|
|
151
|
+
}
|
|
152
|
+
return fragments2;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Convert a Table to a table fragment with nested column, index, and constraint fragments.
|
|
156
|
+
*/
|
|
157
|
+
#tableToFragment(t) {
|
|
158
|
+
const pkConstraint = t.constraints?.find((c) => c.type === "PRIMARY_KEY");
|
|
159
|
+
const pkColumns = new Set(pkConstraint?.columns ?? []);
|
|
160
|
+
const notNullColumns = new Set(
|
|
161
|
+
t.constraints?.filter((c) => c.type === "NOT_NULL").flatMap((c) => c.columns ?? []) ?? []
|
|
162
|
+
);
|
|
163
|
+
const defaultByColumn = /* @__PURE__ */ new Map();
|
|
164
|
+
for (const c of t.constraints?.filter((c2) => c2.type === "DEFAULT") ?? []) {
|
|
165
|
+
for (const col of c.columns ?? []) {
|
|
166
|
+
if (c.defaultValue != null) {
|
|
167
|
+
defaultByColumn.set(col, c.defaultValue);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
21
170
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
171
|
+
const uniqueColumns = new Set(
|
|
172
|
+
t.constraints?.filter((c) => c.type === "UNIQUE" && c.columns?.length === 1).flatMap((c) => c.columns ?? []) ?? []
|
|
173
|
+
);
|
|
174
|
+
const fkByColumn = /* @__PURE__ */ new Map();
|
|
175
|
+
for (const c of t.constraints?.filter((c2) => c2.type === "FOREIGN_KEY") ?? []) {
|
|
176
|
+
const cols = c.columns ?? [];
|
|
177
|
+
const refCols = c.referencedColumns ?? [];
|
|
178
|
+
for (let i = 0; i < cols.length; i++) {
|
|
179
|
+
const refCol = refCols[i] ?? refCols[0] ?? cols[i];
|
|
180
|
+
fkByColumn.set(cols[i], `${c.referencedTable}.${refCol}`);
|
|
26
181
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
182
|
+
}
|
|
183
|
+
const columnFragments = t.columns.map(
|
|
184
|
+
(col) => column({
|
|
185
|
+
name: col.name,
|
|
186
|
+
type: col.type,
|
|
187
|
+
pk: pkColumns.has(col.name) || void 0,
|
|
188
|
+
fk: fkByColumn.get(col.name),
|
|
189
|
+
unique: uniqueColumns.has(col.name) || void 0,
|
|
190
|
+
notNull: notNullColumns.has(col.name) || void 0,
|
|
191
|
+
default: defaultByColumn.get(col.name),
|
|
192
|
+
indexed: col.isIndexed || void 0,
|
|
193
|
+
values: col.values,
|
|
194
|
+
stats: col.stats
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
const indexFragments = (t.indexes ?? []).map(
|
|
198
|
+
(idx) => index({
|
|
199
|
+
name: idx.name,
|
|
200
|
+
columns: idx.columns,
|
|
201
|
+
unique: idx.unique,
|
|
202
|
+
type: idx.type
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
const constraintFragments = (t.constraints ?? []).filter(
|
|
206
|
+
(c) => c.type === "CHECK" || c.type === "UNIQUE" && (c.columns?.length ?? 0) > 1
|
|
207
|
+
).map(
|
|
208
|
+
(c) => constraint({
|
|
209
|
+
name: c.name,
|
|
210
|
+
type: c.type,
|
|
211
|
+
columns: c.columns,
|
|
212
|
+
definition: c.definition
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
return table({
|
|
216
|
+
name: t.name,
|
|
217
|
+
schema: t.schema,
|
|
218
|
+
rowCount: t.rowCount,
|
|
219
|
+
sizeHint: t.sizeHint,
|
|
220
|
+
columns: columnFragments,
|
|
221
|
+
indexes: indexFragments.length > 0 ? indexFragments : void 0,
|
|
222
|
+
constraints: constraintFragments.length > 0 ? constraintFragments : void 0
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Convert a View to a view fragment with nested column fragments.
|
|
227
|
+
*/
|
|
228
|
+
#viewToFragment(v) {
|
|
229
|
+
const columnFragments = v.columns.map(
|
|
230
|
+
(col) => column({
|
|
231
|
+
name: col.name,
|
|
232
|
+
type: col.type,
|
|
233
|
+
values: col.values,
|
|
234
|
+
stats: col.stats
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
return view({
|
|
238
|
+
name: v.name,
|
|
239
|
+
schema: v.schema,
|
|
240
|
+
columns: columnFragments,
|
|
241
|
+
definition: v.definition
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Convert a Relationship to a relationship fragment.
|
|
246
|
+
* Infers cardinality from row counts if available.
|
|
247
|
+
*/
|
|
248
|
+
#relationshipToFragment(rel, sourceTable, targetTable) {
|
|
249
|
+
const sourceCount = sourceTable?.rowCount;
|
|
250
|
+
const targetCount = targetTable?.rowCount;
|
|
251
|
+
let cardinality;
|
|
252
|
+
if (sourceCount != null && targetCount != null && targetCount > 0) {
|
|
253
|
+
const ratio = sourceCount / targetCount;
|
|
254
|
+
if (ratio > 5) {
|
|
255
|
+
cardinality = "many-to-one";
|
|
256
|
+
} else if (ratio < 1.2 && ratio > 0.8) {
|
|
257
|
+
cardinality = "one-to-one";
|
|
258
|
+
} else if (ratio < 0.2) {
|
|
259
|
+
cardinality = "one-to-many";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return relationship({
|
|
263
|
+
from: { table: rel.table, columns: rel.from },
|
|
264
|
+
to: { table: rel.referenced_table, columns: rel.to },
|
|
265
|
+
cardinality
|
|
266
|
+
});
|
|
31
267
|
}
|
|
32
268
|
/**
|
|
33
269
|
* Convert unknown database value to number.
|
|
@@ -82,7 +318,7 @@ ${description}
|
|
|
82
318
|
};
|
|
83
319
|
function filterTablesByName(tables, filter) {
|
|
84
320
|
if (!filter) return tables;
|
|
85
|
-
return tables.filter((
|
|
321
|
+
return tables.filter((table2) => matchesFilter(table2.name, filter));
|
|
86
322
|
}
|
|
87
323
|
function filterRelationshipsByTables(relationships, tableNames) {
|
|
88
324
|
if (tableNames === void 0) {
|
|
@@ -103,7 +339,7 @@ function applyTablesFilter(tables, relationships, filter) {
|
|
|
103
339
|
getTablesWithRelated(tables, relationships, filter)
|
|
104
340
|
);
|
|
105
341
|
return {
|
|
106
|
-
tables: tables.filter((
|
|
342
|
+
tables: tables.filter((table2) => allowedNames.has(table2.name)),
|
|
107
343
|
relationships: filterRelationshipsByTables(relationships, allowedNames)
|
|
108
344
|
};
|
|
109
345
|
}
|
|
@@ -150,629 +386,1841 @@ function getTablesWithRelated(allTables, relationships, filter) {
|
|
|
150
386
|
}
|
|
151
387
|
|
|
152
388
|
// packages/text2sql/src/lib/agents/developer.agent.ts
|
|
153
|
-
import { groq as groq3 } from "@ai-sdk/groq";
|
|
154
389
|
import { tool } from "ai";
|
|
155
390
|
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
|
|
161
|
-
import { groq } from "@ai-sdk/groq";
|
|
162
|
-
import dedent from "dedent";
|
|
163
|
-
import z from "zod";
|
|
164
|
-
import { agent } from "@deepagents/agent";
|
|
165
|
-
var explainerAgent = agent({
|
|
166
|
-
name: "explainer",
|
|
167
|
-
model: groq("openai/gpt-oss-20b"),
|
|
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.
|
|
172
|
-
|
|
173
|
-
<sql>
|
|
174
|
-
${state?.sql}
|
|
175
|
-
</sql>
|
|
176
|
-
`,
|
|
177
|
-
output: z.object({
|
|
178
|
-
explanation: z.string().describe("The explanation of the SQL query.")
|
|
179
|
-
})
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// packages/text2sql/src/lib/agents/sql.agent.ts
|
|
183
|
-
import { groq as groq2 } from "@ai-sdk/groq";
|
|
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";
|
|
197
391
|
import z2 from "zod";
|
|
392
|
+
import { generate, toState } from "@deepagents/agent";
|
|
393
|
+
|
|
394
|
+
// packages/context/dist/index.js
|
|
395
|
+
import { encode } from "gpt-tokenizer";
|
|
396
|
+
import { generateId } from "ai";
|
|
397
|
+
import pluralize from "pluralize";
|
|
398
|
+
import { titlecase } from "stringcase";
|
|
399
|
+
import { defineCommand } from "just-bash";
|
|
400
|
+
import spawn from "nano-spawn";
|
|
401
|
+
import "bash-tool";
|
|
402
|
+
import spawn2 from "nano-spawn";
|
|
198
403
|
import {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
404
|
+
createBashTool
|
|
405
|
+
} from "bash-tool";
|
|
406
|
+
import YAML from "yaml";
|
|
407
|
+
import { DatabaseSync } from "node:sqlite";
|
|
408
|
+
import {
|
|
409
|
+
Output,
|
|
410
|
+
convertToModelMessages,
|
|
411
|
+
createUIMessageStream,
|
|
412
|
+
generateId as generateId2,
|
|
413
|
+
generateText,
|
|
414
|
+
smoothStream,
|
|
415
|
+
stepCountIs,
|
|
416
|
+
streamText
|
|
417
|
+
} from "ai";
|
|
418
|
+
import chalk from "chalk";
|
|
419
|
+
import "zod";
|
|
420
|
+
import "@deepagents/agent";
|
|
421
|
+
var defaultTokenizer = {
|
|
422
|
+
encode(text) {
|
|
423
|
+
return encode(text);
|
|
424
|
+
},
|
|
425
|
+
count(text) {
|
|
426
|
+
return encode(text).length;
|
|
210
427
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
428
|
+
};
|
|
429
|
+
var ModelsRegistry = class {
|
|
430
|
+
#cache = /* @__PURE__ */ new Map();
|
|
431
|
+
#loaded = false;
|
|
432
|
+
#tokenizers = /* @__PURE__ */ new Map();
|
|
433
|
+
#defaultTokenizer = defaultTokenizer;
|
|
434
|
+
/**
|
|
435
|
+
* Load models data from models.dev API
|
|
436
|
+
*/
|
|
437
|
+
async load() {
|
|
438
|
+
if (this.#loaded) return;
|
|
439
|
+
const response = await fetch("https://models.dev/api.json");
|
|
440
|
+
if (!response.ok) {
|
|
441
|
+
throw new Error(`Failed to fetch models: ${response.statusText}`);
|
|
442
|
+
}
|
|
443
|
+
const data = await response.json();
|
|
444
|
+
for (const [providerId, provider] of Object.entries(data)) {
|
|
445
|
+
for (const [modelId, model] of Object.entries(provider.models)) {
|
|
446
|
+
const info = {
|
|
447
|
+
id: model.id,
|
|
448
|
+
name: model.name,
|
|
449
|
+
family: model.family,
|
|
450
|
+
cost: model.cost,
|
|
451
|
+
limit: model.limit,
|
|
452
|
+
provider: providerId
|
|
453
|
+
};
|
|
454
|
+
this.#cache.set(`${providerId}:${modelId}`, info);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
this.#loaded = true;
|
|
218
458
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const safe = escapeXml(value);
|
|
226
|
-
if (safe.includes("\n")) {
|
|
227
|
-
return `<${tag}>
|
|
228
|
-
${indentBlock(safe, 2)}
|
|
229
|
-
</${tag}>`;
|
|
459
|
+
/**
|
|
460
|
+
* Get model info by ID
|
|
461
|
+
* @param modelId - Model ID (e.g., "openai:gpt-4o")
|
|
462
|
+
*/
|
|
463
|
+
get(modelId) {
|
|
464
|
+
return this.#cache.get(modelId);
|
|
230
465
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return
|
|
466
|
+
/**
|
|
467
|
+
* Check if a model exists in the registry
|
|
468
|
+
*/
|
|
469
|
+
has(modelId) {
|
|
470
|
+
return this.#cache.has(modelId);
|
|
236
471
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return "";
|
|
472
|
+
/**
|
|
473
|
+
* List all available model IDs
|
|
474
|
+
*/
|
|
475
|
+
list() {
|
|
476
|
+
return [...this.#cache.keys()];
|
|
243
477
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
leaf("reason", reason)
|
|
307
|
-
])
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
function workflow(input) {
|
|
311
|
-
const { task, steps, triggers, notes } = input;
|
|
312
|
-
return {
|
|
313
|
-
type: "workflow",
|
|
314
|
-
encode: () => ({ type: "workflow", task, steps, triggers, notes }),
|
|
315
|
-
decode: () => wrapBlock("workflow", [
|
|
316
|
-
leaf("task", task),
|
|
317
|
-
triggers?.length ? list("triggers", triggers, "trigger") : "",
|
|
318
|
-
list("steps", steps, "step"),
|
|
319
|
-
notes ? leaf("notes", notes) : ""
|
|
320
|
-
])
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
function quirk(input) {
|
|
324
|
-
const { issue, workaround } = input;
|
|
325
|
-
return {
|
|
326
|
-
type: "quirk",
|
|
327
|
-
encode: () => ({ type: "quirk", issue, workaround }),
|
|
328
|
-
decode: () => wrapBlock("quirk", [
|
|
329
|
-
leaf("issue", issue),
|
|
330
|
-
leaf("workaround", workaround)
|
|
331
|
-
])
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
function styleGuide(input) {
|
|
335
|
-
const { prefer, never, always } = input;
|
|
336
|
-
return {
|
|
337
|
-
type: "styleGuide",
|
|
338
|
-
encode: () => ({ type: "styleGuide", prefer, never, always }),
|
|
339
|
-
decode: () => wrapBlock("style_guide", [
|
|
340
|
-
leaf("prefer", prefer),
|
|
341
|
-
always ? leaf("always", always) : "",
|
|
342
|
-
never ? leaf("never", never) : ""
|
|
343
|
-
])
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
function analogy(input) {
|
|
347
|
-
const { concept, relationship, insight, therefore, pitfall } = input;
|
|
348
|
-
return {
|
|
349
|
-
type: "analogy",
|
|
350
|
-
encode: () => ({
|
|
351
|
-
type: "analogy",
|
|
352
|
-
concept,
|
|
353
|
-
relationship,
|
|
354
|
-
insight,
|
|
355
|
-
therefore,
|
|
356
|
-
pitfall
|
|
357
|
-
}),
|
|
358
|
-
decode: () => wrapBlock("analogy", [
|
|
359
|
-
list("concepts", concept, "concept"),
|
|
360
|
-
leaf("relationship", relationship),
|
|
361
|
-
insight ? leaf("insight", insight) : "",
|
|
362
|
-
therefore ? leaf("therefore", therefore) : "",
|
|
363
|
-
pitfall ? leaf("pitfall", pitfall) : ""
|
|
364
|
-
])
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
function glossary(entries) {
|
|
368
|
-
return {
|
|
369
|
-
type: "glossary",
|
|
370
|
-
encode: () => ({ type: "glossary", entries }),
|
|
371
|
-
decode: () => wrapBlock(
|
|
372
|
-
"glossary",
|
|
373
|
-
Object.entries(entries).map(
|
|
374
|
-
([term2, sql]) => wrapBlock("entry", [leaf("term", term2), leaf("sql", sql)])
|
|
375
|
-
)
|
|
376
|
-
)
|
|
377
|
-
};
|
|
478
|
+
/**
|
|
479
|
+
* Register a custom tokenizer for specific model families
|
|
480
|
+
* @param family - Model family name (e.g., "llama", "claude")
|
|
481
|
+
* @param tokenizer - Tokenizer implementation
|
|
482
|
+
*/
|
|
483
|
+
registerTokenizer(family, tokenizer) {
|
|
484
|
+
this.#tokenizers.set(family, tokenizer);
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Set the default tokenizer used when no family-specific tokenizer is registered
|
|
488
|
+
*/
|
|
489
|
+
setDefaultTokenizer(tokenizer) {
|
|
490
|
+
this.#defaultTokenizer = tokenizer;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Get the appropriate tokenizer for a model
|
|
494
|
+
*/
|
|
495
|
+
getTokenizer(modelId) {
|
|
496
|
+
const model = this.get(modelId);
|
|
497
|
+
if (model) {
|
|
498
|
+
const familyTokenizer = this.#tokenizers.get(model.family);
|
|
499
|
+
if (familyTokenizer) {
|
|
500
|
+
return familyTokenizer;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return this.#defaultTokenizer;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Estimate token count and cost for given text and model
|
|
507
|
+
* @param modelId - Model ID to use for pricing (e.g., "openai:gpt-4o")
|
|
508
|
+
* @param input - Input text (prompt)
|
|
509
|
+
*/
|
|
510
|
+
estimate(modelId, input) {
|
|
511
|
+
const model = this.get(modelId);
|
|
512
|
+
if (!model) {
|
|
513
|
+
throw new Error(
|
|
514
|
+
`Model "${modelId}" not found. Call load() first or check model ID.`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
const tokenizer = this.getTokenizer(modelId);
|
|
518
|
+
const tokens = tokenizer.count(input);
|
|
519
|
+
const cost = tokens / 1e6 * model.cost.input;
|
|
520
|
+
return {
|
|
521
|
+
model: model.id,
|
|
522
|
+
provider: model.provider,
|
|
523
|
+
tokens,
|
|
524
|
+
cost,
|
|
525
|
+
limits: {
|
|
526
|
+
context: model.limit.context,
|
|
527
|
+
output: model.limit.output,
|
|
528
|
+
exceedsContext: tokens > model.limit.context
|
|
529
|
+
},
|
|
530
|
+
fragments: []
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
var _registry = null;
|
|
535
|
+
function getModelsRegistry() {
|
|
536
|
+
if (!_registry) {
|
|
537
|
+
_registry = new ModelsRegistry();
|
|
538
|
+
}
|
|
539
|
+
return _registry;
|
|
378
540
|
}
|
|
379
|
-
function
|
|
380
|
-
|
|
381
|
-
return {
|
|
382
|
-
type: "identity",
|
|
383
|
-
encode: () => ({ type: "identity", name, role }),
|
|
384
|
-
decode: () => wrapBlock("identity", [
|
|
385
|
-
name ? leaf("name", name) : "",
|
|
386
|
-
role ? leaf("role", role) : ""
|
|
387
|
-
])
|
|
388
|
-
};
|
|
541
|
+
function isFragment(data) {
|
|
542
|
+
return typeof data === "object" && data !== null && "name" in data && "data" in data && typeof data.name === "string";
|
|
389
543
|
}
|
|
390
|
-
function
|
|
391
|
-
|
|
392
|
-
return {
|
|
393
|
-
type: "persona",
|
|
394
|
-
encode: () => ({ type: "persona", name, role, tone: tone ?? "" }),
|
|
395
|
-
decode: () => wrapBlock("persona", [
|
|
396
|
-
leaf("name", name),
|
|
397
|
-
leaf("role", role),
|
|
398
|
-
tone ? leaf("tone", tone) : ""
|
|
399
|
-
])
|
|
400
|
-
};
|
|
544
|
+
function isFragmentObject(data) {
|
|
545
|
+
return typeof data === "object" && data !== null && !Array.isArray(data) && !isFragment(data);
|
|
401
546
|
}
|
|
402
|
-
function
|
|
403
|
-
return
|
|
404
|
-
type: "alias",
|
|
405
|
-
encode: () => ({ type: "alias", term: termName, meaning }),
|
|
406
|
-
decode: () => wrapBlock("alias", [leaf("term", termName), leaf("meaning", meaning)])
|
|
407
|
-
};
|
|
547
|
+
function isMessageFragment(fragment2) {
|
|
548
|
+
return fragment2.type === "message";
|
|
408
549
|
}
|
|
409
|
-
function
|
|
550
|
+
function user(content) {
|
|
551
|
+
const message2 = typeof content === "string" ? {
|
|
552
|
+
id: generateId(),
|
|
553
|
+
role: "user",
|
|
554
|
+
parts: [{ type: "text", text: content }]
|
|
555
|
+
} : content;
|
|
410
556
|
return {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
557
|
+
id: message2.id,
|
|
558
|
+
name: "user",
|
|
559
|
+
data: "content",
|
|
560
|
+
type: "message",
|
|
561
|
+
persist: true,
|
|
562
|
+
codec: {
|
|
563
|
+
decode() {
|
|
564
|
+
return message2;
|
|
565
|
+
},
|
|
566
|
+
encode() {
|
|
567
|
+
return message2;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
414
570
|
};
|
|
415
571
|
}
|
|
416
|
-
function
|
|
572
|
+
function assistant(message2) {
|
|
417
573
|
return {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
574
|
+
id: message2.id,
|
|
575
|
+
name: "assistant",
|
|
576
|
+
data: "content",
|
|
577
|
+
type: "message",
|
|
578
|
+
persist: true,
|
|
579
|
+
codec: {
|
|
580
|
+
decode() {
|
|
581
|
+
return message2;
|
|
582
|
+
},
|
|
583
|
+
encode() {
|
|
584
|
+
return message2;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
421
587
|
};
|
|
422
588
|
}
|
|
423
|
-
function
|
|
589
|
+
function message(content) {
|
|
590
|
+
const message2 = typeof content === "string" ? {
|
|
591
|
+
id: generateId(),
|
|
592
|
+
role: "user",
|
|
593
|
+
parts: [{ type: "text", text: content }]
|
|
594
|
+
} : content;
|
|
424
595
|
return {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
596
|
+
id: message2.id,
|
|
597
|
+
name: "message",
|
|
598
|
+
data: "content",
|
|
599
|
+
type: "message",
|
|
600
|
+
persist: true,
|
|
601
|
+
codec: {
|
|
602
|
+
decode() {
|
|
603
|
+
return message2;
|
|
604
|
+
},
|
|
605
|
+
encode() {
|
|
606
|
+
return message2;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
431
609
|
};
|
|
432
610
|
}
|
|
433
|
-
function
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
611
|
+
function assistantText(content, options) {
|
|
612
|
+
const id = options?.id ?? crypto.randomUUID();
|
|
613
|
+
return assistant({
|
|
614
|
+
id,
|
|
615
|
+
role: "assistant",
|
|
616
|
+
parts: [{ type: "text", text: content }]
|
|
617
|
+
});
|
|
439
618
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
619
|
+
var ContextRenderer = class {
|
|
620
|
+
options;
|
|
621
|
+
constructor(options = {}) {
|
|
622
|
+
this.options = options;
|
|
443
623
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
624
|
+
/**
|
|
625
|
+
* Check if data is a primitive (string, number, boolean).
|
|
626
|
+
*/
|
|
627
|
+
isPrimitive(data) {
|
|
628
|
+
return typeof data === "string" || typeof data === "number" || typeof data === "boolean";
|
|
449
629
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
630
|
+
/**
|
|
631
|
+
* Group fragments by name for groupFragments option.
|
|
632
|
+
*/
|
|
633
|
+
groupByName(fragments2) {
|
|
634
|
+
const groups = /* @__PURE__ */ new Map();
|
|
635
|
+
for (const fragment2 of fragments2) {
|
|
636
|
+
const existing = groups.get(fragment2.name) ?? [];
|
|
637
|
+
existing.push(fragment2);
|
|
638
|
+
groups.set(fragment2.name, existing);
|
|
455
639
|
}
|
|
456
|
-
|
|
457
|
-
|
|
640
|
+
return groups;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Template method - dispatches value to appropriate handler.
|
|
644
|
+
*/
|
|
645
|
+
renderValue(key, value, ctx) {
|
|
646
|
+
if (value == null) {
|
|
458
647
|
return "";
|
|
459
648
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if (definedTypes.has(type)) {
|
|
466
|
-
continue;
|
|
649
|
+
if (isFragment(value)) {
|
|
650
|
+
return this.renderFragment(value, ctx);
|
|
651
|
+
}
|
|
652
|
+
if (Array.isArray(value)) {
|
|
653
|
+
return this.renderArray(key, value, ctx);
|
|
467
654
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
sections.push(renderedItems);
|
|
655
|
+
if (isFragmentObject(value)) {
|
|
656
|
+
return this.renderObject(key, value, ctx);
|
|
471
657
|
}
|
|
658
|
+
return this.renderPrimitive(key, String(value), ctx);
|
|
472
659
|
}
|
|
473
|
-
|
|
474
|
-
|
|
660
|
+
/**
|
|
661
|
+
* Render all entries of an object.
|
|
662
|
+
*/
|
|
663
|
+
renderEntries(data, ctx) {
|
|
664
|
+
return Object.entries(data).map(([key, value]) => this.renderValue(key, value, ctx)).filter(Boolean);
|
|
475
665
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
{ type: "persona", tag: "persona" },
|
|
485
|
-
{ type: "context", tag: "user_context" },
|
|
486
|
-
{ type: "preference", tag: "user_preferences" },
|
|
487
|
-
{ type: "alias", tag: "user_vocabulary" },
|
|
488
|
-
{ type: "correction", tag: "user_corrections" },
|
|
489
|
-
// Domain knowledge
|
|
490
|
-
{ type: "guardrail", tag: "guardrails" },
|
|
491
|
-
{ type: "styleGuide", tag: "style_guides" },
|
|
492
|
-
{ type: "hint", tag: "hints" },
|
|
493
|
-
{ type: "clarification", tag: "clarifications" },
|
|
494
|
-
{ type: "workflow", tag: "workflows" },
|
|
495
|
-
{ type: "quirk", tag: "quirks" },
|
|
496
|
-
{ type: "term", tag: "terminology" },
|
|
497
|
-
{ type: "explain", tag: "explanations" },
|
|
498
|
-
{ type: "analogy", tag: "analogies" },
|
|
499
|
-
{ type: "glossary", tag: "glossary" },
|
|
500
|
-
{ type: "example", tag: "examples" }
|
|
501
|
-
];
|
|
502
|
-
function toTeachables(generated) {
|
|
503
|
-
return generated.map((item) => {
|
|
504
|
-
switch (item.type) {
|
|
505
|
-
case "persona":
|
|
506
|
-
return persona({ name: item.name, role: item.role, tone: item.tone });
|
|
507
|
-
case "term":
|
|
508
|
-
return term(item.name, item.definition);
|
|
509
|
-
case "hint":
|
|
510
|
-
return hint(item.text);
|
|
511
|
-
case "guardrail":
|
|
512
|
-
return guardrail({
|
|
513
|
-
rule: item.rule,
|
|
514
|
-
reason: item.reason,
|
|
515
|
-
action: item.action
|
|
516
|
-
});
|
|
517
|
-
case "explain":
|
|
518
|
-
return explain({
|
|
519
|
-
concept: item.concept,
|
|
520
|
-
explanation: item.explanation,
|
|
521
|
-
therefore: item.therefore
|
|
522
|
-
});
|
|
523
|
-
case "example":
|
|
524
|
-
return example({
|
|
525
|
-
question: item.question,
|
|
526
|
-
answer: item.answer,
|
|
527
|
-
note: item.note
|
|
528
|
-
});
|
|
529
|
-
case "clarification":
|
|
530
|
-
return clarification({
|
|
531
|
-
when: item.when,
|
|
532
|
-
ask: item.ask,
|
|
533
|
-
reason: item.reason
|
|
534
|
-
});
|
|
535
|
-
case "workflow":
|
|
536
|
-
return workflow({
|
|
537
|
-
task: item.task,
|
|
538
|
-
steps: item.steps,
|
|
539
|
-
triggers: item.triggers,
|
|
540
|
-
notes: item.notes
|
|
541
|
-
});
|
|
542
|
-
case "quirk":
|
|
543
|
-
return quirk({
|
|
544
|
-
issue: item.issue,
|
|
545
|
-
workaround: item.workaround
|
|
546
|
-
});
|
|
547
|
-
case "styleGuide":
|
|
548
|
-
return styleGuide({
|
|
549
|
-
prefer: item.prefer,
|
|
550
|
-
never: item.never,
|
|
551
|
-
always: item.always
|
|
552
|
-
});
|
|
553
|
-
case "analogy":
|
|
554
|
-
return analogy({
|
|
555
|
-
concept: item.concept,
|
|
556
|
-
relationship: item.relationship,
|
|
557
|
-
insight: item.insight,
|
|
558
|
-
therefore: item.therefore,
|
|
559
|
-
pitfall: item.pitfall
|
|
560
|
-
});
|
|
561
|
-
case "glossary":
|
|
562
|
-
return glossary(item.entries);
|
|
563
|
-
// User-specific teachable types
|
|
564
|
-
case "identity":
|
|
565
|
-
return identity({ name: item.name, role: item.role });
|
|
566
|
-
case "alias":
|
|
567
|
-
return alias(item.term, item.meaning);
|
|
568
|
-
case "preference":
|
|
569
|
-
return preference(item.aspect, item.value);
|
|
570
|
-
case "context":
|
|
571
|
-
return context(item.description);
|
|
572
|
-
case "correction":
|
|
573
|
-
return correction(item.subject, item.clarification);
|
|
666
|
+
};
|
|
667
|
+
var XmlRenderer = class extends ContextRenderer {
|
|
668
|
+
render(fragments2) {
|
|
669
|
+
return fragments2.map((f) => this.#renderTopLevel(f)).filter(Boolean).join("\n");
|
|
670
|
+
}
|
|
671
|
+
#renderTopLevel(fragment2) {
|
|
672
|
+
if (this.isPrimitive(fragment2.data)) {
|
|
673
|
+
return this.#leafRoot(fragment2.name, String(fragment2.data));
|
|
574
674
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
`;
|
|
675
|
+
if (Array.isArray(fragment2.data)) {
|
|
676
|
+
return this.#renderArray(fragment2.name, fragment2.data, 0);
|
|
677
|
+
}
|
|
678
|
+
if (isFragment(fragment2.data)) {
|
|
679
|
+
const child = this.renderFragment(fragment2.data, { depth: 1, path: [] });
|
|
680
|
+
return this.#wrap(fragment2.name, [child]);
|
|
681
|
+
}
|
|
682
|
+
return this.#wrap(
|
|
683
|
+
fragment2.name,
|
|
684
|
+
this.renderEntries(fragment2.data, { depth: 1, path: [] })
|
|
685
|
+
);
|
|
605
686
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
687
|
+
#renderArray(name, items, depth) {
|
|
688
|
+
const fragmentItems = items.filter(isFragment);
|
|
689
|
+
const nonFragmentItems = items.filter((item) => !isFragment(item));
|
|
690
|
+
const children = [];
|
|
691
|
+
for (const item of nonFragmentItems) {
|
|
692
|
+
if (item != null) {
|
|
693
|
+
children.push(
|
|
694
|
+
this.#leaf(pluralize.singular(name), String(item), depth + 1)
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (this.options.groupFragments && fragmentItems.length > 0) {
|
|
699
|
+
const groups = this.groupByName(fragmentItems);
|
|
700
|
+
for (const [groupName, groupFragments] of groups) {
|
|
701
|
+
const groupChildren = groupFragments.map(
|
|
702
|
+
(frag) => this.renderFragment(frag, { depth: depth + 2, path: [] })
|
|
703
|
+
);
|
|
704
|
+
const pluralName = pluralize.plural(groupName);
|
|
705
|
+
children.push(this.#wrapIndented(pluralName, groupChildren, depth + 1));
|
|
706
|
+
}
|
|
707
|
+
} else {
|
|
708
|
+
for (const frag of fragmentItems) {
|
|
709
|
+
children.push(
|
|
710
|
+
this.renderFragment(frag, { depth: depth + 1, path: [] })
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return this.#wrap(name, children);
|
|
715
|
+
}
|
|
716
|
+
#leafRoot(tag, value) {
|
|
717
|
+
const safe = this.#escape(value);
|
|
718
|
+
if (safe.includes("\n")) {
|
|
719
|
+
return `<${tag}>
|
|
720
|
+
${this.#indent(safe, 2)}
|
|
721
|
+
</${tag}>`;
|
|
722
|
+
}
|
|
723
|
+
return `<${tag}>${safe}</${tag}>`;
|
|
724
|
+
}
|
|
725
|
+
renderFragment(fragment2, ctx) {
|
|
726
|
+
const { name, data } = fragment2;
|
|
727
|
+
if (this.isPrimitive(data)) {
|
|
728
|
+
return this.#leaf(name, String(data), ctx.depth);
|
|
729
|
+
}
|
|
730
|
+
if (isFragment(data)) {
|
|
731
|
+
const child = this.renderFragment(data, { ...ctx, depth: ctx.depth + 1 });
|
|
732
|
+
return this.#wrapIndented(name, [child], ctx.depth);
|
|
733
|
+
}
|
|
734
|
+
if (Array.isArray(data)) {
|
|
735
|
+
return this.#renderArrayIndented(name, data, ctx.depth);
|
|
736
|
+
}
|
|
737
|
+
const children = this.renderEntries(data, { ...ctx, depth: ctx.depth + 1 });
|
|
738
|
+
return this.#wrapIndented(name, children, ctx.depth);
|
|
739
|
+
}
|
|
740
|
+
#renderArrayIndented(name, items, depth) {
|
|
741
|
+
const fragmentItems = items.filter(isFragment);
|
|
742
|
+
const nonFragmentItems = items.filter((item) => !isFragment(item));
|
|
743
|
+
const children = [];
|
|
744
|
+
for (const item of nonFragmentItems) {
|
|
745
|
+
if (item != null) {
|
|
746
|
+
children.push(
|
|
747
|
+
this.#leaf(pluralize.singular(name), String(item), depth + 1)
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (this.options.groupFragments && fragmentItems.length > 0) {
|
|
752
|
+
const groups = this.groupByName(fragmentItems);
|
|
753
|
+
for (const [groupName, groupFragments] of groups) {
|
|
754
|
+
const groupChildren = groupFragments.map(
|
|
755
|
+
(frag) => this.renderFragment(frag, { depth: depth + 2, path: [] })
|
|
756
|
+
);
|
|
757
|
+
const pluralName = pluralize.plural(groupName);
|
|
758
|
+
children.push(this.#wrapIndented(pluralName, groupChildren, depth + 1));
|
|
759
|
+
}
|
|
760
|
+
} else {
|
|
761
|
+
for (const frag of fragmentItems) {
|
|
762
|
+
children.push(
|
|
763
|
+
this.renderFragment(frag, { depth: depth + 1, path: [] })
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return this.#wrapIndented(name, children, depth);
|
|
768
|
+
}
|
|
769
|
+
renderPrimitive(key, value, ctx) {
|
|
770
|
+
return this.#leaf(key, value, ctx.depth);
|
|
771
|
+
}
|
|
772
|
+
renderArray(key, items, ctx) {
|
|
773
|
+
if (!items.length) {
|
|
774
|
+
return "";
|
|
775
|
+
}
|
|
776
|
+
const itemTag = pluralize.singular(key);
|
|
777
|
+
const children = items.filter((item) => item != null).map((item) => this.#leaf(itemTag, String(item), ctx.depth + 1));
|
|
778
|
+
return this.#wrapIndented(key, children, ctx.depth);
|
|
779
|
+
}
|
|
780
|
+
renderObject(key, obj, ctx) {
|
|
781
|
+
const children = this.renderEntries(obj, { ...ctx, depth: ctx.depth + 1 });
|
|
782
|
+
return this.#wrapIndented(key, children, ctx.depth);
|
|
783
|
+
}
|
|
784
|
+
#escape(value) {
|
|
785
|
+
if (value == null) {
|
|
786
|
+
return "";
|
|
787
|
+
}
|
|
788
|
+
return value.replaceAll(/&/g, "&").replaceAll(/</g, "<").replaceAll(/>/g, ">").replaceAll(/"/g, """).replaceAll(/'/g, "'");
|
|
789
|
+
}
|
|
790
|
+
#indent(text, spaces) {
|
|
791
|
+
if (!text.trim()) {
|
|
792
|
+
return "";
|
|
793
|
+
}
|
|
794
|
+
const padding = " ".repeat(spaces);
|
|
795
|
+
return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
|
|
796
|
+
}
|
|
797
|
+
#leaf(tag, value, depth) {
|
|
798
|
+
const safe = this.#escape(value);
|
|
799
|
+
const pad = " ".repeat(depth);
|
|
800
|
+
if (safe.includes("\n")) {
|
|
801
|
+
return `${pad}<${tag}>
|
|
802
|
+
${this.#indent(safe, (depth + 1) * 2)}
|
|
803
|
+
${pad}</${tag}>`;
|
|
804
|
+
}
|
|
805
|
+
return `${pad}<${tag}>${safe}</${tag}>`;
|
|
806
|
+
}
|
|
807
|
+
#wrap(tag, children) {
|
|
808
|
+
const content = children.filter(Boolean).join("\n");
|
|
809
|
+
if (!content) {
|
|
810
|
+
return "";
|
|
811
|
+
}
|
|
812
|
+
return `<${tag}>
|
|
813
|
+
${content}
|
|
814
|
+
</${tag}>`;
|
|
815
|
+
}
|
|
816
|
+
#wrapIndented(tag, children, depth) {
|
|
817
|
+
const content = children.filter(Boolean).join("\n");
|
|
818
|
+
if (!content) {
|
|
819
|
+
return "";
|
|
820
|
+
}
|
|
821
|
+
const pad = " ".repeat(depth);
|
|
822
|
+
return `${pad}<${tag}>
|
|
823
|
+
${content}
|
|
824
|
+
${pad}</${tag}>`;
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
var ContextStore = class {
|
|
828
|
+
};
|
|
829
|
+
var ContextEngine = class {
|
|
830
|
+
/** Non-message fragments (role, hints, etc.) - not persisted in graph */
|
|
831
|
+
#fragments = [];
|
|
832
|
+
/** Pending message fragments to be added to graph */
|
|
833
|
+
#pendingMessages = [];
|
|
834
|
+
#store;
|
|
835
|
+
#chatId;
|
|
836
|
+
#branchName;
|
|
837
|
+
#branch = null;
|
|
838
|
+
#chatData = null;
|
|
839
|
+
#initialized = false;
|
|
840
|
+
constructor(options) {
|
|
841
|
+
if (!options.chatId) {
|
|
842
|
+
throw new Error("chatId is required");
|
|
843
|
+
}
|
|
844
|
+
this.#store = options.store;
|
|
845
|
+
this.#chatId = options.chatId;
|
|
846
|
+
this.#branchName = options.branch ?? "main";
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Initialize the chat and branch if they don't exist.
|
|
850
|
+
*/
|
|
851
|
+
async #ensureInitialized() {
|
|
852
|
+
if (this.#initialized) {
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
this.#chatData = await this.#store.upsertChat({ id: this.#chatId });
|
|
856
|
+
const existingBranch = await this.#store.getBranch(
|
|
857
|
+
this.#chatId,
|
|
858
|
+
this.#branchName
|
|
859
|
+
);
|
|
860
|
+
if (existingBranch) {
|
|
861
|
+
this.#branch = existingBranch;
|
|
862
|
+
} else {
|
|
863
|
+
this.#branch = {
|
|
864
|
+
id: crypto.randomUUID(),
|
|
865
|
+
chatId: this.#chatId,
|
|
866
|
+
name: this.#branchName,
|
|
867
|
+
headMessageId: null,
|
|
868
|
+
isActive: true,
|
|
869
|
+
createdAt: Date.now()
|
|
870
|
+
};
|
|
871
|
+
await this.#store.createBranch(this.#branch);
|
|
872
|
+
}
|
|
873
|
+
this.#initialized = true;
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Create a new branch from a specific message.
|
|
877
|
+
* Shared logic between rewind() and btw().
|
|
878
|
+
*/
|
|
879
|
+
async #createBranchFrom(messageId, switchTo) {
|
|
880
|
+
const branches = await this.#store.listBranches(this.#chatId);
|
|
881
|
+
const samePrefix = branches.filter(
|
|
882
|
+
(b) => b.name === this.#branchName || b.name.startsWith(`${this.#branchName}-v`)
|
|
883
|
+
);
|
|
884
|
+
const newBranchName = `${this.#branchName}-v${samePrefix.length + 1}`;
|
|
885
|
+
const newBranch = {
|
|
886
|
+
id: crypto.randomUUID(),
|
|
887
|
+
chatId: this.#chatId,
|
|
888
|
+
name: newBranchName,
|
|
889
|
+
headMessageId: messageId,
|
|
890
|
+
isActive: false,
|
|
891
|
+
createdAt: Date.now()
|
|
892
|
+
};
|
|
893
|
+
await this.#store.createBranch(newBranch);
|
|
894
|
+
if (switchTo) {
|
|
895
|
+
await this.#store.setActiveBranch(this.#chatId, newBranch.id);
|
|
896
|
+
this.#branch = { ...newBranch, isActive: true };
|
|
897
|
+
this.#branchName = newBranchName;
|
|
898
|
+
this.#pendingMessages = [];
|
|
899
|
+
}
|
|
900
|
+
const chain = await this.#store.getMessageChain(messageId);
|
|
901
|
+
return {
|
|
902
|
+
id: newBranch.id,
|
|
903
|
+
name: newBranch.name,
|
|
904
|
+
headMessageId: newBranch.headMessageId,
|
|
905
|
+
isActive: switchTo,
|
|
906
|
+
messageCount: chain.length,
|
|
907
|
+
createdAt: newBranch.createdAt
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Get the current chat ID.
|
|
912
|
+
*/
|
|
913
|
+
get chatId() {
|
|
914
|
+
return this.#chatId;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Get the current branch name.
|
|
918
|
+
*/
|
|
919
|
+
get branch() {
|
|
920
|
+
return this.#branchName;
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Get metadata for the current chat.
|
|
924
|
+
* Returns null if the chat hasn't been initialized yet.
|
|
925
|
+
*/
|
|
926
|
+
get chat() {
|
|
927
|
+
if (!this.#chatData) {
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
id: this.#chatData.id,
|
|
932
|
+
createdAt: this.#chatData.createdAt,
|
|
933
|
+
updatedAt: this.#chatData.updatedAt,
|
|
934
|
+
title: this.#chatData.title,
|
|
935
|
+
metadata: this.#chatData.metadata
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Add fragments to the context.
|
|
940
|
+
*
|
|
941
|
+
* - Message fragments (user/assistant) are queued for persistence
|
|
942
|
+
* - Non-message fragments (role/hint) are kept in memory for system prompt
|
|
943
|
+
*/
|
|
944
|
+
set(...fragments2) {
|
|
945
|
+
for (const fragment2 of fragments2) {
|
|
946
|
+
if (isMessageFragment(fragment2)) {
|
|
947
|
+
this.#pendingMessages.push(fragment2);
|
|
948
|
+
} else {
|
|
949
|
+
this.#fragments.push(fragment2);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return this;
|
|
953
|
+
}
|
|
954
|
+
// Unset a fragment by ID (not implemented yet)
|
|
955
|
+
unset(fragmentId) {
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Render all fragments using the provided renderer.
|
|
959
|
+
* @internal Use resolve() instead for public API.
|
|
960
|
+
*/
|
|
961
|
+
render(renderer) {
|
|
962
|
+
return renderer.render(this.#fragments);
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Resolve context into AI SDK-ready format.
|
|
966
|
+
*
|
|
967
|
+
* - Initializes chat and branch if needed
|
|
968
|
+
* - Loads message history from the graph (walking parent chain)
|
|
969
|
+
* - Separates context fragments for system prompt
|
|
970
|
+
* - Combines with pending messages
|
|
971
|
+
*
|
|
972
|
+
* @example
|
|
973
|
+
* ```ts
|
|
974
|
+
* const context = new ContextEngine({ store, chatId: 'chat-1' })
|
|
975
|
+
* .set(role('You are helpful'), user('Hello'));
|
|
976
|
+
*
|
|
977
|
+
* const { systemPrompt, messages } = await context.resolve();
|
|
978
|
+
* await generateText({ system: systemPrompt, messages });
|
|
979
|
+
* ```
|
|
980
|
+
*/
|
|
981
|
+
async resolve(options) {
|
|
982
|
+
await this.#ensureInitialized();
|
|
983
|
+
const systemPrompt = options.renderer.render(this.#fragments);
|
|
984
|
+
const messages = [];
|
|
985
|
+
if (this.#branch?.headMessageId) {
|
|
986
|
+
const chain = await this.#store.getMessageChain(
|
|
987
|
+
this.#branch.headMessageId
|
|
988
|
+
);
|
|
989
|
+
for (const msg of chain) {
|
|
990
|
+
messages.push(message(msg.data).codec?.decode());
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
for (const fragment2 of this.#pendingMessages) {
|
|
994
|
+
const decoded = fragment2.codec.decode();
|
|
995
|
+
messages.push(decoded);
|
|
996
|
+
}
|
|
997
|
+
return { systemPrompt, messages };
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Save pending messages to the graph.
|
|
1001
|
+
*
|
|
1002
|
+
* Each message is added as a node with parentId pointing to the previous message.
|
|
1003
|
+
* The branch head is updated to point to the last message.
|
|
1004
|
+
*
|
|
1005
|
+
* @example
|
|
1006
|
+
* ```ts
|
|
1007
|
+
* context.set(user('Hello'));
|
|
1008
|
+
* // AI responds...
|
|
1009
|
+
* context.set(assistant('Hi there!'));
|
|
1010
|
+
* await context.save(); // Persist to graph
|
|
1011
|
+
* ```
|
|
1012
|
+
*/
|
|
1013
|
+
async save() {
|
|
1014
|
+
await this.#ensureInitialized();
|
|
1015
|
+
if (this.#pendingMessages.length === 0) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
let parentId = this.#branch.headMessageId;
|
|
1019
|
+
const now = Date.now();
|
|
1020
|
+
for (const fragment2 of this.#pendingMessages) {
|
|
1021
|
+
const messageData = {
|
|
1022
|
+
id: fragment2.id ?? crypto.randomUUID(),
|
|
1023
|
+
chatId: this.#chatId,
|
|
1024
|
+
parentId,
|
|
1025
|
+
name: fragment2.name,
|
|
1026
|
+
type: fragment2.type,
|
|
1027
|
+
data: fragment2.codec.encode(),
|
|
1028
|
+
createdAt: now
|
|
1029
|
+
};
|
|
1030
|
+
await this.#store.addMessage(messageData);
|
|
1031
|
+
parentId = messageData.id;
|
|
1032
|
+
}
|
|
1033
|
+
await this.#store.updateBranchHead(this.#branch.id, parentId);
|
|
1034
|
+
this.#branch.headMessageId = parentId;
|
|
1035
|
+
this.#pendingMessages = [];
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Estimate token count and cost for the full context.
|
|
1039
|
+
*
|
|
1040
|
+
* Includes:
|
|
1041
|
+
* - System prompt fragments (role, hints, etc.)
|
|
1042
|
+
* - Persisted chat messages (from store)
|
|
1043
|
+
* - Pending messages (not yet saved)
|
|
1044
|
+
*
|
|
1045
|
+
* @param modelId - Model ID (e.g., "openai:gpt-4o", "anthropic:claude-3-5-sonnet")
|
|
1046
|
+
* @param options - Optional settings
|
|
1047
|
+
* @returns Estimate result with token counts, costs, and per-fragment breakdown
|
|
1048
|
+
*/
|
|
1049
|
+
async estimate(modelId, options = {}) {
|
|
1050
|
+
await this.#ensureInitialized();
|
|
1051
|
+
const renderer = options.renderer ?? new XmlRenderer();
|
|
1052
|
+
const registry = getModelsRegistry();
|
|
1053
|
+
await registry.load();
|
|
1054
|
+
const model = registry.get(modelId);
|
|
1055
|
+
if (!model) {
|
|
1056
|
+
throw new Error(
|
|
1057
|
+
`Model "${modelId}" not found. Call load() first or check model ID.`
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
const tokenizer = registry.getTokenizer(modelId);
|
|
1061
|
+
const fragmentEstimates = [];
|
|
1062
|
+
for (const fragment2 of this.#fragments) {
|
|
1063
|
+
const rendered = renderer.render([fragment2]);
|
|
1064
|
+
const tokens = tokenizer.count(rendered);
|
|
1065
|
+
const cost = tokens / 1e6 * model.cost.input;
|
|
1066
|
+
fragmentEstimates.push({
|
|
1067
|
+
id: fragment2.id,
|
|
1068
|
+
name: fragment2.name,
|
|
1069
|
+
tokens,
|
|
1070
|
+
cost
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
if (this.#branch?.headMessageId) {
|
|
1074
|
+
const chain = await this.#store.getMessageChain(
|
|
1075
|
+
this.#branch.headMessageId
|
|
1076
|
+
);
|
|
1077
|
+
for (const msg of chain) {
|
|
1078
|
+
const content = String(msg.data);
|
|
1079
|
+
const tokens = tokenizer.count(content);
|
|
1080
|
+
const cost = tokens / 1e6 * model.cost.input;
|
|
1081
|
+
fragmentEstimates.push({
|
|
1082
|
+
name: msg.name,
|
|
1083
|
+
id: msg.id,
|
|
1084
|
+
tokens,
|
|
1085
|
+
cost
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
for (const fragment2 of this.#pendingMessages) {
|
|
1090
|
+
const content = String(fragment2.data);
|
|
1091
|
+
const tokens = tokenizer.count(content);
|
|
1092
|
+
const cost = tokens / 1e6 * model.cost.input;
|
|
1093
|
+
fragmentEstimates.push({
|
|
1094
|
+
name: fragment2.name,
|
|
1095
|
+
id: fragment2.id,
|
|
1096
|
+
tokens,
|
|
1097
|
+
cost
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
const totalTokens = fragmentEstimates.reduce((sum, f) => sum + f.tokens, 0);
|
|
1101
|
+
const totalCost = fragmentEstimates.reduce((sum, f) => sum + f.cost, 0);
|
|
1102
|
+
return {
|
|
1103
|
+
model: model.id,
|
|
1104
|
+
provider: model.provider,
|
|
1105
|
+
tokens: totalTokens,
|
|
1106
|
+
cost: totalCost,
|
|
1107
|
+
limits: {
|
|
1108
|
+
context: model.limit.context,
|
|
1109
|
+
output: model.limit.output,
|
|
1110
|
+
exceedsContext: totalTokens > model.limit.context
|
|
1111
|
+
},
|
|
1112
|
+
fragments: fragmentEstimates
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Rewind to a specific message by ID.
|
|
1117
|
+
*
|
|
1118
|
+
* Creates a new branch from that message, preserving the original branch.
|
|
1119
|
+
* The new branch becomes active.
|
|
1120
|
+
*
|
|
1121
|
+
* @param messageId - The message ID to rewind to
|
|
1122
|
+
* @returns The new branch info
|
|
1123
|
+
*
|
|
1124
|
+
* @example
|
|
1125
|
+
* ```ts
|
|
1126
|
+
* context.set(user('What is 2 + 2?', { id: 'q1' }));
|
|
1127
|
+
* context.set(assistant('The answer is 5.', { id: 'wrong' })); // Oops!
|
|
1128
|
+
* await context.save();
|
|
1129
|
+
*
|
|
1130
|
+
* // Rewind to the question, creates new branch
|
|
1131
|
+
* const newBranch = await context.rewind('q1');
|
|
1132
|
+
*
|
|
1133
|
+
* // Now add correct answer on new branch
|
|
1134
|
+
* context.set(assistant('The answer is 4.'));
|
|
1135
|
+
* await context.save();
|
|
1136
|
+
* ```
|
|
1137
|
+
*/
|
|
1138
|
+
async rewind(messageId) {
|
|
1139
|
+
await this.#ensureInitialized();
|
|
1140
|
+
const message2 = await this.#store.getMessage(messageId);
|
|
1141
|
+
if (!message2) {
|
|
1142
|
+
throw new Error(`Message "${messageId}" not found`);
|
|
1143
|
+
}
|
|
1144
|
+
if (message2.chatId !== this.#chatId) {
|
|
1145
|
+
throw new Error(`Message "${messageId}" belongs to a different chat`);
|
|
1146
|
+
}
|
|
1147
|
+
return this.#createBranchFrom(messageId, true);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Create a checkpoint at the current position.
|
|
1151
|
+
*
|
|
1152
|
+
* A checkpoint is a named pointer to the current branch head.
|
|
1153
|
+
* Use restore() to return to this point later.
|
|
1154
|
+
*
|
|
1155
|
+
* @param name - Name for the checkpoint
|
|
1156
|
+
* @returns The checkpoint info
|
|
1157
|
+
*
|
|
1158
|
+
* @example
|
|
1159
|
+
* ```ts
|
|
1160
|
+
* context.set(user('I want to learn a new skill.'));
|
|
1161
|
+
* context.set(assistant('Would you like coding or cooking?'));
|
|
1162
|
+
* await context.save();
|
|
1163
|
+
*
|
|
1164
|
+
* // Save checkpoint before user's choice
|
|
1165
|
+
* const cp = await context.checkpoint('before-choice');
|
|
1166
|
+
* ```
|
|
1167
|
+
*/
|
|
1168
|
+
async checkpoint(name) {
|
|
1169
|
+
await this.#ensureInitialized();
|
|
1170
|
+
if (!this.#branch?.headMessageId) {
|
|
1171
|
+
throw new Error("Cannot create checkpoint: no messages in conversation");
|
|
1172
|
+
}
|
|
1173
|
+
const checkpoint = {
|
|
1174
|
+
id: crypto.randomUUID(),
|
|
1175
|
+
chatId: this.#chatId,
|
|
1176
|
+
name,
|
|
1177
|
+
messageId: this.#branch.headMessageId,
|
|
1178
|
+
createdAt: Date.now()
|
|
1179
|
+
};
|
|
1180
|
+
await this.#store.createCheckpoint(checkpoint);
|
|
1181
|
+
return {
|
|
1182
|
+
id: checkpoint.id,
|
|
1183
|
+
name: checkpoint.name,
|
|
1184
|
+
messageId: checkpoint.messageId,
|
|
1185
|
+
createdAt: checkpoint.createdAt
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Restore to a checkpoint by creating a new branch from that point.
|
|
1190
|
+
*
|
|
1191
|
+
* @param name - Name of the checkpoint to restore
|
|
1192
|
+
* @returns The new branch info
|
|
1193
|
+
*
|
|
1194
|
+
* @example
|
|
1195
|
+
* ```ts
|
|
1196
|
+
* // User chose cooking, but wants to try coding path
|
|
1197
|
+
* await context.restore('before-choice');
|
|
1198
|
+
*
|
|
1199
|
+
* context.set(user('I want to learn coding.'));
|
|
1200
|
+
* context.set(assistant('Python is a great starting language!'));
|
|
1201
|
+
* await context.save();
|
|
1202
|
+
* ```
|
|
1203
|
+
*/
|
|
1204
|
+
async restore(name) {
|
|
1205
|
+
await this.#ensureInitialized();
|
|
1206
|
+
const checkpoint = await this.#store.getCheckpoint(this.#chatId, name);
|
|
1207
|
+
if (!checkpoint) {
|
|
1208
|
+
throw new Error(
|
|
1209
|
+
`Checkpoint "${name}" not found in chat "${this.#chatId}"`
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
return this.rewind(checkpoint.messageId);
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Switch to a different branch by name.
|
|
1216
|
+
*
|
|
1217
|
+
* @param name - Branch name to switch to
|
|
1218
|
+
*
|
|
1219
|
+
* @example
|
|
1220
|
+
* ```ts
|
|
1221
|
+
* // List branches (via store)
|
|
1222
|
+
* const branches = await store.listBranches(context.chatId);
|
|
1223
|
+
* console.log(branches); // [{name: 'main', ...}, {name: 'main-v2', ...}]
|
|
1224
|
+
*
|
|
1225
|
+
* // Switch to original branch
|
|
1226
|
+
* await context.switchBranch('main');
|
|
1227
|
+
* ```
|
|
1228
|
+
*/
|
|
1229
|
+
async switchBranch(name) {
|
|
1230
|
+
await this.#ensureInitialized();
|
|
1231
|
+
const branch = await this.#store.getBranch(this.#chatId, name);
|
|
1232
|
+
if (!branch) {
|
|
1233
|
+
throw new Error(`Branch "${name}" not found in chat "${this.#chatId}"`);
|
|
1234
|
+
}
|
|
1235
|
+
await this.#store.setActiveBranch(this.#chatId, branch.id);
|
|
1236
|
+
this.#branch = { ...branch, isActive: true };
|
|
1237
|
+
this.#branchName = name;
|
|
1238
|
+
this.#pendingMessages = [];
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Create a parallel branch from the current position ("by the way").
|
|
1242
|
+
*
|
|
1243
|
+
* Use this when you want to fork the conversation without leaving
|
|
1244
|
+
* the current branch. Common use case: user wants to ask another
|
|
1245
|
+
* question while waiting for the model to respond.
|
|
1246
|
+
*
|
|
1247
|
+
* Unlike rewind(), this method:
|
|
1248
|
+
* - Uses the current HEAD (no messageId needed)
|
|
1249
|
+
* - Does NOT switch to the new branch
|
|
1250
|
+
* - Keeps pending messages intact
|
|
1251
|
+
*
|
|
1252
|
+
* @returns The new branch info (does not switch to it)
|
|
1253
|
+
* @throws Error if no messages exist in the conversation
|
|
1254
|
+
*
|
|
1255
|
+
* @example
|
|
1256
|
+
* ```ts
|
|
1257
|
+
* // User asked a question, model is generating...
|
|
1258
|
+
* context.set(user('What is the weather?'));
|
|
1259
|
+
* await context.save();
|
|
1260
|
+
*
|
|
1261
|
+
* // User wants to ask something else without waiting
|
|
1262
|
+
* const newBranch = await context.btw();
|
|
1263
|
+
* // newBranch = { name: 'main-v2', ... }
|
|
1264
|
+
*
|
|
1265
|
+
* // Later, switch to the new branch and add the question
|
|
1266
|
+
* await context.switchBranch(newBranch.name);
|
|
1267
|
+
* context.set(user('Also, what time is it?'));
|
|
1268
|
+
* await context.save();
|
|
1269
|
+
* ```
|
|
1270
|
+
*/
|
|
1271
|
+
async btw() {
|
|
1272
|
+
await this.#ensureInitialized();
|
|
1273
|
+
if (!this.#branch?.headMessageId) {
|
|
1274
|
+
throw new Error("Cannot create btw branch: no messages in conversation");
|
|
1275
|
+
}
|
|
1276
|
+
return this.#createBranchFrom(this.#branch.headMessageId, false);
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Update metadata for the current chat.
|
|
1280
|
+
*
|
|
1281
|
+
* @param updates - Partial metadata to merge (title, metadata)
|
|
1282
|
+
*
|
|
1283
|
+
* @example
|
|
1284
|
+
* ```ts
|
|
1285
|
+
* await context.updateChat({
|
|
1286
|
+
* title: 'Coding Help Session',
|
|
1287
|
+
* metadata: { tags: ['python', 'debugging'] }
|
|
1288
|
+
* });
|
|
1289
|
+
* ```
|
|
1290
|
+
*/
|
|
1291
|
+
async updateChat(updates) {
|
|
1292
|
+
await this.#ensureInitialized();
|
|
1293
|
+
const storeUpdates = {};
|
|
1294
|
+
if (updates.title !== void 0) {
|
|
1295
|
+
storeUpdates.title = updates.title;
|
|
1296
|
+
}
|
|
1297
|
+
if (updates.metadata !== void 0) {
|
|
1298
|
+
storeUpdates.metadata = {
|
|
1299
|
+
...this.#chatData?.metadata,
|
|
1300
|
+
...updates.metadata
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
this.#chatData = await this.#store.updateChat(this.#chatId, storeUpdates);
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Consolidate context fragments (no-op for now).
|
|
1307
|
+
*
|
|
1308
|
+
* This is a placeholder for future functionality that merges context fragments
|
|
1309
|
+
* using specific rules. Currently, it does nothing.
|
|
1310
|
+
*
|
|
1311
|
+
* @experimental
|
|
1312
|
+
*/
|
|
1313
|
+
consolidate() {
|
|
1314
|
+
return void 0;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Inspect the full context state for debugging.
|
|
1318
|
+
* Returns a comprehensive JSON-serializable object with all context information.
|
|
1319
|
+
*
|
|
1320
|
+
* @param options - Inspection options (modelId and renderer required)
|
|
1321
|
+
* @returns Complete inspection data including estimates, rendered output, fragments, and graph
|
|
1322
|
+
*
|
|
1323
|
+
* @example
|
|
1324
|
+
* ```ts
|
|
1325
|
+
* const inspection = await context.inspect({
|
|
1326
|
+
* modelId: 'openai:gpt-4o',
|
|
1327
|
+
* renderer: new XmlRenderer(),
|
|
1328
|
+
* });
|
|
1329
|
+
* console.log(JSON.stringify(inspection, null, 2));
|
|
1330
|
+
*
|
|
1331
|
+
* // Or write to file for analysis
|
|
1332
|
+
* await fs.writeFile('context-debug.json', JSON.stringify(inspection, null, 2));
|
|
1333
|
+
* ```
|
|
1334
|
+
*/
|
|
1335
|
+
async inspect(options) {
|
|
1336
|
+
await this.#ensureInitialized();
|
|
1337
|
+
const { renderer } = options;
|
|
1338
|
+
const estimateResult = await this.estimate(options.modelId, { renderer });
|
|
1339
|
+
const rendered = renderer.render(this.#fragments);
|
|
1340
|
+
const persistedMessages = [];
|
|
1341
|
+
if (this.#branch?.headMessageId) {
|
|
1342
|
+
const chain = await this.#store.getMessageChain(
|
|
1343
|
+
this.#branch.headMessageId
|
|
1344
|
+
);
|
|
1345
|
+
persistedMessages.push(...chain);
|
|
1346
|
+
}
|
|
1347
|
+
const graph = await this.#store.getGraph(this.#chatId);
|
|
1348
|
+
return {
|
|
1349
|
+
estimate: estimateResult,
|
|
1350
|
+
rendered,
|
|
1351
|
+
fragments: {
|
|
1352
|
+
context: [...this.#fragments],
|
|
1353
|
+
pending: [...this.#pendingMessages],
|
|
1354
|
+
persisted: persistedMessages
|
|
1355
|
+
},
|
|
1356
|
+
graph,
|
|
1357
|
+
meta: {
|
|
1358
|
+
chatId: this.#chatId,
|
|
1359
|
+
branch: this.#branchName,
|
|
1360
|
+
timestamp: Date.now()
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
function hint(text) {
|
|
1366
|
+
return {
|
|
1367
|
+
name: "hint",
|
|
1368
|
+
data: text
|
|
1369
|
+
};
|
|
610
1370
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
1371
|
+
function guardrail(input) {
|
|
1372
|
+
return {
|
|
1373
|
+
name: "guardrail",
|
|
1374
|
+
data: {
|
|
1375
|
+
rule: input.rule,
|
|
1376
|
+
...input.reason && { reason: input.reason },
|
|
1377
|
+
...input.action && { action: input.action }
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
function clarification(input) {
|
|
1382
|
+
return {
|
|
1383
|
+
name: "clarification",
|
|
1384
|
+
data: {
|
|
1385
|
+
when: input.when,
|
|
1386
|
+
ask: input.ask,
|
|
1387
|
+
reason: input.reason
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
function workflow(input) {
|
|
1392
|
+
return {
|
|
1393
|
+
name: "workflow",
|
|
1394
|
+
data: {
|
|
1395
|
+
task: input.task,
|
|
1396
|
+
steps: input.steps,
|
|
1397
|
+
...input.triggers?.length && { triggers: input.triggers },
|
|
1398
|
+
...input.notes && { notes: input.notes }
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
function styleGuide(input) {
|
|
1403
|
+
return {
|
|
1404
|
+
name: "styleGuide",
|
|
1405
|
+
data: {
|
|
1406
|
+
prefer: input.prefer,
|
|
1407
|
+
...input.never && { never: input.never },
|
|
1408
|
+
...input.always && { always: input.always }
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
function persona(input) {
|
|
1413
|
+
return {
|
|
1414
|
+
name: "persona",
|
|
1415
|
+
data: {
|
|
1416
|
+
name: input.name,
|
|
1417
|
+
role: input.role,
|
|
1418
|
+
...input.tone && { tone: input.tone }
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
function pass(part) {
|
|
1423
|
+
return { type: "pass", part };
|
|
1424
|
+
}
|
|
1425
|
+
function runGuardrailChain(part, guardrails, context) {
|
|
1426
|
+
let currentPart = part;
|
|
1427
|
+
for (const guardrail2 of guardrails) {
|
|
1428
|
+
const result = guardrail2.handle(currentPart, context);
|
|
1429
|
+
if (result.type === "fail") {
|
|
1430
|
+
return result;
|
|
1431
|
+
}
|
|
1432
|
+
currentPart = result.part;
|
|
1433
|
+
}
|
|
1434
|
+
return pass(currentPart);
|
|
1435
|
+
}
|
|
1436
|
+
var STORE_DDL = `
|
|
1437
|
+
-- Chats table
|
|
1438
|
+
-- createdAt/updatedAt: DEFAULT for insert, inline SET for updates
|
|
1439
|
+
CREATE TABLE IF NOT EXISTS chats (
|
|
1440
|
+
id TEXT PRIMARY KEY,
|
|
1441
|
+
title TEXT,
|
|
1442
|
+
metadata TEXT,
|
|
1443
|
+
createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
1444
|
+
updatedAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
|
|
1445
|
+
);
|
|
1446
|
+
|
|
1447
|
+
CREATE INDEX IF NOT EXISTS idx_chats_updatedAt ON chats(updatedAt);
|
|
1448
|
+
|
|
1449
|
+
-- Messages table (nodes in the DAG)
|
|
1450
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
1451
|
+
id TEXT PRIMARY KEY,
|
|
1452
|
+
chatId TEXT NOT NULL,
|
|
1453
|
+
parentId TEXT,
|
|
1454
|
+
name TEXT NOT NULL,
|
|
1455
|
+
type TEXT,
|
|
1456
|
+
data TEXT NOT NULL,
|
|
1457
|
+
createdAt INTEGER NOT NULL,
|
|
1458
|
+
FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
|
|
1459
|
+
FOREIGN KEY (parentId) REFERENCES messages(id)
|
|
1460
|
+
);
|
|
1461
|
+
|
|
1462
|
+
CREATE INDEX IF NOT EXISTS idx_messages_chatId ON messages(chatId);
|
|
1463
|
+
CREATE INDEX IF NOT EXISTS idx_messages_parentId ON messages(parentId);
|
|
1464
|
+
|
|
1465
|
+
-- Branches table (pointers to head messages)
|
|
1466
|
+
CREATE TABLE IF NOT EXISTS branches (
|
|
1467
|
+
id TEXT PRIMARY KEY,
|
|
1468
|
+
chatId TEXT NOT NULL,
|
|
1469
|
+
name TEXT NOT NULL,
|
|
1470
|
+
headMessageId TEXT,
|
|
1471
|
+
isActive INTEGER NOT NULL DEFAULT 0,
|
|
1472
|
+
createdAt INTEGER NOT NULL,
|
|
1473
|
+
FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
|
|
1474
|
+
FOREIGN KEY (headMessageId) REFERENCES messages(id),
|
|
1475
|
+
UNIQUE(chatId, name)
|
|
1476
|
+
);
|
|
1477
|
+
|
|
1478
|
+
CREATE INDEX IF NOT EXISTS idx_branches_chatId ON branches(chatId);
|
|
1479
|
+
|
|
1480
|
+
-- Checkpoints table (pointers to message nodes)
|
|
1481
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
1482
|
+
id TEXT PRIMARY KEY,
|
|
1483
|
+
chatId TEXT NOT NULL,
|
|
1484
|
+
name TEXT NOT NULL,
|
|
1485
|
+
messageId TEXT NOT NULL,
|
|
1486
|
+
createdAt INTEGER NOT NULL,
|
|
1487
|
+
FOREIGN KEY (chatId) REFERENCES chats(id) ON DELETE CASCADE,
|
|
1488
|
+
FOREIGN KEY (messageId) REFERENCES messages(id),
|
|
1489
|
+
UNIQUE(chatId, name)
|
|
1490
|
+
);
|
|
1491
|
+
|
|
1492
|
+
CREATE INDEX IF NOT EXISTS idx_checkpoints_chatId ON checkpoints(chatId);
|
|
1493
|
+
|
|
1494
|
+
-- FTS5 virtual table for full-text search
|
|
1495
|
+
-- messageId/chatId/name are UNINDEXED (stored but not searchable, used for filtering/joining)
|
|
1496
|
+
-- Only 'content' is indexed for full-text search
|
|
1497
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
|
|
1498
|
+
messageId UNINDEXED,
|
|
1499
|
+
chatId UNINDEXED,
|
|
1500
|
+
name UNINDEXED,
|
|
1501
|
+
content,
|
|
1502
|
+
tokenize='porter unicode61'
|
|
1503
|
+
);
|
|
1504
|
+
`;
|
|
1505
|
+
var SqliteContextStore = class extends ContextStore {
|
|
1506
|
+
#db;
|
|
1507
|
+
constructor(path3) {
|
|
1508
|
+
super();
|
|
1509
|
+
this.#db = new DatabaseSync(path3);
|
|
1510
|
+
this.#db.exec("PRAGMA foreign_keys = ON");
|
|
1511
|
+
this.#db.exec(STORE_DDL);
|
|
1512
|
+
}
|
|
1513
|
+
// ==========================================================================
|
|
1514
|
+
// Chat Operations
|
|
1515
|
+
// ==========================================================================
|
|
1516
|
+
async createChat(chat) {
|
|
1517
|
+
this.#db.prepare(
|
|
1518
|
+
`INSERT INTO chats (id, title, metadata)
|
|
1519
|
+
VALUES (?, ?, ?)`
|
|
1520
|
+
).run(
|
|
1521
|
+
chat.id,
|
|
1522
|
+
chat.title ?? null,
|
|
1523
|
+
chat.metadata ? JSON.stringify(chat.metadata) : null
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
async upsertChat(chat) {
|
|
1527
|
+
const row = this.#db.prepare(
|
|
1528
|
+
`INSERT INTO chats (id, title, metadata)
|
|
1529
|
+
VALUES (?, ?, ?)
|
|
1530
|
+
ON CONFLICT(id) DO UPDATE SET id = excluded.id
|
|
1531
|
+
RETURNING *`
|
|
1532
|
+
).get(
|
|
1533
|
+
chat.id,
|
|
1534
|
+
chat.title ?? null,
|
|
1535
|
+
chat.metadata ? JSON.stringify(chat.metadata) : null
|
|
1536
|
+
);
|
|
1537
|
+
return {
|
|
1538
|
+
id: row.id,
|
|
1539
|
+
title: row.title ?? void 0,
|
|
1540
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
1541
|
+
createdAt: row.createdAt,
|
|
1542
|
+
updatedAt: row.updatedAt
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
async getChat(chatId) {
|
|
1546
|
+
const row = this.#db.prepare("SELECT * FROM chats WHERE id = ?").get(chatId);
|
|
1547
|
+
if (!row) {
|
|
1548
|
+
return void 0;
|
|
1549
|
+
}
|
|
1550
|
+
return {
|
|
1551
|
+
id: row.id,
|
|
1552
|
+
title: row.title ?? void 0,
|
|
1553
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
1554
|
+
createdAt: row.createdAt,
|
|
1555
|
+
updatedAt: row.updatedAt
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
async updateChat(chatId, updates) {
|
|
1559
|
+
const setClauses = ["updatedAt = strftime('%s', 'now') * 1000"];
|
|
1560
|
+
const params = [];
|
|
1561
|
+
if (updates.title !== void 0) {
|
|
1562
|
+
setClauses.push("title = ?");
|
|
1563
|
+
params.push(updates.title ?? null);
|
|
1564
|
+
}
|
|
1565
|
+
if (updates.metadata !== void 0) {
|
|
1566
|
+
setClauses.push("metadata = ?");
|
|
1567
|
+
params.push(JSON.stringify(updates.metadata));
|
|
1568
|
+
}
|
|
1569
|
+
params.push(chatId);
|
|
1570
|
+
const row = this.#db.prepare(
|
|
1571
|
+
`UPDATE chats SET ${setClauses.join(", ")} WHERE id = ? RETURNING *`
|
|
1572
|
+
).get(...params);
|
|
1573
|
+
return {
|
|
1574
|
+
id: row.id,
|
|
1575
|
+
title: row.title ?? void 0,
|
|
1576
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
1577
|
+
createdAt: row.createdAt,
|
|
1578
|
+
updatedAt: row.updatedAt
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
async listChats() {
|
|
1582
|
+
const rows = this.#db.prepare(
|
|
1583
|
+
`SELECT
|
|
1584
|
+
c.id,
|
|
1585
|
+
c.title,
|
|
1586
|
+
c.createdAt,
|
|
1587
|
+
c.updatedAt,
|
|
1588
|
+
COUNT(DISTINCT m.id) as messageCount,
|
|
1589
|
+
COUNT(DISTINCT b.id) as branchCount
|
|
1590
|
+
FROM chats c
|
|
1591
|
+
LEFT JOIN messages m ON m.chatId = c.id
|
|
1592
|
+
LEFT JOIN branches b ON b.chatId = c.id
|
|
1593
|
+
GROUP BY c.id
|
|
1594
|
+
ORDER BY c.updatedAt DESC`
|
|
1595
|
+
).all();
|
|
1596
|
+
return rows.map((row) => ({
|
|
1597
|
+
id: row.id,
|
|
1598
|
+
title: row.title ?? void 0,
|
|
1599
|
+
messageCount: row.messageCount,
|
|
1600
|
+
branchCount: row.branchCount,
|
|
1601
|
+
createdAt: row.createdAt,
|
|
1602
|
+
updatedAt: row.updatedAt
|
|
1603
|
+
}));
|
|
1604
|
+
}
|
|
1605
|
+
// ==========================================================================
|
|
1606
|
+
// Message Operations (Graph Nodes)
|
|
1607
|
+
// ==========================================================================
|
|
1608
|
+
async addMessage(message2) {
|
|
1609
|
+
this.#db.prepare(
|
|
1610
|
+
`INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
|
|
1611
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1612
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
1613
|
+
parentId = excluded.parentId,
|
|
1614
|
+
name = excluded.name,
|
|
1615
|
+
type = excluded.type,
|
|
1616
|
+
data = excluded.data`
|
|
1617
|
+
).run(
|
|
1618
|
+
message2.id,
|
|
1619
|
+
message2.chatId,
|
|
1620
|
+
message2.parentId,
|
|
1621
|
+
message2.name,
|
|
1622
|
+
message2.type ?? null,
|
|
1623
|
+
JSON.stringify(message2.data),
|
|
1624
|
+
message2.createdAt
|
|
1625
|
+
);
|
|
1626
|
+
const content = typeof message2.data === "string" ? message2.data : JSON.stringify(message2.data);
|
|
1627
|
+
this.#db.prepare(`DELETE FROM messages_fts WHERE messageId = ?`).run(message2.id);
|
|
1628
|
+
this.#db.prepare(
|
|
1629
|
+
`INSERT INTO messages_fts(messageId, chatId, name, content)
|
|
1630
|
+
VALUES (?, ?, ?, ?)`
|
|
1631
|
+
).run(message2.id, message2.chatId, message2.name, content);
|
|
1632
|
+
}
|
|
1633
|
+
async getMessage(messageId) {
|
|
1634
|
+
const row = this.#db.prepare("SELECT * FROM messages WHERE id = ?").get(messageId);
|
|
1635
|
+
if (!row) {
|
|
1636
|
+
return void 0;
|
|
1637
|
+
}
|
|
1638
|
+
return {
|
|
1639
|
+
id: row.id,
|
|
1640
|
+
chatId: row.chatId,
|
|
1641
|
+
parentId: row.parentId,
|
|
1642
|
+
name: row.name,
|
|
1643
|
+
type: row.type ?? void 0,
|
|
1644
|
+
data: JSON.parse(row.data),
|
|
1645
|
+
createdAt: row.createdAt
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
async getMessageChain(headId) {
|
|
1649
|
+
const rows = this.#db.prepare(
|
|
1650
|
+
`WITH RECURSIVE chain AS (
|
|
1651
|
+
SELECT *, 0 as depth FROM messages WHERE id = ?
|
|
1652
|
+
UNION ALL
|
|
1653
|
+
SELECT m.*, c.depth + 1 FROM messages m
|
|
1654
|
+
INNER JOIN chain c ON m.id = c.parentId
|
|
1655
|
+
)
|
|
1656
|
+
SELECT * FROM chain
|
|
1657
|
+
ORDER BY depth DESC`
|
|
1658
|
+
).all(headId);
|
|
1659
|
+
return rows.map((row) => ({
|
|
1660
|
+
id: row.id,
|
|
1661
|
+
chatId: row.chatId,
|
|
1662
|
+
parentId: row.parentId,
|
|
1663
|
+
name: row.name,
|
|
1664
|
+
type: row.type ?? void 0,
|
|
1665
|
+
data: JSON.parse(row.data),
|
|
1666
|
+
createdAt: row.createdAt
|
|
1667
|
+
}));
|
|
1668
|
+
}
|
|
1669
|
+
async hasChildren(messageId) {
|
|
1670
|
+
const row = this.#db.prepare(
|
|
1671
|
+
"SELECT EXISTS(SELECT 1 FROM messages WHERE parentId = ?) as hasChildren"
|
|
1672
|
+
).get(messageId);
|
|
1673
|
+
return row.hasChildren === 1;
|
|
1674
|
+
}
|
|
1675
|
+
// ==========================================================================
|
|
1676
|
+
// Branch Operations
|
|
1677
|
+
// ==========================================================================
|
|
1678
|
+
async createBranch(branch) {
|
|
1679
|
+
this.#db.prepare(
|
|
1680
|
+
`INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
|
|
1681
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
1682
|
+
).run(
|
|
1683
|
+
branch.id,
|
|
1684
|
+
branch.chatId,
|
|
1685
|
+
branch.name,
|
|
1686
|
+
branch.headMessageId,
|
|
1687
|
+
branch.isActive ? 1 : 0,
|
|
1688
|
+
branch.createdAt
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1691
|
+
async getBranch(chatId, name) {
|
|
1692
|
+
const row = this.#db.prepare("SELECT * FROM branches WHERE chatId = ? AND name = ?").get(chatId, name);
|
|
1693
|
+
if (!row) {
|
|
1694
|
+
return void 0;
|
|
1695
|
+
}
|
|
1696
|
+
return {
|
|
1697
|
+
id: row.id,
|
|
1698
|
+
chatId: row.chatId,
|
|
1699
|
+
name: row.name,
|
|
1700
|
+
headMessageId: row.headMessageId,
|
|
1701
|
+
isActive: row.isActive === 1,
|
|
1702
|
+
createdAt: row.createdAt
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
async getActiveBranch(chatId) {
|
|
1706
|
+
const row = this.#db.prepare("SELECT * FROM branches WHERE chatId = ? AND isActive = 1").get(chatId);
|
|
1707
|
+
if (!row) {
|
|
1708
|
+
return void 0;
|
|
1709
|
+
}
|
|
1710
|
+
return {
|
|
1711
|
+
id: row.id,
|
|
1712
|
+
chatId: row.chatId,
|
|
1713
|
+
name: row.name,
|
|
1714
|
+
headMessageId: row.headMessageId,
|
|
1715
|
+
isActive: true,
|
|
1716
|
+
createdAt: row.createdAt
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
async setActiveBranch(chatId, branchId) {
|
|
1720
|
+
this.#db.prepare("UPDATE branches SET isActive = 0 WHERE chatId = ?").run(chatId);
|
|
1721
|
+
this.#db.prepare("UPDATE branches SET isActive = 1 WHERE id = ?").run(branchId);
|
|
1722
|
+
}
|
|
1723
|
+
async updateBranchHead(branchId, messageId) {
|
|
1724
|
+
this.#db.prepare("UPDATE branches SET headMessageId = ? WHERE id = ?").run(messageId, branchId);
|
|
1725
|
+
}
|
|
1726
|
+
async listBranches(chatId) {
|
|
1727
|
+
const branches = this.#db.prepare(
|
|
1728
|
+
`SELECT
|
|
1729
|
+
b.id,
|
|
1730
|
+
b.name,
|
|
1731
|
+
b.headMessageId,
|
|
1732
|
+
b.isActive,
|
|
1733
|
+
b.createdAt
|
|
1734
|
+
FROM branches b
|
|
1735
|
+
WHERE b.chatId = ?
|
|
1736
|
+
ORDER BY b.createdAt ASC`
|
|
1737
|
+
).all(chatId);
|
|
1738
|
+
const result = [];
|
|
1739
|
+
for (const branch of branches) {
|
|
1740
|
+
let messageCount = 0;
|
|
1741
|
+
if (branch.headMessageId) {
|
|
1742
|
+
const countRow = this.#db.prepare(
|
|
1743
|
+
`WITH RECURSIVE chain AS (
|
|
1744
|
+
SELECT id, parentId FROM messages WHERE id = ?
|
|
1745
|
+
UNION ALL
|
|
1746
|
+
SELECT m.id, m.parentId FROM messages m
|
|
1747
|
+
INNER JOIN chain c ON m.id = c.parentId
|
|
1748
|
+
)
|
|
1749
|
+
SELECT COUNT(*) as count FROM chain`
|
|
1750
|
+
).get(branch.headMessageId);
|
|
1751
|
+
messageCount = countRow.count;
|
|
1752
|
+
}
|
|
1753
|
+
result.push({
|
|
1754
|
+
id: branch.id,
|
|
1755
|
+
name: branch.name,
|
|
1756
|
+
headMessageId: branch.headMessageId,
|
|
1757
|
+
isActive: branch.isActive === 1,
|
|
1758
|
+
messageCount,
|
|
1759
|
+
createdAt: branch.createdAt
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
return result;
|
|
1763
|
+
}
|
|
1764
|
+
// ==========================================================================
|
|
1765
|
+
// Checkpoint Operations
|
|
1766
|
+
// ==========================================================================
|
|
1767
|
+
async createCheckpoint(checkpoint) {
|
|
1768
|
+
this.#db.prepare(
|
|
1769
|
+
`INSERT INTO checkpoints (id, chatId, name, messageId, createdAt)
|
|
1770
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1771
|
+
ON CONFLICT(chatId, name) DO UPDATE SET
|
|
1772
|
+
messageId = excluded.messageId,
|
|
1773
|
+
createdAt = excluded.createdAt`
|
|
1774
|
+
).run(
|
|
1775
|
+
checkpoint.id,
|
|
1776
|
+
checkpoint.chatId,
|
|
1777
|
+
checkpoint.name,
|
|
1778
|
+
checkpoint.messageId,
|
|
1779
|
+
checkpoint.createdAt
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
async getCheckpoint(chatId, name) {
|
|
1783
|
+
const row = this.#db.prepare("SELECT * FROM checkpoints WHERE chatId = ? AND name = ?").get(chatId, name);
|
|
1784
|
+
if (!row) {
|
|
1785
|
+
return void 0;
|
|
1786
|
+
}
|
|
1787
|
+
return {
|
|
1788
|
+
id: row.id,
|
|
1789
|
+
chatId: row.chatId,
|
|
1790
|
+
name: row.name,
|
|
1791
|
+
messageId: row.messageId,
|
|
1792
|
+
createdAt: row.createdAt
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
async listCheckpoints(chatId) {
|
|
1796
|
+
const rows = this.#db.prepare(
|
|
1797
|
+
`SELECT id, name, messageId, createdAt
|
|
1798
|
+
FROM checkpoints
|
|
1799
|
+
WHERE chatId = ?
|
|
1800
|
+
ORDER BY createdAt DESC`
|
|
1801
|
+
).all(chatId);
|
|
1802
|
+
return rows.map((row) => ({
|
|
1803
|
+
id: row.id,
|
|
1804
|
+
name: row.name,
|
|
1805
|
+
messageId: row.messageId,
|
|
1806
|
+
createdAt: row.createdAt
|
|
1807
|
+
}));
|
|
1808
|
+
}
|
|
1809
|
+
async deleteCheckpoint(chatId, name) {
|
|
1810
|
+
this.#db.prepare("DELETE FROM checkpoints WHERE chatId = ? AND name = ?").run(chatId, name);
|
|
1811
|
+
}
|
|
1812
|
+
// ==========================================================================
|
|
1813
|
+
// Search Operations
|
|
1814
|
+
// ==========================================================================
|
|
1815
|
+
async searchMessages(chatId, query, options) {
|
|
1816
|
+
const limit = options?.limit ?? 20;
|
|
1817
|
+
const roles = options?.roles;
|
|
1818
|
+
let sql = `
|
|
1819
|
+
SELECT
|
|
1820
|
+
m.id,
|
|
1821
|
+
m.chatId,
|
|
1822
|
+
m.parentId,
|
|
1823
|
+
m.name,
|
|
1824
|
+
m.type,
|
|
1825
|
+
m.data,
|
|
1826
|
+
m.createdAt,
|
|
1827
|
+
fts.rank,
|
|
1828
|
+
snippet(messages_fts, 3, '<mark>', '</mark>', '...', 32) as snippet
|
|
1829
|
+
FROM messages_fts fts
|
|
1830
|
+
JOIN messages m ON m.id = fts.messageId
|
|
1831
|
+
WHERE messages_fts MATCH ?
|
|
1832
|
+
AND fts.chatId = ?
|
|
1833
|
+
`;
|
|
1834
|
+
const params = [query, chatId];
|
|
1835
|
+
if (roles && roles.length > 0) {
|
|
1836
|
+
const placeholders = roles.map(() => "?").join(", ");
|
|
1837
|
+
sql += ` AND fts.name IN (${placeholders})`;
|
|
1838
|
+
params.push(...roles);
|
|
1839
|
+
}
|
|
1840
|
+
sql += " ORDER BY fts.rank LIMIT ?";
|
|
1841
|
+
params.push(limit);
|
|
1842
|
+
const rows = this.#db.prepare(sql).all(...params);
|
|
1843
|
+
return rows.map((row) => ({
|
|
1844
|
+
message: {
|
|
1845
|
+
id: row.id,
|
|
1846
|
+
chatId: row.chatId,
|
|
1847
|
+
parentId: row.parentId,
|
|
1848
|
+
name: row.name,
|
|
1849
|
+
type: row.type ?? void 0,
|
|
1850
|
+
data: JSON.parse(row.data),
|
|
1851
|
+
createdAt: row.createdAt
|
|
1852
|
+
},
|
|
1853
|
+
rank: row.rank,
|
|
1854
|
+
snippet: row.snippet
|
|
1855
|
+
}));
|
|
1856
|
+
}
|
|
1857
|
+
// ==========================================================================
|
|
1858
|
+
// Visualization Operations
|
|
1859
|
+
// ==========================================================================
|
|
1860
|
+
async getGraph(chatId) {
|
|
1861
|
+
const messageRows = this.#db.prepare(
|
|
1862
|
+
`SELECT id, parentId, name, data, createdAt
|
|
1863
|
+
FROM messages
|
|
1864
|
+
WHERE chatId = ?
|
|
1865
|
+
ORDER BY createdAt ASC`
|
|
1866
|
+
).all(chatId);
|
|
1867
|
+
const nodes = messageRows.map((row) => {
|
|
1868
|
+
const data = JSON.parse(row.data);
|
|
1869
|
+
const content = typeof data === "string" ? data : JSON.stringify(data);
|
|
1870
|
+
return {
|
|
1871
|
+
id: row.id,
|
|
1872
|
+
parentId: row.parentId,
|
|
1873
|
+
role: row.name,
|
|
1874
|
+
content: content.length > 50 ? content.slice(0, 50) + "..." : content,
|
|
1875
|
+
createdAt: row.createdAt
|
|
1876
|
+
};
|
|
1877
|
+
});
|
|
1878
|
+
const branchRows = this.#db.prepare(
|
|
1879
|
+
`SELECT name, headMessageId, isActive
|
|
1880
|
+
FROM branches
|
|
1881
|
+
WHERE chatId = ?
|
|
1882
|
+
ORDER BY createdAt ASC`
|
|
1883
|
+
).all(chatId);
|
|
1884
|
+
const branches = branchRows.map((row) => ({
|
|
1885
|
+
name: row.name,
|
|
1886
|
+
headMessageId: row.headMessageId,
|
|
1887
|
+
isActive: row.isActive === 1
|
|
1888
|
+
}));
|
|
1889
|
+
const checkpointRows = this.#db.prepare(
|
|
1890
|
+
`SELECT name, messageId
|
|
1891
|
+
FROM checkpoints
|
|
1892
|
+
WHERE chatId = ?
|
|
1893
|
+
ORDER BY createdAt ASC`
|
|
1894
|
+
).all(chatId);
|
|
1895
|
+
const checkpoints = checkpointRows.map((row) => ({
|
|
1896
|
+
name: row.name,
|
|
1897
|
+
messageId: row.messageId
|
|
1898
|
+
}));
|
|
1899
|
+
return {
|
|
1900
|
+
chatId,
|
|
1901
|
+
nodes,
|
|
1902
|
+
branches,
|
|
1903
|
+
checkpoints
|
|
1904
|
+
};
|
|
618
1905
|
}
|
|
619
|
-
|
|
620
|
-
|
|
1906
|
+
};
|
|
1907
|
+
var InMemoryContextStore = class extends SqliteContextStore {
|
|
1908
|
+
constructor() {
|
|
1909
|
+
super(":memory:");
|
|
621
1910
|
}
|
|
622
1911
|
};
|
|
623
|
-
var
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1912
|
+
var Agent = class _Agent {
|
|
1913
|
+
#options;
|
|
1914
|
+
#guardrails = [];
|
|
1915
|
+
tools;
|
|
1916
|
+
constructor(options) {
|
|
1917
|
+
this.#options = options;
|
|
1918
|
+
this.tools = options.tools || {};
|
|
1919
|
+
this.#guardrails = options.guardrails || [];
|
|
1920
|
+
}
|
|
1921
|
+
async generate(contextVariables, config) {
|
|
1922
|
+
if (!this.#options.context) {
|
|
1923
|
+
throw new Error(`Agent ${this.#options.name} is missing a context.`);
|
|
1924
|
+
}
|
|
1925
|
+
if (!this.#options.model) {
|
|
1926
|
+
throw new Error(`Agent ${this.#options.name} is missing a model.`);
|
|
1927
|
+
}
|
|
1928
|
+
const { messages, systemPrompt } = await this.#options.context.resolve({
|
|
1929
|
+
renderer: new XmlRenderer()
|
|
1930
|
+
});
|
|
1931
|
+
return generateText({
|
|
1932
|
+
abortSignal: config?.abortSignal,
|
|
1933
|
+
providerOptions: this.#options.providerOptions,
|
|
1934
|
+
model: this.#options.model,
|
|
1935
|
+
system: systemPrompt,
|
|
1936
|
+
messages: convertToModelMessages(messages),
|
|
1937
|
+
stopWhen: stepCountIs(25),
|
|
1938
|
+
tools: this.#options.tools,
|
|
1939
|
+
experimental_context: contextVariables,
|
|
1940
|
+
toolChoice: this.#options.toolChoice,
|
|
1941
|
+
onStepFinish: (step) => {
|
|
1942
|
+
const toolCall = step.toolCalls.at(-1);
|
|
1943
|
+
if (toolCall) {
|
|
1944
|
+
console.log(
|
|
1945
|
+
`Debug: ${chalk.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
});
|
|
627
1950
|
}
|
|
628
|
-
|
|
629
|
-
|
|
1951
|
+
/**
|
|
1952
|
+
* Stream a response from the agent.
|
|
1953
|
+
*
|
|
1954
|
+
* When guardrails are configured, `toUIMessageStream()` is wrapped to provide
|
|
1955
|
+
* self-correction behavior. Direct access to fullStream/textStream bypasses guardrails.
|
|
1956
|
+
*
|
|
1957
|
+
* @example
|
|
1958
|
+
* ```typescript
|
|
1959
|
+
* const stream = await agent.stream({});
|
|
1960
|
+
*
|
|
1961
|
+
* // With guardrails - use toUIMessageStream for protection
|
|
1962
|
+
* await printer.readableStream(stream.toUIMessageStream());
|
|
1963
|
+
*
|
|
1964
|
+
* // Or use printer.stdout which uses toUIMessageStream internally
|
|
1965
|
+
* await printer.stdout(stream);
|
|
1966
|
+
* ```
|
|
1967
|
+
*/
|
|
1968
|
+
async stream(contextVariables, config) {
|
|
1969
|
+
if (!this.#options.context) {
|
|
1970
|
+
throw new Error(`Agent ${this.#options.name} is missing a context.`);
|
|
1971
|
+
}
|
|
1972
|
+
if (!this.#options.model) {
|
|
1973
|
+
throw new Error(`Agent ${this.#options.name} is missing a model.`);
|
|
1974
|
+
}
|
|
1975
|
+
const result = await this.#createRawStream(contextVariables, config);
|
|
1976
|
+
if (this.#guardrails.length === 0) {
|
|
1977
|
+
return result;
|
|
1978
|
+
}
|
|
1979
|
+
return this.#wrapWithGuardrails(result, contextVariables, config);
|
|
630
1980
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
1981
|
+
/**
|
|
1982
|
+
* Create a raw stream without guardrail processing.
|
|
1983
|
+
*/
|
|
1984
|
+
async #createRawStream(contextVariables, config) {
|
|
1985
|
+
const { messages, systemPrompt } = await this.#options.context.resolve({
|
|
1986
|
+
renderer: new XmlRenderer()
|
|
1987
|
+
});
|
|
1988
|
+
const runId = generateId2();
|
|
1989
|
+
return streamText({
|
|
1990
|
+
abortSignal: config?.abortSignal,
|
|
1991
|
+
providerOptions: this.#options.providerOptions,
|
|
1992
|
+
model: this.#options.model,
|
|
1993
|
+
system: systemPrompt,
|
|
1994
|
+
messages: convertToModelMessages(messages),
|
|
1995
|
+
stopWhen: stepCountIs(25),
|
|
1996
|
+
experimental_transform: config?.transform ?? smoothStream(),
|
|
1997
|
+
tools: this.#options.tools,
|
|
1998
|
+
experimental_context: contextVariables,
|
|
1999
|
+
toolChoice: this.#options.toolChoice,
|
|
2000
|
+
onStepFinish: (step) => {
|
|
2001
|
+
const toolCall = step.toolCalls.at(-1);
|
|
2002
|
+
if (toolCall) {
|
|
2003
|
+
console.log(
|
|
2004
|
+
`Debug: (${runId}) ${chalk.bold.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
/**
|
|
2011
|
+
* Wrap a StreamTextResult with guardrail protection on toUIMessageStream().
|
|
2012
|
+
*
|
|
2013
|
+
* When a guardrail fails:
|
|
2014
|
+
* 1. Accumulated text + feedback is appended as the assistant's self-correction
|
|
2015
|
+
* 2. The feedback is written to the output stream (user sees the correction)
|
|
2016
|
+
* 3. A new stream is started and the model continues from the correction
|
|
2017
|
+
*/
|
|
2018
|
+
#wrapWithGuardrails(result, contextVariables, config) {
|
|
2019
|
+
const maxRetries = config?.maxRetries ?? this.#options.maxGuardrailRetries ?? 3;
|
|
2020
|
+
const context = this.#options.context;
|
|
2021
|
+
const originalToUIMessageStream = result.toUIMessageStream.bind(result);
|
|
2022
|
+
result.toUIMessageStream = (options) => {
|
|
2023
|
+
return createUIMessageStream({
|
|
2024
|
+
generateId: generateId2,
|
|
2025
|
+
execute: async ({ writer }) => {
|
|
2026
|
+
let currentResult = result;
|
|
2027
|
+
let attempt = 0;
|
|
2028
|
+
const guardrailContext = {
|
|
2029
|
+
availableTools: Object.keys(this.tools)
|
|
2030
|
+
};
|
|
2031
|
+
while (attempt < maxRetries) {
|
|
2032
|
+
if (config?.abortSignal?.aborted) {
|
|
2033
|
+
writer.write({ type: "finish" });
|
|
2034
|
+
return;
|
|
643
2035
|
}
|
|
644
|
-
|
|
645
|
-
|
|
2036
|
+
attempt++;
|
|
2037
|
+
let accumulatedText = "";
|
|
2038
|
+
let guardrailFailed = false;
|
|
2039
|
+
let failureFeedback = "";
|
|
2040
|
+
const uiStream = currentResult === result ? originalToUIMessageStream(options) : currentResult.toUIMessageStream(options);
|
|
2041
|
+
for await (const part of uiStream) {
|
|
2042
|
+
const checkResult = runGuardrailChain(
|
|
2043
|
+
part,
|
|
2044
|
+
this.#guardrails,
|
|
2045
|
+
guardrailContext
|
|
2046
|
+
);
|
|
2047
|
+
if (checkResult.type === "fail") {
|
|
2048
|
+
guardrailFailed = true;
|
|
2049
|
+
failureFeedback = checkResult.feedback;
|
|
2050
|
+
console.log(
|
|
2051
|
+
chalk.yellow(
|
|
2052
|
+
`[${this.#options.name}] Guardrail triggered (attempt ${attempt}/${maxRetries}): ${failureFeedback.slice(0, 50)}...`
|
|
2053
|
+
)
|
|
2054
|
+
);
|
|
2055
|
+
break;
|
|
2056
|
+
}
|
|
2057
|
+
if (checkResult.part.type === "text-delta") {
|
|
2058
|
+
accumulatedText += checkResult.part.delta;
|
|
2059
|
+
}
|
|
2060
|
+
writer.write(checkResult.part);
|
|
2061
|
+
}
|
|
2062
|
+
if (!guardrailFailed) {
|
|
2063
|
+
writer.write({ type: "finish" });
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
if (attempt >= maxRetries) {
|
|
2067
|
+
console.error(
|
|
2068
|
+
chalk.red(
|
|
2069
|
+
`[${this.#options.name}] Guardrail retry limit (${maxRetries}) exceeded.`
|
|
2070
|
+
)
|
|
2071
|
+
);
|
|
2072
|
+
writer.write({ type: "finish" });
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
writer.write({
|
|
2076
|
+
type: "text-delta",
|
|
2077
|
+
id: generateId2(),
|
|
2078
|
+
delta: ` ${failureFeedback}`
|
|
2079
|
+
});
|
|
2080
|
+
const selfCorrectionText = accumulatedText + " " + failureFeedback;
|
|
2081
|
+
context.set(assistantText(selfCorrectionText));
|
|
2082
|
+
await context.save();
|
|
2083
|
+
currentResult = await this.#createRawStream(
|
|
2084
|
+
contextVariables,
|
|
2085
|
+
config
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
},
|
|
2089
|
+
onError: (error) => {
|
|
2090
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
2091
|
+
return `Stream failed: ${message2}`;
|
|
2092
|
+
}
|
|
646
2093
|
});
|
|
647
|
-
|
|
648
|
-
|
|
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;
|
|
2094
|
+
};
|
|
2095
|
+
return result;
|
|
689
2096
|
}
|
|
690
|
-
|
|
691
|
-
return
|
|
2097
|
+
clone(overrides) {
|
|
2098
|
+
return new _Agent({
|
|
2099
|
+
...this.#options,
|
|
2100
|
+
...overrides
|
|
2101
|
+
});
|
|
692
2102
|
}
|
|
693
|
-
|
|
2103
|
+
};
|
|
2104
|
+
function agent(options) {
|
|
2105
|
+
return new Agent(options);
|
|
694
2106
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
2107
|
+
function structuredOutput(options) {
|
|
2108
|
+
return {
|
|
2109
|
+
async generate(contextVariables, config) {
|
|
2110
|
+
if (!options.context) {
|
|
2111
|
+
throw new Error(
|
|
2112
|
+
`structuredOutput "${options.name}" is missing a context.`
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
if (!options.model) {
|
|
2116
|
+
throw new Error(
|
|
2117
|
+
`structuredOutput "${options.name}" is missing a model.`
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
2120
|
+
const { messages, systemPrompt } = await options.context.resolve({
|
|
2121
|
+
renderer: new XmlRenderer()
|
|
2122
|
+
});
|
|
2123
|
+
const result = await generateText({
|
|
2124
|
+
abortSignal: config?.abortSignal,
|
|
2125
|
+
providerOptions: options.providerOptions,
|
|
2126
|
+
model: options.model,
|
|
2127
|
+
system: systemPrompt,
|
|
2128
|
+
messages: convertToModelMessages(messages),
|
|
2129
|
+
stopWhen: stepCountIs(25),
|
|
2130
|
+
experimental_context: contextVariables,
|
|
2131
|
+
experimental_output: Output.object({ schema: options.schema })
|
|
2132
|
+
});
|
|
2133
|
+
return result.experimental_output;
|
|
701
2134
|
},
|
|
702
|
-
{
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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.`
|
|
2135
|
+
async stream(contextVariables, config) {
|
|
2136
|
+
if (!options.context) {
|
|
2137
|
+
throw new Error(
|
|
2138
|
+
`structuredOutput "${options.name}" is missing a context.`
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
if (!options.model) {
|
|
2142
|
+
throw new Error(
|
|
2143
|
+
`structuredOutput "${options.name}" is missing a model.`
|
|
731
2144
|
);
|
|
732
|
-
errors.push(context2.error);
|
|
733
2145
|
}
|
|
2146
|
+
const { messages, systemPrompt } = await options.context.resolve({
|
|
2147
|
+
renderer: new XmlRenderer()
|
|
2148
|
+
});
|
|
2149
|
+
return streamText({
|
|
2150
|
+
abortSignal: config?.abortSignal,
|
|
2151
|
+
providerOptions: options.providerOptions,
|
|
2152
|
+
model: options.model,
|
|
2153
|
+
system: systemPrompt,
|
|
2154
|
+
messages: convertToModelMessages(messages),
|
|
2155
|
+
stopWhen: stepCountIs(25),
|
|
2156
|
+
experimental_transform: config?.transform ?? smoothStream(),
|
|
2157
|
+
experimental_context: contextVariables,
|
|
2158
|
+
experimental_output: Output.object({ schema: options.schema })
|
|
2159
|
+
});
|
|
734
2160
|
}
|
|
735
|
-
|
|
2161
|
+
};
|
|
736
2162
|
}
|
|
737
2163
|
|
|
2164
|
+
// packages/text2sql/src/lib/agents/explainer.agent.ts
|
|
2165
|
+
import { groq } from "@ai-sdk/groq";
|
|
2166
|
+
import dedent from "dedent";
|
|
2167
|
+
import z from "zod";
|
|
2168
|
+
import { agent as agent2 } from "@deepagents/agent";
|
|
2169
|
+
var explainerAgent = agent2({
|
|
2170
|
+
name: "explainer",
|
|
2171
|
+
model: groq("openai/gpt-oss-20b"),
|
|
2172
|
+
prompt: (state) => dedent`
|
|
2173
|
+
You are an expert SQL tutor.
|
|
2174
|
+
Explain the following SQL query in plain English to a non-technical user.
|
|
2175
|
+
Focus on the intent and logic, not the syntax.
|
|
2176
|
+
|
|
2177
|
+
<sql>
|
|
2178
|
+
${state?.sql}
|
|
2179
|
+
</sql>
|
|
2180
|
+
`,
|
|
2181
|
+
output: z.object({
|
|
2182
|
+
explanation: z.string().describe("The explanation of the SQL query.")
|
|
2183
|
+
})
|
|
2184
|
+
});
|
|
2185
|
+
|
|
738
2186
|
// packages/text2sql/src/lib/agents/developer.agent.ts
|
|
739
2187
|
var tools = {
|
|
740
2188
|
/**
|
|
741
|
-
*
|
|
742
|
-
* Uses the toSql function with retry logic and validation.
|
|
2189
|
+
* Validate SQL query syntax before execution.
|
|
743
2190
|
*/
|
|
744
|
-
|
|
745
|
-
description:
|
|
746
|
-
|
|
747
|
-
The query
|
|
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")
|
|
2191
|
+
validate_query: tool({
|
|
2192
|
+
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.`,
|
|
2193
|
+
inputSchema: z2.object({
|
|
2194
|
+
sql: z2.string().describe("The SQL query to validate.")
|
|
754
2195
|
}),
|
|
755
|
-
execute: async ({
|
|
2196
|
+
execute: async ({ sql }, options) => {
|
|
756
2197
|
const state = toState(options);
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
};
|
|
2198
|
+
const result = await state.adapter.validate(sql);
|
|
2199
|
+
if (typeof result === "string") {
|
|
2200
|
+
return `Validation Error: ${result}`;
|
|
775
2201
|
}
|
|
2202
|
+
return "Query is valid.";
|
|
2203
|
+
}
|
|
2204
|
+
}),
|
|
2205
|
+
/**
|
|
2206
|
+
* Execute SQL query against the database.
|
|
2207
|
+
*/
|
|
2208
|
+
db_query: tool({
|
|
2209
|
+
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.`,
|
|
2210
|
+
inputSchema: z2.object({
|
|
2211
|
+
reasoning: z2.string().describe(
|
|
2212
|
+
"Your reasoning for why this SQL query is relevant to the user request."
|
|
2213
|
+
),
|
|
2214
|
+
sql: z2.string().min(1, { message: "SQL query cannot be empty." }).refine(
|
|
2215
|
+
(sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
|
|
2216
|
+
{
|
|
2217
|
+
message: "Only read-only SELECT or WITH queries are allowed."
|
|
2218
|
+
}
|
|
2219
|
+
).describe("The SQL query to execute against the database.")
|
|
2220
|
+
}),
|
|
2221
|
+
execute: ({ sql }, options) => {
|
|
2222
|
+
const state = toState(options);
|
|
2223
|
+
return state.adapter.execute(sql);
|
|
776
2224
|
}
|
|
777
2225
|
}),
|
|
778
2226
|
/**
|
|
@@ -785,129 +2233,44 @@ var tools = {
|
|
|
785
2233
|
|
|
786
2234
|
The explanation focuses on intent and logic, not syntax.
|
|
787
2235
|
`,
|
|
788
|
-
inputSchema:
|
|
789
|
-
sql:
|
|
2236
|
+
inputSchema: z2.object({
|
|
2237
|
+
sql: z2.string().min(1).describe("The SQL query to explain")
|
|
790
2238
|
}),
|
|
791
2239
|
execute: async ({ sql }) => {
|
|
792
|
-
const { experimental_output } = await
|
|
2240
|
+
const { experimental_output } = await generate(explainerAgent, [], {
|
|
793
2241
|
sql
|
|
794
2242
|
});
|
|
795
2243
|
return { explanation: experimental_output.explanation };
|
|
796
2244
|
}
|
|
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
|
|
2245
|
+
})
|
|
855
2246
|
};
|
|
856
|
-
var
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
-
});
|
|
2247
|
+
var fragments = [
|
|
2248
|
+
persona({
|
|
2249
|
+
name: "developer_assistant",
|
|
2250
|
+
role: "You are an expert SQL developer assistant helping power users build and refine queries."
|
|
2251
|
+
}),
|
|
2252
|
+
hint("Be transparent: show the SQL you generate before explaining it"),
|
|
2253
|
+
hint("Be precise: provide exact column names and table references"),
|
|
2254
|
+
hint("Suggest refinements and alternatives when appropriate"),
|
|
2255
|
+
hint("Support both natural language questions AND raw SQL input"),
|
|
2256
|
+
hint("When validating user SQL, explain any errors clearly")
|
|
2257
|
+
];
|
|
2258
|
+
var developer_agent_default = { tools, fragments };
|
|
896
2259
|
|
|
897
2260
|
// packages/text2sql/src/lib/agents/suggestions.agents.ts
|
|
898
|
-
import { groq as
|
|
2261
|
+
import { groq as groq2 } from "@ai-sdk/groq";
|
|
899
2262
|
import dedent3 from "dedent";
|
|
900
|
-
import
|
|
901
|
-
import { agent as
|
|
902
|
-
var suggestionsAgent =
|
|
2263
|
+
import z3 from "zod";
|
|
2264
|
+
import { agent as agent3, thirdPersonPrompt } from "@deepagents/agent";
|
|
2265
|
+
var suggestionsAgent = agent3({
|
|
903
2266
|
name: "text2sql-suggestions",
|
|
904
|
-
model:
|
|
905
|
-
output:
|
|
906
|
-
suggestions:
|
|
907
|
-
|
|
908
|
-
question:
|
|
909
|
-
sql:
|
|
910
|
-
businessValue:
|
|
2267
|
+
model: groq2("openai/gpt-oss-20b"),
|
|
2268
|
+
output: z3.object({
|
|
2269
|
+
suggestions: z3.array(
|
|
2270
|
+
z3.object({
|
|
2271
|
+
question: z3.string().describe("A complex, high-impact business question."),
|
|
2272
|
+
sql: z3.string().describe("The SQL statement needed to answer the question."),
|
|
2273
|
+
businessValue: z3.string().describe("Why the question matters to stakeholders.")
|
|
911
2274
|
})
|
|
912
2275
|
).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
|
|
913
2276
|
}),
|
|
@@ -947,187 +2310,33 @@ var suggestionsAgent = agent4({
|
|
|
947
2310
|
});
|
|
948
2311
|
|
|
949
2312
|
// packages/text2sql/src/lib/agents/text2sql.agent.ts
|
|
950
|
-
import { groq as
|
|
2313
|
+
import { groq as groq3 } from "@ai-sdk/groq";
|
|
951
2314
|
import { tool as tool2 } from "ai";
|
|
952
|
-
import
|
|
953
|
-
import {
|
|
954
|
-
import { scratchpad_tool
|
|
955
|
-
|
|
956
|
-
// packages/text2sql/src/lib/memory/memory.prompt.ts
|
|
957
|
-
var memory_prompt_default = toInstructions(
|
|
958
|
-
"memory_guidelines",
|
|
959
|
-
// Section 1: Core behavior
|
|
960
|
-
guardrail({
|
|
961
|
-
rule: "Silently store user information when revealed. Respond to memory-related requests.",
|
|
962
|
-
reason: "Memory enables personalized responses across conversations",
|
|
963
|
-
action: "Store silently on reveal. Respond explicitly when asked about memories."
|
|
964
|
-
}),
|
|
965
|
-
// Section 2: Workflows for memory operations
|
|
966
|
-
workflow({
|
|
967
|
-
task: "Store user identity silently",
|
|
968
|
-
triggers: ["I'm a", "I work as", "my name is", "my role is"],
|
|
969
|
-
steps: [
|
|
970
|
-
"Extract name and/or role from the statement",
|
|
971
|
-
'Call remember_memory() with type: "identity" and name/role fields',
|
|
972
|
-
"Do NOT announce or confirm - continue conversation naturally"
|
|
973
|
-
],
|
|
974
|
-
notes: "This should happen silently without disrupting the conversation flow."
|
|
975
|
-
}),
|
|
976
|
-
workflow({
|
|
977
|
-
task: "Store user preferences and context silently",
|
|
978
|
-
triggers: ["When I say", "I prefer", "I'm working on"],
|
|
979
|
-
steps: [
|
|
980
|
-
"Identify the memory type (alias/preference/context/correction)",
|
|
981
|
-
"Extract the relevant information",
|
|
982
|
-
"Call remember_memory() with appropriate type and fields",
|
|
983
|
-
"Do NOT announce or confirm - continue conversation naturally"
|
|
984
|
-
],
|
|
985
|
-
notes: "This should happen silently without disrupting the conversation flow."
|
|
986
|
-
}),
|
|
987
|
-
workflow({
|
|
988
|
-
task: "Forget a memory",
|
|
989
|
-
triggers: ["forget that", "remove my", "delete the", "don't remember that"],
|
|
990
|
-
steps: [
|
|
991
|
-
"Call recall_memory() to list relevant memories",
|
|
992
|
-
"Find the memory ID that matches user request",
|
|
993
|
-
"Call forget_memory({ id }) with the found ID",
|
|
994
|
-
"Confirm to user what was forgotten"
|
|
995
|
-
]
|
|
996
|
-
}),
|
|
997
|
-
workflow({
|
|
998
|
-
task: "Update a memory",
|
|
999
|
-
triggers: ["actually now I", "I changed", "update my", "no longer"],
|
|
1000
|
-
steps: [
|
|
1001
|
-
"Call recall_memory() to find the existing memory",
|
|
1002
|
-
"Get the memory ID from results",
|
|
1003
|
-
"Call update_memory({ id, memory }) with new data",
|
|
1004
|
-
"Confirm the update to user"
|
|
1005
|
-
]
|
|
1006
|
-
}),
|
|
1007
|
-
// Section 3: Type disambiguation
|
|
1008
|
-
explain({
|
|
1009
|
-
concept: "identity vs context",
|
|
1010
|
-
explanation: "Identity = WHO the user is (name and/or role, permanent). Context = WHAT they are working on (temporary focus).",
|
|
1011
|
-
therefore: "Identity rarely changes. Context changes per project/task."
|
|
1012
|
-
}),
|
|
1013
|
-
explain({
|
|
1014
|
-
concept: "alias vs correction",
|
|
1015
|
-
explanation: "Alias = user defines their own term/shorthand. Correction = user fixes a misunderstanding about existing data/schema.",
|
|
1016
|
-
therefore: "Alias is vocabulary. Correction is data clarification."
|
|
1017
|
-
}),
|
|
1018
|
-
explain({
|
|
1019
|
-
concept: "preference memory type",
|
|
1020
|
-
explanation: "Stores output/style/format preferences. Fields: { aspect: string, value: string }",
|
|
1021
|
-
therefore: "Use for formatting, limits, display style, data scope filters"
|
|
1022
|
-
}),
|
|
1023
|
-
// Section 4: Clarifications for ambiguous situations
|
|
1024
|
-
clarification({
|
|
1025
|
-
when: 'user says something like "X actually means Y" but unclear if defining their term or correcting data',
|
|
1026
|
-
ask: "Are you defining your own shorthand for this term, or correcting how the data/schema actually works?",
|
|
1027
|
-
reason: "Alias is personal vocabulary. Correction is a data/schema clarification that applies universally."
|
|
1028
|
-
}),
|
|
1029
|
-
clarification({
|
|
1030
|
-
when: "user mentions a project or task that could be their identity or current focus",
|
|
1031
|
-
ask: "Is this your ongoing identity (name/role), or a specific project you are currently working on?",
|
|
1032
|
-
reason: "Identity is permanent. Context is temporary focus that may change."
|
|
1033
|
-
}),
|
|
1034
|
-
// Section 5: Examples
|
|
1035
|
-
// Identity - role
|
|
1036
|
-
example({
|
|
1037
|
-
question: "I'm the VP of Sales",
|
|
1038
|
-
answer: 'remember_memory({ memory: { type: "identity", role: "VP of Sales" }})',
|
|
1039
|
-
note: "Identity stores role"
|
|
1040
|
-
}),
|
|
1041
|
-
// Identity - name
|
|
1042
|
-
example({
|
|
1043
|
-
question: "My name is Sarah",
|
|
1044
|
-
answer: 'remember_memory({ memory: { type: "identity", name: "Sarah" }})',
|
|
1045
|
-
note: "Identity stores name"
|
|
1046
|
-
}),
|
|
1047
|
-
// Context
|
|
1048
|
-
example({
|
|
1049
|
-
question: "I'm analyzing Q4 performance",
|
|
1050
|
-
answer: 'remember_memory({ memory: { type: "context", description: "Analyzing Q4 performance" }})',
|
|
1051
|
-
note: "Current task = context"
|
|
1052
|
-
}),
|
|
1053
|
-
// Alias
|
|
1054
|
-
example({
|
|
1055
|
-
question: 'When I say "big customers", I mean revenue > $1M',
|
|
1056
|
-
answer: 'remember_memory({ memory: { type: "alias", term: "big customers", meaning: "revenue > $1M" }})',
|
|
1057
|
-
note: "User defining their vocabulary = alias"
|
|
1058
|
-
}),
|
|
1059
|
-
// Correction
|
|
1060
|
-
example({
|
|
1061
|
-
question: 'No, the status column uses 1 for active, not the string "active"',
|
|
1062
|
-
answer: 'remember_memory({ memory: { type: "correction", subject: "status column values", clarification: "Uses 1 for active, not string" }})',
|
|
1063
|
-
note: "Correcting schema/data assumption = correction"
|
|
1064
|
-
}),
|
|
1065
|
-
// Preference
|
|
1066
|
-
example({
|
|
1067
|
-
question: "Always show dates as YYYY-MM-DD",
|
|
1068
|
-
answer: 'remember_memory({ memory: { type: "preference", aspect: "date format", value: "YYYY-MM-DD" }})'
|
|
1069
|
-
}),
|
|
1070
|
-
// Recall
|
|
1071
|
-
example({
|
|
1072
|
-
question: "What do you remember about me?",
|
|
1073
|
-
answer: "recall_memory({})",
|
|
1074
|
-
note: "List all stored memories"
|
|
1075
|
-
}),
|
|
1076
|
-
// Section 6: What NOT to remember
|
|
1077
|
-
hint('Do NOT remember one-time query details like "show last 10 orders"'),
|
|
1078
|
-
hint(
|
|
1079
|
-
"Do NOT remember information already stored - use recall_memory to check first"
|
|
1080
|
-
),
|
|
1081
|
-
hint("Do NOT remember obvious or universal facts")
|
|
1082
|
-
);
|
|
1083
|
-
|
|
1084
|
-
// packages/text2sql/src/lib/agents/text2sql.agent.ts
|
|
2315
|
+
import z4 from "zod";
|
|
2316
|
+
import { toState as toState2 } from "@deepagents/agent";
|
|
2317
|
+
import { scratchpad_tool } from "@deepagents/toolbox";
|
|
1085
2318
|
var tools2 = {
|
|
1086
2319
|
validate_query: tool2({
|
|
1087
2320
|
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.`,
|
|
1088
|
-
inputSchema:
|
|
1089
|
-
sql:
|
|
2321
|
+
inputSchema: z4.object({
|
|
2322
|
+
sql: z4.string().describe("The SQL query to validate.")
|
|
1090
2323
|
}),
|
|
1091
2324
|
execute: async ({ sql }, options) => {
|
|
1092
2325
|
const state = toState2(options);
|
|
1093
2326
|
const result = await state.adapter.validate(sql);
|
|
1094
2327
|
if (typeof result === "string") {
|
|
1095
2328
|
return `Validation Error: ${result}`;
|
|
1096
|
-
}
|
|
1097
|
-
return "Query is valid.";
|
|
1098
|
-
}
|
|
1099
|
-
}),
|
|
1100
|
-
get_sample_rows: tool2({
|
|
1101
|
-
description: `Sample rows from a table to understand data formatting, codes, and value patterns. Use BEFORE writing queries when:
|
|
1102
|
-
- Column types in schema don't reveal format (e.g., "status" could be 'active'/'inactive' or 1/0)
|
|
1103
|
-
- Date/time formats are unclear (ISO, Unix timestamp, locale-specific)
|
|
1104
|
-
- You need to understand lookup table codes or enum values
|
|
1105
|
-
- Column names are ambiguous (e.g., "type", "category", "code")`,
|
|
1106
|
-
inputSchema: z5.object({
|
|
1107
|
-
tableName: z5.string().describe("The name of the table to sample."),
|
|
1108
|
-
columns: z5.array(z5.string()).optional().describe(
|
|
1109
|
-
"Specific columns to sample. If omitted, samples all columns."
|
|
1110
|
-
),
|
|
1111
|
-
limit: z5.number().min(1).max(10).default(3).optional().describe("Number of rows to sample (1-10, default 3).")
|
|
1112
|
-
}),
|
|
1113
|
-
execute: ({ tableName, columns, limit = 3 }, options) => {
|
|
1114
|
-
const safeLimit = Math.min(Math.max(1, limit), 10);
|
|
1115
|
-
const state = toState2(options);
|
|
1116
|
-
const sql = state.adapter.buildSampleRowsQuery(
|
|
1117
|
-
tableName,
|
|
1118
|
-
columns,
|
|
1119
|
-
safeLimit
|
|
1120
|
-
);
|
|
1121
|
-
return state.adapter.execute(sql);
|
|
2329
|
+
}
|
|
2330
|
+
return "Query is valid.";
|
|
1122
2331
|
}
|
|
1123
2332
|
}),
|
|
1124
2333
|
db_query: tool2({
|
|
1125
2334
|
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.`,
|
|
1126
|
-
inputSchema:
|
|
1127
|
-
reasoning:
|
|
2335
|
+
inputSchema: z4.object({
|
|
2336
|
+
reasoning: z4.string().describe(
|
|
1128
2337
|
"Your reasoning for why this SQL query is relevant to the user request."
|
|
1129
2338
|
),
|
|
1130
|
-
sql:
|
|
2339
|
+
sql: z4.string().min(1, { message: "SQL query cannot be empty." }).refine(
|
|
1131
2340
|
(sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
|
|
1132
2341
|
{
|
|
1133
2342
|
message: "Only read-only SELECT or WITH queries are allowed."
|
|
@@ -1139,198 +2348,20 @@ var tools2 = {
|
|
|
1139
2348
|
return state.adapter.execute(sql);
|
|
1140
2349
|
}
|
|
1141
2350
|
}),
|
|
1142
|
-
scratchpad:
|
|
1143
|
-
};
|
|
1144
|
-
var userMemoryTypes = [
|
|
1145
|
-
"identity",
|
|
1146
|
-
"alias",
|
|
1147
|
-
"preference",
|
|
1148
|
-
"context",
|
|
1149
|
-
"correction"
|
|
1150
|
-
];
|
|
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")
|
|
1155
|
-
}),
|
|
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")
|
|
1160
|
-
}),
|
|
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")
|
|
1165
|
-
}),
|
|
1166
|
-
z5.object({
|
|
1167
|
-
type: z5.literal("context"),
|
|
1168
|
-
description: z5.string().describe("What the user is currently working on")
|
|
1169
|
-
}),
|
|
1170
|
-
z5.object({
|
|
1171
|
-
type: z5.literal("correction"),
|
|
1172
|
-
subject: z5.string().describe("What was misunderstood"),
|
|
1173
|
-
clarification: z5.string().describe("The correct understanding")
|
|
1174
|
-
})
|
|
1175
|
-
]);
|
|
1176
|
-
var memoryTools = {
|
|
1177
|
-
remember_memory: tool2({
|
|
1178
|
-
description: "Store something about the user for future conversations. Use silently when user shares facts, preferences, vocabulary, corrections, or context.",
|
|
1179
|
-
inputSchema: z5.object({ memory: userMemorySchema }),
|
|
1180
|
-
execute: async ({ memory }, options) => {
|
|
1181
|
-
const state = toState2(
|
|
1182
|
-
options
|
|
1183
|
-
);
|
|
1184
|
-
await state.memory.remember(state.userId, memory);
|
|
1185
|
-
return "Remembered.";
|
|
1186
|
-
}
|
|
1187
|
-
}),
|
|
1188
|
-
forget_memory: tool2({
|
|
1189
|
-
description: "Forget a specific memory. Use when user asks to remove something.",
|
|
1190
|
-
inputSchema: z5.object({
|
|
1191
|
-
id: z5.string().describe("The ID of the teachable to forget")
|
|
1192
|
-
}),
|
|
1193
|
-
execute: async ({ id }, options) => {
|
|
1194
|
-
const state = toState2(options);
|
|
1195
|
-
await state.memory.forget(id);
|
|
1196
|
-
return "Forgotten.";
|
|
1197
|
-
}
|
|
1198
|
-
}),
|
|
1199
|
-
recall_memory: tool2({
|
|
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.",
|
|
1201
|
-
inputSchema: z5.object({
|
|
1202
|
-
type: z5.enum(userMemoryTypes).optional().catch(void 0).describe("Optional: filter by memory type")
|
|
1203
|
-
}),
|
|
1204
|
-
execute: async ({ type }, options) => {
|
|
1205
|
-
const state = toState2(
|
|
1206
|
-
options
|
|
1207
|
-
);
|
|
1208
|
-
const memories = await state.memory.recall(state.userId, type);
|
|
1209
|
-
if (memories.length === 0) {
|
|
1210
|
-
return type ? `No ${type} memories stored.` : "No memories stored.";
|
|
1211
|
-
}
|
|
1212
|
-
return memories.map((m) => ({
|
|
1213
|
-
id: m.id,
|
|
1214
|
-
type: m.type,
|
|
1215
|
-
data: m.data,
|
|
1216
|
-
createdAt: m.createdAt
|
|
1217
|
-
}));
|
|
1218
|
-
}
|
|
1219
|
-
}),
|
|
1220
|
-
update_memory: tool2({
|
|
1221
|
-
description: "Update an existing memory. Use when user wants to modify something you previously stored.",
|
|
1222
|
-
inputSchema: z5.object({
|
|
1223
|
-
memory: userMemorySchema,
|
|
1224
|
-
id: z5.string().describe("The ID of the memory to update")
|
|
1225
|
-
}),
|
|
1226
|
-
execute: async ({ id, memory }, options) => {
|
|
1227
|
-
const state = toState2(options);
|
|
1228
|
-
await state.memory.update(id, memory);
|
|
1229
|
-
return "Updated.";
|
|
1230
|
-
}
|
|
1231
|
-
})
|
|
2351
|
+
scratchpad: scratchpad_tool
|
|
1232
2352
|
};
|
|
1233
|
-
var
|
|
1234
|
-
|
|
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"),
|
|
2353
|
+
var t_a_g = agent({
|
|
2354
|
+
model: groq3("openai/gpt-oss-20b"),
|
|
1319
2355
|
tools: tools2,
|
|
1320
|
-
name: "text2sql"
|
|
1321
|
-
prompt: (state) => {
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
${fewShotExamples}
|
|
1330
|
-
|
|
1331
|
-
${hasMemory ? memory_prompt_default : ""}
|
|
1332
|
-
`;
|
|
1333
|
-
}
|
|
2356
|
+
name: "text2sql"
|
|
2357
|
+
// prompt: (state) => {
|
|
2358
|
+
// return `
|
|
2359
|
+
// ${state?.teachings || ''}
|
|
2360
|
+
// ${state?.introspection || ''}
|
|
2361
|
+
// ${chainOfThoughtPrompt}
|
|
2362
|
+
// ${fewShotExamples}
|
|
2363
|
+
// `;
|
|
2364
|
+
// },
|
|
1334
2365
|
});
|
|
1335
2366
|
|
|
1336
2367
|
// packages/text2sql/src/lib/checkpoint.ts
|
|
@@ -1536,845 +2567,186 @@ var JsonCache = class extends FileCache {
|
|
|
1536
2567
|
}
|
|
1537
2568
|
};
|
|
1538
2569
|
|
|
1539
|
-
// packages/text2sql/src/lib/history/history.ts
|
|
1540
|
-
var History = class {
|
|
1541
|
-
};
|
|
1542
|
-
|
|
1543
|
-
// packages/text2sql/src/lib/history/sqlite.history.ts
|
|
1544
|
-
import { DatabaseSync } from "node:sqlite";
|
|
1545
|
-
|
|
1546
|
-
// packages/text2sql/src/lib/history/history.sqlite.sql
|
|
1547
|
-
var history_sqlite_default = 'CREATE TABLE IF NOT EXISTS "chats" (\n "id" VARCHAR PRIMARY KEY,\n "title" VARCHAR,\n "userId" VARCHAR\n);\n\nCREATE TABLE IF NOT EXISTS "messages" (\n "id" VARCHAR PRIMARY KEY,\n "chatId" VARCHAR NOT NULL REFERENCES "chats" ("id") ON DELETE CASCADE,\n "createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n "role" VARCHAR NOT NULL,\n "content" TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS "messages_chat_id_idx" ON "messages" ("chatId");\n\nCREATE INDEX IF NOT EXISTS "messages_chat_id_created_at_idx" ON "messages" ("chatId", "createdAt");\n';
|
|
1548
|
-
|
|
1549
|
-
// packages/text2sql/src/lib/history/sqlite.history.ts
|
|
1550
|
-
var SqliteHistory = class extends History {
|
|
1551
|
-
#db;
|
|
1552
|
-
constructor(path2) {
|
|
1553
|
-
super();
|
|
1554
|
-
this.#db = new DatabaseSync(path2);
|
|
1555
|
-
this.#db.exec(history_sqlite_default);
|
|
1556
|
-
}
|
|
1557
|
-
async listChats(userId) {
|
|
1558
|
-
return this.#db.prepare(`SELECT * FROM chats WHERE "userId" = ?`).all(userId);
|
|
1559
|
-
}
|
|
1560
|
-
async getChat(chatId) {
|
|
1561
|
-
const rows = this.#db.prepare(
|
|
1562
|
-
`SELECT
|
|
1563
|
-
c.id as chatId, c."userId", c.title,
|
|
1564
|
-
m.id as messageId, m.role, m."createdAt", m.content
|
|
1565
|
-
FROM chats c
|
|
1566
|
-
LEFT JOIN messages m ON m."chatId" = c.id
|
|
1567
|
-
WHERE c.id = ?
|
|
1568
|
-
ORDER BY m."createdAt" ASC`
|
|
1569
|
-
).all(chatId);
|
|
1570
|
-
if (!rows.length) return null;
|
|
1571
|
-
const firstRow = rows[0];
|
|
1572
|
-
const chat = {
|
|
1573
|
-
id: firstRow.chatId,
|
|
1574
|
-
userId: firstRow.userId,
|
|
1575
|
-
title: firstRow.title,
|
|
1576
|
-
messages: []
|
|
1577
|
-
};
|
|
1578
|
-
for (const row of rows) {
|
|
1579
|
-
if (row.messageId) {
|
|
1580
|
-
chat.messages.push({
|
|
1581
|
-
id: row.messageId,
|
|
1582
|
-
chatId: firstRow.chatId,
|
|
1583
|
-
role: row.role,
|
|
1584
|
-
createdAt: row.createdAt,
|
|
1585
|
-
content: JSON.parse(row.content)
|
|
1586
|
-
});
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
return chat;
|
|
1590
|
-
}
|
|
1591
|
-
async createChat(chat) {
|
|
1592
|
-
this.#db.prepare(`INSERT INTO chats (id, "userId", title) VALUES (?, ?, ?)`).run(chat.id, chat.userId, chat.title || null);
|
|
1593
|
-
return chat;
|
|
1594
|
-
}
|
|
1595
|
-
async upsertChat(chat) {
|
|
1596
|
-
this.#db.prepare(
|
|
1597
|
-
`INSERT INTO chats (id, "userId", title) VALUES (?, ?, ?)
|
|
1598
|
-
ON CONFLICT(id) DO UPDATE SET title = excluded.title, "userId" = excluded."userId"`
|
|
1599
|
-
).run(chat.id, chat.userId, chat.title || null);
|
|
1600
|
-
return this.getChat(chat.id);
|
|
1601
|
-
}
|
|
1602
|
-
async deleteChat(chatId) {
|
|
1603
|
-
this.#db.prepare(`DELETE FROM chats WHERE id = ?`).run(chatId);
|
|
1604
|
-
}
|
|
1605
|
-
async updateChat(chatId, updates) {
|
|
1606
|
-
if (updates.title !== void 0) {
|
|
1607
|
-
this.#db.prepare(`UPDATE chats SET title = ? WHERE id = ?`).run(updates.title, chatId);
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
async addMessage(message) {
|
|
1611
|
-
const createdAt = message.createdAt ? message.createdAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
1612
|
-
this.#db.prepare(
|
|
1613
|
-
`INSERT INTO messages (id, "chatId", role, "createdAt", content) VALUES (?, ?, ?, ?, ?)
|
|
1614
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
1615
|
-
"chatId" = excluded."chatId",
|
|
1616
|
-
role = excluded.role,
|
|
1617
|
-
content = excluded.content`
|
|
1618
|
-
).run(
|
|
1619
|
-
message.id,
|
|
1620
|
-
message.chatId,
|
|
1621
|
-
message.role,
|
|
1622
|
-
createdAt,
|
|
1623
|
-
JSON.stringify(message.content)
|
|
1624
|
-
);
|
|
1625
|
-
}
|
|
1626
|
-
async upsertMessage(message) {
|
|
1627
|
-
const createdAt = message.createdAt ? message.createdAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
1628
|
-
this.#db.prepare(
|
|
1629
|
-
`INSERT INTO messages (id, "chatId", role, "createdAt", content) VALUES (?, ?, ?, ?, ?)
|
|
1630
|
-
ON CONFLICT(id) DO UPDATE SET "chatId" = excluded."chatId", role = excluded.role, "createdAt" = excluded."createdAt", content = excluded.content`
|
|
1631
|
-
).run(
|
|
1632
|
-
message.id,
|
|
1633
|
-
message.chatId,
|
|
1634
|
-
message.role,
|
|
1635
|
-
createdAt,
|
|
1636
|
-
JSON.stringify(message.content)
|
|
1637
|
-
);
|
|
1638
|
-
return {
|
|
1639
|
-
...message,
|
|
1640
|
-
createdAt
|
|
1641
|
-
};
|
|
1642
|
-
}
|
|
1643
|
-
async deleteMessage(messageId) {
|
|
1644
|
-
this.#db.prepare(`DELETE FROM messages WHERE id = ?`).run(messageId);
|
|
1645
|
-
}
|
|
1646
|
-
};
|
|
1647
|
-
|
|
1648
|
-
// packages/text2sql/src/lib/history/memory.history.ts
|
|
1649
|
-
var InMemoryHistory = class extends SqliteHistory {
|
|
1650
|
-
constructor() {
|
|
1651
|
-
super(":memory:");
|
|
1652
|
-
}
|
|
1653
|
-
};
|
|
1654
|
-
|
|
1655
|
-
// packages/text2sql/src/lib/memory/sqlite.store.ts
|
|
1656
|
-
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
1657
|
-
import { v7 } from "uuid";
|
|
1658
|
-
|
|
1659
|
-
// packages/text2sql/src/lib/memory/store.sqlite.sql
|
|
1660
|
-
var store_sqlite_default = 'CREATE TABLE IF NOT EXISTS "teachables" (\n "id" VARCHAR PRIMARY KEY,\n "userId" VARCHAR,\n "type" VARCHAR NOT NULL,\n "data" TEXT NOT NULL,\n "createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n "updatedAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE INDEX IF NOT EXISTS "teachables_user_id_idx" ON "teachables" ("userId");\n\nCREATE INDEX IF NOT EXISTS "teachables_type_idx" ON "teachables" ("type");\n\nCREATE INDEX IF NOT EXISTS "teachables_user_type_idx" ON "teachables" ("userId", "type");\n';
|
|
1661
|
-
|
|
1662
|
-
// packages/text2sql/src/lib/memory/store.ts
|
|
1663
|
-
var TeachablesStore = class {
|
|
1664
|
-
};
|
|
1665
|
-
|
|
1666
|
-
// packages/text2sql/src/lib/memory/sqlite.store.ts
|
|
1667
|
-
function rowToStoredTeachable(row) {
|
|
1668
|
-
return {
|
|
1669
|
-
id: row.id,
|
|
1670
|
-
userId: row.userId,
|
|
1671
|
-
type: row.type,
|
|
1672
|
-
data: JSON.parse(row.data),
|
|
1673
|
-
createdAt: row.createdAt,
|
|
1674
|
-
updatedAt: row.updatedAt
|
|
1675
|
-
};
|
|
1676
|
-
}
|
|
1677
|
-
var SqliteTeachablesStore = class extends TeachablesStore {
|
|
1678
|
-
#db;
|
|
1679
|
-
constructor(path2) {
|
|
1680
|
-
super();
|
|
1681
|
-
this.#db = new DatabaseSync2(path2);
|
|
1682
|
-
this.#db.exec(store_sqlite_default);
|
|
1683
|
-
}
|
|
1684
|
-
async remember(userId, data) {
|
|
1685
|
-
const id = v7();
|
|
1686
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1687
|
-
this.#db.prepare(
|
|
1688
|
-
"INSERT INTO teachables (id, userId, type, data, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)"
|
|
1689
|
-
).run(id, userId, data.type, JSON.stringify(data), now, now);
|
|
1690
|
-
return await this.get(id);
|
|
1691
|
-
}
|
|
1692
|
-
async recall(userId, type) {
|
|
1693
|
-
let rows;
|
|
1694
|
-
if (type === void 0) {
|
|
1695
|
-
rows = this.#db.prepare("SELECT * FROM teachables WHERE userId = ? ORDER BY createdAt").all(userId);
|
|
1696
|
-
} else {
|
|
1697
|
-
rows = this.#db.prepare(
|
|
1698
|
-
"SELECT * FROM teachables WHERE userId = ? AND type = ? ORDER BY createdAt"
|
|
1699
|
-
).all(userId, type);
|
|
1700
|
-
}
|
|
1701
|
-
return rows.map(rowToStoredTeachable);
|
|
1702
|
-
}
|
|
1703
|
-
async get(id) {
|
|
1704
|
-
const row = this.#db.prepare("SELECT * FROM teachables WHERE id = ?").get(id);
|
|
1705
|
-
if (!row) return null;
|
|
1706
|
-
return rowToStoredTeachable(row);
|
|
1707
|
-
}
|
|
1708
|
-
async update(id, data) {
|
|
1709
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1710
|
-
this.#db.prepare(
|
|
1711
|
-
"UPDATE teachables SET data = ?, type = ?, updatedAt = ? WHERE id = ?"
|
|
1712
|
-
).run(JSON.stringify(data), data.type, now, id);
|
|
1713
|
-
return await this.get(id);
|
|
1714
|
-
}
|
|
1715
|
-
async forget(id) {
|
|
1716
|
-
this.#db.prepare("DELETE FROM teachables WHERE id = ?").run(id);
|
|
1717
|
-
}
|
|
1718
|
-
async forgetAll(userId) {
|
|
1719
|
-
this.#db.prepare("DELETE FROM teachables WHERE userId = ?").run(userId);
|
|
1720
|
-
}
|
|
1721
|
-
async toTeachables(userId) {
|
|
1722
|
-
const stored = await this.recall(userId);
|
|
1723
|
-
return toTeachables(stored.map((s) => s.data));
|
|
1724
|
-
}
|
|
1725
|
-
};
|
|
1726
|
-
|
|
1727
|
-
// packages/text2sql/src/lib/memory/memory.store.ts
|
|
1728
|
-
var InMemoryTeachablesStore = class extends SqliteTeachablesStore {
|
|
1729
|
-
constructor() {
|
|
1730
|
-
super(":memory:");
|
|
1731
|
-
}
|
|
1732
|
-
};
|
|
1733
|
-
|
|
1734
2570
|
// packages/text2sql/src/lib/sql.ts
|
|
1735
2571
|
import {
|
|
1736
2572
|
APICallError as APICallError2,
|
|
1737
2573
|
InvalidToolInputError,
|
|
1738
2574
|
NoSuchToolError,
|
|
1739
2575
|
ToolCallRepairError,
|
|
1740
|
-
generateId
|
|
2576
|
+
generateId as generateId3
|
|
1741
2577
|
} from "ai";
|
|
1742
|
-
import
|
|
1743
|
-
generate as generate5,
|
|
1744
|
-
stream,
|
|
1745
|
-
user as user4
|
|
1746
|
-
} from "@deepagents/agent";
|
|
1747
|
-
|
|
1748
|
-
// packages/text2sql/src/lib/agents/chat1.agent.ts
|
|
1749
|
-
import { groq as groq6 } from "@ai-sdk/groq";
|
|
1750
|
-
import { tool as tool3 } from "ai";
|
|
1751
|
-
import z6 from "zod";
|
|
1752
|
-
import { agent as agent6, toState as toState3 } from "@deepagents/agent";
|
|
1753
|
-
import { scratchpad_tool as scratchpad_tool3 } from "@deepagents/toolbox";
|
|
1754
|
-
var tools3 = {
|
|
1755
|
-
query_database: tool3({
|
|
1756
|
-
description: `Query the database to answer a question. Provide your question in natural language and this tool will:
|
|
1757
|
-
1. Generate the appropriate SQL query
|
|
1758
|
-
2. Validate the SQL syntax
|
|
1759
|
-
3. Execute the query
|
|
1760
|
-
4. Return the results
|
|
2578
|
+
import "@deepagents/agent";
|
|
1761
2579
|
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
error: sqlResult.errors?.join("; ") || "Failed to generate SQL"
|
|
1784
|
-
};
|
|
1785
|
-
}
|
|
1786
|
-
const data = await state.adapter.execute(sqlResult.sql);
|
|
1787
|
-
return {
|
|
1788
|
-
success: true,
|
|
1789
|
-
sql: sqlResult.sql,
|
|
1790
|
-
data
|
|
1791
|
-
};
|
|
1792
|
-
} catch (error) {
|
|
1793
|
-
return {
|
|
1794
|
-
success: false,
|
|
1795
|
-
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
1796
|
-
};
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
}),
|
|
1800
|
-
scratchpad: scratchpad_tool3
|
|
1801
|
-
};
|
|
1802
|
-
var chat1Agent = agent6({
|
|
1803
|
-
name: "chat1-combined",
|
|
1804
|
-
model: groq6("openai/gpt-oss-20b"),
|
|
1805
|
-
tools: tools3,
|
|
1806
|
-
prompt: (state) => {
|
|
1807
|
-
return `
|
|
1808
|
-
${state?.teachings || ""}
|
|
1809
|
-
${state?.introspection || ""}
|
|
1810
|
-
`;
|
|
1811
|
-
}
|
|
2580
|
+
// packages/text2sql/src/lib/agents/sql.agent.ts
|
|
2581
|
+
import { groq as groq4 } from "@ai-sdk/groq";
|
|
2582
|
+
import {
|
|
2583
|
+
APICallError,
|
|
2584
|
+
JSONParseError,
|
|
2585
|
+
NoContentGeneratedError,
|
|
2586
|
+
NoObjectGeneratedError,
|
|
2587
|
+
NoOutputGeneratedError,
|
|
2588
|
+
TypeValidationError,
|
|
2589
|
+
defaultSettingsMiddleware,
|
|
2590
|
+
wrapLanguageModel
|
|
2591
|
+
} from "ai";
|
|
2592
|
+
import { Console } from "node:console";
|
|
2593
|
+
import { createWriteStream } from "node:fs";
|
|
2594
|
+
import pRetry from "p-retry";
|
|
2595
|
+
import z5 from "zod";
|
|
2596
|
+
import "@deepagents/agent";
|
|
2597
|
+
var logger = new Console({
|
|
2598
|
+
stdout: createWriteStream("./sql-agent.log", { flags: "a" }),
|
|
2599
|
+
stderr: createWriteStream("./sql-agent-error.log", { flags: "a" }),
|
|
2600
|
+
inspectOptions: { depth: null }
|
|
1812
2601
|
});
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
- Explain the approach to the user
|
|
1830
|
-
- Decide if the SQL looks correct
|
|
1831
|
-
- Refine your question and regenerate if needed`,
|
|
1832
|
-
inputSchema: z7.object({
|
|
1833
|
-
question: z7.string().min(1).describe(
|
|
1834
|
-
"The question to translate into SQL. Be specific about what data you need."
|
|
1835
|
-
),
|
|
1836
|
-
reasoning: z7.string().optional().describe("Your reasoning for why this data is needed.")
|
|
1837
|
-
}),
|
|
1838
|
-
execute: async ({ question }, options) => {
|
|
1839
|
-
const state = toState4(options);
|
|
1840
|
-
try {
|
|
1841
|
-
const sqlResult = await toSql({
|
|
1842
|
-
input: question,
|
|
1843
|
-
adapter: state.adapter,
|
|
1844
|
-
introspection: state.introspection,
|
|
1845
|
-
instructions: state.instructions
|
|
1846
|
-
});
|
|
1847
|
-
if (!sqlResult.sql) {
|
|
1848
|
-
return {
|
|
1849
|
-
success: false,
|
|
1850
|
-
error: sqlResult.errors?.join("; ") || "Failed to generate SQL",
|
|
1851
|
-
validationErrors: sqlResult.errors
|
|
1852
|
-
};
|
|
1853
|
-
}
|
|
1854
|
-
return {
|
|
1855
|
-
success: true,
|
|
1856
|
-
sql: sqlResult.sql,
|
|
1857
|
-
validationErrors: sqlResult.errors
|
|
1858
|
-
};
|
|
1859
|
-
} catch (error) {
|
|
1860
|
-
return {
|
|
1861
|
-
success: false,
|
|
1862
|
-
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
1863
|
-
};
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
}),
|
|
1867
|
-
execute_sql: tool4({
|
|
1868
|
-
description: `Execute a SQL query and return the results. Use this AFTER generate_sql to run the query.
|
|
1869
|
-
|
|
1870
|
-
Only SELECT and WITH (CTE) queries are allowed - no data modification.`,
|
|
1871
|
-
inputSchema: z7.object({
|
|
1872
|
-
sql: z7.string().min(1).refine(
|
|
1873
|
-
(sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
|
|
1874
|
-
{
|
|
1875
|
-
message: "Only read-only SELECT or WITH queries are allowed."
|
|
1876
|
-
}
|
|
1877
|
-
).describe("The SQL query to execute (must be SELECT or WITH)."),
|
|
1878
|
-
reasoning: z7.string().optional().describe("Brief explanation of what this query retrieves.")
|
|
1879
|
-
}),
|
|
1880
|
-
execute: async ({ sql }, options) => {
|
|
1881
|
-
const state = toState4(options);
|
|
1882
|
-
try {
|
|
1883
|
-
const data = await state.adapter.execute(sql);
|
|
1884
|
-
return {
|
|
1885
|
-
success: true,
|
|
1886
|
-
data,
|
|
1887
|
-
rowCount: Array.isArray(data) ? data.length : void 0
|
|
1888
|
-
};
|
|
1889
|
-
} catch (error) {
|
|
1890
|
-
return {
|
|
1891
|
-
success: false,
|
|
1892
|
-
error: error instanceof Error ? error.message : "Query execution failed"
|
|
1893
|
-
};
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
}),
|
|
1897
|
-
scratchpad: scratchpad_tool4
|
|
2602
|
+
var RETRY_TEMPERATURES = [0, 0.2, 0.3];
|
|
2603
|
+
function extractSql(output) {
|
|
2604
|
+
const match = output.match(/```sql\n?([\s\S]*?)```/);
|
|
2605
|
+
return match ? match[1].trim() : output.trim();
|
|
2606
|
+
}
|
|
2607
|
+
var marker = Symbol("SQLValidationError");
|
|
2608
|
+
var SQLValidationError = class _SQLValidationError extends Error {
|
|
2609
|
+
[marker];
|
|
2610
|
+
constructor(message2) {
|
|
2611
|
+
super(message2);
|
|
2612
|
+
this.name = "SQLValidationError";
|
|
2613
|
+
this[marker] = true;
|
|
2614
|
+
}
|
|
2615
|
+
static isInstance(error) {
|
|
2616
|
+
return error instanceof _SQLValidationError && error[marker] === true;
|
|
2617
|
+
}
|
|
1898
2618
|
};
|
|
1899
|
-
var
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
prompt: (state) => {
|
|
1904
|
-
return `
|
|
1905
|
-
${state?.teachings || ""}
|
|
1906
|
-
${state?.introspection || ""}
|
|
1907
|
-
|
|
1908
|
-
When answering questions that require database queries:
|
|
1909
|
-
1. First use generate_sql to create the SQL query
|
|
1910
|
-
2. Review the generated SQL to ensure it matches the user's intent
|
|
1911
|
-
3. Use execute_sql to run the query
|
|
1912
|
-
4. Present the results to the user
|
|
1913
|
-
|
|
1914
|
-
If the generated SQL doesn't look right, you can refine your question and regenerate.
|
|
1915
|
-
`;
|
|
2619
|
+
var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
|
|
2620
|
+
constructor(message2) {
|
|
2621
|
+
super(message2);
|
|
2622
|
+
this.name = "UnanswerableSQLError";
|
|
1916
2623
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
// packages/text2sql/src/lib/agents/chat3.agent.ts
|
|
1920
|
-
import { groq as groq8 } from "@ai-sdk/groq";
|
|
1921
|
-
import { defaultSettingsMiddleware as defaultSettingsMiddleware2, tool as tool5, wrapLanguageModel as wrapLanguageModel2 } from "ai";
|
|
1922
|
-
import z8 from "zod";
|
|
1923
|
-
import { agent as agent8, generate as generate3, toState as toState5, user as user2 } from "@deepagents/agent";
|
|
1924
|
-
import { scratchpad_tool as scratchpad_tool5 } from "@deepagents/toolbox";
|
|
1925
|
-
var collaborativeSqlOutputSchema = z8.discriminatedUnion("status", [
|
|
1926
|
-
z8.object({
|
|
1927
|
-
status: z8.literal("success"),
|
|
1928
|
-
sql: z8.string().describe("The generated SQL query"),
|
|
1929
|
-
confidence: z8.enum(["high", "medium", "low"]).describe("Confidence level in this SQL being correct"),
|
|
1930
|
-
assumptions: z8.array(z8.string()).optional().describe("Assumptions made during SQL generation"),
|
|
1931
|
-
reasoning: z8.string().optional().describe("Brief explanation of the query approach")
|
|
1932
|
-
}),
|
|
1933
|
-
z8.object({
|
|
1934
|
-
status: z8.literal("clarification_needed"),
|
|
1935
|
-
question: z8.string().describe("Question to clarify the request"),
|
|
1936
|
-
context: z8.string().optional().describe("Why this clarification is needed"),
|
|
1937
|
-
options: z8.array(z8.string()).optional().describe("Possible options if applicable")
|
|
1938
|
-
}),
|
|
1939
|
-
z8.object({
|
|
1940
|
-
status: z8.literal("unanswerable"),
|
|
1941
|
-
reason: z8.string().describe("Why this question cannot be answered"),
|
|
1942
|
-
suggestions: z8.array(z8.string()).optional().describe("Alternative questions that could be answered")
|
|
1943
|
-
})
|
|
1944
|
-
]);
|
|
1945
|
-
var collaborativeSqlAgent = agent8({
|
|
1946
|
-
name: "collaborative-sql",
|
|
1947
|
-
model: groq8("openai/gpt-oss-20b"),
|
|
1948
|
-
output: collaborativeSqlOutputSchema,
|
|
1949
|
-
prompt: (state) => {
|
|
1950
|
-
return `
|
|
1951
|
-
${toInstructions(
|
|
1952
|
-
"instructions",
|
|
1953
|
-
persona({
|
|
1954
|
-
name: "SQLCollab",
|
|
1955
|
-
role: "You are an expert SQL query generator that collaborates with the user to ensure accuracy."
|
|
1956
|
-
}),
|
|
1957
|
-
...state?.instructions || []
|
|
1958
|
-
)}
|
|
1959
|
-
${state?.introspection || ""}
|
|
1960
|
-
|
|
1961
|
-
IMPORTANT: You have three response options:
|
|
1962
|
-
|
|
1963
|
-
1. SUCCESS - When you can confidently generate SQL:
|
|
1964
|
-
- Provide the SQL query
|
|
1965
|
-
- Rate your confidence (high/medium/low)
|
|
1966
|
-
- List any assumptions you made
|
|
1967
|
-
|
|
1968
|
-
2. CLARIFICATION_NEEDED - When the question is ambiguous:
|
|
1969
|
-
- Ask a specific clarifying question
|
|
1970
|
-
- Explain why clarification is needed
|
|
1971
|
-
- Provide options if applicable
|
|
1972
|
-
|
|
1973
|
-
3. UNANSWERABLE - When the question cannot be answered with available data:
|
|
1974
|
-
- Explain why
|
|
1975
|
-
- Suggest alternative questions that could be answered
|
|
1976
|
-
|
|
1977
|
-
Prefer asking for clarification over making low-confidence guesses.
|
|
1978
|
-
`;
|
|
2624
|
+
static isInstance(error) {
|
|
2625
|
+
return error instanceof _UnanswerableSQLError;
|
|
1979
2626
|
}
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
)
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
if (context2) {
|
|
2004
|
-
fullQuestion = `${question}
|
|
2005
|
-
|
|
2006
|
-
Additional context: ${context2}`;
|
|
2007
|
-
}
|
|
2008
|
-
if (previousClarification) {
|
|
2009
|
-
fullQuestion = `${fullQuestion}
|
|
2010
|
-
|
|
2011
|
-
Clarification provided: ${previousClarification}`;
|
|
2012
|
-
}
|
|
2013
|
-
const agentInstance = collaborativeSqlAgent.clone({
|
|
2014
|
-
model: wrapLanguageModel2({
|
|
2015
|
-
model: collaborativeSqlAgent.model,
|
|
2016
|
-
middleware: defaultSettingsMiddleware2({
|
|
2017
|
-
settings: { temperature: 0.1 }
|
|
2018
|
-
})
|
|
2019
|
-
})
|
|
2020
|
-
});
|
|
2021
|
-
const { experimental_output: output } = await generate3(
|
|
2022
|
-
agentInstance,
|
|
2023
|
-
[user2(fullQuestion)],
|
|
2024
|
-
state
|
|
2627
|
+
};
|
|
2628
|
+
async function toSql(options) {
|
|
2629
|
+
const { maxRetries = 3 } = options;
|
|
2630
|
+
return withRetry(
|
|
2631
|
+
async (attemptNumber, errors, attempts) => {
|
|
2632
|
+
const context = new ContextEngine({
|
|
2633
|
+
store: new InMemoryContextStore(),
|
|
2634
|
+
chatId: `sql-gen-${crypto.randomUUID()}`
|
|
2635
|
+
});
|
|
2636
|
+
context.set(
|
|
2637
|
+
persona({
|
|
2638
|
+
name: "Freya",
|
|
2639
|
+
role: "You are an expert SQL query generator. You translate natural language questions into precise, efficient SQL queries based on the provided database schema."
|
|
2640
|
+
}),
|
|
2641
|
+
...options.instructions,
|
|
2642
|
+
...options.schemaFragments
|
|
2643
|
+
);
|
|
2644
|
+
if (errors.length) {
|
|
2645
|
+
context.set(
|
|
2646
|
+
user(options.input),
|
|
2647
|
+
user(
|
|
2648
|
+
`<validation_error>Your previous SQL query had the following error: ${errors.at(-1)?.message}. Please fix the query.</validation_error>`
|
|
2649
|
+
)
|
|
2025
2650
|
);
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
};
|
|
2057
|
-
}
|
|
2058
|
-
return {
|
|
2059
|
-
success: false,
|
|
2060
|
-
error: "Unexpected response from SQL agent"
|
|
2061
|
-
};
|
|
2062
|
-
} catch (error) {
|
|
2063
|
-
return {
|
|
2064
|
-
success: false,
|
|
2065
|
-
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
2066
|
-
};
|
|
2651
|
+
} else {
|
|
2652
|
+
context.set(user(options.input));
|
|
2653
|
+
}
|
|
2654
|
+
const sqlOutput = structuredOutput({
|
|
2655
|
+
name: "text2sql",
|
|
2656
|
+
model: wrapLanguageModel({
|
|
2657
|
+
model: options.model ?? groq4("openai/gpt-oss-20b"),
|
|
2658
|
+
middleware: defaultSettingsMiddleware({
|
|
2659
|
+
settings: {
|
|
2660
|
+
temperature: RETRY_TEMPERATURES[attemptNumber - 1] ?? 0.3,
|
|
2661
|
+
topP: 1
|
|
2662
|
+
}
|
|
2663
|
+
})
|
|
2664
|
+
}),
|
|
2665
|
+
context,
|
|
2666
|
+
schema: z5.union([
|
|
2667
|
+
z5.object({
|
|
2668
|
+
sql: z5.string().describe("The SQL query that answers the question"),
|
|
2669
|
+
reasoning: z5.string().optional().describe("The reasoning steps taken to generate the SQL")
|
|
2670
|
+
}),
|
|
2671
|
+
z5.object({
|
|
2672
|
+
error: z5.string().describe(
|
|
2673
|
+
"Error message explaining why the question cannot be answered with the given schema"
|
|
2674
|
+
)
|
|
2675
|
+
})
|
|
2676
|
+
])
|
|
2677
|
+
});
|
|
2678
|
+
const output = await sqlOutput.generate();
|
|
2679
|
+
if ("error" in output) {
|
|
2680
|
+
throw new UnanswerableSQLError(output.error);
|
|
2067
2681
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
(e.g., after receiving SQL from consult_sql_agent or for follow-up queries).`,
|
|
2073
|
-
inputSchema: z8.object({
|
|
2074
|
-
sql: z8.string().min(1).refine(
|
|
2075
|
-
(sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
|
|
2076
|
-
{
|
|
2077
|
-
message: "Only read-only SELECT or WITH queries are allowed."
|
|
2078
|
-
}
|
|
2079
|
-
).describe("The SQL query to execute.")
|
|
2080
|
-
}),
|
|
2081
|
-
execute: async ({ sql }, options) => {
|
|
2082
|
-
const state = toState5(options);
|
|
2083
|
-
try {
|
|
2084
|
-
const validationError = await state.adapter.validate(sql);
|
|
2085
|
-
if (validationError) {
|
|
2086
|
-
return {
|
|
2087
|
-
success: false,
|
|
2088
|
-
error: `Validation failed: ${validationError}`
|
|
2089
|
-
};
|
|
2090
|
-
}
|
|
2091
|
-
const data = await state.adapter.execute(sql);
|
|
2092
|
-
return {
|
|
2093
|
-
success: true,
|
|
2094
|
-
data,
|
|
2095
|
-
rowCount: Array.isArray(data) ? data.length : void 0
|
|
2096
|
-
};
|
|
2097
|
-
} catch (error) {
|
|
2098
|
-
return {
|
|
2099
|
-
success: false,
|
|
2100
|
-
error: error instanceof Error ? error.message : "Execution failed"
|
|
2101
|
-
};
|
|
2682
|
+
const sql = extractSql(output.sql);
|
|
2683
|
+
const validationError = await options.adapter.validate(sql);
|
|
2684
|
+
if (validationError) {
|
|
2685
|
+
throw new SQLValidationError(validationError);
|
|
2102
2686
|
}
|
|
2687
|
+
return {
|
|
2688
|
+
attempts,
|
|
2689
|
+
sql,
|
|
2690
|
+
errors: errors.length ? errors.map(formatErrorMessage) : void 0
|
|
2691
|
+
};
|
|
2692
|
+
},
|
|
2693
|
+
{ retries: maxRetries - 1 }
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
function formatErrorMessage(error) {
|
|
2697
|
+
if (APICallError.isInstance(error)) {
|
|
2698
|
+
if (error.message.startsWith("Failed to validate JSON")) {
|
|
2699
|
+
return `Schema validation failed: ${error.message}`;
|
|
2103
2700
|
}
|
|
2104
|
-
|
|
2105
|
-
scratchpad: scratchpad_tool5
|
|
2106
|
-
};
|
|
2107
|
-
var chat3Agent = agent8({
|
|
2108
|
-
name: "chat3-collaborative",
|
|
2109
|
-
model: groq8("openai/gpt-oss-20b"),
|
|
2110
|
-
tools: tools5,
|
|
2111
|
-
prompt: (state) => {
|
|
2112
|
-
return `
|
|
2113
|
-
${state?.teachings || ""}
|
|
2114
|
-
${state?.introspection || ""}
|
|
2115
|
-
|
|
2116
|
-
When answering questions that require database queries, use the consult_sql_agent tool.
|
|
2117
|
-
|
|
2118
|
-
The SQL agent may respond in three ways:
|
|
2119
|
-
1. SUCCESS with SQL, confidence, and assumptions - review the confidence and assumptions
|
|
2120
|
-
2. CLARIFICATION_NEEDED with a question - either answer from context or ask the user
|
|
2121
|
-
3. UNANSWERABLE with reason and suggestions - relay this to the user helpfully
|
|
2122
|
-
|
|
2123
|
-
For medium/low confidence results, consider mentioning the assumptions to the user.
|
|
2124
|
-
For clarification requests, try to answer from conversation context first before asking the user.
|
|
2125
|
-
`;
|
|
2701
|
+
return error.message;
|
|
2126
2702
|
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
// packages/text2sql/src/lib/agents/chat4.agent.ts
|
|
2130
|
-
import { groq as groq9 } from "@ai-sdk/groq";
|
|
2131
|
-
import { defaultSettingsMiddleware as defaultSettingsMiddleware3, tool as tool6, wrapLanguageModel as wrapLanguageModel3 } from "ai";
|
|
2132
|
-
import z9 from "zod";
|
|
2133
|
-
import { agent as agent9, generate as generate4, toState as toState6, user as user3 } from "@deepagents/agent";
|
|
2134
|
-
import { scratchpad_tool as scratchpad_tool6 } from "@deepagents/toolbox";
|
|
2135
|
-
var questionDecompositionSchema = z9.object({
|
|
2136
|
-
originalQuestion: z9.string().describe("The original question being decomposed"),
|
|
2137
|
-
breakdown: z9.array(z9.string()).min(1).describe(
|
|
2138
|
-
"Semantic breakdown of the question into its component parts. Each part describes an aspect of what is being asked, NOT how to implement it."
|
|
2139
|
-
),
|
|
2140
|
-
entities: z9.array(z9.string()).optional().describe(
|
|
2141
|
-
"Key entities/concepts mentioned (e.g., customers, orders, products)"
|
|
2142
|
-
),
|
|
2143
|
-
filters: z9.array(z9.string()).optional().describe(
|
|
2144
|
-
'Filtering criteria mentioned (e.g., "last quarter", "above $100")'
|
|
2145
|
-
),
|
|
2146
|
-
aggregation: z9.string().optional().describe(
|
|
2147
|
-
'Type of aggregation if any (e.g., "count", "sum", "average", "top N")'
|
|
2148
|
-
),
|
|
2149
|
-
ambiguities: z9.array(z9.string()).optional().describe("Any ambiguous parts that might need clarification")
|
|
2150
|
-
});
|
|
2151
|
-
var decompositionSqlOutputSchema = z9.union([
|
|
2152
|
-
z9.object({
|
|
2153
|
-
sql: z9.string().describe("The SQL query that answers the decomposed question"),
|
|
2154
|
-
reasoning: z9.string().optional().describe("How each breakdown component was addressed")
|
|
2155
|
-
}),
|
|
2156
|
-
z9.object({
|
|
2157
|
-
error: z9.string().describe("Error message if the question cannot be answered")
|
|
2158
|
-
})
|
|
2159
|
-
]);
|
|
2160
|
-
var decompositionSqlAgent = agent9({
|
|
2161
|
-
name: "decomposition-sql",
|
|
2162
|
-
model: groq9("openai/gpt-oss-20b"),
|
|
2163
|
-
output: decompositionSqlOutputSchema,
|
|
2164
|
-
prompt: (state) => {
|
|
2165
|
-
return `
|
|
2166
|
-
${toInstructions(
|
|
2167
|
-
"instructions",
|
|
2168
|
-
persona({
|
|
2169
|
-
name: "SQLDecomp",
|
|
2170
|
-
role: "You are an expert SQL query generator. You receive questions broken down into semantic components and generate precise SQL."
|
|
2171
|
-
}),
|
|
2172
|
-
...state?.instructions || []
|
|
2173
|
-
)}
|
|
2174
|
-
${state?.introspection || ""}
|
|
2175
|
-
|
|
2176
|
-
You will receive questions in a decomposed format with:
|
|
2177
|
-
- breakdown: Semantic parts of the question
|
|
2178
|
-
- entities: Key concepts mentioned
|
|
2179
|
-
- filters: Filtering criteria
|
|
2180
|
-
- aggregation: Type of aggregation needed
|
|
2181
|
-
- ambiguities: Potentially unclear parts
|
|
2182
|
-
|
|
2183
|
-
Address each component of the breakdown in your SQL.
|
|
2184
|
-
If there are ambiguities, make reasonable assumptions and note them in your reasoning.
|
|
2185
|
-
`;
|
|
2703
|
+
if (SQLValidationError.isInstance(error)) {
|
|
2704
|
+
return `SQL Validation Error: ${error.message}`;
|
|
2186
2705
|
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
'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"]'
|
|
2202
|
-
),
|
|
2203
|
-
entities: z9.array(z9.string()).optional().describe(
|
|
2204
|
-
'Key entities mentioned (e.g., ["customers", "orders", "products"])'
|
|
2205
|
-
),
|
|
2206
|
-
filters: z9.array(z9.string()).optional().describe('Filter criteria (e.g., ["last month", "status = active"])'),
|
|
2207
|
-
aggregation: z9.string().optional().describe(
|
|
2208
|
-
'Aggregation type if any (e.g., "sum revenue", "count orders", "top 10")'
|
|
2209
|
-
),
|
|
2210
|
-
ambiguities: z9.array(z9.string()).optional().describe("Note any ambiguous parts you identified")
|
|
2211
|
-
}),
|
|
2212
|
-
execute: async ({ question, breakdown, entities, filters, aggregation, ambiguities }, options) => {
|
|
2213
|
-
const state = toState6(options);
|
|
2214
|
-
const decomposition = {
|
|
2215
|
-
originalQuestion: question,
|
|
2216
|
-
breakdown,
|
|
2217
|
-
entities,
|
|
2218
|
-
filters,
|
|
2219
|
-
aggregation,
|
|
2220
|
-
ambiguities
|
|
2221
|
-
};
|
|
2222
|
-
const decomposedPrompt = formatDecomposition(decomposition);
|
|
2223
|
-
try {
|
|
2224
|
-
let lastError;
|
|
2225
|
-
for (let attempt = 0; attempt < RETRY_TEMPERATURES2.length; attempt++) {
|
|
2226
|
-
const temperature = RETRY_TEMPERATURES2[attempt];
|
|
2227
|
-
const agentInstance = decompositionSqlAgent.clone({
|
|
2228
|
-
model: wrapLanguageModel3({
|
|
2229
|
-
model: decompositionSqlAgent.model,
|
|
2230
|
-
middleware: defaultSettingsMiddleware3({
|
|
2231
|
-
settings: { temperature }
|
|
2232
|
-
})
|
|
2233
|
-
})
|
|
2234
|
-
});
|
|
2235
|
-
const prompt = lastError ? `${decomposedPrompt}
|
|
2236
|
-
|
|
2237
|
-
Previous attempt failed with: ${lastError}. Please fix the query.` : decomposedPrompt;
|
|
2238
|
-
const { experimental_output: output } = await generate4(
|
|
2239
|
-
agentInstance,
|
|
2240
|
-
[user3(prompt)],
|
|
2241
|
-
state
|
|
2242
|
-
);
|
|
2243
|
-
if ("error" in output) {
|
|
2244
|
-
return {
|
|
2245
|
-
success: false,
|
|
2246
|
-
question,
|
|
2247
|
-
decomposition,
|
|
2248
|
-
error: output.error,
|
|
2249
|
-
attempts: attempt + 1
|
|
2250
|
-
};
|
|
2251
|
-
}
|
|
2252
|
-
const validationError = await state.adapter.validate(output.sql);
|
|
2253
|
-
if (validationError) {
|
|
2254
|
-
lastError = validationError;
|
|
2255
|
-
continue;
|
|
2256
|
-
}
|
|
2257
|
-
const data = await state.adapter.execute(output.sql);
|
|
2258
|
-
return {
|
|
2259
|
-
success: true,
|
|
2260
|
-
question,
|
|
2261
|
-
decomposition,
|
|
2262
|
-
sql: output.sql,
|
|
2263
|
-
data,
|
|
2264
|
-
reasoning: output.reasoning,
|
|
2265
|
-
attempts: attempt + 1
|
|
2266
|
-
};
|
|
2267
|
-
}
|
|
2268
|
-
return {
|
|
2269
|
-
success: false,
|
|
2270
|
-
question,
|
|
2271
|
-
decomposition,
|
|
2272
|
-
error: `Failed after ${RETRY_TEMPERATURES2.length} attempts. Last error: ${lastError}`,
|
|
2273
|
-
attempts: RETRY_TEMPERATURES2.length
|
|
2274
|
-
};
|
|
2275
|
-
} catch (error) {
|
|
2276
|
-
return {
|
|
2277
|
-
success: false,
|
|
2278
|
-
question,
|
|
2279
|
-
decomposition,
|
|
2280
|
-
error: error instanceof Error ? error.message : "Unknown error occurred"
|
|
2281
|
-
};
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
}),
|
|
2285
|
-
execute_sql: tool6({
|
|
2286
|
-
description: `Execute a SQL query directly. Use for follow-up queries or when you already have SQL.`,
|
|
2287
|
-
inputSchema: z9.object({
|
|
2288
|
-
sql: z9.string().min(1).refine(
|
|
2289
|
-
(sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
|
|
2290
|
-
{
|
|
2291
|
-
message: "Only read-only SELECT or WITH queries are allowed."
|
|
2706
|
+
return error.message;
|
|
2707
|
+
}
|
|
2708
|
+
async function withRetry(computation, options = { retries: 3 }) {
|
|
2709
|
+
const errors = [];
|
|
2710
|
+
let attempts = 0;
|
|
2711
|
+
return pRetry(
|
|
2712
|
+
(attemptNumber) => {
|
|
2713
|
+
return computation(attemptNumber, errors, ++attempts);
|
|
2714
|
+
},
|
|
2715
|
+
{
|
|
2716
|
+
retries: options.retries,
|
|
2717
|
+
shouldRetry: (context) => {
|
|
2718
|
+
if (UnanswerableSQLError.isInstance(context.error)) {
|
|
2719
|
+
return false;
|
|
2292
2720
|
}
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
execute: async ({ sql }, options) => {
|
|
2296
|
-
const state = toState6(options);
|
|
2297
|
-
try {
|
|
2298
|
-
const validationError = await state.adapter.validate(sql);
|
|
2299
|
-
if (validationError) {
|
|
2300
|
-
return {
|
|
2301
|
-
success: false,
|
|
2302
|
-
error: `Validation failed: ${validationError}`
|
|
2303
|
-
};
|
|
2721
|
+
if (SQLValidationError.isInstance(context.error)) {
|
|
2722
|
+
return true;
|
|
2304
2723
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2724
|
+
console.log({
|
|
2725
|
+
NoObjectGeneratedError: NoObjectGeneratedError.isInstance(
|
|
2726
|
+
context.error
|
|
2727
|
+
),
|
|
2728
|
+
NoOutputGeneratedError: NoOutputGeneratedError.isInstance(
|
|
2729
|
+
context.error
|
|
2730
|
+
),
|
|
2731
|
+
APICallError: APICallError.isInstance(context.error),
|
|
2732
|
+
JSONParseError: JSONParseError.isInstance(context.error),
|
|
2733
|
+
TypeValidationError: TypeValidationError.isInstance(context.error),
|
|
2734
|
+
NoContentGeneratedError: NoContentGeneratedError.isInstance(
|
|
2735
|
+
context.error
|
|
2736
|
+
)
|
|
2737
|
+
});
|
|
2738
|
+
return APICallError.isInstance(context.error) || JSONParseError.isInstance(context.error) || TypeValidationError.isInstance(context.error) || NoObjectGeneratedError.isInstance(context.error) || NoOutputGeneratedError.isInstance(context.error) || NoContentGeneratedError.isInstance(context.error);
|
|
2739
|
+
},
|
|
2740
|
+
onFailedAttempt(context) {
|
|
2741
|
+
logger.error(`toSQL`, context.error);
|
|
2742
|
+
console.log(
|
|
2743
|
+
`Attempt ${context.attemptNumber} failed. There are ${context.retriesLeft} retries left.`
|
|
2744
|
+
);
|
|
2745
|
+
errors.push(context.error);
|
|
2316
2746
|
}
|
|
2317
2747
|
}
|
|
2318
|
-
}),
|
|
2319
|
-
scratchpad: scratchpad_tool6
|
|
2320
|
-
};
|
|
2321
|
-
function formatDecomposition(decomposition) {
|
|
2322
|
-
const parts = [
|
|
2323
|
-
`Original Question: ${decomposition.originalQuestion}`,
|
|
2324
|
-
"",
|
|
2325
|
-
"Question Breakdown:",
|
|
2326
|
-
...decomposition.breakdown.map((part, i) => ` ${i + 1}. ${part}`)
|
|
2327
|
-
];
|
|
2328
|
-
if (decomposition.entities?.length) {
|
|
2329
|
-
parts.push("", `Entities: ${decomposition.entities.join(", ")}`);
|
|
2330
|
-
}
|
|
2331
|
-
if (decomposition.filters?.length) {
|
|
2332
|
-
parts.push("", `Filters: ${decomposition.filters.join(", ")}`);
|
|
2333
|
-
}
|
|
2334
|
-
if (decomposition.aggregation) {
|
|
2335
|
-
parts.push("", `Aggregation: ${decomposition.aggregation}`);
|
|
2336
|
-
}
|
|
2337
|
-
if (decomposition.ambiguities?.length) {
|
|
2338
|
-
parts.push(
|
|
2339
|
-
"",
|
|
2340
|
-
"Potential Ambiguities:",
|
|
2341
|
-
...decomposition.ambiguities.map((a) => ` - ${a}`)
|
|
2342
|
-
);
|
|
2343
|
-
}
|
|
2344
|
-
parts.push(
|
|
2345
|
-
"",
|
|
2346
|
-
"Generate SQL that addresses each component of the breakdown."
|
|
2347
2748
|
);
|
|
2348
|
-
return parts.join("\n");
|
|
2349
2749
|
}
|
|
2350
|
-
var chat4Agent = agent9({
|
|
2351
|
-
name: "chat4-decomposition",
|
|
2352
|
-
model: groq9("openai/gpt-oss-20b"),
|
|
2353
|
-
tools: tools6,
|
|
2354
|
-
prompt: (state) => {
|
|
2355
|
-
return `
|
|
2356
|
-
${state?.teachings || ""}
|
|
2357
|
-
${state?.introspection || ""}
|
|
2358
|
-
|
|
2359
|
-
When answering questions that require database queries, use the query_with_decomposition tool.
|
|
2360
|
-
|
|
2361
|
-
IMPORTANT: You must break down the question into semantic parts - describe WHAT is being asked, not HOW to implement it.
|
|
2362
|
-
|
|
2363
|
-
Good breakdown example for "Which customers bought the most expensive products last quarter?":
|
|
2364
|
-
- "customers who made purchases" (entity relationship)
|
|
2365
|
-
- "products they purchased" (what products)
|
|
2366
|
-
- "expensive products - need definition" (filter criteria - note ambiguity)
|
|
2367
|
-
- "last quarter" (time filter)
|
|
2368
|
-
- "most - ranking by count or value?" (aggregation - note ambiguity)
|
|
2369
|
-
|
|
2370
|
-
Bad breakdown (too instructional):
|
|
2371
|
-
- "JOIN customers with orders" (this is HOW, not WHAT)
|
|
2372
|
-
- "Use ORDER BY and LIMIT" (this is implementation)
|
|
2373
|
-
|
|
2374
|
-
Break the question into its semantic aspects, and let the SQL specialist figure out the implementation.
|
|
2375
|
-
`;
|
|
2376
|
-
}
|
|
2377
|
-
});
|
|
2378
2750
|
|
|
2379
2751
|
// packages/text2sql/src/lib/teach/teachings.ts
|
|
2380
2752
|
function guidelines(options = {}) {
|
|
@@ -2517,80 +2889,41 @@ var Text2Sql = class {
|
|
|
2517
2889
|
constructor(config) {
|
|
2518
2890
|
this.#config = {
|
|
2519
2891
|
adapter: config.adapter,
|
|
2520
|
-
|
|
2892
|
+
store: config.store,
|
|
2521
2893
|
instructions: [
|
|
2522
2894
|
...guidelines(config.teachingsOptions),
|
|
2523
2895
|
...config.instructions ?? []
|
|
2524
2896
|
],
|
|
2525
2897
|
tools: config.tools ?? {},
|
|
2526
2898
|
model: config.model,
|
|
2527
|
-
|
|
2528
|
-
|
|
2899
|
+
introspection: new JsonCache(
|
|
2900
|
+
"introspection-" + config.version
|
|
2901
|
+
)
|
|
2529
2902
|
};
|
|
2530
2903
|
}
|
|
2531
|
-
async explain(sql) {
|
|
2532
|
-
const { experimental_output } = await generate5(
|
|
2533
|
-
explainerAgent,
|
|
2534
|
-
[user4("Explain this SQL.")],
|
|
2535
|
-
{ sql }
|
|
2536
|
-
);
|
|
2537
|
-
return experimental_output.explanation;
|
|
2538
|
-
}
|
|
2539
2904
|
async toSql(input) {
|
|
2540
|
-
const
|
|
2905
|
+
const schemaFragments = await this.index();
|
|
2541
2906
|
const result = await toSql({
|
|
2542
2907
|
input,
|
|
2543
2908
|
adapter: this.#config.adapter,
|
|
2544
|
-
|
|
2909
|
+
schemaFragments,
|
|
2545
2910
|
instructions: this.#config.instructions,
|
|
2546
2911
|
model: this.#config.model
|
|
2547
2912
|
});
|
|
2548
2913
|
return result.sql;
|
|
2549
2914
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
const
|
|
2556
|
-
(name) => name.startsWith("render_")
|
|
2557
|
-
);
|
|
2558
|
-
const allInstructions = [
|
|
2559
|
-
...this.#config.instructions,
|
|
2560
|
-
guardrail({
|
|
2561
|
-
rule: "ALWAYS use `get_sample_rows` before writing queries that filter or compare against string columns.",
|
|
2562
|
-
reason: "Prevents SQL errors from wrong value formats.",
|
|
2563
|
-
action: "Target specific columns (e.g., get_sample_rows('table', ['status', 'type']))."
|
|
2564
|
-
}),
|
|
2565
|
-
...renderToolNames.length ? [
|
|
2566
|
-
hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
|
|
2567
|
-
styleGuide({
|
|
2568
|
-
prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
|
|
2569
|
-
always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
|
|
2570
|
-
})
|
|
2571
|
-
] : []
|
|
2572
|
-
];
|
|
2573
|
-
const tools7 = Object.keys({
|
|
2574
|
-
...agent10.handoff.tools,
|
|
2575
|
-
...this.#config.memory ? memoryTools : {},
|
|
2576
|
-
...this.#config.tools
|
|
2577
|
-
});
|
|
2578
|
-
return {
|
|
2579
|
-
tools: tools7,
|
|
2580
|
-
prompt: agent10.instructions({
|
|
2581
|
-
introspection: grounding,
|
|
2582
|
-
teachings: toInstructions("instructions", ...allInstructions)
|
|
2583
|
-
})
|
|
2584
|
-
};
|
|
2585
|
-
}
|
|
2586
|
-
async index(options) {
|
|
2587
|
-
const cached = await this.#config.introspection.get();
|
|
2915
|
+
/**
|
|
2916
|
+
* Introspect the database schema and return context fragments.
|
|
2917
|
+
* Results are cached to avoid repeated introspection.
|
|
2918
|
+
*/
|
|
2919
|
+
async index() {
|
|
2920
|
+
const cached = await this.#config.introspection.read();
|
|
2588
2921
|
if (cached) {
|
|
2589
2922
|
return cached;
|
|
2590
2923
|
}
|
|
2591
|
-
const
|
|
2592
|
-
await this.#config.introspection.
|
|
2593
|
-
return
|
|
2924
|
+
const fragments2 = await this.#config.adapter.introspect();
|
|
2925
|
+
await this.#config.introspection.write(fragments2);
|
|
2926
|
+
return fragments2;
|
|
2594
2927
|
}
|
|
2595
2928
|
/**
|
|
2596
2929
|
* Generate training data pairs using a producer factory.
|
|
@@ -2615,529 +2948,136 @@ var Text2Sql = class {
|
|
|
2615
2948
|
const producer = factory(this.#config.adapter);
|
|
2616
2949
|
return producer.toPairs();
|
|
2617
2950
|
}
|
|
2618
|
-
// public async suggest() {
|
|
2619
|
-
// const [introspection, adapterInfo] = await Promise.all([
|
|
2620
|
-
// this.index(),
|
|
2621
|
-
// this.#config.adapter.introspect(),
|
|
2622
|
-
// ]);
|
|
2623
|
-
// const { experimental_output: output } = await generate(
|
|
2624
|
-
// suggestionsAgent,
|
|
2625
|
-
// [
|
|
2626
|
-
// user(
|
|
2627
|
-
// 'Suggest high-impact business questions and matching SQL queries for this database.',
|
|
2628
|
-
// ),
|
|
2629
|
-
// ],
|
|
2630
|
-
// {
|
|
2631
|
-
// },
|
|
2632
|
-
// );
|
|
2633
|
-
// return output.suggestions;
|
|
2634
|
-
// }
|
|
2635
|
-
async chat(messages, params) {
|
|
2636
|
-
const [introspection, userTeachables] = await Promise.all([
|
|
2637
|
-
this.index({ onProgress: console.log }),
|
|
2638
|
-
this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
|
|
2639
|
-
]);
|
|
2640
|
-
const chat = await this.#config.history.upsertChat({
|
|
2641
|
-
id: params.chatId,
|
|
2642
|
-
userId: params.userId,
|
|
2643
|
-
title: "Chat " + params.chatId
|
|
2644
|
-
});
|
|
2645
|
-
const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
|
|
2646
|
-
(name) => name.startsWith("render_")
|
|
2647
|
-
);
|
|
2648
|
-
const instructions = [
|
|
2649
|
-
...this.#config.instructions,
|
|
2650
|
-
guardrail({
|
|
2651
|
-
rule: "ALWAYS use `get_sample_rows` before writing queries that filter or compare against string columns.",
|
|
2652
|
-
reason: "Prevents SQL errors from wrong value formats.",
|
|
2653
|
-
action: "Target specific columns (e.g., get_sample_rows('table', ['status', 'type']))."
|
|
2654
|
-
}),
|
|
2655
|
-
...renderToolNames.length ? [
|
|
2656
|
-
hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
|
|
2657
|
-
styleGuide({
|
|
2658
|
-
prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
|
|
2659
|
-
always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
|
|
2660
|
-
})
|
|
2661
|
-
] : []
|
|
2662
|
-
];
|
|
2663
|
-
const originalMessage = [
|
|
2664
|
-
...chat.messages.map((it) => it.content),
|
|
2665
|
-
...messages
|
|
2666
|
-
];
|
|
2667
|
-
const result = stream(
|
|
2668
|
-
t_a_g.clone({
|
|
2669
|
-
model: this.#config.model,
|
|
2670
|
-
tools: {
|
|
2671
|
-
...t_a_g.handoff.tools,
|
|
2672
|
-
...this.#config.memory ? memoryTools : {},
|
|
2673
|
-
...this.#config.tools
|
|
2674
|
-
}
|
|
2675
|
-
}),
|
|
2676
|
-
originalMessage,
|
|
2677
|
-
{
|
|
2678
|
-
teachings: toInstructions(
|
|
2679
|
-
"instructions",
|
|
2680
|
-
persona({
|
|
2681
|
-
name: "Freya",
|
|
2682
|
-
role: "You are an expert SQL query generator, answering business questions with accurate queries.",
|
|
2683
|
-
tone: "Your tone should be concise and business-friendly."
|
|
2684
|
-
}),
|
|
2685
|
-
...instructions,
|
|
2686
|
-
teachable("user_profile", ...userTeachables)
|
|
2687
|
-
),
|
|
2688
|
-
adapter: this.#config.adapter,
|
|
2689
|
-
introspection,
|
|
2690
|
-
memory: this.#config.memory,
|
|
2691
|
-
userId: params.userId
|
|
2692
|
-
}
|
|
2693
|
-
);
|
|
2694
|
-
return this.#createUIMessageStream(
|
|
2695
|
-
result,
|
|
2696
|
-
messages,
|
|
2697
|
-
params,
|
|
2698
|
-
originalMessage
|
|
2699
|
-
);
|
|
2700
|
-
}
|
|
2701
2951
|
/**
|
|
2702
|
-
*
|
|
2703
|
-
*
|
|
2704
|
-
* Uses a single `query_database` tool that:
|
|
2705
|
-
* 1. Takes a natural language question
|
|
2706
|
-
* 2. Internally calls toSql() to generate validated SQL
|
|
2707
|
-
* 3. Executes the SQL
|
|
2708
|
-
* 4. Returns both SQL and results
|
|
2709
|
-
*
|
|
2710
|
-
* The agent does NOT see the SQL before execution.
|
|
2952
|
+
* Build instructions for rendering tools (hint + styleGuide).
|
|
2953
|
+
* Returns fragments to include when render_* tools are available.
|
|
2711
2954
|
*/
|
|
2712
|
-
|
|
2713
|
-
const [introspection, userTeachables] = await Promise.all([
|
|
2714
|
-
this.index({ onProgress: console.log }),
|
|
2715
|
-
this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
|
|
2716
|
-
]);
|
|
2717
|
-
const chat = await this.#config.history.upsertChat({
|
|
2718
|
-
id: params.chatId,
|
|
2719
|
-
userId: params.userId,
|
|
2720
|
-
title: "Chat " + params.chatId
|
|
2721
|
-
});
|
|
2955
|
+
#buildRenderingInstructions() {
|
|
2722
2956
|
const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
|
|
2723
2957
|
(name) => name.startsWith("render_")
|
|
2724
2958
|
);
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
];
|
|
2735
|
-
const originalMessage = [
|
|
2736
|
-
...chat.messages.map((it) => it.content),
|
|
2737
|
-
...messages
|
|
2959
|
+
if (!renderToolNames.length) {
|
|
2960
|
+
return [];
|
|
2961
|
+
}
|
|
2962
|
+
return [
|
|
2963
|
+
hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
|
|
2964
|
+
styleGuide({
|
|
2965
|
+
prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
|
|
2966
|
+
always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
|
|
2967
|
+
})
|
|
2738
2968
|
];
|
|
2739
|
-
const result = stream(
|
|
2740
|
-
chat1Agent.clone({
|
|
2741
|
-
model: this.#config.model,
|
|
2742
|
-
tools: {
|
|
2743
|
-
...tools3,
|
|
2744
|
-
...this.#config.memory ? memoryTools : {},
|
|
2745
|
-
...this.#config.tools
|
|
2746
|
-
}
|
|
2747
|
-
}),
|
|
2748
|
-
originalMessage,
|
|
2749
|
-
{
|
|
2750
|
-
teachings: toInstructions(
|
|
2751
|
-
"instructions",
|
|
2752
|
-
...instructions,
|
|
2753
|
-
teachable("user_profile", ...userTeachables)
|
|
2754
|
-
),
|
|
2755
|
-
adapter: this.#config.adapter,
|
|
2756
|
-
introspection,
|
|
2757
|
-
instructions: this.#config.instructions,
|
|
2758
|
-
memory: this.#config.memory,
|
|
2759
|
-
userId: params.userId
|
|
2760
|
-
}
|
|
2761
|
-
);
|
|
2762
|
-
return this.#createUIMessageStream(
|
|
2763
|
-
result,
|
|
2764
|
-
messages,
|
|
2765
|
-
params,
|
|
2766
|
-
originalMessage
|
|
2767
|
-
);
|
|
2768
2969
|
}
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
*
|
|
2776
|
-
* The agent sees the SQL before execution and can review/refine.
|
|
2777
|
-
*/
|
|
2778
|
-
async chat2(messages, params) {
|
|
2779
|
-
const [introspection, userTeachables] = await Promise.all([
|
|
2780
|
-
this.index({ onProgress: console.log }),
|
|
2781
|
-
this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
|
|
2782
|
-
]);
|
|
2783
|
-
const chat = await this.#config.history.upsertChat({
|
|
2784
|
-
id: params.chatId,
|
|
2785
|
-
userId: params.userId,
|
|
2786
|
-
title: "Chat " + params.chatId
|
|
2787
|
-
});
|
|
2788
|
-
const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
|
|
2789
|
-
(name) => name.startsWith("render_")
|
|
2790
|
-
);
|
|
2791
|
-
const instructions = [
|
|
2970
|
+
async chat(messages, params) {
|
|
2971
|
+
const schemaFragments = await this.index();
|
|
2972
|
+
const context = new ContextEngine({
|
|
2973
|
+
store: this.#config.store,
|
|
2974
|
+
chatId: params.chatId
|
|
2975
|
+
}).set(
|
|
2792
2976
|
...this.#config.instructions,
|
|
2793
|
-
...
|
|
2794
|
-
|
|
2795
|
-
styleGuide({
|
|
2796
|
-
prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
|
|
2797
|
-
always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
|
|
2798
|
-
})
|
|
2799
|
-
] : []
|
|
2800
|
-
];
|
|
2801
|
-
const originalMessage = [
|
|
2802
|
-
...chat.messages.map((it) => it.content),
|
|
2803
|
-
...messages
|
|
2804
|
-
];
|
|
2805
|
-
const result = stream(
|
|
2806
|
-
chat2Agent.clone({
|
|
2807
|
-
model: this.#config.model,
|
|
2808
|
-
tools: {
|
|
2809
|
-
...tools4,
|
|
2810
|
-
...this.#config.memory ? memoryTools : {},
|
|
2811
|
-
...this.#config.tools
|
|
2812
|
-
}
|
|
2813
|
-
}),
|
|
2814
|
-
originalMessage,
|
|
2815
|
-
{
|
|
2816
|
-
teachings: toInstructions(
|
|
2817
|
-
"instructions",
|
|
2818
|
-
...instructions,
|
|
2819
|
-
teachable("user_profile", ...userTeachables)
|
|
2820
|
-
),
|
|
2821
|
-
adapter: this.#config.adapter,
|
|
2822
|
-
introspection,
|
|
2823
|
-
instructions: this.#config.instructions,
|
|
2824
|
-
memory: this.#config.memory,
|
|
2825
|
-
userId: params.userId
|
|
2826
|
-
}
|
|
2977
|
+
...this.#buildRenderingInstructions(),
|
|
2978
|
+
...schemaFragments
|
|
2827
2979
|
);
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
*
|
|
2838
|
-
* Enables richer interaction where the SQL agent can:
|
|
2839
|
-
* - Surface confidence levels
|
|
2840
|
-
* - State assumptions
|
|
2841
|
-
* - Request clarification when uncertain
|
|
2842
|
-
*/
|
|
2843
|
-
async chat3(messages, params) {
|
|
2844
|
-
const [introspection, userTeachables] = await Promise.all([
|
|
2845
|
-
this.index({ onProgress: console.log }),
|
|
2846
|
-
this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
|
|
2847
|
-
]);
|
|
2848
|
-
const chat = await this.#config.history.upsertChat({
|
|
2849
|
-
id: params.chatId,
|
|
2850
|
-
userId: params.userId,
|
|
2851
|
-
title: "Chat " + params.chatId
|
|
2980
|
+
const userMsg = messages.at(-1);
|
|
2981
|
+
if (userMsg) {
|
|
2982
|
+
context.set(user(userMsg));
|
|
2983
|
+
await context.save();
|
|
2984
|
+
}
|
|
2985
|
+
const chatAgent = t_a_g.clone({
|
|
2986
|
+
model: this.#config.model,
|
|
2987
|
+
context,
|
|
2988
|
+
tools: { ...t_a_g.tools, ...this.#config.tools }
|
|
2852
2989
|
});
|
|
2853
|
-
const
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
] : []
|
|
2865
|
-
];
|
|
2866
|
-
const originalMessage = [
|
|
2867
|
-
...chat.messages.map((it) => it.content),
|
|
2868
|
-
...messages
|
|
2869
|
-
];
|
|
2870
|
-
const result = stream(
|
|
2871
|
-
chat3Agent.clone({
|
|
2872
|
-
model: this.#config.model,
|
|
2873
|
-
tools: {
|
|
2874
|
-
...tools5,
|
|
2875
|
-
...this.#config.memory ? memoryTools : {},
|
|
2876
|
-
...this.#config.tools
|
|
2877
|
-
}
|
|
2878
|
-
}),
|
|
2879
|
-
originalMessage,
|
|
2880
|
-
{
|
|
2881
|
-
teachings: toInstructions(
|
|
2882
|
-
"instructions",
|
|
2883
|
-
...instructions,
|
|
2884
|
-
teachable("user_profile", ...userTeachables)
|
|
2885
|
-
),
|
|
2886
|
-
adapter: this.#config.adapter,
|
|
2887
|
-
introspection,
|
|
2888
|
-
instructions: this.#config.instructions,
|
|
2889
|
-
memory: this.#config.memory,
|
|
2890
|
-
userId: params.userId
|
|
2990
|
+
const result = await chatAgent.stream({});
|
|
2991
|
+
return result.toUIMessageStream({
|
|
2992
|
+
onError: (error) => this.#formatError(error),
|
|
2993
|
+
sendStart: true,
|
|
2994
|
+
sendFinish: true,
|
|
2995
|
+
sendReasoning: true,
|
|
2996
|
+
sendSources: true,
|
|
2997
|
+
generateMessageId: generateId3,
|
|
2998
|
+
onFinish: async ({ responseMessage }) => {
|
|
2999
|
+
context.set(assistant(responseMessage));
|
|
3000
|
+
await context.save();
|
|
2891
3001
|
}
|
|
2892
|
-
);
|
|
2893
|
-
return this.#createUIMessageStream(
|
|
2894
|
-
result,
|
|
2895
|
-
messages,
|
|
2896
|
-
params,
|
|
2897
|
-
originalMessage
|
|
2898
|
-
);
|
|
2899
|
-
}
|
|
2900
|
-
/**
|
|
2901
|
-
* Chat4 - Question decomposition approach.
|
|
2902
|
-
*
|
|
2903
|
-
* Breaks down questions into semantic components before SQL generation:
|
|
2904
|
-
* - entities: Key concepts mentioned
|
|
2905
|
-
* - filters: Filtering criteria
|
|
2906
|
-
* - aggregation: Type of aggregation
|
|
2907
|
-
* - breakdown: Semantic parts of the question
|
|
2908
|
-
*
|
|
2909
|
-
* This helps ensure all aspects of the question are addressed.
|
|
2910
|
-
*/
|
|
2911
|
-
async chat4(messages, params) {
|
|
2912
|
-
const [introspection, userTeachables] = await Promise.all([
|
|
2913
|
-
this.index({ onProgress: console.log }),
|
|
2914
|
-
this.#config.memory ? this.#config.memory.toTeachables(params.userId) : []
|
|
2915
|
-
]);
|
|
2916
|
-
const chat = await this.#config.history.upsertChat({
|
|
2917
|
-
id: params.chatId,
|
|
2918
|
-
userId: params.userId,
|
|
2919
|
-
title: "Chat " + params.chatId
|
|
2920
3002
|
});
|
|
2921
|
-
const renderToolNames = Object.keys(this.#config.tools ?? {}).filter(
|
|
2922
|
-
(name) => name.startsWith("render_")
|
|
2923
|
-
);
|
|
2924
|
-
const instructions = [
|
|
2925
|
-
...this.#config.instructions,
|
|
2926
|
-
...renderToolNames.length ? [
|
|
2927
|
-
hint(`Rendering tools available: ${renderToolNames.join(", ")}.`),
|
|
2928
|
-
styleGuide({
|
|
2929
|
-
prefer: "Use render_* tools for trend/over time/monthly requests or chart asks",
|
|
2930
|
-
always: "Include text insight alongside visualizations. Prefer line charts for time-based data."
|
|
2931
|
-
})
|
|
2932
|
-
] : []
|
|
2933
|
-
];
|
|
2934
|
-
const originalMessage = [
|
|
2935
|
-
...chat.messages.map((it) => it.content),
|
|
2936
|
-
...messages
|
|
2937
|
-
];
|
|
2938
|
-
const result = stream(
|
|
2939
|
-
chat4Agent.clone({
|
|
2940
|
-
model: this.#config.model,
|
|
2941
|
-
tools: {
|
|
2942
|
-
...tools6,
|
|
2943
|
-
...this.#config.memory ? memoryTools : {},
|
|
2944
|
-
...this.#config.tools
|
|
2945
|
-
}
|
|
2946
|
-
}),
|
|
2947
|
-
originalMessage,
|
|
2948
|
-
{
|
|
2949
|
-
teachings: toInstructions(
|
|
2950
|
-
"instructions",
|
|
2951
|
-
...instructions,
|
|
2952
|
-
teachable("user_profile", ...userTeachables)
|
|
2953
|
-
),
|
|
2954
|
-
adapter: this.#config.adapter,
|
|
2955
|
-
introspection,
|
|
2956
|
-
instructions: this.#config.instructions,
|
|
2957
|
-
memory: this.#config.memory,
|
|
2958
|
-
userId: params.userId
|
|
2959
|
-
}
|
|
2960
|
-
);
|
|
2961
|
-
return this.#createUIMessageStream(
|
|
2962
|
-
result,
|
|
2963
|
-
messages,
|
|
2964
|
-
params,
|
|
2965
|
-
originalMessage
|
|
2966
|
-
);
|
|
2967
3003
|
}
|
|
2968
3004
|
/**
|
|
2969
|
-
* Developer
|
|
2970
|
-
*
|
|
2971
|
-
* Provides power-user tools for query building without execution:
|
|
2972
|
-
* - generate_sql: Convert natural language to validated SQL
|
|
2973
|
-
* - validate_sql: Check SQL syntax
|
|
2974
|
-
* - explain_sql: Get plain-English explanations
|
|
2975
|
-
* - show_schema: Explore database schema on demand
|
|
2976
|
-
*
|
|
2977
|
-
* @example
|
|
2978
|
-
* ```typescript
|
|
2979
|
-
* const result = await text2sql.developer(
|
|
2980
|
-
* [user("Generate a query to find top customers by revenue")],
|
|
2981
|
-
* { chatId: 'dev-session-1', userId: 'dev-1' }
|
|
2982
|
-
* );
|
|
2983
|
-
* // Agent responds with SQL, can validate, explain, or refine iteratively
|
|
2984
|
-
* ```
|
|
3005
|
+
* Developer chat interface - power-user mode for SQL generation.
|
|
3006
|
+
* Uses db_query tool for direct SQL execution (LLM writes SQL).
|
|
2985
3007
|
*/
|
|
2986
3008
|
async developer(messages, params) {
|
|
2987
|
-
const
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
(originalMessages) => stream(
|
|
2996
|
-
developerAgent.clone({
|
|
2997
|
-
model: this.#config.model
|
|
2998
|
-
}),
|
|
2999
|
-
originalMessages,
|
|
3000
|
-
{
|
|
3001
|
-
teachings: toInstructions(
|
|
3002
|
-
"instructions",
|
|
3003
|
-
...this.#config.instructions,
|
|
3004
|
-
teachable("user_profile", ...userTeachables)
|
|
3005
|
-
),
|
|
3006
|
-
adapter: this.#config.adapter,
|
|
3007
|
-
introspection,
|
|
3008
|
-
instructions: this.#config.instructions
|
|
3009
|
-
}
|
|
3010
|
-
)
|
|
3009
|
+
const schemaFragments = await this.index();
|
|
3010
|
+
const context = new ContextEngine({
|
|
3011
|
+
store: this.#config.store,
|
|
3012
|
+
chatId: params.chatId
|
|
3013
|
+
}).set(
|
|
3014
|
+
...developer_agent_default.fragments,
|
|
3015
|
+
...this.#config.instructions,
|
|
3016
|
+
...schemaFragments
|
|
3011
3017
|
);
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3018
|
+
const userMsg = messages.at(-1);
|
|
3019
|
+
if (userMsg) {
|
|
3020
|
+
context.set(user(userMsg));
|
|
3021
|
+
await context.save();
|
|
3022
|
+
}
|
|
3023
|
+
const developerAgent = agent({
|
|
3024
|
+
name: "developer",
|
|
3025
|
+
model: this.#config.model,
|
|
3026
|
+
context,
|
|
3027
|
+
tools: developer_agent_default.tools
|
|
3028
|
+
});
|
|
3029
|
+
const result = await developerAgent.stream({
|
|
3030
|
+
adapter: this.#config.adapter
|
|
3031
|
+
});
|
|
3017
3032
|
return result.toUIMessageStream({
|
|
3018
|
-
onError: (error) =>
|
|
3019
|
-
if (NoSuchToolError.isInstance(error)) {
|
|
3020
|
-
return "The model tried to call an unknown tool.";
|
|
3021
|
-
} else if (InvalidToolInputError.isInstance(error)) {
|
|
3022
|
-
return "The model called a tool with invalid arguments.";
|
|
3023
|
-
} else if (ToolCallRepairError.isInstance(error)) {
|
|
3024
|
-
return "The model tried to call a tool with invalid arguments, but it was repaired.";
|
|
3025
|
-
} else if (APICallError2.isInstance(error)) {
|
|
3026
|
-
console.error("Upstream API call failed:", error);
|
|
3027
|
-
return `Upstream API call failed with status ${error.statusCode}: ${error.message}`;
|
|
3028
|
-
} else {
|
|
3029
|
-
return JSON.stringify(error);
|
|
3030
|
-
}
|
|
3031
|
-
},
|
|
3033
|
+
onError: (error) => this.#formatError(error),
|
|
3032
3034
|
sendStart: true,
|
|
3033
3035
|
sendFinish: true,
|
|
3034
3036
|
sendReasoning: true,
|
|
3035
3037
|
sendSources: true,
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
if (!isContinuation && userMessage) {
|
|
3041
|
-
console.log(
|
|
3042
|
-
"Saving user message to history:",
|
|
3043
|
-
JSON.stringify(userMessage)
|
|
3044
|
-
);
|
|
3045
|
-
await this.#config.history.upsertMessage({
|
|
3046
|
-
id: userMessage.id,
|
|
3047
|
-
chatId: params.chatId,
|
|
3048
|
-
role: userMessage.role,
|
|
3049
|
-
content: userMessage
|
|
3050
|
-
});
|
|
3051
|
-
}
|
|
3052
|
-
await this.#config.history.upsertMessage({
|
|
3053
|
-
id: responseMessage.id,
|
|
3054
|
-
chatId: params.chatId,
|
|
3055
|
-
role: responseMessage.role,
|
|
3056
|
-
content: responseMessage
|
|
3057
|
-
});
|
|
3038
|
+
generateMessageId: generateId3,
|
|
3039
|
+
onFinish: async ({ responseMessage }) => {
|
|
3040
|
+
context.set(assistant(responseMessage));
|
|
3041
|
+
await context.save();
|
|
3058
3042
|
}
|
|
3059
3043
|
});
|
|
3060
3044
|
}
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
];
|
|
3072
|
-
const result = streamFn(originalMessages);
|
|
3073
|
-
return result.toUIMessageStream({
|
|
3074
|
-
onError: (error) => {
|
|
3075
|
-
if (NoSuchToolError.isInstance(error)) {
|
|
3076
|
-
return "The model tried to call an unknown tool.";
|
|
3077
|
-
} else if (InvalidToolInputError.isInstance(error)) {
|
|
3078
|
-
return "The model called a tool with invalid arguments.";
|
|
3079
|
-
} else if (ToolCallRepairError.isInstance(error)) {
|
|
3080
|
-
return "The model tried to call a tool with invalid arguments, but it was repaired.";
|
|
3081
|
-
} else if (APICallError2.isInstance(error)) {
|
|
3082
|
-
console.error("Upstream API call failed:", error);
|
|
3083
|
-
return `Upstream API call failed with status ${error.statusCode}: ${error.message}`;
|
|
3084
|
-
} else {
|
|
3085
|
-
return JSON.stringify(error);
|
|
3086
|
-
}
|
|
3087
|
-
},
|
|
3088
|
-
sendStart: true,
|
|
3089
|
-
sendFinish: true,
|
|
3090
|
-
sendReasoning: true,
|
|
3091
|
-
sendSources: true,
|
|
3092
|
-
originalMessages,
|
|
3093
|
-
generateMessageId: generateId,
|
|
3094
|
-
onFinish: async ({ responseMessage, isContinuation }) => {
|
|
3095
|
-
const userMessage = messages.at(-1);
|
|
3096
|
-
if (!isContinuation && userMessage) {
|
|
3097
|
-
console.log(
|
|
3098
|
-
"Saving user message to history:",
|
|
3099
|
-
JSON.stringify(userMessage)
|
|
3100
|
-
);
|
|
3101
|
-
await history.upsertMessage({
|
|
3102
|
-
id: userMessage.id,
|
|
3103
|
-
chatId: params.chatId,
|
|
3104
|
-
role: userMessage.role,
|
|
3105
|
-
content: userMessage
|
|
3106
|
-
});
|
|
3107
|
-
}
|
|
3108
|
-
await history.upsertMessage({
|
|
3109
|
-
id: responseMessage.id,
|
|
3110
|
-
chatId: params.chatId,
|
|
3111
|
-
role: responseMessage.role,
|
|
3112
|
-
content: responseMessage
|
|
3113
|
-
});
|
|
3045
|
+
#formatError(error) {
|
|
3046
|
+
if (NoSuchToolError.isInstance(error)) {
|
|
3047
|
+
return "The model tried to call an unknown tool.";
|
|
3048
|
+
} else if (InvalidToolInputError.isInstance(error)) {
|
|
3049
|
+
return "The model called a tool with invalid arguments.";
|
|
3050
|
+
} else if (ToolCallRepairError.isInstance(error)) {
|
|
3051
|
+
return "The model tried to call a tool with invalid arguments, but it was repaired.";
|
|
3052
|
+
} else if (APICallError2.isInstance(error)) {
|
|
3053
|
+
console.error("Upstream API call failed:", error);
|
|
3054
|
+
return `Upstream API call failed with status ${error.statusCode}: ${error.message}`;
|
|
3114
3055
|
}
|
|
3115
|
-
|
|
3116
|
-
}
|
|
3056
|
+
return JSON.stringify(error);
|
|
3057
|
+
}
|
|
3058
|
+
};
|
|
3117
3059
|
export {
|
|
3118
3060
|
Adapter,
|
|
3119
3061
|
Checkpoint,
|
|
3120
3062
|
FileCache,
|
|
3121
|
-
History,
|
|
3122
|
-
InMemoryHistory,
|
|
3123
|
-
InMemoryTeachablesStore,
|
|
3124
3063
|
JsonCache,
|
|
3125
3064
|
Point,
|
|
3126
|
-
SqliteHistory,
|
|
3127
|
-
SqliteTeachablesStore,
|
|
3128
|
-
TeachablesStore,
|
|
3129
3065
|
Text2Sql,
|
|
3130
3066
|
applyTablesFilter,
|
|
3131
|
-
|
|
3067
|
+
column,
|
|
3068
|
+
constraint,
|
|
3069
|
+
dialectInfo,
|
|
3132
3070
|
filterRelationshipsByTables,
|
|
3133
3071
|
filterTablesByName,
|
|
3134
3072
|
getTablesWithRelated,
|
|
3135
3073
|
guidelines,
|
|
3136
3074
|
hashConfig,
|
|
3075
|
+
index,
|
|
3137
3076
|
matchesFilter,
|
|
3138
|
-
|
|
3077
|
+
relationship,
|
|
3139
3078
|
suggestionsAgent,
|
|
3140
3079
|
t_a_g,
|
|
3141
|
-
|
|
3080
|
+
table,
|
|
3081
|
+
view
|
|
3142
3082
|
};
|
|
3143
3083
|
//# sourceMappingURL=index.js.map
|