activeagent 0.3 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf6889f20afa4e61e46ab6edbe801186aee0d3fe85284f3ce6379509a6a76bc4
4
- data.tar.gz: 3340dcc2d65740cea5a98f4c0640ac806e1686bb48bee2a3260cf3e65b5478b2
3
+ metadata.gz: b5296fe177a73f49b3514b6d82ad8dc45efdf5455b50aed3fb411103551fb308
4
+ data.tar.gz: 889c307af88851d5bb8022f4701af49a96a9701f862cecf1b01bfd625f2568d2
5
5
  SHA512:
6
- metadata.gz: 8c63eaaad2c5049fac65902d5d5676f976580fffd40477287b149d29695c15b75143e1bbf8e60128c04adfc23531770c9ab1c7893a6cf73bd4f258315a93e251
7
- data.tar.gz: 0be4d79a4755d23d1c7e2c659871d8e0480c05583e6fe1d6eae48571fd1d2540cc88ea53a827192109bf76476e237e8c09f1bbc64f97bca429416b3c7503154c
6
+ metadata.gz: '0806180976fb672856ce7396c813659d7ee1edf9e3393e9dbba970b724daa343c0da0397b3edfed9aae6d60b08ee1ac2dd744933b4e177d0c829a3c26e6c63ce'
7
+ data.tar.gz: d59653e4a7e97488b864bcdc0b93558b07920f6ea069ef16070dba7dfe0c36e5876de928008f7b02d848bbb5d2f86d7bf025c52d33be8090f08f1da5fc8c6e9e
data/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.3.2] - 2025-04-15
9
+
10
+ ### Added
11
+ - CI configuration for stable GitHub releases moving forward.
12
+ - Test coverage for core features: ActionPrompt rendering, tool calls, and embeddings.
13
+ - Enhance streaming to support tool calls during stream. Previously, streaming mode blocked tool call execution.
14
+ - Fix layout rendering bug when no block is passed and views now render correctly without requiring a block.
15
+
16
+ ### Removed
17
+ - Generation Provider module and Action Prompt READMEs have been removed, but will be updated along with the main README in the next release.
@@ -1,111 +0,0 @@
1
- # Active Agent: Action Prompt
2
-
3
- Action Prompt provides a structured way to create and manage prompts for AI interactions. It enables composing messages, handling actions/tools, and managing conversation context through several key components.
4
-
5
- Action Prompt manages the overall prompt structure including:
6
-
7
- - Messages list with system/user/assistant roles
8
- - Action/tool definitions
9
- - Headers and context tracking
10
- - Content type and encoding
11
- - Multipart message handling
12
-
13
- ```ruby
14
- prompt = ActionPrompt::Prompt.new(
15
- instructions: "System guidance",
16
- message: "User input",
17
- actions: [tool_definition, action_schema],
18
- context: messages
19
- )
20
- ```
21
-
22
- ## ActionPrompt::Message
23
-
24
- Represents individual messages with:
25
-
26
- ### Content and Role
27
- ```ruby
28
- message = ActionPrompt::Message.new(
29
- content: "Search for a hotel",
30
- role: :user
31
- )
32
- ```
33
-
34
- - Content stores the actual message text
35
- - Role defines the message sender type (system/user/assistant/function/tool)
36
- - Messages form interactions between roles in a Context
37
-
38
- ### Action Requests and Responses
39
- ```ruby
40
- message = ActionPrompt::Message.new(
41
- content: "Search for a hotel",
42
- role: :tool,
43
- requested_actions: [{name: "search", params: {query: "hotel"}}]
44
- )
45
- ```
46
-
47
- - Tracks if message requests actions/tools
48
- - Maintains list of requested_actions
49
- - Action responses include function call results
50
- - Handles tool execution state
51
-
52
- ### Content Type and Encoding
53
- - Default content_type is "text/plain"
54
- - Supports multiple content types for rich messages
55
- - Default charset is UTF-8
56
- - Handles content encoding/decoding
57
-
58
- ### Role Validation
59
- - Enforces valid roles via VALID_ROLES constant
60
- - Validates on message creation
61
- - Raises ArgumentError for invalid roles
62
- - Supported roles: system, assistant, user, tool, function
63
-
64
- ```ruby
65
- message = ActionPrompt::Message.new(
66
- content: "Hello",
67
- role: :user,
68
- content_type: "text/plain",
69
- charset: "UTF-8"
70
- )
71
- ```
72
-
73
- ### ActionPrompt::Action
74
-
75
- Defines callable tools/functions:
76
-
77
- - Name and parameters schema
78
- - Validation and type checking
79
- - Response handling
80
- - Action execution
81
-
82
- ## Usage with Base Agents
83
-
84
- ```ruby
85
- class MyAgent < ActiveAgent::Base
86
- # Define available actions
87
- def action_schemas
88
- [
89
- {name: "search", params: {query: :string}}
90
- ]
91
- end
92
-
93
- # Handle action responses
94
- def perform_action(action)
95
- case action.name
96
- when "search"
97
- search_service.query(action.params[:query])
98
- end
99
- end
100
- end
101
- ```
102
-
103
- The Base agent integrates with ActionPrompt to:
104
-
105
- 1. Create prompts with context
106
- 2. Register available actions
107
- 3. Process action requests
108
- 4. Handle responses and update context
109
- 5. Manage the conversation flow
110
-
111
- See Base.rb for full agent integration details.
@@ -16,6 +16,10 @@ module ActiveAgent
16
16
  validate_role
17
17
  end
18
18
 
19
+ def to_s
20
+ @content.to_s
21
+ end
22
+
19
23
  def to_h
20
24
  hash = {
21
25
  role: role,
@@ -3,7 +3,8 @@ require_relative "message"
3
3
  module ActiveAgent
4
4
  module ActionPrompt
5
5
  class Prompt
6
- attr_accessor :actions, :body, :content_type, :context_id, :instructions, :message, :messages, :options, :mime_version, :charset, :context, :parts
6
+ attr_reader :messages
7
+ attr_accessor :actions, :body, :content_type, :context_id, :instructions, :message, :options, :mime_version, :charset, :context, :parts, :params, :action_choice, :agent_class
7
8
 
8
9
  def initialize(attributes = {})
9
10
  @options = attributes.fetch(:options, {})
@@ -27,6 +28,11 @@ module ActiveAgent
27
28
  set_messages
28
29
  end
29
30
 
31
+ def messages=(messages)
32
+ @messages = messages
33
+ set_messages
34
+ end
35
+
30
36
  # Generate the prompt as a string (for debugging or sending to the provider)
31
37
  def to_s
32
38
  @message.to_s
@@ -62,7 +68,7 @@ module ActiveAgent
62
68
  private
63
69
 
64
70
  def set_messages
65
- @messages = [Message.new(content: @instructions, role: :system)] + @messages
71
+ @messages = [ Message.new(content: @instructions, role: :system) ] + @messages if @instructions.present?
66
72
  end
67
73
 
68
74
  def set_message
@@ -71,6 +77,7 @@ module ActiveAgent
71
77
  elsif @body.is_a?(String) && @message.content.blank?
72
78
  @message = Message.new(content: @body, role: :user)
73
79
  end
80
+
74
81
  @messages << @message
75
82
  end
76
83
  end
@@ -34,90 +34,6 @@ module ActiveAgent
34
34
  # }.freeze
35
35
  end
36
36
 
37
- # # def self.prompt(headers = {}, &)
38
- # # new.prompt(headers, &)
39
- # # end
40
-
41
- # def prompt(headers = {}, &block)
42
- # return @_message if @_prompt_was_called && headers.blank? && !block
43
-
44
- # headers = apply_defaults(headers)
45
-
46
- # @_message = ActiveAgent::ActionPrompt::Prompt.new
47
-
48
- # assign_headers_to_message(@_message, headers)
49
-
50
- # responses = collect_responses(headers, &block)
51
-
52
- # @_prompt_was_called = true
53
-
54
- # create_parts_from_responses(@_message, responses)
55
-
56
- # @_message
57
- # end
58
-
59
- private
60
-
61
- def apply_defaults(headers)
62
- headers.reverse_merge(self.class.default_params)
63
- end
64
-
65
- def assign_headers_to_message(message, headers)
66
- assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
67
- assignable.each { |k, v| message.send(:"#{k}=", v) }
68
- end
69
-
70
- def collect_responses(headers, &block)
71
- if block
72
- collect_responses_from_block(headers, &block)
73
- elsif headers[:body]
74
- collect_responses_from_text(headers)
75
- else
76
- collect_responses_from_templates(headers)
77
- end
78
- end
79
-
80
- def collect_responses_from_block(headers, &block)
81
- templates_name = headers[:template_name] || action_name
82
- collector = ActiveAgent::ActionPrompt::Collector.new(lookup_context) { render(templates_name) }
83
- yield(collector)
84
- collector.responses
85
- end
86
-
87
- def collect_responses_from_text(headers)
88
- [{
89
- body: headers.delete(:body),
90
- content_type: headers[:content_type] || "text/plain"
91
- }]
92
- end
93
-
94
- def collect_responses_from_templates(headers)
95
- templates_path = headers[:template_path] || self.class.name.sub(/Agent$/, "").underscore
96
- templates_name = headers[:template_name] || action_name
97
- each_template(Array(templates_path), templates_name).map do |template|
98
- format = template.format || formats.first
99
- {
100
- body: render(template: template, formats: [format]),
101
- content_type: Mime[format].to_s
102
- }
103
- end
104
- end
105
-
106
- def each_template(paths, name, &)
107
- templates = lookup_context.find_all(name, paths)
108
- if templates.empty?
109
- raise ActionView::MissingTemplate.new(paths, name, paths, false, "prompt")
110
- else
111
- templates.uniq(&:format).each(&)
112
- end
113
- end
114
-
115
- def create_parts_from_responses(message, responses)
116
- responses.each do |response|
117
- message.add_part(response[:body], content_type: response[:content_type])
118
- end
119
- end
120
-
121
37
  class TestAgent
122
38
  class << self
123
39
  attr_accessor :generations
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "active_agent/prompt_helper"
4
4
  require "active_agent/action_prompt/prompt"
5
- require "active_agent/action_prompt/collector"
5
+ require "active_agent/collector"
6
6
  require "active_support/core_ext/string/inflections"
7
7
  require "active_support/core_ext/hash/except"
8
8
  require "active_support/core_ext/module/anonymous"
@@ -53,7 +53,7 @@ module ActiveAgent
53
53
 
54
54
  include ActionView::Layouts
55
55
 
56
- PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [:@_action_has_layout]
56
+ PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [ :@_action_has_layout ]
57
57
 
58
58
  helper ActiveAgent::PromptHelper
59
59
 
@@ -63,7 +63,7 @@ module ActiveAgent
63
63
  mime_version: "1.0",
64
64
  charset: "UTF-8",
65
65
  content_type: "text/plain",
66
- parts_order: ["text/plain", "text/enriched", "text/html"]
66
+ parts_order: [ "text/plain", "text/enriched", "text/html" ]
67
67
  }.freeze
