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