@deepagents/text2sql 0.11.0 → 0.12.1
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.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1334 -491
- package/dist/index.js.map +4 -4
- package/dist/lib/adapters/groundings/index.js +2034 -50
- package/dist/lib/adapters/groundings/index.js.map +4 -4
- package/dist/lib/adapters/groundings/report.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/mysql/index.js +2034 -50
- package/dist/lib/adapters/mysql/index.js.map +4 -4
- package/dist/lib/adapters/postgres/index.js +2034 -50
- package/dist/lib/adapters/postgres/index.js.map +4 -4
- package/dist/lib/adapters/spreadsheet/index.js +35 -49
- package/dist/lib/adapters/spreadsheet/index.js.map +4 -4
- package/dist/lib/adapters/sqlite/index.js +2034 -50
- package/dist/lib/adapters/sqlite/index.js.map +4 -4
- package/dist/lib/adapters/sqlserver/column-stats.sqlserver.grounding.d.ts.map +1 -1
- package/dist/lib/adapters/sqlserver/index.js +2035 -53
- package/dist/lib/adapters/sqlserver/index.js.map +4 -4
- package/dist/lib/agents/developer.agent.d.ts.map +1 -1
- package/dist/lib/agents/explainer.agent.d.ts +4 -5
- package/dist/lib/agents/explainer.agent.d.ts.map +1 -1
- package/dist/lib/agents/question.agent.d.ts.map +1 -1
- package/dist/lib/agents/result-tools.d.ts +37 -0
- package/dist/lib/agents/result-tools.d.ts.map +1 -0
- package/dist/lib/agents/sql.agent.d.ts +1 -1
- package/dist/lib/agents/sql.agent.d.ts.map +1 -1
- package/dist/lib/agents/teachables.agent.d.ts.map +1 -1
- package/dist/lib/agents/text2sql.agent.d.ts +0 -21
- package/dist/lib/agents/text2sql.agent.d.ts.map +1 -1
- package/dist/lib/checkpoint.d.ts +1 -1
- package/dist/lib/checkpoint.d.ts.map +1 -1
- package/dist/lib/instructions.d.ts +9 -28
- package/dist/lib/instructions.d.ts.map +1 -1
- package/dist/lib/sql.d.ts +1 -1
- package/dist/lib/sql.d.ts.map +1 -1
- package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts +6 -7
- package/dist/lib/synthesis/extractors/base-contextual-extractor.d.ts.map +1 -1
- package/dist/lib/synthesis/extractors/last-query-extractor.d.ts.map +1 -1
- package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts +0 -6
- package/dist/lib/synthesis/extractors/segmented-context-extractor.d.ts.map +1 -1
- package/dist/lib/synthesis/extractors/sql-extractor.d.ts.map +1 -1
- package/dist/lib/synthesis/index.js +2394 -2132
- package/dist/lib/synthesis/index.js.map +4 -4
- package/dist/lib/synthesis/synthesizers/breadth-evolver.d.ts.map +1 -1
- package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts +3 -3
- package/dist/lib/synthesis/synthesizers/depth-evolver.d.ts.map +1 -1
- package/dist/lib/synthesis/synthesizers/persona-generator.d.ts.map +1 -1
- package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts +1 -1
- package/dist/lib/synthesis/synthesizers/schema-synthesizer.d.ts.map +1 -1
- package/package.json +9 -15
- package/dist/lib/instructions.js +0 -432
- package/dist/lib/instructions.js.map +0 -7
- package/dist/lib/teach/teachings.d.ts +0 -11
- package/dist/lib/teach/teachings.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -387,15 +387,16 @@ function getTablesWithRelated(allTables, relationships, filter) {
|
|
|
387
387
|
|
|
388
388
|
// packages/text2sql/src/lib/agents/developer.agent.ts
|
|
389
389
|
import { tool } from "ai";
|
|
390
|
-
import
|
|
390
|
+
import dedent from "dedent";
|
|
391
391
|
import z2 from "zod";
|
|
392
|
-
import {
|
|
392
|
+
import { toState } from "@deepagents/agent";
|
|
393
393
|
|
|
394
394
|
// packages/context/dist/index.js
|
|
395
395
|
import { encode } from "gpt-tokenizer";
|
|
396
396
|
import { generateId } from "ai";
|
|
397
397
|
import pluralize from "pluralize";
|
|
398
398
|
import { titlecase } from "stringcase";
|
|
399
|
+
import chalk from "chalk";
|
|
399
400
|
import { defineCommand } from "just-bash";
|
|
400
401
|
import spawn from "nano-spawn";
|
|
401
402
|
import "bash-tool";
|
|
@@ -405,7 +406,9 @@ import {
|
|
|
405
406
|
} from "bash-tool";
|
|
406
407
|
import YAML from "yaml";
|
|
407
408
|
import { DatabaseSync } from "node:sqlite";
|
|
409
|
+
import { groq } from "@ai-sdk/groq";
|
|
408
410
|
import {
|
|
411
|
+
NoSuchToolError,
|
|
409
412
|
Output,
|
|
410
413
|
convertToModelMessages,
|
|
411
414
|
createUIMessageStream,
|
|
@@ -415,7 +418,7 @@ import {
|
|
|
415
418
|
stepCountIs,
|
|
416
419
|
streamText
|
|
417
420
|
} from "ai";
|
|
418
|
-
import
|
|
421
|
+
import chalk2 from "chalk";
|
|
419
422
|
import "zod";
|
|
420
423
|
import "@deepagents/agent";
|
|
421
424
|
var defaultTokenizer = {
|
|
@@ -547,6 +550,12 @@ function isFragmentObject(data) {
|
|
|
547
550
|
function isMessageFragment(fragment2) {
|
|
548
551
|
return fragment2.type === "message";
|
|
549
552
|
}
|
|
553
|
+
function fragment(name, ...children) {
|
|
554
|
+
return {
|
|
555
|
+
name,
|
|
556
|
+
data: children
|
|
557
|
+
};
|
|
558
|
+
}
|
|
550
559
|
function user(content) {
|
|
551
560
|
const message2 = typeof content === "string" ? {
|
|
552
561
|
id: generateId(),
|
|
@@ -594,7 +603,7 @@ function message(content) {
|
|
|
594
603
|
} : content;
|
|
595
604
|
return {
|
|
596
605
|
id: message2.id,
|
|
597
|
-
name:
|
|
606
|
+
name: message2.role,
|
|
598
607
|
data: "content",
|
|
599
608
|
type: "message",
|
|
600
609
|
persist: true,
|
|
@@ -616,6 +625,22 @@ function assistantText(content, options) {
|
|
|
616
625
|
parts: [{ type: "text", text: content }]
|
|
617
626
|
});
|
|
618
627
|
}
|
|
628
|
+
var LAZY_ID = Symbol("lazy-id");
|
|
629
|
+
function isLazyFragment(fragment2) {
|
|
630
|
+
return LAZY_ID in fragment2;
|
|
631
|
+
}
|
|
632
|
+
function lastAssistantMessage(content) {
|
|
633
|
+
return {
|
|
634
|
+
name: "assistant",
|
|
635
|
+
type: "message",
|
|
636
|
+
persist: true,
|
|
637
|
+
data: "content",
|
|
638
|
+
[LAZY_ID]: {
|
|
639
|
+
type: "last-assistant",
|
|
640
|
+
content
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
}
|
|
619
644
|
var ContextRenderer = class {
|
|
620
645
|
options;
|
|
621
646
|
constructor(options = {}) {
|
|
@@ -639,6 +664,68 @@ var ContextRenderer = class {
|
|
|
639
664
|
}
|
|
640
665
|
return groups;
|
|
641
666
|
}
|
|
667
|
+
/**
|
|
668
|
+
* Remove null/undefined from fragments and fragment data recursively.
|
|
669
|
+
* This protects renderers from nullish values and ensures they are ignored
|
|
670
|
+
* consistently across all output formats.
|
|
671
|
+
*/
|
|
672
|
+
sanitizeFragments(fragments2) {
|
|
673
|
+
const sanitized = [];
|
|
674
|
+
for (const fragment2 of fragments2) {
|
|
675
|
+
const cleaned = this.sanitizeFragment(fragment2, /* @__PURE__ */ new WeakSet());
|
|
676
|
+
if (cleaned) {
|
|
677
|
+
sanitized.push(cleaned);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return sanitized;
|
|
681
|
+
}
|
|
682
|
+
sanitizeFragment(fragment2, seen) {
|
|
683
|
+
const data = this.sanitizeData(fragment2.data, seen);
|
|
684
|
+
if (data == null) {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
return {
|
|
688
|
+
...fragment2,
|
|
689
|
+
data
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
sanitizeData(data, seen) {
|
|
693
|
+
if (data == null) {
|
|
694
|
+
return void 0;
|
|
695
|
+
}
|
|
696
|
+
if (isFragment(data)) {
|
|
697
|
+
return this.sanitizeFragment(data, seen) ?? void 0;
|
|
698
|
+
}
|
|
699
|
+
if (Array.isArray(data)) {
|
|
700
|
+
if (seen.has(data)) {
|
|
701
|
+
return void 0;
|
|
702
|
+
}
|
|
703
|
+
seen.add(data);
|
|
704
|
+
const cleaned = [];
|
|
705
|
+
for (const item of data) {
|
|
706
|
+
const sanitizedItem = this.sanitizeData(item, seen);
|
|
707
|
+
if (sanitizedItem != null) {
|
|
708
|
+
cleaned.push(sanitizedItem);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return cleaned;
|
|
712
|
+
}
|
|
713
|
+
if (isFragmentObject(data)) {
|
|
714
|
+
if (seen.has(data)) {
|
|
715
|
+
return void 0;
|
|
716
|
+
}
|
|
717
|
+
seen.add(data);
|
|
718
|
+
const cleaned = {};
|
|
719
|
+
for (const [key, value] of Object.entries(data)) {
|
|
720
|
+
const sanitizedValue = this.sanitizeData(value, seen);
|
|
721
|
+
if (sanitizedValue != null) {
|
|
722
|
+
cleaned[key] = sanitizedValue;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return cleaned;
|
|
726
|
+
}
|
|
727
|
+
return data;
|
|
728
|
+
}
|
|
642
729
|
/**
|
|
643
730
|
* Template method - dispatches value to appropriate handler.
|
|
644
731
|
*/
|
|
@@ -666,7 +753,8 @@ var ContextRenderer = class {
|
|
|
666
753
|
};
|
|
667
754
|
var XmlRenderer = class extends ContextRenderer {
|
|
668
755
|
render(fragments2) {
|
|
669
|
-
|
|
756
|
+
const sanitized = this.sanitizeFragments(fragments2);
|
|
757
|
+
return sanitized.map((f) => this.#renderTopLevel(f)).filter(Boolean).join("\n");
|
|
670
758
|
}
|
|
671
759
|
#renderTopLevel(fragment2) {
|
|
672
760
|
if (this.isPrimitive(fragment2.data)) {
|
|
@@ -679,10 +767,13 @@ var XmlRenderer = class extends ContextRenderer {
|
|
|
679
767
|
const child = this.renderFragment(fragment2.data, { depth: 1, path: [] });
|
|
680
768
|
return this.#wrap(fragment2.name, [child]);
|
|
681
769
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
770
|
+
if (isFragmentObject(fragment2.data)) {
|
|
771
|
+
return this.#wrap(
|
|
772
|
+
fragment2.name,
|
|
773
|
+
this.renderEntries(fragment2.data, { depth: 1, path: [] })
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
return "";
|
|
686
777
|
}
|
|
687
778
|
#renderArray(name, items, depth) {
|
|
688
779
|
const fragmentItems = items.filter(isFragment);
|
|
@@ -690,9 +781,19 @@ var XmlRenderer = class extends ContextRenderer {
|
|
|
690
781
|
const children = [];
|
|
691
782
|
for (const item of nonFragmentItems) {
|
|
692
783
|
if (item != null) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
784
|
+
if (isFragmentObject(item)) {
|
|
785
|
+
children.push(
|
|
786
|
+
this.#wrapIndented(
|
|
787
|
+
pluralize.singular(name),
|
|
788
|
+
this.renderEntries(item, { depth: depth + 2, path: [] }),
|
|
789
|
+
depth + 1
|
|
790
|
+
)
|
|
791
|
+
);
|
|
792
|
+
} else {
|
|
793
|
+
children.push(
|
|
794
|
+
this.#leaf(pluralize.singular(name), String(item), depth + 1)
|
|
795
|
+
);
|
|
796
|
+
}
|
|
696
797
|
}
|
|
697
798
|
}
|
|
698
799
|
if (this.options.groupFragments && fragmentItems.length > 0) {
|
|
@@ -734,8 +835,14 @@ ${this.#indent(safe, 2)}
|
|
|
734
835
|
if (Array.isArray(data)) {
|
|
735
836
|
return this.#renderArrayIndented(name, data, ctx.depth);
|
|
736
837
|
}
|
|
737
|
-
|
|
738
|
-
|
|
838
|
+
if (isFragmentObject(data)) {
|
|
839
|
+
const children = this.renderEntries(data, {
|
|
840
|
+
...ctx,
|
|
841
|
+
depth: ctx.depth + 1
|
|
842
|
+
});
|
|
843
|
+
return this.#wrapIndented(name, children, ctx.depth);
|
|
844
|
+
}
|
|
845
|
+
return "";
|
|
739
846
|
}
|
|
740
847
|
#renderArrayIndented(name, items, depth) {
|
|
741
848
|
const fragmentItems = items.filter(isFragment);
|
|
@@ -743,9 +850,19 @@ ${this.#indent(safe, 2)}
|
|
|
743
850
|
const children = [];
|
|
744
851
|
for (const item of nonFragmentItems) {
|
|
745
852
|
if (item != null) {
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
853
|
+
if (isFragmentObject(item)) {
|
|
854
|
+
children.push(
|
|
855
|
+
this.#wrapIndented(
|
|
856
|
+
pluralize.singular(name),
|
|
857
|
+
this.renderEntries(item, { depth: depth + 2, path: [] }),
|
|
858
|
+
depth + 1
|
|
859
|
+
)
|
|
860
|
+
);
|
|
861
|
+
} else {
|
|
862
|
+
children.push(
|
|
863
|
+
this.#leaf(pluralize.singular(name), String(item), depth + 1)
|
|
864
|
+
);
|
|
865
|
+
}
|
|
749
866
|
}
|
|
750
867
|
}
|
|
751
868
|
if (this.options.groupFragments && fragmentItems.length > 0) {
|
|
@@ -774,7 +891,19 @@ ${this.#indent(safe, 2)}
|
|
|
774
891
|
return "";
|
|
775
892
|
}
|
|
776
893
|
const itemTag = pluralize.singular(key);
|
|
777
|
-
const children = items.filter((item) => item != null).map((item) =>
|
|
894
|
+
const children = items.filter((item) => item != null).map((item) => {
|
|
895
|
+
if (isFragment(item)) {
|
|
896
|
+
return this.renderFragment(item, { ...ctx, depth: ctx.depth + 1 });
|
|
897
|
+
}
|
|
898
|
+
if (isFragmentObject(item)) {
|
|
899
|
+
return this.#wrapIndented(
|
|
900
|
+
itemTag,
|
|
901
|
+
this.renderEntries(item, { ...ctx, depth: ctx.depth + 2 }),
|
|
902
|
+
ctx.depth + 1
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
return this.#leaf(itemTag, String(item), ctx.depth + 1);
|
|
906
|
+
});
|
|
778
907
|
return this.#wrapIndented(key, children, ctx.depth);
|
|
779
908
|
}
|
|
780
909
|
renderObject(key, obj, ctx) {
|
|
@@ -833,6 +962,7 @@ var ContextEngine = class {
|
|
|
833
962
|
#pendingMessages = [];
|
|
834
963
|
#store;
|
|
835
964
|
#chatId;
|
|
965
|
+
#userId;
|
|
836
966
|
#branchName;
|
|
837
967
|
#branch = null;
|
|
838
968
|
#chatData = null;
|
|
@@ -841,9 +971,13 @@ var ContextEngine = class {
|
|
|
841
971
|
if (!options.chatId) {
|
|
842
972
|
throw new Error("chatId is required");
|
|
843
973
|
}
|
|
974
|
+
if (!options.userId) {
|
|
975
|
+
throw new Error("userId is required");
|
|
976
|
+
}
|
|
844
977
|
this.#store = options.store;
|
|
845
978
|
this.#chatId = options.chatId;
|
|
846
|
-
this.#
|
|
979
|
+
this.#userId = options.userId;
|
|
980
|
+
this.#branchName = "main";
|
|
847
981
|
}
|
|
848
982
|
/**
|
|
849
983
|
* Initialize the chat and branch if they don't exist.
|
|
@@ -852,24 +986,11 @@ var ContextEngine = class {
|
|
|
852
986
|
if (this.#initialized) {
|
|
853
987
|
return;
|
|
854
988
|
}
|
|
855
|
-
this.#chatData = await this.#store.upsertChat({
|
|
856
|
-
|
|
857
|
-
this.#
|
|
858
|
-
|
|
859
|
-
);
|
|
860
|
-
if (existingBranch) {
|
|
861
|
-
this.#branch = existingBranch;
|
|
862
|
-
} else {
|
|
863
|
-
this.#branch = {
|
|
864
|
-
id: crypto.randomUUID(),
|
|
865
|
-
chatId: this.#chatId,
|
|
866
|
-
name: this.#branchName,
|
|
867
|
-
headMessageId: null,
|
|
868
|
-
isActive: true,
|
|
869
|
-
createdAt: Date.now()
|
|
870
|
-
};
|
|
871
|
-
await this.#store.createBranch(this.#branch);
|
|
872
|
-
}
|
|
989
|
+
this.#chatData = await this.#store.upsertChat({
|
|
990
|
+
id: this.#chatId,
|
|
991
|
+
userId: this.#userId
|
|
992
|
+
});
|
|
993
|
+
this.#branch = await this.#store.getActiveBranch(this.#chatId);
|
|
873
994
|
this.#initialized = true;
|
|
874
995
|
}
|
|
875
996
|
/**
|
|
@@ -929,6 +1050,7 @@ var ContextEngine = class {
|
|
|
929
1050
|
}
|
|
930
1051
|
return {
|
|
931
1052
|
id: this.#chatData.id,
|
|
1053
|
+
userId: this.#chatData.userId,
|
|
932
1054
|
createdAt: this.#chatData.createdAt,
|
|
933
1055
|
updatedAt: this.#chatData.updatedAt,
|
|
934
1056
|
title: this.#chatData.title,
|
|
@@ -971,7 +1093,7 @@ var ContextEngine = class {
|
|
|
971
1093
|
*
|
|
972
1094
|
* @example
|
|
973
1095
|
* ```ts
|
|
974
|
-
* const context = new ContextEngine({ store, chatId: 'chat-1' })
|
|
1096
|
+
* const context = new ContextEngine({ store, chatId: 'chat-1', userId: 'user-1' })
|
|
975
1097
|
* .set(role('You are helpful'), user('Hello'));
|
|
976
1098
|
*
|
|
977
1099
|
* const { systemPrompt, messages } = await context.resolve();
|
|
@@ -1015,6 +1137,12 @@ var ContextEngine = class {
|
|
|
1015
1137
|
if (this.#pendingMessages.length === 0) {
|
|
1016
1138
|
return;
|
|
1017
1139
|
}
|
|
1140
|
+
for (let i = 0; i < this.#pendingMessages.length; i++) {
|
|
1141
|
+
const fragment2 = this.#pendingMessages[i];
|
|
1142
|
+
if (isLazyFragment(fragment2)) {
|
|
1143
|
+
this.#pendingMessages[i] = await this.#resolveLazyFragment(fragment2);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1018
1146
|
let parentId = this.#branch.headMessageId;
|
|
1019
1147
|
const now = Date.now();
|
|
1020
1148
|
for (const fragment2 of this.#pendingMessages) {
|
|
@@ -1034,6 +1162,39 @@ var ContextEngine = class {
|
|
|
1034
1162
|
this.#branch.headMessageId = parentId;
|
|
1035
1163
|
this.#pendingMessages = [];
|
|
1036
1164
|
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Resolve a lazy fragment by finding the appropriate ID.
|
|
1167
|
+
*/
|
|
1168
|
+
async #resolveLazyFragment(fragment2) {
|
|
1169
|
+
const lazy = fragment2[LAZY_ID];
|
|
1170
|
+
if (lazy.type === "last-assistant") {
|
|
1171
|
+
const lastId = await this.#getLastAssistantId();
|
|
1172
|
+
return assistantText(lazy.content, { id: lastId ?? crypto.randomUUID() });
|
|
1173
|
+
}
|
|
1174
|
+
throw new Error(`Unknown lazy fragment type: ${lazy.type}`);
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Find the most recent assistant message ID (pending or persisted).
|
|
1178
|
+
*/
|
|
1179
|
+
async #getLastAssistantId() {
|
|
1180
|
+
for (let i = this.#pendingMessages.length - 1; i >= 0; i--) {
|
|
1181
|
+
const msg = this.#pendingMessages[i];
|
|
1182
|
+
if (msg.name === "assistant" && !isLazyFragment(msg)) {
|
|
1183
|
+
return msg.id;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (this.#branch?.headMessageId) {
|
|
1187
|
+
const chain = await this.#store.getMessageChain(
|
|
1188
|
+
this.#branch.headMessageId
|
|
1189
|
+
);
|
|
1190
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
1191
|
+
if (chain[i].name === "assistant") {
|
|
1192
|
+
return chain[i].id;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return void 0;
|
|
1197
|
+
}
|
|
1037
1198
|
/**
|
|
1038
1199
|
* Estimate token count and cost for the full context.
|
|
1039
1200
|
*
|
|
@@ -1313,9 +1474,38 @@ var ContextEngine = class {
|
|
|
1313
1474
|
consolidate() {
|
|
1314
1475
|
return void 0;
|
|
1315
1476
|
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Extract skill path mappings from available_skills fragments.
|
|
1479
|
+
* Returns array of { host, sandbox } for mounting in sandbox filesystem.
|
|
1480
|
+
*
|
|
1481
|
+
* Reads the original `paths` configuration stored in fragment metadata
|
|
1482
|
+
* by the skills() fragment helper.
|
|
1483
|
+
*
|
|
1484
|
+
* @example
|
|
1485
|
+
* ```ts
|
|
1486
|
+
* const context = new ContextEngine({ store, chatId, userId })
|
|
1487
|
+
* .set(skills({ paths: [{ host: './skills', sandbox: '/skills' }] }));
|
|
1488
|
+
*
|
|
1489
|
+
* const mounts = context.getSkillMounts();
|
|
1490
|
+
* // [{ host: './skills', sandbox: '/skills' }]
|
|
1491
|
+
* ```
|
|
1492
|
+
*/
|
|
1493
|
+
getSkillMounts() {
|
|
1494
|
+
const mounts = [];
|
|
1495
|
+
for (const fragment2 of this.#fragments) {
|
|
1496
|
+
if (fragment2.name === "available_skills" && fragment2.metadata && Array.isArray(fragment2.metadata.paths)) {
|
|
1497
|
+
for (const mapping of fragment2.metadata.paths) {
|
|
1498
|
+
if (typeof mapping === "object" && mapping !== null && typeof mapping.host === "string" && typeof mapping.sandbox === "string") {
|
|
1499
|
+
mounts.push({ host: mapping.host, sandbox: mapping.sandbox });
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return mounts;
|
|
1505
|
+
}
|
|
1316
1506
|
/**
|
|
1317
1507
|
* Inspect the full context state for debugging.
|
|
1318
|
-
* Returns a
|
|
1508
|
+
* Returns a JSON-serializable object with context information.
|
|
1319
1509
|
*
|
|
1320
1510
|
* @param options - Inspection options (modelId and renderer required)
|
|
1321
1511
|
* @returns Complete inspection data including estimates, rendered output, fragments, and graph
|
|
@@ -1378,6 +1568,26 @@ function guardrail(input) {
|
|
|
1378
1568
|
}
|
|
1379
1569
|
};
|
|
1380
1570
|
}
|
|
1571
|
+
function explain(input) {
|
|
1572
|
+
return {
|
|
1573
|
+
name: "explain",
|
|
1574
|
+
data: {
|
|
1575
|
+
concept: input.concept,
|
|
1576
|
+
explanation: input.explanation,
|
|
1577
|
+
...input.therefore && { therefore: input.therefore }
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
function example(input) {
|
|
1582
|
+
return {
|
|
1583
|
+
name: "example",
|
|
1584
|
+
data: {
|
|
1585
|
+
question: input.question,
|
|
1586
|
+
answer: input.answer,
|
|
1587
|
+
...input.note && { note: input.note }
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1381
1591
|
function clarification(input) {
|
|
1382
1592
|
return {
|
|
1383
1593
|
name: "clarification",
|
|
@@ -1399,6 +1609,15 @@ function workflow(input) {
|
|
|
1399
1609
|
}
|
|
1400
1610
|
};
|
|
1401
1611
|
}
|
|
1612
|
+
function quirk(input) {
|
|
1613
|
+
return {
|
|
1614
|
+
name: "quirk",
|
|
1615
|
+
data: {
|
|
1616
|
+
issue: input.issue,
|
|
1617
|
+
workaround: input.workaround
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1402
1621
|
function styleGuide(input) {
|
|
1403
1622
|
return {
|
|
1404
1623
|
name: "styleGuide",
|
|
@@ -1409,12 +1628,40 @@ function styleGuide(input) {
|
|
|
1409
1628
|
}
|
|
1410
1629
|
};
|
|
1411
1630
|
}
|
|
1631
|
+
function role(content) {
|
|
1632
|
+
return {
|
|
1633
|
+
name: "role",
|
|
1634
|
+
data: content
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
function principle(input) {
|
|
1638
|
+
return {
|
|
1639
|
+
name: "principle",
|
|
1640
|
+
data: {
|
|
1641
|
+
title: input.title,
|
|
1642
|
+
description: input.description,
|
|
1643
|
+
...input.policies?.length && { policies: input.policies }
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
function policy(input) {
|
|
1648
|
+
return {
|
|
1649
|
+
name: "policy",
|
|
1650
|
+
data: {
|
|
1651
|
+
rule: input.rule,
|
|
1652
|
+
...input.before && { before: input.before },
|
|
1653
|
+
...input.reason && { reason: input.reason },
|
|
1654
|
+
...input.policies?.length && { policies: input.policies }
|
|
1655
|
+
}
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1412
1658
|
function persona(input) {
|
|
1413
1659
|
return {
|
|
1414
1660
|
name: "persona",
|
|
1415
1661
|
data: {
|
|
1416
1662
|
name: input.name,
|
|
1417
|
-
role: input.role,
|
|
1663
|
+
...input.role && { role: input.role },
|
|
1664
|
+
...input.objective && { objective: input.objective },
|
|
1418
1665
|
...input.tone && { tone: input.tone }
|
|
1419
1666
|
}
|
|
1420
1667
|
};
|
|
@@ -1422,6 +1669,9 @@ function persona(input) {
|
|
|
1422
1669
|
function pass(part) {
|
|
1423
1670
|
return { type: "pass", part };
|
|
1424
1671
|
}
|
|
1672
|
+
function fail(feedback) {
|
|
1673
|
+
return { type: "fail", feedback };
|
|
1674
|
+
}
|
|
1425
1675
|
function runGuardrailChain(part, guardrails, context) {
|
|
1426
1676
|
let currentPart = part;
|
|
1427
1677
|
for (const guardrail2 of guardrails) {
|
|
@@ -1433,11 +1683,77 @@ function runGuardrailChain(part, guardrails, context) {
|
|
|
1433
1683
|
}
|
|
1434
1684
|
return pass(currentPart);
|
|
1435
1685
|
}
|
|
1686
|
+
var errorRecoveryGuardrail = {
|
|
1687
|
+
id: "error-recovery",
|
|
1688
|
+
name: "API Error Recovery",
|
|
1689
|
+
handle: (part, context) => {
|
|
1690
|
+
if (part.type !== "error") {
|
|
1691
|
+
return pass(part);
|
|
1692
|
+
}
|
|
1693
|
+
const errorText = part.errorText || "";
|
|
1694
|
+
const prefix = chalk.bold.magenta("[ErrorRecovery]");
|
|
1695
|
+
console.log(
|
|
1696
|
+
`${prefix} ${chalk.red("Caught error:")} ${chalk.dim(errorText.slice(0, 150))}`
|
|
1697
|
+
);
|
|
1698
|
+
const logAndFail = (pattern, feedback) => {
|
|
1699
|
+
console.log(
|
|
1700
|
+
`${prefix} ${chalk.yellow("Pattern:")} ${chalk.cyan(pattern)}`
|
|
1701
|
+
);
|
|
1702
|
+
console.log(
|
|
1703
|
+
`${prefix} ${chalk.green("Feedback:")} ${chalk.dim(feedback.slice(0, 80))}...`
|
|
1704
|
+
);
|
|
1705
|
+
return fail(feedback);
|
|
1706
|
+
};
|
|
1707
|
+
if (errorText.includes("Tool choice is none")) {
|
|
1708
|
+
if (context.availableTools.length > 0) {
|
|
1709
|
+
return logAndFail(
|
|
1710
|
+
"Tool choice is none",
|
|
1711
|
+
`I tried to call a tool that doesn't exist. Available tools: ${context.availableTools.join(", ")}. Let me use one of these instead.`
|
|
1712
|
+
);
|
|
1713
|
+
}
|
|
1714
|
+
return logAndFail(
|
|
1715
|
+
"Tool choice is none (no tools)",
|
|
1716
|
+
"I tried to call a tool, but no tools are available. Let me respond with plain text instead."
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
if (errorText.includes("not in request.tools") || errorText.includes("tool") && errorText.includes("not found")) {
|
|
1720
|
+
const toolMatch = errorText.match(/tool '([^']+)'/);
|
|
1721
|
+
const toolName = toolMatch ? toolMatch[1] : "unknown";
|
|
1722
|
+
if (context.availableTools.length > 0) {
|
|
1723
|
+
return logAndFail(
|
|
1724
|
+
`Unregistered tool: ${toolName}`,
|
|
1725
|
+
`I tried to call "${toolName}" but it doesn't exist. Available tools: ${context.availableTools.join(", ")}. Let me use one of these instead.`
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1728
|
+
return logAndFail(
|
|
1729
|
+
`Unregistered tool: ${toolName} (no tools)`,
|
|
1730
|
+
`I tried to call "${toolName}" but no tools are available. Let me respond with plain text instead.`
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
if (errorText.includes("Failed to parse tool call arguments") || errorText.includes("parse tool call") || errorText.includes("invalid JSON")) {
|
|
1734
|
+
return logAndFail(
|
|
1735
|
+
"Malformed JSON arguments",
|
|
1736
|
+
"I generated malformed JSON for the tool arguments. Let me format my tool call properly with valid JSON."
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
if (errorText.includes("Parsing failed")) {
|
|
1740
|
+
return logAndFail(
|
|
1741
|
+
"Parsing failed",
|
|
1742
|
+
"My response format was invalid. Let me try again with a properly formatted response."
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
return logAndFail(
|
|
1746
|
+
"Unknown error",
|
|
1747
|
+
`An error occurred: ${errorText}. Let me try a different approach.`
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1436
1751
|
var STORE_DDL = `
|
|
1437
1752
|
-- Chats table
|
|
1438
1753
|
-- createdAt/updatedAt: DEFAULT for insert, inline SET for updates
|
|
1439
1754
|
CREATE TABLE IF NOT EXISTS chats (
|
|
1440
1755
|
id TEXT PRIMARY KEY,
|
|
1756
|
+
userId TEXT NOT NULL,
|
|
1441
1757
|
title TEXT,
|
|
1442
1758
|
metadata TEXT,
|
|
1443
1759
|
createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
@@ -1445,6 +1761,7 @@ CREATE TABLE IF NOT EXISTS chats (
|
|
|
1445
1761
|
);
|
|
1446
1762
|
|
|
1447
1763
|
CREATE INDEX IF NOT EXISTS idx_chats_updatedAt ON chats(updatedAt);
|
|
1764
|
+
CREATE INDEX IF NOT EXISTS idx_chats_userId ON chats(userId);
|
|
1448
1765
|
|
|
1449
1766
|
-- Messages table (nodes in the DAG)
|
|
1450
1767
|
CREATE TABLE IF NOT EXISTS messages (
|
|
@@ -1510,37 +1827,67 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
1510
1827
|
this.#db.exec("PRAGMA foreign_keys = ON");
|
|
1511
1828
|
this.#db.exec(STORE_DDL);
|
|
1512
1829
|
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Execute a function within a transaction.
|
|
1832
|
+
* Automatically commits on success or rolls back on error.
|
|
1833
|
+
*/
|
|
1834
|
+
#useTransaction(fn) {
|
|
1835
|
+
this.#db.exec("BEGIN TRANSACTION");
|
|
1836
|
+
try {
|
|
1837
|
+
const result = fn();
|
|
1838
|
+
this.#db.exec("COMMIT");
|
|
1839
|
+
return result;
|
|
1840
|
+
} catch (error) {
|
|
1841
|
+
this.#db.exec("ROLLBACK");
|
|
1842
|
+
throw error;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1513
1845
|
// ==========================================================================
|
|
1514
1846
|
// Chat Operations
|
|
1515
1847
|
// ==========================================================================
|
|
1516
1848
|
async createChat(chat) {
|
|
1517
|
-
this.#
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1849
|
+
this.#useTransaction(() => {
|
|
1850
|
+
this.#db.prepare(
|
|
1851
|
+
`INSERT INTO chats (id, userId, title, metadata)
|
|
1852
|
+
VALUES (?, ?, ?, ?)`
|
|
1853
|
+
).run(
|
|
1854
|
+
chat.id,
|
|
1855
|
+
chat.userId,
|
|
1856
|
+
chat.title ?? null,
|
|
1857
|
+
chat.metadata ? JSON.stringify(chat.metadata) : null
|
|
1858
|
+
);
|
|
1859
|
+
this.#db.prepare(
|
|
1860
|
+
`INSERT INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
|
|
1861
|
+
VALUES (?, ?, 'main', NULL, 1, ?)`
|
|
1862
|
+
).run(crypto.randomUUID(), chat.id, Date.now());
|
|
1863
|
+
});
|
|
1525
1864
|
}
|
|
1526
1865
|
async upsertChat(chat) {
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1866
|
+
return this.#useTransaction(() => {
|
|
1867
|
+
const row = this.#db.prepare(
|
|
1868
|
+
`INSERT INTO chats (id, userId, title, metadata)
|
|
1869
|
+
VALUES (?, ?, ?, ?)
|
|
1870
|
+
ON CONFLICT(id) DO UPDATE SET id = excluded.id
|
|
1871
|
+
RETURNING *`
|
|
1872
|
+
).get(
|
|
1873
|
+
chat.id,
|
|
1874
|
+
chat.userId,
|
|
1875
|
+
chat.title ?? null,
|
|
1876
|
+
chat.metadata ? JSON.stringify(chat.metadata) : null
|
|
1877
|
+
);
|
|
1878
|
+
this.#db.prepare(
|
|
1879
|
+
`INSERT OR IGNORE INTO branches (id, chatId, name, headMessageId, isActive, createdAt)
|
|
1880
|
+
VALUES (?, ?, 'main', NULL, 1, ?)`
|
|
1881
|
+
).run(crypto.randomUUID(), chat.id, Date.now());
|
|
1882
|
+
return {
|
|
1883
|
+
id: row.id,
|
|
1884
|
+
userId: row.userId,
|
|
1885
|
+
title: row.title ?? void 0,
|
|
1886
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
1887
|
+
createdAt: row.createdAt,
|
|
1888
|
+
updatedAt: row.updatedAt
|
|
1889
|
+
};
|
|
1890
|
+
});
|
|
1544
1891
|
}
|
|
1545
1892
|
async getChat(chatId) {
|
|
1546
1893
|
const row = this.#db.prepare("SELECT * FROM chats WHERE id = ?").get(chatId);
|
|
@@ -1549,6 +1896,7 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
1549
1896
|
}
|
|
1550
1897
|
return {
|
|
1551
1898
|
id: row.id,
|
|
1899
|
+
userId: row.userId,
|
|
1552
1900
|
title: row.title ?? void 0,
|
|
1553
1901
|
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
1554
1902
|
createdAt: row.createdAt,
|
|
@@ -1572,16 +1920,33 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
1572
1920
|
).get(...params);
|
|
1573
1921
|
return {
|
|
1574
1922
|
id: row.id,
|
|
1923
|
+
userId: row.userId,
|
|
1575
1924
|
title: row.title ?? void 0,
|
|
1576
1925
|
metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
|
|
1577
1926
|
createdAt: row.createdAt,
|
|
1578
1927
|
updatedAt: row.updatedAt
|
|
1579
1928
|
};
|
|
1580
1929
|
}
|
|
1581
|
-
async listChats() {
|
|
1930
|
+
async listChats(options) {
|
|
1931
|
+
const params = [];
|
|
1932
|
+
let whereClause = "";
|
|
1933
|
+
let limitClause = "";
|
|
1934
|
+
if (options?.userId) {
|
|
1935
|
+
whereClause = "WHERE c.userId = ?";
|
|
1936
|
+
params.push(options.userId);
|
|
1937
|
+
}
|
|
1938
|
+
if (options?.limit !== void 0) {
|
|
1939
|
+
limitClause = " LIMIT ?";
|
|
1940
|
+
params.push(options.limit);
|
|
1941
|
+
if (options.offset !== void 0) {
|
|
1942
|
+
limitClause += " OFFSET ?";
|
|
1943
|
+
params.push(options.offset);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1582
1946
|
const rows = this.#db.prepare(
|
|
1583
1947
|
`SELECT
|
|
1584
1948
|
c.id,
|
|
1949
|
+
c.userId,
|
|
1585
1950
|
c.title,
|
|
1586
1951
|
c.createdAt,
|
|
1587
1952
|
c.updatedAt,
|
|
@@ -1590,11 +1955,13 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
1590
1955
|
FROM chats c
|
|
1591
1956
|
LEFT JOIN messages m ON m.chatId = c.id
|
|
1592
1957
|
LEFT JOIN branches b ON b.chatId = c.id
|
|
1958
|
+
${whereClause}
|
|
1593
1959
|
GROUP BY c.id
|
|
1594
|
-
ORDER BY c.updatedAt DESC`
|
|
1595
|
-
).all();
|
|
1960
|
+
ORDER BY c.updatedAt DESC${limitClause}`
|
|
1961
|
+
).all(...params);
|
|
1596
1962
|
return rows.map((row) => ({
|
|
1597
1963
|
id: row.id,
|
|
1964
|
+
userId: row.userId,
|
|
1598
1965
|
title: row.title ?? void 0,
|
|
1599
1966
|
messageCount: row.messageCount,
|
|
1600
1967
|
branchCount: row.branchCount,
|
|
@@ -1602,22 +1969,42 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
1602
1969
|
updatedAt: row.updatedAt
|
|
1603
1970
|
}));
|
|
1604
1971
|
}
|
|
1972
|
+
async deleteChat(chatId, options) {
|
|
1973
|
+
return this.#useTransaction(() => {
|
|
1974
|
+
const messageIds = this.#db.prepare("SELECT id FROM messages WHERE chatId = ?").all(chatId);
|
|
1975
|
+
let sql = "DELETE FROM chats WHERE id = ?";
|
|
1976
|
+
const params = [chatId];
|
|
1977
|
+
if (options?.userId !== void 0) {
|
|
1978
|
+
sql += " AND userId = ?";
|
|
1979
|
+
params.push(options.userId);
|
|
1980
|
+
}
|
|
1981
|
+
const result = this.#db.prepare(sql).run(...params);
|
|
1982
|
+
if (result.changes > 0 && messageIds.length > 0) {
|
|
1983
|
+
const placeholders = messageIds.map(() => "?").join(", ");
|
|
1984
|
+
this.#db.prepare(
|
|
1985
|
+
`DELETE FROM messages_fts WHERE messageId IN (${placeholders})`
|
|
1986
|
+
).run(...messageIds.map((m) => m.id));
|
|
1987
|
+
}
|
|
1988
|
+
return result.changes > 0;
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1605
1991
|
// ==========================================================================
|
|
1606
1992
|
// Message Operations (Graph Nodes)
|
|
1607
1993
|
// ==========================================================================
|
|
1608
1994
|
async addMessage(message2) {
|
|
1995
|
+
const existingParent = message2.parentId === message2.id ? this.#db.prepare("SELECT parentId FROM messages WHERE id = ?").get(message2.id) : void 0;
|
|
1996
|
+
const parentId = message2.parentId === message2.id ? existingParent?.parentId ?? null : message2.parentId;
|
|
1609
1997
|
this.#db.prepare(
|
|
1610
1998
|
`INSERT INTO messages (id, chatId, parentId, name, type, data, createdAt)
|
|
1611
1999
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1612
2000
|
ON CONFLICT(id) DO UPDATE SET
|
|
1613
|
-
parentId = excluded.parentId,
|
|
1614
2001
|
name = excluded.name,
|
|
1615
2002
|
type = excluded.type,
|
|
1616
2003
|
data = excluded.data`
|
|
1617
2004
|
).run(
|
|
1618
2005
|
message2.id,
|
|
1619
2006
|
message2.chatId,
|
|
1620
|
-
|
|
2007
|
+
parentId,
|
|
1621
2008
|
message2.name,
|
|
1622
2009
|
message2.type ?? null,
|
|
1623
2010
|
JSON.stringify(message2.data),
|
|
@@ -1672,6 +2059,17 @@ var SqliteContextStore = class extends ContextStore {
|
|
|
1672
2059
|
).get(messageId);
|
|
1673
2060
|
return row.hasChildren === 1;
|
|
1674
2061
|
}
|
|
2062
|
+
async getMessages(chatId) {
|
|
2063
|
+
const chat = await this.getChat(chatId);
|
|
2064
|
+
if (!chat) {
|
|
2065
|
+
throw new Error(`Chat "${chatId}" not found`);
|
|
2066
|
+
}
|
|
2067
|
+
const activeBranch = await this.getActiveBranch(chatId);
|
|
2068
|
+
if (!activeBranch?.headMessageId) {
|
|
2069
|
+
return [];
|
|
2070
|
+
}
|
|
2071
|
+
return this.getMessageChain(activeBranch.headMessageId);
|
|
2072
|
+
}
|
|
1675
2073
|
// ==========================================================================
|
|
1676
2074
|
// Branch Operations
|
|
1677
2075
|
// ==========================================================================
|
|
@@ -1933,16 +2331,17 @@ var Agent = class _Agent {
|
|
|
1933
2331
|
providerOptions: this.#options.providerOptions,
|
|
1934
2332
|
model: this.#options.model,
|
|
1935
2333
|
system: systemPrompt,
|
|
1936
|
-
messages: convertToModelMessages(messages),
|
|
2334
|
+
messages: await convertToModelMessages(messages),
|
|
1937
2335
|
stopWhen: stepCountIs(25),
|
|
1938
2336
|
tools: this.#options.tools,
|
|
1939
2337
|
experimental_context: contextVariables,
|
|
2338
|
+
experimental_repairToolCall: repairToolCall,
|
|
1940
2339
|
toolChoice: this.#options.toolChoice,
|
|
1941
2340
|
onStepFinish: (step) => {
|
|
1942
2341
|
const toolCall = step.toolCalls.at(-1);
|
|
1943
2342
|
if (toolCall) {
|
|
1944
2343
|
console.log(
|
|
1945
|
-
`Debug: ${
|
|
2344
|
+
`Debug: ${chalk2.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
|
|
1946
2345
|
);
|
|
1947
2346
|
}
|
|
1948
2347
|
}
|
|
@@ -1991,8 +2390,9 @@ var Agent = class _Agent {
|
|
|
1991
2390
|
providerOptions: this.#options.providerOptions,
|
|
1992
2391
|
model: this.#options.model,
|
|
1993
2392
|
system: systemPrompt,
|
|
1994
|
-
messages: convertToModelMessages(messages),
|
|
1995
|
-
|
|
2393
|
+
messages: await convertToModelMessages(messages),
|
|
2394
|
+
experimental_repairToolCall: repairToolCall,
|
|
2395
|
+
stopWhen: stepCountIs(50),
|
|
1996
2396
|
experimental_transform: config?.transform ?? smoothStream(),
|
|
1997
2397
|
tools: this.#options.tools,
|
|
1998
2398
|
experimental_context: contextVariables,
|
|
@@ -2001,7 +2401,7 @@ var Agent = class _Agent {
|
|
|
2001
2401
|
const toolCall = step.toolCalls.at(-1);
|
|
2002
2402
|
if (toolCall) {
|
|
2003
2403
|
console.log(
|
|
2004
|
-
`Debug: (${runId}) ${
|
|
2404
|
+
`Debug: (${runId}) ${chalk2.bold.yellow("ToolCalled")}: ${toolCall.toolName}(${JSON.stringify(toolCall.input)})`
|
|
2005
2405
|
);
|
|
2006
2406
|
}
|
|
2007
2407
|
}
|
|
@@ -2048,7 +2448,7 @@ var Agent = class _Agent {
|
|
|
2048
2448
|
guardrailFailed = true;
|
|
2049
2449
|
failureFeedback = checkResult.feedback;
|
|
2050
2450
|
console.log(
|
|
2051
|
-
|
|
2451
|
+
chalk2.yellow(
|
|
2052
2452
|
`[${this.#options.name}] Guardrail triggered (attempt ${attempt}/${maxRetries}): ${failureFeedback.slice(0, 50)}...`
|
|
2053
2453
|
)
|
|
2054
2454
|
);
|
|
@@ -2065,20 +2465,16 @@ var Agent = class _Agent {
|
|
|
2065
2465
|
}
|
|
2066
2466
|
if (attempt >= maxRetries) {
|
|
2067
2467
|
console.error(
|
|
2068
|
-
|
|
2468
|
+
chalk2.red(
|
|
2069
2469
|
`[${this.#options.name}] Guardrail retry limit (${maxRetries}) exceeded.`
|
|
2070
2470
|
)
|
|
2071
2471
|
);
|
|
2072
2472
|
writer.write({ type: "finish" });
|
|
2073
2473
|
return;
|
|
2074
2474
|
}
|
|
2075
|
-
writer
|
|
2076
|
-
type: "text-delta",
|
|
2077
|
-
id: generateId2(),
|
|
2078
|
-
delta: ` ${failureFeedback}`
|
|
2079
|
-
});
|
|
2475
|
+
writeText(writer, failureFeedback);
|
|
2080
2476
|
const selfCorrectionText = accumulatedText + " " + failureFeedback;
|
|
2081
|
-
context.set(
|
|
2477
|
+
context.set(lastAssistantMessage(selfCorrectionText));
|
|
2082
2478
|
await context.save();
|
|
2083
2479
|
currentResult = await this.#createRawStream(
|
|
2084
2480
|
contextVariables,
|
|
@@ -2108,14 +2504,10 @@ function structuredOutput(options) {
|
|
|
2108
2504
|
return {
|
|
2109
2505
|
async generate(contextVariables, config) {
|
|
2110
2506
|
if (!options.context) {
|
|
2111
|
-
throw new Error(
|
|
2112
|
-
`structuredOutput "${options.name}" is missing a context.`
|
|
2113
|
-
);
|
|
2507
|
+
throw new Error(`structuredOutput is missing a context.`);
|
|
2114
2508
|
}
|
|
2115
2509
|
if (!options.model) {
|
|
2116
|
-
throw new Error(
|
|
2117
|
-
`structuredOutput "${options.name}" is missing a model.`
|
|
2118
|
-
);
|
|
2510
|
+
throw new Error(`structuredOutput is missing a model.`);
|
|
2119
2511
|
}
|
|
2120
2512
|
const { messages, systemPrompt } = await options.context.resolve({
|
|
2121
2513
|
renderer: new XmlRenderer()
|
|
@@ -2125,23 +2517,21 @@ function structuredOutput(options) {
|
|
|
2125
2517
|
providerOptions: options.providerOptions,
|
|
2126
2518
|
model: options.model,
|
|
2127
2519
|
system: systemPrompt,
|
|
2128
|
-
messages: convertToModelMessages(messages),
|
|
2520
|
+
messages: await convertToModelMessages(messages),
|
|
2129
2521
|
stopWhen: stepCountIs(25),
|
|
2522
|
+
experimental_repairToolCall: repairToolCall,
|
|
2130
2523
|
experimental_context: contextVariables,
|
|
2131
|
-
|
|
2524
|
+
output: Output.object({ schema: options.schema }),
|
|
2525
|
+
tools: options.tools
|
|
2132
2526
|
});
|
|
2133
|
-
return result.
|
|
2527
|
+
return result.output;
|
|
2134
2528
|
},
|
|
2135
2529
|
async stream(contextVariables, config) {
|
|
2136
2530
|
if (!options.context) {
|
|
2137
|
-
throw new Error(
|
|
2138
|
-
`structuredOutput "${options.name}" is missing a context.`
|
|
2139
|
-
);
|
|
2531
|
+
throw new Error(`structuredOutput is missing a context.`);
|
|
2140
2532
|
}
|
|
2141
2533
|
if (!options.model) {
|
|
2142
|
-
throw new Error(
|
|
2143
|
-
`structuredOutput "${options.name}" is missing a model.`
|
|
2144
|
-
);
|
|
2534
|
+
throw new Error(`structuredOutput is missing a model.`);
|
|
2145
2535
|
}
|
|
2146
2536
|
const { messages, systemPrompt } = await options.context.resolve({
|
|
2147
2537
|
renderer: new XmlRenderer()
|
|
@@ -2151,37 +2541,90 @@ function structuredOutput(options) {
|
|
|
2151
2541
|
providerOptions: options.providerOptions,
|
|
2152
2542
|
model: options.model,
|
|
2153
2543
|
system: systemPrompt,
|
|
2154
|
-
|
|
2155
|
-
|
|
2544
|
+
experimental_repairToolCall: repairToolCall,
|
|
2545
|
+
messages: await convertToModelMessages(messages),
|
|
2546
|
+
stopWhen: stepCountIs(50),
|
|
2156
2547
|
experimental_transform: config?.transform ?? smoothStream(),
|
|
2157
2548
|
experimental_context: contextVariables,
|
|
2158
|
-
|
|
2549
|
+
output: Output.object({ schema: options.schema }),
|
|
2550
|
+
tools: options.tools
|
|
2159
2551
|
});
|
|
2160
2552
|
}
|
|
2161
2553
|
};
|
|
2162
2554
|
}
|
|
2555
|
+
var repairToolCall = async ({
|
|
2556
|
+
toolCall,
|
|
2557
|
+
tools: tools3,
|
|
2558
|
+
inputSchema,
|
|
2559
|
+
error
|
|
2560
|
+
}) => {
|
|
2561
|
+
console.log(
|
|
2562
|
+
`Debug: ${chalk2.yellow("RepairingToolCall")}: ${toolCall.toolName}`,
|
|
2563
|
+
error.name
|
|
2564
|
+
);
|
|
2565
|
+
if (NoSuchToolError.isInstance(error)) {
|
|
2566
|
+
return null;
|
|
2567
|
+
}
|
|
2568
|
+
const tool3 = tools3[toolCall.toolName];
|
|
2569
|
+
const { output } = await generateText({
|
|
2570
|
+
model: groq("openai/gpt-oss-20b"),
|
|
2571
|
+
output: Output.object({ schema: tool3.inputSchema }),
|
|
2572
|
+
prompt: [
|
|
2573
|
+
`The model tried to call the tool "${toolCall.toolName}" with the following inputs:`,
|
|
2574
|
+
JSON.stringify(toolCall.input),
|
|
2575
|
+
`The tool accepts the following schema:`,
|
|
2576
|
+
JSON.stringify(inputSchema(toolCall)),
|
|
2577
|
+
"Please fix the inputs."
|
|
2578
|
+
].join("\n")
|
|
2579
|
+
});
|
|
2580
|
+
return { ...toolCall, input: JSON.stringify(output) };
|
|
2581
|
+
};
|
|
2582
|
+
function writeText(writer, text) {
|
|
2583
|
+
const feedbackPartId = generateId2();
|
|
2584
|
+
writer.write({
|
|
2585
|
+
id: feedbackPartId,
|
|
2586
|
+
type: "text-start"
|
|
2587
|
+
});
|
|
2588
|
+
writer.write({
|
|
2589
|
+
id: feedbackPartId,
|
|
2590
|
+
type: "text-delta",
|
|
2591
|
+
delta: ` ${text}`
|
|
2592
|
+
});
|
|
2593
|
+
writer.write({
|
|
2594
|
+
id: feedbackPartId,
|
|
2595
|
+
type: "text-end"
|
|
2596
|
+
});
|
|
2597
|
+
}
|
|
2163
2598
|
|
|
2164
2599
|
// packages/text2sql/src/lib/agents/explainer.agent.ts
|
|
2165
|
-
import { groq } from "@ai-sdk/groq";
|
|
2166
|
-
import dedent from "dedent";
|
|
2600
|
+
import { groq as groq2 } from "@ai-sdk/groq";
|
|
2167
2601
|
import z from "zod";
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
name: "explainer",
|
|
2171
|
-
model: groq("openai/gpt-oss-20b"),
|
|
2172
|
-
prompt: (state) => dedent`
|
|
2173
|
-
You are an expert SQL tutor.
|
|
2174
|
-
Explain the following SQL query in plain English to a non-technical user.
|
|
2175
|
-
Focus on the intent and logic, not the syntax.
|
|
2176
|
-
|
|
2177
|
-
<sql>
|
|
2178
|
-
${state?.sql}
|
|
2179
|
-
</sql>
|
|
2180
|
-
`,
|
|
2181
|
-
output: z.object({
|
|
2182
|
-
explanation: z.string().describe("The explanation of the SQL query.")
|
|
2183
|
-
})
|
|
2602
|
+
var outputSchema = z.object({
|
|
2603
|
+
explanation: z.string().describe("The explanation of the SQL query.")
|
|
2184
2604
|
});
|
|
2605
|
+
async function explainSql(sql) {
|
|
2606
|
+
const context = new ContextEngine({
|
|
2607
|
+
store: new InMemoryContextStore(),
|
|
2608
|
+
chatId: `explainer-${crypto.randomUUID()}`,
|
|
2609
|
+
userId: "system"
|
|
2610
|
+
});
|
|
2611
|
+
context.set(
|
|
2612
|
+
persona({
|
|
2613
|
+
name: "explainer",
|
|
2614
|
+
role: "You are an expert SQL tutor.",
|
|
2615
|
+
objective: "Explain SQL queries in plain English that non-technical users understand"
|
|
2616
|
+
}),
|
|
2617
|
+
fragment("sql", sql),
|
|
2618
|
+
fragment("task", "Focus on the intent and logic, not the syntax."),
|
|
2619
|
+
user("Explain this SQL query in plain English to a non-technical user.")
|
|
2620
|
+
);
|
|
2621
|
+
const explainerOutput = structuredOutput({
|
|
2622
|
+
model: groq2("openai/gpt-oss-20b"),
|
|
2623
|
+
context,
|
|
2624
|
+
schema: outputSchema
|
|
2625
|
+
});
|
|
2626
|
+
return explainerOutput.generate();
|
|
2627
|
+
}
|
|
2185
2628
|
|
|
2186
2629
|
// packages/text2sql/src/lib/agents/developer.agent.ts
|
|
2187
2630
|
var tools = {
|
|
@@ -2227,7 +2670,7 @@ var tools = {
|
|
|
2227
2670
|
* Get plain-English explanation of a SQL query.
|
|
2228
2671
|
*/
|
|
2229
2672
|
explain_sql: tool({
|
|
2230
|
-
description:
|
|
2673
|
+
description: dedent`
|
|
2231
2674
|
Get a plain-English explanation of a SQL query.
|
|
2232
2675
|
Use this to help the user understand what a query does.
|
|
2233
2676
|
|
|
@@ -2237,17 +2680,15 @@ var tools = {
|
|
|
2237
2680
|
sql: z2.string().min(1).describe("The SQL query to explain")
|
|
2238
2681
|
}),
|
|
2239
2682
|
execute: async ({ sql }) => {
|
|
2240
|
-
|
|
2241
|
-
sql
|
|
2242
|
-
});
|
|
2243
|
-
return { explanation: experimental_output.explanation };
|
|
2683
|
+
return explainSql(sql);
|
|
2244
2684
|
}
|
|
2245
2685
|
})
|
|
2246
2686
|
};
|
|
2247
2687
|
var fragments = [
|
|
2248
2688
|
persona({
|
|
2249
2689
|
name: "developer_assistant",
|
|
2250
|
-
role: "You are an expert SQL developer assistant helping power users build and refine queries."
|
|
2690
|
+
role: "You are an expert SQL developer assistant helping power users build and refine queries.",
|
|
2691
|
+
objective: "Help power users build and refine SQL queries with precision and clarity"
|
|
2251
2692
|
}),
|
|
2252
2693
|
hint("Be transparent: show the SQL you generate before explaining it"),
|
|
2253
2694
|
hint("Be precise: provide exact column names and table references"),
|
|
@@ -2257,69 +2698,402 @@ var fragments = [
|
|
|
2257
2698
|
];
|
|
2258
2699
|
var developer_agent_default = { tools, fragments };
|
|
2259
2700
|
|
|
2260
|
-
// packages/text2sql/src/lib/agents/
|
|
2261
|
-
import {
|
|
2262
|
-
import
|
|
2263
|
-
import
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
metrics that drive executive decisions.
|
|
2286
|
-
</identity>
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
<instructions>
|
|
2290
|
-
- Recommend one or two UNIQUE questions that go beyond simple counts or listings.
|
|
2291
|
-
- Favor questions that require joins, aggregates, time comparisons, cohort analysis,
|
|
2292
|
-
or window functions.
|
|
2293
|
-
- For each question, explain the business reason stakeholders care about it.
|
|
2294
|
-
- Provide the complete SQL query that could answer the question in the given schema.
|
|
2295
|
-
- Keep result sets scoped with LIMIT clauses (max 50 rows) when returning raw rows.
|
|
2296
|
-
- Ensure table/column names match the provided schema exactly.
|
|
2297
|
-
- Use columns marked [LowCardinality: ...] to identify meaningful categorical filters or segmentations.
|
|
2298
|
-
- Leverage table [rows / size] hints to determine whether to aggregate (large tables) or inspect detailed data (tiny tables).
|
|
2299
|
-
- Reference PK/Indexed annotations and the Indexes list to recommend queries that use efficient join/filter paths.
|
|
2300
|
-
- Column annotations may expose ranges/null percentages—use them to suggest realistic thresholds or quality checks.
|
|
2301
|
-
- Consult <relationship_examples> to anchor your recommendations in the actual join paths between tables.
|
|
2302
|
-
- Output only information grounded in the schema/context provided.
|
|
2303
|
-
</instructions>
|
|
2701
|
+
// packages/text2sql/src/lib/agents/result-tools.ts
|
|
2702
|
+
import { createBashTool as createBashTool2 } from "bash-tool";
|
|
2703
|
+
import chalk3 from "chalk";
|
|
2704
|
+
import {
|
|
2705
|
+
Bash,
|
|
2706
|
+
InMemoryFs,
|
|
2707
|
+
MountableFs,
|
|
2708
|
+
OverlayFs,
|
|
2709
|
+
ReadWriteFs,
|
|
2710
|
+
defineCommand as defineCommand2
|
|
2711
|
+
} from "just-bash";
|
|
2712
|
+
import * as fs from "node:fs/promises";
|
|
2713
|
+
import * as path from "node:path";
|
|
2714
|
+
import { v7 } from "uuid";
|
|
2715
|
+
function createCommand(name, subcommands) {
|
|
2716
|
+
const usageLines = Object.entries(subcommands).map(([, def]) => ` ${name} ${def.usage.padEnd(30)} ${def.description}`).join("\n");
|
|
2717
|
+
return defineCommand2(name, async (args, ctx) => {
|
|
2718
|
+
const subcommand = args[0];
|
|
2719
|
+
const restArgs = args.slice(1);
|
|
2720
|
+
if (subcommand && subcommand in subcommands) {
|
|
2721
|
+
return subcommands[subcommand].handler(restArgs, ctx);
|
|
2722
|
+
}
|
|
2723
|
+
return {
|
|
2724
|
+
stdout: "",
|
|
2725
|
+
stderr: `${name}: ${subcommand ? `unknown subcommand '${subcommand}'` : "missing subcommand"}
|
|
2304
2726
|
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2727
|
+
Usage:
|
|
2728
|
+
${usageLines}`,
|
|
2729
|
+
exitCode: 1
|
|
2730
|
+
};
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
function validateReadOnly(query) {
|
|
2734
|
+
const upper = query.toUpperCase().trim();
|
|
2735
|
+
if (!upper.startsWith("SELECT") && !upper.startsWith("WITH")) {
|
|
2736
|
+
return { valid: false, error: "only SELECT or WITH queries allowed" };
|
|
2737
|
+
}
|
|
2738
|
+
return { valid: true };
|
|
2739
|
+
}
|
|
2740
|
+
function createSqlCommand(adapter) {
|
|
2741
|
+
return createCommand("sql", {
|
|
2742
|
+
run: {
|
|
2743
|
+
usage: 'run "SELECT ..."',
|
|
2744
|
+
description: "Execute query and store results",
|
|
2745
|
+
handler: async (args, ctx) => {
|
|
2746
|
+
const query = args.join(" ").trim();
|
|
2747
|
+
if (!query) {
|
|
2748
|
+
return {
|
|
2749
|
+
stdout: "",
|
|
2750
|
+
stderr: "sql run: no query provided",
|
|
2751
|
+
exitCode: 1
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
const validation = validateReadOnly(query);
|
|
2755
|
+
if (!validation.valid) {
|
|
2756
|
+
return {
|
|
2757
|
+
stdout: "",
|
|
2758
|
+
stderr: `sql run: ${validation.error}`,
|
|
2759
|
+
exitCode: 1
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2762
|
+
try {
|
|
2763
|
+
const rows = await adapter.execute(query);
|
|
2764
|
+
const rowsArray = Array.isArray(rows) ? rows : [];
|
|
2765
|
+
const filePath = `/results/${v7()}.json`;
|
|
2766
|
+
await ctx.fs.writeFile(filePath, JSON.stringify(rowsArray, null, 2));
|
|
2767
|
+
const columns = rowsArray.length > 0 ? Object.keys(rowsArray[0]) : [];
|
|
2768
|
+
return {
|
|
2769
|
+
stdout: [
|
|
2770
|
+
filePath,
|
|
2771
|
+
`columns: ${columns.join(", ") || "(none)"}`,
|
|
2772
|
+
`rows: ${rowsArray.length}`
|
|
2773
|
+
].join("\n") + "\n",
|
|
2774
|
+
stderr: "",
|
|
2775
|
+
exitCode: 0
|
|
2776
|
+
};
|
|
2777
|
+
} catch (error) {
|
|
2778
|
+
return {
|
|
2779
|
+
stdout: "",
|
|
2780
|
+
stderr: `sql run: ${error instanceof Error ? error.message : String(error)}`,
|
|
2781
|
+
exitCode: 1
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
},
|
|
2786
|
+
validate: {
|
|
2787
|
+
usage: 'validate "SELECT ..."',
|
|
2788
|
+
description: "Validate query syntax",
|
|
2789
|
+
handler: (args) => {
|
|
2790
|
+
const query = args.join(" ").trim();
|
|
2791
|
+
if (!query) {
|
|
2792
|
+
return {
|
|
2793
|
+
stdout: "",
|
|
2794
|
+
stderr: "sql validate: no query provided",
|
|
2795
|
+
exitCode: 1
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
const validation = validateReadOnly(query);
|
|
2799
|
+
if (!validation.valid) {
|
|
2800
|
+
return {
|
|
2801
|
+
stdout: "",
|
|
2802
|
+
stderr: `sql validate: ${validation.error}`,
|
|
2803
|
+
exitCode: 1
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
return {
|
|
2807
|
+
stdout: "valid\n",
|
|
2808
|
+
stderr: "",
|
|
2809
|
+
exitCode: 0
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
});
|
|
2814
|
+
}
|
|
2815
|
+
async function createResultTools(options) {
|
|
2816
|
+
const { adapter, chatId, messageId, skillMounts = [] } = options;
|
|
2817
|
+
const sqlCommand = createSqlCommand(adapter);
|
|
2818
|
+
const root = process.env.TEXT2SQL_FS_ROOT || process.cwd();
|
|
2819
|
+
const chatDir = path.join(root, "artifacts", chatId);
|
|
2820
|
+
const resultsDir = path.join(chatDir, messageId, "results");
|
|
2821
|
+
await fs.mkdir(resultsDir, { recursive: true });
|
|
2822
|
+
const fsMounts = skillMounts.map(({ host, sandbox: sandbox2 }) => ({
|
|
2823
|
+
mountPoint: sandbox2,
|
|
2824
|
+
filesystem: new OverlayFs({
|
|
2825
|
+
root: host,
|
|
2826
|
+
mountPoint: "/",
|
|
2827
|
+
readOnly: true
|
|
2828
|
+
})
|
|
2829
|
+
}));
|
|
2830
|
+
const filesystem = new MountableFs({
|
|
2831
|
+
base: new InMemoryFs(),
|
|
2832
|
+
mounts: [
|
|
2833
|
+
...fsMounts,
|
|
2834
|
+
{
|
|
2835
|
+
mountPoint: "/results",
|
|
2836
|
+
filesystem: new ReadWriteFs({ root: resultsDir })
|
|
2837
|
+
},
|
|
2838
|
+
{
|
|
2839
|
+
mountPoint: "/artifacts",
|
|
2840
|
+
filesystem: new ReadWriteFs({ root: chatDir })
|
|
2841
|
+
}
|
|
2842
|
+
]
|
|
2843
|
+
});
|
|
2844
|
+
const bashInstance = new Bash({
|
|
2845
|
+
customCommands: [sqlCommand],
|
|
2846
|
+
fs: filesystem
|
|
2847
|
+
});
|
|
2848
|
+
const { bash, sandbox } = await createBashTool2({
|
|
2849
|
+
sandbox: bashInstance,
|
|
2850
|
+
destination: "/",
|
|
2851
|
+
onBeforeBashCall: ({ command }) => {
|
|
2852
|
+
console.log(chalk3.cyan(`[onBeforeBashCall]: ${command}`));
|
|
2853
|
+
return { command };
|
|
2854
|
+
},
|
|
2855
|
+
onAfterBashCall: ({ result }) => {
|
|
2856
|
+
if (result.exitCode !== 0) {
|
|
2857
|
+
console.log(chalk3.yellow(`[onAfterBashCall]: ${result.exitCode}`));
|
|
2858
|
+
}
|
|
2859
|
+
return { result };
|
|
2860
|
+
}
|
|
2861
|
+
});
|
|
2862
|
+
return { bash, sandbox };
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
// packages/text2sql/src/lib/agents/sql.agent.ts
|
|
2866
|
+
import { groq as groq3 } from "@ai-sdk/groq";
|
|
2867
|
+
import {
|
|
2868
|
+
APICallError,
|
|
2869
|
+
JSONParseError,
|
|
2870
|
+
NoContentGeneratedError,
|
|
2871
|
+
NoObjectGeneratedError,
|
|
2872
|
+
NoOutputGeneratedError,
|
|
2873
|
+
TypeValidationError,
|
|
2874
|
+
defaultSettingsMiddleware,
|
|
2875
|
+
wrapLanguageModel
|
|
2876
|
+
} from "ai";
|
|
2877
|
+
import { Console } from "node:console";
|
|
2878
|
+
import { createWriteStream } from "node:fs";
|
|
2879
|
+
import pRetry from "p-retry";
|
|
2880
|
+
import z3 from "zod";
|
|
2881
|
+
import "@deepagents/agent";
|
|
2882
|
+
var logger = new Console({
|
|
2883
|
+
stdout: createWriteStream("./sql-agent.log", { flags: "a" }),
|
|
2884
|
+
stderr: createWriteStream("./sql-agent-error.log", { flags: "a" }),
|
|
2885
|
+
inspectOptions: { depth: null }
|
|
2886
|
+
});
|
|
2887
|
+
var RETRY_TEMPERATURES = [0, 0.2, 0.3];
|
|
2888
|
+
function extractSql(output) {
|
|
2889
|
+
const match = output.match(/```sql\n?([\s\S]*?)```/);
|
|
2890
|
+
return match ? match[1].trim() : output.trim();
|
|
2891
|
+
}
|
|
2892
|
+
var marker = Symbol("SQLValidationError");
|
|
2893
|
+
var SQLValidationError = class _SQLValidationError extends Error {
|
|
2894
|
+
[marker];
|
|
2895
|
+
constructor(message2) {
|
|
2896
|
+
super(message2);
|
|
2897
|
+
this.name = "SQLValidationError";
|
|
2898
|
+
this[marker] = true;
|
|
2899
|
+
}
|
|
2900
|
+
static isInstance(error) {
|
|
2901
|
+
return error instanceof _SQLValidationError && error[marker] === true;
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2904
|
+
var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
|
|
2905
|
+
constructor(message2) {
|
|
2906
|
+
super(message2);
|
|
2907
|
+
this.name = "UnanswerableSQLError";
|
|
2908
|
+
}
|
|
2909
|
+
static isInstance(error) {
|
|
2910
|
+
return error instanceof _UnanswerableSQLError;
|
|
2911
|
+
}
|
|
2912
|
+
};
|
|
2913
|
+
async function toSql(options) {
|
|
2914
|
+
const { maxRetries = 3 } = options;
|
|
2915
|
+
return withRetry(
|
|
2916
|
+
async (attemptNumber, errors, attempts) => {
|
|
2917
|
+
const context = new ContextEngine({
|
|
2918
|
+
store: new InMemoryContextStore(),
|
|
2919
|
+
chatId: `sql-gen-${crypto.randomUUID()}`,
|
|
2920
|
+
userId: "system"
|
|
2921
|
+
});
|
|
2922
|
+
context.set(
|
|
2923
|
+
persona({
|
|
2924
|
+
name: "Freya",
|
|
2925
|
+
role: "You are an expert SQL query generator. You translate natural language questions into precise, efficient SQL queries based on the provided database schema.",
|
|
2926
|
+
objective: "Translate natural language questions into precise, efficient SQL queries"
|
|
2927
|
+
}),
|
|
2928
|
+
...options.instructions,
|
|
2929
|
+
...options.schemaFragments
|
|
2930
|
+
);
|
|
2931
|
+
if (errors.length) {
|
|
2932
|
+
context.set(
|
|
2933
|
+
user(options.input),
|
|
2934
|
+
user(
|
|
2935
|
+
`<validation_error>Your previous SQL query had the following error: ${errors.at(-1)?.message}. Please fix the query.</validation_error>`
|
|
2936
|
+
)
|
|
2937
|
+
);
|
|
2938
|
+
} else {
|
|
2939
|
+
context.set(user(options.input));
|
|
2940
|
+
}
|
|
2941
|
+
const temperature = RETRY_TEMPERATURES[attemptNumber - 1] ?? RETRY_TEMPERATURES[RETRY_TEMPERATURES.length - 1];
|
|
2942
|
+
const baseModel = options.model ?? groq3("openai/gpt-oss-20b");
|
|
2943
|
+
const model = wrapLanguageModel({
|
|
2944
|
+
model: baseModel,
|
|
2945
|
+
middleware: defaultSettingsMiddleware({ settings: { temperature } })
|
|
2946
|
+
});
|
|
2947
|
+
const sqlOutput = structuredOutput({
|
|
2948
|
+
model,
|
|
2949
|
+
context,
|
|
2950
|
+
schema: z3.union([
|
|
2951
|
+
z3.object({
|
|
2952
|
+
sql: z3.string().describe("The SQL query that answers the question"),
|
|
2953
|
+
reasoning: z3.string().optional().describe("The reasoning steps taken to generate the SQL")
|
|
2954
|
+
}),
|
|
2955
|
+
z3.object({
|
|
2956
|
+
error: z3.string().describe(
|
|
2957
|
+
"Error message explaining why the question cannot be answered with the given schema"
|
|
2958
|
+
)
|
|
2959
|
+
})
|
|
2960
|
+
])
|
|
2961
|
+
});
|
|
2962
|
+
const output = await sqlOutput.generate();
|
|
2963
|
+
if ("error" in output) {
|
|
2964
|
+
throw new UnanswerableSQLError(output.error);
|
|
2965
|
+
}
|
|
2966
|
+
const sql = extractSql(output.sql);
|
|
2967
|
+
const validationError = await options.adapter.validate(sql);
|
|
2968
|
+
if (validationError) {
|
|
2969
|
+
throw new SQLValidationError(validationError);
|
|
2970
|
+
}
|
|
2971
|
+
return {
|
|
2972
|
+
attempts,
|
|
2973
|
+
sql,
|
|
2974
|
+
errors: errors.length ? errors.map(formatErrorMessage) : void 0
|
|
2975
|
+
};
|
|
2976
|
+
},
|
|
2977
|
+
{ retries: maxRetries - 1 }
|
|
2978
|
+
);
|
|
2979
|
+
}
|
|
2980
|
+
function formatErrorMessage(error) {
|
|
2981
|
+
if (APICallError.isInstance(error)) {
|
|
2982
|
+
if (error.message.startsWith("Failed to validate JSON")) {
|
|
2983
|
+
return `Schema validation failed: ${error.message}`;
|
|
2984
|
+
}
|
|
2985
|
+
return error.message;
|
|
2986
|
+
}
|
|
2987
|
+
if (SQLValidationError.isInstance(error)) {
|
|
2988
|
+
return `SQL Validation Error: ${error.message}`;
|
|
2989
|
+
}
|
|
2990
|
+
return error.message;
|
|
2991
|
+
}
|
|
2992
|
+
async function withRetry(computation, options = { retries: 3 }) {
|
|
2993
|
+
const errors = [];
|
|
2994
|
+
let attempts = 0;
|
|
2995
|
+
return pRetry(
|
|
2996
|
+
(attemptNumber) => {
|
|
2997
|
+
return computation(attemptNumber, errors, ++attempts);
|
|
2998
|
+
},
|
|
2999
|
+
{
|
|
3000
|
+
retries: options.retries,
|
|
3001
|
+
shouldRetry: (context) => {
|
|
3002
|
+
if (UnanswerableSQLError.isInstance(context.error)) {
|
|
3003
|
+
return false;
|
|
3004
|
+
}
|
|
3005
|
+
if (SQLValidationError.isInstance(context.error)) {
|
|
3006
|
+
return true;
|
|
3007
|
+
}
|
|
3008
|
+
console.log({
|
|
3009
|
+
NoObjectGeneratedError: NoObjectGeneratedError.isInstance(
|
|
3010
|
+
context.error
|
|
3011
|
+
),
|
|
3012
|
+
NoOutputGeneratedError: NoOutputGeneratedError.isInstance(
|
|
3013
|
+
context.error
|
|
3014
|
+
),
|
|
3015
|
+
APICallError: APICallError.isInstance(context.error),
|
|
3016
|
+
JSONParseError: JSONParseError.isInstance(context.error),
|
|
3017
|
+
TypeValidationError: TypeValidationError.isInstance(context.error),
|
|
3018
|
+
NoContentGeneratedError: NoContentGeneratedError.isInstance(
|
|
3019
|
+
context.error
|
|
3020
|
+
)
|
|
3021
|
+
});
|
|
3022
|
+
return APICallError.isInstance(context.error) || JSONParseError.isInstance(context.error) || TypeValidationError.isInstance(context.error) || NoObjectGeneratedError.isInstance(context.error) || NoOutputGeneratedError.isInstance(context.error) || NoContentGeneratedError.isInstance(context.error);
|
|
3023
|
+
},
|
|
3024
|
+
onFailedAttempt(context) {
|
|
3025
|
+
logger.error(`toSQL`, context.error);
|
|
3026
|
+
console.log(
|
|
3027
|
+
`Attempt ${context.attemptNumber} failed. There are ${context.retriesLeft} retries left.`
|
|
3028
|
+
);
|
|
3029
|
+
errors.push(context.error);
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
);
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
// packages/text2sql/src/lib/agents/suggestions.agents.ts
|
|
3036
|
+
import { groq as groq4 } from "@ai-sdk/groq";
|
|
3037
|
+
import dedent2 from "dedent";
|
|
3038
|
+
import z4 from "zod";
|
|
3039
|
+
import { agent as agent2, thirdPersonPrompt } from "@deepagents/agent";
|
|
3040
|
+
var suggestionsAgent = agent2({
|
|
3041
|
+
name: "text2sql-suggestions",
|
|
3042
|
+
model: groq4("openai/gpt-oss-20b"),
|
|
3043
|
+
output: z4.object({
|
|
3044
|
+
suggestions: z4.array(
|
|
3045
|
+
z4.object({
|
|
3046
|
+
question: z4.string().describe("A complex, high-impact business question."),
|
|
3047
|
+
sql: z4.string().describe("The SQL statement needed to answer the question."),
|
|
3048
|
+
businessValue: z4.string().describe("Why the question matters to stakeholders.")
|
|
3049
|
+
})
|
|
3050
|
+
).min(1).max(5).describe("A set of up to two advanced question + SQL pairs.")
|
|
3051
|
+
}),
|
|
3052
|
+
prompt: (state) => {
|
|
3053
|
+
return dedent2`
|
|
3054
|
+
${thirdPersonPrompt()}
|
|
3055
|
+
|
|
3056
|
+
<identity>
|
|
3057
|
+
You are a senior analytics strategist who proposes ambitious business questions
|
|
3058
|
+
and drafts the SQL needed to answer them. You specialize in identifying ideas
|
|
3059
|
+
that combine multiple tables, apply segmentation or time analysis, and surface
|
|
3060
|
+
metrics that drive executive decisions.
|
|
3061
|
+
</identity>
|
|
3062
|
+
|
|
3063
|
+
|
|
3064
|
+
<instructions>
|
|
3065
|
+
- Recommend one or two UNIQUE questions that go beyond simple counts or listings.
|
|
3066
|
+
- Favor questions that require joins, aggregates, time comparisons, cohort analysis,
|
|
3067
|
+
or window functions.
|
|
3068
|
+
- For each question, explain the business reason stakeholders care about it.
|
|
3069
|
+
- Provide the complete SQL query that could answer the question in the given schema.
|
|
3070
|
+
- Keep result sets scoped with LIMIT clauses (max 50 rows) when returning raw rows.
|
|
3071
|
+
- Ensure table/column names match the provided schema exactly.
|
|
3072
|
+
- Use columns marked [LowCardinality: ...] to identify meaningful categorical filters or segmentations.
|
|
3073
|
+
- Leverage table [rows / size] hints to determine whether to aggregate (large tables) or inspect detailed data (tiny tables).
|
|
3074
|
+
- Reference PK/Indexed annotations and the Indexes list to recommend queries that use efficient join/filter paths.
|
|
3075
|
+
- Column annotations may expose ranges/null percentages—use them to suggest realistic thresholds or quality checks.
|
|
3076
|
+
- Consult <relationship_examples> to anchor your recommendations in the actual join paths between tables.
|
|
3077
|
+
- Output only information grounded in the schema/context provided.
|
|
3078
|
+
</instructions>
|
|
3079
|
+
|
|
3080
|
+
<response-format>
|
|
3081
|
+
Return valid JSON that satisfies the defined output schema.
|
|
3082
|
+
</response-format>
|
|
2308
3083
|
`;
|
|
2309
3084
|
}
|
|
2310
3085
|
});
|
|
2311
3086
|
|
|
2312
3087
|
// packages/text2sql/src/lib/agents/text2sql.agent.ts
|
|
2313
|
-
import { groq as groq3 } from "@ai-sdk/groq";
|
|
2314
3088
|
import { tool as tool2 } from "ai";
|
|
2315
|
-
import
|
|
3089
|
+
import z5 from "zod";
|
|
2316
3090
|
import { toState as toState2 } from "@deepagents/agent";
|
|
2317
3091
|
import { scratchpad_tool } from "@deepagents/toolbox";
|
|
2318
3092
|
var tools2 = {
|
|
2319
3093
|
validate_query: tool2({
|
|
2320
3094
|
description: `Validate SQL query syntax before execution. Use this to check if your SQL is valid before running db_query. This helps catch errors early and allows you to correct the query if needed.`,
|
|
2321
|
-
inputSchema:
|
|
2322
|
-
sql:
|
|
3095
|
+
inputSchema: z5.object({
|
|
3096
|
+
sql: z5.string().describe("The SQL query to validate.")
|
|
2323
3097
|
}),
|
|
2324
3098
|
execute: async ({ sql }, options) => {
|
|
2325
3099
|
const state = toState2(options);
|
|
@@ -2332,11 +3106,11 @@ var tools2 = {
|
|
|
2332
3106
|
}),
|
|
2333
3107
|
db_query: tool2({
|
|
2334
3108
|
description: `Internal tool to fetch data from the store's database. Write a SQL query to retrieve the information needed to answer the user's question. The results will be returned as data that you can then present to the user in natural language.`,
|
|
2335
|
-
inputSchema:
|
|
2336
|
-
reasoning:
|
|
3109
|
+
inputSchema: z5.object({
|
|
3110
|
+
reasoning: z5.string().describe(
|
|
2337
3111
|
"Your reasoning for why this SQL query is relevant to the user request."
|
|
2338
3112
|
),
|
|
2339
|
-
sql:
|
|
3113
|
+
sql: z5.string().min(1, { message: "SQL query cannot be empty." }).refine(
|
|
2340
3114
|
(sql) => sql.trim().toUpperCase().startsWith("SELECT") || sql.trim().toUpperCase().startsWith("WITH"),
|
|
2341
3115
|
{
|
|
2342
3116
|
message: "Only read-only SELECT or WITH queries are allowed."
|
|
@@ -2350,44 +3124,33 @@ var tools2 = {
|
|
|
2350
3124
|
}),
|
|
2351
3125
|
scratchpad: scratchpad_tool
|
|
2352
3126
|
};
|
|
2353
|
-
var t_a_g = agent({
|
|
2354
|
-
model: groq3("openai/gpt-oss-20b"),
|
|
2355
|
-
tools: tools2,
|
|
2356
|
-
name: "text2sql"
|
|
2357
|
-
// prompt: (state) => {
|
|
2358
|
-
// return `
|
|
2359
|
-
// ${state?.teachings || ''}
|
|
2360
|
-
// ${state?.introspection || ''}
|
|
2361
|
-
// ${chainOfThoughtPrompt}
|
|
2362
|
-
// ${fewShotExamples}
|
|
2363
|
-
// `;
|
|
2364
|
-
// },
|
|
2365
|
-
});
|
|
2366
3127
|
|
|
2367
3128
|
// packages/text2sql/src/lib/checkpoint.ts
|
|
2368
3129
|
import { createHash } from "node:crypto";
|
|
2369
3130
|
import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2370
3131
|
import pLimit from "p-limit";
|
|
2371
3132
|
var Checkpoint = class _Checkpoint {
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
3133
|
+
points;
|
|
3134
|
+
path;
|
|
3135
|
+
configHash;
|
|
3136
|
+
constructor(path3, configHash, points) {
|
|
2375
3137
|
this.points = points;
|
|
3138
|
+
this.path = path3;
|
|
3139
|
+
this.configHash = configHash;
|
|
2376
3140
|
}
|
|
2377
|
-
points;
|
|
2378
3141
|
/**
|
|
2379
3142
|
* Load checkpoint from file, or return empty checkpoint if none exists.
|
|
2380
3143
|
* Handles corrupted files and config changes gracefully.
|
|
2381
3144
|
*/
|
|
2382
3145
|
static async load(options) {
|
|
2383
|
-
const { path:
|
|
2384
|
-
if (existsSync(
|
|
3146
|
+
const { path: path3, configHash } = options;
|
|
3147
|
+
if (existsSync(path3)) {
|
|
2385
3148
|
try {
|
|
2386
|
-
const content = readFileSync(
|
|
3149
|
+
const content = readFileSync(path3, "utf-8");
|
|
2387
3150
|
const file = JSON.parse(content);
|
|
2388
3151
|
if (configHash && file.configHash && file.configHash !== configHash) {
|
|
2389
3152
|
console.log("\u26A0 Config changed, starting fresh");
|
|
2390
|
-
return new _Checkpoint(
|
|
3153
|
+
return new _Checkpoint(path3, configHash, {});
|
|
2391
3154
|
}
|
|
2392
3155
|
const points = file.points ?? {};
|
|
2393
3156
|
const totalEntries = Object.values(points).reduce(
|
|
@@ -2395,14 +3158,14 @@ var Checkpoint = class _Checkpoint {
|
|
|
2395
3158
|
0
|
|
2396
3159
|
);
|
|
2397
3160
|
console.log(`\u2713 Resuming from checkpoint (${totalEntries} entries)`);
|
|
2398
|
-
return new _Checkpoint(
|
|
3161
|
+
return new _Checkpoint(path3, configHash, points);
|
|
2399
3162
|
} catch {
|
|
2400
3163
|
console.log("\u26A0 Checkpoint corrupted, starting fresh");
|
|
2401
|
-
return new _Checkpoint(
|
|
3164
|
+
return new _Checkpoint(path3, configHash, {});
|
|
2402
3165
|
}
|
|
2403
3166
|
}
|
|
2404
3167
|
console.log("Starting new checkpoint");
|
|
2405
|
-
return new _Checkpoint(
|
|
3168
|
+
return new _Checkpoint(path3, configHash, {});
|
|
2406
3169
|
}
|
|
2407
3170
|
/**
|
|
2408
3171
|
* Run a single computation with checkpointing.
|
|
@@ -2487,14 +3250,16 @@ function hash(value) {
|
|
|
2487
3250
|
return createHash("md5").update(JSON.stringify(value)).digest("hex");
|
|
2488
3251
|
}
|
|
2489
3252
|
var Point = class {
|
|
3253
|
+
#cache;
|
|
3254
|
+
data;
|
|
3255
|
+
persist;
|
|
2490
3256
|
constructor(data, persist) {
|
|
2491
|
-
this.data = data;
|
|
2492
|
-
this.persist = persist;
|
|
2493
3257
|
this.#cache = new Map(
|
|
2494
3258
|
data.entries.map((e) => [e.inputHash, e.output])
|
|
2495
3259
|
);
|
|
3260
|
+
this.data = data;
|
|
3261
|
+
this.persist = persist;
|
|
2496
3262
|
}
|
|
2497
|
-
#cache;
|
|
2498
3263
|
/**
|
|
2499
3264
|
* Execute computation if input wasn't processed before.
|
|
2500
3265
|
* Returns cached output if input hash exists, otherwise executes, saves, and returns.
|
|
@@ -2534,12 +3299,12 @@ import { createHash as createHash2 } from "node:crypto";
|
|
|
2534
3299
|
import { existsSync as existsSync2 } from "node:fs";
|
|
2535
3300
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2536
3301
|
import { tmpdir } from "node:os";
|
|
2537
|
-
import
|
|
3302
|
+
import path2 from "node:path";
|
|
2538
3303
|
var FileCache = class {
|
|
2539
3304
|
path;
|
|
2540
3305
|
constructor(watermark, extension = ".txt") {
|
|
2541
3306
|
const hash2 = createHash2("md5").update(watermark).digest("hex");
|
|
2542
|
-
this.path =
|
|
3307
|
+
this.path = path2.join(tmpdir(), `text2sql-${hash2}${extension}`);
|
|
2543
3308
|
}
|
|
2544
3309
|
async get() {
|
|
2545
3310
|
if (existsSync2(this.path)) {
|
|
@@ -2567,302 +3332,328 @@ var JsonCache = class extends FileCache {
|
|
|
2567
3332
|
}
|
|
2568
3333
|
};
|
|
2569
3334
|
|
|
2570
|
-
// packages/text2sql/src/lib/
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
TypeValidationError,
|
|
2589
|
-
defaultSettingsMiddleware,
|
|
2590
|
-
wrapLanguageModel
|
|
2591
|
-
} from "ai";
|
|
2592
|
-
import { Console } from "node:console";
|
|
2593
|
-
import { createWriteStream } from "node:fs";
|
|
2594
|
-
import pRetry from "p-retry";
|
|
2595
|
-
import z5 from "zod";
|
|
2596
|
-
import "@deepagents/agent";
|
|
2597
|
-
var logger = new Console({
|
|
2598
|
-
stdout: createWriteStream("./sql-agent.log", { flags: "a" }),
|
|
2599
|
-
stderr: createWriteStream("./sql-agent-error.log", { flags: "a" }),
|
|
2600
|
-
inspectOptions: { depth: null }
|
|
2601
|
-
});
|
|
2602
|
-
var RETRY_TEMPERATURES = [0, 0.2, 0.3];
|
|
2603
|
-
function extractSql(output) {
|
|
2604
|
-
const match = output.match(/```sql\n?([\s\S]*?)```/);
|
|
2605
|
-
return match ? match[1].trim() : output.trim();
|
|
2606
|
-
}
|
|
2607
|
-
var marker = Symbol("SQLValidationError");
|
|
2608
|
-
var SQLValidationError = class _SQLValidationError extends Error {
|
|
2609
|
-
[marker];
|
|
2610
|
-
constructor(message2) {
|
|
2611
|
-
super(message2);
|
|
2612
|
-
this.name = "SQLValidationError";
|
|
2613
|
-
this[marker] = true;
|
|
2614
|
-
}
|
|
2615
|
-
static isInstance(error) {
|
|
2616
|
-
return error instanceof _SQLValidationError && error[marker] === true;
|
|
2617
|
-
}
|
|
2618
|
-
};
|
|
2619
|
-
var UnanswerableSQLError = class _UnanswerableSQLError extends Error {
|
|
2620
|
-
constructor(message2) {
|
|
2621
|
-
super(message2);
|
|
2622
|
-
this.name = "UnanswerableSQLError";
|
|
2623
|
-
}
|
|
2624
|
-
static isInstance(error) {
|
|
2625
|
-
return error instanceof _UnanswerableSQLError;
|
|
2626
|
-
}
|
|
2627
|
-
};
|
|
2628
|
-
async function toSql(options) {
|
|
2629
|
-
const { maxRetries = 3 } = options;
|
|
2630
|
-
return withRetry(
|
|
2631
|
-
async (attemptNumber, errors, attempts) => {
|
|
2632
|
-
const context = new ContextEngine({
|
|
2633
|
-
store: new InMemoryContextStore(),
|
|
2634
|
-
chatId: `sql-gen-${crypto.randomUUID()}`
|
|
2635
|
-
});
|
|
2636
|
-
context.set(
|
|
2637
|
-
persona({
|
|
2638
|
-
name: "Freya",
|
|
2639
|
-
role: "You are an expert SQL query generator. You translate natural language questions into precise, efficient SQL queries based on the provided database schema."
|
|
2640
|
-
}),
|
|
2641
|
-
...options.instructions,
|
|
2642
|
-
...options.schemaFragments
|
|
2643
|
-
);
|
|
2644
|
-
if (errors.length) {
|
|
2645
|
-
context.set(
|
|
2646
|
-
user(options.input),
|
|
2647
|
-
user(
|
|
2648
|
-
`<validation_error>Your previous SQL query had the following error: ${errors.at(-1)?.message}. Please fix the query.</validation_error>`
|
|
2649
|
-
)
|
|
2650
|
-
);
|
|
2651
|
-
} else {
|
|
2652
|
-
context.set(user(options.input));
|
|
2653
|
-
}
|
|
2654
|
-
const sqlOutput = structuredOutput({
|
|
2655
|
-
name: "text2sql",
|
|
2656
|
-
model: wrapLanguageModel({
|
|
2657
|
-
model: options.model ?? groq4("openai/gpt-oss-20b"),
|
|
2658
|
-
middleware: defaultSettingsMiddleware({
|
|
2659
|
-
settings: {
|
|
2660
|
-
temperature: RETRY_TEMPERATURES[attemptNumber - 1] ?? 0.3,
|
|
2661
|
-
topP: 1
|
|
2662
|
-
}
|
|
2663
|
-
})
|
|
2664
|
-
}),
|
|
2665
|
-
context,
|
|
2666
|
-
schema: z5.union([
|
|
2667
|
-
z5.object({
|
|
2668
|
-
sql: z5.string().describe("The SQL query that answers the question"),
|
|
2669
|
-
reasoning: z5.string().optional().describe("The reasoning steps taken to generate the SQL")
|
|
3335
|
+
// packages/text2sql/src/lib/instructions.ts
|
|
3336
|
+
function reasoningFramework() {
|
|
3337
|
+
return [
|
|
3338
|
+
role(
|
|
3339
|
+
"You are a very strong reasoner and planner. Use these critical instructions to structure your plans, thoughts, and responses."
|
|
3340
|
+
),
|
|
3341
|
+
fragment(
|
|
3342
|
+
"Meta-cognitive reasoning framework",
|
|
3343
|
+
hint(
|
|
3344
|
+
"Before taking any action (either tool calls *or* responses to the user), you must proactively, methodically, and independently plan and reason about:"
|
|
3345
|
+
),
|
|
3346
|
+
// 1) Logical dependencies and constraints
|
|
3347
|
+
principle({
|
|
3348
|
+
title: "Logical dependencies and constraints",
|
|
3349
|
+
description: "Analyze the intended action against the following factors. Resolve conflicts in order of importance:",
|
|
3350
|
+
policies: [
|
|
3351
|
+
policy({
|
|
3352
|
+
rule: "Policy-based rules, mandatory prerequisites, and constraints."
|
|
2670
3353
|
}),
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
3354
|
+
policy({
|
|
3355
|
+
rule: "Order of operations: Ensure taking an action does not prevent a subsequent necessary action.",
|
|
3356
|
+
policies: [
|
|
3357
|
+
"The user may request actions in a random order, but you may need to reorder operations to maximize successful completion of the task."
|
|
3358
|
+
]
|
|
3359
|
+
}),
|
|
3360
|
+
policy({
|
|
3361
|
+
rule: "Other prerequisites (information and/or actions needed)."
|
|
3362
|
+
}),
|
|
3363
|
+
policy({ rule: "Explicit user constraints or preferences." })
|
|
3364
|
+
]
|
|
3365
|
+
}),
|
|
3366
|
+
// 2) Risk assessment
|
|
3367
|
+
principle({
|
|
3368
|
+
title: "Risk assessment",
|
|
3369
|
+
description: "What are the consequences of taking the action? Will the new state cause any future issues?",
|
|
3370
|
+
policies: [
|
|
3371
|
+
"For exploratory tasks (like searches), missing *optional* parameters is a LOW risk. **Prefer calling the tool with the available information over asking the user, unless** your Rule 1 (Logical Dependencies) reasoning determines that optional information is required for a later step in your plan."
|
|
3372
|
+
]
|
|
3373
|
+
}),
|
|
3374
|
+
// 3) Abductive reasoning and hypothesis exploration
|
|
3375
|
+
principle({
|
|
3376
|
+
title: "Abductive reasoning and hypothesis exploration",
|
|
3377
|
+
description: "At each step, identify the most logical and likely reason for any problem encountered.",
|
|
3378
|
+
policies: [
|
|
3379
|
+
"Look beyond immediate or obvious causes. The most likely reason may not be the simplest and may require deeper inference.",
|
|
3380
|
+
"Hypotheses may require additional research. Each hypothesis may take multiple steps to test.",
|
|
3381
|
+
"Prioritize hypotheses based on likelihood, but do not discard less likely ones prematurely. A low-probability event may still be the root cause."
|
|
3382
|
+
]
|
|
3383
|
+
}),
|
|
3384
|
+
// 4) Outcome evaluation and adaptability
|
|
3385
|
+
principle({
|
|
3386
|
+
title: "Outcome evaluation and adaptability",
|
|
3387
|
+
description: "Does the previous observation require any changes to your plan?",
|
|
3388
|
+
policies: [
|
|
3389
|
+
"If your initial hypotheses are disproven, actively generate new ones based on the gathered information."
|
|
3390
|
+
]
|
|
3391
|
+
}),
|
|
3392
|
+
// 5) Information availability
|
|
3393
|
+
principle({
|
|
3394
|
+
title: "Information availability",
|
|
3395
|
+
description: "Incorporate all applicable and alternative sources of information, including:",
|
|
3396
|
+
policies: [
|
|
3397
|
+
"Using available tools and their capabilities",
|
|
3398
|
+
"All policies, rules, checklists, and constraints",
|
|
3399
|
+
"Previous observations and conversation history",
|
|
3400
|
+
"Information only available by asking the user"
|
|
3401
|
+
]
|
|
3402
|
+
}),
|
|
3403
|
+
// 6) Precision and Grounding
|
|
3404
|
+
principle({
|
|
3405
|
+
title: "Precision and Grounding",
|
|
3406
|
+
description: "Ensure your reasoning is extremely precise and relevant to each exact ongoing situation.",
|
|
3407
|
+
policies: [
|
|
3408
|
+
"Verify your claims by quoting the exact applicable information (including policies) when referring to them."
|
|
3409
|
+
]
|
|
3410
|
+
}),
|
|
3411
|
+
// 7) Completeness
|
|
3412
|
+
principle({
|
|
3413
|
+
title: "Completeness",
|
|
3414
|
+
description: "Ensure that all requirements, constraints, options, and preferences are exhaustively incorporated into your plan.",
|
|
3415
|
+
policies: [
|
|
3416
|
+
policy({
|
|
3417
|
+
rule: "Resolve conflicts using the order of importance in #1."
|
|
3418
|
+
}),
|
|
3419
|
+
policy({
|
|
3420
|
+
rule: "Avoid premature conclusions: There may be multiple relevant options for a given situation.",
|
|
3421
|
+
policies: [
|
|
3422
|
+
"To check for whether an option is relevant, reason about all information sources from #5.",
|
|
3423
|
+
"You may need to consult the user to even know whether something is applicable. Do not assume it is not applicable without checking."
|
|
3424
|
+
]
|
|
3425
|
+
}),
|
|
3426
|
+
policy({
|
|
3427
|
+
rule: "Review applicable sources of information from #5 to confirm which are relevant to the current state."
|
|
2675
3428
|
})
|
|
2676
|
-
]
|
|
2677
|
-
})
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
}
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
}
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
);
|
|
2695
|
-
}
|
|
2696
|
-
function formatErrorMessage(error) {
|
|
2697
|
-
if (APICallError.isInstance(error)) {
|
|
2698
|
-
if (error.message.startsWith("Failed to validate JSON")) {
|
|
2699
|
-
return `Schema validation failed: ${error.message}`;
|
|
2700
|
-
}
|
|
2701
|
-
return error.message;
|
|
2702
|
-
}
|
|
2703
|
-
if (SQLValidationError.isInstance(error)) {
|
|
2704
|
-
return `SQL Validation Error: ${error.message}`;
|
|
2705
|
-
}
|
|
2706
|
-
return error.message;
|
|
2707
|
-
}
|
|
2708
|
-
async function withRetry(computation, options = { retries: 3 }) {
|
|
2709
|
-
const errors = [];
|
|
2710
|
-
let attempts = 0;
|
|
2711
|
-
return pRetry(
|
|
2712
|
-
(attemptNumber) => {
|
|
2713
|
-
return computation(attemptNumber, errors, ++attempts);
|
|
2714
|
-
},
|
|
2715
|
-
{
|
|
2716
|
-
retries: options.retries,
|
|
2717
|
-
shouldRetry: (context) => {
|
|
2718
|
-
if (UnanswerableSQLError.isInstance(context.error)) {
|
|
2719
|
-
return false;
|
|
2720
|
-
}
|
|
2721
|
-
if (SQLValidationError.isInstance(context.error)) {
|
|
2722
|
-
return true;
|
|
2723
|
-
}
|
|
2724
|
-
console.log({
|
|
2725
|
-
NoObjectGeneratedError: NoObjectGeneratedError.isInstance(
|
|
2726
|
-
context.error
|
|
2727
|
-
),
|
|
2728
|
-
NoOutputGeneratedError: NoOutputGeneratedError.isInstance(
|
|
2729
|
-
context.error
|
|
2730
|
-
),
|
|
2731
|
-
APICallError: APICallError.isInstance(context.error),
|
|
2732
|
-
JSONParseError: JSONParseError.isInstance(context.error),
|
|
2733
|
-
TypeValidationError: TypeValidationError.isInstance(context.error),
|
|
2734
|
-
NoContentGeneratedError: NoContentGeneratedError.isInstance(
|
|
2735
|
-
context.error
|
|
2736
|
-
)
|
|
2737
|
-
});
|
|
2738
|
-
return APICallError.isInstance(context.error) || JSONParseError.isInstance(context.error) || TypeValidationError.isInstance(context.error) || NoObjectGeneratedError.isInstance(context.error) || NoOutputGeneratedError.isInstance(context.error) || NoContentGeneratedError.isInstance(context.error);
|
|
2739
|
-
},
|
|
2740
|
-
onFailedAttempt(context) {
|
|
2741
|
-
logger.error(`toSQL`, context.error);
|
|
2742
|
-
console.log(
|
|
2743
|
-
`Attempt ${context.attemptNumber} failed. There are ${context.retriesLeft} retries left.`
|
|
2744
|
-
);
|
|
2745
|
-
errors.push(context.error);
|
|
2746
|
-
}
|
|
2747
|
-
}
|
|
2748
|
-
);
|
|
3429
|
+
]
|
|
3430
|
+
}),
|
|
3431
|
+
// 8) Persistence and patience
|
|
3432
|
+
principle({
|
|
3433
|
+
title: "Persistence and patience",
|
|
3434
|
+
description: "Do not give up unless all the reasoning above is exhausted.",
|
|
3435
|
+
policies: [
|
|
3436
|
+
"Don't be dissuaded by time taken or user frustration.",
|
|
3437
|
+
"This persistence must be intelligent: On *transient* errors (e.g. please try again), you *must* retry **unless an explicit retry limit (e.g., max x tries) has been reached**. If such a limit is hit, you *must* stop. On *other* errors, you must change your strategy or arguments, not repeat the same failed call."
|
|
3438
|
+
]
|
|
3439
|
+
}),
|
|
3440
|
+
// 9) Inhibit your response
|
|
3441
|
+
principle({
|
|
3442
|
+
title: "Inhibit your response",
|
|
3443
|
+
description: "Only take an action after all the above reasoning is completed. Once you've taken an action, you cannot take it back."
|
|
3444
|
+
})
|
|
3445
|
+
)
|
|
3446
|
+
];
|
|
2749
3447
|
}
|
|
2750
|
-
|
|
2751
|
-
// packages/text2sql/src/lib/teach/teachings.ts
|
|
2752
3448
|
function guidelines(options = {}) {
|
|
2753
3449
|
const { date = "strict" } = options;
|
|
2754
3450
|
const baseTeachings = [
|
|
2755
|
-
//
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
"Apply proper aggregations (COUNT, SUM, AVG, etc.) when the question implies summarization."
|
|
2781
|
-
),
|
|
2782
|
-
hint(
|
|
2783
|
-
'When asked "how many X are there" about types/categories/statuses (e.g., "how many statuses are there?"), use COUNT(DISTINCT column). This asks about variety, not row count.'
|
|
3451
|
+
// Include the meta-cognitive reasoning framework
|
|
3452
|
+
...reasoningFramework(),
|
|
3453
|
+
// Prerequisite policies (must do X before Y)
|
|
3454
|
+
fragment(
|
|
3455
|
+
"Prerequisite policies",
|
|
3456
|
+
policy({
|
|
3457
|
+
rule: "YOU MUST inspect schema structure and available tables",
|
|
3458
|
+
before: "generating ANY SQL query",
|
|
3459
|
+
reason: "NEVER generate SQL without knowing valid tables, columns, and relationships"
|
|
3460
|
+
}),
|
|
3461
|
+
policy({
|
|
3462
|
+
rule: "YOU MUST resolve ambiguous business terms with the user",
|
|
3463
|
+
before: "making ANY assumptions about terminology meaning",
|
|
3464
|
+
reason: "NEVER guess domain-specific language\u2014ask for clarification"
|
|
3465
|
+
}),
|
|
3466
|
+
policy({
|
|
3467
|
+
rule: "YOU MUST validate SQL syntax",
|
|
3468
|
+
before: "executing ANY query against the database",
|
|
3469
|
+
reason: "NEVER execute unvalidated queries"
|
|
3470
|
+
}),
|
|
3471
|
+
policy({
|
|
3472
|
+
rule: "YOU MUST complete ALL reasoning steps",
|
|
3473
|
+
before: "taking ANY tool call or response action",
|
|
3474
|
+
reason: "Once an action is taken, it CANNOT be undone. NO EXCEPTIONS."
|
|
3475
|
+
})
|
|
2784
3476
|
),
|
|
2785
|
-
|
|
2786
|
-
|
|
3477
|
+
// Few-shot: Applying reasoning principles
|
|
3478
|
+
fragment(
|
|
3479
|
+
"Reasoning examples",
|
|
3480
|
+
example({
|
|
3481
|
+
question: "Show me sales last month",
|
|
3482
|
+
answer: `Applying Principle 1 (Logical dependencies):
|
|
3483
|
+
- Need: schema to know which table has sales data
|
|
3484
|
+
- Need: clarify "last month" = calendar month or rolling 30 days?
|
|
3485
|
+
|
|
3486
|
+
Applying Principle 5 (Information availability):
|
|
3487
|
+
- Schema shows: orders table with created_at, total columns
|
|
3488
|
+
- Missing: user's definition of "last month"
|
|
3489
|
+
|
|
3490
|
+
Action: Ask user for date range clarification BEFORE generating SQL.`
|
|
3491
|
+
}),
|
|
3492
|
+
example({
|
|
3493
|
+
question: "Why did my query return no results?",
|
|
3494
|
+
answer: `Applying Principle 3 (Abductive reasoning):
|
|
3495
|
+
- Hypothesis 1 (most likely): Filter too restrictive
|
|
3496
|
+
- Hypothesis 2: Data doesn't exist for that period
|
|
3497
|
+
- Hypothesis 3: JOIN eliminated matching rows
|
|
3498
|
+
|
|
3499
|
+
Testing hypotheses:
|
|
3500
|
+
1. Remove filters one by one to isolate the issue
|
|
3501
|
+
2. Check date range actually has data
|
|
3502
|
+
3. Run subqueries separately to verify each table
|
|
3503
|
+
|
|
3504
|
+
Action: Start with most likely hypothesis, test incrementally. NEVER guess.`
|
|
3505
|
+
}),
|
|
3506
|
+
example({
|
|
3507
|
+
question: "Get me the top customers",
|
|
3508
|
+
answer: `Applying Principle 1 (Logical dependencies):
|
|
3509
|
+
- "Top" is ambiguous\u2014by revenue? by order count? by recency?
|
|
3510
|
+
|
|
3511
|
+
Applying Principle 9 (Inhibition):
|
|
3512
|
+
- MUST NOT generate SQL until "top" is defined
|
|
3513
|
+
|
|
3514
|
+
Action: Ask user: "Top by what metric\u2014total revenue, number of orders, or most recent activity?"`
|
|
3515
|
+
})
|
|
2787
3516
|
),
|
|
2788
|
-
//
|
|
2789
|
-
|
|
2790
|
-
|
|
3517
|
+
// Schema adherence - consolidated into clear rules
|
|
3518
|
+
fragment(
|
|
3519
|
+
"Schema adherence",
|
|
3520
|
+
hint(
|
|
3521
|
+
"Use only tables and columns from the schema. For unspecified columns, use SELECT *. When showing related items, include IDs and requested details."
|
|
3522
|
+
),
|
|
3523
|
+
hint(
|
|
3524
|
+
'"Show" means list items; "count" or "total" means aggregate. Use canonical values verbatim for filtering.'
|
|
3525
|
+
)
|
|
2791
3526
|
),
|
|
3527
|
+
// Joins - use relationship metadata
|
|
2792
3528
|
hint(
|
|
2793
|
-
|
|
3529
|
+
"Use JOINs based on schema relationships. Favor PK/indexed columns; follow relationship metadata for direction and cardinality."
|
|
2794
3530
|
),
|
|
2795
|
-
|
|
2796
|
-
|
|
3531
|
+
// Aggregations - explain the concepts
|
|
3532
|
+
fragment(
|
|
3533
|
+
"Aggregations",
|
|
3534
|
+
hint(
|
|
3535
|
+
"Apply COUNT, SUM, AVG when the question implies summarization. Use window functions for ranking, running totals, or row comparisons."
|
|
3536
|
+
),
|
|
3537
|
+
explain({
|
|
3538
|
+
concept: "counting variety vs counting rows",
|
|
3539
|
+
explanation: '"How many types/categories/statuses exist?" asks about variety (unique values), not total row count',
|
|
3540
|
+
therefore: "Use COUNT(DISTINCT column) for variety questions"
|
|
3541
|
+
})
|
|
2797
3542
|
),
|
|
2798
|
-
|
|
2799
|
-
|
|
3543
|
+
// Query semantics - explain concepts and document quirks
|
|
3544
|
+
fragment(
|
|
3545
|
+
"Query interpretation",
|
|
3546
|
+
explain({
|
|
3547
|
+
concept: "threshold language",
|
|
3548
|
+
explanation: 'Words like "reach", "hit", "exceed" with a value imply a threshold being met or passed',
|
|
3549
|
+
therefore: "Translate to >= (greater than or equal), not = (exact match)"
|
|
3550
|
+
}),
|
|
3551
|
+
quirk({
|
|
3552
|
+
issue: "Contradictory WHERE conditions (e.g., value > 100 AND value < 50) return empty results",
|
|
3553
|
+
workaround: 'Use INTERSECT between separate queries when finding items "shared by" groups with mutually exclusive conditions'
|
|
3554
|
+
}),
|
|
3555
|
+
quirk({
|
|
3556
|
+
issue: "NULL values behave unexpectedly in comparisons and aggregations",
|
|
3557
|
+
workaround: "Use IS NULL, IS NOT NULL, or COALESCE() to handle NULLs explicitly"
|
|
3558
|
+
}),
|
|
3559
|
+
hint(
|
|
3560
|
+
"Always include mentioned filters from joined tables in WHERE conditions."
|
|
3561
|
+
)
|
|
2800
3562
|
),
|
|
2801
|
-
// Style
|
|
3563
|
+
// Style preferences
|
|
2802
3564
|
styleGuide({
|
|
2803
|
-
prefer:
|
|
2804
|
-
never: "
|
|
3565
|
+
prefer: "Full table names as aliases (users AS users). Descriptive column aliases (COUNT(*) AS total_count).",
|
|
3566
|
+
never: "Abbreviated aliases (u, oi) or positional aliases (t1, t2, a, b)."
|
|
2805
3567
|
}),
|
|
2806
3568
|
styleGuide({
|
|
2807
|
-
prefer: "
|
|
2808
|
-
}),
|
|
2809
|
-
// Guardrails - Query safety
|
|
2810
|
-
guardrail({
|
|
2811
|
-
rule: "Generate ONLY valid, executable SQL.",
|
|
2812
|
-
reason: "Invalid SQL wastes resources and confuses users.",
|
|
2813
|
-
action: "Validate syntax and schema references before returning."
|
|
3569
|
+
prefer: "Concise, business-friendly summaries with key comparisons and helpful follow-ups."
|
|
2814
3570
|
}),
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
3571
|
+
// Safety guardrails - consolidated
|
|
3572
|
+
fragment(
|
|
3573
|
+
"Query safety",
|
|
3574
|
+
guardrail({
|
|
3575
|
+
rule: "Generate only valid, executable SELECT/WITH statements.",
|
|
3576
|
+
reason: "Read-only access prevents data modification.",
|
|
3577
|
+
action: "Never generate INSERT, UPDATE, DELETE, DROP, or DDL statements."
|
|
3578
|
+
}),
|
|
3579
|
+
guardrail({
|
|
3580
|
+
rule: "Avoid unbounded scans and cartesian joins.",
|
|
3581
|
+
reason: "Protects performance and correctness.",
|
|
3582
|
+
action: "Apply filters on indexed columns. If join keys are unclear, ask for clarification."
|
|
3583
|
+
}),
|
|
3584
|
+
guardrail({
|
|
3585
|
+
rule: "Preserve query semantics.",
|
|
3586
|
+
reason: "Arbitrary modifications change results.",
|
|
3587
|
+
action: 'Only add LIMIT for explicit "top N" requests. Add ORDER BY for deterministic results.'
|
|
3588
|
+
}),
|
|
3589
|
+
guardrail({
|
|
3590
|
+
rule: "Seek clarification for genuine ambiguity.",
|
|
3591
|
+
reason: "Prevents incorrect assumptions.",
|
|
3592
|
+
action: "Ask a focused question before guessing."
|
|
3593
|
+
})
|
|
3594
|
+
),
|
|
3595
|
+
clarification({
|
|
3596
|
+
when: "Ambiguous ranking language (top, best, active) without a metric.",
|
|
3597
|
+
ask: "Clarify the ranking metric or definition.",
|
|
3598
|
+
reason: "Ensures correct aggregation and ordering."
|
|
2839
3599
|
}),
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
3600
|
+
hint(
|
|
3601
|
+
'Use sample cell values from schema hints to match exact casing and format in WHERE conditions (e.g., "Male" vs "male" vs "M").'
|
|
3602
|
+
),
|
|
3603
|
+
workflow({
|
|
3604
|
+
task: "SQL generation",
|
|
3605
|
+
steps: [
|
|
3606
|
+
"Schema linking: identify which tables and columns are mentioned or implied by the question.",
|
|
3607
|
+
"Check if column names match question terms (e.g., Total_X). Select directly if match found.",
|
|
3608
|
+
"Identify SQL patterns needed: aggregation, segmentation, time range, ranking.",
|
|
3609
|
+
"Select tables and relationships. Note lookup tables and filter values from schema.",
|
|
3610
|
+
"Plan join/filter/aggregation order based on table sizes and indexes.",
|
|
3611
|
+
"Generate SQL that answers the question.",
|
|
3612
|
+
"Verify: mentally translate SQL back to natural language. Does it match the original question?"
|
|
3613
|
+
]
|
|
2844
3614
|
}),
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
3615
|
+
workflow({
|
|
3616
|
+
task: "Error recovery",
|
|
3617
|
+
triggers: ["SQL error", "query failed", "execution error"],
|
|
3618
|
+
steps: [
|
|
3619
|
+
"Classify the error type: syntax error, missing join, wrong aggregation, invalid column, type mismatch.",
|
|
3620
|
+
"For syntax errors: check SQL keywords, quotes, parentheses balance.",
|
|
3621
|
+
"For missing join: identify unlinked tables and add appropriate JOIN clause.",
|
|
3622
|
+
"For wrong aggregation: verify GROUP BY includes all non-aggregated SELECT columns.",
|
|
3623
|
+
"For invalid column: re-check schema for correct column name and table.",
|
|
3624
|
+
"Apply targeted fix based on error classification. Avoid blind regeneration."
|
|
3625
|
+
],
|
|
3626
|
+
notes: "Maximum 3 retry attempts. If still failing, explain the issue to the user."
|
|
2849
3627
|
}),
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
3628
|
+
workflow({
|
|
3629
|
+
task: "Complex query decomposition",
|
|
3630
|
+
triggers: [
|
|
3631
|
+
"multiple conditions",
|
|
3632
|
+
"nested requirements",
|
|
3633
|
+
"compare across",
|
|
3634
|
+
"for each"
|
|
3635
|
+
],
|
|
3636
|
+
steps: [
|
|
3637
|
+
"Identify if question has multiple independent parts or nested dependencies.",
|
|
3638
|
+
"For independent parts: break into sub-questions, solve each, then combine with UNION or JOIN.",
|
|
3639
|
+
"For nested dependencies: solve inner requirement first, use result in outer query (subquery or CTE).",
|
|
3640
|
+
"For comparisons across groups: use window functions or self-joins.",
|
|
3641
|
+
"Combine sub-results into final answer. Verify completeness."
|
|
3642
|
+
],
|
|
3643
|
+
notes: "Complex questions often need CTEs (WITH clauses) for clarity and reusability."
|
|
2855
3644
|
}),
|
|
2856
|
-
// Workflow
|
|
2857
3645
|
workflow({
|
|
2858
|
-
task: "
|
|
3646
|
+
task: "Multi-turn context",
|
|
3647
|
+
triggers: ["follow-up", "and also", "what about", "same but", "instead"],
|
|
2859
3648
|
steps: [
|
|
2860
|
-
'
|
|
2861
|
-
"
|
|
2862
|
-
|
|
2863
|
-
"
|
|
2864
|
-
"
|
|
2865
|
-
|
|
3649
|
+
'Identify references to previous context: "it", "that", "those", "the same".',
|
|
3650
|
+
"Resolve references using conversation history: which tables, filters, or results were mentioned.",
|
|
3651
|
+
'For refinements ("but only X"): add filter to previous query.',
|
|
3652
|
+
'For extensions ("and also Y"): expand SELECT or add JOIN.',
|
|
3653
|
+
'For pivots ("what about Z instead"): replace the changed element, keep unchanged parts.',
|
|
3654
|
+
"Maintain consistency with previous query structure when possible."
|
|
3655
|
+
],
|
|
3656
|
+
notes: "If reference is ambiguous, ask which previous result or entity the user means."
|
|
2866
3657
|
})
|
|
2867
3658
|
];
|
|
2868
3659
|
if (date === "strict") {
|
|
@@ -2884,6 +3675,14 @@ function guidelines(options = {}) {
|
|
|
2884
3675
|
}
|
|
2885
3676
|
|
|
2886
3677
|
// packages/text2sql/src/lib/sql.ts
|
|
3678
|
+
import {
|
|
3679
|
+
APICallError as APICallError2,
|
|
3680
|
+
InvalidToolInputError,
|
|
3681
|
+
NoSuchToolError as NoSuchToolError2,
|
|
3682
|
+
ToolCallRepairError,
|
|
3683
|
+
generateId as generateId3
|
|
3684
|
+
} from "ai";
|
|
3685
|
+
import "@deepagents/agent";
|
|
2887
3686
|
var Text2Sql = class {
|
|
2888
3687
|
#config;
|
|
2889
3688
|
constructor(config) {
|
|
@@ -2971,21 +3770,60 @@ var Text2Sql = class {
|
|
|
2971
3770
|
const schemaFragments = await this.index();
|
|
2972
3771
|
const context = new ContextEngine({
|
|
2973
3772
|
store: this.#config.store,
|
|
2974
|
-
chatId: params.chatId
|
|
3773
|
+
chatId: params.chatId,
|
|
3774
|
+
userId: params.userId
|
|
2975
3775
|
}).set(
|
|
2976
|
-
...
|
|
3776
|
+
...schemaFragments,
|
|
2977
3777
|
...this.#buildRenderingInstructions(),
|
|
2978
|
-
|
|
3778
|
+
fragment(
|
|
3779
|
+
"Bash tool usage",
|
|
3780
|
+
workflow({
|
|
3781
|
+
task: "Query execution",
|
|
3782
|
+
steps: [
|
|
3783
|
+
'Execute SQL through bash tool: sql run "SELECT ..."',
|
|
3784
|
+
"Read the output: file path, column names, and row count.",
|
|
3785
|
+
"Use column names to construct jq filters: cat <path> | jq '.[] | {col1, col2}'",
|
|
3786
|
+
"For large results, slice first: cat <path> | jq '.[:10]'"
|
|
3787
|
+
]
|
|
3788
|
+
}),
|
|
3789
|
+
hint(
|
|
3790
|
+
`You cannot access sql through a tool, it'll fail so the proper way to access it is through the bash tool using "sql run" and "sql validate" commands.`
|
|
3791
|
+
),
|
|
3792
|
+
hint(
|
|
3793
|
+
"The sql command outputs: file path, column names (comma-separated), and row count. Use column names to construct precise jq queries."
|
|
3794
|
+
),
|
|
3795
|
+
hint(
|
|
3796
|
+
'This is virtual bash environment and "sql" commands proxy to the database hence you cannot access sql files directly.'
|
|
3797
|
+
),
|
|
3798
|
+
hint(
|
|
3799
|
+
"If a query fails, the sql command returns an error message in stderr."
|
|
3800
|
+
)
|
|
3801
|
+
),
|
|
3802
|
+
...this.#config.instructions
|
|
2979
3803
|
);
|
|
2980
3804
|
const userMsg = messages.at(-1);
|
|
2981
3805
|
if (userMsg) {
|
|
2982
|
-
context.set(
|
|
3806
|
+
context.set(message(userMsg));
|
|
2983
3807
|
await context.save();
|
|
2984
3808
|
}
|
|
2985
|
-
const
|
|
3809
|
+
const messageId = userMsg?.id ?? generateId3();
|
|
3810
|
+
const skillMounts = context.getSkillMounts();
|
|
3811
|
+
const { bash } = await createResultTools({
|
|
3812
|
+
adapter: this.#config.adapter,
|
|
3813
|
+
chatId: params.chatId,
|
|
3814
|
+
messageId,
|
|
3815
|
+
skillMounts
|
|
3816
|
+
});
|
|
3817
|
+
const chatAgent = agent({
|
|
3818
|
+
name: "text2sql",
|
|
2986
3819
|
model: this.#config.model,
|
|
2987
3820
|
context,
|
|
2988
|
-
tools: {
|
|
3821
|
+
tools: {
|
|
3822
|
+
bash,
|
|
3823
|
+
...this.#config.tools
|
|
3824
|
+
},
|
|
3825
|
+
guardrails: [errorRecoveryGuardrail],
|
|
3826
|
+
maxGuardrailRetries: 3
|
|
2989
3827
|
});
|
|
2990
3828
|
const result = await chatAgent.stream({});
|
|
2991
3829
|
return result.toUIMessageStream({
|
|
@@ -2994,6 +3832,7 @@ var Text2Sql = class {
|
|
|
2994
3832
|
sendFinish: true,
|
|
2995
3833
|
sendReasoning: true,
|
|
2996
3834
|
sendSources: true,
|
|
3835
|
+
originalMessages: messages,
|
|
2997
3836
|
generateMessageId: generateId3,
|
|
2998
3837
|
onFinish: async ({ responseMessage }) => {
|
|
2999
3838
|
context.set(assistant(responseMessage));
|
|
@@ -3009,7 +3848,8 @@ var Text2Sql = class {
|
|
|
3009
3848
|
const schemaFragments = await this.index();
|
|
3010
3849
|
const context = new ContextEngine({
|
|
3011
3850
|
store: this.#config.store,
|
|
3012
|
-
chatId: params.chatId
|
|
3851
|
+
chatId: params.chatId,
|
|
3852
|
+
userId: params.userId
|
|
3013
3853
|
}).set(
|
|
3014
3854
|
...developer_agent_default.fragments,
|
|
3015
3855
|
...this.#config.instructions,
|
|
@@ -3017,7 +3857,7 @@ var Text2Sql = class {
|
|
|
3017
3857
|
);
|
|
3018
3858
|
const userMsg = messages.at(-1);
|
|
3019
3859
|
if (userMsg) {
|
|
3020
|
-
context.set(
|
|
3860
|
+
context.set(message(userMsg));
|
|
3021
3861
|
await context.save();
|
|
3022
3862
|
}
|
|
3023
3863
|
const developerAgent = agent({
|
|
@@ -3043,7 +3883,7 @@ var Text2Sql = class {
|
|
|
3043
3883
|
});
|
|
3044
3884
|
}
|
|
3045
3885
|
#formatError(error) {
|
|
3046
|
-
if (
|
|
3886
|
+
if (NoSuchToolError2.isInstance(error)) {
|
|
3047
3887
|
return "The model tried to call an unknown tool.";
|
|
3048
3888
|
} else if (InvalidToolInputError.isInstance(error)) {
|
|
3049
3889
|
return "The model called a tool with invalid arguments.";
|
|
@@ -3062,10 +3902,13 @@ export {
|
|
|
3062
3902
|
FileCache,
|
|
3063
3903
|
JsonCache,
|
|
3064
3904
|
Point,
|
|
3905
|
+
SQLValidationError,
|
|
3065
3906
|
Text2Sql,
|
|
3907
|
+
UnanswerableSQLError,
|
|
3066
3908
|
applyTablesFilter,
|
|
3067
3909
|
column,
|
|
3068
3910
|
constraint,
|
|
3911
|
+
createResultTools,
|
|
3069
3912
|
dialectInfo,
|
|
3070
3913
|
filterRelationshipsByTables,
|
|
3071
3914
|
filterTablesByName,
|
|
@@ -3076,8 +3919,8 @@ export {
|
|
|
3076
3919
|
matchesFilter,
|
|
3077
3920
|
relationship,
|
|
3078
3921
|
suggestionsAgent,
|
|
3079
|
-
t_a_g,
|
|
3080
3922
|
table,
|
|
3923
|
+
toSql,
|
|
3081
3924
|
view
|
|
3082
3925
|
};
|
|
3083
3926
|
//# sourceMappingURL=index.js.map
|