active_agent_rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +151 -0
- data/Rakefile +12 -0
- data/app/controllers/active_agent/chats_controller.rb +84 -0
- data/app/helpers/active_agent/chat_helper.rb +427 -0
- data/config/routes.rb +6 -0
- data/lib/active_agent/base.rb +121 -0
- data/lib/active_agent/configuration.rb +30 -0
- data/lib/active_agent/engine.rb +16 -0
- data/lib/active_agent/memory/active_record.rb +68 -0
- data/lib/active_agent/memory/base.rb +42 -0
- data/lib/active_agent/memory/in_memory.rb +29 -0
- data/lib/active_agent/provider.rb +90 -0
- data/lib/active_agent/providers/anthropic.rb +208 -0
- data/lib/active_agent/providers/gemini.rb +169 -0
- data/lib/active_agent/providers/openai.rb +178 -0
- data/lib/active_agent/tool.rb +37 -0
- data/lib/active_agent/version.rb +5 -0
- data/lib/active_agent.rb +25 -0
- data/lib/active_agent_rails.rb +3 -0
- data/lib/generators/active_agent/agent/agent_generator.rb +15 -0
- data/lib/generators/active_agent/agent/templates/agent.rb.erb +22 -0
- data/lib/generators/active_agent/install/install_generator.rb +31 -0
- data/lib/generators/active_agent/install/templates/active_agent.rb +15 -0
- data/lib/generators/active_agent/install/templates/create_active_agent_messages.rb +18 -0
- metadata +144 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 02e5f8fc660eb1f31241e29aed06e49203969ad06a131e7e2023d3d767a93799
|
|
4
|
+
data.tar.gz: 73e500f5db889fbdc6b6eb9183bf9f079fb5ec6c5f42177fd5952db4e65fde79
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4c990e108ceb57fb81634f63b1e71e56ce3b2db40915aacf5dc953e22a867ca0b459b271615a09d12421a9470d184c39e04feab9aa4447129c244c3260ecef51
|
|
7
|
+
data.tar.gz: 5bac33da0987e589a30bbf2caff249bcc41325e6e0f06a3248c2d2e3d22d9106352f5b8487d880ca64316f36941a33771c693943e24ef3a7d93f9461c8fc012d
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shiboshree Roy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# ActiveAgent
|
|
2
|
+
|
|
3
|
+
`ActiveAgent` is a Rails-like framework and engine designed to integrate LLMs, AI agents, and custom tools into Ruby on Rails applications with ease.
|
|
4
|
+
|
|
5
|
+
It supports multiple provider adapters (Gemini, OpenAI, Anthropic), handles multi-turn tool-execution loops automatically, offers plug-and-play conversation memory (in-memory or ActiveRecord), and includes a gorgeous streaming chat widget ready for your Rails views.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add this line to your application's Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'active_agent_rails', path: 'path/to/active_agent_rails'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
And then execute:
|
|
18
|
+
```bash
|
|
19
|
+
$ bundle install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Setup Configuration & Memory
|
|
23
|
+
|
|
24
|
+
Run the installation generator to create the initializer and the migration for database-backed conversation history:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
$ rails generate active_agent:install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Run the migration to create the `active_agent_messages` table:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
$ rails db:migrate
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
You can configure providers and API keys in `config/initializers/active_agent.rb`:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
ActiveAgent.configure do |config|
|
|
44
|
+
# Default provider: :gemini, :openai, or :anthropic
|
|
45
|
+
config.default_provider = :gemini
|
|
46
|
+
|
|
47
|
+
# API Keys
|
|
48
|
+
config.gemini_api_key = ENV["GEMINI_API_KEY"]
|
|
49
|
+
config.openai_api_key = ENV["OPENAI_API_KEY"]
|
|
50
|
+
config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
|
|
51
|
+
|
|
52
|
+
# Memory store: :in_memory or :active_record
|
|
53
|
+
config.memory_store = :active_record
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Defining an Agent
|
|
60
|
+
|
|
61
|
+
Generate a new agent using the generator:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
$ rails generate active_agent:agent customer_service
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This scaffolds `app/agents/customer_service_agent.rb`. You can configure the system instructions, providers, and declare custom tools:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# app/agents/customer_service_agent.rb
|
|
71
|
+
class CustomerServiceAgent < ActiveAgent::Base
|
|
72
|
+
provider :gemini # Or :openai, :anthropic
|
|
73
|
+
model "gemini-2.5-flash"
|
|
74
|
+
system_prompt "You are a customer service assistant. You help users check their order details."
|
|
75
|
+
|
|
76
|
+
# Expose a Ruby method as a tool for the agent
|
|
77
|
+
tool :get_order_status, description: "Check shipping status of an order" do
|
|
78
|
+
parameter :order_id, type: :string, description: "Order ID (e.g., ORD-123)", required: true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Define the tool implementation
|
|
82
|
+
def get_order_status(order_id:)
|
|
83
|
+
order = Order.find_by(order_number: order_id)
|
|
84
|
+
return "Order #{order_id} not found." unless order
|
|
85
|
+
|
|
86
|
+
"Order #{order_id} is currently #{order.status}. Expected delivery: #{order.delivery_date}."
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Usage
|
|
94
|
+
|
|
95
|
+
### 1. In Ruby Code (Rails Controller, Job, or Console)
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
# Initialize agent for a specific user/session ID
|
|
99
|
+
agent = CustomerServiceAgent.new(conversation_id: "user_session_99")
|
|
100
|
+
|
|
101
|
+
# Run agent chat
|
|
102
|
+
response = agent.chat("Hi! Can you check the status of my order ORD-5541?")
|
|
103
|
+
puts response
|
|
104
|
+
# => "Order ORD-5541 is currently Shipped. Expected delivery: 2026-07-02."
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 2. View Integration: Streaming Chat Widget
|
|
108
|
+
|
|
109
|
+
Mount the `ActiveAgent` engine routes in your application's `config/routes.rb`:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
# config/routes.rb
|
|
113
|
+
Rails.application.routes.draw do
|
|
114
|
+
# ... other routes
|
|
115
|
+
mount ActiveAgent::Engine => "/active_agent"
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Now, you can drop the chat assistant widget into any of your Rails views:
|
|
120
|
+
|
|
121
|
+
```erb
|
|
122
|
+
<!-- app/views/home/index.html.erb -->
|
|
123
|
+
<%= active_agent_chat_widget(agent: :customer_service, conversation_id: current_user.id) %>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This will inject a floating AI chat assistant at the bottom right. When the user types a message:
|
|
127
|
+
- The UI communicates with `/active_agent/chats`.
|
|
128
|
+
- The controller leverages **Server-Sent Events (SSE)** to stream the response back in real-time.
|
|
129
|
+
- The assistant displays typing animations and updates the response character-by-character.
|
|
130
|
+
- Message history is preserved automatically in the database using the ActiveRecord memory store.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Customizing Tools
|
|
135
|
+
|
|
136
|
+
`ActiveAgent` tools support several parameter types (`:string`, `:integer`, `:number`, `:boolean`, `:array`, `:object`):
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
tool :update_profile, description: "Update user profile data" do
|
|
140
|
+
parameter :user_id, type: :integer, description: "Target user ID"
|
|
141
|
+
parameter :updates, type: :object, description: "Hash of update key-values"
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The engine maps these types to the appropriate API schema formatting expected by the LLM providers (Google Gemini, OpenAI, Anthropic).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAgent
|
|
4
|
+
class ChatsController < ::ActionController::Base
|
|
5
|
+
include ActionController::Live
|
|
6
|
+
|
|
7
|
+
# Disable CSRF verification for this API endpoint since it can be consumed as an API,
|
|
8
|
+
# or handle standard rails session if CSRF token is provided.
|
|
9
|
+
protect_from_forgery with: :null_session
|
|
10
|
+
|
|
11
|
+
def create
|
|
12
|
+
agent_name = params[:agent]
|
|
13
|
+
conversation_id = params[:conversation_id]
|
|
14
|
+
message = params[:message]
|
|
15
|
+
|
|
16
|
+
if agent_name.blank? || conversation_id.blank? || message.blank?
|
|
17
|
+
return render json: { error: "Missing required parameters: agent, conversation_id, message" }, status: :bad_request
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
agent_class = ActiveAgent.find_agent(agent_name)
|
|
21
|
+
agent = agent_class.new(conversation_id: conversation_id)
|
|
22
|
+
|
|
23
|
+
if request.headers["Accept"]&.include?("text/event-stream")
|
|
24
|
+
response.headers["Content-Type"] = "text/event-stream"
|
|
25
|
+
response.headers["Last-Modified"] = Time.now.httpdate
|
|
26
|
+
response.headers["X-Accel-Buffering"] = "no"
|
|
27
|
+
response.headers["Cache-Control"] = "no-cache, no-store"
|
|
28
|
+
response.headers["Connection"] = "keep-alive"
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
agent.chat(message) do |chunk|
|
|
32
|
+
response.stream.write("data: #{ { chunk: chunk }.to_json }\n\n")
|
|
33
|
+
end
|
|
34
|
+
rescue StandardError => e
|
|
35
|
+
ActiveAgent.logger.error("[ActiveAgent] Streaming controller error: #{e.message}\n#{e.backtrace.join("\n")}")
|
|
36
|
+
response.stream.write("data: #{ { error: e.message }.to_json }\n\n")
|
|
37
|
+
ensure
|
|
38
|
+
response.stream.close
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
begin
|
|
42
|
+
reply = agent.chat(message)
|
|
43
|
+
render json: { reply: reply }
|
|
44
|
+
rescue StandardError => e
|
|
45
|
+
ActiveAgent.logger.error("[ActiveAgent] Controller error: #{e.message}")
|
|
46
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Optional endpoint to fetch history
|
|
52
|
+
def index
|
|
53
|
+
agent_name = params[:agent]
|
|
54
|
+
conversation_id = params[:conversation_id]
|
|
55
|
+
|
|
56
|
+
if agent_name.blank? || conversation_id.blank?
|
|
57
|
+
return render json: { error: "Missing agent or conversation_id" }, status: :bad_request
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
agent_class = ActiveAgent.find_agent(agent_name)
|
|
61
|
+
agent = agent_class.new(conversation_id: conversation_id)
|
|
62
|
+
|
|
63
|
+
# Filter out system and tool messages for the client UI if desired
|
|
64
|
+
user_facing_messages = agent.memory.messages.reject { |m| m[:role].to_s == "system" }
|
|
65
|
+
render json: { messages: user_facing_messages }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Optional endpoint to clear history
|
|
69
|
+
def destroy
|
|
70
|
+
agent_name = params[:agent]
|
|
71
|
+
conversation_id = params[:conversation_id]
|
|
72
|
+
|
|
73
|
+
if agent_name.blank? || conversation_id.blank?
|
|
74
|
+
return render json: { error: "Missing agent or conversation_id" }, status: :bad_request
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
agent_class = ActiveAgent.find_agent(agent_name)
|
|
78
|
+
agent = agent_class.new(conversation_id: conversation_id)
|
|
79
|
+
agent.memory.clear
|
|
80
|
+
|
|
81
|
+
render json: { success: true, message: "Conversation history cleared." }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|