@deepagents/text2sql 0.38.0 → 0.39.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 +90 -44
- package/dist/index.js +51 -25
- package/dist/index.js.map +2 -2
- package/dist/lib/adapters/adapter.d.ts +1 -0
- package/dist/lib/adapters/adapter.d.ts.map +1 -1
- package/dist/lib/adapters/bigquery/index.js +24 -13
- package/dist/lib/adapters/bigquery/index.js.map +2 -2
- package/dist/lib/adapters/groundings/context.d.ts +2 -2
- package/dist/lib/adapters/groundings/context.d.ts.map +1 -1
- package/dist/lib/adapters/groundings/index.js +20 -9
- package/dist/lib/adapters/groundings/index.js.map +2 -2
- package/dist/lib/adapters/mysql/index.js +23 -12
- package/dist/lib/adapters/mysql/index.js.map +2 -2
- package/dist/lib/adapters/postgres/index.js +31 -18
- package/dist/lib/adapters/postgres/index.js.map +3 -3
- package/dist/lib/adapters/postgres/postgres.d.ts.map +1 -1
- package/dist/lib/adapters/spreadsheet/index.js +20 -9
- package/dist/lib/adapters/spreadsheet/index.js.map +2 -2
- package/dist/lib/adapters/sqlite/index.js +23 -12
- package/dist/lib/adapters/sqlite/index.js.map +2 -2
- package/dist/lib/adapters/sqlserver/index.js +31 -18
- package/dist/lib/adapters/sqlserver/index.js.map +3 -3
- package/dist/lib/adapters/sqlserver/sqlserver.d.ts.map +1 -1
- package/dist/lib/sql.d.ts +9 -3
- package/dist/lib/sql.d.ts.map +1 -1
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -17,6 +17,15 @@ AI-powered natural language to SQL. Ask questions in plain English, get executab
|
|
|
17
17
|
npm install @deepagents/text2sql
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
Install the database driver or client library that matches your adapter:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install pg # PostgreSQL
|
|
24
|
+
npm install mssql # SQL Server
|
|
25
|
+
npm install mysql2 # MySQL / MariaDB
|
|
26
|
+
npm install @google-cloud/bigquery # BigQuery
|
|
27
|
+
```
|
|
28
|
+
|
|
20
29
|
Requires Node.js LTS (20+).
|
|
21
30
|
|
|
22
31
|
## Quick Start
|
|
@@ -32,6 +41,7 @@ import {
|
|
|
32
41
|
createBashTool,
|
|
33
42
|
createRoutingSandbox,
|
|
34
43
|
createVirtualSandbox,
|
|
44
|
+
user,
|
|
35
45
|
} from '@deepagents/context';
|
|
36
46
|
import { Text2Sql, sqlSandboxExtension } from '@deepagents/text2sql';
|
|
37
47
|
import {
|
|
@@ -50,6 +60,11 @@ const pool = new pg.Pool({
|
|
|
50
60
|
});
|
|
51
61
|
|
|
52
62
|
const store = new InMemoryContextStore();
|
|
63
|
+
const engine = new ContextEngine({
|
|
64
|
+
store,
|
|
65
|
+
chatId: 'chat-123',
|
|
66
|
+
userId: 'user-456',
|
|
67
|
+
});
|
|
53
68
|
const adapter = new Postgres({
|
|
54
69
|
execute: async (sql) => {
|
|
55
70
|
const result = await pool.query(sql);
|
|
@@ -77,15 +92,7 @@ const text2sql = new Text2Sql({
|
|
|
77
92
|
model: groq('openai/gpt-oss-20b'),
|
|
78
93
|
sandbox,
|
|
79
94
|
adapters: { main: adapter },
|
|
80
|
-
context:
|
|
81
|
-
const engine = new ContextEngine({
|
|
82
|
-
store,
|
|
83
|
-
chatId: 'chat-123',
|
|
84
|
-
userId: 'user-456',
|
|
85
|
-
});
|
|
86
|
-
engine.set(...fragments);
|
|
87
|
-
return engine;
|
|
88
|
-
},
|
|
95
|
+
context: engine,
|
|
89
96
|
});
|
|
90
97
|
|
|
91
98
|
// Generate SQL
|
|
@@ -121,10 +128,10 @@ for composition patterns.
|
|
|
121
128
|
|
|
122
129
|
## Fragments
|
|
123
130
|
|
|
124
|
-
Inject domain knowledge
|
|
125
|
-
`
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
Inject domain knowledge by setting fragments on the `ContextEngine` you pass to
|
|
132
|
+
`Text2Sql`. Those fragments affect `chat()` sessions. If you need direct SQL
|
|
133
|
+
generation with extra fragments, use the lower-level `toSql({ fragments })`
|
|
134
|
+
helper exported from `@deepagents/text2sql`.
|
|
128
135
|
|
|
129
136
|
```typescript
|
|
130
137
|
import {
|
|
@@ -137,31 +144,25 @@ import {
|
|
|
137
144
|
} from '@deepagents/context';
|
|
138
145
|
|
|
139
146
|
const store = new InMemoryContextStore();
|
|
147
|
+
const engine = new ContextEngine({
|
|
148
|
+
store,
|
|
149
|
+
chatId: 'chat-123',
|
|
150
|
+
userId: 'user-456',
|
|
151
|
+
});
|
|
140
152
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
action: 'Aggregate by department instead',
|
|
155
|
-
}),
|
|
156
|
-
example({
|
|
157
|
-
question: 'show me churned customers',
|
|
158
|
-
answer: `SELECT * FROM customers WHERE status = 'churned' ORDER BY churned_at DESC`,
|
|
159
|
-
}),
|
|
160
|
-
...fragments,
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
return engine;
|
|
164
|
-
};
|
|
153
|
+
engine.set(
|
|
154
|
+
term('MRR', 'monthly recurring revenue'),
|
|
155
|
+
hint('Always exclude test accounts with email ending in @test.com'),
|
|
156
|
+
guardrail({
|
|
157
|
+
rule: 'Never expose individual salaries',
|
|
158
|
+
reason: 'Confidential HR data',
|
|
159
|
+
action: 'Aggregate by department instead',
|
|
160
|
+
}),
|
|
161
|
+
example({
|
|
162
|
+
question: 'show me churned customers',
|
|
163
|
+
answer: `SELECT * FROM customers WHERE status = 'churned' ORDER BY churned_at DESC`,
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
165
166
|
```
|
|
166
167
|
|
|
167
168
|
**Domain fragments** (11 types): `term`, `hint`, `guardrail`, `example`, `explain`, `clarification`, `workflow`, `quirk`, `styleGuide`, `analogy`, `glossary`.
|
|
@@ -187,23 +188,68 @@ Control what schema metadata the AI receives:
|
|
|
187
188
|
|
|
188
189
|
## Conversations
|
|
189
190
|
|
|
190
|
-
`chat()` persists history through the `ContextEngine`
|
|
191
|
+
`chat()` persists history through the `ContextEngine` you pass to `Text2Sql`.
|
|
192
|
+
Reuse the same store, `chatId`, and `userId` to continue the same thread.
|
|
193
|
+
Before each turn, append only the new incoming message to the engine; do not
|
|
194
|
+
replay earlier turns that are already stored in the context store:
|
|
191
195
|
|
|
192
196
|
```typescript
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
]);
|
|
197
|
+
await engine.continue(user('Show me orders from last month'));
|
|
198
|
+
const stream = await text2sql.chat();
|
|
196
199
|
|
|
197
200
|
for await (const chunk of stream) {
|
|
198
201
|
// handle streaming response
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
// Continue the same conversation
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
+
await engine.continue(user('Now filter to only completed ones'));
|
|
206
|
+
const followUp = await text2sql.chat();
|
|
207
|
+
for await (const chunk of followUp) {
|
|
208
|
+
// handle streaming response
|
|
209
|
+
}
|
|
205
210
|
```
|
|
206
211
|
|
|
212
|
+
While `chat()` builds or reads cached schema context, it emits progress chunks
|
|
213
|
+
before assistant text starts:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import {
|
|
217
|
+
TEXT2SQL_INDEX_PROGRESS_CHUNK,
|
|
218
|
+
type Text2SqlIndexProgressEvent,
|
|
219
|
+
} from '@deepagents/text2sql';
|
|
220
|
+
|
|
221
|
+
await engine.continue(user('Show me top 10 customers'));
|
|
222
|
+
const stream = await text2sql.chat();
|
|
223
|
+
const startedAt = new Map<string, number>();
|
|
224
|
+
|
|
225
|
+
for await (const chunk of stream) {
|
|
226
|
+
if (chunk.type === TEXT2SQL_INDEX_PROGRESS_CHUNK) {
|
|
227
|
+
const event = chunk.data as Text2SqlIndexProgressEvent;
|
|
228
|
+
const scope =
|
|
229
|
+
[event.adapter, event.phase].filter(Boolean).join(':') || 'index';
|
|
230
|
+
|
|
231
|
+
if (event.type.endsWith(':start') || event.type === 'index:start') {
|
|
232
|
+
startedAt.set(scope, event.timestampMs ?? Date.now());
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (event.type.endsWith(':end') || event.type.endsWith(':error')) {
|
|
236
|
+
const durationMs =
|
|
237
|
+
(event.timestampMs ?? Date.now()) -
|
|
238
|
+
(startedAt.get(scope) ?? event.timestampMs ?? Date.now());
|
|
239
|
+
console.log(event.message, durationMs);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// handle assistant stream chunks
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Each progress event includes a generic Unix epoch `timestampMs` so clients can
|
|
250
|
+
calculate per-index, per-adapter, or per-phase durations without Text2SQL
|
|
251
|
+
imposing one duration policy.
|
|
252
|
+
|
|
207
253
|
`toSql()` is stateless. It reads the current schema fragments, but it does not
|
|
208
254
|
write messages, titles, or usage into your context store.
|
|
209
255
|
|
package/dist/index.js
CHANGED
|
@@ -163,6 +163,17 @@ function relationship(input) {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// packages/text2sql/src/lib/adapters/groundings/context.ts
|
|
166
|
+
var noopProgress = () => {
|
|
167
|
+
};
|
|
168
|
+
function timestampProgress(onProgress) {
|
|
169
|
+
if (!onProgress) return noopProgress;
|
|
170
|
+
return (progress) => {
|
|
171
|
+
onProgress({
|
|
172
|
+
...progress,
|
|
173
|
+
timestampMs: progress.timestampMs ?? Date.now()
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
}
|
|
166
177
|
function createGroundingContext(options = {}) {
|
|
167
178
|
return {
|
|
168
179
|
tables: [],
|
|
@@ -170,7 +181,7 @@ function createGroundingContext(options = {}) {
|
|
|
170
181
|
relationships: [],
|
|
171
182
|
info: void 0,
|
|
172
183
|
cache: /* @__PURE__ */ new Map(),
|
|
173
|
-
onProgress: options.onProgress
|
|
184
|
+
onProgress: timestampProgress(options.onProgress)
|
|
174
185
|
};
|
|
175
186
|
}
|
|
176
187
|
|
|
@@ -317,7 +328,7 @@ var Adapter = class {
|
|
|
317
328
|
for (const fn of this.grounding) {
|
|
318
329
|
const grounding = fn(this);
|
|
319
330
|
if (grounding.phase) {
|
|
320
|
-
ctx.onProgress
|
|
331
|
+
ctx.onProgress({
|
|
321
332
|
type: "phase:start",
|
|
322
333
|
phase: grounding.phase,
|
|
323
334
|
message: `Starting ${grounding.name} introspection...`
|
|
@@ -327,7 +338,7 @@ var Adapter = class {
|
|
|
327
338
|
await grounding.execute(ctx);
|
|
328
339
|
} finally {
|
|
329
340
|
if (grounding.phase) {
|
|
330
|
-
ctx.onProgress
|
|
341
|
+
ctx.onProgress({
|
|
331
342
|
type: "phase:end",
|
|
332
343
|
phase: grounding.phase,
|
|
333
344
|
message: `Finished ${grounding.name} introspection.`
|
|
@@ -5238,6 +5249,17 @@ import {
|
|
|
5238
5249
|
fragment as fragment3
|
|
5239
5250
|
} from "@deepagents/context";
|
|
5240
5251
|
var TEXT2SQL_INDEX_PROGRESS_CHUNK = "data-text2sql-index-progress";
|
|
5252
|
+
var noopProgressHandler = () => {
|
|
5253
|
+
};
|
|
5254
|
+
function timestampProgressHandler(onProgress) {
|
|
5255
|
+
if (!onProgress) return noopProgressHandler;
|
|
5256
|
+
return (event) => {
|
|
5257
|
+
onProgress({
|
|
5258
|
+
...event,
|
|
5259
|
+
timestampMs: event.timestampMs ?? Date.now()
|
|
5260
|
+
});
|
|
5261
|
+
};
|
|
5262
|
+
}
|
|
5241
5263
|
var Text2Sql = class {
|
|
5242
5264
|
#config;
|
|
5243
5265
|
#introspection;
|
|
@@ -5283,8 +5305,9 @@ var Text2Sql = class {
|
|
|
5283
5305
|
return this.#index();
|
|
5284
5306
|
}
|
|
5285
5307
|
async #index(onProgress) {
|
|
5308
|
+
const progress = timestampProgressHandler(onProgress);
|
|
5286
5309
|
const entries = Object.entries(this.#config.adapters);
|
|
5287
|
-
|
|
5310
|
+
progress({
|
|
5288
5311
|
type: "index:start",
|
|
5289
5312
|
message: `Indexing ${entries.length} adapter${entries.length === 1 ? "" : "s"}...`,
|
|
5290
5313
|
current: 0,
|
|
@@ -5292,13 +5315,13 @@ var Text2Sql = class {
|
|
|
5292
5315
|
});
|
|
5293
5316
|
const settled = await Promise.allSettled(
|
|
5294
5317
|
entries.map(async ([name, adapter]) => {
|
|
5295
|
-
const schema = await this.#indexAdapter(name, adapter,
|
|
5318
|
+
const schema = await this.#indexAdapter(name, adapter, progress);
|
|
5296
5319
|
return fragment3(name, ...schema);
|
|
5297
5320
|
})
|
|
5298
5321
|
);
|
|
5299
5322
|
const failed = settled.find((result) => result.status === "rejected");
|
|
5300
5323
|
if (failed) {
|
|
5301
|
-
|
|
5324
|
+
progress({
|
|
5302
5325
|
type: "index:error",
|
|
5303
5326
|
message: failed.reason instanceof Error ? failed.reason.message : String(failed.reason)
|
|
5304
5327
|
});
|
|
@@ -5310,7 +5333,7 @@ var Text2Sql = class {
|
|
|
5310
5333
|
}
|
|
5311
5334
|
return result.value;
|
|
5312
5335
|
});
|
|
5313
|
-
|
|
5336
|
+
progress({
|
|
5314
5337
|
type: "index:end",
|
|
5315
5338
|
message: "Finished indexing adapters.",
|
|
5316
5339
|
current: entries.length,
|
|
@@ -5318,8 +5341,8 @@ var Text2Sql = class {
|
|
|
5318
5341
|
});
|
|
5319
5342
|
return wrapped;
|
|
5320
5343
|
}
|
|
5321
|
-
async #indexAdapter(name, adapter, onProgress) {
|
|
5322
|
-
onProgress
|
|
5344
|
+
async #indexAdapter(name, adapter, onProgress = noopProgressHandler) {
|
|
5345
|
+
onProgress({
|
|
5323
5346
|
type: "adapter:start",
|
|
5324
5347
|
adapter: name,
|
|
5325
5348
|
message: `Indexing adapter "${name}"...`
|
|
@@ -5331,13 +5354,13 @@ var Text2Sql = class {
|
|
|
5331
5354
|
try {
|
|
5332
5355
|
const cached = await cache.read();
|
|
5333
5356
|
if (cached) {
|
|
5334
|
-
onProgress
|
|
5357
|
+
onProgress({
|
|
5335
5358
|
type: "adapter:cache-hit",
|
|
5336
5359
|
adapter: name,
|
|
5337
5360
|
message: `Using cached index for adapter "${name}".`,
|
|
5338
5361
|
cached: true
|
|
5339
5362
|
});
|
|
5340
|
-
onProgress
|
|
5363
|
+
onProgress({
|
|
5341
5364
|
type: "adapter:end",
|
|
5342
5365
|
adapter: name,
|
|
5343
5366
|
message: `Finished indexing adapter "${name}".`,
|
|
@@ -5345,18 +5368,18 @@ var Text2Sql = class {
|
|
|
5345
5368
|
});
|
|
5346
5369
|
return cached;
|
|
5347
5370
|
}
|
|
5348
|
-
onProgress
|
|
5371
|
+
onProgress({
|
|
5349
5372
|
type: "adapter:cache-miss",
|
|
5350
5373
|
adapter: name,
|
|
5351
5374
|
message: `No cached index for adapter "${name}".`,
|
|
5352
5375
|
cached: false
|
|
5353
5376
|
});
|
|
5354
5377
|
const ctx = createGroundingContext({
|
|
5355
|
-
onProgress: (
|
|
5378
|
+
onProgress: (event) => onProgress(this.#adapterProgressEvent(name, event))
|
|
5356
5379
|
});
|
|
5357
5380
|
const fragments = await adapter.introspect(ctx);
|
|
5358
5381
|
await cache.write(fragments);
|
|
5359
|
-
onProgress
|
|
5382
|
+
onProgress({
|
|
5360
5383
|
type: "adapter:end",
|
|
5361
5384
|
adapter: name,
|
|
5362
5385
|
message: `Finished indexing adapter "${name}".`,
|
|
@@ -5365,7 +5388,7 @@ var Text2Sql = class {
|
|
|
5365
5388
|
return fragments;
|
|
5366
5389
|
} catch (error) {
|
|
5367
5390
|
const reason = error instanceof Error ? error.message : String(error);
|
|
5368
|
-
onProgress
|
|
5391
|
+
onProgress({
|
|
5369
5392
|
type: "adapter:error",
|
|
5370
5393
|
adapter: name,
|
|
5371
5394
|
message: `Failed indexing adapter "${name}": ${reason}`
|
|
@@ -5384,7 +5407,8 @@ var Text2Sql = class {
|
|
|
5384
5407
|
message: progress.message,
|
|
5385
5408
|
current: progress.current,
|
|
5386
5409
|
total: progress.total,
|
|
5387
|
-
cached: false
|
|
5410
|
+
cached: false,
|
|
5411
|
+
timestampMs: progress.timestampMs
|
|
5388
5412
|
};
|
|
5389
5413
|
}
|
|
5390
5414
|
async toPairs(adapterName, factory) {
|
|
@@ -5392,22 +5416,24 @@ var Text2Sql = class {
|
|
|
5392
5416
|
const producer = factory(adapter);
|
|
5393
5417
|
return producer.toPairs();
|
|
5394
5418
|
}
|
|
5395
|
-
async chat(
|
|
5396
|
-
if (messages.length === 0) {
|
|
5397
|
-
throw new Error("messages must not be empty");
|
|
5398
|
-
}
|
|
5419
|
+
async chat(options) {
|
|
5399
5420
|
return createUIMessageStream({
|
|
5400
5421
|
execute: async ({ writer }) => {
|
|
5422
|
+
const context = this.#config.context;
|
|
5423
|
+
const head = await context.headMessage();
|
|
5424
|
+
if (!head || head.name !== "assistant") {
|
|
5425
|
+
throw new Error(
|
|
5426
|
+
"text2sql.chat: expected head to be an assistant message \u2014 call context.continue() before chat()"
|
|
5427
|
+
);
|
|
5428
|
+
}
|
|
5429
|
+
writer.write({ type: "start", messageId: head.id });
|
|
5401
5430
|
const progress = (event) => {
|
|
5402
5431
|
writer.write({
|
|
5403
5432
|
type: TEXT2SQL_INDEX_PROGRESS_CHUNK,
|
|
5404
5433
|
data: event
|
|
5405
5434
|
});
|
|
5406
5435
|
};
|
|
5407
|
-
|
|
5408
|
-
...guidelines(),
|
|
5409
|
-
...await this.#index(progress)
|
|
5410
|
-
);
|
|
5436
|
+
context.set(...guidelines(), ...await this.#index(progress));
|
|
5411
5437
|
const chatAgent = agent2({
|
|
5412
5438
|
name: "text2sql",
|
|
5413
5439
|
sandbox: this.#config.sandbox,
|
|
@@ -5417,7 +5443,7 @@ var Text2Sql = class {
|
|
|
5417
5443
|
guardrails: [errorRecoveryGuardrail],
|
|
5418
5444
|
maxGuardrailRetries: 3
|
|
5419
5445
|
});
|
|
5420
|
-
const chatStream = await chat(chatAgent,
|
|
5446
|
+
const chatStream = await chat(chatAgent, {
|
|
5421
5447
|
abortSignal: options?.abortSignal,
|
|
5422
5448
|
generateTitle: options?.generateTitle,
|
|
5423
5449
|
transform: this.#config.transform
|