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 +4 -4
- data/CHANGELOG.md +17 -0
- data/lib/active_agent/action_prompt/README.md +0 -111
- data/lib/active_agent/action_prompt/message.rb +4 -0
- data/lib/active_agent/action_prompt/prompt.rb +9 -2
- data/lib/active_agent/action_prompt.rb +0 -84
- data/lib/active_agent/base.rb +32 -19
- data/lib/active_agent/collector.rb +32 -0
- data/lib/active_agent/generation_provider/README.md +0 -72
- data/lib/active_agent/generation_provider/open_ai_provider.rb +51 -45
- data/lib/active_agent/generation_provider/response.rb +2 -2
- data/lib/active_agent/generation_provider.rb +1 -1
- data/lib/active_agent/preview.rb +1 -1
- data/lib/active_agent/railtie.rb +2 -2
- data/lib/active_agent/version.rb +1 -1
- data/lib/active_agent.rb +0 -1
- data/lib/generators/active_agent/agent_generator.rb +0 -49
- data/lib/generators/active_agent/install_generator.rb +2 -1
- metadata +5 -8
- data/lib/active_agent/action_prompt/base.rb +0 -127
- data/lib/active_agent/action_prompt/collector.rb +0 -34
- data/lib/active_agent/engine.rb +0 -14
- data/lib/active_agent/operation.rb +0 -13
- data/lib/generators/active_agent/templates/layout.text.erb +0 -1
- /data/lib/generators/active_agent/templates/{layout.html.erb → agent.html.erb} +0 -0
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b5296fe177a73f49b3514b6d82ad8dc45efdf5455b50aed3fb411103551fb308
         | 
| 4 | 
            +
              data.tar.gz: 889c307af88851d5bb8022f4701af49a96a9701f862cecf1b01bfd625f2568d2
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 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.
         | 
| @@ -3,7 +3,8 @@ require_relative "message" | |
| 3 3 | 
             
            module ActiveAgent
         | 
| 4 4 | 
             
              module ActionPrompt
         | 
| 5 5 | 
             
                class Prompt
         | 
| 6 | 
            -
                   | 
| 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
         | 
    
        data/lib/active_agent/base.rb
    CHANGED
    
    | @@ -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/ | 
| 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 | 
            -
                   | 
| 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 | 
            -
                   | 
| 229 | 
            -
                   | 
| 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 =  | 
| 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 | 
            -
                       | 
| 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 =  | 
| 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 | 
            -
             | 
| 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  | 
| 125 | 
            -
                     | 
| 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 | 
| 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
         | 
    
        data/lib/active_agent/preview.rb
    CHANGED
    
    | @@ -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
         | 
    
        data/lib/active_agent/railtie.rb
    CHANGED
    
    | @@ -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))
         | 
    
        data/lib/active_agent/version.rb
    CHANGED
    
    
    
        data/lib/active_agent.rb
    CHANGED
    
    
| @@ -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  | 
| 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:  | 
| 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- | 
| 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
         | 
    
        data/lib/active_agent/engine.rb
    DELETED
    
    | @@ -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 %>
         | 
| 
            File without changes
         |