ruby-openai-swarm 0.4.0.2 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75a68f00ed7c7e975dad8438d4642e42e035bbe05d5f3e3ef8f759536a74af97
4
- data.tar.gz: 73ec246a79465191f8365064618d7c76832fa3b58148fd60e703f4c3b69b8e11
3
+ metadata.gz: 326dc0fd2fb1f748c48c67f3f610e7dd3d14911f712e4016ab8cd13a19d6fd6e
4
+ data.tar.gz: 4d12edacb8d5daa6fe863daa72a06b1f3cad8ded0c01a2c882995ac8b23e1561
5
5
  SHA512:
6
- metadata.gz: a7b11e67573db5f5197fc137f239f183b0771f9aec067e5f3ba7c4f734145c82bbd9038bfb3a238b6b9b98f58d96c76f61664ef60b875eea516a4d73884b933e
7
- data.tar.gz: 639c4469e468a6819d7ed7bea1ac19a8ca97e3c160c3bbab066aa377087b0854bc13e0dc82044a1fb12f7c85abb2c545d1baec1baa040feca07e1fa4be9bf021
6
+ metadata.gz: 584b8772071a78dfdf43eb33f1e3d31c7c0b47618ffe4d766d07bfdd305781975655a8a1c7b9e9dc4f0e6dee8766881ce3e921ac063bff7a2eb6c7f4dbc1e1bf
7
+ data.tar.gz: 99c9ff59009c4822397299566a19b0ac07d02d81b47ad03c1449dd7700ca3e9ed5ea62ec1d75223fb1f6ef1c27d44efe93d02717d587527c30c7b41541f2e8b5
@@ -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.1"
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;
@@ -3,12 +3,12 @@ require_relative 'lib/ruby-openai-swarm/version'
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "ruby-openai-swarm"
5
5
  spec.version = OpenAISwarm::VERSION
6
- spec.authors = ["Grayson"]
6
+ spec.authors = ["Grayson Chen"]
7
7
  spec.email = ["cgg5207@gmail.com"]
8
8
 
9
9
  spec.summary = " A Ruby implementation of OpenAI function calling swarm"
10
10
  spec.description = "Allows for creating swarms of AI agents that can call functions and interact with each other"
11
- spec.homepage = "https://github.com/grayson/ruby-openai-swarm"
11
+ spec.homepage = "https://github.com/graysonchen/ruby-openai-swarm"
12
12
  spec.license = "MIT"
13
13
  spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
14
14
 
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.1
5
5
  platform: ruby
6
6
  authors:
7
- - Grayson
7
+ - Grayson Chen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-15 00:00:00.000000000 Z
11
+ date: 2025-03-03 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,19 +127,23 @@ 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
132
137
  - lib/ruby-openai-swarm/util.rb
133
138
  - lib/ruby-openai-swarm/version.rb
134
139
  - ruby-openai-swarm.gemspec
135
- homepage: https://github.com/grayson/ruby-openai-swarm
140
+ homepage: https://github.com/graysonchen/ruby-openai-swarm
136
141
  licenses:
137
142
  - MIT
138
143
  metadata:
139
- homepage_uri: https://github.com/grayson/ruby-openai-swarm
140
- source_code_uri: https://github.com/grayson/ruby-openai-swarm
141
- changelog_uri: https://github.com/grayson/ruby-openai-swarm/blob/main/CHANGELOG.md
144
+ homepage_uri: https://github.com/graysonchen/ruby-openai-swarm
145
+ source_code_uri: https://github.com/graysonchen/ruby-openai-swarm
146
+ changelog_uri: https://github.com/graysonchen/ruby-openai-swarm/blob/main/CHANGELOG.md
142
147
  post_install_message:
143
148
  rdoc_options: []
144
149
  require_paths: