ruby-openai-swarm 0.3.0.1 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98aa27742c5ae980d9c1c8b9457beb2e61a4ff3a1548790d4205cd0f68cef039
4
- data.tar.gz: 37d7a5280f47c2a454a2aba4a5f64587787f1e829fc07b6c27accc90f63f91f8
3
+ metadata.gz: 49bcf71e27c830e373b9732691667aa4ff8657dc944a8dc113fb4b13e807ccd6
4
+ data.tar.gz: 38cd26d3670e7a49652b8b52677a2a7bf722c9f5635bfef55bea296d0502c4d1
5
5
  SHA512:
6
- metadata.gz: 0d671ef9fa72e895bfd3b3e62561fccfc3894a6f19605dd43e68da14b0c9f958d22559da8d9eecd981277a7db035ed39c17535f261cf40849f16a95ac6884516
7
- data.tar.gz: 96c6229510613dbb8b858200916bb9ab22583b7918d604adae0b1bc85bb5b89cfae28a6c833b8c16b2acc1758d1d89643c61f240c896ff6d1d3153536801b46d
6
+ metadata.gz: 0f4b422f084d400ef6eea393f20a650fd02fa8b96370e4781c4228113650763c7eb84c6c1aac21daceb8251cfd83d69ec69b536dff16c3bd7be3e8b0f22a353a
7
+ data.tar.gz: f24c83ea576a7d305069eda9da308f0d39b257065897377b228c785049c41254d3889d4e82b96d40755151b801b95d3a6f8ae5550d73fb871c902c9b21755793
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-openai-swarm (0.3.0.1)
4
+ ruby-openai-swarm (0.4.0)
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
@@ -37,6 +39,7 @@ module OpenAISwarm
37
39
  @resource = resource
38
40
  @noisy_tool_calls = noisy_tool_calls
39
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
@@ -1,9 +1,11 @@
1
1
  module OpenAISwarm
2
2
  module Agents
3
3
  class ChangeTracker
4
- attr_reader :current_agent, :previous_agent
4
+ attr_reader :current_agent, :previous_agent, :tracking_agents_tool_name
5
5
 
6
6
  def initialize(agent)
7
+ @tracking_agents_tool_name = []
8
+ add_tracking_agents_tool_name(agent.current_tool_name)
7
9
  update(agent)
8
10
  end
9
11
 
@@ -12,6 +14,12 @@ module OpenAISwarm
12
14
  @current_agent = agent
13
15
  end
14
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
+
15
23
  def agent_changed?
16
24
  previous_agent&.name != current_agent&.name
17
25
  end
@@ -1,10 +1,17 @@
1
1
  module OpenAISwarm
2
2
  module Agents
3
3
  class StrategyOptions
4
- attr_accessor :switch_agent_reset_message
4
+ attr_accessor :switch_agent_reset_message,
5
+ :prevent_agent_reentry
5
6
 
6
7
  def initialize(strategy = {})
7
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
8
15
  end
9
16
  end
10
17
  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
@@ -51,9 +51,11 @@ module OpenAISwarm
51
51
 
52
52
  if stream
53
53
  return Enumerator.new do |yielder|
54
+ yielder << { 'parameters' => create_params }
55
+
54
56
  @client.chat(parameters: create_params.merge(
55
57
  stream: proc do |chunk, _bytesize|
56
- yielder << chunk
58
+ yielder << { chunk: chunk }
57
59
  end
58
60
  ))
59
61
  end
@@ -198,6 +200,11 @@ module OpenAISwarm
198
200
  debug
199
201
  )
200
202
 
203
+ if partial_response.agent
204
+ agent_tool_name = message['tool_calls'].dig(0, 'function', 'name')
205
+ agent_tracker.add_tracking_agents_tool_name(agent_tool_name)
206
+ end
207
+
201
208
  history.concat(partial_response.messages)
202
209
  context_variables.merge!(partial_response.context_variables)
203
210
  active_agent = partial_response.agent if partial_response.agent
@@ -233,7 +240,15 @@ module OpenAISwarm
233
240
  )
