@jaex/dstsx 0.1.0 → 0.1.2

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.cjs CHANGED
@@ -99,6 +99,7 @@ __export(index_exports, {
99
99
  evaluate: () => evaluate,
100
100
  exactMatch: () => exactMatch,
101
101
  f1: () => f1,
102
+ firstPrediction: () => firstPrediction,
102
103
  majority: () => majority,
103
104
  passAtK: () => passAtK,
104
105
  rouge: () => rouge,
@@ -490,7 +491,7 @@ var LM = class {
490
491
  #cache;
491
492
  #diskCache;
492
493
  #requestCount = 0;
493
- #tokenUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
494
+ #tokenUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0, cachedPromptTokens: 0 };
494
495
  constructor(model, cacheOptions = {}) {
495
496
  this.model = model;
496
497
  this.#cache = new LRUCache(cacheOptions.maxSize, cacheOptions.ttlMs);
@@ -524,6 +525,9 @@ var LM = class {
524
525
  this.#tokenUsage.promptTokens += response.usage.promptTokens;
525
526
  this.#tokenUsage.completionTokens += response.usage.completionTokens;
526
527
  this.#tokenUsage.totalTokens += response.usage.totalTokens;
528
+ if (response.usage.cachedPromptTokens) {
529
+ this.#tokenUsage.cachedPromptTokens += response.usage.cachedPromptTokens;
530
+ }
527
531
  }
528
532
  return response;
529
533
  }
@@ -570,21 +574,28 @@ var LM = class {
570
574
  // src/lm/adapters/OpenAI.ts
571
575
  var OpenAI = class extends LM {
572
576
  #options;
577
+ #client;
573
578
  constructor(options = {}) {
574
579
  super(options.model ?? "gpt-4o");
575
580
  this.#options = options;
576
581
  }
582
+ async #getClient() {
583
+ if (!this.#client) {
584
+ const { default: Client } = await import("openai").catch(() => {
585
+ throw new Error(
586
+ "The `openai` package is required for the OpenAI adapter.\nInstall it with: npm install openai"
587
+ );
588
+ });
589
+ this.#client = new Client({
590
+ apiKey: this.#options.apiKey ?? process.env["OPENAI_API_KEY"],
591
+ baseURL: this.#options.baseURL,
592
+ maxRetries: this.#options.maxRetries ?? 3
593
+ });
594
+ }
595
+ return this.#client;
596
+ }
577
597
  async _call(prompt, config) {
578
- const { default: OpenAIClient } = await import("openai").catch(() => {
579
- throw new Error(
580
- "The `openai` package is required for the OpenAI adapter.\nInstall it with: npm install openai"
581
- );
582
- });
583
- const client = new OpenAIClient({
584
- apiKey: this.#options.apiKey ?? process.env["OPENAI_API_KEY"],
585
- baseURL: this.#options.baseURL,
586
- maxRetries: this.#options.maxRetries ?? 3
587
- });
598
+ const client = await this.#getClient();
588
599
  const messages = typeof prompt === "string" ? [{ role: "user", content: prompt }] : prompt;
589
600
  const response = await client.chat.completions.create({
590
601
  model: config.model ?? this.model,
@@ -598,28 +609,21 @@ var OpenAI = class extends LM {
598
609
  const texts = (response.choices ?? []).map(
599
610
  (c) => c.message?.content ?? ""
600
611
  );
612
+ const usageDetails = response.usage;
601
613
  return {
602
614
  text: texts[0] ?? "",
603
615
  texts,
604
- usage: response.usage ? {
605
- promptTokens: response.usage.prompt_tokens,
606
- completionTokens: response.usage.completion_tokens,
607
- totalTokens: response.usage.total_tokens
616
+ usage: usageDetails ? {
617
+ promptTokens: usageDetails.prompt_tokens,
618
+ completionTokens: usageDetails.completion_tokens,
619
+ totalTokens: usageDetails.total_tokens,
620
+ ...usageDetails.prompt_tokens_details?.cached_tokens ? { cachedPromptTokens: usageDetails.prompt_tokens_details.cached_tokens } : {}
608
621
  } : null,
609
622
  raw: response
610
623
  };
611
624
  }
612
625
  async *stream(prompt, config = {}) {
613
- const { default: OpenAIClient } = await import("openai").catch(() => {
614
- throw new Error(
615
- "The `openai` package is required for the OpenAI adapter.\nInstall it with: npm install openai"
616
- );
617
- });
618
- const client = new OpenAIClient({
619
- apiKey: this.#options.apiKey ?? process.env["OPENAI_API_KEY"],
620
- baseURL: this.#options.baseURL,
621
- maxRetries: this.#options.maxRetries ?? 3
622
- });
626
+ const client = await this.#getClient();
623
627
  const messages = typeof prompt === "string" ? [{ role: "user", content: prompt }] : prompt;
624
628
  const stream = await client.chat.completions.create({
625
629
  model: config.model ?? this.model,
@@ -640,64 +644,164 @@ var OpenAI = class extends LM {
640
644
  }
641
645
  };
642
646
 
647
+ // src/settings/Settings.ts
648
+ var import_node_async_hooks = require("async_hooks");
649
+ var contextStore = new import_node_async_hooks.AsyncLocalStorage();
650
+ var Settings = class {
651
+ #global = {};
652
+ // ---------------------------------------------------------------------------
653
+ // Effective settings: async-context overrides take precedence over globals.
654
+ // ---------------------------------------------------------------------------
655
+ get #current() {
656
+ const ctx = contextStore.getStore();
657
+ return ctx !== void 0 ? { ...this.#global, ...ctx } : this.#global;
658
+ }
659
+ // ---------------------------------------------------------------------------
660
+ // Accessors
661
+ // ---------------------------------------------------------------------------
662
+ get lm() {
663
+ return this.#current.lm;
664
+ }
665
+ get rm() {
666
+ return this.#current.rm;
667
+ }
668
+ get lmConfig() {
669
+ return this.#current.lmConfig;
670
+ }
671
+ get logLevel() {
672
+ return this.#current.logLevel ?? "warn";
673
+ }
674
+ get cacheDir() {
675
+ return this.#current.cacheDir;
676
+ }
677
+ // ---------------------------------------------------------------------------
678
+ // Mutation
679
+ // ---------------------------------------------------------------------------
680
+ /**
681
+ * Merge `options` into the global settings. Existing keys are overwritten;
682
+ * omitted keys are unchanged. This does NOT affect currently running
683
+ * {@link Settings.context} scopes.
684
+ */
685
+ configure(options) {
686
+ this.#global = { ...this.#global, ...options };
687
+ }
688
+ /**
689
+ * Reset all global settings to their defaults.
690
+ */
691
+ reset() {
692
+ this.#global = {};
693
+ }
694
+ /**
695
+ * Return a deep-frozen snapshot of the currently effective settings
696
+ * (respects any active async-context overrides).
697
+ */
698
+ inspect() {
699
+ return Object.freeze({ ...this.#current });
700
+ }
701
+ /**
702
+ * Run `fn` inside an async-context-local settings scope.
703
+ *
704
+ * The `overrides` are merged on top of the current global settings and
705
+ * stored in an `AsyncLocalStorage` context. Concurrent calls each get
706
+ * their own isolated snapshot — they never overwrite each other's settings.
707
+ *
708
+ * @example
709
+ * ```ts
710
+ * // In an Express/Fastify handler:
711
+ * await settings.context({ lm: perRequestLM }, () => program.forward(inputs));
712
+ * ```
713
+ */
714
+ async context(overrides, fn) {
715
+ const merged = { ...this.#global, ...overrides };
716
+ return contextStore.run(merged, fn);
717
+ }
718
+ };
719
+ var settings = new Settings();
720
+
643
721
  // src/lm/adapters/Anthropic.ts
644
722
  var Anthropic = class extends LM {
645
723
  #options;
724
+ #client;
646
725
  constructor(options = {}) {
647
726
  super(options.model ?? "claude-3-5-sonnet-20241022");
648
727
  this.#options = options;
649
728
  }
729
+ async #getClient() {
730
+ if (!this.#client) {
731
+ const { default: Client } = await import("@anthropic-ai/sdk").catch(() => {
732
+ throw new Error(
733
+ "The `@anthropic-ai/sdk` package is required for the Anthropic adapter.\nInstall it with: npm install @anthropic-ai/sdk"
734
+ );
735
+ });
736
+ this.#client = new Client({
737
+ apiKey: this.#options.apiKey ?? process.env["ANTHROPIC_API_KEY"],
738
+ maxRetries: this.#options.maxRetries ?? 3
739
+ });
740
+ }
741
+ return this.#client;
742
+ }
650
743
  async _call(prompt, config) {
651
- const { default: Anthropic2 } = await import("@anthropic-ai/sdk").catch(() => {
652
- throw new Error(
653
- "The `@anthropic-ai/sdk` package is required for the Anthropic adapter.\nInstall it with: npm install @anthropic-ai/sdk"
654
- );
655
- });
656
- const client = new Anthropic2({
657
- apiKey: this.#options.apiKey ?? process.env["ANTHROPIC_API_KEY"],
658
- maxRetries: this.#options.maxRetries ?? 3
659
- });
744
+ const client = await this.#getClient();
745
+ const doCache = config.promptCaching ?? settings.lmConfig?.promptCaching ?? this.#options.promptCaching ?? false;
660
746
  const msgs = typeof prompt === "string" ? [{ role: "user", content: prompt }] : prompt;
661
747
  const systemMsg = msgs.find((m) => m.role === "system");
662
748
  const userMsgs = msgs.filter((m) => m.role !== "system");
749
+ let systemPayload;
750
+ if (systemMsg) {
751
+ systemPayload = doCache ? [{ type: "text", text: systemMsg.content, cache_control: { type: "ephemeral" } }] : systemMsg.content;
752
+ }
753
+ const messagesPayload = userMsgs.map((m, idx) => {
754
+ const isLast = idx === userMsgs.length - 1;
755
+ return {
756
+ role: m.role,
757
+ content: doCache && isLast ? [{ type: "text", text: m.content, cache_control: { type: "ephemeral" } }] : m.content
758
+ };
759
+ });
663
760
  const response = await client.messages.create({
664
761
  model: config.model ?? this.model,
665
762
  max_tokens: config.maxTokens ?? 1024,
666
- system: systemMsg?.content,
667
- messages: userMsgs.map((m) => ({ role: m.role, content: m.content })),
763
+ system: systemPayload,
764
+ messages: messagesPayload,
668
765
  temperature: config.temperature,
669
766
  ...config.extra ?? {}
670
767
  });
671
768
  const text = response.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("") ?? "";
769
+ const usage = response.usage;
770
+ const cachedInput = (usage.cache_read_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0);
672
771
  return {
673
772
  text,
674
773
  texts: [text],
675
- usage: response.usage ? {
676
- promptTokens: response.usage.input_tokens,
677
- completionTokens: response.usage.output_tokens,
678
- totalTokens: response.usage.input_tokens + response.usage.output_tokens
774
+ usage: usage ? {
775
+ promptTokens: usage.input_tokens,
776
+ completionTokens: usage.output_tokens,
777
+ totalTokens: usage.input_tokens + usage.output_tokens,
778
+ ...cachedInput > 0 ? { cachedPromptTokens: cachedInput } : {}
679
779
  } : null,
680
780
  raw: response
681
781
  };
682
782
  }
683
783
  async *stream(prompt, config = {}) {
684
- const { default: Anthropic2 } = await import("@anthropic-ai/sdk").catch(() => {
685
- throw new Error(
686
- "The `@anthropic-ai/sdk` package is required for the Anthropic adapter.\nInstall it with: npm install @anthropic-ai/sdk"
687
- );
688
- });
689
- const client = new Anthropic2({
690
- apiKey: this.#options.apiKey ?? process.env["ANTHROPIC_API_KEY"],
691
- maxRetries: this.#options.maxRetries ?? 3
692
- });
784
+ const client = await this.#getClient();
785
+ const doCache = config.promptCaching ?? settings.lmConfig?.promptCaching ?? this.#options.promptCaching ?? false;
693
786
  const msgs = typeof prompt === "string" ? [{ role: "user", content: prompt }] : prompt;
694
787
  const systemMsg = msgs.find((m) => m.role === "system");
695
788
  const userMsgs = msgs.filter((m) => m.role !== "system");
789
+ let systemPayload;
790
+ if (systemMsg) {
791
+ systemPayload = doCache ? [{ type: "text", text: systemMsg.content, cache_control: { type: "ephemeral" } }] : systemMsg.content;
792
+ }
793
+ const messagesPayload = userMsgs.map((m, idx) => {
794
+ const isLast = idx === userMsgs.length - 1;
795
+ return {
796
+ role: m.role,
797
+ content: doCache && isLast ? [{ type: "text", text: m.content, cache_control: { type: "ephemeral" } }] : m.content
798
+ };
799
+ });
696
800
  const stream = client.messages.stream({
697
801
  model: config.model ?? this.model,
698
802
  max_tokens: config.maxTokens ?? 1024,
699
- system: systemMsg?.content,
700
- messages: userMsgs.map((m) => ({ role: m.role, content: m.content })),
803
+ system: systemPayload,
804
+ messages: messagesPayload,
701
805
  ...config.extra ?? {}
702
806
  });
703
807
  for await (const event of stream) {
@@ -951,6 +1055,9 @@ var MockLM = class extends LM {
951
1055
  };
952
1056
 
953
1057
  // src/modules/Module.ts
1058
+ function firstPrediction(result) {
1059
+ return Array.isArray(result) ? result[0] ?? new Prediction({}) : result;
1060
+ }
954
1061
  var Module = class _Module {
955
1062
  /**
956
1063
  * Recursively discover all {@link Predict} sub-modules by walking the own
@@ -1014,80 +1121,6 @@ var Module = class _Module {
1014
1121
  }
1015
1122
  };
1016
1123
 
1017
- // src/settings/Settings.ts
1018
- var import_node_async_hooks = require("async_hooks");
1019
- var contextStore = new import_node_async_hooks.AsyncLocalStorage();
1020
- var Settings = class {
1021
- #global = {};
1022
- // ---------------------------------------------------------------------------
1023
- // Effective settings: async-context overrides take precedence over globals.
1024
- // ---------------------------------------------------------------------------
1025
- get #current() {
1026
- const ctx = contextStore.getStore();
1027
- return ctx !== void 0 ? { ...this.#global, ...ctx } : this.#global;
1028
- }
1029
- // ---------------------------------------------------------------------------
1030
- // Accessors
1031
- // ---------------------------------------------------------------------------
1032
- get lm() {
1033
- return this.#current.lm;
1034
- }
1035
- get rm() {
1036
- return this.#current.rm;
1037
- }
1038
- get lmConfig() {
1039
- return this.#current.lmConfig;
1040
- }
1041
- get logLevel() {
1042
- return this.#current.logLevel ?? "warn";
1043
- }
1044
- get cacheDir() {
1045
- return this.#current.cacheDir;
1046
- }
1047
- // ---------------------------------------------------------------------------
1048
- // Mutation
1049
- // ---------------------------------------------------------------------------
1050
- /**
1051
- * Merge `options` into the global settings. Existing keys are overwritten;
1052
- * omitted keys are unchanged. This does NOT affect currently running
1053
- * {@link Settings.context} scopes.
1054
- */
1055
- configure(options) {
1056
- this.#global = { ...this.#global, ...options };
1057
- }
1058
- /**
1059
- * Reset all global settings to their defaults.
1060
- */
1061
- reset() {
1062
- this.#global = {};
1063
- }
1064
- /**
1065
- * Return a deep-frozen snapshot of the currently effective settings
1066
- * (respects any active async-context overrides).
1067
- */
1068
- inspect() {
1069
- return Object.freeze({ ...this.#current });
1070
- }
1071
- /**
1072
- * Run `fn` inside an async-context-local settings scope.
1073
- *
1074
- * The `overrides` are merged on top of the current global settings and
1075
- * stored in an `AsyncLocalStorage` context. Concurrent calls each get
1076
- * their own isolated snapshot — they never overwrite each other's settings.
1077
- *
1078
- * @example
1079
- * ```ts
1080
- * // In an Express/Fastify handler:
1081
- * await settings.context({ lm: perRequestLM }, () => program.forward(inputs));
1082
- * ```
1083
- */
1084
- async context(overrides, fn) {
1085
- const merged = { ...this.#global, ...overrides };
1086
- return contextStore.run(merged, fn);
1087
- }
1088
- };
1089
- var settings = new Settings();
1090
-
1091
1124
  // src/modules/Predict.ts
1092
1125
  var Predict = class extends Module {
1093
1126
  signature;
@@ -1107,9 +1140,7 @@ var Predict = class extends Module {
1107
1140
  async forward(inputs) {
1108
1141
  const lm = settings.lm;
1109
1142
  if (!lm) {
1110
- throw new Error(
1111
- "No LM configured. Call settings.configure({ lm }) before using Predict."
1112
- );
1143
+ throw new Error("No LM configured. Call settings.configure({ lm }) before using Predict.");
1113
1144
  }
1114
1145
  const prompt = this.#buildPrompt(inputs);
1115
1146
  const config = settings.lmConfig ?? {};
@@ -1124,7 +1155,8 @@ var Predict = class extends Module {
1124
1155
  */
1125
1156
  async *stream(inputs) {
1126
1157
  const lm = settings.lm;
1127
- if (!lm) throw new Error("No LM configured. Call settings.configure({ lm }) before using Predict.");
1158
+ if (!lm)
1159
+ throw new Error("No LM configured. Call settings.configure({ lm }) before using Predict.");
1128
1160
  const prompt = this.#buildPrompt(inputs);
1129
1161
  const config = settings.lmConfig ?? {};
1130
1162
  yield* lm.stream(prompt, config);
@@ -1141,9 +1173,7 @@ var Predict = class extends Module {
1141
1173
  }
1142
1174
  load(state) {
1143
1175
  if (Array.isArray(state["demos"])) {
1144
- this.demos = state["demos"].map(
1145
- (d) => new Example(d)
1146
- );
1176
+ this.demos = state["demos"].map((d) => new Example(d));
1147
1177
  }
1148
1178
  if (typeof state["instructions"] === "string") {
1149
1179
  this.instructions = state["instructions"];
@@ -1164,32 +1194,80 @@ var Predict = class extends Module {
1164
1194
  }
1165
1195
  }
1166
1196
  lines.push(this.#formatInputs(inputs));
1167
- for (const [name] of this.signature.outputs) {
1168
- lines.push(`${name}:`);
1197
+ for (const [name, meta] of this.signature.outputs) {
1198
+ const label = meta.prefix ?? `${name}:`;
1199
+ lines.push(label.endsWith(":") ? label : `${label}:`);
1169
1200
  }
1170
1201
  return lines.join("\n");
1171
1202
  }
1172
1203
  #formatExample(data) {
1173
- return [...this.signature.inputs, ...this.signature.outputs].map(([name]) => `${name}: ${String(data[name] ?? "")}`).join("\n");
1204
+ const formatField = (name, meta) => {
1205
+ const label = meta?.prefix ?? `${name}:`;
1206
+ const prefix = label.endsWith(":") ? label : `${label}:`;
1207
+ return `${prefix} ${String(data[name] ?? "")}`;
1208
+ };
1209
+ return [
1210
+ ...[...this.signature.inputs].map(([name, meta]) => formatField(name, meta)),
1211
+ ...[...this.signature.outputs].map(([name, meta]) => formatField(name, meta))
1212
+ ].join("\n");
1174
1213
  }
1175
1214
  #formatInputs(inputs) {
1176
- return [...this.signature.inputs].map(([name]) => `${name}: ${String(inputs[name] ?? "")}`).join("\n");
1215
+ return [...this.signature.inputs].map(([name, meta]) => {
1216
+ const label = meta.prefix ?? `${name}:`;
1217
+ const prefix = label.endsWith(":") ? label : `${label}:`;
1218
+ return `${prefix} ${String(inputs[name] ?? "")}`;
1219
+ }).join("\n");
1177
1220
  }
1178
1221
  /**
1179
1222
  * Parse a raw completion string into a map of field name → value.
1180
1223
  *
1181
- * Looks for `fieldName: <value>` lines in the completion.
1224
+ * Looks for `fieldName: <value>` or `prefix: <value>` patterns in the completion.
1225
+ * Handles common LLM formatting like **bold**, *italic*, etc.
1226
+ * Supports multi-line values by extracting content between field markers.
1182
1227
  */
1183
1228
  #parseCompletion(text) {
1184
1229
  const result = {};
1185
- const outputKeys = [...this.signature.outputs.keys()];
1186
- for (const key of outputKeys) {
1187
- const regex = new RegExp(`^${key}:\\s*(.*)$`, "mi");
1188
- const match = regex.exec(text);
1189
- if (match) {
1190
- result[key] = (match[1] ?? "").trim();
1230
+ const outputs = [...this.signature.outputs.entries()];
1231
+ const fieldLabels = [];
1232
+ for (const [key, meta] of outputs) {
1233
+ const prefixBase = (meta.prefix ?? "").replace(/:$/, "").trim();
1234
+ const patterns = [key];
1235
+ if (prefixBase && prefixBase.toLowerCase() !== key.toLowerCase()) {
1236
+ patterns.push(prefixBase);
1237
+ }
1238
+ fieldLabels.push({ key, patterns });
1239
+ }
1240
+ const allPatterns = fieldLabels.flatMap((f) => f.patterns);
1241
+ const allPatternsEscaped = allPatterns.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
1242
+ const markerPattern = `(?:\\*\\*|\\*|_)?(${allPatternsEscaped.join("|")})(?:\\*\\*|\\*|_)?:`;
1243
+ const markerRegex = new RegExp(markerPattern, "gi");
1244
+ const markers = [];
1245
+ let markerMatch;
1246
+ while ((markerMatch = markerRegex.exec(text)) !== null) {
1247
+ const matchedLabel = markerMatch[1].toLowerCase();
1248
+ for (const { key, patterns } of fieldLabels) {
1249
+ if (patterns.some((p) => p.toLowerCase() === matchedLabel)) {
1250
+ markers.push({
1251
+ key,
1252
+ start: markerMatch.index,
1253
+ end: markerMatch.index + markerMatch[0].length
1254
+ });
1255
+ break;
1256
+ }
1191
1257
  }
1192
1258
  }
1259
+ for (let i = 0; i < markers.length; i++) {
1260
+ const marker = markers[i];
1261
+ const contentStart = marker.end;
1262
+ const contentEnd = i + 1 < markers.length ? markers[i + 1].start : text.length;
1263
+ let content = text.slice(contentStart, contentEnd).trim();
1264
+ content = content.replace(/^(\*\*|\*|_)+\s*/, "");
1265
+ content = content.replace(/\s*(\*\*|\*|_)+$/, "");
1266
+ if (!(marker.key in result)) {
1267
+ result[marker.key] = content;
1268
+ }
1269
+ }
1270
+ const outputKeys = outputs.map(([k]) => k);
1193
1271
  if (outputKeys.length === 1 && !(outputKeys[0] in result)) {
1194
1272
  result[outputKeys[0]] = text.trim();
1195
1273
  }
@@ -1235,9 +1313,6 @@ var ChainOfThoughtWithHint = class extends ChainOfThought {
1235
1313
  });
1236
1314
  Object.assign(this, { signature: extendedSig });
1237
1315
  }
1238
- async forward(inputs) {
1239
- return super.forward(inputs);
1240
- }
1241
1316
  };
1242
1317
 
1243
1318
  // src/modules/MultiChainComparison.ts
@@ -1255,14 +1330,23 @@ var MultiChainComparison = class extends Module {
1255
1330
  }
1256
1331
  async forward(inputs) {
1257
1332
  const completions = [];
1333
+ const outputKey = [...this.#cot.signature.outputs.keys()].find((k) => k !== "rationale") ?? "answer";
1258
1334
  for (let i = 0; i < this.M; i++) {
1259
1335
  const result = await this.#cot.forward(inputs);
1260
- const outputKey = [...this.#cot.signature.outputs.keys()].find((k) => k !== "rationale");
1261
- completions.push(String(result.get(outputKey ?? "answer") ?? ""));
1336
+ completions.push(String(result.get(outputKey) ?? ""));
1262
1337
  }
1263
- return this.#aggregator.forward({
1338
+ const aggregated = await this.#aggregator.forward({
1264
1339
  completions: completions.map((c, i) => `Option ${i + 1}: ${c}`).join("\n")
1265
1340
  });
1341
+ const rawAnswer = String(aggregated.get("answer") ?? "");
1342
+ const optionMatch = /^Option\s*(\d+)/i.exec(rawAnswer);
1343
+ if (optionMatch) {
1344
+ const optionIdx = parseInt(optionMatch[1], 10) - 1;
1345
+ if (optionIdx >= 0 && optionIdx < completions.length) {
1346
+ return new Prediction({ answer: completions[optionIdx] ?? rawAnswer });
1347
+ }
1348
+ }
1349
+ return new Prediction({ answer: rawAnswer });
1266
1350
  }
1267
1351
  };
1268
1352
 
@@ -1276,7 +1360,6 @@ var ReAct = class extends Module {
1276
1360
  this.tools = new Map(tools.map((t) => [t.name, t]));
1277
1361
  this.maxIter = maxIter;
1278
1362
  const toolDescriptions = tools.map((t) => `${t.name}: ${t.description}`).join("\n");
1279
- const base = typeof signature === "string" ? signature : signature;
1280
1363
  const instructions = `You are an agent. Use the following tools:
1281
1364
  ${toolDescriptions}
1282
1365
 
@@ -1286,9 +1369,12 @@ Action: <tool>[<args>]
1286
1369
  Observation: <result>
1287
1370
  ...
1288
1371
  Finish[<answer>]`;
1289
- this.#predictor = new Predict(
1290
- typeof base === "string" ? `${base}` : base
1291
- );
1372
+ let baseSig = typeof signature === "string" ? Signature.from(signature) : signature;
1373
+ baseSig = baseSig.withInput("trajectory", {
1374
+ description: "Interleaved reasoning steps so far",
1375
+ optional: true
1376
+ });
1377
+ this.#predictor = new Predict(baseSig);
1292
1378
  this.#predictor.instructions = instructions;
1293
1379
  }
1294
1380
  async forward(inputs) {
@@ -1371,10 +1457,11 @@ var ProgramOfThought = class extends Module {
1371
1457
  for (let attempt = 0; attempt < this.maxAttempts; attempt++) {
1372
1458
  const genInputs = attempt === 0 ? {
1373
1459
  ...inputs,
1374
- instructions: "Write JavaScript code to compute the answer. Use a `return` statement for the final value."
1460
+ instructions: "Write JavaScript code to compute the answer. IMPORTANT: Use a `return` statement (not console.log) for the final value. Do not include markdown code fences."
1375
1461
  } : { code, error: lastError };
1376
1462
  const generated = attempt === 0 ? await this.#codeGenerator.forward(genInputs) : await this.#corrector.forward(genInputs);
1377
1463
  code = String(generated.get("code") ?? generated.get("fixed_code") ?? "");
1464
+ code = code.replace(/^```(?:\w+)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
1378
1465
  try {
1379
1466
  if (this.sandbox === "worker") {
1380
1467
  result = await this.#executeInWorker(code, this.timeoutMs);
@@ -1513,7 +1600,9 @@ var Retry = class extends Module {
1513
1600
  let lastError;
1514
1601
  for (let attempt = 0; attempt < this.maxAttempts; attempt++) {
1515
1602
  try {
1516
- return await this.#inner.forward(...args);
1603
+ return firstPrediction(
1604
+ await this.#inner.forward(...args)
1605
+ );
1517
1606
  } catch (err) {
1518
1607
  if (err instanceof AssertionError) {
1519
1608
  lastError = err;
@@ -1548,7 +1637,7 @@ var BestOfN = class extends Module {
1548
1637
  () => this.#inner.forward(...args)
1549
1638
  )
1550
1639
  );
1551
- return this.#reduce(results);
1640
+ return this.#reduce(results.map(firstPrediction));
1552
1641
  }
1553
1642
  };
1554
1643
 
@@ -1567,7 +1656,7 @@ var Ensemble = class extends Module {
1567
1656
  (m) => m.forward(...args)
1568
1657
  )
1569
1658
  );
1570
- return this.#reduce(results);
1659
+ return this.#reduce(results.map(firstPrediction));
1571
1660
  }
1572
1661
  };
1573
1662
 
@@ -1676,6 +1765,7 @@ var TypedChainOfThought = class extends TypedPredictor {
1676
1765
  };
1677
1766
 
1678
1767
  // src/modules/Parallel.ts
1768
+ var import_node_timers = require("timers");
1679
1769
  var Parallel = class extends Module {
1680
1770
  #modules;
1681
1771
  #timeoutMs;
@@ -1684,29 +1774,37 @@ var Parallel = class extends Module {
1684
1774
  this.#modules = modules;
1685
1775
  this.#timeoutMs = options.timeoutMs;
1686
1776
  }
1687
- /** Run all modules in parallel and return all predictions. */
1777
+ /**
1778
+ * Run all modules in parallel and return one {@link Prediction} per module.
1779
+ * If a module's `forward()` returns multiple predictions, the first is used.
1780
+ */
1688
1781
  async run(...args) {
1689
1782
  const tasks = this.#modules.map(
1690
1783
  (m) => m.forward(...args)
1691
1784
  );
1785
+ let settled;
1692
1786
  if (this.#timeoutMs !== void 0) {
1693
1787
  const timeoutMs = this.#timeoutMs;
1694
1788
  const withTimeout = tasks.map(
1695
1789
  (t) => Promise.race([
1696
1790
  t,
1697
1791
  new Promise(
1698
- (_, reject) => setTimeout(() => reject(new Error("Parallel: timeout")), timeoutMs)
1792
+ (_, reject) => (0, import_node_timers.setTimeout)(() => reject(new Error("Parallel: timeout")), timeoutMs)
1699
1793
  )
1700
1794
  ])
1701
1795
  );
1702
- return Promise.all(withTimeout);
1796
+ settled = Promise.all(withTimeout);
1797
+ } else {
1798
+ settled = Promise.all(tasks);
1703
1799
  }
1704
- return Promise.all(tasks);
1800
+ return (await settled).map(firstPrediction);
1705
1801
  }
1706
- /** For Module interface compatibility — returns first prediction. */
1802
+ /**
1803
+ * Execute all modules in parallel and return all predictions as an array
1804
+ * (one entry per module).
1805
+ */
1707
1806
  async forward(...args) {
1708
- const results = await this.run(...args);
1709
- return results[0];
1807
+ return this.run(...args);
1710
1808
  }
1711
1809
  };
1712
1810
 
@@ -1727,7 +1825,7 @@ var Refine = class extends Module {
1727
1825
  }
1728
1826
  async forward(...args) {
1729
1827
  const innerForward = this.#inner.forward.bind(this.#inner);
1730
- let prediction = await innerForward(...args);
1828
+ let prediction = firstPrediction(await innerForward(...args));
1731
1829
  for (let i = 0; i < this.#maxRefinements; i++) {
1732
1830
  if (this.#stopCondition?.(prediction)) break;
1733
1831
  const outputStr = JSON.stringify(prediction.toDict());
@@ -1750,7 +1848,7 @@ var Refine = class extends Module {
1750
1848
  };
1751
1849
  }
1752
1850
  try {
1753
- prediction = await innerForward(...newArgs);
1851
+ prediction = firstPrediction(await innerForward(...newArgs));
1754
1852
  } catch {
1755
1853
  break;
1756
1854
  }
@@ -2875,6 +2973,7 @@ var JsonFileTracker = class extends Tracker {
2875
2973
  evaluate,
2876
2974
  exactMatch,
2877
2975
  f1,
2976
+ firstPrediction,
2878
2977
  majority,
2879
2978
  passAtK,
2880
2979
  rouge,