@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.js CHANGED
@@ -389,7 +389,7 @@ var LM = class {
389
389
  #cache;
390
390
  #diskCache;
391
391
  #requestCount = 0;
392
- #tokenUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
392
+ #tokenUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0, cachedPromptTokens: 0 };
393
393
  constructor(model, cacheOptions = {}) {
394
394
  this.model = model;
395
395
  this.#cache = new LRUCache(cacheOptions.maxSize, cacheOptions.ttlMs);
@@ -423,6 +423,9 @@ var LM = class {
423
423
  this.#tokenUsage.promptTokens += response.usage.promptTokens;
424
424
  this.#tokenUsage.completionTokens += response.usage.completionTokens;
425
425
  this.#tokenUsage.totalTokens += response.usage.totalTokens;
426
+ if (response.usage.cachedPromptTokens) {
427
+ this.#tokenUsage.cachedPromptTokens += response.usage.cachedPromptTokens;
428
+ }
426
429
  }
427
430
  return response;
428
431
  }
@@ -469,21 +472,28 @@ var LM = class {
469
472
  // src/lm/adapters/OpenAI.ts
470
473
  var OpenAI = class extends LM {
471
474
  #options;
475
+ #client;
472
476
  constructor(options = {}) {
473
477
  super(options.model ?? "gpt-4o");
474
478
  this.#options = options;
475
479
  }
480
+ async #getClient() {
481
+ if (!this.#client) {
482
+ const { default: Client } = await import("openai").catch(() => {
483
+ throw new Error(
484
+ "The `openai` package is required for the OpenAI adapter.\nInstall it with: npm install openai"
485
+ );
486
+ });
487
+ this.#client = new Client({
488
+ apiKey: this.#options.apiKey ?? process.env["OPENAI_API_KEY"],
489
+ baseURL: this.#options.baseURL,
490
+ maxRetries: this.#options.maxRetries ?? 3
491
+ });
492
+ }
493
+ return this.#client;
494
+ }
476
495
  async _call(prompt, config) {
477
- const { default: OpenAIClient } = await import("openai").catch(() => {
478
- throw new Error(
479
- "The `openai` package is required for the OpenAI adapter.\nInstall it with: npm install openai"
480
- );
481
- });
482
- const client = new OpenAIClient({
483
- apiKey: this.#options.apiKey ?? process.env["OPENAI_API_KEY"],
484
- baseURL: this.#options.baseURL,
485
- maxRetries: this.#options.maxRetries ?? 3
486
- });
496
+ const client = await this.#getClient();
487
497
  const messages = typeof prompt === "string" ? [{ role: "user", content: prompt }] : prompt;
488
498
  const response = await client.chat.completions.create({
489
499
  model: config.model ?? this.model,
@@ -497,28 +507,21 @@ var OpenAI = class extends LM {
497
507
  const texts = (response.choices ?? []).map(
498
508
  (c) => c.message?.content ?? ""
499
509
  );
510
+ const usageDetails = response.usage;
500
511
  return {
501
512
  text: texts[0] ?? "",
502
513
  texts,
503
- usage: response.usage ? {
504
- promptTokens: response.usage.prompt_tokens,
505
- completionTokens: response.usage.completion_tokens,
506
- totalTokens: response.usage.total_tokens
514
+ usage: usageDetails ? {
515
+ promptTokens: usageDetails.prompt_tokens,
516
+ completionTokens: usageDetails.completion_tokens,
517
+ totalTokens: usageDetails.total_tokens,
518
+ ...usageDetails.prompt_tokens_details?.cached_tokens ? { cachedPromptTokens: usageDetails.prompt_tokens_details.cached_tokens } : {}
507
519
  } : null,
508
520
  raw: response
509
521
  };
510
522
  }
511
523
  async *stream(prompt, config = {}) {
512
- const { default: OpenAIClient } = await import("openai").catch(() => {
513
- throw new Error(
514
- "The `openai` package is required for the OpenAI adapter.\nInstall it with: npm install openai"
515
- );
516
- });
517
- const client = new OpenAIClient({
518
- apiKey: this.#options.apiKey ?? process.env["OPENAI_API_KEY"],
519
- baseURL: this.#options.baseURL,
520
- maxRetries: this.#options.maxRetries ?? 3
521
- });
524
+ const client = await this.#getClient();
522
525
  const messages = typeof prompt === "string" ? [{ role: "user", content: prompt }] : prompt;
523
526
  const stream = await client.chat.completions.create({
524
527
  model: config.model ?? this.model,
@@ -539,64 +542,164 @@ var OpenAI = class extends LM {
539
542
  }
540
543
  };
541
544
 
545
+ // src/settings/Settings.ts
546
+ import { AsyncLocalStorage } from "async_hooks";
547
+ var contextStore = new AsyncLocalStorage();
548
+ var Settings = class {
549
+ #global = {};
550
+ // ---------------------------------------------------------------------------
551
+ // Effective settings: async-context overrides take precedence over globals.
552
+ // ---------------------------------------------------------------------------
553
+ get #current() {
554
+ const ctx = contextStore.getStore();
555
+ return ctx !== void 0 ? { ...this.#global, ...ctx } : this.#global;
556
+ }
557
+ // ---------------------------------------------------------------------------
558
+ // Accessors
559
+ // ---------------------------------------------------------------------------
560
+ get lm() {
561
+ return this.#current.lm;
562
+ }
563
+ get rm() {
564
+ return this.#current.rm;
565
+ }
566
+ get lmConfig() {
567
+ return this.#current.lmConfig;
568
+ }
569
+ get logLevel() {
570
+ return this.#current.logLevel ?? "warn";
571
+ }
572
+ get cacheDir() {
573
+ return this.#current.cacheDir;
574
+ }
575
+ // ---------------------------------------------------------------------------
576
+ // Mutation
577
+ // ---------------------------------------------------------------------------
578
+ /**
579
+ * Merge `options` into the global settings. Existing keys are overwritten;
580
+ * omitted keys are unchanged. This does NOT affect currently running
581
+ * {@link Settings.context} scopes.
582
+ */
583
+ configure(options) {
584
+ this.#global = { ...this.#global, ...options };
585
+ }
586
+ /**
587
+ * Reset all global settings to their defaults.
588
+ */
589
+ reset() {
590
+ this.#global = {};
591
+ }
592
+ /**
593
+ * Return a deep-frozen snapshot of the currently effective settings
594
+ * (respects any active async-context overrides).
595
+ */
596
+ inspect() {
597
+ return Object.freeze({ ...this.#current });
598
+ }
599
+ /**
600
+ * Run `fn` inside an async-context-local settings scope.
601
+ *
602
+ * The `overrides` are merged on top of the current global settings and
603
+ * stored in an `AsyncLocalStorage` context. Concurrent calls each get
604
+ * their own isolated snapshot — they never overwrite each other's settings.
605
+ *
606
+ * @example
607
+ * ```ts
608
+ * // In an Express/Fastify handler:
609
+ * await settings.context({ lm: perRequestLM }, () => program.forward(inputs));
610
+ * ```
611
+ */
612
+ async context(overrides, fn) {
613
+ const merged = { ...this.#global, ...overrides };
614
+ return contextStore.run(merged, fn);
615
+ }
616
+ };
617
+ var settings = new Settings();
618
+
542
619
  // src/lm/adapters/Anthropic.ts
543
620
  var Anthropic = class extends LM {
544
621
  #options;
622
+ #client;
545
623
  constructor(options = {}) {
546
624
  super(options.model ?? "claude-3-5-sonnet-20241022");
547
625
  this.#options = options;
548
626
  }
627
+ async #getClient() {
628
+ if (!this.#client) {
629
+ const { default: Client } = await import("@anthropic-ai/sdk").catch(() => {
630
+ throw new Error(
631
+ "The `@anthropic-ai/sdk` package is required for the Anthropic adapter.\nInstall it with: npm install @anthropic-ai/sdk"
632
+ );
633
+ });
634
+ this.#client = new Client({
635
+ apiKey: this.#options.apiKey ?? process.env["ANTHROPIC_API_KEY"],
636
+ maxRetries: this.#options.maxRetries ?? 3
637
+ });
638
+ }
639
+ return this.#client;
640
+ }
549
641
  async _call(prompt, config) {
550
- const { default: Anthropic2 } = await import("@anthropic-ai/sdk").catch(() => {
551
- throw new Error(
552
- "The `@anthropic-ai/sdk` package is required for the Anthropic adapter.\nInstall it with: npm install @anthropic-ai/sdk"
553
- );
554
- });
555
- const client = new Anthropic2({
556
- apiKey: this.#options.apiKey ?? process.env["ANTHROPIC_API_KEY"],
557
- maxRetries: this.#options.maxRetries ?? 3
558
- });
642
+ const client = await this.#getClient();
643
+ const doCache = config.promptCaching ?? settings.lmConfig?.promptCaching ?? this.#options.promptCaching ?? false;
559
644
  const msgs = typeof prompt === "string" ? [{ role: "user", content: prompt }] : prompt;
560
645
  const systemMsg = msgs.find((m) => m.role === "system");
561
646
  const userMsgs = msgs.filter((m) => m.role !== "system");
647
+ let systemPayload;
648
+ if (systemMsg) {
649
+ systemPayload = doCache ? [{ type: "text", text: systemMsg.content, cache_control: { type: "ephemeral" } }] : systemMsg.content;
650
+ }
651
+ const messagesPayload = userMsgs.map((m, idx) => {
652
+ const isLast = idx === userMsgs.length - 1;
653
+ return {
654
+ role: m.role,
655
+ content: doCache && isLast ? [{ type: "text", text: m.content, cache_control: { type: "ephemeral" } }] : m.content
656
+ };
657
+ });
562
658
  const response = await client.messages.create({
563
659
  model: config.model ?? this.model,
564
660
  max_tokens: config.maxTokens ?? 1024,
565
- system: systemMsg?.content,
566
- messages: userMsgs.map((m) => ({ role: m.role, content: m.content })),
661
+ system: systemPayload,
662
+ messages: messagesPayload,
567
663
  temperature: config.temperature,
568
664
  ...config.extra ?? {}
569
665
  });
570
666
  const text = response.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("") ?? "";
667
+ const usage = response.usage;
668
+ const cachedInput = (usage.cache_read_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0);
571
669
  return {
572
670
  text,
573
671
  texts: [text],
574
- usage: response.usage ? {
575
- promptTokens: response.usage.input_tokens,
576
- completionTokens: response.usage.output_tokens,
577
- totalTokens: response.usage.input_tokens + response.usage.output_tokens
672
+ usage: usage ? {
673
+ promptTokens: usage.input_tokens,
674
+ completionTokens: usage.output_tokens,
675
+ totalTokens: usage.input_tokens + usage.output_tokens,
676
+ ...cachedInput > 0 ? { cachedPromptTokens: cachedInput } : {}
578
677
  } : null,
579
678
  raw: response
580
679
  };
581
680
  }
582
681
  async *stream(prompt, config = {}) {
583
- const { default: Anthropic2 } = await import("@anthropic-ai/sdk").catch(() => {
584
- throw new Error(
585
- "The `@anthropic-ai/sdk` package is required for the Anthropic adapter.\nInstall it with: npm install @anthropic-ai/sdk"
586
- );
587
- });
588
- const client = new Anthropic2({
589
- apiKey: this.#options.apiKey ?? process.env["ANTHROPIC_API_KEY"],
590
- maxRetries: this.#options.maxRetries ?? 3
591
- });
682
+ const client = await this.#getClient();
683
+ const doCache = config.promptCaching ?? settings.lmConfig?.promptCaching ?? this.#options.promptCaching ?? false;
592
684
  const msgs = typeof prompt === "string" ? [{ role: "user", content: prompt }] : prompt;
593
685
  const systemMsg = msgs.find((m) => m.role === "system");
594
686
  const userMsgs = msgs.filter((m) => m.role !== "system");
687
+ let systemPayload;
688
+ if (systemMsg) {
689
+ systemPayload = doCache ? [{ type: "text", text: systemMsg.content, cache_control: { type: "ephemeral" } }] : systemMsg.content;
690
+ }
691
+ const messagesPayload = userMsgs.map((m, idx) => {
692
+ const isLast = idx === userMsgs.length - 1;
693
+ return {
694
+ role: m.role,
695
+ content: doCache && isLast ? [{ type: "text", text: m.content, cache_control: { type: "ephemeral" } }] : m.content
696
+ };
697
+ });
595
698
  const stream = client.messages.stream({
596
699
  model: config.model ?? this.model,
597
700
  max_tokens: config.maxTokens ?? 1024,
598
- system: systemMsg?.content,
599
- messages: userMsgs.map((m) => ({ role: m.role, content: m.content })),
701
+ system: systemPayload,
702
+ messages: messagesPayload,
600
703
  ...config.extra ?? {}
601
704
  });
602
705
  for await (const event of stream) {
@@ -850,6 +953,9 @@ var MockLM = class extends LM {
850
953
  };
851
954
 
852
955
  // src/modules/Module.ts
956
+ function firstPrediction(result) {
957
+ return Array.isArray(result) ? result[0] ?? new Prediction({}) : result;
958
+ }
853
959
  var Module = class _Module {
854
960
  /**
855
961
  * Recursively discover all {@link Predict} sub-modules by walking the own
@@ -913,80 +1019,6 @@ var Module = class _Module {
913
1019
  }
914
1020
  };
915
1021
 
916
- // src/settings/Settings.ts
917
- import { AsyncLocalStorage } from "async_hooks";
918
- var contextStore = new AsyncLocalStorage();
919
- var Settings = class {
920
- #global = {};
921
- // ---------------------------------------------------------------------------
922
- // Effective settings: async-context overrides take precedence over globals.
923
- // ---------------------------------------------------------------------------
924
- get #current() {
925
- const ctx = contextStore.getStore();
926
- return ctx !== void 0 ? { ...this.#global, ...ctx } : this.#global;
927
- }
928
- // ---------------------------------------------------------------------------
929
- // Accessors
930
- // ---------------------------------------------------------------------------
931
- get lm() {
932
- return this.#current.lm;
933
- }
934
- get rm() {
935
- return this.#current.rm;
936
- }
937
- get lmConfig() {
938
- return this.#current.lmConfig;
939
- }
940
- get logLevel() {
941
- return this.#current.logLevel ?? "warn";
942
- }
943
- get cacheDir() {
944
- return this.#current.cacheDir;
945
- }
946
- // ---------------------------------------------------------------------------
947
- // Mutation
948
- // ---------------------------------------------------------------------------
949
- /**
950
- * Merge `options` into the global settings. Existing keys are overwritten;
951
- * omitted keys are unchanged. This does NOT affect currently running
952
- * {@link Settings.context} scopes.
953
- */
954
- configure(options) {
955
- this.#global = { ...this.#global, ...options };
956
- }
957
- /**
958
- * Reset all global settings to their defaults.
959
- */
960
- reset() {
961
- this.#global = {};
962
- }
963
- /**
964
- * Return a deep-frozen snapshot of the currently effective settings
965
- * (respects any active async-context overrides).
966
- */
967
- inspect() {
968
- return Object.freeze({ ...this.#current });
969
- }
970
- /**
971
- * Run `fn` inside an async-context-local settings scope.
972
- *
973
- * The `overrides` are merged on top of the current global settings and
974
- * stored in an `AsyncLocalStorage` context. Concurrent calls each get
975
- * their own isolated snapshot — they never overwrite each other's settings.
976
- *
977
- * @example
978
- * ```ts
979
- * // In an Express/Fastify handler:
980
- * await settings.context({ lm: perRequestLM }, () => program.forward(inputs));
981
- * ```
982
- */
983
- async context(overrides, fn) {
984
- const merged = { ...this.#global, ...overrides };
985
- return contextStore.run(merged, fn);
986
- }
987
- };
988
- var settings = new Settings();
989
-
990
1022
  // src/modules/Predict.ts
991
1023
  var Predict = class extends Module {
992
1024
  signature;
@@ -1006,9 +1038,7 @@ var Predict = class extends Module {
1006
1038
  async forward(inputs) {
1007
1039
  const lm = settings.lm;
1008
1040
  if (!lm) {
1009
- throw new Error(
1010
- "No LM configured. Call settings.configure({ lm }) before using Predict."
1011
- );
1041
+ throw new Error("No LM configured. Call settings.configure({ lm }) before using Predict.");
1012
1042
  }
1013
1043
  const prompt = this.#buildPrompt(inputs);
1014
1044
  const config = settings.lmConfig ?? {};
@@ -1023,7 +1053,8 @@ var Predict = class extends Module {
1023
1053
  */
1024
1054
  async *stream(inputs) {
1025
1055
  const lm = settings.lm;
1026
- if (!lm) throw new Error("No LM configured. Call settings.configure({ lm }) before using Predict.");
1056
+ if (!lm)
1057
+ throw new Error("No LM configured. Call settings.configure({ lm }) before using Predict.");
1027
1058
  const prompt = this.#buildPrompt(inputs);
1028
1059
  const config = settings.lmConfig ?? {};
1029
1060
  yield* lm.stream(prompt, config);
@@ -1040,9 +1071,7 @@ var Predict = class extends Module {
1040
1071
  }
1041
1072
  load(state) {
1042
1073
  if (Array.isArray(state["demos"])) {
1043
- this.demos = state["demos"].map(
1044
- (d) => new Example(d)
1045
- );
1074
+ this.demos = state["demos"].map((d) => new Example(d));
1046
1075
  }
1047
1076
  if (typeof state["instructions"] === "string") {
1048
1077
  this.instructions = state["instructions"];
@@ -1063,32 +1092,80 @@ var Predict = class extends Module {
1063
1092
  }
1064
1093
  }
1065
1094
  lines.push(this.#formatInputs(inputs));
1066
- for (const [name] of this.signature.outputs) {
1067
- lines.push(`${name}:`);
1095
+ for (const [name, meta] of this.signature.outputs) {
1096
+ const label = meta.prefix ?? `${name}:`;
1097
+ lines.push(label.endsWith(":") ? label : `${label}:`);
1068
1098
  }
1069
1099
  return lines.join("\n");
1070
1100
  }
1071
1101
  #formatExample(data) {
1072
- return [...this.signature.inputs, ...this.signature.outputs].map(([name]) => `${name}: ${String(data[name] ?? "")}`).join("\n");
1102
+ const formatField = (name, meta) => {
1103
+ const label = meta?.prefix ?? `${name}:`;
1104
+ const prefix = label.endsWith(":") ? label : `${label}:`;
1105
+ return `${prefix} ${String(data[name] ?? "")}`;
1106
+ };
1107
+ return [
1108
+ ...[...this.signature.inputs].map(([name, meta]) => formatField(name, meta)),
1109
+ ...[...this.signature.outputs].map(([name, meta]) => formatField(name, meta))
1110
+ ].join("\n");
1073
1111
  }
1074
1112
  #formatInputs(inputs) {
1075
- return [...this.signature.inputs].map(([name]) => `${name}: ${String(inputs[name] ?? "")}`).join("\n");
1113
+ return [...this.signature.inputs].map(([name, meta]) => {
1114
+ const label = meta.prefix ?? `${name}:`;
1115
+ const prefix = label.endsWith(":") ? label : `${label}:`;
1116
+ return `${prefix} ${String(inputs[name] ?? "")}`;
1117
+ }).join("\n");
1076
1118
  }
1077
1119
  /**
1078
1120
  * Parse a raw completion string into a map of field name → value.
1079
1121
  *
1080
- * Looks for `fieldName: <value>` lines in the completion.
1122
+ * Looks for `fieldName: <value>` or `prefix: <value>` patterns in the completion.
1123
+ * Handles common LLM formatting like **bold**, *italic*, etc.
1124
+ * Supports multi-line values by extracting content between field markers.
1081
1125
  */
1082
1126
  #parseCompletion(text) {
1083
1127
  const result = {};
1084
- const outputKeys = [...this.signature.outputs.keys()];
1085
- for (const key of outputKeys) {
1086
- const regex = new RegExp(`^${key}:\\s*(.*)$`, "mi");
1087
- const match = regex.exec(text);
1088
- if (match) {
1089
- result[key] = (match[1] ?? "").trim();
1128
+ const outputs = [...this.signature.outputs.entries()];
1129
+ const fieldLabels = [];
1130
+ for (const [key, meta] of outputs) {
1131
+ const prefixBase = (meta.prefix ?? "").replace(/:$/, "").trim();
1132
+ const patterns = [key];
1133
+ if (prefixBase && prefixBase.toLowerCase() !== key.toLowerCase()) {
1134
+ patterns.push(prefixBase);
1135
+ }
1136
+ fieldLabels.push({ key, patterns });
1137
+ }
1138
+ const allPatterns = fieldLabels.flatMap((f) => f.patterns);
1139
+ const allPatternsEscaped = allPatterns.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
1140
+ const markerPattern = `(?:\\*\\*|\\*|_)?(${allPatternsEscaped.join("|")})(?:\\*\\*|\\*|_)?:`;
1141
+ const markerRegex = new RegExp(markerPattern, "gi");
1142
+ const markers = [];
1143
+ let markerMatch;
1144
+ while ((markerMatch = markerRegex.exec(text)) !== null) {
1145
+ const matchedLabel = markerMatch[1].toLowerCase();
1146
+ for (const { key, patterns } of fieldLabels) {
1147
+ if (patterns.some((p) => p.toLowerCase() === matchedLabel)) {
1148
+ markers.push({
1149
+ key,
1150
+ start: markerMatch.index,
1151
+ end: markerMatch.index + markerMatch[0].length
1152
+ });
1153
+ break;
1154
+ }
1090
1155
  }
1091
1156
  }
1157
+ for (let i = 0; i < markers.length; i++) {
1158
+ const marker = markers[i];
1159
+ const contentStart = marker.end;
1160
+ const contentEnd = i + 1 < markers.length ? markers[i + 1].start : text.length;
1161
+ let content = text.slice(contentStart, contentEnd).trim();
1162
+ content = content.replace(/^(\*\*|\*|_)+\s*/, "");
1163
+ content = content.replace(/\s*(\*\*|\*|_)+$/, "");
1164
+ if (!(marker.key in result)) {
1165
+ result[marker.key] = content;
1166
+ }
1167
+ }
1168
+ const outputKeys = outputs.map(([k]) => k);
1092
1169
  if (outputKeys.length === 1 && !(outputKeys[0] in result)) {
1093
1170
  result[outputKeys[0]] = text.trim();
1094
1171
  }
@@ -1134,9 +1211,6 @@ var ChainOfThoughtWithHint = class extends ChainOfThought {
1134
1211
  });
1135
1212
  Object.assign(this, { signature: extendedSig });
1136
1213
  }
1137
- async forward(inputs) {
1138
- return super.forward(inputs);
1139
- }
1140
1214
  };
1141
1215
 
1142
1216
  // src/modules/MultiChainComparison.ts
@@ -1154,14 +1228,23 @@ var MultiChainComparison = class extends Module {
1154
1228
  }
1155
1229
  async forward(inputs) {
1156
1230
  const completions = [];
1231
+ const outputKey = [...this.#cot.signature.outputs.keys()].find((k) => k !== "rationale") ?? "answer";
1157
1232
  for (let i = 0; i < this.M; i++) {
1158
1233
  const result = await this.#cot.forward(inputs);
1159
- const outputKey = [...this.#cot.signature.outputs.keys()].find((k) => k !== "rationale");
1160
- completions.push(String(result.get(outputKey ?? "answer") ?? ""));
1234
+ completions.push(String(result.get(outputKey) ?? ""));
1161
1235
  }
1162
- return this.#aggregator.forward({
1236
+ const aggregated = await this.#aggregator.forward({
1163
1237
  completions: completions.map((c, i) => `Option ${i + 1}: ${c}`).join("\n")
1164
1238
  });
1239
+ const rawAnswer = String(aggregated.get("answer") ?? "");
1240
+ const optionMatch = /^Option\s*(\d+)/i.exec(rawAnswer);
1241
+ if (optionMatch) {
1242
+ const optionIdx = parseInt(optionMatch[1], 10) - 1;
1243
+ if (optionIdx >= 0 && optionIdx < completions.length) {
1244
+ return new Prediction({ answer: completions[optionIdx] ?? rawAnswer });
1245
+ }
1246
+ }
1247
+ return new Prediction({ answer: rawAnswer });
1165
1248
  }
1166
1249
  };
1167
1250
 
@@ -1175,7 +1258,6 @@ var ReAct = class extends Module {
1175
1258
  this.tools = new Map(tools.map((t) => [t.name, t]));
1176
1259
  this.maxIter = maxIter;
1177
1260
  const toolDescriptions = tools.map((t) => `${t.name}: ${t.description}`).join("\n");
1178
- const base = typeof signature === "string" ? signature : signature;
1179
1261
  const instructions = `You are an agent. Use the following tools:
1180
1262
  ${toolDescriptions}
1181
1263
 
@@ -1185,9 +1267,12 @@ Action: <tool>[<args>]
1185
1267
  Observation: <result>
1186
1268
  ...
1187
1269
  Finish[<answer>]`;
1188
- this.#predictor = new Predict(
1189
- typeof base === "string" ? `${base}` : base
1190
- );
1270
+ let baseSig = typeof signature === "string" ? Signature.from(signature) : signature;
1271
+ baseSig = baseSig.withInput("trajectory", {
1272
+ description: "Interleaved reasoning steps so far",
1273
+ optional: true
1274
+ });
1275
+ this.#predictor = new Predict(baseSig);
1191
1276
  this.#predictor.instructions = instructions;
1192
1277
  }
1193
1278
  async forward(inputs) {
@@ -1270,10 +1355,11 @@ var ProgramOfThought = class extends Module {
1270
1355
  for (let attempt = 0; attempt < this.maxAttempts; attempt++) {
1271
1356
  const genInputs = attempt === 0 ? {
1272
1357
  ...inputs,
1273
- instructions: "Write JavaScript code to compute the answer. Use a `return` statement for the final value."
1358
+ 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."
1274
1359
  } : { code, error: lastError };
1275
1360
  const generated = attempt === 0 ? await this.#codeGenerator.forward(genInputs) : await this.#corrector.forward(genInputs);
1276
1361
  code = String(generated.get("code") ?? generated.get("fixed_code") ?? "");
1362
+ code = code.replace(/^```(?:\w+)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
1277
1363
  try {
1278
1364
  if (this.sandbox === "worker") {
1279
1365
  result = await this.#executeInWorker(code, this.timeoutMs);
@@ -1412,7 +1498,9 @@ var Retry = class extends Module {
1412
1498
  let lastError;
1413
1499
  for (let attempt = 0; attempt < this.maxAttempts; attempt++) {
1414
1500
  try {
1415
- return await this.#inner.forward(...args);
1501
+ return firstPrediction(
1502
+ await this.#inner.forward(...args)
1503
+ );
1416
1504
  } catch (err) {
1417
1505
  if (err instanceof AssertionError) {
1418
1506
  lastError = err;
@@ -1447,7 +1535,7 @@ var BestOfN = class extends Module {
1447
1535
  () => this.#inner.forward(...args)
1448
1536
  )
1449
1537
  );
1450
- return this.#reduce(results);
1538
+ return this.#reduce(results.map(firstPrediction));
1451
1539
  }
1452
1540
  };
1453
1541
 
@@ -1466,7 +1554,7 @@ var Ensemble = class extends Module {
1466
1554
  (m) => m.forward(...args)
1467
1555
  )
1468
1556
  );
1469
- return this.#reduce(results);
1557
+ return this.#reduce(results.map(firstPrediction));
1470
1558
  }
1471
1559
  };
1472
1560
 
@@ -1575,6 +1663,7 @@ var TypedChainOfThought = class extends TypedPredictor {
1575
1663
  };
1576
1664
 
1577
1665
  // src/modules/Parallel.ts
1666
+ import { setTimeout as setTimeout2 } from "timers";
1578
1667
  var Parallel = class extends Module {
1579
1668
  #modules;
1580
1669
  #timeoutMs;
@@ -1583,29 +1672,37 @@ var Parallel = class extends Module {
1583
1672
  this.#modules = modules;
1584
1673
  this.#timeoutMs = options.timeoutMs;
1585
1674
  }
1586
- /** Run all modules in parallel and return all predictions. */
1675
+ /**
1676
+ * Run all modules in parallel and return one {@link Prediction} per module.
1677
+ * If a module's `forward()` returns multiple predictions, the first is used.
1678
+ */
1587
1679
  async run(...args) {
1588
1680
  const tasks = this.#modules.map(
1589
1681
  (m) => m.forward(...args)
1590
1682
  );
1683
+ let settled;
1591
1684
  if (this.#timeoutMs !== void 0) {
1592
1685
  const timeoutMs = this.#timeoutMs;
1593
1686
  const withTimeout = tasks.map(
1594
1687
  (t) => Promise.race([
1595
1688
  t,
1596
1689
  new Promise(
1597
- (_, reject) => setTimeout(() => reject(new Error("Parallel: timeout")), timeoutMs)
1690
+ (_, reject) => setTimeout2(() => reject(new Error("Parallel: timeout")), timeoutMs)
1598
1691
  )
1599
1692
  ])
1600
1693
  );
1601
- return Promise.all(withTimeout);
1694
+ settled = Promise.all(withTimeout);
1695
+ } else {
1696
+ settled = Promise.all(tasks);
1602
1697
  }
1603
- return Promise.all(tasks);
1698
+ return (await settled).map(firstPrediction);
1604
1699
  }
1605
- /** For Module interface compatibility — returns first prediction. */
1700
+ /**
1701
+ * Execute all modules in parallel and return all predictions as an array
1702
+ * (one entry per module).
1703
+ */
1606
1704
  async forward(...args) {
1607
- const results = await this.run(...args);
1608
- return results[0];
1705
+ return this.run(...args);
1609
1706
  }
1610
1707
  };
1611
1708
 
@@ -1626,7 +1723,7 @@ var Refine = class extends Module {
1626
1723
  }
1627
1724
  async forward(...args) {
1628
1725
  const innerForward = this.#inner.forward.bind(this.#inner);
1629
- let prediction = await innerForward(...args);
1726
+ let prediction = firstPrediction(await innerForward(...args));
1630
1727
  for (let i = 0; i < this.#maxRefinements; i++) {
1631
1728
  if (this.#stopCondition?.(prediction)) break;
1632
1729
  const outputStr = JSON.stringify(prediction.toDict());
@@ -1649,7 +1746,7 @@ var Refine = class extends Module {
1649
1746
  };
1650
1747
  }
1651
1748
  try {
1652
- prediction = await innerForward(...newArgs);
1749
+ prediction = firstPrediction(await innerForward(...newArgs));
1653
1750
  } catch {
1654
1751
  break;
1655
1752
  }
@@ -2773,6 +2870,7 @@ export {
2773
2870
  evaluate,
2774
2871
  exactMatch,
2775
2872
  f1,
2873
+ firstPrediction,
2776
2874
  majority,
2777
2875
  passAtK,
2778
2876
  rouge,