rubyrana 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/CHANGELOG.md +9 -0
- data/LICENSE +190 -0
- data/NOTICE +4 -0
- data/README.md +179 -0
- data/REPORT.md +156 -0
- data/docs/CHECKLIST.md +32 -0
- data/docs/RELEASE.md +16 -0
- data/docs/USAGE.md +54 -0
- data/examples/mcp.rb +22 -0
- data/examples/quick_start.rb +23 -0
- data/examples/streaming.rb +19 -0
- data/examples/tools_loader.rb +18 -0
- data/lib/rubyrana/agent.rb +153 -0
- data/lib/rubyrana/config.rb +15 -0
- data/lib/rubyrana/errors.rb +11 -0
- data/lib/rubyrana/mcp/client.rb +124 -0
- data/lib/rubyrana/multi_agent.rb +21 -0
- data/lib/rubyrana/persistence/base.rb +15 -0
- data/lib/rubyrana/persistence/file_store.rb +38 -0
- data/lib/rubyrana/persistence/redis_store.rb +36 -0
- data/lib/rubyrana/providers/anthropic.rb +176 -0
- data/lib/rubyrana/providers/base.rb +25 -0
- data/lib/rubyrana/providers/bedrock.rb +11 -0
- data/lib/rubyrana/providers/openai.rb +11 -0
- data/lib/rubyrana/routing/keyword_router.rb +20 -0
- data/lib/rubyrana/safety/filter.rb +28 -0
- data/lib/rubyrana/tool.rb +30 -0
- data/lib/rubyrana/tool_registry.rb +26 -0
- data/lib/rubyrana/tooling.rb +33 -0
- data/lib/rubyrana/tools/code_interpreter.rb +68 -0
- data/lib/rubyrana/tools/loader.rb +20 -0
- data/lib/rubyrana/tools/mcp_web_search.rb +36 -0
- data/lib/rubyrana/tools/web_search.rb +79 -0
- data/lib/rubyrana/tools.rb +22 -0
- data/lib/rubyrana/version.rb +5 -0
- data/lib/rubyrana.rb +33 -0
- metadata +149 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyrana
|
|
4
|
+
module Routing
|
|
5
|
+
class KeywordRouter
|
|
6
|
+
def initialize(routes: {}, default_index: 0)
|
|
7
|
+
@routes = routes
|
|
8
|
+
@default_index = default_index
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def route(prompt, agents:)
|
|
12
|
+
match = @routes.find { |keyword, _| prompt.to_s.downcase.include?(keyword.to_s.downcase) }
|
|
13
|
+
index = match ? match[1] : @default_index
|
|
14
|
+
agents.fetch(index)
|
|
15
|
+
rescue IndexError
|
|
16
|
+
raise Rubyrana::RoutingError, "No agent found for route"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyrana
|
|
4
|
+
module Safety
|
|
5
|
+
class Filter
|
|
6
|
+
def check(_text)
|
|
7
|
+
[]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def enforce!(text)
|
|
11
|
+
violations = check(text)
|
|
12
|
+
return if violations.empty?
|
|
13
|
+
|
|
14
|
+
raise Rubyrana::SafetyError, "Safety filter triggered: #{violations.join(", ")}" if violations.any?
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class BlocklistFilter < Filter
|
|
19
|
+
def initialize(patterns: [])
|
|
20
|
+
@patterns = patterns.map { |p| p.is_a?(Regexp) ? p : /#{Regexp.escape(p)}/i }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def check(text)
|
|
24
|
+
@patterns.select { |pattern| text.match?(pattern) }.map(&:source)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyrana
|
|
4
|
+
class Tool
|
|
5
|
+
attr_reader :name, :description, :schema
|
|
6
|
+
|
|
7
|
+
def initialize(name, description: nil, schema: nil, &block)
|
|
8
|
+
raise ToolError, "Tool requires a block" unless block_given?
|
|
9
|
+
|
|
10
|
+
@name = name.to_s
|
|
11
|
+
@description = description
|
|
12
|
+
@schema = schema
|
|
13
|
+
@block = block
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(**kwargs)
|
|
17
|
+
@block.call(**kwargs)
|
|
18
|
+
rescue StandardError => e
|
|
19
|
+
raise ToolError, e.message
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_h
|
|
23
|
+
{
|
|
24
|
+
name: name,
|
|
25
|
+
description: description,
|
|
26
|
+
input_schema: schema || { type: "object", properties: {}, required: [] }
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyrana
|
|
4
|
+
class ToolRegistry
|
|
5
|
+
def initialize(tools = [])
|
|
6
|
+
@tools = {}
|
|
7
|
+
tools.each { |tool| register(tool) }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def register(tool)
|
|
11
|
+
@tools[tool.name] = tool
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def fetch(name)
|
|
15
|
+
@tools[name.to_s]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def all
|
|
19
|
+
@tools.values
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def definitions
|
|
23
|
+
all.map(&:to_h)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyrana
|
|
4
|
+
module Tooling
|
|
5
|
+
@registry = Rubyrana::ToolRegistry.new
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
attr_reader :registry
|
|
9
|
+
|
|
10
|
+
def reset!
|
|
11
|
+
@registry = Rubyrana::ToolRegistry.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def register(tool)
|
|
15
|
+
@registry.register(tool)
|
|
16
|
+
tool
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def tools
|
|
20
|
+
@registry.all
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def define(name, description: nil, schema: nil, &block)
|
|
24
|
+
tool = Rubyrana::Tool.new(name, description: description, schema: schema, &block)
|
|
25
|
+
register(tool)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.tool(name, description: nil, schema: nil, &block)
|
|
31
|
+
Tooling.define(name, description: description, schema: schema, &block)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "tempfile"
|
|
6
|
+
require "timeout"
|
|
7
|
+
|
|
8
|
+
module Rubyrana
|
|
9
|
+
module Tools
|
|
10
|
+
class CodeInterpreter
|
|
11
|
+
DEFAULT_TIMEOUT = 5
|
|
12
|
+
|
|
13
|
+
def initialize(timeout_s: DEFAULT_TIMEOUT, ruby_bin: "ruby")
|
|
14
|
+
@timeout_s = timeout_s
|
|
15
|
+
@ruby_bin = ruby_bin
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def tool
|
|
19
|
+
Rubyrana::Tool.new(
|
|
20
|
+
"code_interpreter",
|
|
21
|
+
description: "Run Ruby code in a temporary process and return stdout/stderr.",
|
|
22
|
+
schema: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
code: { type: "string" },
|
|
26
|
+
timeout_s: { type: "number" }
|
|
27
|
+
},
|
|
28
|
+
required: ["code"]
|
|
29
|
+
}
|
|
30
|
+
) do |code:, timeout_s: nil|
|
|
31
|
+
execute(code: code, timeout_s: timeout_s)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def execute(code:, timeout_s: nil)
|
|
38
|
+
timeout_value = (timeout_s || @timeout_s).to_f
|
|
39
|
+
|
|
40
|
+
Tempfile.create(["rubyrana", ".rb"]) do |file|
|
|
41
|
+
file.write(code)
|
|
42
|
+
file.flush
|
|
43
|
+
|
|
44
|
+
stdout, stderr, status = run_with_timeout(file.path, timeout_value)
|
|
45
|
+
{
|
|
46
|
+
stdout: stdout,
|
|
47
|
+
stderr: stderr,
|
|
48
|
+
exit_status: status&.exitstatus
|
|
49
|
+
}.to_json
|
|
50
|
+
end
|
|
51
|
+
rescue Timeout::Error
|
|
52
|
+
{ stdout: "", stderr: "Execution timed out", exit_status: nil }.to_json
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def run_with_timeout(path, timeout_value)
|
|
56
|
+
stdout = ""
|
|
57
|
+
stderr = ""
|
|
58
|
+
status = nil
|
|
59
|
+
|
|
60
|
+
Timeout.timeout(timeout_value) do
|
|
61
|
+
stdout, stderr, status = Open3.capture3(@ruby_bin, path)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
[stdout, stderr, status]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyrana
|
|
4
|
+
module Tools
|
|
5
|
+
class Loader
|
|
6
|
+
def initialize(directory)
|
|
7
|
+
@directory = directory
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def load
|
|
11
|
+
return [] unless Dir.exist?(@directory)
|
|
12
|
+
|
|
13
|
+
before = Rubyrana::Tooling.tools.dup
|
|
14
|
+
Dir[File.join(@directory, "**/*.rb")].sort.each { |file| require file }
|
|
15
|
+
after = Rubyrana::Tooling.tools
|
|
16
|
+
(after - before)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubyrana
|
|
4
|
+
module Tools
|
|
5
|
+
class MCPWebSearch
|
|
6
|
+
def initialize(command:, args: [], tool_name: "web_search")
|
|
7
|
+
@command = command
|
|
8
|
+
@args = args
|
|
9
|
+
@tool_name = tool_name
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def tool
|
|
13
|
+
Rubyrana::Tool.new(
|
|
14
|
+
"web_search",
|
|
15
|
+
description: "Web search via MCP server tool.",
|
|
16
|
+
schema: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
query: { type: "string" },
|
|
20
|
+
limit: { type: "number" }
|
|
21
|
+
},
|
|
22
|
+
required: ["query"]
|
|
23
|
+
}
|
|
24
|
+
) do |query:, limit: 5|
|
|
25
|
+
mcp = Rubyrana::MCP::Client.new(command: @command, args: @args)
|
|
26
|
+
mcp.with_session do |tools|
|
|
27
|
+
tool = tools.find { |t| t.name == @tool_name }
|
|
28
|
+
raise Rubyrana::ToolError, "MCP tool not found: #{@tool_name}" unless tool
|
|
29
|
+
|
|
30
|
+
tool.call(query: query, limit: limit)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "openssl"
|
|
6
|
+
require "uri"
|
|
7
|
+
|
|
8
|
+
module Rubyrana
|
|
9
|
+
module Tools
|
|
10
|
+
class WebSearch
|
|
11
|
+
DEFAULT_PROVIDER = "serper"
|
|
12
|
+
|
|
13
|
+
def initialize(api_key: ENV["WEB_SEARCH_API_KEY"], provider: DEFAULT_PROVIDER, ssl_cert_file: ENV["SSL_CERT_FILE"])
|
|
14
|
+
@api_key = api_key
|
|
15
|
+
@provider = provider
|
|
16
|
+
@ssl_cert_file = ssl_cert_file
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def tool
|
|
20
|
+
Rubyrana::Tool.new(
|
|
21
|
+
"web_search",
|
|
22
|
+
description: "Search the web and return top results.",
|
|
23
|
+
schema: {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
query: { type: "string" },
|
|
27
|
+
limit: { type: "number" }
|
|
28
|
+
},
|
|
29
|
+
required: ["query"]
|
|
30
|
+
}
|
|
31
|
+
) do |query:, limit: 5|
|
|
32
|
+
search(query: query, limit: limit)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def search(query:, limit: 5)
|
|
39
|
+
raise Rubyrana::ConfigurationError, "WEB_SEARCH_API_KEY not set. Users must supply their own web search API key." unless @api_key
|
|
40
|
+
|
|
41
|
+
case @provider
|
|
42
|
+
when "serper"
|
|
43
|
+
serper_search(query: query, limit: limit)
|
|
44
|
+
else
|
|
45
|
+
raise Rubyrana::ConfigurationError, "Unsupported web search provider: #{@provider}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def serper_search(query:, limit:)
|
|
50
|
+
uri = URI("https://google.serper.dev/search")
|
|
51
|
+
request = Net::HTTP::Post.new(uri)
|
|
52
|
+
request["X-API-KEY"] = @api_key
|
|
53
|
+
request["Content-Type"] = "application/json"
|
|
54
|
+
request.body = JSON.dump({ q: query, num: limit })
|
|
55
|
+
|
|
56
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
57
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
58
|
+
http.ca_file = @ssl_cert_file if @ssl_cert_file && !@ssl_cert_file.empty?
|
|
59
|
+
http.request(request)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
raise Rubyrana::ProviderError, "Web search failed (status #{response.code})" unless response.is_a?(Net::HTTPSuccess)
|
|
63
|
+
|
|
64
|
+
body = JSON.parse(response.body)
|
|
65
|
+
results = (body["organic"] || []).first(limit).map do |item|
|
|
66
|
+
{
|
|
67
|
+
title: item["title"],
|
|
68
|
+
url: item["link"],
|
|
69
|
+
snippet: item["snippet"]
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
{ results: results }.to_json
|
|
74
|
+
rescue JSON::ParserError
|
|
75
|
+
raise Rubyrana::ProviderError, "Invalid web search response"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tools/code_interpreter"
|
|
4
|
+
require_relative "tools/mcp_web_search"
|
|
5
|
+
require_relative "tools/loader"
|
|
6
|
+
require_relative "tools/web_search"
|
|
7
|
+
|
|
8
|
+
module Rubyrana
|
|
9
|
+
module Tools
|
|
10
|
+
def self.code_interpreter(timeout_s: CodeInterpreter::DEFAULT_TIMEOUT)
|
|
11
|
+
CodeInterpreter.new(timeout_s: timeout_s).tool
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.web_search(api_key: ENV["WEB_SEARCH_API_KEY"], provider: WebSearch::DEFAULT_PROVIDER)
|
|
15
|
+
WebSearch.new(api_key: api_key, provider: provider).tool
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.web_search_mcp(command:, args: [], tool_name: "web_search")
|
|
19
|
+
MCPWebSearch.new(command: command, args: args, tool_name: tool_name).tool
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/rubyrana.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "rubyrana/version"
|
|
4
|
+
require_relative "rubyrana/errors"
|
|
5
|
+
require_relative "rubyrana/config"
|
|
6
|
+
require_relative "rubyrana/tool"
|
|
7
|
+
require_relative "rubyrana/tool_registry"
|
|
8
|
+
require_relative "rubyrana/tooling"
|
|
9
|
+
require_relative "rubyrana/agent"
|
|
10
|
+
require_relative "rubyrana/tools"
|
|
11
|
+
require_relative "rubyrana/multi_agent"
|
|
12
|
+
require_relative "rubyrana/safety/filter"
|
|
13
|
+
require_relative "rubyrana/persistence/base"
|
|
14
|
+
require_relative "rubyrana/persistence/file_store"
|
|
15
|
+
require_relative "rubyrana/persistence/redis_store"
|
|
16
|
+
require_relative "rubyrana/routing/keyword_router"
|
|
17
|
+
require_relative "rubyrana/mcp/client"
|
|
18
|
+
require_relative "rubyrana/providers/base"
|
|
19
|
+
require_relative "rubyrana/providers/openai"
|
|
20
|
+
require_relative "rubyrana/providers/anthropic"
|
|
21
|
+
require_relative "rubyrana/providers/bedrock"
|
|
22
|
+
|
|
23
|
+
module Rubyrana
|
|
24
|
+
class << self
|
|
25
|
+
def configure
|
|
26
|
+
yield(config)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def config
|
|
30
|
+
@config ||= Rubyrana::Config.new
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rubyrana
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Rubyrana Contributors
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-02-01 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: json
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: minitest
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '5.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '5.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rubocop
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: dotenv
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '2.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '2.0'
|
|
83
|
+
description: Rubyrana is a model-driven Ruby SDK for building AI agents.
|
|
84
|
+
email:
|
|
85
|
+
- hello@rubyrana.dev
|
|
86
|
+
executables: []
|
|
87
|
+
extensions: []
|
|
88
|
+
extra_rdoc_files: []
|
|
89
|
+
files:
|
|
90
|
+
- CHANGELOG.md
|
|
91
|
+
- LICENSE
|
|
92
|
+
- NOTICE
|
|
93
|
+
- README.md
|
|
94
|
+
- REPORT.md
|
|
95
|
+
- docs/CHECKLIST.md
|
|
96
|
+
- docs/RELEASE.md
|
|
97
|
+
- docs/USAGE.md
|
|
98
|
+
- examples/mcp.rb
|
|
99
|
+
- examples/quick_start.rb
|
|
100
|
+
- examples/streaming.rb
|
|
101
|
+
- examples/tools_loader.rb
|
|
102
|
+
- lib/rubyrana.rb
|
|
103
|
+
- lib/rubyrana/agent.rb
|
|
104
|
+
- lib/rubyrana/config.rb
|
|
105
|
+
- lib/rubyrana/errors.rb
|
|
106
|
+
- lib/rubyrana/mcp/client.rb
|
|
107
|
+
- lib/rubyrana/multi_agent.rb
|
|
108
|
+
- lib/rubyrana/persistence/base.rb
|
|
109
|
+
- lib/rubyrana/persistence/file_store.rb
|
|
110
|
+
- lib/rubyrana/persistence/redis_store.rb
|
|
111
|
+
- lib/rubyrana/providers/anthropic.rb
|
|
112
|
+
- lib/rubyrana/providers/base.rb
|
|
113
|
+
- lib/rubyrana/providers/bedrock.rb
|
|
114
|
+
- lib/rubyrana/providers/openai.rb
|
|
115
|
+
- lib/rubyrana/routing/keyword_router.rb
|
|
116
|
+
- lib/rubyrana/safety/filter.rb
|
|
117
|
+
- lib/rubyrana/tool.rb
|
|
118
|
+
- lib/rubyrana/tool_registry.rb
|
|
119
|
+
- lib/rubyrana/tooling.rb
|
|
120
|
+
- lib/rubyrana/tools.rb
|
|
121
|
+
- lib/rubyrana/tools/code_interpreter.rb
|
|
122
|
+
- lib/rubyrana/tools/loader.rb
|
|
123
|
+
- lib/rubyrana/tools/mcp_web_search.rb
|
|
124
|
+
- lib/rubyrana/tools/web_search.rb
|
|
125
|
+
- lib/rubyrana/version.rb
|
|
126
|
+
homepage: https://github.com/your-org/rubyrana
|
|
127
|
+
licenses:
|
|
128
|
+
- Apache-2.0
|
|
129
|
+
metadata: {}
|
|
130
|
+
post_install_message:
|
|
131
|
+
rdoc_options: []
|
|
132
|
+
require_paths:
|
|
133
|
+
- lib
|
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: 3.1.0
|
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
|
+
requirements:
|
|
141
|
+
- - ">="
|
|
142
|
+
- !ruby/object:Gem::Version
|
|
143
|
+
version: '0'
|
|
144
|
+
requirements: []
|
|
145
|
+
rubygems_version: 3.3.27
|
|
146
|
+
signing_key:
|
|
147
|
+
specification_version: 4
|
|
148
|
+
summary: Build production-ready AI agents in Ruby
|
|
149
|
+
test_files: []
|