Skip to content

[BUG] OpenRouter responseFormat ignored when tools are configured but unused #2917

@PlaneInABottle

Description

@PlaneInABottle

Summary

OpenRouter agent blocks with a response format and tools enabled were not applying the response format when the model returned no tool calls. This is an OpenRouter-wide behavior (not just Grok), and it explains why structured output fields (for example file_key) were missing and why fallback warnings appeared.

Root cause

In the OpenRouter provider, the final structured-output call was only made when tools were configured AND toolCalls.length > 0. If the model decided not to use any tools, the structured-output pass was skipped and the raw model response was returned, bypassing the response format entirely.

Expected behavior

When responseFormat is configured, the final response should be coerced into the structured schema even if no tools were called, as long as the model supports structured output. When tool calls are present, the existing structured-output behavior should remain unchanged.

Actual behavior

If no tool calls were returned:

  • The structured-output pass was skipped
  • The model returned its own freeform output
  • Required fields (e.g., file_key) were missing
  • The system emitted a structured-output fallback warning

Fix

I fixed it by applying the structured-output pass when tools are configured even if no tool calls were returned. When tool calls are present, the existing structured-output path is unchanged.

// If responseFormat is set and tools were configured, perform a final structured output call.
// This handles both cases: when tools were used (toolCalls.length > 0) and when the model
// decided not to use any tools.
if (request.responseFormat && hasActiveTools) {
  const noToolCallsUsed = toolCalls.length === 0
  if (noToolCallsUsed) {
    logger.info(
      'Tools configured but model returned no tool calls; applying responseFormat for structured output'
    )
  }

  const finalPayload: any = {
    model: payload.model,
    messages: [...currentMessages],
    response_format: request.responseFormat,
  }

  // ...

  timeSegments.push({
    type: 'model',
    name: noToolCallsUsed ? 'Structured output (no tool calls)' : 'Final structured response',
    startTime: finalStartTime,
    endTime: finalEndTime,
    duration: finalDuration,
  })
}

Notes on OpenRouter model support

OpenRouter includes models that do not support structured output, so those will still warn/fallback. This fix ensures that models which do support structured output now receive the response format correctly even when they skip tool calls.

Reproduction

  1. Create an agent block using any OpenRouter model
  2. Enable tools and set a custom responseFormat with a field like file_key
  3. Run a prompt where the model decides not to call tools
  4. Observe the missing field / fallback warning prior to the fix

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions