ruby-openai-swarm 0.4.0.2 → 0.5.0

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: 75a68f00ed7c7e975dad8438d4642e42e035bbe05d5f3e3ef8f759536a74af97
4
- data.tar.gz: 73ec246a79465191f8365064618d7c76832fa3b58148fd60e703f4c3b69b8e11
3
+ metadata.gz: 922c68533aa6e9752cde8dd2fd2aaaca4d0d415acf7be59e41e5621c74cbb38d
4
+ data.tar.gz: 5472851d697e19fcc866a7898298a4fd67ed9486e9f30520e06584a15d640770
5
5
  SHA512:
6
- metadata.gz: a7b11e67573db5f5197fc137f239f183b0771f9aec067e5f3ba7c4f734145c82bbd9038bfb3a238b6b9b98f58d96c76f61664ef60b875eea516a4d73884b933e
7
- data.tar.gz: 639c4469e468a6819d7ed7bea1ac19a8ca97e3c160c3bbab066aa377087b0854bc13e0dc82044a1fb12f7c85abb2c545d1baec1baa040feca07e1fa4be9bf021
6
+ metadata.gz: 3fa0c822a84dfd6c10220a28e5d266fbbcc0334d4bbd000333a889eb3483ed2c386706c4c1b72a62122789028ebe36124bd720bde97f561c8e6fda9136d666f6
7
+ data.tar.gz: 6653473c3e18ad66425437cd19583019826a1f827afd0b641d7993382fc5f4aee6377f9615cee50db4261f4d9818d32f33d28b57e24eda875056449b6487784e
@@ -20,7 +20,7 @@ jobs:
20
20
  - name: Run tests
21
21
  run: bundle exec rspec
22
22
  - name: Upload test results
23
- uses: actions/upload-artifact@v3
23
+ uses: actions/upload-artifact@v4
24
24
  if: failure()
25
25
  with:
26
26
  name: rspec-results
data/README.md CHANGED
@@ -165,7 +165,7 @@ DeepSeek V3 is 1/10 price of gpt-4o-mini, so try it!
165
165
  - [X] [`triage_agent`](examples/triage_agent): Simple example of setting up a basic triage step to hand off to the right agent
166
166
  - running: `ruby examples/triage_agent/main.rb`
167
167
  - [X] [`weather_agent`](examples/weather_agent): Simple example of function calling
168
- - running: `ruby examples/weather_agent/agents.rb`
168
+ - running: `ruby examples/weather_agent/run.rb`
169
169
  - [X] [`airline`](examples/airline): A multi-agent setup for handling different customer service requests in an airline context.
170
170
  - running: `DEBUG=1 ruby examples/airline/main.rb`
171
171
  - [ ] [`support_bot`](examples/support_bot): A customer service bot which includes a user interface agent and a help center agent with several tools
