Skip to content

Commit 0b1798d

Browse files
committed
Refactor OpenAI's Responses Provider to use Native Gem Types
1 parent a9ef325 commit 0b1798d

63 files changed

Lines changed: 455 additions & 2596 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

lib/active_agent/providers/open_ai/responses/_types.rb

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,7 @@ def cast(value)
2424
def serialize(value)
2525
case value
2626
when Request
27-
hash = value.serialize
28-
29-
if hash[:input] in [ { role: "user", content: String } ]
30-
hash[:input] = hash[:input][0][:content]
31-
end
32-
33-
hash
27+
value.serialize
3428
when Hash
3529
value
3630
when nil

lib/active_agent/providers/open_ai/responses/request.rb

Lines changed: 108 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,136 @@
11
# frozen_string_literal: true
22

3-
require "active_agent/providers/common/model"
4-
require_relative "requests/_types"
3+
require_relative "transforms"
54

65
module ActiveAgent
76
module Providers
87
module OpenAI
98
module Responses
10-
class Request < Common::BaseModel
11-
# Background execution
12-
attribute :background, :boolean, default: false
13-
14-
# Conversation
15-
attribute :conversation, Requests::ConversationType.new
16-
17-
# Include additional output data
18-
attribute :include, default: -> { [] } # Array of strings
19-
20-
# Input items (text, image, or file inputs)
21-
attribute :input, Requests::Inputs::MessagesType.new
22-
23-
# Instructions (system/developer message)
24-
attribute :instructions, :string
25-
26-
# Token limits
27-
attribute :max_output_tokens, :integer
28-
attribute :max_tool_calls, :integer
29-
30-
# Metadata
31-
attribute :metadata # Hash of key-value pairs
32-
33-
# Model ID
34-
attribute :model, :string
35-
36-
# Parallel tool calls
37-
attribute :parallel_tool_calls, :boolean, default: true
38-
39-
# Previous response ID for multi-turn conversations
40-
attribute :previous_response_id, :string
41-
42-
# Prompt template reference
43-
attribute :prompt, Requests::PromptReferenceType.new
44-
45-
# Cache key for optimization
46-
attribute :prompt_cache_key, :string
47-
48-
# Reasoning configuration (for o-series and gpt-5 models)
49-
attribute :reasoning, Requests::ReasoningType.new
50-
51-
# Safety identifier for usage policy detection
52-
attribute :safety_identifier, :string
53-
54-
# Service tier
55-
attribute :service_tier, :string, default: "auto"
56-
57-
# Storage
58-
attribute :store, :boolean, default: true
59-
60-
# Streaming
61-
attribute :stream, :boolean, default: false
62-
attribute :stream_options, Requests::StreamOptionsType.new
63-
64-
# Temperature sampling
65-
attribute :temperature, :float, default: 1
66-
67-
# Text response configuration
68-
attribute :text, Requests::TextType.new
69-
70-
# Tool choice
71-
attribute :tool_choice, Requests::ToolChoiceType.new
9+
# Wraps OpenAI Responses API request parameters with field mapping and normalization.
10+
#
11+
# Uses SimpleDelegator to delegate to OpenAI::Models::Responses::ResponseCreateParams
12+
# while providing compatibility layer for common format fields (messages→input,
13+
# response_format→text) and content normalization.
14+
class Request < SimpleDelegator
15+
# Default parameter values applied during initialization
16+
DEFAULTS = {
17+
service_tier: "auto",
18+
store: true,
19+
temperature: 1.0,
20+
top_p: 1.0,
21+
truncation: "disabled",
22+
parallel_tool_calls: true,
23+
background: false,
24+
include: []
25+
}.freeze
26+
27+
# @return [Boolean, nil]
28+
attr_reader :stream
29+
30+
# @return [Hash, nil]
31+
attr_reader :response_format
32+
33+
# Initializes request with field mapping and normalization.
34+
#
35+
# Maps common format fields to Responses API format:
36+
# - messages → input
37+
# - response_format → text (via ResponseTextConfig)
38+
# - instructions array joined to string
39+
#
40+
# @param params [Hash]
41+
# @option params [String] :model
42+
# @option params [Array, String, Hash] :input messages or content
43+
# @option params [Array, String, Hash] :messages alternative to :input
44+
# @option params [Hash, String, Symbol] :response_format
45+
# @option params [Array<String>, String] :instructions
46+
# @option params [Integer] :max_output_tokens
47+
# @raise [ArgumentError] when gem model initialization fails
48+
def initialize(**params)
49+
# Extract custom fields
50+
@stream = params[:stream]
51+
@response_format = params.delete(:response_format)
52+
53+
# Map common format 'messages' to OpenAI Responses 'input'
54+
if params.key?(:messages)
55+
params[:input] = params.delete(:messages)
56+
end
7257

73-
# Tools array
74-
attribute :tools, Requests::Tools::ToolsType.new
58+
# Join instructions array into string (like Chat API)
59+
if params[:instructions].is_a?(Array)
60+
params[:instructions] = params[:instructions].join("\n")
61+
end
7562

