ruby-openai-swarm 0.2.6 → 0.2.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +13 -0
- data/examples/airline/main.rb +1 -1
- data/examples/basic/function_calling.rb +1 -17
- data/lib/ruby-openai-swarm/configuration.rb +24 -0
- data/lib/ruby-openai-swarm/core.rb +40 -6
- data/lib/ruby-openai-swarm/logger.rb +58 -0
- data/lib/ruby-openai-swarm/version.rb +1 -1
- data/lib/ruby-openai-swarm.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb75077c1cd68a61448103423fec5401c54963c3341b9c7a11aef564584375d7
|
4
|
+
data.tar.gz: 6950967689f250591671095dd296a0e266d3e218b41e618811a3a0c8c8c48ee2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0caeb42cc055869a3b9bd830e3d52cb63add1a07af767cd1e42abe8b5fb7d98565327af96a1bd36ff72616d775f3be72066eeb9298da37762afe1461ff0cd345
|
7
|
+
data.tar.gz: 0d515f09b04a1229e08ee1957e5958805a512f0fd9a7bb79ddf43c0962d868b9415f27714802468be1450d6d55130df7295f4ee3ed791c6c0230e09d2fa149ac
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -15,6 +15,7 @@ A Ruby-based educational framework adapted from OpenAI’s [Swarm](https://githu
|
|
15
15
|
- [Installation](#installation)
|
16
16
|
- [Bundler](#bundler)
|
17
17
|
- [Gem install](#gem-install)
|
18
|
+
- [Logger](#logger)
|
18
19
|
- [examples](#examples)
|
19
20
|
- [Documentation](#documentation)
|
20
21
|
|
@@ -114,6 +115,18 @@ pp response.messages.last
|
|
114
115
|
:sender=>"Spanish Agent"}
|
115
116
|
```
|
116
117
|
|
118
|
+
### Logger
|
119
|
+
|
120
|
+
```
|
121
|
+
OpenAISwarm.configure do |config|
|
122
|
+
# config.logger = Logger.new(STDOUT)
|
123
|
+
# config.logger = Rails.logger
|
124
|
+
config.log_file = Rails.root.join('log', 'openai_swarm.log')
|
125
|
+
# config.logger = Logger.new(Rails.root.join('log', 'openai_swarm.log'))
|
126
|
+
# config.logger = Rails.configuration.lograge.logger
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
117
130
|
# Examples
|
118
131
|
|
119
132
|
Setting ACCESS_TOKEN for AI Providers in examples
|
data/examples/airline/main.rb
CHANGED
@@ -65,4 +65,4 @@ params:
|
|
65
65
|
GUIDE_EXAMPLES
|
66
66
|
puts guide_examples
|
67
67
|
|
68
|
-
OpenAISwarm::Repl.run_demo_loop(triage_agent, context_variables: context_variables,
|
68
|
+
OpenAISwarm::Repl.run_demo_loop(triage_agent, context_variables: context_variables, debug: env_debug)
|
@@ -46,7 +46,6 @@ Details:
|
|
46
46
|
Example: “What’s the weather in NYC?”
|
47
47
|
Action: Calls get_weather with location “New York City”.
|
48
48
|
Response: Only provides weather details.
|
49
|
-
|
50
49
|
2. Multiple Function Calls
|
51
50
|
Example: “Tell me the weather in New York and the latest news headlines.”
|
52
51
|
Action: Calls get_weather for weather and get_news for news.
|
@@ -59,19 +58,4 @@ GUIDE_EXAMPLES
|
|
59
58
|
|
60
59
|
puts guide_examples
|
61
60
|
|
62
|
-
|
63
|
-
|
64
|
-
OpenAISwarm.new.run_and_stream(
|
65
|
-
agent: agent,
|
66
|
-
debug: true,
|
67
|
-
messages: [{"role" => "user", "content" => "Tell me the weather in New York and the latest news headlines."}]
|
68
|
-
) do |chunk|
|
69
|
-
if chunk.key?("content") && !chunk["content"].nil?
|
70
|
-
puts ">>>#{chunk}"
|
71
|
-
end
|
72
|
-
|
73
|
-
if chunk.key?('response')
|
74
|
-
binding.pry
|
75
|
-
# log_llm_request(chunk['response'])
|
76
|
-
end
|
77
|
-
end
|
61
|
+
OpenAISwarm::Repl.run_demo_loop(agent, stream: true, debug: env_debug)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module OpenAISwarm
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :logger, :log_file
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@log_file = nil
|
7
|
+
@logger = nil
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def configuration
|
13
|
+
@configuration ||= Configuration.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def configure
|
17
|
+
yield(configuration) if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
def reset_configuration!
|
21
|
+
@configuration = Configuration.new
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -11,13 +11,15 @@ module OpenAISwarm
|
|
11
11
|
|
12
12
|
def initialize(client = nil)
|
13
13
|
@client = client || OpenAI::Client.new
|
14
|
+
@logger = OpenAISwarm::Logger.instance.logger
|
14
15
|
end
|
15
16
|
|
16
17
|
def get_chat_completion(agent, history, context_variables, model_override, stream, debug)
|
17
18
|
context_variables = context_variables.dup
|
18
19
|
instructions = agent.instructions.respond_to?(:call) ? agent.instructions.call(context_variables) : agent.instructions
|
19
20
|
messages = [{ role: 'system', content: instructions }] + history
|
20
|
-
|
21
|
+
|
22
|
+
# Util.debug_print(debug, "Getting chat completion for...:", messages)
|
21
23
|
|
22
24
|
tools = agent.functions.map { |f| Util.function_to_json(f) }
|
23
25
|
# hide context_variables from model
|
@@ -40,7 +42,9 @@ module OpenAISwarm
|
|
40
42
|
create_params[:tool_choice] = agent.tool_choice if agent.tool_choice
|
41
43
|
create_params[:parallel_tool_calls] = agent.parallel_tool_calls if tools.any?
|
42
44
|
|
43
|
-
Util.debug_print(debug, "
|
45
|
+
Util.debug_print(debug, "Getting chat completion for...:", create_params)
|
46
|
+
log_message(:info, "Getting chat completion for...:", create_params)
|
47
|
+
|
44
48
|
if stream
|
45
49
|
return Enumerator.new do |yielder|
|
46
50
|
@client.chat(parameters: create_params.merge(
|
@@ -56,6 +60,7 @@ module OpenAISwarm
|
|
56
60
|
Util.debug_print(debug, "API Response:", response)
|
57
61
|
response
|
58
62
|
rescue OpenAI::Error => e
|
63
|
+
log_message(:error, "OpenAI API Error: #{e.message}")
|
59
64
|
Util.debug_print(true, "OpenAI API Error:", e.message)
|
60
65
|
raise
|
61
66
|
end
|
@@ -101,6 +106,7 @@ module OpenAISwarm
|
|
101
106
|
name = tool_call.dig('function', 'name')
|
102
107
|
unless function_map.key?(name)
|
103
108
|
Util.debug_print(debug, "Tool #{name} not found in function map.")
|
109
|
+
log_message(:error, "Tool #{name} not found in function map.")
|
104
110
|
partial_response.messages << {
|
105
111
|
'role' => 'tool',
|
106
112
|
'tool_call_id' => tool_call['id'],
|
@@ -112,6 +118,7 @@ module OpenAISwarm
|
|
112
118
|
|
113
119
|
args = JSON.parse(tool_call.dig('function', 'arguments') || '{}')
|
114
120
|
Util.debug_print(debug, "Processing tool call: #{name} with arguments #{args}")
|
121
|
+
log_message(:info, "Processing tool call: #{name} with arguments #{args}")
|
115
122
|
|
116
123
|
func = function_map[name]
|
117
124
|
# pass context_variables to agent functions
|
@@ -166,11 +173,15 @@ module OpenAISwarm
|
|
166
173
|
|
167
174
|
message = completion.dig('choices', 0, 'message')
|
168
175
|
Util.debug_print(debug, "Received completion:", message)
|
176
|
+
log_message(:info, "Received completion:", message)
|
169
177
|
|
170
178
|
message['sender'] = active_agent.name
|
171
179
|
history << message
|
172
180
|
|
173
|
-
|
181
|
+
if !message['tool_calls'] || !execute_tools
|
182
|
+
log_message(:info, "Ending turn.")
|
183
|
+
break
|
184
|
+
end
|
174
185
|
|
175
186
|
partial_response = handle_tool_calls(
|
176
187
|
message['tool_calls'],
|
@@ -191,8 +202,6 @@ module OpenAISwarm
|
|
191
202
|
)
|
192
203
|
end
|
193
204
|
|
194
|
-
# private
|
195
|
-
|
196
205
|
def run_and_stream(agent:, messages:, context_variables: {}, model_override: nil, debug: false, max_turns: Float::INFINITY, execute_tools: true)
|
197
206
|
active_agent = agent
|
198
207
|
context_variables = context_variables.dup
|
@@ -239,9 +248,15 @@ module OpenAISwarm
|
|
239
248
|
message['tool_calls'] = message['tool_calls'].values
|
240
249
|
message['tool_calls'] = nil if message['tool_calls'].empty?
|
241
250
|
Util.debug_print(debug, "Received completion:", message)
|
251
|
+
log_message(:info, "Received completion:", message)
|
252
|
+
|
242
253
|
history << message
|
243
254
|
|
244
|
-
|
255
|
+
|
256
|
+
if !message['tool_calls'] || !execute_tools
|
257
|
+
log_message(:info, "Ending turn.")
|
258
|
+
break
|
259
|
+
end
|
245
260
|
|
246
261
|
# convert tool_calls to objects
|
247
262
|
tool_calls = message['tool_calls'].map do |tool_call|
|
@@ -265,6 +280,14 @@ module OpenAISwarm
|
|
265
280
|
history.concat(partial_response.messages)
|
266
281
|
context_variables.merge!(partial_response.context_variables)
|
267
282
|
active_agent = partial_response.agent if partial_response.agent
|
283
|
+
|
284
|
+
tool_call_messages = (Array.wrap(message) + partial_response.messages)
|
285
|
+
yield(
|
286
|
+
'tool_call_messages' => Response.new(
|
287
|
+
messages: tool_call_messages,
|
288
|
+
agent: active_agent,
|
289
|
+
context_variables: context_variables)
|
290
|
+
) if block_given?
|
268
291
|
end
|
269
292
|
|
270
293
|
yield(
|
@@ -273,5 +296,16 @@ module OpenAISwarm
|
|
273
296
|
context_variables: context_variables)
|
274
297
|
) if block_given?
|
275
298
|
end
|
299
|
+
|
300
|
+
private
|
301
|
+
|
302
|
+
def log_message(level, message, data = nil)
|
303
|
+
return unless @logger
|
304
|
+
|
305
|
+
log_text = message
|
306
|
+
log_text += "\n#{data.inspect}" if data
|
307
|
+
|
308
|
+
@logger.send(level, log_text)
|
309
|
+
end
|
276
310
|
end
|
277
311
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module OpenAISwarm
|
5
|
+
class Logger
|
6
|
+
SEVERITY_COLORS = {
|
7
|
+
'DEBUG' => "\e[36m", # Cyan
|
8
|
+
'INFO' => "\e[32m", # Green
|
9
|
+
'WARN' => "\e[33m", # Yellow
|
10
|
+
'ERROR' => "\e[31m", # Red
|
11
|
+
'FATAL' => "\e[35m", # Purple
|
12
|
+
'ANY' => "\e[0m" # Reset color
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def self.instance
|
16
|
+
@instance ||= new
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@loggers = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def logger(log_path = nil)
|
24
|
+
return OpenAISwarm.configuration.logger if OpenAISwarm.configuration.logger
|
25
|
+
return @loggers[log_path] if @loggers[log_path]
|
26
|
+
|
27
|
+
path = determine_log_path(log_path)
|
28
|
+
ensure_log_directory(path)
|
29
|
+
|
30
|
+
logger = ::Logger.new(path)
|
31
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
32
|
+
color = SEVERITY_COLORS[severity] || SEVERITY_COLORS['ANY']
|
33
|
+
reset = SEVERITY_COLORS['ANY']
|
34
|
+
"[#{datetime}] #{color}#{severity}#{reset} OpenAISwarm: #{msg}\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
@loggers[log_path] = logger
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def determine_log_path(log_path)
|
43
|
+
return log_path if log_path
|
44
|
+
return OpenAISwarm.configuration.log_file if OpenAISwarm.configuration.log_file
|
45
|
+
|
46
|
+
if defined?(Rails)
|
47
|
+
Rails.root.join('log', "#{Rails.env}.log").to_s
|
48
|
+
else
|
49
|
+
'log/openai_swarm.log'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def ensure_log_directory(path)
|
54
|
+
dir = File.dirname(path)
|
55
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/ruby-openai-swarm.rb
CHANGED
@@ -6,6 +6,8 @@ require 'ruby-openai-swarm/util'
|
|
6
6
|
require 'ruby-openai-swarm/core'
|
7
7
|
require 'ruby-openai-swarm/function_descriptor'
|
8
8
|
require 'ruby-openai-swarm/repl'
|
9
|
+
require 'ruby-openai-swarm/configuration'
|
10
|
+
require 'ruby-openai-swarm/logger'
|
9
11
|
|
10
12
|
module OpenAISwarm
|
11
13
|
class Error < StandardError;
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-openai-swarm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Grayson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-openai
|
@@ -106,8 +106,10 @@ files:
|
|
106
106
|
- examples/weather_agent/run.rb
|
107
107
|
- lib/ruby-openai-swarm.rb
|
108
108
|
- lib/ruby-openai-swarm/agent.rb
|
109
|
+
- lib/ruby-openai-swarm/configuration.rb
|
109
110
|
- lib/ruby-openai-swarm/core.rb
|
110
111
|
- lib/ruby-openai-swarm/function_descriptor.rb
|
112
|
+
- lib/ruby-openai-swarm/logger.rb
|
111
113
|
- lib/ruby-openai-swarm/repl.rb
|
112
114
|
- lib/ruby-openai-swarm/response.rb
|
113
115
|
- lib/ruby-openai-swarm/result.rb
|