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 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