activeagent 1.0.1 → 1.0.2
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/README.md +10 -4
- data/lib/active_agent/base.rb +3 -2
- data/lib/active_agent/concerns/provider.rb +6 -2
- data/lib/active_agent/concerns/rescue.rb +39 -0
- data/lib/active_agent/concerns/streaming.rb +2 -1
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/api/traces_controller.rb +117 -0
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/application_controller.rb +54 -0
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/dashboard_controller.rb +126 -0
- data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/traces_controller.rb +103 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/agent_execution_job.rb +56 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/application_job.rb +14 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_cleanup_job.rb +49 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_provision_job.rb +65 -0
- data/lib/active_agent/dashboard/app/jobs/active_agent/process_telemetry_traces_job.rb +77 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent.rb +256 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_run.rb +113 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_template.rb +208 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_version.rb +60 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/application_record.rb +46 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_action.rb +125 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_snapshot.rb +83 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_run.rb +52 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_session.rb +169 -0
- data/lib/active_agent/dashboard/app/models/active_agent/dashboard/session_recording.rb +193 -0
- data/lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb +198 -0
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/_trace_detail.html.erb +105 -0
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/index.html.erb +135 -0
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/metrics.html.erb +143 -0
- data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/show.html.erb +36 -0
- data/lib/active_agent/dashboard/app/views/layouts/active_agent/dashboard/application.html.erb +94 -0
- data/lib/active_agent/dashboard/config/routes.rb +78 -0
- data/lib/active_agent/dashboard/engine.rb +39 -0
- data/lib/active_agent/dashboard.rb +151 -0
- data/lib/active_agent/providers/_base_provider.rb +2 -1
- data/lib/active_agent/providers/anthropic_provider.rb +14 -4
- data/lib/active_agent/providers/azure/_types.rb +5 -0
- data/lib/active_agent/providers/azure/options.rb +111 -0
- data/lib/active_agent/providers/azure_open_ai_provider.rb +2 -0
- data/lib/active_agent/providers/azure_openai_provider.rb +2 -0
- data/lib/active_agent/providers/azure_provider.rb +133 -0
- data/lib/active_agent/providers/azureopenai_provider.rb +2 -0
- data/lib/active_agent/providers/bedrock/_types.rb +8 -0
- data/lib/active_agent/providers/bedrock/bearer_client.rb +109 -0
- data/lib/active_agent/providers/bedrock/options.rb +77 -0
- data/lib/active_agent/providers/bedrock_provider.rb +84 -0
- data/lib/active_agent/providers/common/messages/_types.rb +6 -2
- data/lib/active_agent/providers/concerns/exception_handler.rb +1 -0
- data/lib/active_agent/providers/gemini/_types.rb +19 -0
- data/lib/active_agent/providers/gemini/options.rb +41 -0
- data/lib/active_agent/providers/gemini_provider.rb +94 -0
- data/lib/active_agent/providers/open_ai/chat/transforms.rb +37 -1
- data/lib/active_agent/providers/open_ai/chat_provider.rb +2 -0
- data/lib/active_agent/providers/ruby_llm/_types.rb +77 -0
- data/lib/active_agent/providers/ruby_llm/embedding_request.rb +16 -0
- data/lib/active_agent/providers/ruby_llm/messages/_types.rb +109 -0
- data/lib/active_agent/providers/ruby_llm/messages/assistant.rb +27 -0
- data/lib/active_agent/providers/ruby_llm/messages/base.rb +48 -0
- data/lib/active_agent/providers/ruby_llm/messages/system.rb +18 -0
- data/lib/active_agent/providers/ruby_llm/messages/tool.rb +24 -0
- data/lib/active_agent/providers/ruby_llm/messages/user.rb +18 -0
- data/lib/active_agent/providers/ruby_llm/options.rb +28 -0
- data/lib/active_agent/providers/ruby_llm/request.rb +30 -0
- data/lib/active_agent/providers/ruby_llm/tool_proxy.rb +45 -0
- data/lib/active_agent/providers/ruby_llm_provider.rb +407 -0
- data/lib/active_agent/railtie.rb +32 -1
- data/lib/active_agent/telemetry/configuration.rb +213 -0
- data/lib/active_agent/telemetry/instrumentation.rb +155 -0
- data/lib/active_agent/telemetry/reporter.rb +176 -0
- data/lib/active_agent/telemetry/span.rb +267 -0
- data/lib/active_agent/telemetry/tracer.rb +184 -0
- data/lib/active_agent/telemetry.rb +162 -0
- data/lib/active_agent/version.rb +1 -1
- data/lib/active_agent.rb +2 -0
- data/lib/generators/active_agent/dashboard/install/install_generator.rb +96 -0
- data/lib/generators/active_agent/dashboard/install/templates/initializer.rb +89 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_runs.rb +42 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_templates.rb +38 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agent_versions.rb +22 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_agents.rb +53 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_sandbox_runs.rb +28 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_sandbox_sessions.rb +43 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_session_recordings.rb +44 -0
- data/lib/generators/active_agent/dashboard/install/templates/migrations/create_active_agent_telemetry_traces.rb +56 -0
- data/lib/generators/active_agent/dashboard/install_generator.rb +64 -0
- data/lib/generators/active_agent/dashboard/templates/active_agent_dashboard.rb.erb +30 -0
- data/lib/generators/active_agent/dashboard/templates/create_active_agent_telemetry_traces.rb.erb +30 -0
- metadata +99 -13
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require_relative "_base_provider"
|
|
2
|
+
|
|
3
|
+
require_gem!(:openai, __FILE__)
|
|
4
|
+
|
|
5
|
+
require_relative "open_ai_provider"
|
|
6
|
+
require_relative "gemini/_types"
|
|
7
|
+
|
|
8
|
+
module ActiveAgent
|
|
9
|
+
module Providers
|
|
10
|
+
# Provides access to Google's Gemini API via OpenAI-compatible endpoint.
|
|
11
|
+
#
|
|
12
|
+
# Extends OpenAI provider to work with Gemini's OpenAI-compatible API,
|
|
13
|
+
# enabling access to Gemini models through a familiar interface.
|
|
14
|
+
#
|
|
15
|
+
# @see OpenAI::ChatProvider
|
|
16
|
+
# @see https://ai.google.dev/gemini-api/docs/openai
|
|
17
|
+
class GeminiProvider < OpenAI::ChatProvider
|
|
18
|
+
# @return [String]
|
|
19
|
+
def self.service_name
|
|
20
|
+
"Gemini"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Class]
|
|
24
|
+
def self.options_klass
|
|
25
|
+
namespace::Options
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [ActiveModel::Type::Value]
|
|
29
|
+
def self.prompt_request_type
|
|
30
|
+
namespace::RequestType.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [ActiveModel::Type::Value]
|
|
34
|
+
def self.embed_request_type
|
|
35
|
+
namespace::Embedding::RequestType.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
protected
|
|
39
|
+
|
|
40
|
+
# Executes chat completion request with Gemini-specific error handling.
|
|
41
|
+
#
|
|
42
|
+
# @see OpenAI::ChatProvider#api_prompt_execute
|
|
43
|
+
# @param parameters [Hash]
|
|
44
|
+
# @return [Object, nil] response object or nil for streaming
|
|
45
|
+
# @raise [OpenAI::Errors::APIConnectionError] when Gemini API unreachable
|
|
46
|
+
def api_prompt_execute(parameters)
|
|
47
|
+
super
|
|
48
|
+
|
|
49
|
+
rescue ::OpenAI::Errors::APIConnectionError => exception
|
|
50
|
+
log_connection_error(exception)
|
|
51
|
+
raise exception
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Executes embedding request with Gemini-specific error handling.
|
|
55
|
+
#
|
|
56
|
+
# @param parameters [Hash]
|
|
57
|
+
# @return [Hash] symbolized API response
|
|
58
|
+
# @raise [OpenAI::Errors::APIConnectionError] when Gemini API unreachable
|
|
59
|
+
def api_embed_execute(parameters)
|
|
60
|
+
client.embeddings.create(**parameters).as_json.deep_symbolize_keys
|
|
61
|
+
rescue ::OpenAI::Errors::APIConnectionError => exception
|
|
62
|
+
log_connection_error(exception)
|
|
63
|
+
raise exception
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Merges streaming delta into the message with role cleanup.
|
|
67
|
+
#
|
|
68
|
+
# Overrides parent to handle Gemini's role copying behavior which duplicates
|
|
69
|
+
# the role field in every streaming chunk, requiring manual cleanup to prevent
|
|
70
|
+
# message corruption.
|
|
71
|
+
#
|
|
72
|
+
# @see OpenAI::ChatProvider#message_merge_delta
|
|
73
|
+
# @param message [Hash]
|
|
74
|
+
# @param delta [Hash]
|
|
75
|
+
# @return [Hash]
|
|
76
|
+
def message_merge_delta(message, delta)
|
|
77
|
+
message[:role] = delta.delete(:role) if delta[:role]
|
|
78
|
+
|
|
79
|
+
hash_merge_delta(message, delta)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Logs connection failures with Gemini API details for debugging.
|
|
83
|
+
#
|
|
84
|
+
# @param error [Exception]
|
|
85
|
+
# @return [void]
|
|
86
|
+
def log_connection_error(error)
|
|
87
|
+
instrument("connection_error.provider.active_agent",
|
|
88
|
+
uri_base: options.base_url,
|
|
89
|
+
exception: error.class,
|
|
90
|
+
message: error.message)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_support/core_ext/hash/keys"
|
|
4
|
+
require "uri"
|
|
4
5
|
|
|
5
6
|
module ActiveAgent
|
|
6
7
|
module Providers
|
|
@@ -124,10 +125,19 @@ module ActiveAgent
|
|
|
124
125
|
{ type: "text", text: msg_hash[:text] },
|
|
125
126
|
{ type: "image_url", image_url: { url: msg_hash[:image] } }
|
|
126
127
|
]
|
|
128
|
+
elsif msg_hash.key?(:text) && msg_hash.key?(:document)
|
|
129
|
+
# Shorthand with both text and document: { text: "...", document: "url" }
|
|
130
|
+
[
|
|
131
|
+
{ type: "text", text: msg_hash[:text] },
|
|
132
|
+
build_file_content(msg_hash[:document])
|
|
133
|
+
]
|
|
127
134
|
elsif msg_hash.key?(:image)
|
|
128
135
|
# Shorthand with only image: { image: "url" }
|
|
129
136
|
# Text comes from adjacent prompt arguments
|
|
130
137
|
[ { type: "image_url", image_url: { url: msg_hash[:image] } } ]
|
|
138
|
+
elsif msg_hash.key?(:document)
|
|
139
|
+
# Shorthand with only document: { document: "url" }
|
|
140
|
+
[ build_file_content(msg_hash[:document]) ]
|
|
131
141
|
elsif msg_hash.key?(:text)
|
|
132
142
|
# Shorthand: { text: "..." } or { role: "...", text: "..." }
|
|
133
143
|
msg_hash[:text]
|
|
@@ -137,13 +147,39 @@ module ActiveAgent
|
|
|
137
147
|
end
|
|
138
148
|
|
|
139
149
|
# Create appropriate message param based on role and content
|
|
140
|
-
extra_params = msg_hash.except(:role, :content, :text, :image)
|
|
150
|
+
extra_params = msg_hash.except(:role, :content, :text, :image, :document)
|
|
141
151
|
create_message_param(role, content, extra_params)
|
|
142
152
|
else
|
|
143
153
|
raise ArgumentError, "Cannot normalize #{message.class} to message"
|
|
144
154
|
end
|
|
145
155
|
end
|
|
146
156
|
|
|
157
|
+
# Builds a file content block for the Chat API file type
|
|
158
|
+
#
|
|
159
|
+
# Handles both URL and data URI formats:
|
|
160
|
+
# - URL: "http://example.com/document.pdf" → { type: "file", file: { filename: "...", url: "..." } }
|
|
161
|
+
# - Data URI: "data:application/pdf;base64,..." → { type: "file", file: { filename: "...", file_data: "..." } }
|
|
162
|
+
#
|
|
163
|
+
# @param document [String] URL or data URI
|
|
164
|
+
# @return [Hash] file content block
|
|
165
|
+
def build_file_content(document)
|
|
166
|
+
if document.start_with?("data:")
|
|
167
|
+
# Data URI - extract or infer filename from media type
|
|
168
|
+
media_type = document.match(%r{\Adata:([^;,]+)})&.[](1) || "application/octet-stream"
|
|
169
|
+
extension = media_type.split("/").last&.gsub(/[^a-zA-Z0-9]/, "_") || "bin"
|
|
170
|
+
filename = "document.#{extension}"
|
|
171
|
+
{ type: "file", file: { filename: filename, file_data: document } }
|
|
172
|
+
else
|
|
173
|
+
# Regular URL
|
|
174
|
+
filename = File.basename(URI.parse(document).path.to_s)
|
|
175
|
+
filename = "document.pdf" if filename.empty?
|
|
176
|
+
{ type: "file", file: { filename: filename, url: document } }
|
|
177
|
+
end
|
|
178
|
+
rescue URI::Error
|
|
179
|
+
# Fallback for invalid URLs
|
|
180
|
+
{ type: "file", file: { filename: "document.pdf", url: document } }
|
|
181
|
+
end
|
|
182
|
+
|
|
147
183
|
# Creates the appropriate gem message param class for the given role
|
|
148
184
|
#
|
|
149
185
|
# @param role [String] message role (developer, system, user, assistant, tool, function)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "options"
|
|
4
|
+
require_relative "request"
|
|
5
|
+
require_relative "embedding_request"
|
|
6
|
+
|
|
7
|
+
module ActiveAgent
|
|
8
|
+
module Providers
|
|
9
|
+
module RubyLLM
|
|
10
|
+
# Type for Request model
|
|
11
|
+
class RequestType < ActiveModel::Type::Value
|
|
12
|
+
def cast(value)
|
|
13
|
+
case value
|
|
14
|
+
when Request
|
|
15
|
+
value
|
|
16
|
+
when Hash
|
|
17
|
+
Request.new(**value.deep_symbolize_keys)
|
|
18
|
+
when nil
|
|
19
|
+
nil
|
|
20
|
+
else
|
|
21
|
+
raise ArgumentError, "Cannot cast #{value.class} to Request"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def serialize(value)
|
|
26
|
+
case value
|
|
27
|
+
when Request
|
|
28
|
+
value.serialize
|
|
29
|
+
when Hash
|
|
30
|
+
value
|
|
31
|
+
when nil
|
|
32
|
+
nil
|
|
33
|
+
else
|
|
34
|
+
raise ArgumentError, "Cannot serialize #{value.class}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def deserialize(value)
|
|
39
|
+
cast(value)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Type for embedding requests
|
|
44
|
+
class EmbeddingRequestType < ActiveModel::Type::Value
|
|
45
|
+
def cast(value)
|
|
46
|
+
case value
|
|
47
|
+
when RubyLLM::EmbeddingRequest
|
|
48
|
+
value
|
|
49
|
+
when Hash
|
|
50
|
+
RubyLLM::EmbeddingRequest.new(**value.deep_symbolize_keys)
|
|
51
|
+
when nil
|
|
52
|
+
nil
|
|
53
|
+
else
|
|
54
|
+
raise ArgumentError, "Cannot cast #{value.class} to EmbeddingRequest"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def serialize(value)
|
|
59
|
+
case value
|
|
60
|
+
when RubyLLM::EmbeddingRequest
|
|
61
|
+
value.serialize
|
|
62
|
+
when Hash
|
|
63
|
+
value
|
|
64
|
+
when nil
|
|
65
|
+
nil
|
|
66
|
+
else
|
|
67
|
+
raise ArgumentError, "Cannot serialize #{value.class}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def deserialize(value)
|
|
72
|
+
cast(value)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_agent/providers/common/model"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module RubyLLM
|
|
8
|
+
# Embedding request model for RubyLLM provider.
|
|
9
|
+
class EmbeddingRequest < Common::BaseModel
|
|
10
|
+
attribute :model, :string
|
|
11
|
+
attribute :input
|
|
12
|
+
attribute :dimensions, :integer
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Load all message classes
|
|
4
|
+
require_relative "base"
|
|
5
|
+
require_relative "system"
|
|
6
|
+
require_relative "user"
|
|
7
|
+
require_relative "assistant"
|
|
8
|
+
require_relative "tool"
|
|
9
|
+
|
|
10
|
+
module ActiveAgent
|
|
11
|
+
module Providers
|
|
12
|
+
module RubyLLM
|
|
13
|
+
module Messages
|
|
14
|
+
# Type for Messages array
|
|
15
|
+
class MessagesType < ActiveModel::Type::Value
|
|
16
|
+
def initialize
|
|
17
|
+
super
|
|
18
|
+
@message_type = MessageType.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def cast(value)
|
|
22
|
+
case value
|
|
23
|
+
when Array
|
|
24
|
+
value.map { |v| @message_type.cast(v) }
|
|
25
|
+
when nil
|
|
26
|
+
nil
|
|
27
|
+
else
|
|
28
|
+
raise ArgumentError, "Cannot cast #{value.class} to Messages array"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def serialize(value)
|
|
33
|
+
case value
|
|
34
|
+
when Array
|
|
35
|
+
grouped = []
|
|
36
|
+
|
|
37
|
+
value.each do |message|
|
|
38
|
+
if grouped.empty? || grouped.last.role != message.role
|
|
39
|
+
grouped << message.deep_dup
|
|
40
|
+
else
|
|
41
|
+
grouped.last.content += message.content.deep_dup
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
grouped.map { |v| @message_type.serialize(v) }
|
|
46
|
+
when nil
|
|
47
|
+
nil
|
|
48
|
+
else
|
|
49
|
+
raise ArgumentError, "Cannot serialize #{value.class}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def deserialize(value)
|
|
54
|
+
cast(value)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Type for individual Message
|
|
59
|
+
class MessageType < ActiveModel::Type::Value
|
|
60
|
+
def cast(value)
|
|
61
|
+
case value
|
|
62
|
+
when Base
|
|
63
|
+
value
|
|
64
|
+
when String
|
|
65
|
+
User.new(content: value)
|
|
66
|
+
when Hash
|
|
67
|
+
hash = value.deep_symbolize_keys
|
|
68
|
+
role = hash[:role]&.to_sym
|
|
69
|
+
|
|
70
|
+
case role
|
|
71
|
+
when :user, nil
|
|
72
|
+
User.new(**hash)
|
|
73
|
+
when :assistant
|
|
74
|
+
Assistant.new(**hash)
|
|
75
|
+
when :system
|
|
76
|
+
System.new(**hash)
|
|
77
|
+
when :tool
|
|
78
|
+
Tool.new(**hash)
|
|
79
|
+
else
|
|
80
|
+
raise ArgumentError, "Unknown message role: #{role}"
|
|
81
|
+
end
|
|
82
|
+
when nil
|
|
83
|
+
nil
|
|
84
|
+
else
|
|
85
|
+
raise ArgumentError, "Cannot cast #{value.class} to Message"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def serialize(value)
|
|
90
|
+
case value
|
|
91
|
+
when Base
|
|
92
|
+
value.serialize
|
|
93
|
+
when Hash
|
|
94
|
+
value
|
|
95
|
+
when nil
|
|
96
|
+
nil
|
|
97
|
+
else
|
|
98
|
+
raise ArgumentError, "Cannot serialize #{value.class}"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def deserialize(value)
|
|
103
|
+
cast(value)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module RubyLLM
|
|
8
|
+
module Messages
|
|
9
|
+
# Assistant message for RubyLLM provider.
|
|
10
|
+
#
|
|
11
|
+
# Drops extra fields that are part of the API response but not
|
|
12
|
+
# part of the message structure.
|
|
13
|
+
class Assistant < Base
|
|
14
|
+
attribute :role, :string, as: "assistant"
|
|
15
|
+
attribute :content
|
|
16
|
+
attribute :tool_calls
|
|
17
|
+
|
|
18
|
+
validates :content, presence: true, unless: :tool_calls
|
|
19
|
+
|
|
20
|
+
# Drop API response fields that aren't part of the message
|
|
21
|
+
drop_attributes :usage, :id, :model, :stop_reason, :stop_sequence, :type,
|
|
22
|
+
:input_tokens, :output_tokens
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_agent/providers/common/model"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module RubyLLM
|
|
8
|
+
module Messages
|
|
9
|
+
# Base class for RubyLLM messages.
|
|
10
|
+
class Base < Common::BaseModel
|
|
11
|
+
attribute :role, :string
|
|
12
|
+
attribute :content
|
|
13
|
+
|
|
14
|
+
validates :role, presence: true
|
|
15
|
+
|
|
16
|
+
# Converts to common format.
|
|
17
|
+
#
|
|
18
|
+
# @return [Hash] message in canonical format with role and text content
|
|
19
|
+
def to_common
|
|
20
|
+
{
|
|
21
|
+
role: role,
|
|
22
|
+
content: extract_text_content,
|
|
23
|
+
name: nil
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# Extracts text content from the content structure.
|
|
30
|
+
#
|
|
31
|
+
# @return [String] extracted text content
|
|
32
|
+
def extract_text_content
|
|
33
|
+
case content
|
|
34
|
+
when String
|
|
35
|
+
content
|
|
36
|
+
when Array
|
|
37
|
+
content.select { |block| block.is_a?(Hash) && block[:type] == "text" }
|
|
38
|
+
.map { |block| block[:text] }
|
|
39
|
+
.join("\n")
|
|
40
|
+
else
|
|
41
|
+
content.to_s
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module RubyLLM
|
|
8
|
+
module Messages
|
|
9
|
+
# System message for RubyLLM provider.
|
|
10
|
+
class System < Base
|
|
11
|
+
attribute :role, :string, as: "system"
|
|
12
|
+
|
|
13
|
+
validates :content, presence: true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module RubyLLM
|
|
8
|
+
module Messages
|
|
9
|
+
# Tool result message for RubyLLM provider.
|
|
10
|
+
class Tool < Base
|
|
11
|
+
attribute :role, :string, as: "tool"
|
|
12
|
+
attribute :content
|
|
13
|
+
attribute :tool_call_id, :string
|
|
14
|
+
|
|
15
|
+
def to_common
|
|
16
|
+
common = super
|
|
17
|
+
common[:tool_call_id] = tool_call_id if tool_call_id
|
|
18
|
+
common
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module RubyLLM
|
|
8
|
+
module Messages
|
|
9
|
+
# User message for RubyLLM provider.
|
|
10
|
+
class User < Base
|
|
11
|
+
attribute :role, :string, as: "user"
|
|
12
|
+
|
|
13
|
+
validates :content, presence: true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_agent/providers/common/model"
|
|
4
|
+
|
|
5
|
+
module ActiveAgent
|
|
6
|
+
module Providers
|
|
7
|
+
module RubyLLM
|
|
8
|
+
# Configuration options for the RubyLLM provider.
|
|
9
|
+
#
|
|
10
|
+
# RubyLLM manages its own API keys via RubyLLM.configure, so no
|
|
11
|
+
# provider-specific API key attributes are needed here.
|
|
12
|
+
class Options < Common::BaseModel
|
|
13
|
+
attribute :model, :string
|
|
14
|
+
attribute :temperature, :float
|
|
15
|
+
attribute :max_tokens, :integer
|
|
16
|
+
|
|
17
|
+
def initialize(kwargs = {})
|
|
18
|
+
kwargs = kwargs.deep_symbolize_keys if kwargs.respond_to?(:deep_symbolize_keys)
|
|
19
|
+
super(**deep_compact(kwargs))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def extra_headers
|
|
23
|
+
{}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_agent/providers/common/model"
|
|
4
|
+
|
|
5
|
+
require_relative "messages/_types"
|
|
6
|
+
|
|
7
|
+
module ActiveAgent
|
|
8
|
+
module Providers
|
|
9
|
+
module RubyLLM
|
|
10
|
+
# Request model for RubyLLM provider.
|
|
11
|
+
class Request < Common::BaseModel
|
|
12
|
+
attribute :model, :string
|
|
13
|
+
attribute :messages, Messages::MessagesType.new
|
|
14
|
+
attribute :instructions
|
|
15
|
+
attribute :tools
|
|
16
|
+
attribute :tool_choice
|
|
17
|
+
attribute :temperature, :float
|
|
18
|
+
attribute :max_tokens, :integer
|
|
19
|
+
attribute :stream, :boolean, default: false
|
|
20
|
+
attribute :response_format
|
|
21
|
+
|
|
22
|
+
# Common Format Compatibility
|
|
23
|
+
def message=(value)
|
|
24
|
+
self.messages ||= []
|
|
25
|
+
self.messages << value
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAgent
|
|
4
|
+
module Providers
|
|
5
|
+
module RubyLLM
|
|
6
|
+
# Bridges ActiveAgent tool definitions to RubyLLM's expected tool interface.
|
|
7
|
+
# RubyLLM expects tools as { "name" => tool } where each tool responds to
|
|
8
|
+
# #name, #description, #parameters, #params_schema, and #provider_params.
|
|
9
|
+
class ToolProxy
|
|
10
|
+
attr_reader :name, :description, :parameters
|
|
11
|
+
|
|
12
|
+
def initialize(name:, description:, parameters:)
|
|
13
|
+
@name = name
|
|
14
|
+
@description = description
|
|
15
|
+
@parameters = parameters
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# RubyLLM checks this first; returns the JSON Schema directly so
|
|
19
|
+
# RubyLLM doesn't try to interpret our parameters as Parameter objects.
|
|
20
|
+
# Deep-stringifies keys to match RubyLLM's internal schema format.
|
|
21
|
+
def params_schema
|
|
22
|
+
deep_stringify(@parameters) if @parameters.is_a?(Hash) && @parameters.any?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# RubyLLM merges this into the tool definition
|
|
26
|
+
def provider_params
|
|
27
|
+
{}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def deep_stringify(obj)
|
|
33
|
+
case obj
|
|
34
|
+
when Hash
|
|
35
|
+
obj.each_with_object({}) { |(k, v), h| h[k.to_s] = deep_stringify(v) }
|
|
36
|
+
when Array
|
|
37
|
+
obj.map { |v| deep_stringify(v) }
|
|
38
|
+
else
|
|
39
|
+
obj
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|