68
68
 
69
69
  class << self
@@ -212,6 +212,20 @@ module ActiveAgent
212
212
  handle_response(generation_provider.response)
213
213
  end
214
214
 
215
+ # Add embedding capability to Message class
216
+ ActiveAgent::ActionPrompt::Message.class_eval do
217
+ def embed
218
+ agent_class = ActiveAgent::Base.descendants.first
219
+ agent = agent_class.new
220
+ agent.prompt_context = ActiveAgent::ActionPrompt::Prompt.new(message: self)
221
+ agent.embed
222
+ self
223
+ end
224
+ end
225
+
226
+ # Make prompt_context accessible for chaining
227
+ # attr_accessor :prompt_context
228
+
215
229
  def perform_generation
216
230
  prompt_context.options.merge(options)
217
231
  generation_provider.generate(prompt_context) if prompt_context && generation_provider
@@ -219,15 +233,14 @@ module ActiveAgent
219
233
  end
220
234
 
221
235
  def handle_response(response)
222
- perform_actions(requested_actions: response.message.requested_actions) if response.message.requested_actions.present?
223
-
236
+ return response unless response.message.requested_actions.present?
237
+ perform_actions(requested_actions: response.message.requested_actions)
224
238
  update_prompt_context(response)
225
239
  end
226
240
 
227
241
  def update_prompt_context(response)
228
- # response.prompt = prompt_context
229
- # response.message = response.messages.last
230
- response
242
+ prompt_context.message = prompt_context.messages.last
243
+ ActiveAgent::GenerationProvider::Response.new(prompt: prompt_context)
231
244
  end
232
245
 
233
246
  def perform_actions(requested_actions:)
@@ -237,9 +250,12 @@ module ActiveAgent
237
250
  end
238
251
 
239
252
  def perform_action(action)
253
+ current_context = prompt_context.clone
240
254
  process(action.name, *action.params)
241
255
  prompt_context.messages.last.role = :tool
242
256
  prompt_context.messages.last.action_id = action.id
257
+ current_context.messages << prompt_context.messages.last
258
+ self.prompt_context = current_context
243
259
  end
244
260
 
245
261
  def initialize
@@ -299,15 +315,11 @@ module ActiveAgent
299
315
 
300
316
  def prompt(headers = {}, &block)
301
317
  return prompt_context if @_prompt_was_called && headers.blank? && !block
302
-
303
318
  content_type = headers[:content_type]
304
-
305
319
  headers = apply_defaults(headers)
306
-
320
+ prompt_context.messages = headers[:messages] || []
307
321
  prompt_context.context_id = headers[:context_id]
308
322
 
309
- prompt_context.options = options.merge(headers[:options] || {})
310
-
311
323
  prompt_context.charset = charset = headers[:charset]
312
324
 
313
325
  responses = collect_responses(headers, &block)
@@ -319,13 +331,14 @@ module ActiveAgent
319
331
  prompt_context.content_type = set_content_type(prompt_context, content_type, headers[:content_type])
320
332
  prompt_context.charset = charset
321
333
  prompt_context.actions = headers[:actions] || action_schemas
334
+
322
335
  prompt_context
323
336
  end
324
337
 
325
338
  def action_schemas
326
339
  action_methods.map do |action|
327
340
  if action != "text_prompt"
328
- JSON.parse render_to_string(locals: {action_name: action}, action: action, formats: :json)
341
+ JSON.parse render_to_string(locals: { action_name: action }, action: action, formats: :json)
329
342
  end
330
343
  end.compact
331
344
  end
@@ -346,7 +359,7 @@ module ActiveAgent
346
359
  # If the subject has interpolations, you can pass them through the +interpolations+ parameter.
347
360
  def default_i18n_subject(interpolations = {}) # :doc:
348
361
  agent_scope = self.class.agent_name.tr("/", ".")
349
- I18n.t(:subject, **interpolations.merge(scope: [agent_scope, action_name], default: action_name.humanize))
362
+ I18n.t(:subject, **interpolations.merge(scope: [ agent_scope, action_name ], default: action_name.humanize))
350
363
  end
