ruby-openai-swarm 0.1.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 +7 -0
- data/.github/workflows/rspec.yml +27 -0
- data/.gitignore +32 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +56 -0
- data/README.md +141 -0
- data/Rakefile +1 -0
- data/assets/logo-swarm.png +0 -0
- data/bin/console +14 -0
- data/bin/setup +14 -0
- data/examples/basic/README.md +0 -0
- data/examples/basic/agent_handoff.rb +41 -0
- data/examples/basic/bare_minimum.rb +27 -0
- data/examples/basic/context_variables.rb +67 -0
- data/examples/basic/function_calling.rb +32 -0
- data/examples/basic/simple_loop_no_helpers.rb +38 -0
- data/examples/triage_agent/README.md +34 -0
- data/examples/triage_agent/agents.rb +84 -0
- data/examples/triage_agent/main.rb +3 -0
- data/examples/weather_agent/README.md +0 -0
- data/examples/weather_agent/agents.rb +59 -0
- data/examples/weather_agent/run.rb +0 -0
- data/lib/ruby-openai-swarm/agent.rb +21 -0
- data/lib/ruby-openai-swarm/core.rb +269 -0
- data/lib/ruby-openai-swarm/function_descriptor.rb +10 -0
- data/lib/ruby-openai-swarm/repl.rb +90 -0
- data/lib/ruby-openai-swarm/response.rb +11 -0
- data/lib/ruby-openai-swarm/result.rb +11 -0
- data/lib/ruby-openai-swarm/util.rb +78 -0
- data/lib/ruby-openai-swarm/version.rb +4 -0
- data/lib/ruby-openai-swarm.rb +18 -0
- data/ruby-openai-swarm.gemspec +28 -0
- metadata +135 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: df437749d299781cdac584185c29c03d6bcdd0ae8ae547395d23656c7a5ff308
|
|
4
|
+
data.tar.gz: ad06764c570c061aab9077ae280bad27b174a740f5721616ab0f3fb052eeb638
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 22dff45e435bbe72d598cae52e1924f1921f9d4c07e0795c6b0d58368c72050d33cb63f999c936465fdf5a9bd1065d7dcbeb8696259b9468f8c5b325bfb686d6
|
|
7
|
+
data.tar.gz: c578b9500e861456618f7a576756963cae6bfa53249123b23c7a57363c4cc4993dd796865b42c43d2ff8edd38d9b9d6823ec6b4388da26d43858a38fec9a922f
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: RSpec Tests
|
|
2
|
+
on: [push, pull_request]
|
|
3
|
+
|
|
4
|
+
jobs:
|
|
5
|
+
test:
|
|
6
|
+
runs-on: ubuntu-latest
|
|
7
|
+
strategy:
|
|
8
|
+
matrix:
|
|
9
|
+
ruby-version: ['2.7', '3.0', '3.1']
|
|
10
|
+
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v3
|
|
13
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
14
|
+
uses: ruby/setup-ruby@v1
|
|
15
|
+
with:
|
|
16
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
17
|
+
bundler-cache: true
|
|
18
|
+
- name: Install dependencies
|
|
19
|
+
run: bundle install
|
|
20
|
+
- name: Run tests
|
|
21
|
+
run: bundle exec rspec
|
|
22
|
+
- name: Upload test results
|
|
23
|
+
uses: actions/upload-artifact@v3
|
|
24
|
+
if: failure()
|
|
25
|
+
with:
|
|
26
|
+
name: rspec-results
|
|
27
|
+
path: tmp/rspec_results
|
data/.gitignore
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
*.gem
|
|
2
|
+
*.rbc
|
|
3
|
+
/.config
|
|
4
|
+
/coverage/
|
|
5
|
+
/InstalledFiles
|
|
6
|
+
/pkg/
|
|
7
|
+
/spec/reports/
|
|
8
|
+
/spec/examples.txt
|
|
9
|
+
/test/tmp/
|
|
10
|
+
/test/version_tmp/
|
|
11
|
+
/tmp/
|
|
12
|
+
.byebug_history
|
|
13
|
+
.dat*
|
|
14
|
+
.repl_history
|
|
15
|
+
build/
|
|
16
|
+
*.bridgesupport
|
|
17
|
+
build-iPhoneOS/
|
|
18
|
+
build-iPhoneSimulator/
|
|
19
|
+
/.yardoc/
|
|
20
|
+
/_yardoc/
|
|
21
|
+
/doc/
|
|
22
|
+
/rdoc/
|
|
23
|
+
/.bundle/
|
|
24
|
+
/vendor/bundle
|
|
25
|
+
/lib/bundler/man/
|
|
26
|
+
.rvmrc
|
|
27
|
+
.ruby-version
|
|
28
|
+
.ruby-gemset
|
|
29
|
+
/tmp
|
|
30
|
+
|
|
31
|
+
# rspec failure tracking
|
|
32
|
+
.rspec_status
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
ruby-openai-swarm (0.1.0)
|
|
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.0.4)
|
|
19
|
+
multipart-post (~> 2)
|
|
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
|
data/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Ruby OpenAI Swarm
|
|
4
|
+
|
|
5
|
+
[](https://github.com/graysonchen/ruby-openai-swarm/actions)
|
|
6
|
+
|
|
7
|
+
A Ruby-based educational framework adapted from OpenAI’s [Swarm](https://github.com/openai/swarm), exploring ergonomic, lightweight multi-agent orchestration.
|
|
8
|
+
|
|
9
|
+
> The primary goal of Swarm is to showcase the handoff & routines patterns explored in the [Orchestrating Agents: Handoffs & Routines](https://cookbook.openai.com/examples/orchestrating_agents) cookbook. It is not meant as a standalone library, and is primarily for educational purposes.
|
|
10
|
+
|
|
11
|
+
## Contents
|
|
12
|
+
- [Ruby OpenAI Swarm](#ruby-openai-swarm)
|
|
13
|
+
- [Table of Contents](#table-of-contents)
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Bundler](#bundler)
|
|
16
|
+
- [Gem install](#gem-install)
|
|
17
|
+
- [examples](#examples)
|
|
18
|
+
- [Documentation](#documentation)
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
### Bundler
|
|
23
|
+
|
|
24
|
+
Add this line to your application's Gemfile:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
gem "ruby-openai-swarm"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
And then execute:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
$ bundle install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Gem install
|
|
37
|
+
|
|
38
|
+
Or install with:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
$ gem install ruby-openai-swarm
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
and require with:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
require "ruby-openai-swarm"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
### With Config
|
|
53
|
+
|
|
54
|
+
For a more robust setup, you can configure the gem with your API keys, for example in an `openai.rb` initializer file. Never hardcode secrets into your codebase - instead use something like [dotenv](https://github.com/motdotla/dotenv) to pass the keys safely into your environments.
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
OpenAI.configure do |config|
|
|
58
|
+
config.access_token = ENV['OPENAI_ACCESS_TOKEN']
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
OR
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
# https://openrouter.ai
|
|
66
|
+
OpenAI.configure do |config|
|
|
67
|
+
config.access_token = ENV['OPEN_ROUTER_ACCESS_TOKEN']
|
|
68
|
+
config.uri_base = "https://openrouter.ai/api/v1"
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
more see: https://github.com/alexrudall/ruby-openai/tree/main?tab=readme-ov-file#ollama
|
|
73
|
+
|
|
74
|
+
Then you can create a client like this:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
client = OpenAISwarm.new
|
|
78
|
+
|
|
79
|
+
def spanish_agent
|
|
80
|
+
OpenAISwarm::Agent.new(
|
|
81
|
+
name: "Spanish Agent",
|
|
82
|
+
instructions: "You only speak Spanish.",
|
|
83
|
+
model: "gpt-4o-mini"
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
transfer_to_spanish_agent = OpenAISwarm::FunctionDescriptor.new(
|
|
88
|
+
target_method: :spanish_agent,
|
|
89
|
+
description: 'Transfer spanish speaking users immediately.'
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
english_agent = OpenAISwarm::Agent.new(
|
|
93
|
+
name: "English Agent",
|
|
94
|
+
instructions: "You only speak English.",
|
|
95
|
+
model: "gpt-4o-mini",
|
|
96
|
+
functions: [transfer_to_spanish_agent]
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
messages = [{"role": "user", "content": "Hola. ¿Como estás?"}]
|
|
100
|
+
response = client.run(agent: english_agent, messages: messages, debug: true)
|
|
101
|
+
|
|
102
|
+
pp response.messages.last
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
{"role"=>"assistant",
|
|
107
|
+
"content"=>"¡Hola! Estoy bien, gracias. ¿Y tú?",
|
|
108
|
+
"refusal"=>nil,
|
|
109
|
+
:sender=>"Spanish Agent"}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
# Examples
|
|
113
|
+
|
|
114
|
+
Check out `/examples` for inspiration! Learn more about each one in its README.
|
|
115
|
+
|
|
116
|
+
- [X] [`basic`](examples/basic): Simple examples of fundamentals like setup, function calling, handoffs, and context variables
|
|
117
|
+
- [X] [`triage_agent`](examples/triage_agent): Simple example of setting up a basic triage step to hand off to the right agent
|
|
118
|
+
- [X] [`weather_agent`](examples/weather_agent): Simple example of function calling
|
|
119
|
+
- [ ] [`airline`](examples/airline): A multi-agent setup for handling different customer service requests in an airline context.
|
|
120
|
+
- [ ] [`support_bot`](examples/support_bot): A customer service bot which includes a user interface agent and a help center agent with several tools
|
|
121
|
+
- [ ] [`personal_shopper`](examples/personal_shopper): A personal shopping agent that can help with making sales and refunding orders
|
|
122
|
+
|
|
123
|
+
link: https://github.com/openai/swarm/tree/main?tab=readme-ov-file#examples
|
|
124
|
+
|
|
125
|
+
## Documentation
|
|
126
|
+

|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
132
|
+
|
|
133
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
134
|
+
|
|
135
|
+
## Contributing
|
|
136
|
+
|
|
137
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/graysonchen/ruby-openai-swarm. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/graysonchen/ruby-openai-swarm/blob/main/CODE_OF_CONDUCT.md).
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
Binary file
|
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "ruby-openai-swarm"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "ruby-openai-swarm"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# link: https://github.com/openai/swarm/blob/main/examples/basic/agent_handoff.py
|
|
2
|
+
|
|
3
|
+
# OpenAI.configure do |config|
|
|
4
|
+
# config.access_token = ENV['OPENAI_ACCESS_TOKEN']
|
|
5
|
+
# end
|
|
6
|
+
|
|
7
|
+
OpenAI.configure do |config|
|
|
8
|
+
config.access_token = ENV['OPEN_ROUTER_ACCESS_TOKEN']
|
|
9
|
+
config.uri_base = "https://openrouter.ai/api/v1"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
client = OpenAISwarm.new
|
|
13
|
+
|
|
14
|
+
def spanish_agent
|
|
15
|
+
OpenAISwarm::Agent.new(
|
|
16
|
+
name: "Spanish Agent",
|
|
17
|
+
instructions: "You only speak Spanish.",
|
|
18
|
+
model: "gpt-4o-mini"
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
transfer_to_spanish_agent = OpenAISwarm::FunctionDescriptor.new(
|
|
23
|
+
target_method: :spanish_agent,
|
|
24
|
+
description: 'Transfer spanish speaking users immediately.'
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
english_agent = OpenAISwarm::Agent.new(
|
|
28
|
+
name: "English Agent",
|
|
29
|
+
instructions: "You only speak English.",
|
|
30
|
+
model: "gpt-4o-mini",
|
|
31
|
+
functions: [transfer_to_spanish_agent]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
messages = [{"role": "user", "content": "Hola. ¿Como estás?"}]
|
|
35
|
+
response = client.run(agent: english_agent, messages: messages, debug: true)
|
|
36
|
+
|
|
37
|
+
p response.messages.last
|
|
38
|
+
# => {"role"=>"assistant", "content"=>"¡Hola! Estoy bien, gracias. ¿Y tú?", "refusal"=>nil, :sender=>"Spanish Agent"}
|
|
39
|
+
|
|
40
|
+
msg = response.messages.last
|
|
41
|
+
msg['sender'] == "Spanish Agent"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "bundler/setup"
|
|
2
|
+
require "ruby-openai-swarm"
|
|
3
|
+
|
|
4
|
+
# link: https://github.com/openai/swarm/blob/main/examples/basic/bare_minimum.py
|
|
5
|
+
|
|
6
|
+
# OpenAI.configure do |config|
|
|
7
|
+
# config.access_token = ENV['OPENAI_ACCESS_TOKEN']
|
|
8
|
+
# end
|
|
9
|
+
|
|
10
|
+
OpenAI.configure do |config|
|
|
11
|
+
config.access_token = ENV['OPEN_ROUTER_ACCESS_TOKEN']
|
|
12
|
+
config.uri_base = "https://openrouter.ai/api/v1"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
client = OpenAISwarm.new
|
|
16
|
+
|
|
17
|
+
agent = OpenAISwarm::Agent.new(
|
|
18
|
+
name: "Agent",
|
|
19
|
+
instructions: "You are a helpful agent.",
|
|
20
|
+
model: "gpt-4o-mini"
|
|
21
|
+
)
|
|
22
|
+
messages = [{"role": "user", "content": "Hi!"}]
|
|
23
|
+
response = client.run(agent: agent, messages: messages)
|
|
24
|
+
p response.messages.last["content"]
|
|
25
|
+
# => "Hello! How can I assist you today?"
|
|
26
|
+
p response.messages
|
|
27
|
+
# => [{"role"=>"assistant", "content"=>"Hello! How can I assist you today?", "refusal"=>nil, :sender=>"Agent"}]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# link: https://github.com/openai/swarm/blob/main/examples/basic/context_variables.py
|
|
2
|
+
|
|
3
|
+
# OpenAI.configure do |config|
|
|
4
|
+
# config.access_token = ENV['OPENAI_ACCESS_TOKEN']
|
|
5
|
+
# end
|
|
6
|
+
OpenAI.configure do |config|
|
|
7
|
+
config.access_token = ENV['OPEN_ROUTER_ACCESS_TOKEN']
|
|
8
|
+
config.uri_base = "https://openrouter.ai/api/v1"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
client = OpenAISwarm.new
|
|
12
|
+
|
|
13
|
+
def instructions(context_variables)
|
|
14
|
+
name = context_variables.fetch(:name, :User)
|
|
15
|
+
"You are a helpful agent. Greet the user by name (#{name})."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def print_account_details(context_variables: {})
|
|
19
|
+
puts "print_account_details context_variables: #{context_variables.inspect}"
|
|
20
|
+
|
|
21
|
+
user_id = context_variables[:user_id]
|
|
22
|
+
name = context_variables[:name]
|
|
23
|
+
puts "Account Details: name: #{name}, user_id: #{user_id}"
|
|
24
|
+
"Success"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
function_instance = OpenAISwarm::FunctionDescriptor.new(
|
|
28
|
+
target_method: :print_account_details,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
agent = OpenAISwarm::Agent.new(
|
|
32
|
+
name: "Agent",
|
|
33
|
+
instructions: method(:instructions),
|
|
34
|
+
model: "gpt-4o-mini",
|
|
35
|
+
functions: [function_instance]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
context_variables = { 'name': 'James', 'user_id': 123 }
|
|
39
|
+
|
|
40
|
+
# debugger logger: {:model=>"gpt-4o-mini", :messages=>[{:role=>"system", :content=>"You are a helpful agent. Greet the user by name (James)."}, {:role=>"user", :content=>"Hi!"}], :tools=>[{:type=>"function", :function=>{:name=>"print_account_details", :description=>"", :parameters=>{:type=>"object", :properties=>{}, :required=>[]}}}], :stream=>false, :parallel_tool_calls=>true}
|
|
41
|
+
response = client.run(
|
|
42
|
+
messages: [{"role": "user", "content": "Hi!"}],
|
|
43
|
+
agent: agent,
|
|
44
|
+
context_variables: context_variables,
|
|
45
|
+
debug: true,
|
|
46
|
+
)
|
|
47
|
+
msg = response.messages.last
|
|
48
|
+
# Hello, James! How can I assist you today?
|
|
49
|
+
|
|
50
|
+
msg['content'].include?('James')
|
|
51
|
+
|
|
52
|
+
# debugger logger: {:model=>"gpt-4o-mini", :messages=>[{:role=>"system", :content=>"You are a helpful agent. Greet the user by name (James)."}, {:role=>"user", :content=>"Print my account details!"}], :tools=>[{:type=>"function", :function=>{:name=>"print_account_details", :description=>"", :parameters=>{:type=>"object", :properties=>{}, :required=>[]}}}], :stream=>false, :parallel_tool_calls=>true
|
|
53
|
+
response = client.run(
|
|
54
|
+
messages: [{"role": "user", "content": "Print my account details!"}],
|
|
55
|
+
agent: agent,
|
|
56
|
+
context_variables: context_variables,
|
|
57
|
+
debug: true,
|
|
58
|
+
)
|
|
59
|
+
msg = response.messages.last
|
|
60
|
+
msg['content']
|
|
61
|
+
response.context_variables == {:name=>"James", :user_id=>123}
|
|
62
|
+
|
|
63
|
+
# print(response.messages[-1]["content"])
|
|
64
|
+
# Hello, James! Your account details have been printed successfully. If you need anything else, just let me know!
|
|
65
|
+
#
|
|
66
|
+
# print_account_details context_variables: {:name=>"James", :user_id=>123}
|
|
67
|
+
# Account Details: name: James, user_id: 123
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
|
|
2
|
+
OpenAI.configure do |config|
|
|
3
|
+
config.access_token = ENV['OPEN_ROUTER_ACCESS_TOKEN']
|
|
4
|
+
config.uri_base = "https://openrouter.ai/api/v1"
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
client = OpenAISwarm.new
|
|
8
|
+
|
|
9
|
+
def get_weather(location:)
|
|
10
|
+
"{'temp':67, 'unit':'F'}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
function_instance = OpenAISwarm::FunctionDescriptor.new(
|
|
14
|
+
target_method: :get_weather,
|
|
15
|
+
description: 'Simulate fetching weather data'
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
agent = OpenAISwarm::Agent.new(
|
|
19
|
+
name: "Agent",
|
|
20
|
+
instructions: "You are a helpful agent.",
|
|
21
|
+
model: "gpt-4o-mini",
|
|
22
|
+
functions: [function_instance]
|
|
23
|
+
)
|
|
24
|
+
# debugger logger: {:model=>"gpt-4o-mini", :messages=>[{:role=>"system", :content=>"You are a helpful agent."}, {"role"=>"user", "content"=>"What's the weather in NYC?"}], :tools=>[{:type=>"function", :function=>{:name=>"get_weather", :description=>"", :parameters=>{:type=>"object", :properties=>{:location=>{:type=>"string"}}, :required=>["location"]}}}], :stream=>false, :parallel_tool_calls=>true}
|
|
25
|
+
response = client.run(
|
|
26
|
+
messages: [{"role" => "user", "content" => "What's the weather in NYC?"}],
|
|
27
|
+
agent: agent,
|
|
28
|
+
debug: true,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# print(response.messages[-1]["content"])
|
|
32
|
+
# The current temperature in New York City is 67°F. => nil
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
OpenAI.configure do |config|
|
|
3
|
+
config.access_token = ENV['OPEN_ROUTER_ACCESS_TOKEN']
|
|
4
|
+
config.uri_base = "https://openrouter.ai/api/v1"
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
client = OpenAISwarm.new
|
|
8
|
+
|
|
9
|
+
agent = OpenAISwarm::Agent.new(
|
|
10
|
+
name: "Agent",
|
|
11
|
+
instructions: "You are a helpful agent.",
|
|
12
|
+
model: "gpt-4o-mini",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
def pretty_print_messages(messages)
|
|
16
|
+
messages.each do |message|
|
|
17
|
+
next if message["content"].nil?
|
|
18
|
+
puts "#{message["sender"]}: #{message["content"]}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
messages = []
|
|
23
|
+
agent = my_agent
|
|
24
|
+
loop do
|
|
25
|
+
print "> "
|
|
26
|
+
user_input = gets.chomp
|
|
27
|
+
|
|
28
|
+
break if user_input.downcase == "exit"
|
|
29
|
+
|
|
30
|
+
messages << { "role": "user", "content": user_input }
|
|
31
|
+
response = client.run(agent: agent, messages: messages)
|
|
32
|
+
|
|
33
|
+
messages.concat(response.messages)
|
|
34
|
+
agent = response.agent
|
|
35
|
+
|
|
36
|
+
pretty_print_messages(response.messages)
|
|
37
|
+
end
|
|
38
|
+
puts "Goodbye!"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Triage agent
|
|
2
|
+
|
|
3
|
+
This example is a Swarm containing a triage agent, which takes in user inputs and chooses whether to respond directly, or triage the request
|
|
4
|
+
to a sales or refunds agent.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
|
|
8
|
+
To run the triage agent Swarm:
|
|
9
|
+
|
|
10
|
+
1. Run
|
|
11
|
+
|
|
12
|
+
```shell
|
|
13
|
+
ruby main.rb
|
|
14
|
+
```
|
|
15
|
+
or
|
|
16
|
+
|
|
17
|
+
```shell
|
|
18
|
+
ruby examples/triage_agent/main.rb
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Evals (TODO)
|
|
22
|
+
|
|
23
|
+
> [!NOTE]
|
|
24
|
+
> These evals are intended to be examples to demonstrate functionality, but will have to be updated and catered to your particular use case.
|
|
25
|
+
|
|
26
|
+
This example uses `Pytest` to run eval unit tests. We have two tests in the `evals.py` file, one which
|
|
27
|
+
tests if we call the correct triage function when expected, and one which assesses if a conversation
|
|
28
|
+
is 'successful', as defined in our prompt in `evals.py`.
|
|
29
|
+
|
|
30
|
+
To run the evals, run
|
|
31
|
+
|
|
32
|
+
```shell
|
|
33
|
+
ruby evals.rb
|
|
34
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require "bundler/setup"
|
|
2
|
+
require "ruby-openai-swarm"
|
|
3
|
+
|
|
4
|
+
OpenAI.configure do |config|
|
|
5
|
+
config.access_token = ENV['OPEN_ROUTER_ACCESS_TOKEN']
|
|
6
|
+
config.uri_base = "https://openrouter.ai/api/v1"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def client
|
|
10
|
+
OpenAISwarm.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def process_refund(item_id, reason = "NOT SPECIFIED")
|
|
14
|
+
# Refund an item. Make sure you have the item_id of the form item_...
|
|
15
|
+
# Ask for user confirmation before processing the refund.
|
|
16
|
+
puts "[mock] Refunding item #{item_id} because #{reason}..."
|
|
17
|
+
"Success!"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def process_refund_function_instance
|
|
21
|
+
OpenAISwarm::FunctionDescriptor.new(
|
|
22
|
+
target_method: :process_refund,
|
|
23
|
+
description: "Refund an item. Make sure you have the item_id of the form item_...Ask for user confirmation before processing the refund."
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def apply_discount
|
|
28
|
+
# Apply a discount to the user's cart.
|
|
29
|
+
puts "[mock] Applying discount..."
|
|
30
|
+
"Applied discount of 11%"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def apply_discount_function_instance
|
|
34
|
+
OpenAISwarm::FunctionDescriptor.new(
|
|
35
|
+
target_method: :apply_discount,
|
|
36
|
+
description: "Apply a discount to the user's cart."
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def triage_agent
|
|
41
|
+
@triage_agent ||=
|
|
42
|
+
OpenAISwarm::Agent.new(
|
|
43
|
+
name: "Triage Agent",
|
|
44
|
+
instructions: "Determine which agent is best suited to handle the user's request, and transfer the conversation to that agent."
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def sales_agent
|
|
49
|
+
@sales_agent ||=
|
|
50
|
+
OpenAISwarm::Agent.new(
|
|
51
|
+
name: "Sales Agent",
|
|
52
|
+
instructions: "Be super enthusiastic about selling bees."
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def refunds_agent
|
|
57
|
+
@refunds_agent ||=
|
|
58
|
+
OpenAISwarm::Agent.new(
|
|
59
|
+
name: "Refunds Agent",
|
|
60
|
+
instructions: "Help the user with a refund. If the reason is that it was too expensive, offer the user a refund code. If they insist, then process the refund.",
|
|
61
|
+
functions: [
|
|
62
|
+
process_refund_function_instance,
|
|
63
|
+
apply_discount_function_instance
|
|
64
|
+
]
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
transfer_back_to_triage = OpenAISwarm::FunctionDescriptor.new(
|
|
69
|
+
target_method: :triage_agent,
|
|
70
|
+
description: "Call this function if a user is asking about a topic that is not handled by the current agent."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def transfer_to_sales
|
|
74
|
+
sales_agent
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def transfer_to_refunds
|
|
78
|
+
refunds_agent
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Assign functions to agents
|
|
82
|
+
triage_agent.functions = [method(:transfer_to_sales), method(:transfer_to_refunds)]
|
|
83
|
+
sales_agent.functions << transfer_back_to_triage
|
|
84
|
+
refunds_agent.functions << transfer_back_to_triage
|
|
File without changes
|