@deepagents/text2sql 0.2.1 → 0.2.3
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/dist/index.js +1421 -1390
- package/dist/index.js.map +3 -3
- package/dist/lib/agents/brief.agent.d.ts +10 -5
- package/dist/lib/agents/brief.agent.d.ts.map +1 -1
- package/dist/lib/sql.d.ts +6 -4
- package/dist/lib/sql.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -293,6 +293,23 @@ import {
|
|
|
293
293
|
user as user2
|
|
294
294
|
} from "@deepagents/agent";
|
|
295
295
|
|
|
296
|
+
// packages/text2sql/src/lib/agents/brief.agent.ts
|
|
297
|
+
import { groq as groq2 } from "@ai-sdk/groq";
|
|
298
|
+
import { createUIMessageStream, tool as tool2 } from "ai";
|
|
299
|
+
import dedent from "dedent";
|
|
300
|
+
import { createHash } from "node:crypto";
|
|
301
|
+
import { existsSync } from "node:fs";
|
|
302
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
303
|
+
import { tmpdir } from "node:os";
|
|
304
|
+
import path from "node:path";
|
|
305
|
+
import z2 from "zod";
|
|
306
|
+
import {
|
|
307
|
+
agent as agent2,
|
|
308
|
+
generate,
|
|
309
|
+
toState as toState2,
|
|
310
|
+
user
|
|
311
|
+
} from "@deepagents/agent";
|
|
312
|
+
|
|
296
313
|
// packages/text2sql/src/lib/adapters/adapter.ts
|
|
297
314
|
var Adapter = class {
|
|
298
315
|
async resolveTables(filter) {
|
|
@@ -370,1485 +387,1498 @@ function getTablesWithRelated(allTables, relationships, filter) {
|
|
|
370
387
|
return Array.from(result);
|
|
371
388
|
}
|
|
372
389
|
|
|
373
|
-
// packages/text2sql/src/lib/
|
|
374
|
-
var
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
pattern: /^no such column: .+$/,
|
|
382
|
-
type: "INVALID_COLUMN",
|
|
383
|
-
hint: "Check the table schema for correct column names. The column may not exist or is ambiguous (exists in multiple joined tables)."
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
pattern: /^ambiguous column name: .+$/,
|
|
387
|
-
type: "INVALID_COLUMN",
|
|
388
|
-
hint: "Check the table schema for correct column names. The column may not exist or is ambiguous (exists in multiple joined tables)."
|
|
389
|
-
},
|
|
390
|
-
{
|
|
391
|
-
pattern: /^near ".+": syntax error$/,
|
|
392
|
-
type: "SYNTAX_ERROR",
|
|
393
|
-
hint: "There is a SQL syntax error. Review the query structure, keywords, and punctuation."
|
|
394
|
-
},
|
|
395
|
-
{
|
|
396
|
-
pattern: /^no tables specified$/,
|
|
397
|
-
type: "SYNTAX_ERROR",
|
|
398
|
-
hint: "There is a SQL syntax error. Review the query structure, keywords, and punctuation."
|
|
399
|
-
},
|
|
400
|
-
{
|
|
401
|
-
pattern: /^attempt to write a readonly database$/,
|
|
402
|
-
type: "CONSTRAINT_ERROR",
|
|
403
|
-
hint: "A database constraint was violated. This should not happen with read-only queries."
|
|
404
|
-
}
|
|
405
|
-
];
|
|
406
|
-
var LOW_CARDINALITY_LIMIT = 20;
|
|
407
|
-
function formatError(sql, error) {
|
|
408
|
-
const errorMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error occurred";
|
|
409
|
-
const errorInfo = SQL_ERROR_MAP.find((it) => it.pattern.test(errorMessage));
|
|
410
|
-
if (!errorInfo) {
|
|
411
|
-
return {
|
|
412
|
-
error: errorMessage,
|
|
413
|
-
error_type: "UNKNOWN_ERROR",
|
|
414
|
-
suggestion: "Review the query and try again",
|
|
415
|
-
sql_attempted: sql
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
return {
|
|
419
|
-
error: errorMessage,
|
|
420
|
-
error_type: errorInfo.type,
|
|
421
|
-
suggestion: errorInfo.hint,
|
|
422
|
-
sql_attempted: sql
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
var Sqlite = class extends Adapter {
|
|
426
|
-
#options;
|
|
427
|
-
#introspection = null;
|
|
428
|
-
#info = null;
|
|
429
|
-
constructor(options) {
|
|
430
|
-
super();
|
|
431
|
-
if (!options || typeof options.execute !== "function") {
|
|
432
|
-
throw new Error("Sqlite adapter requires an execute function.");
|
|
433
|
-
}
|
|
434
|
-
this.#options = options;
|
|
390
|
+
// packages/text2sql/src/lib/agents/brief.agent.ts
|
|
391
|
+
var TmpCache = class {
|
|
392
|
+
path;
|
|
393
|
+
constructor(watermark, extension = ".txt") {
|
|
394
|
+
const hash = createHash("md5").update(watermark).digest("hex");
|
|
395
|
+
this.path = path.join(tmpdir(), `text2sql-${hash}${extension}`);
|
|
435
396
|
}
|
|
436
|
-
async
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
return this.#introspection;
|
|
440
|
-
}
|
|
441
|
-
if (this.#options.introspect) {
|
|
442
|
-
this.#introspection = await this.#options.introspect();
|
|
443
|
-
return this.#introspection;
|
|
397
|
+
async get() {
|
|
398
|
+
if (existsSync(this.path)) {
|
|
399
|
+
return readFile(this.path, "utf-8");
|
|
444
400
|
}
|
|
445
|
-
|
|
446
|
-
const allRelationships = await this.#loadRelationships(
|
|
447
|
-
allTables.map((t) => t.name)
|
|
448
|
-
);
|
|
449
|
-
const { tables, relationships } = this.#applyTablesFilter(
|
|
450
|
-
allTables,
|
|
451
|
-
allRelationships
|
|
452
|
-
);
|
|
453
|
-
onProgress?.({
|
|
454
|
-
phase: "tables",
|
|
455
|
-
message: `Loaded ${tables.length} tables`,
|
|
456
|
-
total: tables.length
|
|
457
|
-
});
|
|
458
|
-
onProgress?.({ phase: "row_counts", message: "Counting table rows..." });
|
|
459
|
-
await this.#annotateRowCounts(tables, onProgress);
|
|
460
|
-
onProgress?.({
|
|
461
|
-
phase: "column_stats",
|
|
462
|
-
message: "Collecting column statistics..."
|
|
463
|
-
});
|
|
464
|
-
await this.#annotateColumnStats(tables, onProgress);
|
|
465
|
-
onProgress?.({ phase: "indexes", message: "Loading index information..." });
|
|
466
|
-
await this.#annotateIndexes(tables, onProgress);
|
|
467
|
-
onProgress?.({
|
|
468
|
-
phase: "low_cardinality",
|
|
469
|
-
message: "Identifying low cardinality columns..."
|
|
470
|
-
});
|
|
471
|
-
await this.#annotateLowCardinalityColumns(tables, onProgress);
|
|
472
|
-
onProgress?.({
|
|
473
|
-
phase: "relationships",
|
|
474
|
-
message: "Loading foreign key relationships..."
|
|
475
|
-
});
|
|
476
|
-
this.#introspection = { tables, relationships };
|
|
477
|
-
return this.#introspection;
|
|
478
|
-
}
|
|
479
|
-
async execute(sql) {
|
|
480
|
-
return this.#options.execute(sql);
|
|
401
|
+
return null;
|
|
481
402
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
await this.#options.execute(`EXPLAIN ${text}`);
|
|
485
|
-
});
|
|
486
|
-
try {
|
|
487
|
-
return await validator(sql);
|
|
488
|
-
} catch (error) {
|
|
489
|
-
return JSON.stringify(formatError(sql, error));
|
|
490
|
-
}
|
|
403
|
+
set(content) {
|
|
404
|
+
return writeFile(this.path, content, "utf-8");
|
|
491
405
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
this.#info = await this.#resolveInfo();
|
|
497
|
-
return this.#info;
|
|
406
|
+
};
|
|
407
|
+
var JsonCache = class extends TmpCache {
|
|
408
|
+
constructor(watermark) {
|
|
409
|
+
super(watermark, ".json");
|
|
498
410
|
}
|
|
499
|
-
|
|
500
|
-
const
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
}
|
|
504
|
-
if (info.database) {
|
|
505
|
-
lines.push(`Database: ${info.database}`);
|
|
506
|
-
}
|
|
507
|
-
if (info.host) {
|
|
508
|
-
lines.push(`Host: ${info.host}`);
|
|
509
|
-
}
|
|
510
|
-
if (info.details && Object.keys(info.details).length) {
|
|
511
|
-
lines.push(`Details: ${JSON.stringify(info.details)}`);
|
|
411
|
+
async read() {
|
|
412
|
+
const content = await this.get();
|
|
413
|
+
if (content) {
|
|
414
|
+
return JSON.parse(content);
|
|
512
415
|
}
|
|
513
|
-
return
|
|
416
|
+
return null;
|
|
514
417
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const allRelationships = await this.#loadRelationships(
|
|
518
|
-
allTables.map((t) => t.name)
|
|
519
|
-
);
|
|
520
|
-
return this.#applyTablesFilter(allTables, allRelationships).tables;
|
|
418
|
+
write(data) {
|
|
419
|
+
return this.set(JSON.stringify(data));
|
|
521
420
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
421
|
+
};
|
|
422
|
+
var briefAgent = agent2({
|
|
423
|
+
name: "db-brief-agent",
|
|
424
|
+
model: groq2("openai/gpt-oss-20b"),
|
|
425
|
+
prompt: (state) => dedent`
|
|
426
|
+
<identity>
|
|
427
|
+
You are a database analyst expert. Your job is to understand what a database represents and provide business context about it.
|
|
428
|
+
You have READ-ONLY access to the database.
|
|
429
|
+
</identity>
|
|
430
|
+
|
|
431
|
+
${databaseSchemaPrompt(state)}
|
|
432
|
+
|
|
433
|
+
<instructions>
|
|
434
|
+
Write a business context that helps another agent answer questions accurately.
|
|
435
|
+
|
|
436
|
+
For EACH table, do queries ONE AT A TIME:
|
|
437
|
+
1. SELECT COUNT(*) to get row count
|
|
438
|
+
2. SELECT * LIMIT 3 to see sample data
|
|
439
|
+
|
|
440
|
+
Then write a report with:
|
|
441
|
+
- What business this database is for
|
|
442
|
+
- For each table: purpose, row count, and example of what the data looks like
|
|
443
|
+
|
|
444
|
+
Include concrete examples like "Track prices are $0.99", "Customer names like 'Lu\u00eds Gon\u00e7alves'", etc.
|
|
445
|
+
|
|
446
|
+
Keep it 400-600 words, conversational style.
|
|
447
|
+
</instructions>
|
|
448
|
+
`,
|
|
449
|
+
tools: {
|
|
450
|
+
query_database: tool2({
|
|
451
|
+
description: "Execute a SELECT query to explore the database and gather insights.",
|
|
452
|
+
inputSchema: z2.object({
|
|
453
|
+
sql: z2.string().describe("The SELECT query to execute"),
|
|
454
|
+
purpose: z2.string().describe("What insight you are trying to gather with this query")
|
|
455
|
+
}),
|
|
456
|
+
execute: ({ sql }, options) => {
|
|
457
|
+
const state = toState2(options);
|
|
458
|
+
return state.execute(sql);
|
|
459
|
+
}
|
|
460
|
+
})
|
|
546
461
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
462
|
+
});
|
|
463
|
+
async function runAndCache(introspection, cache) {
|
|
464
|
+
const { text } = await generate(
|
|
465
|
+
briefAgent,
|
|
466
|
+
[
|
|
467
|
+
user(
|
|
468
|
+
"Please analyze the database and write a contextual report about what this database represents."
|
|
469
|
+
)
|
|
470
|
+
],
|
|
471
|
+
{ introspection }
|
|
472
|
+
);
|
|
473
|
+
await cache.set(text);
|
|
474
|
+
return text;
|
|
475
|
+
}
|
|
476
|
+
async function generateBrief(introspection, cache) {
|
|
477
|
+
const brief = await cache.get();
|
|
478
|
+
if (!brief) {
|
|
479
|
+
return runAndCache(introspection, cache);
|
|
553
480
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
)
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
481
|
+
return brief;
|
|
482
|
+
}
|
|
483
|
+
function toBrief(forceRefresh = false) {
|
|
484
|
+
return (state, setState) => {
|
|
485
|
+
return createUIMessageStream({
|
|
486
|
+
execute: async ({ writer }) => {
|
|
487
|
+
if (forceRefresh) {
|
|
488
|
+
const brief = await runAndCache(state.introspection, state.cache);
|
|
489
|
+
writer.write({
|
|
490
|
+
type: "data-brief-agent",
|
|
491
|
+
data: {
|
|
492
|
+
cache: "forced",
|
|
493
|
+
brief
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
setState({ context: brief });
|
|
497
|
+
} else {
|
|
498
|
+
let brief = await state.cache.get();
|
|
499
|
+
if (!brief) {
|
|
500
|
+
writer.write({
|
|
501
|
+
type: "data-brief-agent",
|
|
502
|
+
data: {
|
|
503
|
+
cache: "miss"
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
brief = await runAndCache(state.introspection, state.cache);
|
|
507
|
+
writer.write({
|
|
508
|
+
type: "data-brief-agent",
|
|
509
|
+
data: {
|
|
510
|
+
cache: "new",
|
|
511
|
+
brief
|
|
512
|
+
}
|
|
574
513
|
});
|
|
575
514
|
} else {
|
|
576
|
-
|
|
577
|
-
|
|
515
|
+
writer.write({
|
|
516
|
+
type: "data-brief-agent",
|
|
517
|
+
data: {
|
|
518
|
+
cache: "hit",
|
|
519
|
+
brief
|
|
520
|
+
}
|
|
521
|
+
});
|
|
578
522
|
}
|
|
579
523
|
}
|
|
580
|
-
return Array.from(groups.values());
|
|
581
|
-
})
|
|
582
|
-
);
|
|
583
|
-
return relationshipGroups.flat();
|
|
584
|
-
}
|
|
585
|
-
async #annotateRowCounts(tables, onProgress) {
|
|
586
|
-
const total = tables.length;
|
|
587
|
-
for (let i = 0; i < tables.length; i++) {
|
|
588
|
-
const table = tables[i];
|
|
589
|
-
const tableIdentifier = this.#formatTableIdentifier(table);
|
|
590
|
-
onProgress?.({
|
|
591
|
-
phase: "row_counts",
|
|
592
|
-
message: `Counting rows in ${table.name}...`,
|
|
593
|
-
current: i + 1,
|
|
594
|
-
total
|
|
595
|
-
});
|
|
596
|
-
try {
|
|
597
|
-
const rows = await this.#runQuery(
|
|
598
|
-
`SELECT COUNT(*) as count FROM ${tableIdentifier}`
|
|
599
|
-
);
|
|
600
|
-
const rowCount = this.#toNumber(rows[0]?.count);
|
|
601
|
-
if (rowCount != null) {
|
|
602
|
-
table.rowCount = rowCount;
|
|
603
|
-
table.sizeHint = this.#classifyRowCount(rowCount);
|
|
604
|
-
}
|
|
605
|
-
} catch {
|
|
606
|
-
continue;
|
|
607
524
|
}
|
|
608
|
-
}
|
|
525
|
+
});
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// packages/text2sql/src/lib/agents/explainer.agent.ts
|
|
530
|
+
import { groq as groq3 } from "@ai-sdk/groq";
|
|
531
|
+
import dedent2 from "dedent";
|
|
532
|
+
import z3 from "zod";
|
|
533
|
+
import { agent as agent3 } from "@deepagents/agent";
|
|
534
|
+
var explainerAgent = agent3({
|
|
535
|
+
name: "explainer",
|
|
536
|
+
model: groq3("openai/gpt-oss-20b"),
|
|
537
|
+
prompt: (state) => dedent2`
|
|
538
|
+
You are an expert SQL tutor.
|
|
539
|
+
Explain the following SQL query in plain English to a non-technical user.
|
|
540
|
+
Focus on the intent and logic, not the syntax.
|
|
541
|
+
|
|
542
|
+
<sql>
|
|
543
|
+
${state?.sql}
|
|
544
|
+
</sql>
|
|
545
|
+
`,
|
|
546
|
+
output: z3.object({
|
|
547
|
+
explanation: z3.string().describe("The explanation of the SQL query.")
|
|
548
|
+
})
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// packages/text2sql/src/lib/agents/suggestions.agents.ts
|
|
552
|
+
import { groq as groq4 } from "@ai-sdk/groq";
|
|
553
|
+
import dedent3 from "dedent";
|
|
554
|
+
import z4 from "zod";
|
|
555
|
+
import { agent as agent4, thirdPersonPrompt } from "@deepagents/agent";
|
|
556
|
+
var suggestionsAgent = agent4({
|
|
557
|
+
name: "text2sql-suggestions",
|
|
558
|
+
model: groq4("openai/gpt-oss-20b"),
|
|
559
|
+
output: z4.object({
|
|
560
|
+
suggestions: z4.array(
|
|
561
|
+
z4.object({
|
|
562
|
+
question: z4.string().describe("A complex, high-impact business question."),
|
|
563
|
+
sql: z4.string().describe("The SQL statement needed to answer the question."),
|
|
564
|
+
businessValue: z4.string().describe("Why the question matters to stakeholders.")
|
|
565
|
+
})
|
|
566
|
+
).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
|
|
567
|
+
}),
|
|
568
|
+
prompt: (state) => {
|
|
569
|
+
return dedent3`
|
|
570
|
+
${thirdPersonPrompt()}
|
|
571
|
+
|
|
572
|
+
<identity>
|
|
573
|
+
You are a senior analytics strategist who proposes ambitious business questions
|
|
574
|
+
and drafts the SQL needed to answer them. You specialize in identifying ideas
|
|
575
|
+
that combine multiple tables, apply segmentation or time analysis, and surface
|
|
576
|
+
metrics that drive executive decisions.
|
|
577
|
+
</identity>
|
|
578
|
+
|
|
579
|
+
${databaseSchemaPrompt(state)}
|
|
580
|
+
|
|
581
|
+
<instructions>
|
|
582
|
+
- Recommend one or two UNIQUE questions that go beyond simple counts or listings.
|
|
583
|
+
- Favor questions that require joins, aggregates, time comparisons, cohort analysis,
|
|
584
|
+
or window functions.
|
|
585
|
+
- For each question, explain the business reason stakeholders care about it.
|
|
586
|
+
- Provide the complete SQL query that could answer the question in the given schema.
|
|
587
|
+
- Keep result sets scoped with LIMIT clauses (max 50 rows) when returning raw rows.
|
|
588
|
+
- Ensure table/column names match the provided schema exactly.
|
|
589
|
+
- Use columns marked [LowCardinality: ...] to identify meaningful categorical filters or segmentations.
|
|
590
|
+
- Leverage table [rows / size] hints to determine whether to aggregate (large tables) or inspect detailed data (tiny tables).
|
|
591
|
+
- Reference PK/Indexed annotations and the Indexes list to recommend queries that use efficient join/filter paths.
|
|
592
|
+
- Column annotations may expose ranges/null percentages—use them to suggest realistic thresholds or quality checks.
|
|
593
|
+
- Consult <relationship_examples> to anchor your recommendations in the actual join paths between tables.
|
|
594
|
+
- Output only information grounded in the schema/context provided.
|
|
595
|
+
</instructions>
|
|
596
|
+
|
|
597
|
+
<response-format>
|
|
598
|
+
Return valid JSON that satisfies the defined output schema.
|
|
599
|
+
</response-format>
|
|
600
|
+
`;
|
|
609
601
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
} catch {
|
|
649
|
-
continue;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
// packages/text2sql/src/lib/agents/synthesizer.agent.ts
|
|
605
|
+
import { groq as groq5 } from "@ai-sdk/groq";
|
|
606
|
+
import dedent4 from "dedent";
|
|
607
|
+
import { agent as agent5 } from "@deepagents/agent";
|
|
608
|
+
var synthesizerAgent = agent5({
|
|
609
|
+
name: "synthesizer_agent",
|
|
610
|
+
model: groq5("openai/gpt-oss-20b"),
|
|
611
|
+
handoffDescription: "Use this tool to synthesizes the final user-facing response. This agent understands how the user interface works and can tailor the response accordingly.",
|
|
612
|
+
prompt: (state) => {
|
|
613
|
+
const contextInfo = state?.context ?? "No additional context provided.";
|
|
614
|
+
return dedent4`
|
|
615
|
+
<identity>
|
|
616
|
+
You are a data insights companion helping users understand information using clear, everyday language.
|
|
617
|
+
You communicate in a friendly, conversational manner.
|
|
618
|
+
You only see the user's question and the results from internal systems. You do not know how those results were produced, so never reference technical systems or implementation details.
|
|
619
|
+
</identity>
|
|
620
|
+
|
|
621
|
+
<context>
|
|
622
|
+
${contextInfo}
|
|
623
|
+
</context>
|
|
624
|
+
|
|
625
|
+
<response-strategy>
|
|
626
|
+
1. Re-read the user's question, then inspect the <data> provided to understand what it represents.
|
|
627
|
+
2. Translate technical field names into friendly descriptions based on the domain context.
|
|
628
|
+
3. Explain the core insight in 2-4 sentences focused on what the data reveals.
|
|
629
|
+
4. When multiple records are present, highlight only the most relevant ones (max 5) with comparisons or rankings.
|
|
630
|
+
5. If data is empty or contains an error, state that plainly and suggest what to clarify or try next.
|
|
631
|
+
6. Close with an optional follow-up recommendation or next step based on the insights.
|
|
632
|
+
</response-strategy>
|
|
633
|
+
|
|
634
|
+
<guardrails>
|
|
635
|
+
- Never mention technical implementation details, data structures, or internal systems.
|
|
636
|
+
- Keep tone casual, confident, and insight-driven; do not narrate your process.
|
|
637
|
+
- Base every statement strictly on the <data> provided plus the context - no speculation.
|
|
638
|
+
</guardrails>
|
|
639
|
+
`;
|
|
653
640
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
let indexes = [];
|
|
668
|
-
try {
|
|
669
|
-
const indexList = await this.#runQuery(
|
|
670
|
-
`PRAGMA index_list(${tableIdentifier})`
|
|
671
|
-
);
|
|
672
|
-
indexes = await Promise.all(
|
|
673
|
-
indexList.filter((index) => index.name).map(async (index) => {
|
|
674
|
-
const indexName = String(index.name);
|
|
675
|
-
const indexInfo = await this.#runQuery(
|
|
676
|
-
`PRAGMA index_info('${indexName.replace(/'/g, "''")}')`
|
|
677
|
-
);
|
|
678
|
-
const columns = indexInfo.map((col) => col.name).filter((name) => Boolean(name));
|
|
679
|
-
for (const columnName of columns) {
|
|
680
|
-
const column = table.columns.find(
|
|
681
|
-
(col) => col.name === columnName
|
|
682
|
-
);
|
|
683
|
-
if (column) {
|
|
684
|
-
column.isIndexed = true;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
return {
|
|
688
|
-
name: indexName,
|
|
689
|
-
columns,
|
|
690
|
-
unique: index.unique === 1,
|
|
691
|
-
primary: index.origin === "pk",
|
|
692
|
-
type: index.origin ?? void 0
|
|
693
|
-
};
|
|
694
|
-
})
|
|
695
|
-
);
|
|
696
|
-
} catch {
|
|
697
|
-
indexes = [];
|
|
698
|
-
}
|
|
699
|
-
if (indexes.length) {
|
|
700
|
-
table.indexes = indexes;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// packages/text2sql/src/lib/agents/teachables.agent.ts
|
|
644
|
+
import { groq as groq6 } from "@ai-sdk/groq";
|
|
645
|
+
import dedent5 from "dedent";
|
|
646
|
+
import z5 from "zod";
|
|
647
|
+
import { agent as agent6, thirdPersonPrompt as thirdPersonPrompt2 } from "@deepagents/agent";
|
|
648
|
+
|
|
649
|
+
// packages/text2sql/src/lib/teach/xml.ts
|
|
650
|
+
function wrapBlock(tag, children) {
|
|
651
|
+
const content = children.filter((child) => Boolean(child)).join("\n");
|
|
652
|
+
if (!content) {
|
|
653
|
+
return "";
|
|
703
654
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
message: `Analyzing cardinality in ${table.name}...`,
|
|
712
|
-
current: i + 1,
|
|
713
|
-
total
|
|
714
|
-
});
|
|
715
|
-
for (const column of table.columns) {
|
|
716
|
-
const columnIdentifier = this.#quoteSqlIdentifier(column.name);
|
|
717
|
-
const limit = LOW_CARDINALITY_LIMIT + 1;
|
|
718
|
-
const sql = `
|
|
719
|
-
SELECT DISTINCT ${columnIdentifier} AS value
|
|
720
|
-
FROM ${tableIdentifier}
|
|
721
|
-
WHERE ${columnIdentifier} IS NOT NULL
|
|
722
|
-
LIMIT ${limit}
|
|
723
|
-
`;
|
|
724
|
-
let rows = [];
|
|
725
|
-
try {
|
|
726
|
-
rows = await this.#runQuery(sql);
|
|
727
|
-
} catch {
|
|
728
|
-
continue;
|
|
729
|
-
}
|
|
730
|
-
if (!rows.length || rows.length > LOW_CARDINALITY_LIMIT) {
|
|
731
|
-
continue;
|
|
732
|
-
}
|
|
733
|
-
const values = [];
|
|
734
|
-
let shouldSkip = false;
|
|
735
|
-
for (const row of rows) {
|
|
736
|
-
const formatted = this.#normalizeValue(row.value);
|
|
737
|
-
if (formatted == null) {
|
|
738
|
-
shouldSkip = true;
|
|
739
|
-
break;
|
|
740
|
-
}
|
|
741
|
-
values.push(formatted);
|
|
742
|
-
}
|
|
743
|
-
if (shouldSkip || !values.length) {
|
|
744
|
-
continue;
|
|
745
|
-
}
|
|
746
|
-
column.kind = "LowCardinality";
|
|
747
|
-
column.values = values;
|
|
748
|
-
}
|
|
749
|
-
}
|
|
655
|
+
return `<${tag}>
|
|
656
|
+
${indentBlock(content, 2)}
|
|
657
|
+
</${tag}>`;
|
|
658
|
+
}
|
|
659
|
+
function list(tag, values, childTag) {
|
|
660
|
+
if (!values.length) {
|
|
661
|
+
return "";
|
|
750
662
|
}
|
|
751
|
-
|
|
752
|
-
|
|
663
|
+
const children = values.map((value) => leaf(childTag, value)).join("\n");
|
|
664
|
+
return `<${tag}>
|
|
665
|
+
${indentBlock(children, 2)}
|
|
666
|
+
</${tag}>`;
|
|
667
|
+
}
|
|
668
|
+
function leaf(tag, value) {
|
|
669
|
+
const safe = escapeXml(value);
|
|
670
|
+
if (safe.includes("\n")) {
|
|
671
|
+
return `<${tag}>
|
|
672
|
+
${indentBlock(safe, 2)}
|
|
673
|
+
</${tag}>`;
|
|
753
674
|
}
|
|
754
|
-
|
|
755
|
-
|
|
675
|
+
return `<${tag}>${safe}</${tag}>`;
|
|
676
|
+
}
|
|
677
|
+
function indentBlock(text, spaces) {
|
|
678
|
+
if (!text.trim()) {
|
|
679
|
+
return "";
|
|
756
680
|
}
|
|
757
|
-
|
|
758
|
-
|
|
681
|
+
const padding = " ".repeat(spaces);
|
|
682
|
+
return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
|
|
683
|
+
}
|
|
684
|
+
function escapeXml(value) {
|
|
685
|
+
return value.replaceAll(/&/g, "&").replaceAll(/</g, "<").replaceAll(/>/g, ">").replaceAll(/"/g, """).replaceAll(/'/g, "'");
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// packages/text2sql/src/lib/teach/teachables.ts
|
|
689
|
+
function term(name, definition) {
|
|
690
|
+
return {
|
|
691
|
+
type: "term",
|
|
692
|
+
format: () => wrapBlock("term", [leaf("name", name), leaf("definition", definition)])
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
function hint(text) {
|
|
696
|
+
return {
|
|
697
|
+
type: "hint",
|
|
698
|
+
format: () => leaf("hint", text)
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
function guardrail(input) {
|
|
702
|
+
const { rule, reason, action } = input;
|
|
703
|
+
return {
|
|
704
|
+
type: "guardrail",
|
|
705
|
+
format: () => wrapBlock("guardrail", [
|
|
706
|
+
leaf("rule", rule),
|
|
707
|
+
reason ? leaf("reason", reason) : "",
|
|
708
|
+
action ? leaf("action", action) : ""
|
|
709
|
+
])
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function explain(input) {
|
|
713
|
+
const { concept, explanation, therefore } = input;
|
|
714
|
+
return {
|
|
715
|
+
type: "explain",
|
|
716
|
+
format: () => wrapBlock("explanation", [
|
|
717
|
+
leaf("concept", concept),
|
|
718
|
+
leaf("details", explanation),
|
|
719
|
+
therefore ? leaf("therefore", therefore) : ""
|
|
720
|
+
])
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
function example(input) {
|
|
724
|
+
const { question, sql, note } = input;
|
|
725
|
+
return {
|
|
726
|
+
type: "example",
|
|
727
|
+
format: () => wrapBlock("example", [
|
|
728
|
+
leaf("question", question),
|
|
729
|
+
leaf("sql", sql),
|
|
730
|
+
note ? leaf("note", note) : ""
|
|
731
|
+
])
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
function clarification(input) {
|
|
735
|
+
const { when, ask, reason } = input;
|
|
736
|
+
return {
|
|
737
|
+
type: "clarification",
|
|
738
|
+
format: () => wrapBlock("clarification", [
|
|
739
|
+
leaf("when", when),
|
|
740
|
+
leaf("ask", ask),
|
|
741
|
+
leaf("reason", reason)
|
|
742
|
+
])
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function workflow(input) {
|
|
746
|
+
const { task, steps, triggers, notes } = input;
|
|
747
|
+
return {
|
|
748
|
+
type: "workflow",
|
|
749
|
+
format: () => wrapBlock("workflow", [
|
|
750
|
+
leaf("task", task),
|
|
751
|
+
triggers?.length ? list("triggers", triggers, "trigger") : "",
|
|
752
|
+
list("steps", steps, "step"),
|
|
753
|
+
notes ? leaf("notes", notes) : ""
|
|
754
|
+
])
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function quirk(input) {
|
|
758
|
+
const { issue, workaround } = input;
|
|
759
|
+
return {
|
|
760
|
+
type: "quirk",
|
|
761
|
+
format: () => wrapBlock("quirk", [
|
|
762
|
+
leaf("issue", issue),
|
|
763
|
+
leaf("workaround", workaround)
|
|
764
|
+
])
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
function styleGuide(input) {
|
|
768
|
+
const { prefer, never, always } = input;
|
|
769
|
+
return {
|
|
770
|
+
type: "styleGuide",
|
|
771
|
+
format: () => wrapBlock("style_guide", [
|
|
772
|
+
leaf("prefer", prefer),
|
|
773
|
+
always ? leaf("always", always) : "",
|
|
774
|
+
never ? leaf("never", never) : ""
|
|
775
|
+
])
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function analogy(input) {
|
|
779
|
+
const { concept, relationship, insight, therefore, pitfall } = input;
|
|
780
|
+
return {
|
|
781
|
+
type: "analogy",
|
|
782
|
+
format: () => wrapBlock("analogy", [
|
|
783
|
+
list("concepts", concept, "concept"),
|
|
784
|
+
leaf("relationship", relationship),
|
|
785
|
+
insight ? leaf("insight", insight) : "",
|
|
786
|
+
therefore ? leaf("therefore", therefore) : "",
|
|
787
|
+
pitfall ? leaf("pitfall", pitfall) : ""
|
|
788
|
+
])
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
function toInstructions(...teachables) {
|
|
792
|
+
if (!teachables.length) {
|
|
793
|
+
return "";
|
|
759
794
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
return this.#quoteSqlIdentifier(name);
|
|
795
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
796
|
+
for (const teachable of teachables) {
|
|
797
|
+
const existing = grouped.get(teachable.type) ?? [];
|
|
798
|
+
existing.push(teachable);
|
|
799
|
+
grouped.set(teachable.type, existing);
|
|
766
800
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
if (typeof value === "bigint") {
|
|
772
|
-
return Number(value);
|
|
801
|
+
const sections = SECTION_ORDER.map(({ type, tag }) => {
|
|
802
|
+
const items = grouped.get(type);
|
|
803
|
+
if (!items?.length) {
|
|
804
|
+
return "";
|
|
773
805
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
return
|
|
806
|
+
const renderedItems = items.map((item) => item.format().trim()).filter(Boolean).map((item) => indentBlock(item, 2)).join("\n");
|
|
807
|
+
if (!renderedItems.length) {
|
|
808
|
+
return "";
|
|
777
809
|
}
|
|
778
|
-
return
|
|
810
|
+
return `<${tag}>
|
|
811
|
+
${renderedItems}
|
|
812
|
+
</${tag}>`;
|
|
813
|
+
}).filter((section) => Boolean(section));
|
|
814
|
+
if (!sections.length) {
|
|
815
|
+
return "";
|
|
779
816
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
817
|
+
const content = indentBlock(sections.join("\n"), 2);
|
|
818
|
+
return `<teachings>
|
|
819
|
+
${content}
|
|
820
|
+
</teachings>`;
|
|
821
|
+
}
|
|
822
|
+
var SECTION_ORDER = [
|
|
823
|
+
{ type: "guardrail", tag: "guardrails" },
|
|
824
|
+
{ type: "styleGuide", tag: "style_guides" },
|
|
825
|
+
{ type: "hint", tag: "hints" },
|
|
826
|
+
{ type: "clarification", tag: "clarifications" },
|
|
827
|
+
{ type: "workflow", tag: "workflows" },
|
|
828
|
+
{ type: "quirk", tag: "quirks" },
|
|
829
|
+
{ type: "term", tag: "terminology" },
|
|
830
|
+
{ type: "explain", tag: "explanations" },
|
|
831
|
+
{ type: "analogy", tag: "analogies" },
|
|
832
|
+
{ type: "example", tag: "examples" }
|
|
833
|
+
];
|
|
834
|
+
function toTeachables(generated) {
|
|
835
|
+
return generated.map((item) => {
|
|
836
|
+
switch (item.type) {
|
|
837
|
+
case "term":
|
|
838
|
+
return term(item.name, item.definition);
|
|
839
|
+
case "hint":
|
|
840
|
+
return hint(item.text);
|
|
841
|
+
case "guardrail":
|
|
842
|
+
return guardrail({
|
|
843
|
+
rule: item.rule,
|
|
844
|
+
reason: item.reason,
|
|
845
|
+
action: item.action
|
|
846
|
+
});
|
|
847
|
+
case "explain":
|
|
848
|
+
return explain({
|
|
849
|
+
concept: item.concept,
|
|
850
|
+
explanation: item.explanation,
|
|
851
|
+
therefore: item.therefore
|
|
852
|
+
});
|
|
853
|
+
case "example":
|
|
854
|
+
return example({
|
|
855
|
+
question: item.question,
|
|
856
|
+
sql: item.sql,
|
|
857
|
+
note: item.note
|
|
858
|
+
});
|
|
859
|
+
case "clarification":
|
|
860
|
+
return clarification({
|
|
861
|
+
when: item.when,
|
|
862
|
+
ask: item.ask,
|
|
863
|
+
reason: item.reason
|
|
864
|
+
});
|
|
865
|
+
case "workflow":
|
|
866
|
+
return workflow({
|
|
867
|
+
task: item.task,
|
|
868
|
+
steps: item.steps,
|
|
869
|
+
triggers: item.triggers,
|
|
870
|
+
notes: item.notes
|
|
871
|
+
});
|
|
872
|
+
case "quirk":
|
|
873
|
+
return quirk({
|
|
874
|
+
issue: item.issue,
|
|
875
|
+
workaround: item.workaround
|
|
876
|
+
});
|
|
877
|
+
case "styleGuide":
|
|
878
|
+
return styleGuide({
|
|
879
|
+
prefer: item.prefer,
|
|
880
|
+
never: item.never,
|
|
881
|
+
always: item.always
|
|
882
|
+
});
|
|
883
|
+
case "analogy":
|
|
884
|
+
return analogy({
|
|
885
|
+
concept: item.concept,
|
|
886
|
+
relationship: item.relationship,
|
|
887
|
+
insight: item.insight,
|
|
888
|
+
therefore: item.therefore,
|
|
889
|
+
pitfall: item.pitfall
|
|
890
|
+
});
|
|
789
891
|
}
|
|
790
|
-
|
|
791
|
-
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
function userProfile(input) {
|
|
895
|
+
return {
|
|
896
|
+
type: "user_profile",
|
|
897
|
+
format: () => {
|
|
898
|
+
return "";
|
|
792
899
|
}
|
|
793
|
-
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// packages/text2sql/src/lib/agents/teachables.agent.ts
|
|
904
|
+
var teachableSchema = z5.discriminatedUnion("type", [
|
|
905
|
+
z5.object({
|
|
906
|
+
type: z5.literal("term"),
|
|
907
|
+
name: z5.string(),
|
|
908
|
+
definition: z5.string()
|
|
909
|
+
}),
|
|
910
|
+
z5.object({
|
|
911
|
+
type: z5.literal("hint"),
|
|
912
|
+
text: z5.string()
|
|
913
|
+
}),
|
|
914
|
+
z5.object({
|
|
915
|
+
type: z5.literal("guardrail"),
|
|
916
|
+
rule: z5.string(),
|
|
917
|
+
reason: z5.string().optional(),
|
|
918
|
+
action: z5.string().optional()
|
|
919
|
+
}),
|
|
920
|
+
z5.object({
|
|
921
|
+
type: z5.literal("explain"),
|
|
922
|
+
concept: z5.string(),
|
|
923
|
+
explanation: z5.string(),
|
|
924
|
+
therefore: z5.string().optional()
|
|
925
|
+
}),
|
|
926
|
+
z5.object({
|
|
927
|
+
type: z5.literal("example"),
|
|
928
|
+
question: z5.string(),
|
|
929
|
+
sql: z5.string(),
|
|
930
|
+
note: z5.string().optional()
|
|
931
|
+
}),
|
|
932
|
+
z5.object({
|
|
933
|
+
type: z5.literal("clarification"),
|
|
934
|
+
when: z5.string(),
|
|
935
|
+
ask: z5.string(),
|
|
936
|
+
reason: z5.string()
|
|
937
|
+
}),
|
|
938
|
+
z5.object({
|
|
939
|
+
type: z5.literal("workflow"),
|
|
940
|
+
task: z5.string(),
|
|
941
|
+
steps: z5.array(z5.string()).min(2),
|
|
942
|
+
triggers: z5.array(z5.string()).optional(),
|
|
943
|
+
notes: z5.string().optional()
|
|
944
|
+
}),
|
|
945
|
+
z5.object({
|
|
946
|
+
type: z5.literal("quirk"),
|
|
947
|
+
issue: z5.string(),
|
|
948
|
+
workaround: z5.string()
|
|
949
|
+
}),
|
|
950
|
+
z5.object({
|
|
951
|
+
type: z5.literal("styleGuide"),
|
|
952
|
+
prefer: z5.string(),
|
|
953
|
+
never: z5.string().optional(),
|
|
954
|
+
always: z5.string().optional()
|
|
955
|
+
}),
|
|
956
|
+
z5.object({
|
|
957
|
+
type: z5.literal("analogy"),
|
|
958
|
+
concept: z5.array(z5.string()).min(2),
|
|
959
|
+
relationship: z5.string(),
|
|
960
|
+
insight: z5.string().optional(),
|
|
961
|
+
therefore: z5.string().optional(),
|
|
962
|
+
pitfall: z5.string().optional()
|
|
963
|
+
})
|
|
964
|
+
]);
|
|
965
|
+
var teachablesAuthorAgent = agent6({
|
|
966
|
+
name: "teachables-author",
|
|
967
|
+
model: groq6("openai/gpt-oss-20b"),
|
|
968
|
+
output: z5.object({
|
|
969
|
+
teachables: z5.array(teachableSchema).min(3).max(10).describe(
|
|
970
|
+
"A concise, high-value set of teachables grounded in the provided schema."
|
|
971
|
+
)
|
|
972
|
+
}),
|
|
973
|
+
prompt: (state) => dedent5`
|
|
974
|
+
${thirdPersonPrompt2()}
|
|
975
|
+
|
|
976
|
+
<identity>
|
|
977
|
+
You design "teachables" for a Text2SQL system. Teachables become structured XML instructions.
|
|
978
|
+
Choose only high-impact items that improve accuracy, safety, or clarity for this database.
|
|
979
|
+
</identity>
|
|
980
|
+
|
|
981
|
+
${databaseSchemaPrompt(state)}
|
|
982
|
+
|
|
983
|
+
<teachables_catalog>
|
|
984
|
+
term: name + definition for domain vocabulary.
|
|
985
|
+
hint: behavioral rule/constraint to apply by default.
|
|
986
|
+
guardrail: hard safety/performance boundary with action and optional reason.
|
|
987
|
+
explain: deeper concept metaphor/explanation (+ optional therefore).
|
|
988
|
+
example: question + SQL (+ optional note).
|
|
989
|
+
clarification: when/ask/reason to prompt the user before querying.
|
|
990
|
+
workflow: task + ordered steps (+ optional triggers/notes).
|
|
991
|
+
quirk: data edge case with workaround.
|
|
992
|
+
styleGuide: prefer/never/always guidance for SQL output.
|
|
993
|
+
analogy: comparison of two concepts with relationship (+ optional insight/therefore/pitfall).
|
|
994
|
+
</teachables_catalog>
|
|
995
|
+
|
|
996
|
+
<instructions>
|
|
997
|
+
- Ground everything in the provided schema/context; do not invent tables/columns.
|
|
998
|
+
- Prefer guardrails + clarifications for performance, safety, and ambiguity.
|
|
999
|
+
- Use examples only when a clear, schema-valid pattern is evident.
|
|
1000
|
+
- Keep the set lean (3-10 items) and non-duplicative; combine overlapping ideas.
|
|
1001
|
+
- Return JSON that satisfies the output schema; do not wrap in prose.
|
|
1002
|
+
</instructions>
|
|
1003
|
+
`
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
// packages/text2sql/src/lib/history/history.ts
|
|
1007
|
+
var History = class {
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
// packages/text2sql/src/lib/memory/user-profile.ts
|
|
1011
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
1012
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1013
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
1014
|
+
import path2 from "node:path";
|
|
1015
|
+
var UserProfileStore = class {
|
|
1016
|
+
constructor(userId) {
|
|
1017
|
+
this.userId = userId;
|
|
1018
|
+
const safeUserId = userId.replace(/[^a-z0-9]/gi, "_").toLowerCase();
|
|
1019
|
+
this.path = path2.join(tmpdir2(), `user-profile-${safeUserId}.json`);
|
|
794
1020
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1021
|
+
path;
|
|
1022
|
+
/**
|
|
1023
|
+
* Retrieve the full user profile data.
|
|
1024
|
+
*/
|
|
1025
|
+
async get() {
|
|
1026
|
+
if (existsSync2(this.path)) {
|
|
1027
|
+
try {
|
|
1028
|
+
const content = await readFile2(this.path, "utf-8");
|
|
1029
|
+
return JSON.parse(content);
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
console.error("Failed to read user profile:", error);
|
|
1032
|
+
}
|
|
798
1033
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
1034
|
+
return {
|
|
1035
|
+
items: [],
|
|
1036
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1037
|
+
};
|
|
803
1038
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
}
|
|
811
|
-
if (typeof value === "number" || typeof value === "bigint") {
|
|
812
|
-
return String(value);
|
|
813
|
-
}
|
|
814
|
-
if (typeof value === "boolean") {
|
|
815
|
-
return value ? "true" : "false";
|
|
816
|
-
}
|
|
817
|
-
if (value instanceof Date) {
|
|
818
|
-
return value.toISOString();
|
|
819
|
-
}
|
|
820
|
-
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
821
|
-
return value.toString("utf-8");
|
|
822
|
-
}
|
|
823
|
-
return null;
|
|
1039
|
+
/**
|
|
1040
|
+
* Save the user profile data.
|
|
1041
|
+
*/
|
|
1042
|
+
async save(data) {
|
|
1043
|
+
data.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
1044
|
+
await writeFile2(this.path, JSON.stringify(data, null, 2), "utf-8");
|
|
824
1045
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
throw new Error(
|
|
834
|
-
"Sqlite adapter execute() must return an array of rows or an object with a rows array when introspecting."
|
|
1046
|
+
/**
|
|
1047
|
+
* Add an item to the profile.
|
|
1048
|
+
*/
|
|
1049
|
+
async add(type, text) {
|
|
1050
|
+
const data = await this.get();
|
|
1051
|
+
const exists = data.items.some(
|
|
1052
|
+
(item) => item.type === type && item.text === text
|
|
835
1053
|
);
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (!info) {
|
|
840
|
-
return { dialect: "sqlite" };
|
|
841
|
-
}
|
|
842
|
-
if (typeof info === "function") {
|
|
843
|
-
return info();
|
|
1054
|
+
if (!exists) {
|
|
1055
|
+
data.items.push({ type, text });
|
|
1056
|
+
await this.save(data);
|
|
844
1057
|
}
|
|
845
|
-
return info;
|
|
846
1058
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
import { tmpdir } from "node:os";
|
|
857
|
-
import path from "node:path";
|
|
858
|
-
import z2 from "zod";
|
|
859
|
-
import {
|
|
860
|
-
agent as agent2,
|
|
861
|
-
generate,
|
|
862
|
-
toState as toState2,
|
|
863
|
-
user
|
|
864
|
-
} from "@deepagents/agent";
|
|
865
|
-
var BriefCache = class {
|
|
866
|
-
path;
|
|
867
|
-
constructor(watermark) {
|
|
868
|
-
const hash = createHash("md5").update(watermark).digest("hex");
|
|
869
|
-
this.path = path.join(tmpdir(), `db-brief-${hash}.txt`);
|
|
1059
|
+
/**
|
|
1060
|
+
* Remove a specific item from the profile.
|
|
1061
|
+
*/
|
|
1062
|
+
async remove(type, text) {
|
|
1063
|
+
const data = await this.get();
|
|
1064
|
+
const filtered = data.items.filter((item) => {
|
|
1065
|
+
return !(item.type === type && item.text === text);
|
|
1066
|
+
});
|
|
1067
|
+
await this.save({ ...data, items: filtered });
|
|
870
1068
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1069
|
+
/**
|
|
1070
|
+
* Clear the entire profile.
|
|
1071
|
+
*/
|
|
1072
|
+
async clear() {
|
|
1073
|
+
await this.save({ items: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() });
|
|
876
1074
|
}
|
|
877
|
-
|
|
878
|
-
|
|
1075
|
+
/**
|
|
1076
|
+
* Get the formatted XML string for the system prompt.
|
|
1077
|
+
*/
|
|
1078
|
+
async toXml() {
|
|
1079
|
+
const data = await this.get();
|
|
1080
|
+
return toUserProfileXml(data.items);
|
|
879
1081
|
}
|
|
880
1082
|
};
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
prompt: (state) => dedent`
|
|
885
|
-
<identity>
|
|
886
|
-
You are a database analyst expert. Your job is to understand what a database represents and provide business context about it.
|
|
887
|
-
You have READ-ONLY access to the database.
|
|
888
|
-
</identity>
|
|
889
|
-
|
|
890
|
-
${databaseSchemaPrompt(state)}
|
|
891
|
-
|
|
892
|
-
<instructions>
|
|
893
|
-
Write a business context that helps another agent answer questions accurately.
|
|
894
|
-
|
|
895
|
-
For EACH table, do queries ONE AT A TIME:
|
|
896
|
-
1. SELECT COUNT(*) to get row count
|
|
897
|
-
2. SELECT * LIMIT 3 to see sample data
|
|
898
|
-
|
|
899
|
-
Then write a report with:
|
|
900
|
-
- What business this database is for
|
|
901
|
-
- For each table: purpose, row count, and example of what the data looks like
|
|
902
|
-
|
|
903
|
-
Include concrete examples like "Track prices are $0.99", "Customer names like 'Lu\u00eds Gon\u00e7alves'", etc.
|
|
904
|
-
|
|
905
|
-
Keep it 400-600 words, conversational style.
|
|
906
|
-
</instructions>
|
|
907
|
-
`,
|
|
908
|
-
tools: {
|
|
909
|
-
query_database: tool2({
|
|
910
|
-
description: "Execute a SELECT query to explore the database and gather insights.",
|
|
911
|
-
inputSchema: z2.object({
|
|
912
|
-
sql: z2.string().describe("The SELECT query to execute"),
|
|
913
|
-
purpose: z2.string().describe("What insight you are trying to gather with this query")
|
|
914
|
-
}),
|
|
915
|
-
execute: ({ sql }, options) => {
|
|
916
|
-
const state = toState2(options);
|
|
917
|
-
return state.execute(sql);
|
|
918
|
-
}
|
|
919
|
-
})
|
|
1083
|
+
function toUserProfileXml(items) {
|
|
1084
|
+
if (items.length === 0) {
|
|
1085
|
+
return "";
|
|
920
1086
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
)
|
|
929
|
-
],
|
|
930
|
-
{ introspection }
|
|
931
|
-
);
|
|
932
|
-
await cache.set(text);
|
|
933
|
-
return text;
|
|
934
|
-
}
|
|
935
|
-
async function generateBrief(introspection, cache) {
|
|
936
|
-
const brief = await cache.get();
|
|
937
|
-
if (!brief) {
|
|
938
|
-
return runAndCache(introspection, cache);
|
|
1087
|
+
const facts = items.filter((i) => i.type === "fact");
|
|
1088
|
+
const preferences = items.filter((i) => i.type === "preference");
|
|
1089
|
+
const present = items.filter((i) => i.type === "present");
|
|
1090
|
+
const sections = [];
|
|
1091
|
+
if (facts.length > 0) {
|
|
1092
|
+
const lines = facts.map((f) => `- ${f.text}`);
|
|
1093
|
+
sections.push(wrapBlock2("identity", lines));
|
|
939
1094
|
}
|
|
940
|
-
|
|
1095
|
+
if (preferences.length > 0) {
|
|
1096
|
+
const lines = preferences.map((p) => `- ${p.text}`);
|
|
1097
|
+
sections.push(wrapBlock2("preferences", lines));
|
|
1098
|
+
}
|
|
1099
|
+
if (present.length > 0) {
|
|
1100
|
+
const lines = present.map((c) => `- ${c.text}`);
|
|
1101
|
+
sections.push(wrapBlock2("working_context", lines));
|
|
1102
|
+
}
|
|
1103
|
+
if (sections.length === 0) return "";
|
|
1104
|
+
return `<user_profile>
|
|
1105
|
+
${indentBlock2(sections.join("\n"), 2)}
|
|
1106
|
+
</user_profile>`;
|
|
941
1107
|
}
|
|
942
|
-
function
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
cache: "forced",
|
|
952
|
-
brief
|
|
953
|
-
}
|
|
954
|
-
});
|
|
955
|
-
setState({ context: brief });
|
|
956
|
-
} else {
|
|
957
|
-
let brief = await state.cache.get();
|
|
958
|
-
if (!brief) {
|
|
959
|
-
writer.write({
|
|
960
|
-
type: "data-brief-agent",
|
|
961
|
-
data: {
|
|
962
|
-
cache: "miss"
|
|
963
|
-
}
|
|
964
|
-
});
|
|
965
|
-
brief = await runAndCache(state.introspection, state.cache);
|
|
966
|
-
writer.write({
|
|
967
|
-
type: "data-brief-agent",
|
|
968
|
-
data: {
|
|
969
|
-
cache: "new",
|
|
970
|
-
brief
|
|
971
|
-
}
|
|
972
|
-
});
|
|
973
|
-
} else {
|
|
974
|
-
writer.write({
|
|
975
|
-
type: "data-brief-agent",
|
|
976
|
-
data: {
|
|
977
|
-
cache: "hit",
|
|
978
|
-
brief
|
|
979
|
-
}
|
|
980
|
-
});
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
});
|
|
985
|
-
};
|
|
1108
|
+
function wrapBlock2(tag, lines) {
|
|
1109
|
+
if (lines.length === 0) return "";
|
|
1110
|
+
return `<${tag}>
|
|
1111
|
+
${indentBlock2(lines.join("\n"), 2)}
|
|
1112
|
+
</${tag}>`;
|
|
1113
|
+
}
|
|
1114
|
+
function indentBlock2(text, spaces) {
|
|
1115
|
+
const padding = " ".repeat(spaces);
|
|
1116
|
+
return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
|
|
986
1117
|
}
|
|
987
1118
|
|
|
988
|
-
// packages/text2sql/src/lib/
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
<sql>
|
|
1002
|
-
${state?.sql}
|
|
1003
|
-
</sql>
|
|
1004
|
-
`,
|
|
1005
|
-
output: z3.object({
|
|
1006
|
-
explanation: z3.string().describe("The explanation of the SQL query.")
|
|
1007
|
-
})
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
// packages/text2sql/src/lib/agents/suggestions.agents.ts
|
|
1011
|
-
import { groq as groq4 } from "@ai-sdk/groq";
|
|
1012
|
-
import dedent3 from "dedent";
|
|
1013
|
-
import z4 from "zod";
|
|
1014
|
-
import { agent as agent4, thirdPersonPrompt } from "@deepagents/agent";
|
|
1015
|
-
var suggestionsAgent = agent4({
|
|
1016
|
-
name: "text2sql-suggestions",
|
|
1017
|
-
model: groq4("openai/gpt-oss-20b"),
|
|
1018
|
-
output: z4.object({
|
|
1019
|
-
suggestions: z4.array(
|
|
1020
|
-
z4.object({
|
|
1021
|
-
question: z4.string().describe("A complex, high-impact business question."),
|
|
1022
|
-
sql: z4.string().describe("The SQL statement needed to answer the question."),
|
|
1023
|
-
businessValue: z4.string().describe("Why the question matters to stakeholders.")
|
|
1024
|
-
})
|
|
1025
|
-
).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
|
|
1026
|
-
}),
|
|
1027
|
-
prompt: (state) => {
|
|
1028
|
-
return dedent3`
|
|
1029
|
-
${thirdPersonPrompt()}
|
|
1030
|
-
|
|
1031
|
-
<identity>
|
|
1032
|
-
You are a senior analytics strategist who proposes ambitious business questions
|
|
1033
|
-
and drafts the SQL needed to answer them. You specialize in identifying ideas
|
|
1034
|
-
that combine multiple tables, apply segmentation or time analysis, and surface
|
|
1035
|
-
metrics that drive executive decisions.
|
|
1036
|
-
</identity>
|
|
1037
|
-
|
|
1038
|
-
${databaseSchemaPrompt(state)}
|
|
1039
|
-
|
|
1040
|
-
<instructions>
|
|
1041
|
-
- Recommend one or two UNIQUE questions that go beyond simple counts or listings.
|
|
1042
|
-
- Favor questions that require joins, aggregates, time comparisons, cohort analysis,
|
|
1043
|
-
or window functions.
|
|
1044
|
-
- For each question, explain the business reason stakeholders care about it.
|
|
1045
|
-
- Provide the complete SQL query that could answer the question in the given schema.
|
|
1046
|
-
- Keep result sets scoped with LIMIT clauses (max 50 rows) when returning raw rows.
|
|
1047
|
-
- Ensure table/column names match the provided schema exactly.
|
|
1048
|
-
- Use columns marked [LowCardinality: ...] to identify meaningful categorical filters or segmentations.
|
|
1049
|
-
- Leverage table [rows / size] hints to determine whether to aggregate (large tables) or inspect detailed data (tiny tables).
|
|
1050
|
-
- Reference PK/Indexed annotations and the Indexes list to recommend queries that use efficient join/filter paths.
|
|
1051
|
-
- Column annotations may expose ranges/null percentages—use them to suggest realistic thresholds or quality checks.
|
|
1052
|
-
- Consult <relationship_examples> to anchor your recommendations in the actual join paths between tables.
|
|
1053
|
-
- Output only information grounded in the schema/context provided.
|
|
1054
|
-
</instructions>
|
|
1055
|
-
|
|
1056
|
-
<response-format>
|
|
1057
|
-
Return valid JSON that satisfies the defined output schema.
|
|
1058
|
-
</response-format>
|
|
1059
|
-
`;
|
|
1119
|
+
// packages/text2sql/src/lib/sql.ts
|
|
1120
|
+
var Text2Sql = class {
|
|
1121
|
+
#config;
|
|
1122
|
+
#introspectionCache;
|
|
1123
|
+
constructor(config) {
|
|
1124
|
+
this.#config = {
|
|
1125
|
+
...config,
|
|
1126
|
+
instructions: config.instructions ?? [],
|
|
1127
|
+
tools: config.tools ?? {}
|
|
1128
|
+
};
|
|
1129
|
+
this.#introspectionCache = new JsonCache(
|
|
1130
|
+
"introspection" + config.version
|
|
1131
|
+
);
|
|
1060
1132
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
handoffDescription: "Use this tool to synthesizes the final user-facing response. This agent understands how the user interface works and can tailor the response accordingly.",
|
|
1071
|
-
prompt: (state) => {
|
|
1072
|
-
const contextInfo = state?.context ?? "No additional context provided.";
|
|
1073
|
-
return dedent4`
|
|
1074
|
-
<identity>
|
|
1075
|
-
You are a data insights companion helping users understand information using clear, everyday language.
|
|
1076
|
-
You communicate in a friendly, conversational manner.
|
|
1077
|
-
You only see the user's question and the results from internal systems. You do not know how those results were produced, so never reference technical systems or implementation details.
|
|
1078
|
-
</identity>
|
|
1079
|
-
|
|
1080
|
-
<context>
|
|
1081
|
-
${contextInfo}
|
|
1082
|
-
</context>
|
|
1083
|
-
|
|
1084
|
-
<response-strategy>
|
|
1085
|
-
1. Re-read the user's question, then inspect the <data> provided to understand what it represents.
|
|
1086
|
-
2. Translate technical field names into friendly descriptions based on the domain context.
|
|
1087
|
-
3. Explain the core insight in 2-4 sentences focused on what the data reveals.
|
|
1088
|
-
4. When multiple records are present, highlight only the most relevant ones (max 5) with comparisons or rankings.
|
|
1089
|
-
5. If data is empty or contains an error, state that plainly and suggest what to clarify or try next.
|
|
1090
|
-
6. Close with an optional follow-up recommendation or next step based on the insights.
|
|
1091
|
-
</response-strategy>
|
|
1092
|
-
|
|
1093
|
-
<guardrails>
|
|
1094
|
-
- Never mention technical implementation details, data structures, or internal systems.
|
|
1095
|
-
- Keep tone casual, confident, and insight-driven; do not narrate your process.
|
|
1096
|
-
- Base every statement strictly on the <data> provided plus the context - no speculation.
|
|
1097
|
-
</guardrails>
|
|
1098
|
-
`;
|
|
1133
|
+
async #getSql(stream2) {
|
|
1134
|
+
const chunks = await Array.fromAsync(
|
|
1135
|
+
stream2
|
|
1136
|
+
);
|
|
1137
|
+
const sql = chunks.at(-1);
|
|
1138
|
+
if (sql && sql.type === "data-text-delta") {
|
|
1139
|
+
return sql.data.text;
|
|
1140
|
+
}
|
|
1141
|
+
throw new Error("No SQL generated");
|
|
1099
1142
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
// packages/text2sql/src/lib/teach/xml.ts
|
|
1109
|
-
function wrapBlock(tag, children) {
|
|
1110
|
-
const content = children.filter((child) => Boolean(child)).join("\n");
|
|
1111
|
-
if (!content) {
|
|
1112
|
-
return "";
|
|
1143
|
+
async explain(sql) {
|
|
1144
|
+
const { experimental_output } = await generate2(
|
|
1145
|
+
explainerAgent,
|
|
1146
|
+
[user2("Explain this SQL.")],
|
|
1147
|
+
{ sql }
|
|
1148
|
+
);
|
|
1149
|
+
return experimental_output.explanation;
|
|
1113
1150
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
return
|
|
1151
|
+
async toSql(input) {
|
|
1152
|
+
const [introspection, adapterInfo] = await Promise.all([
|
|
1153
|
+
this.index(),
|
|
1154
|
+
this.#config.adapter.info()
|
|
1155
|
+
]);
|
|
1156
|
+
const context = await generateBrief(introspection, this.#config.cache);
|
|
1157
|
+
return {
|
|
1158
|
+
generate: async () => {
|
|
1159
|
+
const { experimental_output: output } = await generate2(
|
|
1160
|
+
text2sqlOnly,
|
|
1161
|
+
[user2(input)],
|
|
1162
|
+
{
|
|
1163
|
+
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1164
|
+
context,
|
|
1165
|
+
introspection,
|
|
1166
|
+
teachings: toInstructions(...this.#config.instructions)
|
|
1167
|
+
}
|
|
1168
|
+
);
|
|
1169
|
+
return output.sql;
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1121
1172
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1173
|
+
async inspect() {
|
|
1174
|
+
const [introspection, adapterInfo] = await Promise.all([
|
|
1175
|
+
this.index(),
|
|
1176
|
+
this.#config.adapter.info()
|
|
1177
|
+
]);
|
|
1178
|
+
const context = await generateBrief(introspection, this.#config.cache);
|
|
1179
|
+
return text2sqlOnly.instructions({
|
|
1180
|
+
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1181
|
+
context,
|
|
1182
|
+
introspection,
|
|
1183
|
+
teachings: toInstructions(...this.#config.instructions)
|
|
1184
|
+
});
|
|
1133
1185
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
function indentBlock(text, spaces) {
|
|
1137
|
-
if (!text.trim()) {
|
|
1138
|
-
return "";
|
|
1186
|
+
instruct(...dataset) {
|
|
1187
|
+
this.#config.instructions.push(...dataset);
|
|
1139
1188
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1189
|
+
async index(options) {
|
|
1190
|
+
const cached = await this.#introspectionCache.read();
|
|
1191
|
+
if (cached) {
|
|
1192
|
+
return cached;
|
|
1193
|
+
}
|
|
1194
|
+
const introspection = await this.#config.adapter.introspect(options);
|
|
1195
|
+
await this.#introspectionCache.write(introspection);
|
|
1196
|
+
return introspection;
|
|
1197
|
+
}
|
|
1198
|
+
async teach(input) {
|
|
1199
|
+
const [introspection, adapterInfo] = await Promise.all([
|
|
1200
|
+
this.index(),
|
|
1201
|
+
this.#config.adapter.info()
|
|
1202
|
+
]);
|
|
1203
|
+
const context = await generateBrief(introspection, this.#config.cache);
|
|
1204
|
+
const { experimental_output } = await generate2(
|
|
1205
|
+
teachablesAuthorAgent,
|
|
1206
|
+
[user2(input)],
|
|
1207
|
+
{
|
|
1208
|
+
introspection,
|
|
1209
|
+
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1210
|
+
context
|
|
1211
|
+
}
|
|
1212
|
+
);
|
|
1213
|
+
const teachables = toTeachables(experimental_output.teachables);
|
|
1214
|
+
this.#config.instructions.push(...teachables);
|
|
1215
|
+
return {
|
|
1216
|
+
teachables,
|
|
1217
|
+
teachings: toInstructions(...this.#config.instructions)
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
async tag(input) {
|
|
1221
|
+
const [introspection, adapterInfo] = await Promise.all([
|
|
1222
|
+
this.index(),
|
|
1223
|
+
this.#config.adapter.info()
|
|
1224
|
+
]);
|
|
1225
|
+
const pipeline = pipe(
|
|
1226
|
+
{
|
|
1227
|
+
input,
|
|
1228
|
+
adapter: this.#config.adapter,
|
|
1229
|
+
cache: this.#config.cache,
|
|
1230
|
+
introspection,
|
|
1231
|
+
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1232
|
+
messages: [user2(input)],
|
|
1233
|
+
renderingTools: this.#config.tools || {},
|
|
1234
|
+
teachings: toInstructions(...this.#config.instructions)
|
|
1235
|
+
},
|
|
1236
|
+
toBrief(),
|
|
1237
|
+
async (state, update) => {
|
|
1238
|
+
const { experimental_output: output } = await generate2(
|
|
1239
|
+
text2sqlOnly,
|
|
1240
|
+
state.messages,
|
|
1241
|
+
state
|
|
1242
|
+
);
|
|
1243
|
+
update({
|
|
1244
|
+
messages: [
|
|
1245
|
+
user2(
|
|
1246
|
+
dedent6`
|
|
1247
|
+
Based on the data provided, please explain in clear, conversational language what insights this reveals.
|
|
1146
1248
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
action ? leaf("action", action) : ""
|
|
1168
|
-
])
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
function explain(input) {
|
|
1172
|
-
const { concept, explanation, therefore } = input;
|
|
1173
|
-
return {
|
|
1174
|
-
type: "explain",
|
|
1175
|
-
format: () => wrapBlock("explanation", [
|
|
1176
|
-
leaf("concept", concept),
|
|
1177
|
-
leaf("details", explanation),
|
|
1178
|
-
therefore ? leaf("therefore", therefore) : ""
|
|
1179
|
-
])
|
|
1180
|
-
};
|
|
1181
|
-
}
|
|
1182
|
-
function example(input) {
|
|
1183
|
-
const { question, sql, note } = input;
|
|
1184
|
-
return {
|
|
1185
|
-
type: "example",
|
|
1186
|
-
format: () => wrapBlock("example", [
|
|
1187
|
-
leaf("question", question),
|
|
1188
|
-
leaf("sql", sql),
|
|
1189
|
-
note ? leaf("note", note) : ""
|
|
1190
|
-
])
|
|
1191
|
-
};
|
|
1192
|
-
}
|
|
1193
|
-
function clarification(input) {
|
|
1194
|
-
const { when, ask, reason } = input;
|
|
1195
|
-
return {
|
|
1196
|
-
type: "clarification",
|
|
1197
|
-
format: () => wrapBlock("clarification", [
|
|
1198
|
-
leaf("when", when),
|
|
1199
|
-
leaf("ask", ask),
|
|
1200
|
-
leaf("reason", reason)
|
|
1201
|
-
])
|
|
1202
|
-
};
|
|
1203
|
-
}
|
|
1204
|
-
function workflow(input) {
|
|
1205
|
-
const { task, steps, triggers, notes } = input;
|
|
1206
|
-
return {
|
|
1207
|
-
type: "workflow",
|
|
1208
|
-
format: () => wrapBlock("workflow", [
|
|
1209
|
-
leaf("task", task),
|
|
1210
|
-
triggers?.length ? list("triggers", triggers, "trigger") : "",
|
|
1211
|
-
list("steps", steps, "step"),
|
|
1212
|
-
notes ? leaf("notes", notes) : ""
|
|
1213
|
-
])
|
|
1214
|
-
};
|
|
1215
|
-
}
|
|
1216
|
-
function quirk(input) {
|
|
1217
|
-
const { issue, workaround } = input;
|
|
1218
|
-
return {
|
|
1219
|
-
type: "quirk",
|
|
1220
|
-
format: () => wrapBlock("quirk", [
|
|
1221
|
-
leaf("issue", issue),
|
|
1222
|
-
leaf("workaround", workaround)
|
|
1223
|
-
])
|
|
1224
|
-
};
|
|
1225
|
-
}
|
|
1226
|
-
function styleGuide(input) {
|
|
1227
|
-
const { prefer, never, always } = input;
|
|
1228
|
-
return {
|
|
1229
|
-
type: "styleGuide",
|
|
1230
|
-
format: () => wrapBlock("style_guide", [
|
|
1231
|
-
leaf("prefer", prefer),
|
|
1232
|
-
always ? leaf("always", always) : "",
|
|
1233
|
-
never ? leaf("never", never) : ""
|
|
1234
|
-
])
|
|
1235
|
-
};
|
|
1236
|
-
}
|
|
1237
|
-
function analogy(input) {
|
|
1238
|
-
const { concept, relationship, insight, therefore, pitfall } = input;
|
|
1239
|
-
return {
|
|
1240
|
-
type: "analogy",
|
|
1241
|
-
format: () => wrapBlock("analogy", [
|
|
1242
|
-
list("concepts", concept, "concept"),
|
|
1243
|
-
leaf("relationship", relationship),
|
|
1244
|
-
insight ? leaf("insight", insight) : "",
|
|
1245
|
-
therefore ? leaf("therefore", therefore) : "",
|
|
1246
|
-
pitfall ? leaf("pitfall", pitfall) : ""
|
|
1247
|
-
])
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
|
-
function toInstructions(...teachables) {
|
|
1251
|
-
if (!teachables.length) {
|
|
1252
|
-
return "";
|
|
1249
|
+
<user_question>${state.input}</user_question>
|
|
1250
|
+
<data>${JSON.stringify(this.#config.adapter.execute(output.sql))}</data>
|
|
1251
|
+
`
|
|
1252
|
+
)
|
|
1253
|
+
]
|
|
1254
|
+
});
|
|
1255
|
+
return output.sql;
|
|
1256
|
+
},
|
|
1257
|
+
synthesizerAgent
|
|
1258
|
+
);
|
|
1259
|
+
const stream2 = pipeline();
|
|
1260
|
+
return {
|
|
1261
|
+
generate: async () => {
|
|
1262
|
+
const sql = await this.#getSql(stream2);
|
|
1263
|
+
return sql;
|
|
1264
|
+
},
|
|
1265
|
+
stream: () => {
|
|
1266
|
+
return stream2;
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1253
1269
|
}
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1270
|
+
async suggest() {
|
|
1271
|
+
const [introspection, adapterInfo] = await Promise.all([
|
|
1272
|
+
this.index(),
|
|
1273
|
+
this.#config.adapter.info()
|
|
1274
|
+
]);
|
|
1275
|
+
const context = await generateBrief(introspection, this.#config.cache);
|
|
1276
|
+
const { experimental_output: output } = await generate2(
|
|
1277
|
+
suggestionsAgent,
|
|
1278
|
+
[
|
|
1279
|
+
user2(
|
|
1280
|
+
"Suggest high-impact business questions and matching SQL queries for this database."
|
|
1281
|
+
)
|
|
1282
|
+
],
|
|
1283
|
+
{
|
|
1284
|
+
introspection,
|
|
1285
|
+
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1286
|
+
context
|
|
1287
|
+
}
|
|
1288
|
+
);
|
|
1289
|
+
return output.suggestions;
|
|
1259
1290
|
}
|
|
1260
|
-
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1291
|
+
async single(input) {
|
|
1292
|
+
const [introspection, adapterInfo] = await Promise.all([
|
|
1293
|
+
this.index(),
|
|
1294
|
+
this.#config.adapter.info()
|
|
1295
|
+
]);
|
|
1296
|
+
return stream(
|
|
1297
|
+
text2sqlMonolith.clone({
|
|
1298
|
+
tools: {
|
|
1299
|
+
...text2sqlMonolith.handoff.tools,
|
|
1300
|
+
...this.#config.tools
|
|
1301
|
+
}
|
|
1302
|
+
}),
|
|
1303
|
+
[user2(input)],
|
|
1304
|
+
{
|
|
1305
|
+
teachings: toInstructions(...this.#config.instructions),
|
|
1306
|
+
adapter: this.#config.adapter,
|
|
1307
|
+
introspection,
|
|
1308
|
+
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1309
|
+
context: await generateBrief(introspection, this.#config.cache),
|
|
1310
|
+
renderingTools: this.#config.tools || {}
|
|
1311
|
+
}
|
|
1312
|
+
);
|
|
1275
1313
|
}
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1314
|
+
async chat(messages, params, model) {
|
|
1315
|
+
const [introspection, adapterInfo] = await Promise.all([
|
|
1316
|
+
this.index({ onProgress: console.log }),
|
|
1317
|
+
this.#config.adapter.info()
|
|
1318
|
+
]);
|
|
1319
|
+
const chat = await this.#config.history.upsertChat({
|
|
1320
|
+
id: params.chatId,
|
|
1321
|
+
userId: params.userId,
|
|
1322
|
+
title: "Chat " + params.chatId
|
|
1323
|
+
});
|
|
1324
|
+
const userProfileStore = new UserProfileStore(params.userId);
|
|
1325
|
+
const userProfileXml = await userProfileStore.toXml();
|
|
1326
|
+
const result = stream(
|
|
1327
|
+
text2sqlMonolith.clone({
|
|
1328
|
+
model,
|
|
1329
|
+
tools: {
|
|
1330
|
+
...text2sqlMonolith.handoff.tools,
|
|
1331
|
+
...this.#config.tools,
|
|
1332
|
+
update_user_profile: tool3({
|
|
1333
|
+
description: `Update the user's profile with new facts, preferences, or present context.
|
|
1334
|
+
Use this when the user explicitly states a preference (e.g., "I like dark mode", "Call me Ezz")
|
|
1335
|
+
or when their working context changes (e.g., "I'm working on a hackathon").`,
|
|
1336
|
+
inputSchema: z6.object({
|
|
1337
|
+
type: z6.enum(["fact", "preference", "present"]).describe("The type of information to update."),
|
|
1338
|
+
text: z6.string().describe(
|
|
1339
|
+
"The content of the fact, preference, or present context."
|
|
1340
|
+
),
|
|
1341
|
+
action: z6.enum(["add", "remove"]).default("add").describe("Whether to add or remove the item.")
|
|
1342
|
+
}),
|
|
1343
|
+
execute: async ({ type, text, action }) => {
|
|
1344
|
+
if (action === "remove") {
|
|
1345
|
+
await userProfileStore.remove(type, text);
|
|
1346
|
+
return `Removed ${type}: ${text}`;
|
|
1347
|
+
}
|
|
1348
|
+
await userProfileStore.add(type, text);
|
|
1349
|
+
return `Added ${type}: ${text}`;
|
|
1350
|
+
}
|
|
1351
|
+
})
|
|
1352
|
+
}
|
|
1353
|
+
}),
|
|
1354
|
+
[...chat.messages.map((it) => it.content), ...messages],
|
|
1355
|
+
{
|
|
1356
|
+
teachings: toInstructions(...this.#config.instructions),
|
|
1357
|
+
adapter: this.#config.adapter,
|
|
1358
|
+
renderingTools: this.#config.tools || {},
|
|
1359
|
+
introspection,
|
|
1360
|
+
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1361
|
+
context: await generateBrief(introspection, this.#config.cache),
|
|
1362
|
+
userProfile: userProfileXml
|
|
1363
|
+
}
|
|
1364
|
+
);
|
|
1365
|
+
return result.toUIMessageStream({
|
|
1366
|
+
onError: (error) => {
|
|
1367
|
+
if (NoSuchToolError.isInstance(error)) {
|
|
1368
|
+
return "The model tried to call a unknown tool.";
|
|
1369
|
+
} else if (InvalidToolInputError.isInstance(error)) {
|
|
1370
|
+
return "The model called a tool with invalid arguments.";
|
|
1371
|
+
} else if (ToolCallRepairError.isInstance(error)) {
|
|
1372
|
+
return "The model tried to call a tool with invalid arguments, but it was repaired.";
|
|
1373
|
+
} else {
|
|
1374
|
+
return "An unknown error occurred.";
|
|
1375
|
+
}
|
|
1376
|
+
},
|
|
1377
|
+
sendStart: true,
|
|
1378
|
+
sendFinish: true,
|
|
1379
|
+
sendReasoning: true,
|
|
1380
|
+
sendSources: true,
|
|
1381
|
+
originalMessages: messages,
|
|
1382
|
+
onFinish: async ({ messages: messages2 }) => {
|
|
1383
|
+
const userMessage = messages2.at(-2);
|
|
1384
|
+
const botMessage = messages2.at(-1);
|
|
1385
|
+
if (!userMessage || !botMessage) {
|
|
1386
|
+
throw new Error("Not implemented yet");
|
|
1387
|
+
}
|
|
1388
|
+
await this.#config.history.addMessage({
|
|
1389
|
+
id: v7(),
|
|
1390
|
+
chatId: params.chatId,
|
|
1391
|
+
role: userMessage.role,
|
|
1392
|
+
content: userMessage
|
|
1393
|
+
});
|
|
1394
|
+
await this.#config.history.addMessage({
|
|
1395
|
+
id: v7(),
|
|
1396
|
+
chatId: params.chatId,
|
|
1397
|
+
role: botMessage.role,
|
|
1398
|
+
content: botMessage
|
|
1349
1399
|
});
|
|
1350
|
-
}
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1353
|
-
function userProfile(input) {
|
|
1354
|
-
return {
|
|
1355
|
-
type: "user_profile",
|
|
1356
|
-
format: () => {
|
|
1357
|
-
return "";
|
|
1358
|
-
}
|
|
1359
|
-
};
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
// packages/text2sql/src/lib/agents/teachables.agent.ts
|
|
1363
|
-
var teachableSchema = z5.discriminatedUnion("type", [
|
|
1364
|
-
z5.object({
|
|
1365
|
-
type: z5.literal("term"),
|
|
1366
|
-
name: z5.string(),
|
|
1367
|
-
definition: z5.string()
|
|
1368
|
-
}),
|
|
1369
|
-
z5.object({
|
|
1370
|
-
type: z5.literal("hint"),
|
|
1371
|
-
text: z5.string()
|
|
1372
|
-
}),
|
|
1373
|
-
z5.object({
|
|
1374
|
-
type: z5.literal("guardrail"),
|
|
1375
|
-
rule: z5.string(),
|
|
1376
|
-
reason: z5.string().optional(),
|
|
1377
|
-
action: z5.string().optional()
|
|
1378
|
-
}),
|
|
1379
|
-
z5.object({
|
|
1380
|
-
type: z5.literal("explain"),
|
|
1381
|
-
concept: z5.string(),
|
|
1382
|
-
explanation: z5.string(),
|
|
1383
|
-
therefore: z5.string().optional()
|
|
1384
|
-
}),
|
|
1385
|
-
z5.object({
|
|
1386
|
-
type: z5.literal("example"),
|
|
1387
|
-
question: z5.string(),
|
|
1388
|
-
sql: z5.string(),
|
|
1389
|
-
note: z5.string().optional()
|
|
1390
|
-
}),
|
|
1391
|
-
z5.object({
|
|
1392
|
-
type: z5.literal("clarification"),
|
|
1393
|
-
when: z5.string(),
|
|
1394
|
-
ask: z5.string(),
|
|
1395
|
-
reason: z5.string()
|
|
1396
|
-
}),
|
|
1397
|
-
z5.object({
|
|
1398
|
-
type: z5.literal("workflow"),
|
|
1399
|
-
task: z5.string(),
|
|
1400
|
-
steps: z5.array(z5.string()).min(2),
|
|
1401
|
-
triggers: z5.array(z5.string()).optional(),
|
|
1402
|
-
notes: z5.string().optional()
|
|
1403
|
-
}),
|
|
1404
|
-
z5.object({
|
|
1405
|
-
type: z5.literal("quirk"),
|
|
1406
|
-
issue: z5.string(),
|
|
1407
|
-
workaround: z5.string()
|
|
1408
|
-
}),
|
|
1409
|
-
z5.object({
|
|
1410
|
-
type: z5.literal("styleGuide"),
|
|
1411
|
-
prefer: z5.string(),
|
|
1412
|
-
never: z5.string().optional(),
|
|
1413
|
-
always: z5.string().optional()
|
|
1414
|
-
}),
|
|
1415
|
-
z5.object({
|
|
1416
|
-
type: z5.literal("analogy"),
|
|
1417
|
-
concept: z5.array(z5.string()).min(2),
|
|
1418
|
-
relationship: z5.string(),
|
|
1419
|
-
insight: z5.string().optional(),
|
|
1420
|
-
therefore: z5.string().optional(),
|
|
1421
|
-
pitfall: z5.string().optional()
|
|
1422
|
-
})
|
|
1423
|
-
]);
|
|
1424
|
-
var teachablesAuthorAgent = agent6({
|
|
1425
|
-
name: "teachables-author",
|
|
1426
|
-
model: groq6("openai/gpt-oss-20b"),
|
|
1427
|
-
output: z5.object({
|
|
1428
|
-
teachables: z5.array(teachableSchema).min(3).max(10).describe(
|
|
1429
|
-
"A concise, high-value set of teachables grounded in the provided schema."
|
|
1430
|
-
)
|
|
1431
|
-
}),
|
|
1432
|
-
prompt: (state) => dedent5`
|
|
1433
|
-
${thirdPersonPrompt2()}
|
|
1434
|
-
|
|
1435
|
-
<identity>
|
|
1436
|
-
You design "teachables" for a Text2SQL system. Teachables become structured XML instructions.
|
|
1437
|
-
Choose only high-impact items that improve accuracy, safety, or clarity for this database.
|
|
1438
|
-
</identity>
|
|
1439
|
-
|
|
1440
|
-
${databaseSchemaPrompt(state)}
|
|
1441
|
-
|
|
1442
|
-
<teachables_catalog>
|
|
1443
|
-
term: name + definition for domain vocabulary.
|
|
1444
|
-
hint: behavioral rule/constraint to apply by default.
|
|
1445
|
-
guardrail: hard safety/performance boundary with action and optional reason.
|
|
1446
|
-
explain: deeper concept metaphor/explanation (+ optional therefore).
|
|
1447
|
-
example: question + SQL (+ optional note).
|
|
1448
|
-
clarification: when/ask/reason to prompt the user before querying.
|
|
1449
|
-
workflow: task + ordered steps (+ optional triggers/notes).
|
|
1450
|
-
quirk: data edge case with workaround.
|
|
1451
|
-
styleGuide: prefer/never/always guidance for SQL output.
|
|
1452
|
-
analogy: comparison of two concepts with relationship (+ optional insight/therefore/pitfall).
|
|
1453
|
-
</teachables_catalog>
|
|
1454
|
-
|
|
1455
|
-
<instructions>
|
|
1456
|
-
- Ground everything in the provided schema/context; do not invent tables/columns.
|
|
1457
|
-
- Prefer guardrails + clarifications for performance, safety, and ambiguity.
|
|
1458
|
-
- Use examples only when a clear, schema-valid pattern is evident.
|
|
1459
|
-
- Keep the set lean (3-10 items) and non-duplicative; combine overlapping ideas.
|
|
1460
|
-
- Return JSON that satisfies the output schema; do not wrap in prose.
|
|
1461
|
-
</instructions>
|
|
1462
|
-
`
|
|
1463
|
-
});
|
|
1464
|
-
|
|
1465
|
-
// packages/text2sql/src/lib/history/history.ts
|
|
1466
|
-
var History = class {
|
|
1467
|
-
};
|
|
1468
|
-
|
|
1469
|
-
// packages/text2sql/src/lib/memory/user-profile.ts
|
|
1470
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
1471
|
-
import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1472
|
-
import { tmpdir as tmpdir2 } from "node:os";
|
|
1473
|
-
import path2 from "node:path";
|
|
1474
|
-
var UserProfileStore = class {
|
|
1475
|
-
constructor(userId) {
|
|
1476
|
-
this.userId = userId;
|
|
1477
|
-
const safeUserId = userId.replace(/[^a-z0-9]/gi, "_").toLowerCase();
|
|
1478
|
-
this.path = path2.join(tmpdir2(), `user-profile-${safeUserId}.json`);
|
|
1479
|
-
}
|
|
1480
|
-
path;
|
|
1481
|
-
/**
|
|
1482
|
-
* Retrieve the full user profile data.
|
|
1483
|
-
*/
|
|
1484
|
-
async get() {
|
|
1485
|
-
if (existsSync2(this.path)) {
|
|
1486
|
-
try {
|
|
1487
|
-
const content = await readFile2(this.path, "utf-8");
|
|
1488
|
-
return JSON.parse(content);
|
|
1489
|
-
} catch (error) {
|
|
1490
|
-
console.error("Failed to read user profile:", error);
|
|
1491
1400
|
}
|
|
1492
|
-
}
|
|
1493
|
-
return {
|
|
1494
|
-
items: [],
|
|
1495
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1496
|
-
};
|
|
1497
|
-
}
|
|
1498
|
-
/**
|
|
1499
|
-
* Save the user profile data.
|
|
1500
|
-
*/
|
|
1501
|
-
async save(data) {
|
|
1502
|
-
data.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
1503
|
-
await writeFile2(this.path, JSON.stringify(data, null, 2), "utf-8");
|
|
1504
|
-
}
|
|
1505
|
-
/**
|
|
1506
|
-
* Add an item to the profile.
|
|
1507
|
-
*/
|
|
1508
|
-
async add(type, text) {
|
|
1509
|
-
const data = await this.get();
|
|
1510
|
-
const exists = data.items.some(
|
|
1511
|
-
(item) => item.type === type && item.text === text
|
|
1512
|
-
);
|
|
1513
|
-
if (!exists) {
|
|
1514
|
-
data.items.push({ type, text });
|
|
1515
|
-
await this.save(data);
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
/**
|
|
1519
|
-
* Remove a specific item from the profile.
|
|
1520
|
-
*/
|
|
1521
|
-
async remove(type, text) {
|
|
1522
|
-
const data = await this.get();
|
|
1523
|
-
const filtered = data.items.filter((item) => {
|
|
1524
|
-
return !(item.type === type && item.text === text);
|
|
1525
1401
|
});
|
|
1526
|
-
await this.save({ ...data, items: filtered });
|
|
1527
|
-
}
|
|
1528
|
-
/**
|
|
1529
|
-
* Clear the entire profile.
|
|
1530
|
-
*/
|
|
1531
|
-
async clear() {
|
|
1532
|
-
await this.save({ items: [], lastUpdated: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1533
|
-
}
|
|
1534
|
-
/**
|
|
1535
|
-
* Get the formatted XML string for the system prompt.
|
|
1536
|
-
*/
|
|
1537
|
-
async toXml() {
|
|
1538
|
-
const data = await this.get();
|
|
1539
|
-
return toUserProfileXml(data.items);
|
|
1540
1402
|
}
|
|
1541
1403
|
};
|
|
1542
|
-
|
|
1543
|
-
if (items.length === 0) {
|
|
1544
|
-
return "";
|
|
1545
|
-
}
|
|
1546
|
-
const facts = items.filter((i) => i.type === "fact");
|
|
1547
|
-
const preferences = items.filter((i) => i.type === "preference");
|
|
1548
|
-
const present = items.filter((i) => i.type === "present");
|
|
1549
|
-
const sections = [];
|
|
1550
|
-
if (facts.length > 0) {
|
|
1551
|
-
const lines = facts.map((f) => `- ${f.text}`);
|
|
1552
|
-
sections.push(wrapBlock2("identity", lines));
|
|
1553
|
-
}
|
|
1554
|
-
if (preferences.length > 0) {
|
|
1555
|
-
const lines = preferences.map((p) => `- ${p.text}`);
|
|
1556
|
-
sections.push(wrapBlock2("preferences", lines));
|
|
1557
|
-
}
|
|
1558
|
-
if (present.length > 0) {
|
|
1559
|
-
const lines = present.map((c) => `- ${c.text}`);
|
|
1560
|
-
sections.push(wrapBlock2("working_context", lines));
|
|
1561
|
-
}
|
|
1562
|
-
if (sections.length === 0) return "";
|
|
1563
|
-
return `<user_profile>
|
|
1564
|
-
${indentBlock2(sections.join("\n"), 2)}
|
|
1565
|
-
</user_profile>`;
|
|
1566
|
-
}
|
|
1567
|
-
function wrapBlock2(tag, lines) {
|
|
1568
|
-
if (lines.length === 0) return "";
|
|
1569
|
-
return `<${tag}>
|
|
1570
|
-
${indentBlock2(lines.join("\n"), 2)}
|
|
1571
|
-
</${tag}>`;
|
|
1572
|
-
}
|
|
1573
|
-
function indentBlock2(text, spaces) {
|
|
1574
|
-
const padding = " ".repeat(spaces);
|
|
1575
|
-
return text.split("\n").map((line) => line.length ? padding + line : padding).join("\n");
|
|
1404
|
+
if (import.meta.main) {
|
|
1576
1405
|
}
|
|
1577
1406
|
|
|
1578
|
-
// packages/text2sql/src/lib/
|
|
1579
|
-
var
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1407
|
+
// packages/text2sql/src/lib/adapters/sqlite.ts
|
|
1408
|
+
var SQL_ERROR_MAP = [
|
|
1409
|
+
{
|
|
1410
|
+
pattern: /^no such table: .+$/,
|
|
1411
|
+
type: "MISSING_TABLE",
|
|
1412
|
+
hint: "Check the database schema for the correct table name. The table you referenced does not exist."
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
pattern: /^no such column: .+$/,
|
|
1416
|
+
type: "INVALID_COLUMN",
|
|
1417
|
+
hint: "Check the table schema for correct column names. The column may not exist or is ambiguous (exists in multiple joined tables)."
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
pattern: /^ambiguous column name: .+$/,
|
|
1421
|
+
type: "INVALID_COLUMN",
|
|
1422
|
+
hint: "Check the table schema for correct column names. The column may not exist or is ambiguous (exists in multiple joined tables)."
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
pattern: /^near ".+": syntax error$/,
|
|
1426
|
+
type: "SYNTAX_ERROR",
|
|
1427
|
+
hint: "There is a SQL syntax error. Review the query structure, keywords, and punctuation."
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
pattern: /^no tables specified$/,
|
|
1431
|
+
type: "SYNTAX_ERROR",
|
|
1432
|
+
hint: "There is a SQL syntax error. Review the query structure, keywords, and punctuation."
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
pattern: /^attempt to write a readonly database$/,
|
|
1436
|
+
type: "CONSTRAINT_ERROR",
|
|
1437
|
+
hint: "A database constraint was violated. This should not happen with read-only queries."
|
|
1438
|
+
}
|
|
1439
|
+
];
|
|
1440
|
+
var LOW_CARDINALITY_LIMIT = 20;
|
|
1441
|
+
function formatError(sql, error) {
|
|
1442
|
+
const errorMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error occurred";
|
|
1443
|
+
const errorInfo = SQL_ERROR_MAP.find((it) => it.pattern.test(errorMessage));
|
|
1444
|
+
if (!errorInfo) {
|
|
1445
|
+
return {
|
|
1446
|
+
error: errorMessage,
|
|
1447
|
+
error_type: "UNKNOWN_ERROR",
|
|
1448
|
+
suggestion: "Review the query and try again",
|
|
1449
|
+
sql_attempted: sql
|
|
1586
1450
|
};
|
|
1587
1451
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1452
|
+
return {
|
|
1453
|
+
error: errorMessage,
|
|
1454
|
+
error_type: errorInfo.type,
|
|
1455
|
+
suggestion: errorInfo.hint,
|
|
1456
|
+
sql_attempted: sql
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
var Sqlite = class extends Adapter {
|
|
1460
|
+
#options;
|
|
1461
|
+
#introspection = null;
|
|
1462
|
+
#info = null;
|
|
1463
|
+
constructor(options) {
|
|
1464
|
+
super();
|
|
1465
|
+
if (!options || typeof options.execute !== "function") {
|
|
1466
|
+
throw new Error("Sqlite adapter requires an execute function.");
|
|
1595
1467
|
}
|
|
1596
|
-
|
|
1468
|
+
this.#options = options;
|
|
1597
1469
|
}
|
|
1598
|
-
async
|
|
1599
|
-
const
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1470
|
+
async introspect(options) {
|
|
1471
|
+
const onProgress = options?.onProgress;
|
|
1472
|
+
if (this.#introspection) {
|
|
1473
|
+
return this.#introspection;
|
|
1474
|
+
}
|
|
1475
|
+
if (this.#options.introspect) {
|
|
1476
|
+
this.#introspection = await this.#options.introspect();
|
|
1477
|
+
return this.#introspection;
|
|
1478
|
+
}
|
|
1479
|
+
const allTables = await this.#loadTables();
|
|
1480
|
+
const allRelationships = await this.#loadRelationships(
|
|
1481
|
+
allTables.map((t) => t.name)
|
|
1603
1482
|
);
|
|
1604
|
-
|
|
1483
|
+
const { tables, relationships } = this.#applyTablesFilter(
|
|
1484
|
+
allTables,
|
|
1485
|
+
allRelationships
|
|
1486
|
+
);
|
|
1487
|
+
onProgress?.({
|
|
1488
|
+
phase: "tables",
|
|
1489
|
+
message: `Loaded ${tables.length} tables`,
|
|
1490
|
+
total: tables.length
|
|
1491
|
+
});
|
|
1492
|
+
onProgress?.({ phase: "row_counts", message: "Counting table rows..." });
|
|
1493
|
+
await this.#annotateRowCounts(tables, onProgress);
|
|
1494
|
+
onProgress?.({
|
|
1495
|
+
phase: "column_stats",
|
|
1496
|
+
message: "Collecting column statistics..."
|
|
1497
|
+
});
|
|
1498
|
+
await this.#annotateColumnStats(tables, onProgress);
|
|
1499
|
+
onProgress?.({ phase: "indexes", message: "Loading index information..." });
|
|
1500
|
+
await this.#annotateIndexes(tables, onProgress);
|
|
1501
|
+
onProgress?.({
|
|
1502
|
+
phase: "low_cardinality",
|
|
1503
|
+
message: "Identifying low cardinality columns..."
|
|
1504
|
+
});
|
|
1505
|
+
await this.#annotateLowCardinalityColumns(tables, onProgress);
|
|
1506
|
+
onProgress?.({
|
|
1507
|
+
phase: "relationships",
|
|
1508
|
+
message: "Loading foreign key relationships..."
|
|
1509
|
+
});
|
|
1510
|
+
this.#introspection = { tables, relationships };
|
|
1511
|
+
return this.#introspection;
|
|
1605
1512
|
}
|
|
1606
|
-
async
|
|
1607
|
-
|
|
1608
|
-
this.#config.adapter.introspect(),
|
|
1609
|
-
this.#config.adapter.info()
|
|
1610
|
-
]);
|
|
1611
|
-
const context = await generateBrief(introspection, this.#config.cache);
|
|
1612
|
-
return {
|
|
1613
|
-
generate: async () => {
|
|
1614
|
-
const { experimental_output: output } = await generate2(
|
|
1615
|
-
text2sqlOnly,
|
|
1616
|
-
[user2(input)],
|
|
1617
|
-
{
|
|
1618
|
-
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1619
|
-
context,
|
|
1620
|
-
introspection,
|
|
1621
|
-
teachings: toInstructions(...this.#config.instructions)
|
|
1622
|
-
}
|
|
1623
|
-
);
|
|
1624
|
-
return output.sql;
|
|
1625
|
-
}
|
|
1626
|
-
};
|
|
1513
|
+
async execute(sql) {
|
|
1514
|
+
return this.#options.execute(sql);
|
|
1627
1515
|
}
|
|
1628
|
-
async
|
|
1629
|
-
const
|
|
1630
|
-
this.#
|
|
1631
|
-
this.#config.adapter.info()
|
|
1632
|
-
]);
|
|
1633
|
-
const context = await generateBrief(introspection, this.#config.cache);
|
|
1634
|
-
return text2sqlOnly.instructions({
|
|
1635
|
-
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1636
|
-
context,
|
|
1637
|
-
introspection,
|
|
1638
|
-
teachings: toInstructions(...this.#config.instructions)
|
|
1516
|
+
async validate(sql) {
|
|
1517
|
+
const validator = this.#options.validate ?? (async (text) => {
|
|
1518
|
+
await this.#options.execute(`EXPLAIN ${text}`);
|
|
1639
1519
|
});
|
|
1520
|
+
try {
|
|
1521
|
+
return await validator(sql);
|
|
1522
|
+
} catch (error) {
|
|
1523
|
+
return JSON.stringify(formatError(sql, error));
|
|
1524
|
+
}
|
|
1640
1525
|
}
|
|
1641
|
-
|
|
1642
|
-
this.#
|
|
1526
|
+
async info() {
|
|
1527
|
+
if (this.#info) {
|
|
1528
|
+
return this.#info;
|
|
1529
|
+
}
|
|
1530
|
+
this.#info = await this.#resolveInfo();
|
|
1531
|
+
return this.#info;
|
|
1532
|
+
}
|
|
1533
|
+
formatInfo(info) {
|
|
1534
|
+
const lines = [`Dialect: ${info.dialect ?? "unknown"}`];
|
|
1535
|
+
if (info.version) {
|
|
1536
|
+
lines.push(`Version: ${info.version}`);
|
|
1537
|
+
}
|
|
1538
|
+
if (info.database) {
|
|
1539
|
+
lines.push(`Database: ${info.database}`);
|
|
1540
|
+
}
|
|
1541
|
+
if (info.host) {
|
|
1542
|
+
lines.push(`Host: ${info.host}`);
|
|
1543
|
+
}
|
|
1544
|
+
if (info.details && Object.keys(info.details).length) {
|
|
1545
|
+
lines.push(`Details: ${JSON.stringify(info.details)}`);
|
|
1546
|
+
}
|
|
1547
|
+
return lines.join("\n");
|
|
1643
1548
|
}
|
|
1644
|
-
async
|
|
1645
|
-
const
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
]);
|
|
1649
|
-
const context = await generateBrief(introspection, this.#config.cache);
|
|
1650
|
-
const { experimental_output } = await generate2(
|
|
1651
|
-
teachablesAuthorAgent,
|
|
1652
|
-
[user2(input)],
|
|
1653
|
-
{
|
|
1654
|
-
introspection,
|
|
1655
|
-
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1656
|
-
context
|
|
1657
|
-
}
|
|
1549
|
+
async getTables() {
|
|
1550
|
+
const allTables = await this.#loadTables();
|
|
1551
|
+
const allRelationships = await this.#loadRelationships(
|
|
1552
|
+
allTables.map((t) => t.name)
|
|
1658
1553
|
);
|
|
1659
|
-
|
|
1660
|
-
this.#config.instructions.push(...teachables);
|
|
1661
|
-
return {
|
|
1662
|
-
teachables,
|
|
1663
|
-
teachings: toInstructions(...this.#config.instructions)
|
|
1664
|
-
};
|
|
1554
|
+
return this.#applyTablesFilter(allTables, allRelationships).tables;
|
|
1665
1555
|
}
|
|
1666
|
-
async
|
|
1667
|
-
const
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1678
|
-
messages: [user2(input)],
|
|
1679
|
-
renderingTools: this.#config.tools || {},
|
|
1680
|
-
teachings: toInstructions(...this.#config.instructions)
|
|
1681
|
-
},
|
|
1682
|
-
toBrief(),
|
|
1683
|
-
async (state, update) => {
|
|
1684
|
-
const { experimental_output: output } = await generate2(
|
|
1685
|
-
text2sqlOnly,
|
|
1686
|
-
state.messages,
|
|
1687
|
-
state
|
|
1556
|
+
async #loadTables() {
|
|
1557
|
+
const rows = await this.#runQuery(
|
|
1558
|
+
`SELECT name FROM sqlite_master WHERE type='table' ORDER BY name`
|
|
1559
|
+
);
|
|
1560
|
+
const tableNames = rows.map((row) => row.name).filter(
|
|
1561
|
+
(name) => typeof name === "string" && !name.startsWith("sqlite_")
|
|
1562
|
+
);
|
|
1563
|
+
const tables = await Promise.all(
|
|
1564
|
+
tableNames.map(async (tableName) => {
|
|
1565
|
+
const columns = await this.#runQuery(
|
|
1566
|
+
`PRAGMA table_info(${this.#quoteIdentifier(tableName)})`
|
|
1688
1567
|
);
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
]
|
|
1700
|
-
});
|
|
1701
|
-
return output.sql;
|
|
1702
|
-
},
|
|
1703
|
-
synthesizerAgent
|
|
1568
|
+
return {
|
|
1569
|
+
name: tableName,
|
|
1570
|
+
rawName: tableName,
|
|
1571
|
+
columns: columns.map((col) => ({
|
|
1572
|
+
name: col.name ?? "unknown",
|
|
1573
|
+
type: col.type ?? "unknown",
|
|
1574
|
+
isPrimaryKey: (col.pk ?? 0) > 0
|
|
1575
|
+
}))
|
|
1576
|
+
};
|
|
1577
|
+
})
|
|
1704
1578
|
);
|
|
1705
|
-
|
|
1706
|
-
return {
|
|
1707
|
-
generate: async () => {
|
|
1708
|
-
const sql = await this.#getSql(stream2);
|
|
1709
|
-
return sql;
|
|
1710
|
-
},
|
|
1711
|
-
stream: () => {
|
|
1712
|
-
return stream2;
|
|
1713
|
-
}
|
|
1714
|
-
};
|
|
1579
|
+
return tables;
|
|
1715
1580
|
}
|
|
1716
|
-
async
|
|
1717
|
-
const
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
]);
|
|
1721
|
-
const context = await generateBrief(introspection, this.#config.cache);
|
|
1722
|
-
const { experimental_output: output } = await generate2(
|
|
1723
|
-
suggestionsAgent,
|
|
1724
|
-
[
|
|
1725
|
-
user2(
|
|
1726
|
-
"Suggest high-impact business questions and matching SQL queries for this database."
|
|
1727
|
-
)
|
|
1728
|
-
],
|
|
1729
|
-
{
|
|
1730
|
-
introspection,
|
|
1731
|
-
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1732
|
-
context
|
|
1733
|
-
}
|
|
1581
|
+
async getRelationships() {
|
|
1582
|
+
const allTables = await this.#loadTables();
|
|
1583
|
+
const allRelationships = await this.#loadRelationships(
|
|
1584
|
+
allTables.map((t) => t.name)
|
|
1734
1585
|
);
|
|
1735
|
-
return
|
|
1586
|
+
return this.#applyTablesFilter(allTables, allRelationships).relationships;
|
|
1736
1587
|
}
|
|
1737
|
-
async
|
|
1738
|
-
const
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1588
|
+
async #loadRelationships(tableNames) {
|
|
1589
|
+
const names = tableNames ?? (await this.#loadTables()).map((table) => table.name);
|
|
1590
|
+
const relationshipGroups = await Promise.all(
|
|
1591
|
+
names.map(async (tableName) => {
|
|
1592
|
+
const rows = await this.#runQuery(
|
|
1593
|
+
`PRAGMA foreign_key_list(${this.#quoteIdentifier(tableName)})`
|
|
1594
|
+
);
|
|
1595
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1596
|
+
for (const row of rows) {
|
|
1597
|
+
if (row.id == null || row.table == null || row.from == null || row.to == null) {
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
const id = Number(row.id);
|
|
1601
|
+
const existing = groups.get(id);
|
|
1602
|
+
if (!existing) {
|
|
1603
|
+
groups.set(id, {
|
|
1604
|
+
table: tableName,
|
|
1605
|
+
from: [String(row.from)],
|
|
1606
|
+
referenced_table: String(row.table),
|
|
1607
|
+
to: [String(row.to)]
|
|
1608
|
+
});
|
|
1609
|
+
} else {
|
|
1610
|
+
existing.from.push(String(row.from));
|
|
1611
|
+
existing.to.push(String(row.to));
|
|
1612
|
+
}
|
|
1747
1613
|
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
{
|
|
1751
|
-
teachings: toInstructions(...this.#config.instructions),
|
|
1752
|
-
adapter: this.#config.adapter,
|
|
1753
|
-
introspection,
|
|
1754
|
-
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1755
|
-
context: await generateBrief(introspection, this.#config.cache),
|
|
1756
|
-
renderingTools: this.#config.tools || {}
|
|
1757
|
-
}
|
|
1614
|
+
return Array.from(groups.values());
|
|
1615
|
+
})
|
|
1758
1616
|
);
|
|
1617
|
+
return relationshipGroups.flat();
|
|
1759
1618
|
}
|
|
1760
|
-
async
|
|
1761
|
-
const
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1619
|
+
async #annotateRowCounts(tables, onProgress) {
|
|
1620
|
+
const total = tables.length;
|
|
1621
|
+
for (let i = 0; i < tables.length; i++) {
|
|
1622
|
+
const table = tables[i];
|
|
1623
|
+
const tableIdentifier = this.#formatTableIdentifier(table);
|
|
1624
|
+
onProgress?.({
|
|
1625
|
+
phase: "row_counts",
|
|
1626
|
+
message: `Counting rows in ${table.name}...`,
|
|
1627
|
+
current: i + 1,
|
|
1628
|
+
total
|
|
1629
|
+
});
|
|
1630
|
+
try {
|
|
1631
|
+
const rows = await this.#runQuery(
|
|
1632
|
+
`SELECT COUNT(*) as count FROM ${tableIdentifier}`
|
|
1633
|
+
);
|
|
1634
|
+
const rowCount = this.#toNumber(rows[0]?.count);
|
|
1635
|
+
if (rowCount != null) {
|
|
1636
|
+
table.rowCount = rowCount;
|
|
1637
|
+
table.sizeHint = this.#classifyRowCount(rowCount);
|
|
1638
|
+
}
|
|
1639
|
+
} catch {
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
async #annotateColumnStats(tables, onProgress) {
|
|
1645
|
+
const total = tables.length;
|
|
1646
|
+
for (let i = 0; i < tables.length; i++) {
|
|
1647
|
+
const table = tables[i];
|
|
1648
|
+
const tableIdentifier = this.#formatTableIdentifier(table);
|
|
1649
|
+
onProgress?.({
|
|
1650
|
+
phase: "column_stats",
|
|
1651
|
+
message: `Collecting stats for ${table.name}...`,
|
|
1652
|
+
current: i + 1,
|
|
1653
|
+
total
|
|
1654
|
+
});
|
|
1655
|
+
for (const column of table.columns) {
|
|
1656
|
+
if (!this.#shouldCollectStats(column.type)) {
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
const columnIdentifier = this.#quoteSqlIdentifier(column.name);
|
|
1660
|
+
const sql = `
|
|
1661
|
+
SELECT
|
|
1662
|
+
MIN(${columnIdentifier}) AS min_value,
|
|
1663
|
+
MAX(${columnIdentifier}) AS max_value,
|
|
1664
|
+
AVG(CASE WHEN ${columnIdentifier} IS NULL THEN 1.0 ELSE 0.0 END) AS null_fraction
|
|
1665
|
+
FROM ${tableIdentifier}
|
|
1666
|
+
`;
|
|
1667
|
+
try {
|
|
1668
|
+
const rows = await this.#runQuery(sql);
|
|
1669
|
+
if (!rows.length) {
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
const min = this.#normalizeValue(rows[0]?.min_value);
|
|
1673
|
+
const max = this.#normalizeValue(rows[0]?.max_value);
|
|
1674
|
+
const nullFraction = this.#toNumber(rows[0]?.null_fraction);
|
|
1675
|
+
if (min != null || max != null || nullFraction != null) {
|
|
1676
|
+
column.stats = {
|
|
1677
|
+
min: min ?? void 0,
|
|
1678
|
+
max: max ?? void 0,
|
|
1679
|
+
nullFraction: nullFraction != null && Number.isFinite(nullFraction) ? Math.max(0, Math.min(1, nullFraction)) : void 0
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
} catch {
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
async #annotateIndexes(tables, onProgress) {
|
|
1689
|
+
const total = tables.length;
|
|
1690
|
+
for (let i = 0; i < tables.length; i++) {
|
|
1691
|
+
const table = tables[i];
|
|
1692
|
+
const tableIdentifier = this.#quoteIdentifier(
|
|
1693
|
+
table.rawName ?? table.name
|
|
1694
|
+
);
|
|
1695
|
+
onProgress?.({
|
|
1696
|
+
phase: "indexes",
|
|
1697
|
+
message: `Loading indexes for ${table.name}...`,
|
|
1698
|
+
current: i + 1,
|
|
1699
|
+
total
|
|
1700
|
+
});
|
|
1701
|
+
let indexes = [];
|
|
1702
|
+
try {
|
|
1703
|
+
const indexList = await this.#runQuery(
|
|
1704
|
+
`PRAGMA index_list(${tableIdentifier})`
|
|
1705
|
+
);
|
|
1706
|
+
indexes = await Promise.all(
|
|
1707
|
+
indexList.filter((index) => index.name).map(async (index) => {
|
|
1708
|
+
const indexName = String(index.name);
|
|
1709
|
+
const indexInfo = await this.#runQuery(
|
|
1710
|
+
`PRAGMA index_info('${indexName.replace(/'/g, "''")}')`
|
|
1711
|
+
);
|
|
1712
|
+
const columns = indexInfo.map((col) => col.name).filter((name) => Boolean(name));
|
|
1713
|
+
for (const columnName of columns) {
|
|
1714
|
+
const column = table.columns.find(
|
|
1715
|
+
(col) => col.name === columnName
|
|
1716
|
+
);
|
|
1717
|
+
if (column) {
|
|
1718
|
+
column.isIndexed = true;
|
|
1793
1719
|
}
|
|
1794
|
-
await userProfileStore.add(type, text);
|
|
1795
|
-
return `Added ${type}: ${text}`;
|
|
1796
1720
|
}
|
|
1721
|
+
return {
|
|
1722
|
+
name: indexName,
|
|
1723
|
+
columns,
|
|
1724
|
+
unique: index.unique === 1,
|
|
1725
|
+
primary: index.origin === "pk",
|
|
1726
|
+
type: index.origin ?? void 0
|
|
1727
|
+
};
|
|
1797
1728
|
})
|
|
1798
|
-
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
|
-
{
|
|
1802
|
-
teachings: toInstructions(...this.#config.instructions),
|
|
1803
|
-
adapter: this.#config.adapter,
|
|
1804
|
-
renderingTools: this.#config.tools || {},
|
|
1805
|
-
introspection,
|
|
1806
|
-
adapterInfo: this.#config.adapter.formatInfo(adapterInfo),
|
|
1807
|
-
context: await generateBrief(introspection, this.#config.cache),
|
|
1808
|
-
userProfile: userProfileXml
|
|
1729
|
+
);
|
|
1730
|
+
} catch {
|
|
1731
|
+
indexes = [];
|
|
1809
1732
|
}
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1733
|
+
if (indexes.length) {
|
|
1734
|
+
table.indexes = indexes;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
async #annotateLowCardinalityColumns(tables, onProgress) {
|
|
1739
|
+
const total = tables.length;
|
|
1740
|
+
for (let i = 0; i < tables.length; i++) {
|
|
1741
|
+
const table = tables[i];
|
|
1742
|
+
const tableIdentifier = this.#formatTableIdentifier(table);
|
|
1743
|
+
onProgress?.({
|
|
1744
|
+
phase: "low_cardinality",
|
|
1745
|
+
message: `Analyzing cardinality in ${table.name}...`,
|
|
1746
|
+
current: i + 1,
|
|
1747
|
+
total
|
|
1748
|
+
});
|
|
1749
|
+
for (const column of table.columns) {
|
|
1750
|
+
const columnIdentifier = this.#quoteSqlIdentifier(column.name);
|
|
1751
|
+
const limit = LOW_CARDINALITY_LIMIT + 1;
|
|
1752
|
+
const sql = `
|
|
1753
|
+
SELECT DISTINCT ${columnIdentifier} AS value
|
|
1754
|
+
FROM ${tableIdentifier}
|
|
1755
|
+
WHERE ${columnIdentifier} IS NOT NULL
|
|
1756
|
+
LIMIT ${limit}
|
|
1757
|
+
`;
|
|
1758
|
+
let rows = [];
|
|
1759
|
+
try {
|
|
1760
|
+
rows = await this.#runQuery(sql);
|
|
1761
|
+
} catch {
|
|
1762
|
+
continue;
|
|
1821
1763
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
sendFinish: true,
|
|
1825
|
-
sendReasoning: true,
|
|
1826
|
-
sendSources: true,
|
|
1827
|
-
originalMessages: messages,
|
|
1828
|
-
onFinish: async ({ messages: messages2 }) => {
|
|
1829
|
-
const userMessage = messages2.at(-2);
|
|
1830
|
-
const botMessage = messages2.at(-1);
|
|
1831
|
-
if (!userMessage || !botMessage) {
|
|
1832
|
-
throw new Error("Not implemented yet");
|
|
1764
|
+
if (!rows.length || rows.length > LOW_CARDINALITY_LIMIT) {
|
|
1765
|
+
continue;
|
|
1833
1766
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1767
|
+
const values = [];
|
|
1768
|
+
let shouldSkip = false;
|
|
1769
|
+
for (const row of rows) {
|
|
1770
|
+
const formatted = this.#normalizeValue(row.value);
|
|
1771
|
+
if (formatted == null) {
|
|
1772
|
+
shouldSkip = true;
|
|
1773
|
+
break;
|
|
1774
|
+
}
|
|
1775
|
+
values.push(formatted);
|
|
1776
|
+
}
|
|
1777
|
+
if (shouldSkip || !values.length) {
|
|
1778
|
+
continue;
|
|
1779
|
+
}
|
|
1780
|
+
column.kind = "LowCardinality";
|
|
1781
|
+
column.values = values;
|
|
1846
1782
|
}
|
|
1847
|
-
}
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
#quoteIdentifier(name) {
|
|
1786
|
+
return `'${name.replace(/'/g, "''")}'`;
|
|
1787
|
+
}
|
|
1788
|
+
#quoteSqlIdentifier(identifier) {
|
|
1789
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
1790
|
+
}
|
|
1791
|
+
#applyTablesFilter(tables, relationships) {
|
|
1792
|
+
return applyTablesFilter(tables, relationships, this.#options.tables);
|
|
1793
|
+
}
|
|
1794
|
+
#formatTableIdentifier(table) {
|
|
1795
|
+
const name = table.rawName ?? table.name;
|
|
1796
|
+
if (table.schema) {
|
|
1797
|
+
return `${this.#quoteSqlIdentifier(table.schema)}.${this.#quoteSqlIdentifier(name)}`;
|
|
1798
|
+
}
|
|
1799
|
+
return this.#quoteSqlIdentifier(name);
|
|
1800
|
+
}
|
|
1801
|
+
#toNumber(value) {
|
|
1802
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1803
|
+
return value;
|
|
1804
|
+
}
|
|
1805
|
+
if (typeof value === "bigint") {
|
|
1806
|
+
return Number(value);
|
|
1807
|
+
}
|
|
1808
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
1809
|
+
const parsed = Number(value);
|
|
1810
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1811
|
+
}
|
|
1812
|
+
return null;
|
|
1813
|
+
}
|
|
1814
|
+
#classifyRowCount(count) {
|
|
1815
|
+
if (count < 100) {
|
|
1816
|
+
return "tiny";
|
|
1817
|
+
}
|
|
1818
|
+
if (count < 1e3) {
|
|
1819
|
+
return "small";
|
|
1820
|
+
}
|
|
1821
|
+
if (count < 1e4) {
|
|
1822
|
+
return "medium";
|
|
1823
|
+
}
|
|
1824
|
+
if (count < 1e5) {
|
|
1825
|
+
return "large";
|
|
1826
|
+
}
|
|
1827
|
+
return "huge";
|
|
1828
|
+
}
|
|
1829
|
+
#shouldCollectStats(type) {
|
|
1830
|
+
if (!type) {
|
|
1831
|
+
return false;
|
|
1832
|
+
}
|
|
1833
|
+
const normalized = type.toLowerCase();
|
|
1834
|
+
return /int|real|numeric|double|float|decimal|date|time|bool/.test(
|
|
1835
|
+
normalized
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
#normalizeValue(value) {
|
|
1839
|
+
if (value === null || value === void 0) {
|
|
1840
|
+
return null;
|
|
1841
|
+
}
|
|
1842
|
+
if (typeof value === "string") {
|
|
1843
|
+
return value;
|
|
1844
|
+
}
|
|
1845
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
1846
|
+
return String(value);
|
|
1847
|
+
}
|
|
1848
|
+
if (typeof value === "boolean") {
|
|
1849
|
+
return value ? "true" : "false";
|
|
1850
|
+
}
|
|
1851
|
+
if (value instanceof Date) {
|
|
1852
|
+
return value.toISOString();
|
|
1853
|
+
}
|
|
1854
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) {
|
|
1855
|
+
return value.toString("utf-8");
|
|
1856
|
+
}
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
async #runQuery(sql) {
|
|
1860
|
+
const result = await this.#options.execute(sql);
|
|
1861
|
+
if (Array.isArray(result)) {
|
|
1862
|
+
return result;
|
|
1863
|
+
}
|
|
1864
|
+
if (result && typeof result === "object" && "rows" in result && Array.isArray(result.rows)) {
|
|
1865
|
+
return result.rows;
|
|
1866
|
+
}
|
|
1867
|
+
throw new Error(
|
|
1868
|
+
"Sqlite adapter execute() must return an array of rows or an object with a rows array when introspecting."
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1871
|
+
async #resolveInfo() {
|
|
1872
|
+
const { info } = this.#options;
|
|
1873
|
+
if (!info) {
|
|
1874
|
+
return { dialect: "sqlite" };
|
|
1875
|
+
}
|
|
1876
|
+
if (typeof info === "function") {
|
|
1877
|
+
return info();
|
|
1878
|
+
}
|
|
1879
|
+
return info;
|
|
1848
1880
|
}
|
|
1849
1881
|
};
|
|
1850
|
-
if (import.meta.main) {
|
|
1851
|
-
}
|
|
1852
1882
|
|
|
1853
1883
|
// packages/text2sql/src/lib/adapters/postgres.ts
|
|
1854
1884
|
var POSTGRES_ERROR_MAP = {
|
|
@@ -3070,14 +3100,15 @@ var InMemoryHistory = class extends SqliteHistory {
|
|
|
3070
3100
|
};
|
|
3071
3101
|
export {
|
|
3072
3102
|
Adapter,
|
|
3073
|
-
BriefCache,
|
|
3074
3103
|
History,
|
|
3075
3104
|
InMemoryHistory,
|
|
3105
|
+
JsonCache,
|
|
3076
3106
|
Postgres,
|
|
3077
3107
|
SqlServer,
|
|
3078
3108
|
Sqlite,
|
|
3079
3109
|
SqliteHistory,
|
|
3080
3110
|
Text2Sql,
|
|
3111
|
+
TmpCache,
|
|
3081
3112
|
analogy,
|
|
3082
3113
|
applyTablesFilter,
|
|
3083
3114
|
clarification,
|