351
364
 
352
365
  def apply_defaults(headers)
@@ -385,16 +398,16 @@ module ActiveAgent
385
398
 
386
399
  def collect_responses_from_block(headers)
387
400
  templates_name = headers[:template_name] || action_name
388
- collector = ActiveAgent::ActionPrompt::Collector.new(lookup_context) { render(templates_name) }
401
+ collector = Collector.new(lookup_context) { render(templates_name) }
389
402
  yield(collector)
390
403
  collector.responses
391
404
  end
392
405
 
393
406
  def collect_responses_from_text(headers)
394
- [{
407
+ [ {
395
408
  body: headers.delete(:body),
396
409
  content_type: headers[:content_type] || "text/plain"
397
- }]
410
+ } ]
398
411
  end
399
412
 
400
413
  def collect_responses_from_templates(headers)
@@ -406,7 +419,7 @@ module ActiveAgent
406
419
 
407
420
  format = template.format || formats.first
408
421
  {
409
- body: render(template: template, formats: [format]),
422
+ body: render(template: template, formats: [ format ]),
410
423
  content_type: Mime[format].to_s
411
424
  }
412
425
  end.compact
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "abstract_controller/collector"
4
+ require "active_support/core_ext/hash/reverse_merge"
5
+ require "active_support/core_ext/array/extract_options"
6
+
7
+ module ActiveAgent
8
+ class Collector
9
+ include AbstractController::Collector
10
+ attr_reader :responses
11
+
12
+ def initialize(context, &block)
13
+ @context = context
14
+ @responses = []
15
+ @default_render = block
16
+ end
17
+
18
+ def any(*args, &block)
19
+ options = args.extract_options!
20
+ raise ArgumentError, "You have to supply at least one format" if args.empty?
21
+ args.each { |type| send(type, options.dup, &block) }
22
+ end
23
+ alias_method :all, :any
24
+
25
+ def custom(mime, options = {})
26
+ options.reverse_merge!(content_type: mime.to_s)
27
+ @context.formats = [ mime.to_sym ]
28
+ options[:body] = block_given? ? yield : @default_render.call
29
+ @responses << options
30
+ end
31
+ end
32
+ end
@@ -1,72 +0,0 @@
1
- # Active Agent: Generation Provider
2
-
3
- This README provides information about the generation provider interfaces and implementations in the ActiveAgent library.
4
-
5
- ## Main Components
6
-
7
- - Base class - Abstract class for implementing generation providers
8
- - OpenAI Provider - Reference implementation using OpenAI's API
9
- - Response class - Standardized response wrapper
10
- - Module - For including generation provider functionality in agents
11
-
12
- ## Core Concepts
13
-
14
- ### Base Provider Class
15
-
16
- The `ActiveAgent::GenerationProvider::Base` class defines the core interface that all providers must implement:
17
-
18
- ```ruby
19
- def generate(prompt)
20
- raise NotImplementedError
21
- end
22
- ```
23
-
24
- ### OpenAI Provider Implementation
25
-
26
- The OpenAI provider shows how to implement a concrete generation provider:
27
-
28
- - Handles authentication and client setup
29
- - Implements prompt/completion generation
30
- - Supports streaming responses
31
- - Handles embeddings generation
32
- - Manages context updates
33
- - Processes tool/action calls
34
-
35
- ### Provider Features
36
-
37
- - Configuration - Providers accept config options for API keys, models, etc
38
- - Streaming - Optional streaming support for realtime responses
39
- - Action handling - Support for function/tool calling
40
- - Error handling - Standardized error handling via GenerationProviderError
41
- - Context management - Tracks conversation context and message history
42
-
43
- ### Response Handling
44
-
45
- The Response class wraps provider responses with a consistent interface:
46
-
47
- ```ruby
48
- Response.new(
49
- prompt: prompt, # Original prompt
50
- message: message, # Generated response
51
- raw_response: raw # Provider-specific response
52
- )
53
- ```
54
-
55
- ## Usage Example
56
-
57
- ```ruby
58
- # Configure provider
59
- provider = ActiveAgent::GenerationProvider::OpenAIProvider.new(
60
- "api_key" => ENV["OPENAI_API_KEY"],
61
- "model" => "gpt-4"
62
- )
63
-
64
- # Generate completion
65
- response = provider.generate(prompt)
66
-
67
- # Access response
68
- response.message.content # Generated text
69
- response.raw_response # Raw provider response
70
- ```
71
-
72
- See the OpenAI provider implementation for a complete reference example.
@@ -23,12 +23,6 @@ module ActiveAgent
23
23
  raise GenerationProviderError, e.message
24
24
  end
25
25
 
26
- def chat_prompt(parameters: prompt_parameters)
27
- parameters[:stream] = provider_stream if prompt.options[:stream] || config["stream"]
28
-
29
- chat_response(@client.chat(parameters: parameters))
30
- end
31
-
32
26
  def embed(prompt)
33
27
  @prompt = prompt
34
28
 
