ruby-openai-swarm 0.4.0.1 → 0.5.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 +4 -4
- data/.github/workflows/rspec.yml +1 -1
- data/.gitignore +2 -1
- data/README.md +22 -7
- data/examples/airline/configs/agents.rb +5 -5
- data/examples/basic/agent_handoff.rb +2 -2
- data/examples/basic/bare_minimum.rb +1 -1
- data/examples/basic/context_variables.rb +1 -1
- data/examples/basic/function_calling.rb +1 -1
- data/examples/basic/simple_loop_no_helpers.rb +1 -1
- data/examples/bootstrap.rb +5 -0
- data/examples/memory/memory_save.rb +71 -0
- data/examples/weather_agent/agents.rb +1 -1
- data/lib/ruby-openai-swarm/agent.rb +11 -2
- data/lib/ruby-openai-swarm/core.rb +25 -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 +1 -0
- metadata +22 -4
- data/Gemfile.lock +0 -56
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/.gitignore
CHANGED
data/README.md
CHANGED
@@ -16,7 +16,7 @@ A Ruby-based educational framework adapted from OpenAI’s [Swarm](https://githu
|
|
16
16
|
- [Bundler](#bundler)
|
17
17
|
- [Gem install](#gem-install)
|
18
18
|
- [Logger](#logger)
|
19
|
-
- [
|
19
|
+
- [Quick start](#quick-start)
|
20
20
|
- [Documentation](#documentation)
|
21
21
|
|
22
22
|
## quick show
|
@@ -86,7 +86,7 @@ def spanish_agent
|
|
86
86
|
OpenAISwarm::Agent.new(
|
87
87
|
name: "Spanish Agent",
|
88
88
|
instructions: "You only speak Spanish.",
|
89
|
-
model:
|
89
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL']
|
90
90
|
)
|
91
91
|
end
|
92
92
|
|
@@ -98,7 +98,7 @@ transfer_to_spanish_agent = OpenAISwarm::FunctionDescriptor.new(
|
|
98
98
|
english_agent = OpenAISwarm::Agent.new(
|
99
99
|
name: "English Agent",
|
100
100
|
instructions: "You only speak English.",
|
101
|
-
model:
|
101
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL'],
|
102
102
|
functions: [transfer_to_spanish_agent]
|
103
103
|
)
|
104
104
|
|
@@ -127,9 +127,20 @@ OpenAISwarm.configure do |config|
|
|
127
127
|
end
|
128
128
|
```
|
129
129
|
|
130
|
-
|
130
|
+
## Quick Start
|
131
131
|
|
132
|
-
|
132
|
+
### Choose the default model name
|
133
|
+
|
134
|
+
`export SWARM_AGENT_DEFAULT_MODEL=gpt-4o-mini`
|
135
|
+
|
136
|
+
or
|
137
|
+
|
138
|
+
`export SWARM_AGENT_DEFAULT_MODEL=deepseek-chat`
|
139
|
+
|
140
|
+
DeepSeek V3 is 1/10 price of gpt-4o-mini, so try it!
|
141
|
+
|
142
|
+
|
143
|
+
### Setting ACCESS_TOKEN for AI Providers in examples
|
133
144
|
|
134
145
|
- For OpenRouter:
|
135
146
|
|
@@ -139,7 +150,11 @@ Setting ACCESS_TOKEN for AI Providers in examples
|
|
139
150
|
|
140
151
|
`OPENAI_ACCESS_TOKEN=cxxxxx` or `export OPENAI_ACCESS_TOKEN=cxxxxx`
|
141
152
|
|
142
|
-
|
153
|
+
- For DeepSeek:
|
154
|
+
|
155
|
+
`DEEPSEEK_ACCESS_TOKEN=cxxxxx` or `export DEEPSEEK_ACCESS_TOKEN=cxxxxx`
|
156
|
+
|
157
|
+
### Check out `/examples` for inspiration! Learn more about each one in its README.
|
143
158
|
|
144
159
|
- [X] [`basic`](examples/basic): Simple examples of fundamentals like setup, function calling, handoffs, and context variables
|
145
160
|
- running: `ruby examples/basic/agent_handoff.rb`
|
@@ -150,7 +165,7 @@ Check out `/examples` for inspiration! Learn more about each one in its README.
|
|
150
165
|
- [X] [`triage_agent`](examples/triage_agent): Simple example of setting up a basic triage step to hand off to the right agent
|
151
166
|
- running: `ruby examples/triage_agent/main.rb`
|
152
167
|
- [X] [`weather_agent`](examples/weather_agent): Simple example of function calling
|
153
|
-
- running: `ruby examples/weather_agent/
|
168
|
+
- running: `ruby examples/weather_agent/run.rb`
|
154
169
|
- [X] [`airline`](examples/airline): A multi-agent setup for handling different customer service requests in an airline context.
|
155
170
|
- running: `DEBUG=1 ruby examples/airline/main.rb`
|
156
171
|
- [ ] [`support_bot`](examples/support_bot): A customer service bot which includes a user interface agent and a help center agent with several tools
|
@@ -44,7 +44,7 @@ end
|
|
44
44
|
# Define agents
|
45
45
|
def triage_agent
|
46
46
|
@triage_agent ||= OpenAISwarm::Agent.new(
|
47
|
-
model:
|
47
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL'],
|
48
48
|
name: "Triage Agent",
|
49
49
|
instructions: method(:triage_instructions),
|
50
50
|
functions: [
|
@@ -55,7 +55,7 @@ end
|
|
55
55
|
|
56
56
|
def flight_modification
|
57
57
|
@flight_modification ||= OpenAISwarm::Agent.new(
|
58
|
-
model:
|
58
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL'],
|
59
59
|
name: "Flight Modification Agent",
|
60
60
|
instructions: <<~INSTRUCTIONS,
|
61
61
|
You are a Flight Modification Agent for a customer service airlines company.
|
@@ -70,7 +70,7 @@ end
|
|
70
70
|
|
71
71
|
def flight_cancel
|
72
72
|
@flight_cancel ||= OpenAISwarm::Agent.new(
|
73
|
-
model:
|
73
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL'],
|
74
74
|
name: "Flight Cancel Traversal",
|
75
75
|
instructions: STARTER_PROMPT + FLIGHT_CANCELLATION_POLICY,
|
76
76
|
functions: [
|
@@ -85,7 +85,7 @@ end
|
|
85
85
|
|
86
86
|
def flight_change
|
87
87
|
@flight_change ||= OpenAISwarm::Agent.new(
|
88
|
-
model:
|
88
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL'],
|
89
89
|
name: "Flight Change Traversal",
|
90
90
|
instructions: STARTER_PROMPT + FLIGHT_CHANGE_POLICY,
|
91
91
|
functions: [
|
@@ -100,7 +100,7 @@ end
|
|
100
100
|
|
101
101
|
def lost_baggage
|
102
102
|
@lost_baggage ||= OpenAISwarm::Agent.new(
|
103
|
-
model:
|
103
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL'],
|
104
104
|
name: "Lost Baggage Traversal",
|
105
105
|
instructions: STARTER_PROMPT + LOST_BAGGAGE_POLICY,
|
106
106
|
functions: [
|
@@ -7,7 +7,7 @@ def spanish_agent
|
|
7
7
|
OpenAISwarm::Agent.new(
|
8
8
|
name: "Spanish Agent",
|
9
9
|
instructions: "You only speak Spanish.",
|
10
|
-
model:
|
10
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL']
|
11
11
|
)
|
12
12
|
end
|
13
13
|
|
@@ -19,7 +19,7 @@ transfer_to_spanish_agent = OpenAISwarm::FunctionDescriptor.new(
|
|
19
19
|
english_agent = OpenAISwarm::Agent.new(
|
20
20
|
name: "English Agent",
|
21
21
|
instructions: "You only speak English.",
|
22
|
-
model:
|
22
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL'],
|
23
23
|
functions: [transfer_to_spanish_agent]
|
24
24
|
)
|
25
25
|
|
@@ -6,7 +6,7 @@ client = OpenAISwarm.new
|
|
6
6
|
agent = OpenAISwarm::Agent.new(
|
7
7
|
name: "Agent",
|
8
8
|
instructions: "You are a helpful agent.",
|
9
|
-
model:
|
9
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL']
|
10
10
|
)
|
11
11
|
messages = [{"role": "user", "content": "Hi!"}]
|
12
12
|
response = client.run(agent: agent, messages: messages)
|
@@ -24,7 +24,7 @@ function_instance = OpenAISwarm::FunctionDescriptor.new(
|
|
24
24
|
agent = OpenAISwarm::Agent.new(
|
25
25
|
name: "Agent",
|
26
26
|
instructions: method(:instructions),
|
27
|
-
model:
|
27
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL'],
|
28
28
|
functions: [function_instance]
|
29
29
|
)
|
30
30
|
|
@@ -27,7 +27,7 @@ get_weather_instance = OpenAISwarm::FunctionDescriptor.new(
|
|
27
27
|
agent = OpenAISwarm::Agent.new(
|
28
28
|
name: "Agent",
|
29
29
|
instructions: "You are a helpful agent.",
|
30
|
-
model:
|
30
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL'],
|
31
31
|
functions: [
|
32
32
|
get_weather_instance,
|
33
33
|
get_news_instance
|
data/examples/bootstrap.rb
CHANGED
@@ -6,6 +6,11 @@ def env_debug
|
|
6
6
|
end
|
7
7
|
|
8
8
|
# TODO: refactor it
|
9
|
+
OpenAI.configure do |config|
|
10
|
+
config.access_token = ENV['DEEPSEEK_ACCESS_TOKEN']
|
11
|
+
config.uri_base = "https://api.deepseek.com"
|
12
|
+
end if ENV['DEEPSEEK_ACCESS_TOKEN']
|
13
|
+
|
9
14
|
OpenAI.configure do |config|
|
10
15
|
config.access_token = ENV['OPEN_ROUTER_ACCESS_TOKEN']
|
11
16
|
config.uri_base = "https://openrouter.ai/api/v1"
|
@@ -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']
|
@@ -33,7 +33,7 @@ def weather_agent
|
|
33
33
|
OpenAISwarm::Agent.new(
|
34
34
|
name: "Weather Agent",
|
35
35
|
instructions: "You are a helpful agent.",
|
36
|
-
model:
|
36
|
+
model: ENV['SWARM_AGENT_DEFAULT_MODEL'],
|
37
37
|
functions: [
|
38
38
|
function_instance_send_email,
|
39
39
|
function_instance_get_weather
|
@@ -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
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'ruby/openai'
|
2
|
+
require 'ostruct'
|
2
3
|
begin
|
3
4
|
require 'pry'
|
4
5
|
rescue LoadError
|
@@ -14,11 +15,28 @@ module OpenAISwarm
|
|
14
15
|
@logger = OpenAISwarm::Logger.instance.logger
|
15
16
|
end
|
16
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
|
+
|
17
31
|
def get_chat_completion(agent_tracker, history, context_variables, model_override, stream, debug)
|
18
32
|
agent = agent_tracker.current_agent
|
19
33
|
context_variables = context_variables.dup
|
20
34
|
instructions = agent.instructions.respond_to?(:call) ? agent.instructions.call(context_variables) : agent.instructions
|
21
|
-
|
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
|
22
40
|
|
23
41
|
# Util.debug_print(debug, "Getting chat completion for...:", messages)
|
24
42
|
|
@@ -65,9 +83,10 @@ module OpenAISwarm
|
|
65
83
|
|
66
84
|
Util.debug_print(debug, "API Response:", response)
|
67
85
|
response
|
68
|
-
rescue OpenAI::Error => e
|
69
|
-
|
70
|
-
|
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)
|
71
90
|
raise
|
72
91
|
end
|
73
92
|
|
@@ -181,11 +200,11 @@ module OpenAISwarm
|
|
181
200
|
debug
|
182
201
|
)
|
183
202
|
|
184
|
-
message = completion.dig('choices', 0, 'message')
|
203
|
+
message = completion.dig('choices', 0, 'message') || {}
|
185
204
|
Util.debug_print(debug, "Received completion:", message)
|
186
205
|
log_message(:info, "Received completion:", message)
|
187
206
|
|
188
|
-
message['sender'] = active_agent
|
207
|
+
message['sender'] = active_agent&.name
|
189
208
|
history << message
|
190
209
|
|
191
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
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
spec.add_dependency "ruby-openai", "~> 7.3"
|
25
|
+
spec.add_dependency "ostruct"
|
25
26
|
spec.add_development_dependency "rspec", "~> 3.0"
|
26
27
|
spec.add_development_dependency "rake", "~> 13.0"
|
27
28
|
spec.add_development_dependency "pry"
|
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:
|
11
|
+
date: 2025-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-openai
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '7.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: ostruct
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rspec
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -78,7 +92,6 @@ files:
|
|
78
92
|
- ".gitignore"
|
79
93
|
- ".rspec"
|
80
94
|
- Gemfile
|
81
|
-
- Gemfile.lock
|
82
95
|
- README.md
|
83
96
|
- Rakefile
|
84
97
|
- assets/logo-swarm.png
|
@@ -98,6 +111,7 @@ files:
|
|
98
111
|
- examples/basic/function_calling.rb
|
99
112
|
- examples/basic/simple_loop_no_helpers.rb
|
100
113
|
- examples/bootstrap.rb
|
114
|
+
- examples/memory/memory_save.rb
|
101
115
|
- examples/triage_agent/README.md
|
102
116
|
- examples/triage_agent/agents.rb
|
103
117
|
- examples/triage_agent/main.rb
|
@@ -113,6 +127,10 @@ files:
|
|
113
127
|
- lib/ruby-openai-swarm/core_ext.rb
|
114
128
|
- lib/ruby-openai-swarm/function_descriptor.rb
|
115
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
|
116
134
|
- lib/ruby-openai-swarm/repl.rb
|
117
135
|
- lib/ruby-openai-swarm/response.rb
|
118
136
|
- lib/ruby-openai-swarm/result.rb
|
@@ -141,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
159
|
- !ruby/object:Gem::Version
|
142
160
|
version: '0'
|
143
161
|
requirements: []
|
144
|
-
rubygems_version: 3.
|
162
|
+
rubygems_version: 3.5.3
|
145
163
|
signing_key:
|
146
164
|
specification_version: 4
|
147
165
|
summary: A Ruby implementation of OpenAI function calling swarm
|
data/Gemfile.lock
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
ruby-openai-swarm (0.4.0.1)
|
5
|
-
ruby-openai (~> 7.3)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
base64 (0.2.0)
|
11
|
-
coderay (1.1.3)
|
12
|
-
diff-lcs (1.5.1)
|
13
|
-
event_stream_parser (1.0.0)
|
14
|
-
faraday (2.8.1)
|
15
|
-
base64
|
16
|
-
faraday-net_http (>= 2.0, < 3.1)
|
17
|
-
ruby2_keywords (>= 0.0.4)
|
18
|
-
faraday-multipart (1.1.0)
|
19
|
-
multipart-post (~> 2.0)
|
20
|
-
faraday-net_http (3.0.2)
|
21
|
-
method_source (1.1.0)
|
22
|
-
multipart-post (2.4.1)
|
23
|
-
pry (0.14.2)
|
24
|
-
coderay (~> 1.1)
|
25
|
-
method_source (~> 1.0)
|
26
|
-
rake (13.2.1)
|
27
|
-
rspec (3.13.0)
|
28
|
-
rspec-core (~> 3.13.0)
|
29
|
-
rspec-expectations (~> 3.13.0)
|
30
|
-
rspec-mocks (~> 3.13.0)
|
31
|
-
rspec-core (3.13.2)
|
32
|
-
rspec-support (~> 3.13.0)
|
33
|
-
rspec-expectations (3.13.3)
|
34
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
-
rspec-support (~> 3.13.0)
|
36
|
-
rspec-mocks (3.13.2)
|
37
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
38
|
-
rspec-support (~> 3.13.0)
|
39
|
-
rspec-support (3.13.1)
|
40
|
-
ruby-openai (7.3.1)
|
41
|
-
event_stream_parser (>= 0.3.0, < 2.0.0)
|
42
|
-
faraday (>= 1)
|
43
|
-
faraday-multipart (>= 1)
|
44
|
-
ruby2_keywords (0.0.5)
|
45
|
-
|
46
|
-
PLATFORMS
|
47
|
-
ruby
|
48
|
-
|
49
|
-
DEPENDENCIES
|
50
|
-
pry
|
51
|
-
rake (~> 13.0)
|
52
|
-
rspec (~> 3.0)
|
53
|
-
ruby-openai-swarm!
|
54
|
-
|
55
|
-
BUNDLED WITH
|
56
|
-
2.1.4
|