rails_orchestrator 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.rubocop.yml +35 -0
- data/README.md +55 -14
- data/Rakefile +5 -1
- data/lib/rails_orchestrator/client.rb +63 -0
- data/lib/rails_orchestrator/configuration.rb +13 -0
- data/lib/rails_orchestrator/conversation.rb +38 -0
- data/lib/rails_orchestrator/errors.rb +16 -0
- data/lib/rails_orchestrator/message.rb +20 -0
- data/lib/rails_orchestrator/response.rb +29 -0
- data/lib/rails_orchestrator/version.rb +1 -1
- data/lib/rails_orchestrator.rb +19 -1
- data/rails_orchestrator.gemspec +5 -6
- metadata +38 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d2f38594952e49f0d659722dcb48401a6d5f2427d48781ac2048433bd84557b
|
|
4
|
+
data.tar.gz: 4841db7dd3ff00b4befc942d7706ea13474a94a6efb1405070ef20472c10335f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 53eaa449566c589561b0a091be660075ec3a91c97a5f8559878c11e9477fdb22f4f748b9ca56917fa471c030a7277f913614d5e3898baf8a994eb52f25581b45
|
|
7
|
+
data.tar.gz: 14d45a700a36801ce88878ee3ba9624637943245177fbdf4c2aee7b8f6ca44ec98d13b593c7e12c884a1f4380e100df55280303cf05e5bf6ac474bf9aa1bbd4b
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.0
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
6
|
+
Style/StringLiterals:
|
|
7
|
+
EnforcedStyle: double_quotes
|
|
8
|
+
|
|
9
|
+
Style/StringLiteralsInInterpolation:
|
|
10
|
+
EnforcedStyle: double_quotes
|
|
11
|
+
|
|
12
|
+
Layout/LineLength:
|
|
13
|
+
Max: 120
|
|
14
|
+
|
|
15
|
+
Metrics/BlockLength:
|
|
16
|
+
Exclude:
|
|
17
|
+
- "spec/**/*"
|
|
18
|
+
- "*.gemspec"
|
|
19
|
+
|
|
20
|
+
Metrics/AbcSize:
|
|
21
|
+
Exclude:
|
|
22
|
+
- "spec/**/*"
|
|
23
|
+
|
|
24
|
+
Metrics/MethodLength:
|
|
25
|
+
Exclude:
|
|
26
|
+
- "spec/**/*"
|
|
27
|
+
|
|
28
|
+
Style/Documentation:
|
|
29
|
+
Enabled: false
|
|
30
|
+
|
|
31
|
+
Gemspec/DevelopmentDependencies:
|
|
32
|
+
Enabled: false
|
|
33
|
+
|
|
34
|
+
Gemspec/RequireMFA:
|
|
35
|
+
Enabled: false
|
data/README.md
CHANGED
|
@@ -1,31 +1,72 @@
|
|
|
1
1
|
# RailsOrchestrator
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/rails_orchestrator`. To experiment with that code, run `bin/console` for an interactive prompt.
|
|
3
|
+
A framework to build AI agents in Rails applications. Send conversations to a local LLM (Ollama, etc.) and get responses.
|
|
6
4
|
|
|
7
5
|
## Installation
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
Add to your Gemfile:
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
```ruby
|
|
10
|
+
gem "rails_orchestrator"
|
|
11
|
+
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Then run `bundle install`.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Usage
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
### Configuration
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
```ruby
|
|
20
|
+
RailsOrchestrator.configure do |config|
|
|
21
|
+
config.base_url = "http://localhost:11434" # default
|
|
22
|
+
config.model = "llama3" # default
|
|
23
|
+
config.request_timeout = 120 # default, in seconds
|
|
24
|
+
end
|
|
25
|
+
```
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
### Sending a conversation
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
```ruby
|
|
30
|
+
conversation = RailsOrchestrator::Conversation.new
|
|
31
|
+
conversation.system("You are a helpful assistant.")
|
|
32
|
+
conversation.user("Hello!")
|
|
33
|
+
|
|
34
|
+
client = RailsOrchestrator::Client.new
|
|
35
|
+
response = client.chat(conversation)
|
|
36
|
+
response.content # => "Hi there!"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
You can also pass plain arrays:
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
```ruby
|
|
42
|
+
response = client.chat([{ role: "user", content: "Hello" }])
|
|
43
|
+
```
|
|
26
44
|
|
|
27
|
-
|
|
45
|
+
### Error handling
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
begin
|
|
49
|
+
response = client.chat(conversation)
|
|
50
|
+
rescue RailsOrchestrator::ConnectionError => e
|
|
51
|
+
# Network failure (connection refused, timeout, etc.)
|
|
52
|
+
rescue RailsOrchestrator::ApiError => e
|
|
53
|
+
e.status # HTTP status code
|
|
54
|
+
e.body # Raw response body
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Development
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
bundle install
|
|
62
|
+
bundle exec rspec
|
|
63
|
+
bundle exec rubocop
|
|
64
|
+
```
|
|
28
65
|
|
|
29
66
|
## Contributing
|
|
30
67
|
|
|
31
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
68
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jpmsantana/rails_orchestrator.
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module RailsOrchestrator
|
|
8
|
+
class Client
|
|
9
|
+
def initialize(config: nil)
|
|
10
|
+
@config = config || RailsOrchestrator.configuration
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def chat(messages)
|
|
14
|
+
messages = messages.to_a if messages.is_a?(Conversation)
|
|
15
|
+
messages = normalize_messages(messages)
|
|
16
|
+
|
|
17
|
+
body = { model: @config.model, messages: messages, stream: false }
|
|
18
|
+
Response.new(post("/api/chat", body))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def normalize_messages(messages)
|
|
24
|
+
messages.map do |msg|
|
|
25
|
+
if msg.is_a?(Hash)
|
|
26
|
+
{ role: msg[:role] || msg["role"], content: msg[:content] || msg["content"] }
|
|
27
|
+
else
|
|
28
|
+
msg.to_h
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def post(path, body)
|
|
34
|
+
uri = URI.join(@config.base_url, path)
|
|
35
|
+
response = execute_request(uri, body)
|
|
36
|
+
handle_response(response)
|
|
37
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Net::OpenTimeout, Net::ReadTimeout, SocketError => e
|
|
38
|
+
raise ConnectionError, "Failed to connect to #{@config.base_url}: #{e.message}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def execute_request(uri, body)
|
|
42
|
+
request = Net::HTTP::Post.new(uri)
|
|
43
|
+
request["Content-Type"] = "application/json"
|
|
44
|
+
request.body = JSON.generate(body)
|
|
45
|
+
|
|
46
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
47
|
+
http.read_timeout = @config.request_timeout
|
|
48
|
+
http.open_timeout = @config.request_timeout
|
|
49
|
+
http.request(request)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def handle_response(response)
|
|
53
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
54
|
+
raise ApiError.new(
|
|
55
|
+
"API request failed with status #{response.code}",
|
|
56
|
+
status: response.code.to_i, body: response.body
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
JSON.parse(response.body)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsOrchestrator
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :base_url, :model, :request_timeout
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@base_url = "http://localhost:11434"
|
|
9
|
+
@model = "llama3"
|
|
10
|
+
@request_timeout = 120
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsOrchestrator
|
|
4
|
+
class Conversation
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@messages = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def system(content)
|
|
12
|
+
@messages << Message.new(role: "system", content: content)
|
|
13
|
+
self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def user(content)
|
|
17
|
+
@messages << Message.new(role: "user", content: content)
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def assistant(content)
|
|
22
|
+
@messages << Message.new(role: "assistant", content: content)
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def each(&block)
|
|
27
|
+
@messages.each(&block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_a
|
|
31
|
+
@messages.map(&:to_h)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def size
|
|
35
|
+
@messages.size
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsOrchestrator
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
class ConnectionError < Error; end
|
|
6
|
+
|
|
7
|
+
class ApiError < Error
|
|
8
|
+
attr_reader :status, :body
|
|
9
|
+
|
|
10
|
+
def initialize(message, status:, body:)
|
|
11
|
+
@status = status
|
|
12
|
+
@body = body
|
|
13
|
+
super(message)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsOrchestrator
|
|
4
|
+
class Message
|
|
5
|
+
attr_reader :role, :content
|
|
6
|
+
|
|
7
|
+
def initialize(role:, content:)
|
|
8
|
+
@role = role.to_s
|
|
9
|
+
@content = content
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_h
|
|
13
|
+
{ role: @role, content: @content }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def ==(other)
|
|
17
|
+
other.is_a?(Message) && role == other.role && content == other.content
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsOrchestrator
|
|
4
|
+
class Response
|
|
5
|
+
attr_reader :raw
|
|
6
|
+
|
|
7
|
+
def initialize(raw)
|
|
8
|
+
@raw = raw
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def content
|
|
12
|
+
# Ollama format
|
|
13
|
+
if raw.key?("message")
|
|
14
|
+
raw.dig("message", "content")
|
|
15
|
+
# OpenAI-compatible format
|
|
16
|
+
elsif raw.key?("choices")
|
|
17
|
+
raw.dig("choices", 0, "message", "content")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def model
|
|
22
|
+
raw["model"]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def done?
|
|
26
|
+
raw["done"] == true
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/rails_orchestrator.rb
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "rails_orchestrator/version"
|
|
4
|
+
require_relative "rails_orchestrator/errors"
|
|
5
|
+
require_relative "rails_orchestrator/configuration"
|
|
6
|
+
require_relative "rails_orchestrator/message"
|
|
7
|
+
require_relative "rails_orchestrator/conversation"
|
|
8
|
+
require_relative "rails_orchestrator/response"
|
|
9
|
+
require_relative "rails_orchestrator/client"
|
|
4
10
|
|
|
5
11
|
module RailsOrchestrator
|
|
6
|
-
class
|
|
12
|
+
class << self
|
|
13
|
+
def configuration
|
|
14
|
+
@configuration ||= Configuration.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def configure
|
|
18
|
+
yield(configuration)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def reset_configuration!
|
|
22
|
+
@configuration = Configuration.new
|
|
23
|
+
end
|
|
24
|
+
end
|
|
7
25
|
end
|
data/rails_orchestrator.gemspec
CHANGED
|
@@ -9,7 +9,9 @@ Gem::Specification.new do |spec|
|
|
|
9
9
|
spec.email = ["jpmsantana14@gmail.com"]
|
|
10
10
|
|
|
11
11
|
spec.summary = "A framework to build AI agents in Rails applications with tool support, memory and orchestration."
|
|
12
|
-
spec.description = "RailsOrchestrator provides a structured framework for building AI agents
|
|
12
|
+
spec.description = "RailsOrchestrator provides a structured framework for building AI agents " \
|
|
13
|
+
"in Ruby on Rails applications. It includes tool support, memory management, " \
|
|
14
|
+
"and orchestration capabilities for composing complex agent workflows."
|
|
13
15
|
spec.homepage = "https://github.com/jpmsantana/rails_orchestrator"
|
|
14
16
|
spec.license = "MIT"
|
|
15
17
|
spec.required_ruby_version = ">= 3.0.0"
|
|
@@ -32,9 +34,6 @@ Gem::Specification.new do |spec|
|
|
|
32
34
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
33
35
|
spec.require_paths = ["lib"]
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# For more information and examples about making a new gem, check out our
|
|
39
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
|
37
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
38
|
+
spec.add_development_dependency "rubocop", "~> 1.21"
|
|
40
39
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_orchestrator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- João Paulo Moreira Sant'ana
|
|
@@ -9,7 +9,35 @@ autorequire:
|
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2026-02-02 00:00:00.000000000 Z
|
|
12
|
-
dependencies:
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rspec
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '3.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rubocop
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.21'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.21'
|
|
13
41
|
description: RailsOrchestrator provides a structured framework for building AI agents
|
|
14
42
|
in Ruby on Rails applications. It includes tool support, memory management, and
|
|
15
43
|
orchestration capabilities for composing complex agent workflows.
|
|
@@ -19,10 +47,18 @@ executables: []
|
|
|
19
47
|
extensions: []
|
|
20
48
|
extra_rdoc_files: []
|
|
21
49
|
files:
|
|
50
|
+
- ".rspec"
|
|
51
|
+
- ".rubocop.yml"
|
|
22
52
|
- LICENSE.txt
|
|
23
53
|
- README.md
|
|
24
54
|
- Rakefile
|
|
25
55
|
- lib/rails_orchestrator.rb
|
|
56
|
+
- lib/rails_orchestrator/client.rb
|
|
57
|
+
- lib/rails_orchestrator/configuration.rb
|
|
58
|
+
- lib/rails_orchestrator/conversation.rb
|
|
59
|
+
- lib/rails_orchestrator/errors.rb
|
|
60
|
+
- lib/rails_orchestrator/message.rb
|
|
61
|
+
- lib/rails_orchestrator/response.rb
|
|
26
62
|
- lib/rails_orchestrator/version.rb
|
|
27
63
|
- rails_orchestrator.gemspec
|
|
28
64
|
- sig/rails_orchestrator.rbs
|