ruby-openai-swarm 0.3.0 → 0.3.1

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