ruby-openai-swarm 0.2.6 → 0.2.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 143ab1b153e54393e27a5a2f02d77c94ecaa8ed477d6ecdc2f072730b63ffd1f
4
- data.tar.gz: fa72888457647eefc2fedc5c5d97dcd1ccf7b2e35f1395eebf0500dc4a14fb22
3
+ metadata.gz: bb75077c1cd68a61448103423fec5401c54963c3341b9c7a11aef564584375d7
4
+ data.tar.gz: 6950967689f250591671095dd296a0e266d3e218b41e618811a3a0c8c8c48ee2
5
5
  SHA512:
6
- metadata.gz: 6b6830bb7dc3cb4e0512e0c5e0967ba7160b1967b5a44136923108df60cbb76450da27df55cadc9357a8a6c86d093db27dfbfdcf0f839e8db1e12114606055d7
7
- data.tar.gz: f8f67a146f3e089973e333a9ae39d2310779fa89648b91459ebcfe83029b4b6328852085cfb4179ae6627e60d802ff974ddaa8bebeee2f638fedcd89f1020e2a
6
+ metadata.gz: 0caeb42cc055869a3b9bd830e3d52cb63add1a07af767cd1e42abe8b5fb7d98565327af96a1bd36ff72616d775f3be72066eeb9298da37762afe1461ff0cd345
7
+ data.tar.gz: 0d515f09b04a1229e08ee1957e5958805a512f0fd9a7bb79ddf43c0962d868b9415f27714802468be1450d6d55130df7295f4ee3ed791c6c0230e09d2fa149ac
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-openai-swarm (0.2.6)
4
+ ruby-openai-swarm (0.2.8)
5
5
  ruby-openai (~> 7.3)
6
6
 
7
7
  GEM
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
@@ -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, stream: true, debug: env_debug)
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
- # OpenAISwarm::Repl.run_demo_loop(agent, stream: true, debug: env_debug)
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
- Util.debug_print(debug, "Getting chat completion for...:", messages)
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, "Client chat parameters:", create_params)
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
- break if !message['tool_calls'] || !execute_tools
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
- break if !message['tool_calls'] || !execute_tools
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
@@ -1,3 +1,3 @@
1
1
  module OpenAISwarm
2
- VERSION = "0.2.6"
2
+ VERSION = "0.2.8"
3
3
  end
@@ -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.6
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-05 00:00:00.000000000 Z
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