ruby-openai-swarm 0.3.0 → 0.3.1

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: 161226570a77888c84129d825b862ab7868f1d9f54b233eee7ab13950427678e
4
- data.tar.gz: e976c2a6bb9dae417b53fdc8f3f8216d3346a295b7e18838d89d24faff5612ce
3
+ metadata.gz: 984c58d324ae34f1a8eec694c3bca07aaf44420ce9ce322ceffbbc8c722e0ddd
4
+ data.tar.gz: 4ca2dd01658cbf5091aee65e41951fc64d5f16a72ea93c9dafe4a3802d38cad2
5
5
  SHA512:
6
- metadata.gz: 65cb09238a9e1ee5fb57d2cd9e73b56d0259bafc3dc19290e449a5db75fefce0d0cf92e65a946f525c5e6571db70e813edaa11a26c436d2a3c28c3baa88bff9f
7
- data.tar.gz: d90997ebfbbf64a4d9a815510db0653e07f73f2c7b53e5d5837bcfc760589ecabeca2aba87a799b92f67c1c7c82baa3cd64c44445390e6896bae16d8a5aa67e9
6
+ metadata.gz: 5a6357efdc1963cfd240fa23aaf3211b89cea7c39e04b4101a4ff2f25e48eb6810cc281a29977fcf5cc2ef33c23ffde4667334cc34374408fff8791c656273b4
7
+ data.tar.gz: b241abf134b927a950e50bdd4f2bfb06a6217a5ac5b9ea5c80d306bfcce2dd24e7c7b6f1ff85c8fd6700ac3a10080cccd13354f2f15afa6533d466d46014c7a7
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-openai-swarm (0.3.0)
4
+ ruby-openai-swarm (0.3.1)
5
5
  ruby-openai (~> 7.3)
6
6
 
7
7
  GEM
@@ -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)
@@ -6,6 +6,7 @@ module OpenAISwarm
6
6
  :strategy,
7
7
  :noisy_tool_calls,
8
8
  :temperature,
9
+ :current_tool_name,
9
10
  :resource
10
11
  # These attributes can be read and written externally. They include:
11
12
  # - name: The name of the agent.
@@ -25,7 +26,8 @@ module OpenAISwarm
25
26
  parallel_tool_calls: true,
26
27
  resource: nil,
27
28
  noisy_tool_calls: [],
28
- strategy: {}
29
+ strategy: {},
30
+ current_tool_name: nil
29
31
  )
30
32
  @name = name
31
33
  @model = model
@@ -36,7 +38,8 @@ module OpenAISwarm
36
38
  @parallel_tool_calls = parallel_tool_calls
37
39
  @resource = resource
38
40
  @noisy_tool_calls = noisy_tool_calls
39
- @strategy = AgentStrategyOptions.new(strategy)
41
+ @strategy = Agents::StrategyOptions.new(strategy)
42
+ @current_tool_name = current_tool_name.nil? ? nil : current_tool_name.to_s
40
43
  end
41
44
  end
42
45
  end
@@ -0,0 +1,32 @@
1
+ module OpenAISwarm
2
+ module Agents
3
+ class ChangeTracker
4
+ attr_reader :current_agent, :previous_agent, :tracking_agents_tool_name
5
+
6
+ def initialize(agent)
7
+ @tracking_agents_tool_name = []
8
+ add_tracking_agents_tool_name(agent.current_tool_name)
9
+ update(agent)
10
+ end
11
+
12
+ def update(agent)
13
+ @previous_agent = @current_agent
14
+ @current_agent = agent
15
+ end
16
+
17
+ def add_tracking_agents_tool_name(tool_name)
18
+ return if tool_name.nil?
19
+
20
+ @tracking_agents_tool_name << tool_name
21
+ end
22
+
23
+ def agent_changed?
24
+ previous_agent&.name != current_agent&.name
25
+ end
26
+
27
+ def switch_agent_reset_message?
28
+ agent_changed? && current_agent.strategy.switch_agent_reset_message
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ module OpenAISwarm
2
+ module Agents
3
+ class StrategyOptions
4
+ attr_accessor :switch_agent_reset_message,
5
+ :prevent_agent_reentry
6
+
7
+ def initialize(strategy = {})
8
+ @switch_agent_reset_message = strategy[:switch_agent_reset_message] || false
9
+ # INFO:
10
+ # 1. When `prevent_agent_reentry` is false, LLM is used to control the agent's jump.
11
+ # - In this case, there is a possibility of an infinite loop, so additional mechanisms (e.g., jump count limit) are needed to avoid it.
12
+ # 2. When `prevent_agent_reentry` is true, it prevents the agent from being called again if it has already been called.
13
+ # - In this case, if an agent has already been called, it will not be called again.
14
+ @prevent_agent_reentry = strategy[:prevent_agent_reentry] || false
15
+ end
16
+ end
17
+ end
18
+ end
@@ -30,12 +30,12 @@ module OpenAISwarm
30
30
  params[:required]&.delete(CTX_VARS_NAME.to_sym)