@@ -0,0 +1,71 @@
1
+ require_relative "../bootstrap"
2
+
3
+ client = OpenAISwarm.new
4
+ model = ENV['SWARM_AGENT_DEFAULT_MODEL'] || "gpt-4o-mini"
5
+
6
+ memory = OpenAISwarm::Memory.new(memory_fields: ["language", "grade", "name", "sex"])
7
+ memory.function
8
+
9
+ def get_weather(location:)
10
+ puts "tool call: get_weather"
11
+ "{'temp':67, 'unit':'F'}"
12
+ end
13
+
14
+ def get_news(category:)
15
+ puts "tool call: get_news"
16
+ [
17
+ "Tech Company A Acquires Startup B",
18
+ "New AI Model Revolutionizes Industry",
19
+ "Breakthrough in Quantum Computing"
20
+ ].sample
21
+ end
22
+
23
+ get_news_instance = OpenAISwarm::FunctionDescriptor.new(
24
+ target_method: :get_news,
25
+ description: 'Get the latest news headlines. The category of news, e.g., world, business, sports.'
26
+ )
27
+
28
+ get_weather_instance = OpenAISwarm::FunctionDescriptor.new(
29
+ target_method: :get_weather,
30
+ description: 'Simulate fetching weather data'
31
+ )
32
+
33
+
34
+ system_prompt = "You are a helpful teaching assistant. Remember to save important information about the student using the core_memory_save function. Always greet the student by name if you know it."
35
+
36
+ chatbot_agent = OpenAISwarm::Agent.new(
37
+ name: "teaching_assistant",
38
+ instructions: system_prompt,
39
+ model: model,
40
+ functions: [
41
+ get_weather_instance,
42
+ get_news_instance
43
+ ],
44
+ memory: memory
45
+ )
46
+
47
+ messages1 = [
48
+ {
49
+ "role": "user",
50
+ "content": "Hi, I'm John. I speak Chinese and I'm in Senior Year. Get the current weather in a given location. Location MUST be a city."
51
+ }
52
+ ]
53
+
54
+ puts "first call, set memory"
55
+ puts "messages: #{messages1}"
56
+
57
+ response1 = client.run(agent: chatbot_agent, messages: messages1, debug: env_debug)
58
+ puts "memory data: #{memory.entity_store.data}"
59
+ puts response1.messages.last['content']
60
+
61
+ puts ""
62
+ messages2 = [
63
+ {"role": "user", "content": "what is my name"},
64
+ ]
65
+ puts "2nd call, get memory"
66
+ puts "memory data: #{memory.entity_store.data}"
67
+ puts "messages: #{messages2}"
68
+
69
+ response2 = client.run(agent: chatbot_agent, messages: messages2, debug: env_debug)
70
+
71
+ puts response2.messages.last['content']
@@ -7,7 +7,8 @@ module OpenAISwarm
7
7
  :noisy_tool_calls,
8
8
  :temperature,
9
9
  :current_tool_name,
10
- :resource
10
+ :resource,
11
+ :memory
11
12
  # These attributes can be read and written externally. They include:
12
13
  # - name: The name of the agent.
13
14
  # - model: The model used, e.g., "gpt-4".
@@ -27,7 +28,8 @@ module OpenAISwarm
27
28
  resource: nil,
28
29
  noisy_tool_calls: [],
29
30
  strategy: {},
30
- current_tool_name: nil
31
+ current_tool_name: nil,
32
+ memory: nil
31
33
  )
32
34
  @name = name
33
35
  @model = model
@@ -40,6 +42,13 @@ module OpenAISwarm
40
42
  @noisy_tool_calls = noisy_tool_calls
41
43
  @strategy = Agents::StrategyOptions.new(strategy)
42
44
  @current_tool_name = current_tool_name.nil? ? nil : current_tool_name.to_s
45
+ @memory = memory
46
+ end
47
+
48
+ def functions
49
+ return @functions if memory&.function.nil?
50
+
51
+ @functions.push(memory.function)
43
52
  end
44
53
  end
45
54
  end
@@ -15,11 +15,28 @@ module OpenAISwarm
15
15
  @logger = OpenAISwarm::Logger.instance.logger
16
16
  end
17
17
 
18
+ # TODO(Grayson)
19
+ # def create_agent(name:, model:, instructions:, **options)
20
+ # memory = Memory.new(@memory_fields)
21
+ # Agent.new(
22
+ # name: name,
23
+ # model: model,
24
+ # instructions: instructions,
25
+ # memory: memory,
26
+ # functions: functions,
27
+ # **options
28
+ # )
29
+ # end
30
+
18
31
  def get_chat_completion(agent_tracker, history, context_variables, model_override, stream, debug)
19
32
  agent = agent_tracker.current_agent
20
33
  context_variables = context_variables.dup
21
34
  instructions = agent.instructions.respond_to?(:call) ? agent.instructions.call(context_variables) : agent.instructions
22
- messages = [{ role: 'system', content: instructions }] + history
35
+
36
+ # Build a message history, including memories
37
+ messages = [{ role: 'system', content: instructions }]
38
+ messages << { role: 'system', content: agent.memory.prompt_content } unless agent&.memory&.prompt_content.nil?
39
+ messages += history
23
40
 
24
41
  # Util.debug_print(debug, "Getting chat completion for...:", messages)
25
42
 
@@ -66,9 +83,10 @@ module OpenAISwarm
66
83
 
