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 +4 -4
- data/.github/workflows/rspec.yml +1 -1
- data/README.md +1 -1
- data/examples/memory/memory_save.rb +71 -0
- data/lib/ruby-openai-swarm/agent.rb +11 -2
- data/lib/ruby-openai-swarm/core.rb +24 -6
- data/lib/ruby-openai-swarm/memories/core_memory_function.rb +27 -0
- data/lib/ruby-openai-swarm/memories/entity_store.rb +54 -0
- data/lib/ruby-openai-swarm/memories/field.rb +39 -0
- data/lib/ruby-openai-swarm/memory.rb +49 -0
- data/lib/ruby-openai-swarm/version.rb +1 -1
- data/lib/ruby-openai-swarm.rb +5 -0
- data/ruby-openai-swarm.gemspec +2 -2
- metadata +12 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 326dc0fd2fb1f748c48c67f3f610e7dd3d14911f712e4016ab8cd13a19d6fd6e
|
4
|
+
data.tar.gz: 4d12edacb8d5daa6fe863daa72a06b1f3cad8ded0c01a2c882995ac8b23e1561
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 584b8772071a78dfdf43eb33f1e3d31c7c0b47618ffe4d766d07bfdd305781975655a8a1c7b9e9dc4f0e6dee8766881ce3e921ac063bff7a2eb6c7f4dbc1e1bf
|
7
|
+
data.tar.gz: 99c9ff59009c4822397299566a19b0ac07d02d81b47ad03c1449dd7700ca3e9ed5ea62ec1d75223fb1f6ef1c27d44efe93d02717d587527c30c7b41541f2e8b5
|
data/.github/workflows/rspec.yml
CHANGED
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/
|
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
|
-
|
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
|
-
|
71
|
-
|
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
|
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
|
data/lib/ruby-openai-swarm.rb
CHANGED
@@ -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;
|
data/ruby-openai-swarm.gemspec
CHANGED
@@ -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/
|
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
|
+
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-
|
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/
|
140
|
+
homepage: https://github.com/graysonchen/ruby-openai-swarm
|
136
141
|
licenses:
|
137
142
|
- MIT
|
138
143
|
metadata:
|
139
|
-
homepage_uri: https://github.com/
|
140
|
-
source_code_uri: https://github.com/
|
141
|
-
changelog_uri: https://github.com/
|
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:
|