activeagent 0.6.3 → 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 +240 -2
- data/README.md +15 -24
- data/lib/active_agent/base.rb +389 -39
- data/lib/active_agent/concerns/callbacks.rb +251 -0
- data/lib/active_agent/concerns/observers.rb +147 -0
- data/lib/active_agent/concerns/parameterized.rb +292 -0
- data/lib/active_agent/concerns/provider.rb +120 -0
- data/lib/active_agent/concerns/queueing.rb +36 -0
- data/lib/active_agent/concerns/rescue.rb +64 -0
- data/lib/active_agent/concerns/streaming.rb +282 -0
- data/lib/active_agent/concerns/tooling.rb +23 -0
- data/lib/active_agent/concerns/view.rb +150 -0
- data/lib/active_agent/configuration.rb +442 -20
- data/lib/active_agent/generation.rb +141 -47
- data/lib/active_agent/providers/_base_provider.rb +420 -0
- data/lib/active_agent/providers/anthropic/_types.rb +63 -0
- data/lib/active_agent/providers/anthropic/options.rb +53 -0
- data/lib/active_agent/providers/anthropic/request.rb +163 -0
- data/lib/active_agent/providers/anthropic/transforms.rb +353 -0
- data/lib/active_agent/providers/anthropic_provider.rb +254 -0
- data/lib/active_agent/providers/common/messages/_types.rb +160 -0
- data/lib/active_agent/providers/common/messages/assistant.rb +57 -0
- data/lib/active_agent/providers/common/messages/base.rb +17 -0
- data/lib/active_agent/providers/common/messages/system.rb +20 -0
- data/lib/active_agent/providers/common/messages/tool.rb +21 -0
- data/lib/active_agent/providers/common/messages/user.rb +20 -0
- data/lib/active_agent/providers/common/model.rb +361 -0
- data/lib/active_agent/providers/common/response.rb +13 -0
- data/lib/active_agent/providers/common/responses/_types.rb +51 -0
- data/lib/active_agent/providers/common/responses/base.rb +199 -0
- data/lib/active_agent/providers/common/responses/embed.rb +33 -0
- data/lib/active_agent/providers/common/responses/format.rb +31 -0
- data/lib/active_agent/providers/common/responses/message.rb +3 -0
- data/lib/active_agent/providers/common/responses/prompt.rb +42 -0
- data/lib/active_agent/providers/common/usage.rb +385 -0
- data/lib/active_agent/providers/concerns/exception_handler.rb +72 -0
- data/lib/active_agent/providers/concerns/instrumentation.rb +263 -0
- data/lib/active_agent/providers/concerns/previewable.rb +150 -0
- data/lib/active_agent/providers/log_subscriber.rb +178 -0
- data/lib/active_agent/providers/mock/_types.rb +77 -0
- data/lib/active_agent/providers/mock/embedding_request.rb +17 -0
- data/lib/active_agent/providers/mock/messages/_types.rb +103 -0
- data/lib/active_agent/providers/mock/messages/assistant.rb +26 -0
- data/lib/active_agent/providers/mock/messages/base.rb +63 -0
- data/lib/active_agent/providers/mock/messages/user.rb +18 -0
- data/lib/active_agent/providers/mock/options.rb +30 -0
- data/lib/active_agent/providers/mock/request.rb +38 -0
- data/lib/active_agent/providers/mock_provider.rb +311 -0
- data/lib/active_agent/providers/ollama/_types.rb +5 -0
- data/lib/active_agent/providers/ollama/chat/_types.rb +44 -0
- data/lib/active_agent/providers/ollama/chat/request.rb +249 -0
- data/lib/active_agent/providers/ollama/chat/transforms.rb +135 -0
- data/lib/active_agent/providers/ollama/embedding/_types.rb +44 -0
- data/lib/active_agent/providers/ollama/embedding/request.rb +190 -0
- data/lib/active_agent/providers/ollama/embedding/transforms.rb +160 -0
- data/lib/active_agent/providers/ollama/options.rb +27 -0
- data/lib/active_agent/providers/ollama_provider.rb +94 -0
- data/lib/active_agent/providers/open_ai/_base.rb +59 -0
- data/lib/active_agent/providers/open_ai/_types.rb +5 -0
- data/lib/active_agent/providers/open_ai/chat/_types.rb +56 -0
- data/lib/active_agent/providers/open_ai/chat/request.rb +161 -0
- data/lib/active_agent/providers/open_ai/chat/transforms.rb +364 -0
- data/lib/active_agent/providers/open_ai/chat_provider.rb +219 -0
- data/lib/active_agent/providers/open_ai/embedding/_types.rb +56 -0
- data/lib/active_agent/providers/open_ai/embedding/request.rb +53 -0
- data/lib/active_agent/providers/open_ai/embedding/transforms.rb +88 -0
- data/lib/active_agent/providers/open_ai/options.rb +74 -0
- data/lib/active_agent/providers/open_ai/responses/_types.rb +44 -0
- data/lib/active_agent/providers/open_ai/responses/request.rb +129 -0
- data/lib/active_agent/providers/open_ai/responses/transforms.rb +228 -0
- data/lib/active_agent/providers/open_ai/responses_provider.rb +200 -0
- data/lib/active_agent/providers/open_ai_provider.rb +94 -0
- data/lib/active_agent/providers/open_router/_types.rb +71 -0
- data/lib/active_agent/providers/open_router/options.rb +141 -0
- data/lib/active_agent/providers/open_router/request.rb +249 -0
- data/lib/active_agent/providers/open_router/requests/_types.rb +197 -0
- data/lib/active_agent/providers/open_router/requests/messages/_types.rb +56 -0
- data/lib/active_agent/providers/open_router/requests/messages/content/_types.rb +97 -0
- data/lib/active_agent/providers/open_router/requests/messages/content/file.rb +43 -0
- data/lib/active_agent/providers/open_router/requests/messages/content/files/_types.rb +61 -0
- data/lib/active_agent/providers/open_router/requests/messages/content/files/details.rb +37 -0
- data/lib/active_agent/providers/open_router/requests/plugin.rb +41 -0
- data/lib/active_agent/providers/open_router/requests/plugins/_types.rb +46 -0
- data/lib/active_agent/providers/open_router/requests/plugins/pdf_config.rb +51 -0
- data/lib/active_agent/providers/open_router/requests/prediction.rb +34 -0
- data/lib/active_agent/providers/open_router/requests/provider_preferences/_types.rb +44 -0
- data/lib/active_agent/providers/open_router/requests/provider_preferences/max_price.rb +64 -0
- data/lib/active_agent/providers/open_router/requests/provider_preferences.rb +105 -0
- data/lib/active_agent/providers/open_router/requests/response_format.rb +77 -0
- data/lib/active_agent/providers/open_router/transforms.rb +134 -0
- data/lib/active_agent/providers/open_router_provider.rb +62 -0
- data/lib/active_agent/providers/openai_provider.rb +2 -0
- data/lib/active_agent/providers/openrouter_provider.rb +2 -0
- data/lib/active_agent/railtie.rb +8 -6
- data/lib/active_agent/schema_generator.rb +333 -166
- data/lib/active_agent/version.rb +1 -1
- data/lib/active_agent.rb +112 -36
- data/lib/generators/active_agent/agent/USAGE +78 -0
- data/lib/generators/active_agent/{agent_generator.rb → agent/agent_generator.rb} +14 -4
- data/lib/generators/active_agent/install/USAGE +25 -0
- data/lib/generators/active_agent/{install_generator.rb → install/install_generator.rb} +1 -19
- data/lib/generators/active_agent/templates/agent.rb.tt +7 -3
- data/lib/generators/active_agent/templates/application_agent.rb.tt +0 -2
- data/lib/generators/erb/agent_generator.rb +31 -16
- data/lib/generators/erb/templates/instructions.md.erb.tt +3 -0
- data/lib/generators/erb/templates/instructions.md.tt +3 -0
- data/lib/generators/erb/templates/instructions.text.tt +1 -0
- data/lib/generators/erb/templates/message.md.erb.tt +5 -0
- data/lib/generators/erb/templates/schema.json.tt +10 -0
- data/lib/generators/test_unit/agent_generator.rb +1 -1
- data/lib/generators/test_unit/templates/functional_test.rb.tt +4 -2
- metadata +182 -71
- data/lib/active_agent/action_prompt/action.rb +0 -13
- data/lib/active_agent/action_prompt/base.rb +0 -623
- data/lib/active_agent/action_prompt/message.rb +0 -126
- data/lib/active_agent/action_prompt/prompt.rb +0 -136
- data/lib/active_agent/action_prompt.rb +0 -19
- data/lib/active_agent/callbacks.rb +0 -33
- data/lib/active_agent/generation_provider/anthropic_provider.rb +0 -163
- data/lib/active_agent/generation_provider/base.rb +0 -55
- data/lib/active_agent/generation_provider/base_adapter.rb +0 -19
- data/lib/active_agent/generation_provider/error_handling.rb +0 -167
- data/lib/active_agent/generation_provider/log_subscriber.rb +0 -92
- data/lib/active_agent/generation_provider/message_formatting.rb +0 -107
- data/lib/active_agent/generation_provider/ollama_provider.rb +0 -66
- data/lib/active_agent/generation_provider/open_ai_provider.rb +0 -279
- data/lib/active_agent/generation_provider/open_router_provider.rb +0 -385
- data/lib/active_agent/generation_provider/parameter_builder.rb +0 -119
- data/lib/active_agent/generation_provider/response.rb +0 -75
- data/lib/active_agent/generation_provider/responses_adapter.rb +0 -44
- data/lib/active_agent/generation_provider/stream_processing.rb +0 -58
- data/lib/active_agent/generation_provider/tool_management.rb +0 -142
- data/lib/active_agent/generation_provider.rb +0 -67
- data/lib/active_agent/log_subscriber.rb +0 -44
- data/lib/active_agent/parameterized.rb +0 -75
- data/lib/active_agent/prompt_helper.rb +0 -19
- data/lib/active_agent/queued_generation.rb +0 -12
- data/lib/active_agent/rescuable.rb +0 -34
- data/lib/active_agent/sanitizers.rb +0 -40
- data/lib/active_agent/streaming.rb +0 -34
- data/lib/active_agent/test_case.rb +0 -125
- data/lib/generators/USAGE +0 -47
- data/lib/generators/active_agent/USAGE +0 -56
- data/lib/generators/erb/install_generator.rb +0 -44
- data/lib/generators/erb/templates/layout.html.erb.tt +0 -1
- data/lib/generators/erb/templates/layout.json.erb.tt +0 -1
- data/lib/generators/erb/templates/layout.text.erb.tt +0 -1
- data/lib/generators/erb/templates/view.html.erb.tt +0 -5
- data/lib/generators/erb/templates/view.json.erb.tt +0 -16
- /data/lib/active_agent/{preview.rb → concerns/preview.rb} +0 -0
- /data/lib/generators/erb/templates/{view.text.erb.tt → message.text.erb.tt} +0 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
require_relative "_base"
|
|
2
|
+
require_relative "responses/_types"
|
|
3
|
+
require_relative "responses/transforms"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module OpenAI
|
|
8
|
+
# Provider implementation for OpenAI's Responses API
|
|
9
|
+
#
|
|
10
|
+
# Uses the responses endpoint for improved streaming and structured function
|
|
11
|
+
# calling compared to the chat completions endpoint.
|
|
12
|
+
#
|
|
13
|
+
# @see Base
|
|
14
|
+
# @see https://platform.openai.com/docs/api-reference/responses
|
|
15
|
+
class ResponsesProvider < Base
|
|
16
|
+
# @return [Class]
|
|
17
|
+
def self.options_klass
|
|
18
|
+
Options
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @return [Responses::RequestType]
|
|
22
|
+
def self.prompt_request_type
|
|
23
|
+
Responses::RequestType.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
# @return [Object] OpenAI client's responses endpoint
|
|
29
|
+
def api_prompt_executer
|
|
30
|
+
client.responses
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @see BaseProvider#api_response_normalize
|
|
34
|
+
# @param api_response [OpenAI::Models::Responses::Response]
|
|
35
|
+
# @return [Hash] normalized response hash
|
|
36
|
+
def api_response_normalize(api_response)
|
|
37
|
+
return api_response unless api_response
|
|
38
|
+
|
|
39
|
+
Responses::Transforms.gem_to_hash(api_response)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Processes streaming response chunks from the Responses API
|
|
43
|
+
#
|
|
44
|
+
# Event types handled:
|
|
45
|
+
# - `:"response.created"`, `:"response.in_progress"` - response lifecycle
|
|
46
|
+
# - `:"response.output_item.added"` - message or function call added
|
|
47
|
+
# - `:"response.content_part.added"` - content part started
|
|
48
|
+
# - `:"response.output_text.delta"` - incremental text updates
|
|
49
|
+
# - `:"response.output_text.done"` - complete text
|
|
50
|
+
# - `:"response.function_call_arguments.delta"` - function argument updates
|
|
51
|
+
# - `:"response.function_call_arguments.done"` - complete function arguments
|
|
52
|
+
# - `:"response.content_part.done"` - content part completed
|
|
53
|
+
# - `:"response.output_item.done"` - message or function call completed
|
|
54
|
+
# - `:"response.completed"` - response finished
|
|
55
|
+
#
|
|
56
|
+
# @param api_response_event [Hash] streaming chunk with :type key
|
|
57
|
+
# @return [void]
|
|
58
|
+
# @see Base#process_stream_chunk
|
|
59
|
+
def process_stream_chunk(api_response_event)
|
|
60
|
+
instrument("stream_chunk.active_agent", chunk_type: api_response_event.type)
|
|
61
|
+
|
|
62
|
+
case api_response_event.type
|
|
63
|
+
# Response Created
|
|
64
|
+
when :"response.created", :"response.in_progress"
|
|
65
|
+
broadcast_stream_open
|
|
66
|
+
|
|
67
|
+
# -> Message Created
|
|
68
|
+
when :"response.output_item.added"
|
|
69
|
+
process_stream_output_item_added(api_response_event)
|
|
70
|
+
|
|
71
|
+
# -> -> Content Part Create
|
|
72
|
+
when :"response.content_part.added"
|
|
73
|
+
|
|
74
|
+
# -> -> -> Content Text Append
|
|
75
|
+
when :"response.output_text.delta"
|
|
76
|
+
message = message_stack.find { _1[:id] == api_response_event.item_id }
|
|
77
|
+
message[:content] += api_response_event.delta
|
|
78
|
+
broadcast_stream_update(message, api_response_event.delta)
|
|
79
|
+
|
|
80
|
+
# -> -> -> Content Text Completed [Full Text]
|
|
81
|
+
when :"response.output_text.done"
|
|
82
|
+
message = message_stack.find { _1[:id] == api_response_event.item_id }
|
|
83
|
+
message[:content] = api_response_event.text
|
|
84
|
+
broadcast_stream_update(message, nil) # Don't double send content
|
|
85
|
+
|
|
86
|
+
# -> -> -> Content Function Call Append
|
|
87
|
+
when :"response.function_call_arguments.delta", :"response.function_call_arguments.done"
|
|
88
|
+
# No-Op: Wait for FC to Land
|
|
89
|
+
|
|
90
|
+
# -> -> Content Part Completed [Full Part]
|
|
91
|
+
when :"response.content_part.done"
|
|
92
|
+
|
|
93
|
+
# -> Message Completed
|
|
94
|
+
when :"response.output_item.done"
|
|
95
|
+
process_stream_output_item_done(api_response_event)
|
|
96
|
+
|
|
97
|
+
# Response Completed
|
|
98
|
+
when :"response.completed"
|
|
99
|
+
# Once we are finished, close out and run tooling callbacks (Recursive)
|
|
100
|
+
process_prompt_finished
|
|
101
|
+
else
|
|
102
|
+
raise "Unexpected Response Chunk Type: #{api_response_event.type}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Processes output item added events from streaming response
|
|
107
|
+
#
|
|
108
|
+
# Handles message and function_call item types. For messages, adds to stack
|
|
109
|
+
# with empty content. For function calls, waits for completion event.
|
|
110
|
+
#
|
|
111
|
+
# Required because API returns empty array instead of empty string for
|
|
112
|
+
# initial message content due to serialization bug.
|
|
113
|
+
#
|
|
114
|
+
# @param api_response_event [Hash] response chunk with :item key
|
|
115
|
+
# @return [void]
|
|
116
|
+
def process_stream_output_item_added(api_response_event)
|
|
117
|
+
case api_response_event.item.type
|
|
118
|
+
when :message
|
|
119
|
+
# PATCH: API returns an empty array instead of empty string due to a bug in their serialization
|
|
120
|
+
item_hash = Responses::Transforms.gem_to_hash(api_response_event.item).compact_blank
|
|
121
|
+
message_stack << { content: "" }.merge(item_hash)
|
|
122
|
+
when :function_call
|
|
123
|
+
# No-Op: Wait for FC to Land (-> response.output_item.done)
|
|
124
|
+
else
|
|
125
|
+
raise "Unexpected Item Type: #{api_response_event.item.type}"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Processes output item completion events from streaming response
|
|
130
|
+
#
|
|
131
|
+
# For function calls, adds completed item to message stack.
|
|
132
|
+
# For messages, no action needed as content already updated via delta events.
|
|
133
|
+
#
|
|
134
|
+
# @param api_response_event [Hash] response chunk with completed :item
|
|
135
|
+
# @return [void]
|
|
136
|
+
def process_stream_output_item_done(api_response_event)
|
|
137
|
+
case api_response_event.item.type
|
|
138
|
+
when :message
|
|
139
|
+
# No-Op: Message Up to Date
|
|
140
|
+
when :function_call
|
|
141
|
+
item_hash = Responses::Transforms.gem_to_hash(api_response_event.item)
|
|
142
|
+
message_stack << item_hash
|
|
143
|
+
else
|
|
144
|
+
raise "Unexpected Item Type: #{api_response_event.item.type}"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Executes function calls and creates output messages for conversation continuation
|
|
149
|
+
#
|
|
150
|
+
# @param api_function_calls [Array<Hash>] function calls with :call_id and :name keys
|
|
151
|
+
# @return [void]
|
|
152
|
+
# @see Base#process_function_calls
|
|
153
|
+
def process_function_calls(api_function_calls)
|
|
154
|
+
api_function_calls.each do |api_function_call|
|
|
155
|
+
output = instrument("tool_call.active_agent", tool_name: api_function_call[:name]) do
|
|
156
|
+
process_tool_call_function(api_function_call).to_json
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Create native gem input item for function call output
|
|
160
|
+
message = ::OpenAI::Models::Responses::ResponseInputItem::FunctionCallOutput.new(
|
|
161
|
+
call_id: api_function_call[:call_id],
|
|
162
|
+
output:
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Convert to hash for message_stack
|
|
166
|
+
message_stack.push(Responses::Transforms.gem_to_hash(message))
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Converts OpenAI gem response object to hash for storage.
|
|
171
|
+
#
|
|
172
|
+
# @param api_response [OpenAI::Models::Responses::Response]
|
|
173
|
+
# @return [Common::PromptResponse, nil]
|
|
174
|
+
def process_prompt_finished(api_response = nil)
|
|
175
|
+
# Convert gem object to hash so that raw_response["usage"] works
|
|
176
|
+
api_response_hash = api_response ? Responses::Transforms.gem_to_hash(api_response) : nil
|
|
177
|
+
super(api_response_hash)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Extracts messages from completed API response.
|
|
181
|
+
#
|
|
182
|
+
# @param api_response [Hash] converted response hash
|
|
183
|
+
# @return [Array, nil] output array from response.output or nil
|
|
184
|
+
def process_prompt_finished_extract_messages(api_response)
|
|
185
|
+
return unless api_response
|
|
186
|
+
|
|
187
|
+
# Response is already a hash from process_prompt_finished
|
|
188
|
+
api_response[:output]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Extracts function calls from message stack.
|
|
192
|
+
#
|
|
193
|
+
# @return [Array<Hash>] function call objects with type "function_call"
|
|
194
|
+
def process_prompt_finished_extract_function_calls
|
|
195
|
+
message_stack.select { _1[:type] == "function_call" }
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require_relative "_base_provider"
|
|
2
|
+
|
|
3
|
+
require_gem!(:openai, __FILE__)
|
|
4
|
+
|
|
5
|
+
require_relative "open_ai/_base"
|
|
6
|
+
require_relative "open_ai/chat_provider"
|
|
7
|
+
require_relative "open_ai/responses_provider"
|
|
8
|
+
require_relative "open_ai/embedding/_types"
|
|
9
|
+
|
|
10
|
+
module ActiveAgent
|
|
11
|
+
module Providers
|
|
12
|
+
# Router for OpenAI's API versions based on supported features.
|
|
13
|
+
#
|
|
14
|
+
# This provider acts as a thin wrapper that routes requests between different versions
|
|
15
|
+
# of OpenAI's API (Chat API and Responses API) depending on the features used in the prompt.
|
|
16
|
+
# It automatically selects the appropriate API version based on:
|
|
17
|
+
# - Explicit API version specification (+:api_version+ option)
|
|
18
|
+
# - Presence of audio content in the request
|
|
19
|
+
#
|
|
20
|
+
# @example Basic usage
|
|
21
|
+
# provider = ActiveAgent::Providers::OpenAIProvider.new(...)
|
|
22
|
+
# result = provider.generate
|
|
23
|
+
#
|
|
24
|
+
# @see https://platform.openai.com/docs/guides/migrate-to-responses
|
|
25
|
+
class OpenAIProvider < OpenAI::Base
|
|
26
|
+
# Returns the embedding request type for OpenAI.
|
|
27
|
+
#
|
|
28
|
+
# @return [ActiveModel::Type::Value] The OpenAI embedding request type
|
|
29
|
+
def self.embed_request_type
|
|
30
|
+
OpenAI::Embedding::RequestType.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
attr_internal :api_version
|
|
34
|
+
attr_internal :raw_options
|
|
35
|
+
|
|
36
|
+
# Initializes the OpenAI provider router.
|
|
37
|
+
#
|
|
38
|
+
# Since this layer is just routing based on API version, we want to wait
|
|
39
|
+
# to cast values into their types.
|
|
40
|
+
#
|
|
41
|
+
# @param kwargs [Hash] Configuration options for the provider
|
|
42
|
+
# @option kwargs [Symbol] :service The service name to validate
|
|
43
|
+
# @option kwargs [Symbol] :api_version The OpenAI API version to use (:chat or :responses)
|
|
44
|
+
def initialize(kwargs = {})
|
|
45
|
+
# For Routing Prompt APIs
|
|
46
|
+
self.api_version = kwargs.delete(:api_version)
|
|
47
|
+
self.raw_options = kwargs.deep_dup
|
|
48
|
+
|
|
49
|
+
super
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Generates a response by routing to the appropriate OpenAI API version.
|
|
53
|
+
#
|
|
54
|
+
# This method determines which API version to use based on the prompt context:
|
|
55
|
+
# - Uses Chat API if +api_version: :chat+ is specified or audio is present
|
|
56
|
+
# - Uses Responses API otherwise (default)
|
|
57
|
+
#
|
|
58
|
+
# @return [Object] The generation result from the selected API provider
|
|
59
|
+
#
|
|
60
|
+
# @see https://platform.openai.com/docs/guides/migrate-to-responses
|
|
61
|
+
def prompt
|
|
62
|
+
if api_version == :chat || context[:audio].present?
|
|
63
|
+
OpenAI::ChatProvider.new(raw_options).prompt
|
|
64
|
+
else # api_version == :responses || true
|
|
65
|
+
OpenAI::ResponsesProvider.new(raw_options).prompt
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Generates a preview by routing to the appropriate OpenAI API version.
|
|
70
|
+
#
|
|
71
|
+
# Routes to Chat API or Responses API using the same logic as {#prompt}.
|
|
72
|
+
#
|
|
73
|
+
# @return [String] markdown-formatted preview
|
|
74
|
+
# @see #prompt
|
|
75
|
+
def preview
|
|
76
|
+
if api_version == :chat || context[:audio].present?
|
|
77
|
+
OpenAI::ChatProvider.new(raw_options).preview
|
|
78
|
+
else # api_version == :responses || true
|
|
79
|
+
OpenAI::ResponsesProvider.new(raw_options).preview
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
protected
|
|
84
|
+
|
|
85
|
+
# Executes an embedding request via OpenAI's API.
|
|
86
|
+
#
|
|
87
|
+
# @param parameters [Hash] The embedding request parameters
|
|
88
|
+
# @return [Object] The embedding response from OpenAI
|
|
89
|
+
def api_embed_execute(parameters)
|
|
90
|
+
client.embeddings.create(**parameters).as_json.deep_symbolize_keys
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "requests/_types"
|
|
4
|
+
|
|
5
|
+
require_relative "request"
|
|
6
|
+
require_relative "options"
|
|
7
|
+
|
|
8
|
+
module ActiveAgent
|
|
9
|
+
module Providers
|
|
10
|
+
module OpenRouter
|
|
11
|
+
# ActiveModel type for casting and serializing OpenRouter requests
|
|
12
|
+
#
|
|
13
|
+
# Handles conversion between hashes, Request objects, and serialized
|
|
14
|
+
# request hashes for the OpenRouter API.
|
|
15
|
+
#
|
|
16
|
+
# @example Type casting
|
|
17
|
+
# type = RequestType.new
|
|
18
|
+
# request = type.cast({ model: "openai/gpt-4", messages: "Hello" })
|
|
19
|
+
# # => #<Request ...>
|
|
20
|
+
#
|
|
21
|
+
# @example Serialization
|
|
22
|
+
# serialized = type.serialize(request)
|
|
23
|
+
# # => { model: "openai/gpt-4", messages: [...] }
|
|
24
|
+
class RequestType < ActiveModel::Type::Value
|
|
25
|
+
# Casts value to Request object
|
|
26
|
+
#
|
|
27
|
+
# @param value [Request, Hash, nil]
|
|
28
|
+
# @return [Request, nil]
|
|
29
|
+
# @raise [ArgumentError] if value cannot be cast
|
|
30
|
+
def cast(value)
|
|
31
|
+
case value
|
|
32
|
+
when Request
|
|
33
|
+
value
|
|
34
|
+
when Hash
|
|
35
|
+
Request.new(**value.deep_symbolize_keys)
|
|
36
|
+
when nil
|
|
37
|
+
nil
|
|
38
|
+
else
|
|
39
|
+
raise ArgumentError, "Cannot cast #{value.class} to Request"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Serializes Request to hash for API submission
|
|
44
|
+
#
|
|
45
|
+
# @param value [Request, Hash, nil]
|
|
46
|
+
# @return [Hash, nil]
|
|
47
|
+
# @raise [ArgumentError] if value cannot be serialized
|
|
48
|
+
def serialize(value)
|
|
49
|
+
case value
|
|
50
|
+
when Request
|
|
51
|
+
value.serialize
|
|
52
|
+
when Hash
|
|
53
|
+
value
|
|
54
|
+
when nil
|
|
55
|
+
nil
|
|
56
|
+
else
|
|
57
|
+
raise ArgumentError, "Cannot serialize #{value.class}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Deserializes value from storage
|
|
62
|
+
#
|
|
63
|
+
# @param value [Object]
|
|
64
|
+
# @return [Request, nil]
|
|
65
|
+
def deserialize(value)
|
|
66
|
+
cast(value)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../open_ai/options"
|
|
4
|
+
require_relative "requests/response_format"
|
|
5
|
+
require_relative "requests/prediction"
|
|
6
|
+
require_relative "requests/provider_preferences"
|
|
7
|
+
|
|
8
|
+
module ActiveAgent
|
|
9
|
+
module Providers
|
|
10
|
+
module OpenRouter
|
|
11
|
+
# Configuration options for OpenRouter provider
|
|
12
|
+
#
|
|
13
|
+
# Extends OpenAI::Options with OpenRouter-specific settings including
|
|
14
|
+
# HTTP-Referer and X-Title headers for app identification and ranking.
|
|
15
|
+
#
|
|
16
|
+
# @example Basic configuration
|
|
17
|
+
# options = Options.new(
|
|
18
|
+
# api_key: 'sk-or-v1-...',
|
|
19
|
+
# app_name: 'MyApp',
|
|
20
|
+
# site_url: 'https://myapp.com'
|
|
21
|
+
# )
|
|
22
|
+
#
|
|
23
|
+
# @example Rails auto-configuration
|
|
24
|
+
# # Automatically resolves app_name from Rails.application
|
|
25
|
+
# # and site_url from routes.default_url_options
|
|
26
|
+
# options = Options.new(api_key: ENV['OPENROUTER_API_KEY'])
|
|
27
|
+
#
|
|
28
|
+
# @see https://openrouter.ai/docs/api-keys OpenRouter API Keys
|
|
29
|
+
# @see https://openrouter.ai/docs/rankings OpenRouter App Rankings
|
|
30
|
+
class Options < ActiveAgent::Providers::OpenAI::Options
|
|
31
|
+
# @!attribute base_url
|
|
32
|
+
# @return [String] API endpoint (default: "https://openrouter.ai/api/v1")
|
|
33
|
+
attribute :base_url, :string, as: "https://openrouter.ai/api/v1"
|
|
34
|
+
|
|
35
|
+
# @!attribute app_name
|
|
36
|
+
# @return [String] application name for X-Title header (default: "ActiveAgent" or Rails app name)
|
|
37
|
+
attribute :app_name, :string, fallback: "ActiveAgent"
|
|
38
|
+
|
|
39
|
+
# @!attribute site_url
|
|
40
|
+
# @return [String] site URL for HTTP-Referer header (default: "https://activeagents.ai/" or Rails URL)
|
|
41
|
+
attribute :site_url, :string, fallback: "https://activeagents.ai/"
|
|
42
|
+
|
|
43
|
+
# Creates new OpenRouter options with auto-resolution
|
|
44
|
+
#
|
|
45
|
+
# Automatically resolves app_name from Rails application name and
|
|
46
|
+
# site_url from Rails routes/ActionMailer default_url_options.
|
|
47
|
+
#
|
|
48
|
+
# @param kwargs [Hash] configuration options
|
|
49
|
+
# @option kwargs [String] :api_key OpenRouter API key
|
|
50
|
+
# @option kwargs [String] :app_name application name for rankings
|
|
51
|
+
# @option kwargs [String] :site_url site URL for rankings
|
|
52
|
+
# @return [Options]
|
|
53
|
+
def initialize(kwargs = {})
|
|
54
|
+
kwargs = kwargs.deep_symbolize_keys if kwargs.respond_to?(:deep_symbolize_keys)
|
|
55
|
+
|
|
56
|
+
super(**deep_compact(kwargs.merge(
|
|
57
|
+
app_name: kwargs[:app_name] || resolve_app_name(kwargs),
|
|
58
|
+
site_url: kwargs[:site_url] || resolve_site_url(kwargs),
|
|
59
|
+
)))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Serializes options for API requests
|
|
63
|
+
#
|
|
64
|
+
# Excludes app_name and site_url as they're sent via headers.
|
|
65
|
+
#
|
|
66
|
+
# @return [Hash] serialized options
|
|
67
|
+
def serialize
|
|
68
|
+
super.except(:app_name, :site_url)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Returns extra headers for OpenRouter API
|
|
72
|
+
#
|
|
73
|
+
# Maps app_name and site_url to OpenRouter's required headers:
|
|
74
|
+
# - HTTP-Referer: site_url
|
|
75
|
+
# - X-Title: app_name
|
|
76
|
+
#
|
|
77
|
+
# @return [Hash] headers hash
|
|
78
|
+
def extra_headers
|
|
79
|
+
deep_compact(
|
|
80
|
+
"http-referer" => site_url.presence,
|
|
81
|
+
"x-title" => app_name.presence
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def resolve_api_key(kwargs)
|
|
88
|
+
kwargs["api_key"] ||
|
|
89
|
+
ENV["OPENROUTER_API_KEY"] ||
|
|
90
|
+
ENV["OPEN_ROUTER_API_KEY"] ||
|
|
91
|
+
ENV["OPENROUTER_ACCESS_TOKEN"] ||
|
|
92
|
+
ENV["OPEN_ROUTER_ACCESS_TOKEN"]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Not Used as Part of Open Router
|
|
96
|
+
def resolve_organization_id(kwargs) = nil
|
|
97
|
+
def resolve_project_id(kwargs) = nil
|
|
98
|
+
|
|
99
|
+
def resolve_app_name(kwargs)
|
|
100
|
+
if defined?(Rails) && Rails.application
|
|
101
|
+
Rails.application.class.name.split("::").first
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def resolve_site_url(kwargs)
|
|
106
|
+
# First check ActiveAgent kwargs
|
|
107
|
+
return kwargs[:default_url_options][:host] if kwargs.dig(:default_url_options, :host)
|
|
108
|
+
|
|
109
|
+
# Then check Rails routes default_url_options
|
|
110
|
+
if defined?(Rails) && Rails.application&.routes&.default_url_options&.any?
|
|
111
|
+
host = Rails.application.routes.default_url_options[:host]
|
|
112
|
+
port = Rails.application.routes.default_url_options[:port]
|
|
113
|
+
protocol = Rails.application.routes.default_url_options[:protocol] || "https"
|
|
114
|
+
|
|
115
|
+
if host
|
|
116
|
+
url = "#{protocol}://#{host}"
|
|
117
|
+
url += ":#{port}" if port && port != 80 && port != 443
|
|
118
|
+
return url
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Finally check ActionMailer options as fallback
|
|
123
|
+
if defined?(Rails) && Rails.application&.config&.action_mailer&.default_url_options
|
|
124
|
+
options = Rails.application.config.action_mailer.default_url_options
|
|
125
|
+
host = options[:host]
|
|
126
|
+
port = options[:port]
|
|
127
|
+
protocol = options[:protocol] || "https"
|
|
128
|
+
|
|
129
|
+
if host
|
|
130
|
+
url = "#{protocol}://#{host}"
|
|
131
|
+
url += ":#{port}" if port && port != 80 && port != 443
|
|
132
|
+
return url
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
nil
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|