67
84
  Util.debug_print(debug, "API Response:", response)
68
85
  response
69
- rescue OpenAI::Error => e
70
- log_message(:error, "OpenAI API Error: #{e.message}")
71
- Util.debug_print(true, "OpenAI API Error:", e.message)
86
+ rescue OpenAI::Error, Faraday::BadRequestError => e
87
+ error_message = (e.response || {}).dig(:body) || e.inspect
88
+ log_message(:error, "OpenAI API Error: #{error_message}")
89
+ Util.debug_print(true, "OpenAI API Error:", error_message)
72
90
  raise
73
91
  end
74
92
 
@@ -182,11 +200,11 @@ module OpenAISwarm
182
200
  debug
183
201
  )
184
202
 
185
- message = completion.dig('choices', 0, 'message')
203
+ message = completion.dig('choices', 0, 'message') || {}
186
204
  Util.debug_print(debug, "Received completion:", message)
187
205
  log_message(:info, "Received completion:", message)
188
206
 
189
- message['sender'] = active_agent.name
207
+ message['sender'] = active_agent&.name
190
208
  history << message
191
209
 
192
210
  if !message['tool_calls'] || !execute_tools
@@ -0,0 +1,27 @@
1
+ module OpenAISwarm
2
+ module Memories
3
+ class CoreMemoryFunction
4
+ def self.definition(memory_fields = [])
5
+ properties = {}
6
+
7
+ memory_fields.each do |memory_field|
8
+ field = memory_field.field
9
+ description = "The #{field} to remember." + memory_field&.tool_call_description.to_s
10
+ properties[field] = { type: "string", description: description }
11
+ end
12
+
13
+ {
14
+ type: "function",
15
+ function: {
16
+ name: "core_memory_save",
17
+ description: "Save important information about you, the agent or the human you are chatting with.",
18
+ parameters: {
19
+ type: "object",
20
+ properties: properties,
21
+ }
22
+ }
23
+ }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,54 @@
1
+ module OpenAISwarm
2
+ module Memories
3
+ class EntityStore
4
+ attr_accessor :entity_store,
5
+ :data
6
+
7
+ def initialize(entity_store = nil)
8
+ @entity_store = entity_store
9
+ @data = entity_store&.data || {}
10
+ end
11
+
12
+ def entity_store_save
13
+ return unless entity_store.respond_to?(:update)
14
+
15
+ entity_store.update(data: data)
16
+ end
17
+
18
+ def add_entities(entities)
19
+ entities.each { |key, value| @data[key.to_sym] = value }
20
+ entity_store_save
21
+ end
22
+
23
+ def memories
24
+ data&.to_json
25
+ end
26
+
27
+ # def add(key, value)
28
+ # @data[key] = value
29
+ # entity_store_save
30
+ # @data
31
+ # end
32
+
33
+ # def get(key)
34
+ # @data[key]
35
+ # end
36
+
37
+ # def exists?(key)
38
+ # @data.key?(key)
39
+ # end
40
+
41
+ # def remove(key)
42
+ # @data.delete(key)
43
+ # end
44
+
45
+ # def clear
46
+ # @data = {}
47
+ # end
48
+
49
+ # def all
50
+ # @data
51
+ # end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,39 @@
1
+ module OpenAISwarm
2
+ module Memories
3
+ class Field
4
+ attr_accessor :field,
5
+ :tool_call_description
6
+
7
+ VALID_MEMORY_FIELDS = [:field, :tool_call_description].freeze
8
+
9
+ def initialize(memory_field)
10
+ memory_field.is_a?(Hash) ? parse_hash(memory_field) : parse_string(memory_field)
11
+ end
12
+
13
+ def parse_hash(memory_field)
14
+ validate_memory_field!(memory_field)
15
+
16
+ @field = memory_field[:field]
17
+ @tool_call_description = memory_field[:tool_call_description]
18
+ end
19
+
20
+ def parse_string(memory_field)
21
+ @field = memory_field
22
+ end
23
+
24
+ private
25
+
26
+ def validate_memory_field!(memory_field)
27
+ unless memory_field.include?(:field)
28
+ raise ArgumentError, "memory_field must include :field"
29
+ end
30
+
31
+ invalid_fields = memory_field.keys - VALID_MEMORY_FIELDS
32
+
33
+ unless invalid_fields.empty?
34
+ raise ArgumentError, "Invalid memory fields: #{invalid_fields.join(', ')}. Valid fields are: #{VALID_MEMORY_FIELDS.join(', ')}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,49 @@
1
+ module OpenAISwarm
2
+ class Memory
3
+ attr_reader :memories,
4
+ :entity_store
5
+
6
+ def initialize(memory_fields: [], entity_store: nil)
7
+ @memory_fields = normalize_memory_fields(memory_fields)
8
+ @entity_store = Memories::EntityStore.new(entity_store)
9
+ end
10
+
11
+ def normalize_memory_fields(memory_fields)
12
+ return [] if memory_fields.empty?
13
+
14
+ memory_fields.map { |memory_field| Memories::Field.new(memory_field) }
15
+ end
16
+
17
+ def core_memory_save(entities)
18
+ entity_store.add_entities(entities)
19
+ end
20
+
21
+ def prompt_content
22
+ return nil if get_memories_data.nil?
23
+
24
+ fields = @memory_fields.map(&:field).join(", ")
25
+ "You have a section of your context called [MEMORY] " \
26
+ "that contains the following information: #{fields}. " \
27
+ "Here are the relevant details: [MEMORY]\n" \
28
+ "#{get_memories_data}"
29
+ end
30
+
31
+ def function
32
+ return nil if @memory_fields.empty?
33
+
34
+ OpenAISwarm::FunctionDescriptor.new(
35
+ target_method: method(:core_memory_save),
36
+ description: core_memory_save_metadata[:function][:description],
37
+ parameters: core_memory_save_metadata[:function][:parameters]
38
+ )
39
+ end
40
+
41
+ def core_memory_save_metadata
42
+ @core_memory_save_metadata ||= Memories::CoreMemoryFunction.definition(@memory_fields)
43
+ end
44
+
45
+ def get_memories_data
46
+ entity_store&.memories
47
+ end
48
+ end
49
+ end
@@ -1,3 +1,3 @@
1
1
  module OpenAISwarm
