swarm-rb 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/Readme.md +36 -0
- data/lib/swarm/core.rb +199 -0
- data/lib/swarm/repl.rb +97 -0
- data/lib/swarm/types.rb +41 -0
- data/lib/swarm/util.rb +71 -0
- data/lib/swarm.rb +9 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cae2fec017926fd78d147037ca44fa5f0d77118cf098e2dc47796f9d069f2dff
|
4
|
+
data.tar.gz: 64084bfbbb87381a25270868903338192f04e05df375286c11c2ad8dc2393c0a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d93372e43f1600086779e286783a90c9048318d01b6ad96a60210c77e49e114685b58b5a4106c96a5cb91be8a3522ceaa6d2e845d21c302ebdd6c82991285ab3
|
7
|
+
data.tar.gz: d6ad7fece31eb2bbe265da75a21fc79e4b2349e097073c13f311f3894bbfab2d1bcffb6b21fe5353d88a0df9a52adaf5d2f7c71f716234174929244137971c2d
|
data/Readme.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+

|
2
|
+
|
3
|
+
# Swarm-RB (experimental, educational)
|
4
|
+
|
5
|
+
An educational framework exploring ergonomic, lightweight multi-agent orchestration in Ruby. It is a port of [Swarm](https://github.com/openai/swarm/tree/main/swarm) created by Open AI.
|
6
|
+
|
7
|
+
## Install
|
8
|
+
|
9
|
+
```shell
|
10
|
+
gem install swarm-rb
|
11
|
+
```
|
12
|
+
|
13
|
+
Or, if using Bundler, add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'swarm-rb', git: 'https://github.com/openai/swarm-rb.git'
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
```shell
|
22
|
+
bundle install
|
23
|
+
```
|
24
|
+
|
25
|
+
# Examples
|
26
|
+
|
27
|
+
Check out `/examples` for inspiration!
|
28
|
+
|
29
|
+
- [`basic`](examples/basic): Simple examples of fundamentals like setup, function calling, handoffs, and context variables.
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
Everyone is welcome to contribute to this. This gem was created just because I wanted to use Open AI's swarm framework in Ruby. There is likly a ton of 🍌s code in this repo since I relied heavily on LLMs to create this gem in < 2 days.
|
34
|
+
|
35
|
+
It needs more testing, documentation clean up and expantion on the examples to parity the Open AI's [Swarm](https://github.com/openai/swarm) library.
|
36
|
+
|
data/lib/swarm/core.rb
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
require "openai"
|
2
|
+
require "json"
|
3
|
+
require_relative "util"
|
4
|
+
require_relative "types"
|
5
|
+
|
6
|
+
module Swarm
|
7
|
+
class Swarm
|
8
|
+
CTX_VARS_NAME = "context_variables"
|
9
|
+
|
10
|
+
def initialize(client = nil)
|
11
|
+
@client = client || OpenAI::Client.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_chat_completion(
|
15
|
+
agent:,
|
16
|
+
history:,
|
17
|
+
context_variables:,
|
18
|
+
model_override: nil,
|
19
|
+
stream: false,
|
20
|
+
debug: false
|
21
|
+
)
|
22
|
+
context_variables = context_variables.dup
|
23
|
+
instructions = if agent.instructions.respond_to?(:call)
|
24
|
+
agent.instructions.call(context_variables)
|
25
|
+
else
|
26
|
+
agent.instructions
|
27
|
+
end
|
28
|
+
|
29
|
+
messages = [{"role" => "system", "content" => instructions}] + history
|
30
|
+
Util.debug_print(debug, "Getting chat completion for:", messages)
|
31
|
+
|
32
|
+
tools = agent.functions.map { |f| Util.function_to_json(f) }
|
33
|
+
|
34
|
+
# Hide context_variables from the model
|
35
|
+
tools.each do |tool|
|
36
|
+
puts tool
|
37
|
+
params = tool["parameters"]
|
38
|
+
params["properties"]&.delete(CTX_VARS_NAME)
|
39
|
+
params["required"]&.delete(CTX_VARS_NAME)
|
40
|
+
end
|
41
|
+
|
42
|
+
parameters = {
|
43
|
+
model: model_override || agent.model,
|
44
|
+
messages: messages
|
45
|
+
}
|
46
|
+
|
47
|
+
parameters[:functions] = tools unless tools.empty?
|
48
|
+
parameters[:function_call] = agent.tool_choice if agent.tool_choice
|
49
|
+
parameters[:stream] = stream
|
50
|
+
|
51
|
+
Util.debug_print(debug, "Chat parameters:", parameters)
|
52
|
+
|
53
|
+
begin
|
54
|
+
if stream
|
55
|
+
@client.chat(parameters: parameters) do |chunk|
|
56
|
+
# Handle streaming response if needed
|
57
|
+
end
|
58
|
+
else
|
59
|
+
response = @client.chat(parameters: parameters)
|
60
|
+
Util.debug_print(debug, "API Response:", response)
|
61
|
+
response
|
62
|
+
end
|
63
|
+
rescue OpenAI::Error => e
|
64
|
+
Util.debug_print(true, "OpenAI API Error:", e.message)
|
65
|
+
raise
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle_function_result(result, debug)
|
70
|
+
case result
|
71
|
+
when Result
|
72
|
+
result
|
73
|
+
when Agent
|
74
|
+
Result.new(value: {"assistant" => result.name}.to_json, agent: result)
|
75
|
+
else
|
76
|
+
begin
|
77
|
+
Result.new(value: result.to_s)
|
78
|
+
rescue => e
|
79
|
+
error_message = "Failed to cast response to string: #{result}. Make sure agent functions return a string or Result object. Error: #{e.message}"
|
80
|
+
Util.debug_print(debug, error_message)
|
81
|
+
raise TypeError, error_message
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def handle_tool_calls(tool_calls, functions, context_variables, debug)
|
87
|
+
function_map = functions.map { |f| [f.name.to_s, f] }.to_h
|
88
|
+
partial_response = Response.new(messages: [], agent: nil, context_variables: {})
|
89
|
+
|
90
|
+
tool_calls.each do |tool_call|
|
91
|
+
name = tool_call["name"]
|
92
|
+
unless function_map.key?(name)
|
93
|
+
Util.debug_print(debug, "Tool #{name} not found in function map.")
|
94
|
+
partial_response.messages << {
|
95
|
+
"role" => "function",
|
96
|
+
"name" => name,
|
97
|
+
"content" => "Error: Tool #{name} not found."
|
98
|
+
}
|
99
|
+
next
|
100
|
+
end
|
101
|
+
|
102
|
+
args = JSON.parse(tool_call["arguments"] || "{}")
|
103
|
+
Util.debug_print(debug, "Processing tool call: #{name} with arguments #{args}")
|
104
|
+
|
105
|
+
func = function_map[name]
|
106
|
+
# Pass context_variables to agent functions
|
107
|
+
if func.parameters.map(&:last).include?(CTX_VARS_NAME.to_sym)
|
108
|
+
args[CTX_VARS_NAME] = context_variables
|
109
|
+
end
|
110
|
+
|
111
|
+
raw_result = func.call(**args.transform_keys(&:to_sym))
|
112
|
+
result = handle_function_result(raw_result, debug)
|
113
|
+
partial_response.messages << {
|
114
|
+
"role" => "function",
|
115
|
+
"name" => name,
|
116
|
+
"content" => result.value
|
117
|
+
}
|
118
|
+
partial_response.context_variables.merge!(result.context_variables)
|
119
|
+
partial_response.agent ||= result.agent
|
120
|
+
end
|
121
|
+
|
122
|
+
partial_response
|
123
|
+
end
|
124
|
+
|
125
|
+
def run(
|
126
|
+
agent:,
|
127
|
+
messages:,
|
128
|
+
context_variables: {},
|
129
|
+
model_override: nil,
|
130
|
+
stream: false,
|
131
|
+
debug: false,
|
132
|
+
max_turns: Float::INFINITY,
|
133
|
+
execute_tools: true
|
134
|
+
)
|
135
|
+
if stream
|
136
|
+
run_and_stream(
|
137
|
+
agent: agent,
|
138
|
+
messages: messages,
|
139
|
+
context_variables: context_variables,
|
140
|
+
model_override: model_override,
|
141
|
+
debug: debug,
|
142
|
+
max_turns: max_turns,
|
143
|
+
execute_tools: execute_tools
|
144
|
+
)
|
145
|
+
else
|
146
|
+
active_agent = agent
|
147
|
+
context_variables = context_variables.dup
|
148
|
+
history = messages.dup
|
149
|
+
init_len = messages.length
|
150
|
+
|
151
|
+
while (history.length - init_len) < max_turns && active_agent
|
152
|
+
completion = get_chat_completion(
|
153
|
+
agent: active_agent,
|
154
|
+
history: history,
|
155
|
+
context_variables: context_variables,
|
156
|
+
model_override: model_override,
|
157
|
+
stream: stream,
|
158
|
+
debug: debug
|
159
|
+
)
|
160
|
+
|
161
|
+
message = completion["choices"][0]["message"]
|
162
|
+
Util.debug_print(debug, "Received completion:", message)
|
163
|
+
message["sender"] = active_agent.name
|
164
|
+
history << message
|
165
|
+
|
166
|
+
unless message["function_call"] && execute_tools
|
167
|
+
Util.debug_print(debug, "Ending turn.")
|
168
|
+
break
|
169
|
+
end
|
170
|
+
|
171
|
+
tool_calls = [message["function_call"]]
|
172
|
+
partial_response = handle_tool_calls(
|
173
|
+
tool_calls, active_agent.functions, context_variables, debug
|
174
|
+
)
|
175
|
+
history.concat(partial_response.messages)
|
176
|
+
context_variables.merge!(partial_response.context_variables)
|
177
|
+
active_agent = partial_response.agent if partial_response.agent
|
178
|
+
end
|
179
|
+
|
180
|
+
Response.new(
|
181
|
+
messages: history[init_len..],
|
182
|
+
agent: active_agent,
|
183
|
+
context_variables: context_variables
|
184
|
+
)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def run_and_stream(
|
189
|
+
agent:,
|
190
|
+
messages:,
|
191
|
+
context_variables: {},
|
192
|
+
model_override: nil,
|
193
|
+
debug: false,
|
194
|
+
max_turns: Float::INFINITY,
|
195
|
+
execute_tools: true
|
196
|
+
)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
data/lib/swarm/repl.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require "json"
|
2
|
+
require_relative "core"
|
3
|
+
require "dotenv/load"
|
4
|
+
|
5
|
+
module Swarm
|
6
|
+
module Repl
|
7
|
+
def self.process_and_print_streaming_response(response)
|
8
|
+
content = ""
|
9
|
+
last_sender = ""
|
10
|
+
|
11
|
+
response.each do |chunk|
|
12
|
+
if chunk["sender"]
|
13
|
+
last_sender = chunk["sender"]
|
14
|
+
end
|
15
|
+
|
16
|
+
if chunk["content"]
|
17
|
+
if content.empty? && last_sender
|
18
|
+
print "\e[94m#{last_sender}:\e[0m "
|
19
|
+
last_sender = ""
|
20
|
+
end
|
21
|
+
print chunk["content"]
|
22
|
+
content += chunk["content"]
|
23
|
+
end
|
24
|
+
|
25
|
+
chunk["tool_calls"]&.each do |tool_call|
|
26
|
+
func = tool_call["function"]
|
27
|
+
name = func["name"]
|
28
|
+
next unless name
|
29
|
+
puts "\e[94m#{last_sender}: \e[95m#{name}\e[0m()"
|
30
|
+
end
|
31
|
+
|
32
|
+
if chunk["delim"] == "end" && !content.empty?
|
33
|
+
puts
|
34
|
+
content = ""
|
35
|
+
end
|
36
|
+
|
37
|
+
return chunk["response"] if chunk["response"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.pretty_print_messages(messages)
|
42
|
+
messages.each do |message|
|
43
|
+
next unless message["role"] == "assistant"
|
44
|
+
|
45
|
+
# Print agent name in blue
|
46
|
+
print "\e[94m#{message["sender"]}\e[0m: "
|
47
|
+
|
48
|
+
puts message["content"] if message["content"]
|
49
|
+
|
50
|
+
tool_calls = message["tool_calls"] || []
|
51
|
+
puts if tool_calls.length > 1
|
52
|
+
tool_calls.each do |tool_call|
|
53
|
+
func = tool_call["function"]
|
54
|
+
name = func["name"]
|
55
|
+
args = JSON.parse(func["arguments"] || "{}").map { |k, v| "#{k}=#{v}" }.join(", ")
|
56
|
+
puts "\e[95m#{name}\e[0m(#{args})"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.run_demo_loop(
|
62
|
+
starting_agent,
|
63
|
+
context_variables = nil,
|
64
|
+
stream = false,
|
65
|
+
debug = false
|
66
|
+
)
|
67
|
+
client = Swarm.new
|
68
|
+
puts "Starting Swarm CLI \u{1F41D}"
|
69
|
+
|
70
|
+
messages = []
|
71
|
+
agent = starting_agent
|
72
|
+
|
73
|
+
loop do
|
74
|
+
print "\e[90mUser\e[0m: "
|
75
|
+
user_input = gets.chomp
|
76
|
+
messages << {"role" => "user", "content" => user_input}
|
77
|
+
|
78
|
+
response = client.run(
|
79
|
+
agent: agent,
|
80
|
+
messages: messages,
|
81
|
+
context_variables: context_variables || {},
|
82
|
+
stream: stream,
|
83
|
+
debug: debug
|
84
|
+
)
|
85
|
+
|
86
|
+
if stream
|
87
|
+
response = process_and_print_streaming_response(response)
|
88
|
+
else
|
89
|
+
pretty_print_messages(response.messages)
|
90
|
+
end
|
91
|
+
|
92
|
+
messages.concat(response.messages)
|
93
|
+
agent = response.agent
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/swarm/types.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Swarm
|
2
|
+
class Agent
|
3
|
+
attr_accessor :name, :model, :instructions, :functions, :tool_choice, :parallel_tool_calls
|
4
|
+
|
5
|
+
def initialize(
|
6
|
+
name: "Agent",
|
7
|
+
model: "gpt-4",
|
8
|
+
instructions: "You are a helpful agent.",
|
9
|
+
functions: [],
|
10
|
+
tool_choice: nil,
|
11
|
+
parallel_tool_calls: true
|
12
|
+
)
|
13
|
+
@name = name
|
14
|
+
@model = model
|
15
|
+
@instructions = instructions
|
16
|
+
@functions = functions
|
17
|
+
@tool_choice = tool_choice
|
18
|
+
@parallel_tool_calls = parallel_tool_calls
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Response
|
23
|
+
attr_accessor :messages, :agent, :context_variables
|
24
|
+
|
25
|
+
def initialize(messages: [], agent: nil, context_variables: {})
|
26
|
+
@messages = messages
|
27
|
+
@agent = agent
|
28
|
+
@context_variables = context_variables
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Result
|
33
|
+
attr_accessor :value, :agent, :context_variables
|
34
|
+
|
35
|
+
def initialize(value: "", agent: nil, context_variables: {})
|
36
|
+
@value = value
|
37
|
+
@agent = agent
|
38
|
+
@context_variables = context_variables
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/swarm/util.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require "time"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module Swarm
|
5
|
+
module Util
|
6
|
+
def self.debug_print(debug, *args)
|
7
|
+
return unless debug
|
8
|
+
timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
9
|
+
message = args.join(" ")
|
10
|
+
puts "\e[97m[\e[90m#{timestamp}\e[97m]\e[90m #{message}\e[0m"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.merge_fields(target, source)
|
14
|
+
source.each do |key, value|
|
15
|
+
if value.is_a?(String)
|
16
|
+
target[key] += value
|
17
|
+
elsif value.is_a?(Hash)
|
18
|
+
merge_fields(target[key], value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.merge_chunk(final_response, delta)
|
24
|
+
delta.delete("role")
|
25
|
+
merge_fields(final_response, delta)
|
26
|
+
|
27
|
+
tool_calls = delta["tool_calls"]
|
28
|
+
if tool_calls && !tool_calls.empty?
|
29
|
+
index = tool_calls[0].delete("index")
|
30
|
+
merge_fields(final_response["tool_calls"][index], tool_calls[0])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.function_to_json(func)
|
35
|
+
type_map = {
|
36
|
+
String => "string",
|
37
|
+
Integer => "integer",
|
38
|
+
Float => "number",
|
39
|
+
TrueClass => "boolean",
|
40
|
+
FalseClass => "boolean",
|
41
|
+
Array => "array",
|
42
|
+
Hash => "object",
|
43
|
+
NilClass => "null"
|
44
|
+
}
|
45
|
+
|
46
|
+
parameters = {}
|
47
|
+
required = []
|
48
|
+
|
49
|
+
func.parameters.each do |type, name|
|
50
|
+
param_type = type_map[name.class] || "string" # Default to 'string' if type is unknown
|
51
|
+
|
52
|
+
if name.to_s == "context_variables" && type == :keyreq
|
53
|
+
param_type = "object"
|
54
|
+
end
|
55
|
+
|
56
|
+
parameters[name.to_s] = {"type" => param_type}
|
57
|
+
required << name.to_s if type == :req || type == :keyreq
|
58
|
+
end
|
59
|
+
|
60
|
+
{
|
61
|
+
"name" => func.name.to_s,
|
62
|
+
"description" => "",
|
63
|
+
"parameters" => {
|
64
|
+
"type" => "object",
|
65
|
+
"properties" => parameters,
|
66
|
+
"required" => required
|
67
|
+
}
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/swarm.rb
ADDED
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: swarm-rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Landon gray
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-10-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ruby-openai
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-struct
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: colorize
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.8'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: dotenv
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '5.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '5.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '13.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '13.0'
|
97
|
+
description: A Ruby implementation of the Swarm library for managing AI agent interactions
|
98
|
+
email: landon.gray@hey.com
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- Readme.md
|
104
|
+
- lib/swarm.rb
|
105
|
+
- lib/swarm/core.rb
|
106
|
+
- lib/swarm/repl.rb
|
107
|
+
- lib/swarm/types.rb
|
108
|
+
- lib/swarm/util.rb
|
109
|
+
homepage: https://rubygems.org/gems/swarm-rb
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubygems_version: 3.3.7
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: 'Swarm-rb: A Ruby gem for AI agent interactions'
|
132
|
+
test_files: []
|