76-
# Top logprobs
77-
attribute :top_logprobs, :integer
63+
# Map response_format to text parameter for Responses API
64+
if @response_format
65+
params[:text] = Responses::Transforms.normalize_response_format(@response_format)
66+
end
7867

79-
# Top P sampling
80-
attribute :top_p, :float, default: 1
68+
# Apply defaults
69+
params = apply_defaults(params)
8170

82-
# Truncation strategy
83-
attribute :truncation, :string, default: "disabled"
71+
# Normalize input content for gem compatibility
72+
params[:input] = Responses::Transforms.normalize_input(params[:input]) if params[:input]
8473

85-
# User identifier (deprecated, use safety_identifier or prompt_cache_key)
86-
attribute :user, :string
74+
# Create gem model - delegates to OpenAI gem
75+
gem_model = ::OpenAI::Models::Responses::ResponseCreateParams.new(**params)
8776

88-
# Validations
89-
validates :max_output_tokens, numericality: { greater_than: 0 }, allow_nil: true
90-
validates :max_tool_calls, numericality: { greater_than: 0 }, allow_nil: true
91-
validates :temperature, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 2 }, allow_nil: true
92-
validates :top_p, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }, allow_nil: true
93-
validates :top_logprobs, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 20 }, allow_nil: true
94-
validates :service_tier, inclusion: { in: %w[auto default flex priority] }, allow_nil: true
95-
validates :truncation, inclusion: { in: %w[auto disabled] }, allow_nil: true
77+
# Delegate all method calls to gem model
78+
super(gem_model)
79+
rescue ArgumentError => e
80+
# Re-raise with more context
81+
raise ArgumentError, "Invalid OpenAI Responses request parameters: #{e.message}"
82+
end
9683

97-
validate :validate_conversation_exclusivity
98-
validate :validate_metadata_format
99-
validate :validate_include_values
84+
# Serializes request for API call.
85+
#
86+
# Removes default values to keep request body minimal and simplifies
87+
# single-element input arrays to strings where possible.
88+
#
89+
# @return [Hash]
90+
def serialize
91+
hash = Responses::Transforms.gem_to_hash(__getobj__)
92+
93+
# Remove default values that shouldn't be in the request body
94+
DEFAULTS.each do |key, value|
95+
hash.delete(key) if hash[key] == value
96+
end
10097

101-
# Common Format Mapping
102-
alias_attribute :messages, :input
103-
alias_attribute :message, :input
104-
alias_attribute :response_format, :text
98+
# Simplify input when possible for cleaner API requests
99+
hash[:input] = Responses::Transforms.simplify_input(hash[:input]) if hash[:input]
105100

106-
# Common Format Compatability
107-
def instructions=(value)
108-
super(value.is_a?(Array) ? value.join("\n") : value)
101+
hash
109102
end
110103

111-
private
112-
113-
def validate_conversation_exclusivity
114-
if conversation.present? && previous_response_id.present?
115-
errors.add(:base, "Cannot use both conversation and previous_response_id")
116-
end
104+
# @return [Array, String, Hash, nil]
105+
def messages
106+
__getobj__.instance_variable_get(:@data)[:input]
117107
end
118108

119-
def validate_metadata_format
120-
return if metadata.nil?
109+
# Sets input messages with normalization.
110+
#
111+
# Converts strings to message objects and ensures proper format.
112+
#
113+
# @param value [Array, String, Hash]
114+
# @return [void]
115+
def messages=(value)
116+
normalized_value = Responses::Transforms.normalize_input(value)
117+
__getobj__.instance_variable_get(:@data)[:input] = normalized_value
118+
end
121119

122-
unless metadata.is_a?(Hash)
123-
errors.add(:metadata, "must be a hash")
124-
return
125-
end
120+
alias_method :message, :messages
121+
alias_method :message=, :messages=
126122

127-
metadata.each do |key, value|
128-
if key.to_s.length > 64
129-
errors.add(:metadata, "keys must be 64 characters or less")
130-
end
131-
if value.to_s.length > 512
132-
errors.add(:metadata, "values must be 512 characters or less")
133-
end
134-
end
123+
private
135124

136-
if metadata.size > 16
137-
errors.add(:metadata, "must have 16 key-value pairs or less")
125+
# @api private
126+
# @param params [Hash]
127+
# @return [Hash]
128+
def apply_defaults(params)
129+
DEFAULTS.each do |key, value|
130+
params[key] = value unless params.key?(key)
138131
end
139-
end
140132

141-
def validate_include_values
142-
return if include.nil? || include.empty?
143-
144-
valid_include_values = [
145-
"web_search_call.action.sources",
146-
"code_interpreter_call.outputs",
147-
"computer_call_output.output.image_url",
148-
"file_search_call.results",
149-
"message.input_image.image_url",
150-
"message.output_text.logprobs",
151-
"reasoning.encrypted_content"
152-
]
153-
154-
invalid_values = include - valid_include_values
155-
if invalid_values.any?
156-
errors.add(:include, "contains invalid values: #{invalid_values.join(', ')}")
157-
end
133+
params
158134
end
159135
end
160136
end

0 commit comments

Comments
 (0)