ruby-openai-swarm 0.2.8 → 0.3.0
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/.gitignore +1 -0
- data/Gemfile.lock +1 -1
- data/examples/airline/main.rb +1 -1
- data/examples/basic/function_calling.rb +17 -1
- data/lib/ruby-openai-swarm/agent.rb +13 -1
- data/lib/ruby-openai-swarm/agent_change_tracker.rb +22 -0
- data/lib/ruby-openai-swarm/agent_strategy_options.rb +9 -0
- data/lib/ruby-openai-swarm/core.rb +17 -4
- data/lib/ruby-openai-swarm/core_ext.rb +16 -0
- data/lib/ruby-openai-swarm/util.rb +44 -0
- data/lib/ruby-openai-swarm/version.rb +1 -1
- data/lib/ruby-openai-swarm.rb +3 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 161226570a77888c84129d825b862ab7868f1d9f54b233eee7ab13950427678e
|
4
|
+
data.tar.gz: e976c2a6bb9dae417b53fdc8f3f8216d3346a295b7e18838d89d24faff5612ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65cb09238a9e1ee5fb57d2cd9e73b56d0259bafc3dc19290e449a5db75fefce0d0cf92e65a946f525c5e6571db70e813edaa11a26c436d2a3c28c3baa88bff9f
|
7
|
+
data.tar.gz: d90997ebfbbf64a4d9a815510db0653e07f73f2c7b53e5d5837bcfc760589ecabeca2aba87a799b92f67c1c7c82baa3cd64c44445390e6896bae16d8a5aa67e9
|
data/.gitignore
CHANGED
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, debug: env_debug)
|
68
|
+
OpenAISwarm::Repl.run_demo_loop(triage_agent, context_variables: context_variables, stream: true, debug: env_debug)
|
@@ -46,6 +46,7 @@ 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
|
+
|
49
50
|
2. Multiple Function Calls
|
50
51
|
Example: “Tell me the weather in New York and the latest news headlines.”
|
51
52
|
Action: Calls get_weather for weather and get_news for news.
|
@@ -58,4 +59,19 @@ GUIDE_EXAMPLES
|
|
58
59
|
|
59
60
|
puts guide_examples
|
60
61
|
|
61
|
-
OpenAISwarm::Repl.run_demo_loop(agent, stream: true, debug: env_debug)
|
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
|
@@ -3,11 +3,17 @@ module OpenAISwarm
|
|
3
3
|
attr_accessor :name, :model, :instructions,
|
4
4
|
:functions, :tool_choice,
|
5
5
|
:parallel_tool_calls,
|
6
|
+
:strategy,
|
7
|
+
:noisy_tool_calls,
|
8
|
+
:temperature,
|
6
9
|
:resource
|
7
10
|
# These attributes can be read and written externally. They include:
|
8
11
|
# - name: The name of the agent.
|
9
12
|
# - model: The model used, e.g., "gpt-4".
|
10
13
|
# - resource: Additional custom parameters or data that the agent might need.
|
14
|
+
# - noisy_tool_calls: is an array that contains the names of tool calls that should be excluded because they are considered "noise".
|
15
|
+
# These tool calls generate irrelevant or unnecessary messages that the agent should not send to OpenAI.
|
16
|
+
# When filtering messages, any message that includes these tool calls will be removed from the list, preventing them from being sent to OpenAI.
|
11
17
|
|
12
18
|
def initialize(
|
13
19
|
name: "Agent",
|
@@ -15,16 +21,22 @@ module OpenAISwarm
|
|
15
21
|
instructions: "You are a helpful agent.",
|
16
22
|
functions: [],
|
17
23
|
tool_choice: nil,
|
24
|
+
temperature: nil,
|
18
25
|
parallel_tool_calls: true,
|
19
|
-
resource: nil
|
26
|
+
resource: nil,
|
27
|
+
noisy_tool_calls: [],
|
28
|
+
strategy: {}
|
20
29
|
)
|
21
30
|
@name = name
|
22
31
|
@model = model
|
23
32
|
@instructions = instructions
|
24
33
|
@functions = functions
|
25
34
|
@tool_choice = tool_choice
|
35
|
+
@temperature = temperature
|
26
36
|
@parallel_tool_calls = parallel_tool_calls
|
27
37
|
@resource = resource
|
38
|
+
@noisy_tool_calls = noisy_tool_calls
|
39
|
+
@strategy = AgentStrategyOptions.new(strategy)
|
28
40
|
end
|
29
41
|
end
|
30
42
|
end
|
@@ -0,0 +1,22 @@
|
|
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
|
@@ -14,7 +14,8 @@ module OpenAISwarm
|
|
14
14
|
@logger = OpenAISwarm::Logger.instance.logger
|
15
15
|
end
|
16
16
|
|
17
|
-
def get_chat_completion(
|
17
|
+
def get_chat_completion(agent_tracker, history, context_variables, model_override, stream, debug)
|
18
|
+
agent = agent_tracker.current_agent
|
18
19
|
context_variables = context_variables.dup
|
19
20
|
instructions = agent.instructions.respond_to?(:call) ? agent.instructions.call(context_variables) : agent.instructions
|
20
21
|
messages = [{ role: 'system', content: instructions }] + history
|
@@ -29,9 +30,11 @@ module OpenAISwarm
|
|
29
30
|
params[:required]&.delete(CTX_VARS_NAME.to_sym)
|
30
31
|
end
|
31
32
|
|
33
|
+
cleaned_messages = OpenAISwarm::Util.clean_message_tools(messages, agent.noisy_tool_calls)
|
34
|
+
|
32
35
|
create_params = {
|
33
36
|
model: model_override || agent.model,
|
34
|
-
messages:
|
37
|
+
messages: cleaned_messages,
|
35
38
|
tools: tools.empty? ? nil : tools,
|
36
39
|
}
|
37
40
|
|
@@ -39,6 +42,7 @@ module OpenAISwarm
|
|
39
42
|
# create_params[:functions] = tools unless tools.empty?
|
40
43
|
# create_params[:function_call] = agent.tool_choice if agent.tool_choice
|
41
44
|
|
45
|
+
create_params[:temperature] = agent.temperature if agent.temperature
|
42
46
|
create_params[:tool_choice] = agent.tool_choice if agent.tool_choice
|
43
47
|
create_params[:parallel_tool_calls] = agent.parallel_tool_calls if tools.any?
|
44
48
|
|
@@ -144,6 +148,7 @@ module OpenAISwarm
|
|
144
148
|
end
|
145
149
|
|
146
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)
|
147
152
|
if stream
|
148
153
|
return run_and_stream(
|
149
154
|
agent: agent,
|
@@ -162,8 +167,11 @@ module OpenAISwarm
|
|
162
167
|
init_len = messages.length
|
163
168
|
|
164
169
|
while history.length - init_len < max_turns && active_agent
|
170
|
+
agent_tracker.update(active_agent)
|
171
|
+
history = [history.first] if agent_tracker.switch_agent_reset_message?
|
172
|
+
|
165
173
|
completion = get_chat_completion(
|
166
|
-
|
174
|
+
agent_tracker,
|
167
175
|
history,
|
168
176
|
context_variables,
|
169
177
|
model_override,
|
@@ -202,16 +210,21 @@ module OpenAISwarm
|
|
202
210
|
)
|
203
211
|
end
|
204
212
|
|
213
|
+
# TODO(Grayson): a lot of copied code here that will be refactored
|
205
214
|
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)
|
206
216
|
active_agent = agent
|
207
217
|
context_variables = context_variables.dup
|
208
218
|
history = messages.dup
|
209
219
|
init_len = messages.length
|
210
220
|
|
211
221
|
while history.length - init_len < max_turns && active_agent
|
222
|
+
agent_tracker.update(active_agent)
|
223
|
+
history = [history.first] if agent_tracker.switch_agent_reset_message?
|
224
|
+
|
212
225
|
message = OpenAISwarm::Util.message_template(agent.name)
|
213
226
|
completion = get_chat_completion(
|
214
|
-
|
227
|
+
agent_tracker,
|
215
228
|
history,
|
216
229
|
context_variables,
|
217
230
|
model_override,
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Backport of Array.wrap for Ruby versions prior to 3.0
|
2
|
+
# This provides a consistent way to wrap objects as arrays across different Ruby versions
|
3
|
+
# link: https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/array/wrap.rb
|
4
|
+
unless Array.respond_to?(:wrap)
|
5
|
+
class Array
|
6
|
+
def self.wrap(object)
|
7
|
+
if object.nil?
|
8
|
+
[]
|
9
|
+
elsif object.respond_to?(:to_ary)
|
10
|
+
object.to_ary || [object]
|
11
|
+
else
|
12
|
+
[object]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -7,6 +7,50 @@ module OpenAISwarm
|
|
7
7
|
puts "\e[97m[\e[90m#{timestamp}\e[97m]\e[90m #{message}\e[0m"
|
8
8
|
end
|
9
9
|
|
10
|
+
def self.symbolize_keys_to_string(obj)
|
11
|
+
case obj
|
12
|
+
when Hash
|
13
|
+
obj.transform_keys(&:to_s).transform_values { |v| symbolize_keys_to_string(v) }
|
14
|
+
when Array
|
15
|
+
obj.map { |v| symbolize_keys_to_string(v) }
|
16
|
+
else
|
17
|
+
obj
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.clean_message_tools(messages, tool_names)
|
22
|
+
return messages if tool_names.empty?
|
23
|
+
filtered_messages = symbolize_keys_to_string(messages.dup)
|
24
|
+
# Collect tool call IDs to be removed
|
25
|
+
tool_call_ids_to_remove = filtered_messages
|
26
|
+
.select { |msg| msg['tool_calls'] }
|
27
|
+
.flat_map { |msg| msg['tool_calls'] }
|
28
|
+
.select { |tool_call| tool_names.include?(tool_call['function']['name']) }
|
29
|
+
.map { |tool_call| tool_call['id'] }
|
30
|
+
|
31
|
+
# Remove specific messages
|
32
|
+
filtered_messages.reject! do |msg|
|
33
|
+
# Remove tool call messages for specified tool names
|
34
|
+
(msg['role'] == 'assistant' &&
|
35
|
+
msg['tool_calls']&.all? { |tool_call| tool_names.include?(tool_call['function']['name']) }) ||
|
36
|
+
# Remove tool response messages for specified tool calls
|
37
|
+
(msg['role'] == 'tool' && tool_call_ids_to_remove.include?(msg['tool_call_id']))
|
38
|
+
end
|
39
|
+
|
40
|
+
# If assistant message's tool_calls becomes empty, modify that message
|
41
|
+
filtered_messages.map! do |msg|
|
42
|
+
if msg['role'] == 'assistant' && msg['tool_calls']
|
43
|
+
msg['tool_calls'].reject! { |tool_call| tool_names.include?(tool_call['function']['name']) }
|
44
|
+
msg['tool_calls'] = nil if msg['tool_calls'].empty?
|
45
|
+
msg
|
46
|
+
else
|
47
|
+
msg
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
filtered_messages
|
52
|
+
end
|
53
|
+
|
10
54
|
def self.message_template(agent_name)
|
11
55
|
{
|
12
56
|
"content" => "",
|
data/lib/ruby-openai-swarm.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'ruby-openai-swarm/version'
|
2
|
+
require 'ruby-openai-swarm/core_ext'
|
2
3
|
require 'ruby-openai-swarm/agent'
|
4
|
+
require 'ruby-openai-swarm/agent_change_tracker'
|
5
|
+
require 'ruby-openai-swarm/agent_strategy_options'
|
3
6
|
require 'ruby-openai-swarm/response'
|
4
7
|
require 'ruby-openai-swarm/result'
|
5
8
|
require 'ruby-openai-swarm/util'
|
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.
|
4
|
+
version: 0.3.0
|
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-
|
11
|
+
date: 2024-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-openai
|
@@ -106,8 +106,11 @@ 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
111
|
- lib/ruby-openai-swarm/configuration.rb
|
110
112
|
- lib/ruby-openai-swarm/core.rb
|
113
|
+
- lib/ruby-openai-swarm/core_ext.rb
|
111
114
|
- lib/ruby-openai-swarm/function_descriptor.rb
|
112
115
|
- lib/ruby-openai-swarm/logger.rb
|
113
116
|
- lib/ruby-openai-swarm/repl.rb
|