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 +4 -4
- data/Gemfile.lock +1 -1
- data/examples/airline/main.rb +1 -1
- data/examples/basic/function_calling.rb +1 -17
- data/lib/ruby-openai-swarm/agent.rb +5 -2
- data/lib/ruby-openai-swarm/agents/change_tracker.rb +32 -0
- data/lib/ruby-openai-swarm/agents/strategy_options.rb +18 -0
- data/lib/ruby-openai-swarm/core.rb +16 -6
- data/lib/ruby-openai-swarm/util.rb +18 -0
- data/lib/ruby-openai-swarm/version.rb +1 -1
- data/lib/ruby-openai-swarm.rb +2 -2
- metadata +3 -3
- data/lib/ruby-openai-swarm/agent_change_tracker.rb +0 -22
- data/lib/ruby-openai-swarm/agent_strategy_options.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 984c58d324ae34f1a8eec694c3bca07aaf44420ce9ce322ceffbbc8c722e0ddd
|
4
|
+
data.tar.gz: 4ca2dd01658cbf5091aee65e41951fc64d5f16a72ea93c9dafe4a3802d38cad2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a6357efdc1963cfd240fa23aaf3211b89cea7c39e04b4101a4ff2f25e48eb6810cc281a29977fcf5cc2ef33c23ffde4667334cc34374408fff8791c656273b4
|
7
|
+
data.tar.gz: b241abf134b927a950e50bdd4f2bfb06a6217a5ac5b9ea5c80d306bfcce2dd24e7c7b6f1ff85c8fd6700ac3a10080cccd13354f2f15afa6533d466d46014c7a7
|
data/Gemfile.lock
CHANGED
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)
|
@@ -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 =
|
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 =
|
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.
|
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::
|
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 =
|
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::
|
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 =
|
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")
|
data/lib/ruby-openai-swarm.rb
CHANGED
@@ -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/
|
5
|
-
require 'ruby-openai-swarm/
|
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.
|
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/
|
110
|
-
- lib/ruby-openai-swarm/
|
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
|