2
- VERSION = "0.4.0.2"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -11,6 +11,11 @@ require 'ruby-openai-swarm/function_descriptor'
11
11
  require 'ruby-openai-swarm/repl'
12
12
  require 'ruby-openai-swarm/configuration'
13
13
  require 'ruby-openai-swarm/logger'
14
+ require 'ruby-openai-swarm/memory'
15
+ require 'ruby-openai-swarm/memories/entity_store'
16
+ require 'ruby-openai-swarm/memories/core_memory_function'
17
+ require 'ruby-openai-swarm/memories/field'
18
+
14
19
 
15
20
  module OpenAISwarm
16
21
  class Error < StandardError;
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.0.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Grayson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-15 00:00:00.000000000 Z
11
+ date: 2025-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-openai
@@ -111,6 +111,7 @@ files:
111
111
  - examples/basic/function_calling.rb
112
112
  - examples/basic/simple_loop_no_helpers.rb
113
113
  - examples/bootstrap.rb
114
+ - examples/memory/memory_save.rb
114
115
  - examples/triage_agent/README.md
115
116
  - examples/triage_agent/agents.rb
116
117
  - examples/triage_agent/main.rb
@@ -126,6 +127,10 @@ files:
126
127
  - lib/ruby-openai-swarm/core_ext.rb
127
128
  - lib/ruby-openai-swarm/function_descriptor.rb
128
129
  - lib/ruby-openai-swarm/logger.rb
130
+ - lib/ruby-openai-swarm/memories/core_memory_function.rb
131
+ - lib/ruby-openai-swarm/memories/entity_store.rb
132
+ - lib/ruby-openai-swarm/memories/field.rb
133
+ - lib/ruby-openai-swarm/memory.rb
129
134
  - lib/ruby-openai-swarm/repl.rb
130
135
  - lib/ruby-openai-swarm/response.rb
131
136
  - lib/ruby-openai-swarm/result.rb