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 +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
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 922c68533aa6e9752cde8dd2fd2aaaca4d0d415acf7be59e41e5621c74cbb38d
|
4
|
+
data.tar.gz: 5472851d697e19fcc866a7898298a4fd67ed9486e9f30520e06584a15d640770
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3fa0c822a84dfd6c10220a28e5d266fbbcc0334d4bbd000333a889eb3483ed2c386706c4c1b72a62122789028ebe36124bd720bde97f561c8e6fda9136d666f6
|
7
|
+
data.tar.gz: 6653473c3e18ad66425437cd19583019826a1f827afd0b641d7993382fc5f4aee6377f9615cee50db4261f4d9818d32f33d28b57e24eda875056449b6487784e
|
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;
|
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.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-
|
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
|