@@ -37,39 +31,25 @@ module ActiveAgent
37
31
  raise GenerationProviderError, e.message
38
32
  end
39
33
 
40
- def embeddings_parameters(input: prompt.message.content, model: "text-embedding-3-large")
41
- {
42
- model: model,
43
- input: input
44
- }
45
- end
46
-
47
- def embeddings_response(response)
48
- message = Message.new(content: response.dig("data", 0, "embedding"), role: "assistant")
49
-
50
- @response = ActiveAgent::GenerationProvider::Response.new(prompt: prompt, message: message, raw_response: response)
51
- end
52
-
53
- def embeddings_prompt(parameters:)
54
- embeddings_response(@client.embeddings(parameters: embeddings_parameters))
55
- end
56
-
57
34
  private
58
35
 
59
36
  def provider_stream
60
- # prompt.options[:stream] will define a proc found in prompt at runtime
61
- # config[:stream] will define a proc found in config. stream would come from an Agent class's generate_with or stream_with method calls
62
37
  agent_stream = prompt.options[:stream]
63
38
  message = ActiveAgent::ActionPrompt::Message.new(content: "", role: :assistant)
64
- @response = ActiveAgent::GenerationProvider::Response.new(prompt: prompt, message:)
65
39
 
40
+ @response = ActiveAgent::GenerationProvider::Response.new(prompt:, message:)
66
41
  proc do |chunk, bytesize|
67
- if (new_content = chunk.dig("choices", 0, "delta", "content"))
42
+ new_content = chunk.dig("choices", 0, "delta", "content")
43
+ if new_content && !new_content.blank
68
44
  message.content += new_content
69
45
 
70
46
  agent_stream.call(message, new_content, false) do |message, new_content|
71
47
  yield message, new_content if block_given?
72
48
  end
49
+ elsif chunk.dig("choices", 0, "delta", "tool_calls") && !chunk.dig("choices", 0, "delta", "tool_calls").empty?
50
+ message = handle_message(chunk.dig("choices", 0, "delta"))
51
+ prompt.messages << message
52
+ @response = ActiveAgent::GenerationProvider::Response.new(prompt:, message:)
73
53
  end
74
54
 
75
55
  agent_stream.call(message, nil, true) do |message|
@@ -98,42 +78,68 @@ module ActiveAgent
98
78
  }.compact
99
79
 
100
80
  if message.content_type == "image_url"
101
- provider_message[:image_url] = {url: message.content}
81
+ provider_message[:image_url] = { url: message.content }
102
82
  end
103
83
  provider_message
104
84
  end
105
85
  end
106
86
 
107
87
  def chat_response(response)
108
- binding.irb
109
88
  return @response if prompt.options[:stream]
110
89
 
111
90
  message_json = response.dig("choices", 0, "message")
112
91
 
