ruby-agents 0.1.2
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/.gitignore +59 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +48 -0
- data/LICENSE +21 -0
- data/README.md +43 -0
- data/agents.gemspec +25 -0
- data/lib/actions/action.rb +43 -0
- data/lib/actions/action_argument.rb +14 -0
- data/lib/actions/action_example.rb +14 -0
- data/lib/agents/agent.rb +24 -0
- data/lib/agents/dispatcher.rb +29 -0
- data/lib/agents/echo_agent.rb +12 -0
- data/lib/agents/gpt_agents/calendar_gpt_agent.rb +17 -0
- data/lib/agents/gpt_agents/categorizing_gpt_agent.rb +19 -0
- data/lib/agents/gpt_agents/dispatching_gpt_agent.rb +27 -0
- data/lib/agents/gpt_agents/echo_gpt_agent.rb +12 -0
- data/lib/agents/gpt_agents/gpt_agent.rb +29 -0
- data/lib/agents/gpt_agents/information_retrieval_gpt_agent.rb +17 -0
- data/lib/agents/gpt_agents/promptless_gpt_agent.rb +10 -0
- data/lib/agents/gpt_agents/todo_gpt_agent.rb +31 -0
- data/lib/agents/unhandleable_request_agent.rb +8 -0
- data/lib/agents.rb +39 -0
- data/lib/gpt_clients/echo_gpt_client.rb +11 -0
- data/lib/gpt_clients/gpt_client.rb +14 -0
- data/lib/gpt_clients/open_ai_gpt_client.rb +40 -0
- data/lib/logger.rb +13 -0
- data/lib/requests/request.rb +8 -0
- data/lib/responses/gpt_response.rb +51 -0
- data/lib/responses/response.rb +8 -0
- data/lib/responses/unhandleable_request_response.rb +8 -0
- data/lib/version.rb +3 -0
- data/test/test_actions.rb +87 -0
- data/test/test_dispatcher.rb +38 -0
- data/test/test_gpt_dispatcher.rb +25 -0
- data/test/test_openai.rb +73 -0
- data/test/test_todo_gpt_agent.rb +51 -0
- metadata +105 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: ca0396754afd0be4aac5df2f2c120e72d2898eea74f52df8fdfc8c414a941e70
|
|
4
|
+
data.tar.gz: 8805783b3594ed85b56658d2a0f8e9386ce6e9cdf1f18c4a9eaa0d12c6f7a7f6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1b08f556416b78e11f9ab6d13f25c3f7f6b7a5c841c859b70d674fe37c91f62f36a3734ee0a388aea0584a5786e495e38b8ab3eb513265a95bfadc46cb1af8db
|
|
7
|
+
data.tar.gz: 5dba6e632f9d6cf3c452a796297fbcb191a02296cb0197889bfdc69b28d581f454b74882f47b0a56126ca015b0ce5be2bbb7dd250fa3d316d3af6ca65cd24e47
|
data/.gitignore
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
|
|
13
|
+
# Used by dotenv library to load environment variables.
|
|
14
|
+
# .env
|
|
15
|
+
|
|
16
|
+
# Ignore Byebug command history file.
|
|
17
|
+
.byebug_history
|
|
18
|
+
|
|
19
|
+
## Specific to RubyMotion:
|
|
20
|
+
.dat*
|
|
21
|
+
.repl_history
|
|
22
|
+
build/
|
|
23
|
+
*.bridgesupport
|
|
24
|
+
build-iPhoneOS/
|
|
25
|
+
build-iPhoneSimulator/
|
|
26
|
+
|
|
27
|
+
## Specific to RubyMotion (use of CocoaPods):
|
|
28
|
+
#
|
|
29
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
|
30
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
|
31
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
|
32
|
+
#
|
|
33
|
+
# vendor/Pods/
|
|
34
|
+
|
|
35
|
+
## Documentation cache and generated files:
|
|
36
|
+
/.yardoc/
|
|
37
|
+
/_yardoc/
|
|
38
|
+
/doc/
|
|
39
|
+
/rdoc/
|
|
40
|
+
|
|
41
|
+
## Environment normalization:
|
|
42
|
+
/.bundle/
|
|
43
|
+
/vendor/bundle
|
|
44
|
+
/lib/bundler/man/
|
|
45
|
+
|
|
46
|
+
# for a library or gem, you might want to ignore these files since the code is
|
|
47
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
48
|
+
# Gemfile.lock
|
|
49
|
+
# .ruby-version
|
|
50
|
+
# .ruby-gemset
|
|
51
|
+
|
|
52
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
|
53
|
+
.rvmrc
|
|
54
|
+
|
|
55
|
+
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
|
56
|
+
# .rubocop-https?--*
|
|
57
|
+
|
|
58
|
+
.idea
|
|
59
|
+
.DS_Store
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
ruby-agents (0.1.2)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: http://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
attr_extras (7.1.0)
|
|
10
|
+
base64 (0.1.1)
|
|
11
|
+
concurrent-ruby (1.2.2)
|
|
12
|
+
diff-lcs (1.5.0)
|
|
13
|
+
event_stream_parser (0.3.0)
|
|
14
|
+
faraday (2.7.11)
|
|
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
|
+
multipart-post (2.3.0)
|
|
22
|
+
optimist (3.1.0)
|
|
23
|
+
patience_diff (1.2.0)
|
|
24
|
+
optimist (~> 3.0)
|
|
25
|
+
ruby-openai (6.0.0)
|
|
26
|
+
event_stream_parser (>= 0.3.0, < 1.0.0)
|
|
27
|
+
faraday (>= 1)
|
|
28
|
+
faraday-multipart (>= 1)
|
|
29
|
+
ruby2_keywords (0.0.5)
|
|
30
|
+
super_diff (0.10.0)
|
|
31
|
+
attr_extras (>= 6.2.4)
|
|
32
|
+
diff-lcs
|
|
33
|
+
patience_diff
|
|
34
|
+
tldr (0.9.5)
|
|
35
|
+
concurrent-ruby (~> 1.2)
|
|
36
|
+
super_diff (~> 0.10)
|
|
37
|
+
|
|
38
|
+
PLATFORMS
|
|
39
|
+
arm64-darwin-21
|
|
40
|
+
arm64-darwin-22
|
|
41
|
+
|
|
42
|
+
DEPENDENCIES
|
|
43
|
+
ruby-agents!
|
|
44
|
+
ruby-openai (>= 5.1)
|
|
45
|
+
tldr (>= 0.9.5)
|
|
46
|
+
|
|
47
|
+
BUNDLED WITH
|
|
48
|
+
2.4.19
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Jeff McFadden
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# agents
|
|
2
|
+
|
|
3
|
+
A ruby library for building and managing AI agents within your application.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'agents', git: "jeffmcfadden/agents"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`bundle install`
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require 'agents'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Built-in Agents
|
|
22
|
+
|
|
23
|
+
### Building Your Own Agents
|
|
24
|
+
|
|
25
|
+
### Using a Dispatcher
|
|
26
|
+
|
|
27
|
+
### Running Actions
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## Testing
|
|
31
|
+
|
|
32
|
+
Use `tldr` to run the tests.
|
|
33
|
+
|
|
34
|
+
```shell
|
|
35
|
+
$ bundle install
|
|
36
|
+
$ tldr --watch
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
By default, no network calls are made while testing. If you want to test the full path into OpenAI, you can run that with:
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
$ OPENAI_ACCESS_TOKEN="sk-123YourToken" bundle exec ruby test/test_openai.rb
|
|
43
|
+
```
|
data/agents.gemspec
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'logger'
|
|
5
|
+
require_relative 'lib/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |s|
|
|
8
|
+
s.name = "ruby-agents"
|
|
9
|
+
s.version = Agents::VERSION
|
|
10
|
+
s.platform = Gem::Platform::RUBY
|
|
11
|
+
|
|
12
|
+
s.authors = ["Jeff McFadden"]
|
|
13
|
+
s.date = "2023-11-05"
|
|
14
|
+
s.description = "Put AI to Work."
|
|
15
|
+
s.email = "55709+jeffmcfadden@users.noreply.github.com"
|
|
16
|
+
s.files = `git ls-files`.split("\n")
|
|
17
|
+
|
|
18
|
+
s.homepage = "https://github.com/jeffmcfadden/agents"
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
s.rubygems_version = Agents::VERSION
|
|
21
|
+
s.summary = "Setup and organize requests between multiple AI agents."
|
|
22
|
+
|
|
23
|
+
s.add_development_dependency "tldr", ">= 0.9.5"
|
|
24
|
+
s.add_development_dependency "ruby-openai", ">= 5.1"
|
|
25
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
|
|
3
|
+
# Takes a block and executes it when the action is called.
|
|
4
|
+
class Action
|
|
5
|
+
attr_reader :name, :description, :arguments, :examples
|
|
6
|
+
|
|
7
|
+
# Create a new action
|
|
8
|
+
# @param [String] name
|
|
9
|
+
# @param [String] description
|
|
10
|
+
# @param [Array<ActionArgument>] arguments
|
|
11
|
+
# @param [Array<ActionExample>] examples
|
|
12
|
+
# @param [Block] block
|
|
13
|
+
def initialize(name:, description: "", arguments: [], examples: [], &block)
|
|
14
|
+
@name = name
|
|
15
|
+
@description = description
|
|
16
|
+
@arguments = arguments
|
|
17
|
+
@examples = examples
|
|
18
|
+
@block = block
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Execute the block with the given arguments.
|
|
22
|
+
def call(args)
|
|
23
|
+
@block.call(args)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def for_prompt
|
|
27
|
+
s = "#{name}: #{description}\n"
|
|
28
|
+
|
|
29
|
+
unless arguments.empty?
|
|
30
|
+
s << " Arguments:\n"
|
|
31
|
+
s << arguments.collect(&:for_prompt).join("\n") << "\n"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
unless examples.empty?
|
|
35
|
+
s << " For example:\n"
|
|
36
|
+
s << examples.collect(&:for_prompt).join("\n") << "\n"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
class ActionArgument
|
|
3
|
+
attr_reader :name, :type, :description
|
|
4
|
+
def initialize(name:, type:, description:)
|
|
5
|
+
@name = name
|
|
6
|
+
@type = type
|
|
7
|
+
@description = description
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def for_prompt
|
|
11
|
+
" #{name}: #{description}"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/agents/agent.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
class Agent
|
|
3
|
+
attr_reader :actions
|
|
4
|
+
|
|
5
|
+
def initialize(actions: [])
|
|
6
|
+
@actions = actions
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# @param [Request] request
|
|
10
|
+
# @return [Response] response
|
|
11
|
+
def handle(request:)
|
|
12
|
+
Response.new("Sorry, this agent doesn't know how to handle requests.")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def register(action:)
|
|
16
|
+
@actions << action
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def unregister(action:)
|
|
20
|
+
@actions.delete(action)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
class Dispatcher < Agent
|
|
3
|
+
attr_reader :agents
|
|
4
|
+
def initialize(agents: [], **args)
|
|
5
|
+
super(**args)
|
|
6
|
+
@agents = agents
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def register(agent)
|
|
10
|
+
@agents << agent
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def unregister(agent)
|
|
14
|
+
@agents.delete(agent)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def handle(request:)
|
|
18
|
+
agent = agent_for_request(request: request)
|
|
19
|
+
agent.handle(request: request)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Default behavior is a random agent. Use a subclass to specify behavior.
|
|
23
|
+
def agent_for_request(request:)
|
|
24
|
+
return UnhandleableRequestAgent.new if agents.empty?
|
|
25
|
+
|
|
26
|
+
@agents.shuffle.first
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
class CalendarGptAgent < GptAgent
|
|
3
|
+
|
|
4
|
+
attr_reader :system_prompt
|
|
5
|
+
def initialize(**args)
|
|
6
|
+
super(**args)
|
|
7
|
+
@description = "I am an AI Assistant that specializes in Calendar and Scheduling Management for Appointments, meetings, etc."
|
|
8
|
+
@system_prompt = <<~EOS
|
|
9
|
+
You are a helpful assistant.
|
|
10
|
+
EOS
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def handle(request:)
|
|
14
|
+
gpt_client.chat system_prompt: system_prompt, prompt: request.request_text
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
class CategorizingGptAgent < GptAgent
|
|
3
|
+
|
|
4
|
+
attr_reader :system_prompt
|
|
5
|
+
def initialize(**args)
|
|
6
|
+
super(**args)
|
|
7
|
+
@system_prompt = <<~EOS
|
|
8
|
+
You are a helpful assistant specializing in categorization. For each request to follow,
|
|
9
|
+
please respond with a JSON object with a single key, 'category'. Each request will include
|
|
10
|
+
the valid values for the category key.
|
|
11
|
+
EOS
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [GPTResponse] response
|
|
15
|
+
def handle(request:)
|
|
16
|
+
gpt_client.chat system_prompt: system_prompt, prompt: request.request_text
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
class DispatchingGptAgent < Dispatcher
|
|
3
|
+
attr_reader :gpt_client
|
|
4
|
+
|
|
5
|
+
def initialize(gpt_client:, **args)
|
|
6
|
+
super(**args)
|
|
7
|
+
@gpt_client = gpt_client
|
|
8
|
+
end
|
|
9
|
+
def agent_for_request(request:)
|
|
10
|
+
categorize_request = Request.new(prompt_for_categorizing_request(request: request))
|
|
11
|
+
|
|
12
|
+
response_data = CategorizingGptAgent.new(gpt_client: gpt_client)
|
|
13
|
+
.handle(request: categorize_request).data
|
|
14
|
+
|
|
15
|
+
agent_class = response_data["category"]
|
|
16
|
+
agents.find{ _1.class.name == agent_class } || UnhandleableRequestAgent.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def prompt_for_categorizing_request(request:)
|
|
20
|
+
prompt = "The following is a list of Assistants and a description of what each can do: \n\n"
|
|
21
|
+
prompt << agents.collect{ "#{_1.class.name}: #{_1.description}" }.join("\n\n")
|
|
22
|
+
prompt << "If you are not sure which Assistant to use, it's okay to ask for more information.\n\n"
|
|
23
|
+
prompt << "Please categorize the request by typing the name of the Assistant that best fits the request.\n\n"
|
|
24
|
+
prompt << "Request: #{request.request_text}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agents
|
|
4
|
+
# An agent that echoes back the request text. Useful for testing.
|
|
5
|
+
class EchoGptAgent < Agent
|
|
6
|
+
|
|
7
|
+
# @param [Request] request
|
|
8
|
+
def handle(request:)
|
|
9
|
+
GptResponse.new(request.request_text)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
class GptAgent < Agent
|
|
3
|
+
attr_reader :gpt_client
|
|
4
|
+
attr_reader :description
|
|
5
|
+
attr_reader :system_prompt
|
|
6
|
+
|
|
7
|
+
def initialize(gpt_client:, description: "An AI Chatbot", system_prompt: "", **args)
|
|
8
|
+
super(**args)
|
|
9
|
+
@gpt_client = gpt_client
|
|
10
|
+
@description = description
|
|
11
|
+
@system_prompt = system_prompt
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def handle(request:)
|
|
15
|
+
response = gpt_client.chat system_prompt: system_prompt, prompt: "#{prompt_prefix}#{request.request_text}"
|
|
16
|
+
|
|
17
|
+
response.suggested_actions.each do |action|
|
|
18
|
+
actions.find{ |a| a.name == action["name"] }&.call(action["args"])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
response
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def prompt_prefix
|
|
25
|
+
""
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
class InformationRetrievalGptAgent < GptAgent
|
|
3
|
+
|
|
4
|
+
attr_reader :system_prompt
|
|
5
|
+
def initialize(**args)
|
|
6
|
+
super(**args)
|
|
7
|
+
@description = "I am an AI Assistant that specializes in fetching information and answering general questions, etc."
|
|
8
|
+
@system_prompt = <<~EOS
|
|
9
|
+
You are a helpful assistant.
|
|
10
|
+
EOS
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def handle(request:)
|
|
14
|
+
gpt_client.chat system_prompt: system_prompt, prompt: request.request_text
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
# An agent that does not add a prompt to the request, but simply passes it through.
|
|
3
|
+
# Use if your test _is _the prompt, for example.
|
|
4
|
+
class PromptlessGptAgent < GptAgent
|
|
5
|
+
def handle(request:)
|
|
6
|
+
Response.new(response_text: gpt_client.chat(prompt: request.request_text))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
class TodoGptAgent < GptAgent
|
|
3
|
+
|
|
4
|
+
def initialize(**args)
|
|
5
|
+
super(**args)
|
|
6
|
+
@description = "I am an AI Assistant that specializes in Todo List and Reminders Management."
|
|
7
|
+
@system_prompt = <<~EOS
|
|
8
|
+
You are a helpful assistant specializing in Todo List and Reminders Management. Please do your best to complete
|
|
9
|
+
any requests you are given. For each request, please respond ONLY with a JSON object. The JSON object should have
|
|
10
|
+
two top-level keys: `response` and `actions`. The `response` key should be a short string that summarizes the actions
|
|
11
|
+
to be taken. The `actions` key should be an array of JSON objects, each of which has a `name` key and an `args` key.
|
|
12
|
+
If you aren't sure what actions should be taken, or you don't think there's anything relevant, then respond with
|
|
13
|
+
an empty array for the `actions` key.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
EOS
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def system_prompt
|
|
20
|
+
prompt = @system_prompt
|
|
21
|
+
|
|
22
|
+
unless self.actions.empty?
|
|
23
|
+
prompt << "The following actions are supported: \n"
|
|
24
|
+
prompt << self.actions.collect(&:for_prompt).join("\n\n")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
prompt << "\n\n"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/agents.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require_relative 'logger'
|
|
2
|
+
require_relative 'version'
|
|
3
|
+
|
|
4
|
+
require 'openai' rescue nil # OpenAI is optional
|
|
5
|
+
|
|
6
|
+
require_relative 'gpt_clients/gpt_client'
|
|
7
|
+
require_relative 'gpt_clients/echo_gpt_client'
|
|
8
|
+
|
|
9
|
+
if defined?(::OpenAI::Client)
|
|
10
|
+
require_relative 'gpt_clients/open_ai_gpt_client'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require_relative 'agents/agent'
|
|
14
|
+
require_relative 'agents/dispatcher'
|
|
15
|
+
require_relative 'agents/echo_agent'
|
|
16
|
+
|
|
17
|
+
require_relative 'agents/gpt_agents/gpt_agent'
|
|
18
|
+
require_relative 'agents/gpt_agents/calendar_gpt_agent'
|
|
19
|
+
require_relative 'agents/gpt_agents/categorizing_gpt_agent'
|
|
20
|
+
require_relative 'agents/gpt_agents/dispatching_gpt_agent'
|
|
21
|
+
require_relative 'agents/gpt_agents/information_retrieval_gpt_agent'
|
|
22
|
+
require_relative 'agents/gpt_agents/promptless_gpt_agent'
|
|
23
|
+
require_relative 'agents/gpt_agents/todo_gpt_agent'
|
|
24
|
+
|
|
25
|
+
require_relative 'agents/unhandleable_request_agent'
|
|
26
|
+
|
|
27
|
+
require_relative 'requests/request'
|
|
28
|
+
|
|
29
|
+
require_relative 'responses/response'
|
|
30
|
+
require_relative 'responses/gpt_response'
|
|
31
|
+
require_relative 'responses/unhandleable_request_response'
|
|
32
|
+
|
|
33
|
+
require_relative 'actions/action'
|
|
34
|
+
require_relative 'actions/action_argument'
|
|
35
|
+
require_relative 'actions/action_example'
|
|
36
|
+
|
|
37
|
+
module Agents
|
|
38
|
+
|
|
39
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
class EchoGptClient < GptClient
|
|
3
|
+
|
|
4
|
+
# Returns the input prompt as the output.
|
|
5
|
+
# @param [String] prompt
|
|
6
|
+
# @return [GPTResponse] response The response will be the same as the prompt.
|
|
7
|
+
def chat(prompt: "", **args)
|
|
8
|
+
GptResponse.new(prompt.to_s)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Agents
|
|
4
|
+
class GptClient
|
|
5
|
+
def initialize()
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Chats via the GPT Client, returns a GPTResponse
|
|
9
|
+
# @return [GPTResponse] response
|
|
10
|
+
def chat(prompt: "", **args)
|
|
11
|
+
GptResponse.new("Error: No GPT Client Setup.")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
class OpenAiGptClient < GptClient
|
|
3
|
+
attr_reader :open_ai_client
|
|
4
|
+
attr_reader :default_params
|
|
5
|
+
|
|
6
|
+
def initialize(open_ai_client:, default_params: {}, **args)
|
|
7
|
+
super(**args)
|
|
8
|
+
@open_ai_client = open_ai_client
|
|
9
|
+
@default_params = {
|
|
10
|
+
model: "gpt-3.5-turbo-1106",
|
|
11
|
+
temperature: 0.7,
|
|
12
|
+
}.merge(default_params)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def chat(prompt: "", **args)
|
|
16
|
+
messages = []
|
|
17
|
+
messages << { role: "system", content: "#{args[:system_prompt] || 'You are a helpful assistant.'}" }
|
|
18
|
+
messages << { role: "user", content: "#{prompt}" }
|
|
19
|
+
|
|
20
|
+
Agents.logger.debug("Sending to ChatGPT:")
|
|
21
|
+
Agents.logger.debug(messages)
|
|
22
|
+
|
|
23
|
+
parameters = default_params.merge({messages: messages})
|
|
24
|
+
|
|
25
|
+
completions = open_ai_client.chat parameters: parameters
|
|
26
|
+
|
|
27
|
+
Agents.logger.debug("ChatGPT Responded:")
|
|
28
|
+
Agents.logger.debug(completions)
|
|
29
|
+
|
|
30
|
+
text = completions.dig("choices", 0, "message", "content")
|
|
31
|
+
|
|
32
|
+
Agents.logger.debug("-" * 80)
|
|
33
|
+
Agents.logger.debug text
|
|
34
|
+
Agents.logger.debug("-" * 80)
|
|
35
|
+
|
|
36
|
+
GptResponse.new(text)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/logger.rb
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Agents
|
|
2
|
+
# The response from a GPT request.
|
|
3
|
+
class GptResponse < Response
|
|
4
|
+
|
|
5
|
+
attr_reader :raw_text
|
|
6
|
+
attr_reader :response_text
|
|
7
|
+
attr_reader :suggested_actions
|
|
8
|
+
attr_reader :data
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# @param [String] raw_text The raw text the GPT service returned.
|
|
12
|
+
def initialize(raw_text)
|
|
13
|
+
@raw_text = raw_text
|
|
14
|
+
|
|
15
|
+
begin
|
|
16
|
+
@data = extract_json_data(raw_text)
|
|
17
|
+
@response_text = data["response"] || raw_text
|
|
18
|
+
@suggested_actions = data["actions"] || []
|
|
19
|
+
rescue
|
|
20
|
+
@data = {}
|
|
21
|
+
@response_text = raw_text
|
|
22
|
+
@suggested_actions = []
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# TODO: Extract this into an object:
|
|
27
|
+
def extract_json_data(string)
|
|
28
|
+
# Check if the string is valid JSON.
|
|
29
|
+
begin
|
|
30
|
+
json_obj = JSON.parse(string)
|
|
31
|
+
return json_obj
|
|
32
|
+
rescue JSON::ParserError => error
|
|
33
|
+
Agents.logger.info "Whole string was not pure JSON. #{error}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Search for JSON using regular expression
|
|
37
|
+
match_data = string.match(/\{.*\}/m)
|
|
38
|
+
if match_data
|
|
39
|
+
begin
|
|
40
|
+
Agents.logger.info "Found some JSON in the string."
|
|
41
|
+
|
|
42
|
+
json_obj = JSON.parse(match_data[0])
|
|
43
|
+
return json_obj
|
|
44
|
+
rescue JSON::ParserError
|
|
45
|
+
Agents.logger.info "Extracted string was not pure JSON. #{error}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/version.rb
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require 'agents'
|
|
2
|
+
|
|
3
|
+
module Agents
|
|
4
|
+
class TestActions < TLDR
|
|
5
|
+
|
|
6
|
+
def setup
|
|
7
|
+
@gpt_client = EchoGptClient.new
|
|
8
|
+
@dispatcher = DispatchingGptAgent.new(gpt_client: @gpt_client)
|
|
9
|
+
@agent = TodoGptAgent.new(gpt_client: @gpt_client)
|
|
10
|
+
|
|
11
|
+
@dispatcher.register(@agent)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_dispatching_prompt
|
|
15
|
+
test_action = Action.new(name: "test_action") do |args|
|
|
16
|
+
puts "This is a test action"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@agent.register(action: test_action)
|
|
20
|
+
|
|
21
|
+
# This is a JSON payload because the EchoGPTClient will echo this back as the response
|
|
22
|
+
test_payload = {
|
|
23
|
+
"response": "I added peanut butter to your grocery list.",
|
|
24
|
+
"actions": [
|
|
25
|
+
{
|
|
26
|
+
"name": "test_action",
|
|
27
|
+
"args": [
|
|
28
|
+
{
|
|
29
|
+
"todo_list": "Grocery",
|
|
30
|
+
"item_title": "peanut butter"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test_request = Request.new(test_payload.to_json)
|
|
38
|
+
|
|
39
|
+
response = @agent.handle(request: test_request)
|
|
40
|
+
|
|
41
|
+
assert_equal "{\"response\":\"I added peanut butter to your grocery list.\",\"actions\":[{\"name\":\"test_action\",\"args\":[{\"todo_list\":\"Grocery\",\"item_title\":\"peanut butter\"}]}]}", response.raw_text
|
|
42
|
+
# assert_equal "I added peanut butter to your grocery list.", response.response_text
|
|
43
|
+
# assert_equal response.suggested_actions.size, 1
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestablePerformingAction < Action
|
|
48
|
+
|
|
49
|
+
attr_reader :was_performed
|
|
50
|
+
def initialize(**args)
|
|
51
|
+
super(**args)
|
|
52
|
+
@was_performed = false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def call(args)
|
|
56
|
+
super(args)
|
|
57
|
+
@was_performed = true
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class TestActionPerformingGptAgent < GptAgent
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_performing_action
|
|
66
|
+
test_action = TestablePerformingAction.new(name: "test_action") do
|
|
67
|
+
return "This is a test action"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
agent = TestActionPerformingGptAgent.new(gpt_client: @gpt_client, system_prompt: "This is a test prompt", description: "This is a test description")
|
|
71
|
+
|
|
72
|
+
assert_equal false, test_action.was_performed
|
|
73
|
+
|
|
74
|
+
agent.register(action: test_action)
|
|
75
|
+
|
|
76
|
+
# This is a JSON payload because the EchoGPTClient will echo this back as the response
|
|
77
|
+
test_payload = { response: "I performed the test action for you", actions: [{ name: "test_action", args: {} }] }
|
|
78
|
+
|
|
79
|
+
test_request = Request.new(test_payload.to_json)
|
|
80
|
+
response = agent.handle(request: test_request)
|
|
81
|
+
|
|
82
|
+
assert_equal true, test_action.was_performed
|
|
83
|
+
assert_equal "I performed the test action for you", response.response_text
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'agents'
|
|
2
|
+
|
|
3
|
+
module Agents
|
|
4
|
+
class TestDispatcher < TLDR
|
|
5
|
+
|
|
6
|
+
def setup
|
|
7
|
+
@dispatcher = Dispatcher.new
|
|
8
|
+
@agent1 = Agent.new
|
|
9
|
+
@agent2 = Agent.new
|
|
10
|
+
|
|
11
|
+
@echo_agent = EchoAgent.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_registration
|
|
15
|
+
@dispatcher.register(@agent1)
|
|
16
|
+
@dispatcher.register(@agent2)
|
|
17
|
+
assert_equal(@dispatcher.agents, [@agent1, @agent2])
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_unregistration
|
|
21
|
+
@dispatcher.register(@agent1)
|
|
22
|
+
@dispatcher.register(@agent2)
|
|
23
|
+
@dispatcher.unregister(@agent1)
|
|
24
|
+
assert_equal(@dispatcher.agents, [@agent2])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_default_handling
|
|
28
|
+
@dispatcher.register(@echo_agent)
|
|
29
|
+
|
|
30
|
+
# Default handling is to select a random agent. In this case we have a single
|
|
31
|
+
# echoing agent, so we expect the codepath to be exercised, but we know it will
|
|
32
|
+
# always be that one agent.
|
|
33
|
+
response = @dispatcher.handle(request: Request.new("Test Request"))
|
|
34
|
+
assert_equal("Test Request", response.response_text )
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'agents'
|
|
2
|
+
|
|
3
|
+
module Agents
|
|
4
|
+
class TestGptDispatcher < TLDR
|
|
5
|
+
|
|
6
|
+
def setup
|
|
7
|
+
@gpt_client = EchoGptClient.new
|
|
8
|
+
@dispatcher = DispatchingGptAgent.new(gpt_client: @gpt_client)
|
|
9
|
+
@agent1 = TodoGptAgent.new(gpt_client: @gpt_client)
|
|
10
|
+
@agent2 = CalendarGptAgent.new(gpt_client: @gpt_client)
|
|
11
|
+
|
|
12
|
+
@dispatcher.register(@agent1)
|
|
13
|
+
@dispatcher.register(@agent2)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_dispatching_prompt
|
|
17
|
+
request = Request.new("I need to buy some bread")
|
|
18
|
+
|
|
19
|
+
expected = "The following is a list of Assistants and a description of what each can do: \n\nAgents::TodoGptAgent: I am an AI Assistant that specializes in Todo List and Reminders Management.\n\nAgents::CalendarGptAgent: I am an AI Assistant that specializes in Calendar and Scheduling Management for Appointments, meetings, etc.Please categorize the request by typing the name of the Assistant that best fits the request.\n\nRequest: I need to buy some bread"
|
|
20
|
+
|
|
21
|
+
assert_equal(expected, @dispatcher.prompt_for_categorizing_request(request: request))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
data/test/test_openai.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This actually tests the full Open AI call path. It can be slow, and tldr will
|
|
4
|
+
# fail after 1.8 seconds, so you need to run this separately.
|
|
5
|
+
|
|
6
|
+
# Run this file with:
|
|
7
|
+
# OPENAI_ACCESS_TOKEN="sk-123YourToken" bundle exec ruby test/test_openai.rb
|
|
8
|
+
require 'openai'
|
|
9
|
+
require 'agents'
|
|
10
|
+
|
|
11
|
+
module Agents
|
|
12
|
+
|
|
13
|
+
class ActionPerformingGptAgent < GptAgent
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class TestOpenAi
|
|
17
|
+
def initialize
|
|
18
|
+
@openai_client = OpenAI::Client.new(access_token: ENV['OPENAI_ACCESS_TOKEN'])
|
|
19
|
+
@gpt_client = Agents::OpenAiGptClient.new(open_ai_client: @openai_client)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_basic_chat
|
|
23
|
+
puts "-" * 60
|
|
24
|
+
puts "test_basic_chat:"
|
|
25
|
+
@agent = Agents::PromptlessGptAgent.new(gpt_client: @gpt_client)
|
|
26
|
+
response = @agent.handle(request: Request.new("Tell me a joke"))
|
|
27
|
+
puts response.response_text
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_dispatching_chat
|
|
31
|
+
puts "-" * 60
|
|
32
|
+
puts "test_dispatching_chat:"
|
|
33
|
+
dispatcher = Agents::DispatchingGptAgent.new(gpt_client: @gpt_client)
|
|
34
|
+
dispatcher.register(Agents::TodoGptAgent.new(gpt_client: @gpt_client))
|
|
35
|
+
dispatcher.register(Agents::CalendarGptAgent.new(gpt_client: @gpt_client))
|
|
36
|
+
dispatcher.register(Agents::InformationRetrievalGptAgent.new(gpt_client: @gpt_client))
|
|
37
|
+
|
|
38
|
+
response = dispatcher.handle(request: Request.new("I need to buy break and milk"))
|
|
39
|
+
puts response
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_todo_action
|
|
43
|
+
puts "-" * 60
|
|
44
|
+
puts "test_dispatching_chat:"
|
|
45
|
+
|
|
46
|
+
todo_agent = Agents::TodoGptAgent.new(gpt_client: @gpt_client)
|
|
47
|
+
|
|
48
|
+
add_todo_action = Action.new(name: "add_todo",
|
|
49
|
+
description: "Add a todo to a list",
|
|
50
|
+
arguments: [{name: "todo_list", description: "The name of the todo list to add the item to"},
|
|
51
|
+
{name: "item_title", description: "The title of the item to add to the list"}]
|
|
52
|
+
) do |**args|
|
|
53
|
+
puts "I added the todo"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
todo_agent.register(action: add_todo_action)
|
|
57
|
+
|
|
58
|
+
dispatcher = Agents::DispatchingGptAgent.new(gpt_client: @gpt_client)
|
|
59
|
+
dispatcher.register(todo_agent)
|
|
60
|
+
|
|
61
|
+
response = dispatcher.handle(request: Request.new("I need to buy break and milk"))
|
|
62
|
+
puts response.response_text
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Skip this test if we don't have an access token
|
|
69
|
+
unless ENV['OPENAI_ACCESS_TOKEN'].nil?
|
|
70
|
+
# Agents::TestOpenAi.new.test_basic_chat
|
|
71
|
+
# Agents::TestOpenAi.new.test_dispatching_chat
|
|
72
|
+
Agents::TestOpenAi.new.test_todo_action
|
|
73
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require 'agents'
|
|
2
|
+
|
|
3
|
+
module Agents
|
|
4
|
+
class TestTodoGptAgent < TLDR
|
|
5
|
+
|
|
6
|
+
def setup
|
|
7
|
+
@agent = TodoGptAgent.new(gpt_client: EchoGptClient.new)
|
|
8
|
+
|
|
9
|
+
add_todo_action = Action.new(name: "add_todo",
|
|
10
|
+
description: "Add a todo to a list",
|
|
11
|
+
arguments: [ActionArgument.new(name: "todo_list", type: "String", description: "The name of the todo list to add the item to"),
|
|
12
|
+
ActionArgument.new(name: "item_title", type: "String", description: "The title of the item to add to the list")],
|
|
13
|
+
examples: [ActionExample.new(input: "I'm out of milk and bread",
|
|
14
|
+
output: { response: "I added milk and bread to your grocery list.",
|
|
15
|
+
actions: [{name: "add_item", args:[{todo_list: "Grocery", "item_title": "milk"}]},
|
|
16
|
+
{name: "add_item", args:[{todo_list: "Grocery", "item_title": "bread"}]}
|
|
17
|
+
]}.to_json )]
|
|
18
|
+
) do |**args|
|
|
19
|
+
return "I added the todo"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
@agent.register(action: add_todo_action)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_prompt
|
|
26
|
+
expected_prompt = <<~EOS
|
|
27
|
+
You are a helpful assistant specializing in Todo List and Reminders Management. Please do your best to complete
|
|
28
|
+
any requests you are given. For each request, please respond ONLY with a JSON object. The JSON object should have
|
|
29
|
+
two top-level keys: `response` and `actions`. The `response` key should be a short string that summarizes the actions
|
|
30
|
+
to be taken. The `actions` key should be an array of JSON objects, each of which has a `name` key and an `args` key.
|
|
31
|
+
If you aren't sure what actions should be taken, or you don't think there's anything relevant, then respond with
|
|
32
|
+
an empty array for the `actions` key.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
The following actions are supported:
|
|
36
|
+
add_todo: Add a todo to a list
|
|
37
|
+
Arguments:
|
|
38
|
+
todo_list: The name of the todo list to add the item to
|
|
39
|
+
item_title: The title of the item to add to the list
|
|
40
|
+
For example:
|
|
41
|
+
Input: I'm out of milk and bread
|
|
42
|
+
Output: {"response":"I added milk and bread to your grocery list.","actions":[{"name":"add_item","args":[{"todo_list":"Grocery","item_title":"milk"}]},{"name":"add_item","args":[{"todo_list":"Grocery","item_title":"bread"}]}]}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
EOS
|
|
46
|
+
assert_equal(expected_prompt, @agent.system_prompt)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ruby-agents
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jeff McFadden
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2023-11-05 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: tldr
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.9.5
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.9.5
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: ruby-openai
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '5.1'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '5.1'
|
|
41
|
+
description: Put AI to Work.
|
|
42
|
+
email: 55709+jeffmcfadden@users.noreply.github.com
|
|
43
|
+
executables: []
|
|
44
|
+
extensions: []
|
|
45
|
+
extra_rdoc_files: []
|
|
46
|
+
files:
|
|
47
|
+
- ".gitignore"
|
|
48
|
+
- Gemfile
|
|
49
|
+
- Gemfile.lock
|
|
50
|
+
- LICENSE
|
|
51
|
+
- README.md
|
|
52
|
+
- agents.gemspec
|
|
53
|
+
- lib/actions/action.rb
|
|
54
|
+
- lib/actions/action_argument.rb
|
|
55
|
+
- lib/actions/action_example.rb
|
|
56
|
+
- lib/agents.rb
|
|
57
|
+
- lib/agents/agent.rb
|
|
58
|
+
- lib/agents/dispatcher.rb
|
|
59
|
+
- lib/agents/echo_agent.rb
|
|
60
|
+
- lib/agents/gpt_agents/calendar_gpt_agent.rb
|
|
61
|
+
- lib/agents/gpt_agents/categorizing_gpt_agent.rb
|
|
62
|
+
- lib/agents/gpt_agents/dispatching_gpt_agent.rb
|
|
63
|
+
- lib/agents/gpt_agents/echo_gpt_agent.rb
|
|
64
|
+
- lib/agents/gpt_agents/gpt_agent.rb
|
|
65
|
+
- lib/agents/gpt_agents/information_retrieval_gpt_agent.rb
|
|
66
|
+
- lib/agents/gpt_agents/promptless_gpt_agent.rb
|
|
67
|
+
- lib/agents/gpt_agents/todo_gpt_agent.rb
|
|
68
|
+
- lib/agents/unhandleable_request_agent.rb
|
|
69
|
+
- lib/gpt_clients/echo_gpt_client.rb
|
|
70
|
+
- lib/gpt_clients/gpt_client.rb
|
|
71
|
+
- lib/gpt_clients/open_ai_gpt_client.rb
|
|
72
|
+
- lib/logger.rb
|
|
73
|
+
- lib/requests/request.rb
|
|
74
|
+
- lib/responses/gpt_response.rb
|
|
75
|
+
- lib/responses/response.rb
|
|
76
|
+
- lib/responses/unhandleable_request_response.rb
|
|
77
|
+
- lib/version.rb
|
|
78
|
+
- test/test_actions.rb
|
|
79
|
+
- test/test_dispatcher.rb
|
|
80
|
+
- test/test_gpt_dispatcher.rb
|
|
81
|
+
- test/test_openai.rb
|
|
82
|
+
- test/test_todo_gpt_agent.rb
|
|
83
|
+
homepage: https://github.com/jeffmcfadden/agents
|
|
84
|
+
licenses: []
|
|
85
|
+
metadata: {}
|
|
86
|
+
post_install_message:
|
|
87
|
+
rdoc_options: []
|
|
88
|
+
require_paths:
|
|
89
|
+
- lib
|
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '0'
|
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '0'
|
|
100
|
+
requirements: []
|
|
101
|
+
rubygems_version: 3.3.7
|
|
102
|
+
signing_key:
|
|
103
|
+
specification_version: 4
|
|
104
|
+
summary: Setup and organize requests between multiple AI agents.
|
|
105
|
+
test_files: []
|