@jaypie/llm 1.3.12 → 1.3.13

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.
@@ -485,6 +485,15 @@ function resolveModelChain(model) {
485
485
  * Providers can extend this class to reduce boilerplate.
486
486
  */
487
487
  class BaseProviderAdapter {
488
+ constructor() {
489
+ /**
490
+ * Whether OperateLoop may take a corrective turn when a `format` request
491
+ * completes with prose instead of structured output. Providers whose
492
+ * structured output rides a tool emulation (where compliance is a model
493
+ * decision) opt in; native grammar-constrained providers do not need it.
494
+ */
495
+ this.supportsStructuredOutputRetry = false;
496
+ }
488
497
  /**
489
498
  * Default implementation checks if error is retryable via classifyError
490
499
  */
@@ -3412,6 +3421,10 @@ class FireworksAdapter extends BaseProviderAdapter {
3412
3421
  super(...arguments);
3413
3422
  this.name = PROVIDER.FIREWORKS.NAME;
3414
3423
  this.defaultModel = PROVIDER.FIREWORKS.DEFAULT;
3424
+ // Structured output with tools rides the structured_output tool emulation
3425
+ // (Fireworks rejects response_format + tools), and emulation compliance is
3426
+ // a model decision — opt in to OperateLoop's corrective retry turn.
3427
+ this.supportsStructuredOutputRetry = true;
3415
3428
  // Session-level cache of models observed to reject native
3416
3429
  // `response_format: json_schema`. When a model is in this set, buildRequest
3417
3430
  // engages the legacy fake-tool path instead of native structured output.
@@ -3466,9 +3479,10 @@ class FireworksAdapter extends BaseProviderAdapter {
3466
3479
  const useFallbackStructuredOutput = Boolean(request.format) &&
3467
3480
  (hasCallerTools ||
3468
3481
  !this.supportsStructuredOutput(fireworksRequest.model));
3469
- const allTools = request.tools
3470
- ? [...request.tools]
3471
- : [];
3482
+ // On a corrective retry turn (the model answered a format request with
3483
+ // prose), offer only the structured_output tool so the demanded call is
3484
+ // the sole option.
3485
+ const allTools = request.tools && !request.structuredOutputRetry ? [...request.tools] : [];
3472
3486
  if (useFallbackStructuredOutput && request.format) {
3473
3487
  log$1.log.warn(hasCallerTools
3474
3488
  ? `[FireworksAdapter] Fireworks does not support response_format combined with tools; using structured_output tool emulation for model ${fireworksRequest.model}.`
@@ -8009,6 +8023,31 @@ function createErrorClassifier(adapter) {
8009
8023
  },
8010
8024
  };
8011
8025
  }
8026
+ /**
8027
+ * Attempt to read a prose response as the structured payload itself: parse the
8028
+ * text (stripping a Markdown code fence if present) and return the object, or
8029
+ * undefined when the text is not a JSON object.
8030
+ */
8031
+ function tryParseJsonObject(text) {
8032
+ let candidate = text.trim();
8033
+ const fence = candidate.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/);
8034
+ if (fence) {
8035
+ candidate = fence[1].trim();
8036
+ }
8037
+ if (!candidate.startsWith("{")) {
8038
+ return undefined;
8039
+ }
8040
+ try {
8041
+ const parsed = JSON.parse(candidate);
8042
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
8043
+ return parsed;
8044
+ }
8045
+ }
8046
+ catch {
8047
+ // Not JSON; caller falls through to the corrective-turn path.
8048
+ }
8049
+ return undefined;
8050
+ }
8012
8051
  //
8013
8052
  //
8014
8053
  // Main
@@ -8077,6 +8116,7 @@ class OperateLoop {
8077
8116
  messages: state.currentInput,
8078
8117
  model: modelName,
8079
8118
  providerOptions: options.providerOptions,
8119
+ structuredOutputRetry: state.structuredOutputRetry,
8080
8120
  system: options.system,
8081
8121
  temperature: options.temperature,
8082
8122
  tools: state.formattedTools,
@@ -8480,6 +8520,42 @@ class OperateLoop {
8480
8520
  return true; // Continue to next turn
8481
8521
  }
8482
8522
  }
8523
+ // Format contract enforcement: the loop is about to complete but the
8524
+ // model answered with prose instead of structured output.
8525
+ if (state.formattedFormat && typeof parsed.content === "string") {
8526
+ // First salvage attempt: the text may be the JSON itself (with or
8527
+ // without a code fence).
8528
+ const salvaged = tryParseJsonObject(parsed.content);
8529
+ if (salvaged) {
8530
+ state.responseBuilder.setContent(this.applyFormatArrayDefaults(salvaged, options));
8531
+ state.responseBuilder.complete();
8532
+ for (const item of this.adapter.responseToHistoryItems(parsed.raw)) {
8533
+ state.responseBuilder.appendToHistory(item);
8534
+ }
8535
+ return false; // Stop loop
8536
+ }
8537
+ // Corrective turn: for adapters whose structured output rides a tool
8538
+ // emulation, take another turn offering only the structured_output tool
8539
+ // and demand it be called. Bounded by maxTurns.
8540
+ if (this.adapter.supportsStructuredOutputRetry &&
8541
+ state.currentTurn < state.maxTurns) {
8542
+ log.warn(`[operate] Model returned text despite format on turn ${state.currentTurn}; retrying with structured_output tool only`);
8543
+ for (const item of this.adapter.responseToHistoryItems(parsed.raw)) {
8544
+ state.currentInput.push(item);
8545
+ state.responseBuilder.appendToHistory(item);
8546
+ }
8547
+ const corrective = {
8548
+ content: "You must provide your final answer by calling the structured_output tool " +
8549
+ "with arguments matching the required schema. Do not respond with text.",
8550
+ role: exports.LlmMessageRole.User,
8551
+ type: exports.LlmMessageType.Message,
8552
+ };
8553
+ state.currentInput.push(corrective);
8554
+ state.responseBuilder.appendToHistory(corrective);
8555
+ state.structuredOutputRetry = true;
8556
+ return true; // Continue to corrective turn
8557
+ }
8558
+ }
8483
8559
  // No tool calls or no toolkit - we're done
8484
8560
  state.responseBuilder.setContent(this.applyFormatArrayDefaults(parsed.content, options));
8485
8561
  state.responseBuilder.complete();