234
241
 
235
242
  yield({ delim: "start" }) if block_given?
236
- completion.each do |chunk|
243
+ completion.each do |stream|
244
+
245
+ # TODO(Grayson): will refactor it
246
+ if stream['parameters']
247
+ yield({ 'parameters' => stream['parameters'] }) if block_given?
248
+ end
249
+ next if stream.key?('parameters')
250
+
251
+ chunk = stream[:chunk]
237
252
  if chunk['error']
238
253
  details = {
239
254
  'response' =>
@@ -250,7 +265,7 @@ module OpenAISwarm
250
265
  delta['sender'] = active_agent.name
251
266
  end
252
267
 
253
- yield delta if block_given?
268
+ yield({ 'delta' => delta }) if block_given?
254
269
 
255
270
  delta.delete('role')
256
271
  delta.delete('sender')
@@ -290,6 +305,11 @@ module OpenAISwarm
290
305
  debug
291
306
  )
292
307
 
308
+ if partial_response.agent
309
+ agent_tool_name = message['tool_calls'].dig(0, 'function', 'name')
310
+ agent_tracker.add_tracking_agents_tool_name(agent_tool_name)
311
+ end
312
+
293
313
  history.concat(partial_response.messages)
294
314
  context_variables.merge!(partial_response.context_variables)
295
315
  active_agent = partial_response.agent if partial_response.agent
@@ -4,21 +4,24 @@ module OpenAISwarm
4
4
  def process_and_print_streaming_response(response)
5
5
  content = []
6
6
  last_sender = ""
7
- response.each do |chunk|
8
- last_sender = chunk['sender'] if chunk.key?('sender')
7
+ response.each do |stream|
8
+ delta = stream['delta']
9
+ if delta
10
+ last_sender = delta['sender'] if delta.key?('sender')
9
11
 
10
- if chunk.key?("content") && !chunk["content"].nil?
11
- if content.empty? && !last_sender.empty?
12
- puts
13
- print "\033[94m#{last_sender}:\033[0m "
14
- last_sender = ""
12
+ if delta.key?("content") && !delta["content"].nil?
13
+ if content.empty? && !last_sender.empty?
14
+ puts
15
+ print "\033[94m#{last_sender}:\033[0m "
16
+ last_sender = ""
17
+ end
18
+ print delta["content"]
19
+ content << delta["content"]
15
20
  end
16
- print chunk["content"]
17
- content << chunk["content"]
18
21
  end
19
22
 
20
- if chunk.key?("tool_calls") && !chunk["tool_calls"].nil?
21
- chunk["tool_calls"].each do |tool_call|
23
+ if stream.key?("tool_calls") && !stream["tool_calls"].nil?
24
+ stream["tool_calls"].each do |tool_call|
22
25
  f = tool_call["function"]
23
26
  name = f["name"]
24
27
  next if name.nil?
@@ -26,11 +29,11 @@ module OpenAISwarm
26
29
  end
27
30
  end
28
31
 
29
- if chunk.key?("delim") && chunk["delim"] == "end" && !content.empty?
32
+ if stream.key?("delim") && stream["delim"] == "end" && !content.empty?
30
33
  puts
31
34
  content = ""
32
35
  end
33
- return chunk["response"] if chunk.key?("response")
36
+ return stream["response"] if stream.key?("response")
34
37
  end
35
38
  end
36
39
 
@@ -7,7 +7,17 @@ module OpenAISwarm
7
7
  last_user_message = filtered_messages.reverse.find { |msg| msg['role'] == 'user' }
8
8
  last_user_message ? [last_user_message] : history
9
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
10
19
  end
20
+
11
21
  def self.debug_print(debug, *args)
12
22
  return unless debug
13
23
  timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
@@ -1,3 +1,3 @@
1
1
  module OpenAISwarm
2
- VERSION = "0.3.0.1"
2
+ VERSION = "0.4.0"
3
3
  end
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.3.0.1
4
+ version: 0.4.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-18 00:00:00.000000000 Z
11
+ date: 2024-12-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-openai