Skip to content

Bug: Tool format not transformed between common and provider-native formats #286

@dariye

Description

@dariye

Description

Active Agent documentation claims tools support a "universal common format" that works across all providers.
However, Active Agent 1.0.0 does NOT transform tools, causing OpenAI agents to fail with API errors when
using the documented common format.

Documentation vs Reality

Documentation claims (Tools Guide):
ActiveAgent supports a universal common format that works across all providers. Here's the reusable
pattern:

WEATHER_TOOL = {
name: "get_weather",
description: "Get current weather for a location",
parameters: {
 type: "object",
 properties: { ... }
}
}

This same definition works identically with Anthropic, OpenAI, Ollama, and OpenRouter without modification.

Reality: Tools are NOT transformed. OpenAI agents fail.

Steps to Reproduce

  1. Create an agent using OpenAI with the documented common format:
class MyAgent < ApplicationAgent
  generate_with :openai,
    api_version: :chat,
    instructions: { template: :instructions }

  def respond
    prompt(messages: build_messages, tools: my_tools)
  end

  private

  def my_tools
    [{
      name: "fetch_data",
      description: "Fetch some data",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string", description: "Search query" }
        },
        required: ["query"]
      }
    }]
  end
end
  1. Call the agent
  2. OpenAI API returns error

Expected Behavior

Tools should be transformed to provider-native format automatically, as documented.

Actual Behavior

  Error: Missing required parameter: 'tools[0].type'
  Status: 400
  Body: {
    error: {
      message: "Missing required parameter: 'tools[0].type'.",
      type: "invalid_request_error",
      param: "tools[0].type",
      code: "missing_required_parameter"
    }
  }

Root Cause Analysis

OpenAI requires wrapped format:

  {
    type: "function",                    # ← Required by OpenAI
    function: {
      name: "fetch_data",
      description: "...",
      parameters: { ... }                # ← OpenAI uses "parameters"
    }
  }

Anthropic accepts common format:

  {
    name: "fetch_data",
    description: "...",
    input_schema: { ... }                # ← Anthropic uses "input_schema"
  }

Active Agent transform modules do NOT handle tools:

  1. Anthropic (lib/active_agent/providers/anthropic/transforms.rb):
    - Line 27-32: normalize_params only handles messages and system
    - No tool transformation
  2. OpenAI (lib/active_agent/providers/open_ai/chat/transforms.rb):
    - Line 32-48: normalize_params only handles instructions, messages, and response_format
    - No tool transformation

Why it appears to work with Anthropic:
The Anthropic API natively accepts the common format, so no transformation is needed. This creates the false
impression that the common format "works" - but it's actually the API, not Active Agent, doing the work.

Impact

This affects any application using:

  • Multiple providers (forces provider-specific tool definitions)
  • OpenAI provider (must use native format, can't follow docs)
  • Tool definitions in shared modules (can't reuse across providers)

Suggested Fix

Add tool transformation to both providers:

Option 1: Transform in normalize_params

lib/active_agent/providers/open_ai/chat/transforms.rb

  def normalize_params(params)
    params = params.dup
    # Existing transformations...
    params[:messages] = normalize_messages(params[:messages]) if params[:messages]
    params[:response_format] = normalize_response_format(params[:response_format]) if params[:response_format]

    # NEW: Transform tools to OpenAI format
    params[:tools] = normalize_tools(params[:tools]) if params[:tools]

    params
  end

  def normalize_tools(tools)
    return tools unless tools.is_a?(Array)

    tools.map do |tool|
      # Already in OpenAI format? Pass through
      next tool if tool[:type] == "function" && tool[:function]

      # Convert common format to OpenAI format
      {
        type: "function",
        function: {
          name: tool[:name],
          description: tool[:description],
          parameters: tool[:parameters] || tool[:input_schema]  # Support both keys
        }.compact
      }
    end
  end

Option 2: Transform in api_request_build

Add tool transformation in BaseProvider#api_request_build before serialization.

Workaround

We're currently using provider-specific tool formats:

OpenAI agents:

  def my_tools
    [{
      type: "function",
      function: {
        name: "fetch_data",
        description: "...",
        parameters: { ... }
      }
    }]
  end

Anthropic agents:

  def my_tools
    [{
      name: "fetch_data",
      description: "...",
      input_schema: { ... }
    }]
  end

This prevents tool reuse across providers.

Environment

  • Active Agent version: 1.0.0
  • Ruby version: 3.4.7
  • Rails version: 8.0.3
  • Affected providers: OpenAI (openai gem 0.36.1)
  • Working providers: Anthropic (anthropic gem 1.16.0)

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