113
- message = ActiveAgent::ActionPrompt::Message.new(
92
+ message = handle_message(message_json)
93
+
94
+ update_context(prompt: prompt, message: message, response: response)
95
+
96
+ @response = ActiveAgent::GenerationProvider::Response.new(prompt: prompt, message: message, raw_response: response)
97
+ end
98
+
99
+ def handle_message(message_json)
100
+ ActiveAgent::ActionPrompt::Message.new(
114
101
  content: message_json["content"],
115
- role: message_json["role"],
102
+ role: message_json["role"].intern,
116
103
  action_requested: message_json["finish_reason"] == "tool_calls",
117
104
  requested_actions: handle_actions(message_json["tool_calls"])
118
105
  )
119
- update_context(prompt: prompt, message: message, response: response)
106
+ end
107
+
108
+ def handle_actions(tool_calls)
109
+ return [] if tool_calls.nil? || tool_calls.empty?
110
+
111
+ tool_calls.map do |tool_call|
112
+ next if tool_call["function"].nil? || tool_call["function"]["name"].blank?
113
+ args = tool_call["function"]["arguments"].blank? ? nil : JSON.parse(tool_call["function"]["arguments"], { symbolize_names: true })
114
+
115
+ ActiveAgent::ActionPrompt::Action.new(
116
+ id: tool_call["id"],
117
+ name: tool_call.dig("function", "name"),
118
+ params: args
119
+ )
120
+ end.compact
121
+ end
122
+
123
+ def chat_prompt(parameters: prompt_parameters)
124
+ parameters[:stream] = provider_stream if prompt.options[:stream] || config["stream"]
125
+ chat_response(@client.chat(parameters: parameters))
126
+ end
127
+
128
+ def embeddings_parameters(input: prompt.message.content, model: "text-embedding-3-large")
129
+ {
130
+ model: model,
131
+ input: input
132
+ }
133
+ end
134
+
135
+ def embeddings_response(response)
136
+ message = ActiveAgent::ActionPrompt::Message.new(content: response.dig("data", 0, "embedding"), role: "assistant")
120
137
 
121
138
  @response = ActiveAgent::GenerationProvider::Response.new(prompt: prompt, message: message, raw_response: response)
122
139
  end
123
140
 
124
- def handle_actions(tool_calls)
125
- if tool_calls
126
- tool_calls.map do |tool_call|
127
- ActiveAgent::ActionPrompt::Action.new(
128
- id: tool_call["id"],
129
- name: tool_call.dig("function", "name"),
130
- params: JSON.parse(
131
- tool_call.dig("function", "arguments"),
132
- {symbolize_names: true}
133
- )
134
- )
135
- end
136
- end
141
+ def embeddings_prompt(parameters:)
142
+ embeddings_response(@client.embeddings(parameters: embeddings_parameters))
137
143
  end
138
144
  end
139
145
  end
@@ -5,9 +5,9 @@ module ActiveAgent
5
5
  class Response
6
6
  attr_reader :message, :prompt, :raw_response
7
7
 
8
- def initialize(prompt:, message:, raw_response: nil)
9
- @message = message
8
+ def initialize(prompt:, message: nil, raw_response: nil)
10
9
  @prompt = prompt
10
+ @message = message || prompt.message
11
11
  @raw_response = raw_response
12
12
  end
13
13
  end
@@ -24,7 +24,7 @@ module ActiveAgent
24
24
  require "active_agent/generation_provider/#{config["service"].underscore}_provider"
25
25
  ActiveAgent::GenerationProvider.const_get("#{config["service"].camelize}Provider").new(config)
26
26
  rescue LoadError
27
- raise "Missing generation provider for #{config["service"].inspect}"
27
+ raise "Missing generation provider configuration for #{config["service"].inspect}"
28
28
  end
29
29
 
30
30
  def generation_provider
@@ -11,7 +11,7 @@ module ActiveAgent
11
11
 
12
12
  mattr_accessor :show_previews, instance_writer: false
13
13
 
14
- mattr_accessor :preview_interceptors, instance_writer: false, default: [ActiveAgent::InlinePreviewInterceptor]
14
+ mattr_accessor :preview_interceptors, instance_writer: false, default: [ ActiveAgent::InlinePreviewInterceptor ]
15
15
  end
16
16
 
17
17
  module ClassMethods
@@ -29,7 +29,7 @@ module ActiveAgent
29
29
  options.stylesheets_dir ||= paths["public/stylesheets"].first
30
30
  options.show_previews = Rails.env.development? if options.show_previews.nil?
31
31
  options.cache_store ||= Rails.cache
32
- options.preview_paths |= ["#{Rails.root}/test/agents/previews"]
32
+ options.preview_paths |= [ "#{Rails.root}/test/agents/previews" ]
33
33
 
34
34
  # make sure readers methods get compiled
35
35
  options.asset_host ||= app.config.asset_host
@@ -45,7 +45,7 @@ module ActiveAgent
45
45
  register_interceptors(options.delete(:interceptors))
46
46
  register_preview_interceptors(options.delete(:preview_interceptors))
47
47
  register_observers(options.delete(:observers))
48
- self.view_paths = ["#{Rails.root}/app/views"]
48
+ self.view_paths = [ "#{Rails.root}/app/views" ]
49
49
  self.preview_paths |= options[:preview_paths]
50
50
 
51
51
  if (generation_job = options.delete(:generation_job))
@@ -1,3 +1,3 @@
1
1
  module ActiveAgent
2
- VERSION = "0.3"
2
+ VERSION = "0.3.2"
3
3
  end
data/lib/active_agent.rb CHANGED
@@ -12,7 +12,6 @@ require "active_support/core_ext/class"
12
12
  require "active_support/core_ext/module/attr_internal"
13
13
  require "active_support/core_ext/string/inflections"
14
14
  require "active_support/lazy_load_hooks"
15
-
16
15
  module ActiveAgent
17
16
  extend ActiveSupport::Autoload
18
17
 
@@ -19,57 +19,8 @@ module ActiveAgent
19
19
  end
20
20
  end
21
21
 
22
- def create_test_file
23
- if test_framework == :rspec
24
- template "agent_spec.rb", File.join("spec/agents", class_path, "#{file_name}_agent_spec.rb")
25
- else
26
- template "agent_test.rb", File.join("test/agents", class_path, "#{file_name}_agent_test.rb")
27
- end
28
- end
29
-
30
- def create_view_files
31
- actions.each do |action|
32
- @action = action
33
-
34
- # Use configured template engines or fall back to defaults
35
- json_template_engine = json_template_engine_for_views || "jbuilder"
36
- html_template_engine = html_template_engine_for_views || "erb"
37
-
38
- @schema_path = File.join("app/views", class_path, file_name, "#{action}.json.#{json_template_engine}")
39
- @view_path = File.join("app/views", class_path, file_name, "#{action}.html.#{html_template_engine}")
40
-
41
- template "action.json.#{json_template_engine}", @schema_path
42
- template "action.html.#{html_template_engine}", @view_path
43
- end
44
- end
45
-
46
- def create_layout_files
47
- # Create the application agent layouts
48
- template "layout.text.erb", "app/views/layouts/agent.text.erb"
49
- template "layout.html.erb", "app/views/layouts/agent.html.erb"
50
- end
51
-
52
22
  private
53
23
 
54
- def test_framework
55
- ::Rails.application.config.generators.options[:rails][:test_framework]
56
- end
57
-
58
- def template_engine
59
- ::Rails.application.config.generators.options[:rails][:template_engine]
60
- end
61
-
62
- def json_template_engine_for_views
63
- # Check if there's a specific JSON template engine configured
64
- json_engine = ::Rails.application.config.generators.options[:rails][:json_template_engine]
65
- json_engine || "jbuilder" # Default to jbuilder if not specified
66
- end
67
-
68
- def html_template_engine_for_views
69
- # Use the configured template engine or default to erb
70
- template_engine || "erb"
71
- end
72
-
73
24
  def file_name # :doc:
74
25
  @_file_name ||= super + "_agent"
75
26
  end
@@ -13,7 +13,8 @@ module ActiveAgent
13
13
  template "application_agent.rb", "app/agents/application_agent.rb"
14
14
  end
15
15
 
16
- def create_agent_layout_template
16
+ def create_agent_layouts
17
+ template "agent.html.erb", "app/views/layouts/agent.html.erb"
17
18
  template "agent.text.erb", "app/views/layouts/agent.text.erb"
18
19
  end
19
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeagent
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.3'
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Bowen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-30 00:00:00.000000000 Z
11
+ date: 2025-04-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -138,19 +138,18 @@ executables: []
138
138
  extensions: []
139
139
  extra_rdoc_files: []
140
140
  files:
141
+ - CHANGELOG.md
141
142
  - lib/active_agent.rb
142
143
  - lib/active_agent/README.md
143
144
  - lib/active_agent/action_prompt.rb
144
145
  - lib/active_agent/action_prompt/README.md
145
146
  - lib/active_agent/action_prompt/action.rb
146
- - lib/active_agent/action_prompt/base.rb
147
- - lib/active_agent/action_prompt/collector.rb
148
147
  - lib/active_agent/action_prompt/message.rb
149
148
  - lib/active_agent/action_prompt/prompt.rb
150
149
  - lib/active_agent/base.rb
151
150
  - lib/active_agent/callbacks.rb
151
+ - lib/active_agent/collector.rb
152
152
  - lib/active_agent/deprecator.rb
153
- - lib/active_agent/engine.rb
154
153
  - lib/active_agent/generation.rb
155
154
  - lib/active_agent/generation_job.rb
156
155
  - lib/active_agent/generation_methods.rb
@@ -162,7 +161,6 @@ files:
162
161
  - lib/active_agent/generation_provider/response.rb
163
162
  - lib/active_agent/inline_preview_interceptor.rb
164
163
  - lib/active_agent/log_subscriber.rb
165
- - lib/active_agent/operation.rb
166
164
  - lib/active_agent/parameterized.rb
167
165
  - lib/active_agent/preview.rb
168
166
  - lib/active_agent/prompt_helper.rb
@@ -179,13 +177,12 @@ files:
179
177
  - lib/generators/active_agent/templates/action.html.erb.tt
180
178
  - lib/generators/active_agent/templates/action.json.jbuilder.tt
181
179
  - lib/generators/active_agent/templates/active_agent.yml
180
+ - lib/generators/active_agent/templates/agent.html.erb
182
181
  - lib/generators/active_agent/templates/agent.rb.tt
183
182
  - lib/generators/active_agent/templates/agent.text.erb
184
183
  - lib/generators/active_agent/templates/agent_spec.rb.tt
185
184
  - lib/generators/active_agent/templates/agent_test.rb.tt
186
185
  - lib/generators/active_agent/templates/application_agent.rb.tt
187
- - lib/generators/active_agent/templates/layout.html.erb
188
- - lib/generators/active_agent/templates/layout.text.erb
189
186
  - lib/tasks/activeagent_tasks.rake
190
187
  homepage: https://activeagents.ai
191
188
  licenses:
@@ -1,127 +0,0 @@
1
- # require "abstract_controller"
2
- # require "active_support/core_ext/string/inflections"
3
-
4
- # module ActiveAgent
5
- # module ActionPrompt
6
- # extend ::ActiveSupport::Autoload
7
-
8
- # eager_autoload do
9
- # autoload :Collector
10
- # autoload :Message
11
- # autoload :Prompt
12
- # autoload :PromptHelper
13
- # end
14
-
15
- # autoload :Base
16
-
17
- # extend ActiveSupport::Concern
18
-
19
- # included do
20
- # include AbstractController::Rendering
21
- # include AbstractController::Layouts
22
- # include AbstractController::Helpers
23
- # include AbstractController::Translation
24
- # include AbstractController::AssetPaths
25
- # include AbstractController::Callbacks
26
- # include AbstractController::Caching
27
-
28
- # include ActionView::Layouts
29
-
30
- # helper ActiveAgent::PromptHelper
31
- # # class_attribute :default_params, default: {
32
- # # content_type: "text/plain",
33
- # # parts_order: ["text/plain", "text/html", "application/json"]
34
- # # }.freeze
35
- # end
36
-
37
- # # # def self.prompt(headers = {}, &)
38
- # # # new.prompt(headers, &)
39
- # # # end
40
-
41
- # # def prompt(headers = {}, &block)
42
- # # return @_message if @_prompt_was_called && headers.blank? && !block
43
-
44
- # # headers = apply_defaults(headers)
45
-
46
- # # @_message = ActiveAgent::ActionPrompt::Prompt.new
47
-
48
- # # assign_headers_to_message(@_message, headers)
49
-
50
- # # responses = collect_responses(headers, &block)
51
-
52
- # # @_prompt_was_called = true
53
-
54
- # # create_parts_from_responses(@_message, responses)
55
-
56
- # # @_message
57
- # # end
58
-
59
- # private
60
-
61
- # def apply_defaults(headers)
62
- # headers.reverse_merge(self.class.default_params)
63
- # end
64
-
65
- # def assign_headers_to_message(message, headers)
66
- # assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path)
67
- # assignable.each { |k, v| message.send(:"#{k}=", v) }
68
- # end
69
-
70
- # def collect_responses(headers, &block)
71
- # if block
72
- # collect_responses_from_block(headers, &block)
73
- # elsif headers[:body]
74
- # collect_responses_from_text(headers)
75
- # else
76
- # collect_responses_from_templates(headers)
77
- # end
78
- # end
79
-
80
- # def collect_responses_from_block(headers, &block)
81
- # templates_name = headers[:template_name] || action_name
82
- # collector = ActiveAgent::ActionPrompt::Collector.new(lookup_context) { render(templates_name) }
83
- # yield(collector)
84
- # collector.responses
85
- # end
86
-
87
- # def collect_responses_from_text(headers)
88
- # [{
89
- # body: headers.delete(:body),
90
- # content_type: headers[:content_type] || "text/plain"
91
- # }]
92
- # end
93
-
94
- # def collect_responses_from_templates(headers)
95
- # templates_path = headers[:template_path] || self.class.name.sub(/Agent$/, "").underscore
96
- # templates_name = headers[:template_name] || action_name
97
- # each_template(Array(templates_path), templates_name).map do |template|
98
- # format = template.format || formats.first
99
- # {
100
- # body: render(template: template, formats: [format]),
101
- # content_type: Mime[format].to_s
102
- # }
103
- # end
104
- # end
105
-
106
- # def each_template(paths, name, &)
107
- # templates = lookup_context.find_all(name, paths)
108
- # if templates.empty?
109
- # raise ActionView::MissingTemplate.new(paths, name, paths, false, "prompt")
110
- # else
111
- # templates.uniq(&:format).each(&)
112
- # end
113
- # end
114
-
115
- # def create_parts_from_responses(message, responses)
116
- # responses.each do |response|
117
- # message.add_part(response[:body], content_type: response[:content_type])
118
- # end
119
- # end
120
-
121
- # class TestAgent
122
- # class << self
123
- # attr_accessor :generations
124
- # end
125
- # end
126
- # end
127
- # end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "abstract_controller/collector"
4
- require "active_support/core_ext/hash/reverse_merge"
5
- require "active_support/core_ext/array/extract_options"
6
-
7
- module ActiveAgent
8
- module ActionPrompt
9
- class Collector
10
- include AbstractController::Collector
11
- attr_reader :responses
12
-
13
- def initialize(context, &block)
14
- @context = context
15
- @responses = []
16
- @default_render = block
17
- end
18
-
19
- def any(*args, &block)
20
- options = args.extract_options!
21
- raise ArgumentError, "You have to supply at least one format" if args.empty?
22
- args.each { |type| send(type, options.dup, &block) }
23
- end
24
- alias_method :all, :any
25
-
26
- def custom(mime, options = {})
27
- options.reverse_merge!(content_type: mime.to_s)
28
- @context.formats = [mime.to_sym]
29
- options[:body] = block_given? ? yield : @default_render.call
30
- @responses << options
31
- end
32
- end
33
- end
34
- end
@@ -1,14 +0,0 @@
1
- # require "rails/engine"
2
-
3
- # module ActiveAgent
4
- # class Engine < ::Rails::Engine
5
- # isolate_namespace ActiveAgent
6
-
7
- # initializer "active_agent.view_paths" do |app|
8
- # # Adding your gem's view path to Rails lookup paths
9
- # ActiveSupport.on_load(:action_controller) do
10
- # append_view_path Engine.root.join("app/views")
11
- # end
12
- # end
13
- # end
14
- # end
@@ -1,13 +0,0 @@
1
- module ActiveAgent
2
- class Operation < AbstractController::Base
3
- include AbstractController::Rendering
4
- include ActionView::Rendering # Allows rendering of ERB templates without a view context tied to a request
5
- append_view_path "app/views" # Ensure the controller knows where to look for view templates
6
-
7
- def process_tool(tool_name, params)
8
- send(tool_name, params) # Dynamically calls the method corresponding to tool_name
9
- rescue NoMethodError
10
- "Tool not found: #{tool_name}"
11
- end
12
- end
13
- end
@@ -1 +0,0 @@
1
- <%= yield %>