31
31
  end
32
32
 
33
- cleaned_messages = OpenAISwarm::Util.clean_message_tools(messages, agent.noisy_tool_calls)
33
+ cleaned_messages = Util.clean_message_tools(messages, agent.noisy_tool_calls)
34
34
 
35
35
  create_params = {
36
36
  model: model_override || agent.model,
37
37
  messages: cleaned_messages,
38
- tools: tools.empty? ? nil : tools,
38
+ tools: Util.request_tools_excluded(tools, agent_tracker.tracking_agents_tool_name, agent.strategy.prevent_agent_reentry),
39
39
  }
40
40
 
41
41
  # TODO: https://platform.openai.com/docs/guides/function-calling/how-do-functions-differ-from-tools
@@ -148,7 +148,7 @@ module OpenAISwarm
148
148
  end
149
149
 
150
150
  def run(agent:, messages:, context_variables: {}, model_override: nil, stream: false, debug: false, max_turns: Float::INFINITY, execute_tools: true)
151
- agent_tracker = OpenAISwarm::AgentChangeTracker.new(agent)
151
+ agent_tracker = OpenAISwarm::Agents::ChangeTracker.new(agent)
152
152
  if stream
153
153
  return run_and_stream(
154
154
  agent: agent,
@@ -168,7 +168,7 @@ module OpenAISwarm
168
168
 
169
169
  while history.length - init_len < max_turns && active_agent
170
170
  agent_tracker.update(active_agent)
171
- history = [history.first] if agent_tracker.switch_agent_reset_message?
171
+ history = OpenAISwarm::Util.latest_role_user_message(history) if agent_tracker.switch_agent_reset_message?
172
172
 
173
173
  completion = get_chat_completion(
174
174
  agent_tracker,
@@ -198,6 +198,11 @@ module OpenAISwarm
198
198
  debug
199
199
  )
200
200
 
201
+ if partial_response.agent
202
+ agent_tool_name = message['tool_calls'].dig(0, 'function', 'name')
203
+ agent_tracker.add_tracking_agents_tool_name(agent_tool_name)
204
+ end
205
+
201
206
  history.concat(partial_response.messages)
202
207
  context_variables.merge!(partial_response.context_variables)
203
208
  active_agent = partial_response.agent if partial_response.agent
@@ -212,7 +217,7 @@ module OpenAISwarm
212
217
 
213
218
  # TODO(Grayson): a lot of copied code here that will be refactored
214
219
  def run_and_stream(agent:, messages:, context_variables: {}, model_override: nil, debug: false, max_turns: Float::INFINITY, execute_tools: true)
215
- agent_tracker = OpenAISwarm::AgentChangeTracker.new(agent)
220
+ agent_tracker = OpenAISwarm::Agents::ChangeTracker.new(agent)
216
221
  active_agent = agent
217
222
  context_variables = context_variables.dup
218
223
  history = messages.dup
@@ -220,7 +225,7 @@ module OpenAISwarm
220
225
 
221
226
  while history.length - init_len < max_turns && active_agent
222
227
  agent_tracker.update(active_agent)
223
- history = [history.first] if agent_tracker.switch_agent_reset_message?
228
+ history = OpenAISwarm::Util.latest_role_user_message(history) if agent_tracker.switch_agent_reset_message?
224
229
 
225
230
  message = OpenAISwarm::Util.message_template(agent.name)
226
231
  completion = get_chat_completion(
@@ -290,6 +295,11 @@ module OpenAISwarm
290
295
  debug
291
296
  )
292
297
 
298
+ if partial_response.agent
299
+ agent_tool_name = message['tool_calls'].dig(0, 'function', 'name')
300
+ agent_tracker.add_tracking_agents_tool_name(agent_tool_name)
301
+ end
302
+
293
303
  history.concat(partial_response.messages)
294
304
  context_variables.merge!(partial_response.context_variables)
295
305
  active_agent = partial_response.agent if partial_response.agent
@@ -1,5 +1,23 @@
1
1
  module OpenAISwarm
2
2
  module Util
3
+ class << self
4
+ def latest_role_user_message(history)
5
+ return history if history.empty?
6
+ filtered_messages = symbolize_keys_to_string(history.dup)
7
+ last_user_message = filtered_messages.reverse.find { |msg| msg['role'] == 'user' }
8
+ last_user_message ? [last_user_message] : history
9
+ end
10
+
11
+ def request_tools_excluded(tools, tool_names, prevent_agent_reentry = false)
12
+ return nil if tools.empty?
13
+ return tools if tool_names.empty? || !prevent_agent_reentry
14
+
15
+ symbolize_keys_to_string(tools).reject do |tool|
16
+ tool_names.include?("#{tool['function']['name']}")
17
+ end
18
+ end
19
+ end
20
+
3
21
  def self.debug_print(debug, *args)
4
22
  return unless debug
5
23
  timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
@@ -1,3 +1,3 @@
1
1
  module OpenAISwarm
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -1,8 +1,8 @@
1
1
  require 'ruby-openai-swarm/version'
2
2
  require 'ruby-openai-swarm/core_ext'
3
3
  require 'ruby-openai-swarm/agent'
4
- require 'ruby-openai-swarm/agent_change_tracker'
5
- require 'ruby-openai-swarm/agent_strategy_options'
4
+ require 'ruby-openai-swarm/agents/change_tracker'
5
+ require 'ruby-openai-swarm/agents/strategy_options'
6
6
  require 'ruby-openai-swarm/response'
7
7
  require 'ruby-openai-swarm/result'
8
8
  require 'ruby-openai-swarm/util'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-openai-swarm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Grayson
@@ -106,8 +106,8 @@ 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/agent_change_tracker.rb
110
- - lib/ruby-openai-swarm/agent_strategy_options.rb
109
+ - lib/ruby-openai-swarm/agents/change_tracker.rb
110
+ - lib/ruby-openai-swarm/agents/strategy_options.rb
111
111
  - lib/ruby-openai-swarm/configuration.rb
112
112
  - lib/ruby-openai-swarm/core.rb
113
113
  - lib/ruby-openai-swarm/core_ext.rb
@@ -1,22 +0,0 @@
1
- module OpenAISwarm
2
- class AgentChangeTracker
3
- attr_reader :current_agent, :previous_agent
4
-
5
- def initialize(agent)
6
- update(agent)
7
- end
8
-
9
- def update(agent)
10
- @previous_agent = @current_agent
11
- @current_agent = agent
12
- end
13
-
14
- def agent_changed?
15
- previous_agent&.name != current_agent&.name
16
- end
17
-
18
- def switch_agent_reset_message?
19
- agent_changed? && current_agent.strategy.switch_agent_reset_message
20
- end
21
- end
22
- end
@@ -1,9 +0,0 @@
1
- module OpenAISwarm
2
- class AgentStrategyOptions
3
- attr_accessor :switch_agent_reset_message
4
-
5
- def initialize(strategy = {})
6
- @switch_agent_reset_message = strategy[:switch_agent_reset_message] || false
7
- end
8
- end
9
- end