ruby_llm-responses_api 0.6.1 → 1.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/MIGRATION.md +276 -0
- data/README.md +42 -32
- data/lib/ruby_llm/providers/openai_responses/active_record_extension.rb +12 -109
- data/lib/ruby_llm/providers/openai_responses/base.rb +2 -7
- data/lib/ruby_llm/providers/openai_responses/batch.rb +8 -8
- data/lib/ruby_llm/providers/openai_responses/batches.rb +6 -14
- data/lib/ruby_llm/providers/openai_responses/built_in_tools.rb +1 -1
- data/lib/ruby_llm/providers/openai_responses/chat_extension.rb +3 -12
- data/lib/ruby_llm/providers/openai_responses/compaction.rb +1 -1
- data/lib/ruby_llm/providers/openai_responses/message_extension.rb +11 -21
- data/lib/ruby_llm/providers/openai_responses/model_registry.rb +17 -261
- data/lib/ruby_llm/providers/openai_responses/protocol.rb +103 -0
- data/lib/ruby_llm/providers/openai_responses/stream_accumulator.rb +48 -0
- data/lib/ruby_llm/providers/openai_responses/web_socket.rb +51 -77
- data/lib/ruby_llm/providers/openai_responses.rb +23 -80
- data/lib/rubyllm_responses_api.rb +13 -46
- metadata +17 -18
- data/lib/ruby_llm/providers/openai_responses/capabilities.rb +0 -267
- data/lib/ruby_llm/providers/openai_responses/chat.rb +0 -273
- data/lib/ruby_llm/providers/openai_responses/media.rb +0 -95
- data/lib/ruby_llm/providers/openai_responses/models.rb +0 -48
- data/lib/ruby_llm/providers/openai_responses/stream_accumulator_extension.rb +0 -39
- data/lib/ruby_llm/providers/openai_responses/streaming.rb +0 -140
- data/lib/ruby_llm/providers/openai_responses/tools.rb +0 -222
|
@@ -3,133 +3,36 @@
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Providers
|
|
5
5
|
class OpenAIResponses
|
|
6
|
-
#
|
|
7
|
-
# for stateful conversations with the OpenAI Responses API.
|
|
8
|
-
#
|
|
9
|
-
# This is automatically applied when Rails loads ActiveRecord.
|
|
10
|
-
# Just add a migration: add_column :messages, :response_id, :string
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
# Extension for the NEW MessageMethods (RubyLLM 2.0+)
|
|
6
|
+
# Keep native persistence for tools, usage, attachments and reasoning.
|
|
14
7
|
module MessageMethodsExtension
|
|
15
8
|
def to_llm
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
RubyLLM::Message.new(
|
|
21
|
-
role: role.to_sym,
|
|
22
|
-
content: extract_content,
|
|
23
|
-
tool_calls: extract_tool_calls,
|
|
24
|
-
tool_call_id: extract_tool_call_id,
|
|
25
|
-
input_tokens: input_tokens,
|
|
26
|
-
output_tokens: output_tokens,
|
|
27
|
-
cached_tokens: cached,
|
|
28
|
-
cache_creation_tokens: cache_creation,
|
|
29
|
-
model_id: model_association&.model_id,
|
|
30
|
-
response_id: resp_id
|
|
31
|
-
)
|
|
9
|
+
message = super
|
|
10
|
+
message.response_id = self[:response_id] if has_attribute?(:response_id)
|
|
11
|
+
message
|
|
32
12
|
end
|
|
33
13
|
end
|
|
34
14
|
|
|
35
|
-
# Extension for the NEW ChatMethods (RubyLLM 2.0+)
|
|
36
15
|
module ChatMethodsExtension
|
|
37
|
-
def persist_message_completion(message)
|
|
38
|
-
super
|
|
39
|
-
save_response_id(message)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
16
|
private
|
|
43
17
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@message.update_column(:response_id, message.response_id)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Extension for LEGACY MessageLegacyMethods (RubyLLM 1.x)
|
|
54
|
-
module MessageLegacyMethodsExtension
|
|
55
|
-
def to_llm
|
|
56
|
-
resp_id = has_attribute?(:response_id) ? self[:response_id] : nil
|
|
57
|
-
|
|
58
|
-
RubyLLM::Message.new(
|
|
59
|
-
role: role.to_sym,
|
|
60
|
-
content: extract_content,
|
|
61
|
-
tool_calls: extract_tool_calls,
|
|
62
|
-
tool_call_id: extract_tool_call_id,
|
|
63
|
-
input_tokens: input_tokens,
|
|
64
|
-
output_tokens: output_tokens,
|
|
65
|
-
model_id: model_id,
|
|
66
|
-
response_id: resp_id
|
|
67
|
-
)
|
|
18
|
+
def message_attributes(message)
|
|
19
|
+
attributes = super
|
|
20
|
+
assign_supported_attribute(attributes, :response_id, message.response_id)
|
|
21
|
+
attributes
|
|
68
22
|
end
|
|
69
23
|
end
|
|
70
24
|
|
|
71
|
-
# Extension for LEGACY ChatLegacyMethods (RubyLLM 1.x)
|
|
72
|
-
module ChatLegacyMethodsExtension
|
|
73
|
-
def persist_message_completion(message)
|
|
74
|
-
super
|
|
75
|
-
save_response_id_legacy(message)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
private
|
|
79
|
-
|
|
80
|
-
def save_response_id_legacy(message)
|
|
81
|
-
return unless message
|
|
82
|
-
return unless message.respond_to?(:response_id) && message.response_id
|
|
83
|
-
return unless @message.has_attribute?(:response_id)
|
|
84
|
-
|
|
85
|
-
@message.update_column(:response_id, message.response_id)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
@active_record_extensions_applied = false
|
|
90
|
-
|
|
91
|
-
# Apply ActiveRecord extensions for response_id persistence.
|
|
92
|
-
# Called automatically when ActiveRecord loads, or can be called manually.
|
|
93
25
|
def self.apply_active_record_extensions!
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
applied = false
|
|
97
|
-
|
|
98
|
-
# Try to apply to NEW modules (RubyLLM 2.0+)
|
|
99
|
-
begin
|
|
100
|
-
require 'ruby_llm/active_record/message_methods'
|
|
101
|
-
require 'ruby_llm/active_record/chat_methods'
|
|
102
|
-
|
|
103
|
-
RubyLLM::ActiveRecord::MessageMethods.prepend(MessageMethodsExtension)
|
|
104
|
-
RubyLLM::ActiveRecord::ChatMethods.prepend(ChatMethodsExtension)
|
|
105
|
-
applied = true
|
|
106
|
-
rescue LoadError, NameError
|
|
107
|
-
# New modules not available
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Try to apply to LEGACY modules (RubyLLM 1.x)
|
|
111
|
-
begin
|
|
112
|
-
require 'ruby_llm/active_record/acts_as_legacy'
|
|
113
|
-
|
|
114
|
-
if defined?(RubyLLM::ActiveRecord::MessageLegacyMethods)
|
|
115
|
-
RubyLLM::ActiveRecord::MessageLegacyMethods.prepend(MessageLegacyMethodsExtension)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
if defined?(RubyLLM::ActiveRecord::ChatLegacyMethods)
|
|
119
|
-
RubyLLM::ActiveRecord::ChatLegacyMethods.prepend(ChatLegacyMethodsExtension)
|
|
120
|
-
end
|
|
121
|
-
applied = true
|
|
122
|
-
rescue LoadError, NameError
|
|
123
|
-
# Legacy modules not available
|
|
124
|
-
end
|
|
26
|
+
require 'ruby_llm/active_record/message_methods'
|
|
27
|
+
require 'ruby_llm/active_record/chat_methods'
|
|
125
28
|
|
|
126
|
-
|
|
29
|
+
RubyLLM::ActiveRecord::MessageMethods.prepend(MessageMethodsExtension)
|
|
30
|
+
RubyLLM::ActiveRecord::ChatMethods.prepend(ChatMethodsExtension)
|
|
127
31
|
end
|
|
128
32
|
end
|
|
129
33
|
end
|
|
130
34
|
end
|
|
131
35
|
|
|
132
|
-
# Auto-apply extensions when ActiveRecord is loaded in Rails
|
|
133
36
|
if defined?(ActiveSupport.on_load)
|
|
134
37
|
ActiveSupport.on_load(:active_record) do
|
|
135
38
|
RubyLLM::Providers::OpenAIResponses.apply_active_record_extensions!
|
|
@@ -2,13 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Providers
|
|
5
|
-
# OpenAI
|
|
6
|
-
|
|
7
|
-
# stateful conversations, background mode, and MCP support.
|
|
8
|
-
#
|
|
9
|
-
# This base file defines the class structure before modules are loaded
|
|
10
|
-
# to avoid "superclass mismatch" errors.
|
|
11
|
-
class OpenAIResponses < Provider
|
|
5
|
+
# OpenAI's native provider with opt-in server-side conversation state.
|
|
6
|
+
class OpenAIResponses < OpenAI
|
|
12
7
|
end
|
|
13
8
|
end
|
|
14
9
|
end
|
|
@@ -10,7 +10,7 @@ module RubyLLM
|
|
|
10
10
|
# behind a clean Ruby API that mirrors RubyLLM::Chat.
|
|
11
11
|
#
|
|
12
12
|
# @example
|
|
13
|
-
# batch = RubyLLM.batch(model: 'gpt-4o', provider: :openai_responses)
|
|
13
|
+
# batch = RubyLLM::ResponsesAPI.batch(model: 'gpt-4o', provider: :openai_responses)
|
|
14
14
|
# batch.add("What is Ruby?")
|
|
15
15
|
# batch.add("What is Python?", instructions: "Be brief")
|
|
16
16
|
# batch.create!
|
|
@@ -60,8 +60,8 @@ module RubyLLM
|
|
|
60
60
|
# @param metadata [Hash, nil] Optional metadata for the batch
|
|
61
61
|
# @return [self]
|
|
62
62
|
def create!(metadata: nil)
|
|
63
|
-
raise Error
|
|
64
|
-
raise Error
|
|
63
|
+
raise Error, 'No requests added' if @requests.empty?
|
|
64
|
+
raise Error, 'Batch already created' if @id
|
|
65
65
|
|
|
66
66
|
jsonl = Batches.build_jsonl(@requests)
|
|
67
67
|
file_id = upload_file(jsonl)
|
|
@@ -82,7 +82,7 @@ module RubyLLM
|
|
|
82
82
|
# Fetch the latest batch status from the API.
|
|
83
83
|
# @return [self]
|
|
84
84
|
def refresh!
|
|
85
|
-
raise Error
|
|
85
|
+
raise Error, 'Batch not yet created' unless @id
|
|
86
86
|
|
|
87
87
|
response = @provider.instance_variable_get(:@connection).get(Batches.batch_url(@id))
|
|
88
88
|
@data = response.body
|
|
@@ -149,7 +149,7 @@ module RubyLLM
|
|
|
149
149
|
break if Batches.terminal?(status)
|
|
150
150
|
|
|
151
151
|
if timeout && (Time.now - start_time) > timeout
|
|
152
|
-
raise Error
|
|
152
|
+
raise Error, "Batch polling timeout after #{timeout} seconds"
|
|
153
153
|
end
|
|
154
154
|
|
|
155
155
|
sleep interval
|
|
@@ -162,7 +162,7 @@ module RubyLLM
|
|
|
162
162
|
# @return [Hash<String, Message>] Results keyed by custom_id
|
|
163
163
|
def results
|
|
164
164
|
output_file_id = @data['output_file_id']
|
|
165
|
-
raise Error
|
|
165
|
+
raise Error, 'No output file available yet' unless output_file_id
|
|
166
166
|
|
|
167
167
|
jsonl = fetch_file_content(output_file_id)
|
|
168
168
|
Batches.parse_results_to_messages(jsonl)
|
|
@@ -181,7 +181,7 @@ module RubyLLM
|
|
|
181
181
|
# Cancel the batch.
|
|
182
182
|
# @return [self]
|
|
183
183
|
def cancel!
|
|
184
|
-
raise Error
|
|
184
|
+
raise Error, 'Batch not yet created' unless @id
|
|
185
185
|
|
|
186
186
|
response = @provider.instance_variable_get(:@connection).post(Batches.cancel_batch_url(@id), {})
|
|
187
187
|
@data = response.body
|
|
@@ -195,7 +195,7 @@ module RubyLLM
|
|
|
195
195
|
when Symbol, String
|
|
196
196
|
slug = provider.to_sym
|
|
197
197
|
provider_class = RubyLLM::Provider.providers[slug]
|
|
198
|
-
raise Error
|
|
198
|
+
raise Error, "Unknown provider: #{slug}" unless provider_class
|
|
199
199
|
|
|
200
200
|
provider_class.new(RubyLLM.config)
|
|
201
201
|
else
|
|
@@ -96,7 +96,7 @@ module RubyLLM
|
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
# Parse JSONL output into a Hash of { custom_id => Message }.
|
|
99
|
-
#
|
|
99
|
+
# Uses the native Responses parser, including reasoning and server tools.
|
|
100
100
|
def parse_results_to_messages(jsonl_string)
|
|
101
101
|
results = parse_results(jsonl_string)
|
|
102
102
|
results.each_with_object({}) do |result, hash|
|
|
@@ -104,19 +104,11 @@ module RubyLLM
|
|
|
104
104
|
response_body = result.dig('response', 'body')
|
|
105
105
|
next unless response_body
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
hash[custom_id] = Message.new(
|
|
113
|
-
role: :assistant,
|
|
114
|
-
content: content,
|
|
115
|
-
tool_calls: tool_calls,
|
|
116
|
-
input_tokens: usage['input_tokens'],
|
|
117
|
-
output_tokens: usage['output_tokens'],
|
|
118
|
-
model_id: response_body['model']
|
|
119
|
-
)
|
|
107
|
+
next if result.dig('response', 'status_code').to_i >= 400
|
|
108
|
+
|
|
109
|
+
raw = Faraday::Response.new(status: result.dig('response', 'status_code') || 200, body: response_body)
|
|
110
|
+
provider = OpenAIResponses.new(RubyLLM.config)
|
|
111
|
+
hash[custom_id] = Protocol.new(provider).parse_completion_body(response_body, raw: raw)
|
|
120
112
|
end
|
|
121
113
|
end
|
|
122
114
|
|
|
@@ -213,7 +213,7 @@ module RubyLLM
|
|
|
213
213
|
|
|
214
214
|
# Build a list of {tool_call:, result:} events from the response output.
|
|
215
215
|
# Used to surface server-side built-in tool activity through the standard
|
|
216
|
-
#
|
|
216
|
+
# before_tool_call / after_tool_result callbacks (issue #1).
|
|
217
217
|
def extract_events(output)
|
|
218
218
|
return [] unless output.is_a?(Array)
|
|
219
219
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Providers
|
|
5
5
|
class OpenAIResponses
|
|
6
|
-
# Extends RubyLLM::Chat to fire
|
|
6
|
+
# Extends RubyLLM::Chat to fire before_tool_call / after_tool_result for built-in
|
|
7
7
|
# server-side tools (web_search, code_interpreter, file_search, etc.)
|
|
8
8
|
# carried on the assistant message. This lets users observe built-in tool
|
|
9
9
|
# activity through the same callback API as locally executed function
|
|
@@ -25,19 +25,10 @@ module RubyLLM
|
|
|
25
25
|
|
|
26
26
|
def dispatch_built_in_tool_events(message)
|
|
27
27
|
message.built_in_tool_events.each do |event|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
run_callbacks(:before_tool_call, event[:tool_call])
|
|
29
|
+
run_callbacks(:after_tool_result, event[:result])
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
|
-
|
|
33
|
-
# Mirrors RubyLLM::Chat#run_callbacks (private since 1.13): dispatches
|
|
34
|
-
# through the new @callbacks array API if present, and always falls
|
|
35
|
-
# back to the legacy @on hash so older ruby_llm versions still work.
|
|
36
|
-
def fire_callback(new_name, legacy_name, *args)
|
|
37
|
-
callbacks = instance_variable_defined?(:@callbacks) ? @callbacks : nil
|
|
38
|
-
callbacks[new_name]&.each { |cb| cb.call(*args) } if callbacks
|
|
39
|
-
@on[legacy_name]&.call(*args)
|
|
40
|
-
end
|
|
41
32
|
end
|
|
42
33
|
end
|
|
43
34
|
end
|
|
@@ -10,7 +10,7 @@ module RubyLLM
|
|
|
10
10
|
|
|
11
11
|
# Build context_management parameter for compaction
|
|
12
12
|
# @param compact_threshold [Integer] Token count threshold to trigger compaction (minimum 1000)
|
|
13
|
-
# @return [Hash] Parameters to merge into request payload via
|
|
13
|
+
# @return [Hash] Parameters to merge into request payload via with_provider_options
|
|
14
14
|
def compaction_params(compact_threshold: 200_000)
|
|
15
15
|
{
|
|
16
16
|
context_management: [
|
|
@@ -3,33 +3,23 @@
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Providers
|
|
5
5
|
class OpenAIResponses
|
|
6
|
-
#
|
|
7
|
-
# and built_in_tool_events for server-side tool calls (web_search,
|
|
8
|
-
# code_interpreter, etc.) that should fire on_tool_call/on_tool_result.
|
|
6
|
+
# Extra state carried by this provider; native Message fields are preserved.
|
|
9
7
|
module MessageExtension
|
|
10
|
-
attr_accessor :response_id, :built_in_tool_events
|
|
8
|
+
attr_accessor :response_id, :built_in_tool_events, :responses_api_body
|
|
11
9
|
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@response_id = options[:response_id]
|
|
19
|
-
@built_in_tool_events = options[:built_in_tool_events]
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
alias_method :original_to_h, :to_h
|
|
10
|
+
def initialize(options = {})
|
|
11
|
+
super
|
|
12
|
+
@response_id = options[:response_id]
|
|
13
|
+
@built_in_tool_events = options[:built_in_tool_events]
|
|
14
|
+
@responses_api_body = options[:responses_api_body]
|
|
15
|
+
end
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
end
|
|
27
|
-
end
|
|
17
|
+
def to_h
|
|
18
|
+
response_id ? super.merge(response_id: response_id) : super
|
|
28
19
|
end
|
|
29
20
|
end
|
|
30
21
|
end
|
|
31
22
|
end
|
|
32
23
|
end
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
RubyLLM::Message.include(RubyLLM::Providers::OpenAIResponses::MessageExtension)
|
|
25
|
+
RubyLLM::Message.prepend(RubyLLM::Providers::OpenAIResponses::MessageExtension)
|
|
@@ -3,273 +3,29 @@
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Providers
|
|
5
5
|
class OpenAIResponses
|
|
6
|
-
#
|
|
7
|
-
# Models updated May 2026 based on OpenAI documentation
|
|
6
|
+
# Reuse the current OpenAI catalog rather than maintaining model snapshots.
|
|
8
7
|
module ModelRegistry
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# ===================
|
|
13
|
-
{
|
|
14
|
-
id: 'gpt-5.5',
|
|
15
|
-
name: 'GPT-5.5',
|
|
16
|
-
provider: 'openai_responses',
|
|
17
|
-
family: 'gpt-5.5',
|
|
18
|
-
context_window: 1_050_000,
|
|
19
|
-
max_output_tokens: 128_000,
|
|
20
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
21
|
-
capabilities: %w[
|
|
22
|
-
streaming function_calling structured_output vision reasoning
|
|
23
|
-
web_search file_search image_generation code_interpreter shell
|
|
24
|
-
apply_patch computer_use mcp
|
|
25
|
-
]
|
|
26
|
-
},
|
|
8
|
+
def by_provider(provider)
|
|
9
|
+
own = super
|
|
10
|
+
return own unless provider.to_s == 'openai_responses'
|
|
27
11
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
name: 'GPT-5.2',
|
|
34
|
-
provider: 'openai_responses',
|
|
35
|
-
family: 'gpt-5.2',
|
|
36
|
-
context_window: 400_000,
|
|
37
|
-
max_output_tokens: 128_000,
|
|
38
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
39
|
-
capabilities: %w[streaming function_calling structured_output vision reasoning web_search code_interpreter]
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
# ===================
|
|
43
|
-
# GPT-5.1 Series (November 2025)
|
|
44
|
-
# ===================
|
|
45
|
-
{
|
|
46
|
-
id: 'gpt-5.1',
|
|
47
|
-
name: 'GPT-5.1',
|
|
48
|
-
provider: 'openai_responses',
|
|
49
|
-
family: 'gpt-5.1',
|
|
50
|
-
context_window: 400_000,
|
|
51
|
-
max_output_tokens: 128_000,
|
|
52
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
53
|
-
capabilities: %w[streaming function_calling structured_output vision reasoning web_search code_interpreter]
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
id: 'gpt-5.1-codex-max',
|
|
57
|
-
name: 'GPT-5.1 Codex Max',
|
|
58
|
-
provider: 'openai_responses',
|
|
59
|
-
family: 'gpt-5.1',
|
|
60
|
-
context_window: 400_000,
|
|
61
|
-
max_output_tokens: 128_000,
|
|
62
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
63
|
-
capabilities: %w[streaming function_calling structured_output reasoning web_search]
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
id: 'gpt-5.1-codex',
|
|
67
|
-
name: 'GPT-5.1 Codex',
|
|
68
|
-
provider: 'openai_responses',
|
|
69
|
-
family: 'gpt-5.1',
|
|
70
|
-
context_window: 400_000,
|
|
71
|
-
max_output_tokens: 128_000,
|
|
72
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
73
|
-
capabilities: %w[streaming function_calling structured_output reasoning]
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
id: 'gpt-5.1-codex-mini',
|
|
77
|
-
name: 'GPT-5.1 Codex Mini',
|
|
78
|
-
provider: 'openai_responses',
|
|
79
|
-
family: 'gpt-5.1',
|
|
80
|
-
context_window: 400_000,
|
|
81
|
-
max_output_tokens: 128_000,
|
|
82
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
83
|
-
capabilities: %w[streaming function_calling structured_output reasoning]
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
id: 'gpt-5.1-chat',
|
|
87
|
-
name: 'GPT-5.1 Chat',
|
|
88
|
-
provider: 'openai_responses',
|
|
89
|
-
family: 'gpt-5.1',
|
|
90
|
-
context_window: 128_000,
|
|
91
|
-
max_output_tokens: 16_384,
|
|
92
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
93
|
-
capabilities: %w[streaming function_calling structured_output vision]
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
# ===================
|
|
97
|
-
# GPT-5 Series (August 2025)
|
|
98
|
-
# ===================
|
|
99
|
-
{
|
|
100
|
-
id: 'gpt-5',
|
|
101
|
-
name: 'GPT-5',
|
|
102
|
-
provider: 'openai_responses',
|
|
103
|
-
family: 'gpt-5',
|
|
104
|
-
context_window: 400_000,
|
|
105
|
-
max_output_tokens: 128_000,
|
|
106
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
107
|
-
capabilities: %w[streaming function_calling structured_output vision reasoning web_search code_interpreter]
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
id: 'gpt-5-pro',
|
|
111
|
-
name: 'GPT-5 Pro',
|
|
112
|
-
provider: 'openai_responses',
|
|
113
|
-
family: 'gpt-5',
|
|
114
|
-
context_window: 400_000,
|
|
115
|
-
max_output_tokens: 128_000,
|
|
116
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
117
|
-
capabilities: %w[streaming function_calling structured_output vision reasoning web_search code_interpreter]
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
id: 'gpt-5-mini',
|
|
121
|
-
name: 'GPT-5 Mini',
|
|
122
|
-
provider: 'openai_responses',
|
|
123
|
-
family: 'gpt-5',
|
|
124
|
-
context_window: 400_000,
|
|
125
|
-
max_output_tokens: 128_000,
|
|
126
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
127
|
-
capabilities: %w[streaming function_calling structured_output vision reasoning web_search code_interpreter]
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
id: 'gpt-5-nano',
|
|
131
|
-
name: 'GPT-5 Nano',
|
|
132
|
-
provider: 'openai_responses',
|
|
133
|
-
family: 'gpt-5',
|
|
134
|
-
context_window: 400_000,
|
|
135
|
-
max_output_tokens: 128_000,
|
|
136
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
137
|
-
capabilities: %w[streaming function_calling structured_output vision reasoning]
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
# ===================
|
|
141
|
-
# O-Series Reasoning Models
|
|
142
|
-
# ===================
|
|
143
|
-
{
|
|
144
|
-
id: 'o4-mini',
|
|
145
|
-
name: 'O4 Mini',
|
|
146
|
-
provider: 'openai_responses',
|
|
147
|
-
family: 'o4',
|
|
148
|
-
context_window: 200_000,
|
|
149
|
-
max_output_tokens: 100_000,
|
|
150
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
151
|
-
capabilities: %w[streaming function_calling structured_output vision reasoning web_search code_interpreter]
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
id: 'o3-pro',
|
|
155
|
-
name: 'O3 Pro',
|
|
156
|
-
provider: 'openai_responses',
|
|
157
|
-
family: 'o3',
|
|
158
|
-
context_window: 200_000,
|
|
159
|
-
max_output_tokens: 100_000,
|
|
160
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
161
|
-
capabilities: %w[streaming function_calling structured_output vision reasoning web_search code_interpreter]
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
id: 'o3',
|
|
165
|
-
name: 'O3',
|
|
166
|
-
provider: 'openai_responses',
|
|
167
|
-
family: 'o3',
|
|
168
|
-
context_window: 200_000,
|
|
169
|
-
max_output_tokens: 100_000,
|
|
170
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
171
|
-
capabilities: %w[streaming function_calling structured_output vision reasoning web_search code_interpreter]
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
id: 'o3-mini',
|
|
175
|
-
name: 'O3 Mini',
|
|
176
|
-
provider: 'openai_responses',
|
|
177
|
-
family: 'o3',
|
|
178
|
-
context_window: 200_000,
|
|
179
|
-
max_output_tokens: 100_000,
|
|
180
|
-
modalities: { input: ['text'], output: ['text'] },
|
|
181
|
-
capabilities: %w[streaming function_calling structured_output reasoning]
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
id: 'o1',
|
|
185
|
-
name: 'O1',
|
|
186
|
-
provider: 'openai_responses',
|
|
187
|
-
family: 'o1',
|
|
188
|
-
context_window: 200_000,
|
|
189
|
-
max_output_tokens: 100_000,
|
|
190
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
191
|
-
capabilities: %w[streaming function_calling structured_output vision reasoning]
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
id: 'o1-mini',
|
|
195
|
-
name: 'O1 Mini',
|
|
196
|
-
provider: 'openai_responses',
|
|
197
|
-
family: 'o1',
|
|
198
|
-
context_window: 128_000,
|
|
199
|
-
max_output_tokens: 65_536,
|
|
200
|
-
modalities: { input: ['text'], output: ['text'] },
|
|
201
|
-
capabilities: %w[streaming function_calling structured_output reasoning]
|
|
202
|
-
},
|
|
203
|
-
|
|
204
|
-
# ===================
|
|
205
|
-
# GPT-4.1 Series (Legacy - still supported)
|
|
206
|
-
# ===================
|
|
207
|
-
{
|
|
208
|
-
id: 'gpt-4.1',
|
|
209
|
-
name: 'GPT-4.1',
|
|
210
|
-
provider: 'openai_responses',
|
|
211
|
-
family: 'gpt-4.1',
|
|
212
|
-
context_window: 1_000_000,
|
|
213
|
-
max_output_tokens: 32_768,
|
|
214
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
215
|
-
capabilities: %w[streaming function_calling structured_output vision web_search code_interpreter]
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
id: 'gpt-4.1-mini',
|
|
219
|
-
name: 'GPT-4.1 Mini',
|
|
220
|
-
provider: 'openai_responses',
|
|
221
|
-
family: 'gpt-4.1',
|
|
222
|
-
context_window: 1_000_000,
|
|
223
|
-
max_output_tokens: 32_768,
|
|
224
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
225
|
-
capabilities: %w[streaming function_calling structured_output vision web_search code_interpreter]
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
id: 'gpt-4.1-nano',
|
|
229
|
-
name: 'GPT-4.1 Nano',
|
|
230
|
-
provider: 'openai_responses',
|
|
231
|
-
family: 'gpt-4.1',
|
|
232
|
-
context_window: 1_000_000,
|
|
233
|
-
max_output_tokens: 32_768,
|
|
234
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
235
|
-
capabilities: %w[streaming function_calling structured_output vision web_search code_interpreter]
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
# ===================
|
|
239
|
-
# GPT-4o Series (Legacy - still widely used)
|
|
240
|
-
# ===================
|
|
241
|
-
{
|
|
242
|
-
id: 'gpt-4o',
|
|
243
|
-
name: 'GPT-4o',
|
|
244
|
-
provider: 'openai_responses',
|
|
245
|
-
family: 'gpt-4o',
|
|
246
|
-
context_window: 128_000,
|
|
247
|
-
max_output_tokens: 16_384,
|
|
248
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
249
|
-
capabilities: %w[streaming function_calling structured_output vision web_search code_interpreter]
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
id: 'gpt-4o-mini',
|
|
253
|
-
name: 'GPT-4o Mini',
|
|
254
|
-
provider: 'openai_responses',
|
|
255
|
-
family: 'gpt-4o',
|
|
256
|
-
context_window: 128_000,
|
|
257
|
-
max_output_tokens: 16_384,
|
|
258
|
-
modalities: { input: %w[text image], output: ['text'] },
|
|
259
|
-
capabilities: %w[streaming function_calling structured_output vision web_search code_interpreter]
|
|
260
|
-
}
|
|
261
|
-
].freeze
|
|
12
|
+
native = super(:openai).all_including_unlisted.map do |source|
|
|
13
|
+
RubyLLM::Model.new(source.to_h.merge(provider: 'openai_responses'))
|
|
14
|
+
end
|
|
15
|
+
self.class.new((own.all_including_unlisted + native).uniq(&:id))
|
|
16
|
+
end
|
|
262
17
|
|
|
263
|
-
|
|
18
|
+
def find(model_id, provider: nil, config: nil)
|
|
19
|
+
super
|
|
20
|
+
rescue RubyLLM::ModelNotFoundError
|
|
21
|
+
raise unless provider.to_s == 'openai_responses'
|
|
264
22
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
model = RubyLLM::Model::Info.new(model_data)
|
|
268
|
-
existing = RubyLLM::Models.instance.all.find { |m| m.id == model.id && m.provider == model.provider }
|
|
269
|
-
RubyLLM::Models.instance.all << model unless existing
|
|
270
|
-
end
|
|
23
|
+
source = super(model_id, provider: :openai, config: config)
|
|
24
|
+
RubyLLM::Model.new(source.to_h.merge(provider: 'openai_responses'))
|
|
271
25
|
end
|
|
272
26
|
end
|
|
273
27
|
end
|
|
274
28
|
end
|
|
275
29
|
end
|
|
30
|
+
|
|
31
|
+
RubyLLM::Models.prepend(RubyLLM::Providers::OpenAIResponses::ModelRegistry)
|