agent99 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 827e521c4a6580cfe954cf07cf04f1a822c00b9ed6eebf75598d925aeb555626
4
+ data.tar.gz: 1b3e9b499610b9b3853757432fd4e3e39acab8fb70e29feb94eae1d22f6097aa
5
+ SHA512:
6
+ metadata.gz: ce549f64fb88cd2f152e7af27b9724dbfedfda0f6255033ea1773dda7eaf89dd71a6b622667d023b3093ae1eca72ae3a55e7bd6b1f538e3035fe889ab52ac7c4
7
+ data.tar.gz: 959b0c9c342738bb9ee75f2dd82a4caaa825a13656f6d12dba2a11434857c08f11d746c1754f03ce1902f9e4ced1f3b7e1067ab1cdbe39c054250b23592ae94f
data/.envrc ADDED
@@ -0,0 +1,3 @@
1
+ # ai_agent/.envrc
2
+
3
+ export RR=`pwd`
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-11-28
4
+
5
+ - Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Dewayne VanHoozer
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,99 @@
1
+ # Agent99 Framework (AFW)
2
+
3
+ **Under Development** Initial release has no AI components - its just a generic client-server / request-response micro-services system using a peer-to-peer messaging broker and a centralized agent registry.
4
+
5
+ Agent99 is a Ruby-based framework for building and managing AI agents in a distributed system. It provides a robust foundation for creating intelligent agents that can communicate, discover each other, and perform various tasks.
6
+
7
+ ## Features
8
+
9
+ - Agent Lifecycle Management: Easy setup and teardown of agents
10
+ - Message Processing: Handle requests, responses, and control messages
11
+ - Agent Discovery: Find other agents based on capabilities
12
+ - Flexible Communication: Support for both AMQP and NATS messaging systems
13
+ - Registry Integration: Register and discover agents through a central registry
14
+ - Error Handling and Logging: Built-in error management and logging capabilities
15
+ - Control Actions: Pause, resume, update configuration, and request status of agents
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'agent99'
23
+ ```
24
+
25
+ And then execute:
26
+
27
+ ```
28
+ $ bundle install
29
+ ```
30
+
31
+ Or install it yourself as:
32
+
33
+ ```
34
+ $ gem install agent99
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ Here's a basic example of how to create an AI agent:
40
+
41
+ ```ruby
42
+ require 'agent99'
43
+
44
+ class MyAgentRequest < SimpleJsonSchemaBuilder::Base
45
+ object do
46
+ object :header, schema: Agent99::HeaderSchema
47
+
48
+ # Define your agents parameters ....
49
+ string :greeting, required: false, examples: ["Hello"]
50
+ string :name, required: true, examples: ["World"]
51
+ end
52
+ end
53
+
54
+ class MyAgent < Agent99::Base
55
+ REQUEST_SCHEMA = MyAgentRequest.schema
56
+
57
+ def capabilities
58
+ ['text_processing', 'sentiment_analysis']
59
+ # TODO: make up mind on keyword or unstructured text
60
+ end
61
+
62
+ def receive_request
63
+ # Handle the validated incoming requests
64
+ response = { result: "Processed request" }
65
+
66
+ # Not every request needs a response
67
+ send_response(response)
68
+ end
69
+
70
+ def receive_response
71
+ # You sent a request to another agent
72
+ # now handle the response.
73
+ end
74
+ end
75
+
76
+ agent = MyAgent.new
77
+ agent.run
78
+ ```
79
+
80
+ ## Configuration
81
+
82
+ The framework can be configured through environment variables:
83
+
84
+ - `REGISTRY_BASE_URL`: URL of the agent registry service (default: 'http://localhost:4567') See the default registry service in the examples folder.
85
+
86
+ ## Contributing
87
+
88
+ Bug reports and pull requests are welcome on GitHub at https://github.com/MadBomber/agent99.
89
+
90
+ ## Short-term Roadmap
91
+
92
+ - In the example registry, replace the Array(Hash) datastore with sqlite3 with a vector table to support discovery using semantic search.
93
+ - Treat the agent like a Tool w/r/t RAG for prompts.
94
+ - Add AgentRequest schema to agent's info in the registry.
95
+ - Add AgentResponse schema to define the `result` element in the response JSON payload
96
+
97
+ ## License
98
+
99
+ 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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ task default: :test
data/docs/todo.md ADDED
@@ -0,0 +1,66 @@
1
+ # Documentation
2
+
3
+ TODO: Lets get some more detailed documentation underway that can be linked to from the main README.md file.
4
+
5
+ Here are some key areas that should be covered in comprehensive documentation files:
6
+
7
+ 1. Architecture Overview:
8
+ - Explain the overall structure of the AiAgent framework
9
+ - Describe the roles of different components (Base, MessageClient, RegistryClient, etc.)
10
+ - Illustrate how agents communicate and interact within the system
11
+
12
+ 2. Agent Lifecycle:
13
+ - Detail the process of creating, initializing, running, and shutting down an agent
14
+ - Explain the registration and withdrawal process with the registry service
15
+
16
+ 3. Message Processing:
17
+ - Describe the different types of messages (request, response, control)
18
+ - Explain how messages are routed and processed
19
+ - Detail the schema validation process for incoming messages
20
+
21
+ 4. Agent Discovery:
22
+ - Explain how agents can discover other agents based on capabilities
23
+ - Describe the process of querying the registry for available agents
24
+
25
+ 5. Control Actions:
26
+ - List and explain all available control actions (shutdown, pause, resume, etc.)
27
+ - Describe how to implement custom control actions
28
+
29
+ 6. Configuration:
30
+ - Detail all configuration options available for agents
31
+ - Explain how to use environment variables for configuration
32
+
33
+ 7. Error Handling and Logging:
34
+ - Describe the error handling mechanisms in place
35
+ - Explain how to configure and use the logging system effectively
36
+
37
+ 8. Messaging Systems:
38
+ - Provide details on both AMQP and NATS messaging systems
39
+ - Explain how to switch between different messaging backends
40
+
41
+ 9. Custom Agent Implementation:
42
+ - Provide a step-by-step guide on creating a custom agent
43
+ - Explain how to define capabilities, handle requests, and send responses
44
+
45
+ 10. Schema Definition:
46
+ - Explain how to define and use request and response schemas
47
+ - Provide examples of complex schema definitions
48
+
49
+ 11. Performance Considerations:
50
+ - Discuss any performance optimizations in the framework
51
+ - Provide guidelines for writing efficient agents
52
+
53
+ 12. Security:
54
+ - Explain any security measures in place (if any)
55
+ - Provide best practices for securing agent communications
56
+
57
+ 13. Extending the Framework:
58
+ - Describe how to add new features or modify existing functionality
59
+ - Explain the plugin system (if one exists)
60
+
61
+ 14. Troubleshooting:
62
+ - Provide a list of common issues and their solutions
63
+ - Explain how to debug agents effectively
64
+
65
+ 15. API Reference:
66
+ - Provide a comprehensive API reference for all public methods and classes
@@ -0,0 +1,79 @@
1
+ # Agent99 Framework Examples
2
+
3
+ - TODO
4
+ - Review and edit
5
+ - Add instructions for brew install rabbitmq nats-server boxes
6
+ - review example agents to see code can be tightened
7
+
8
+ This folder contains example implementations using the Agent99 framework. The framework provides a foundation for building AI agents that can communicate with each other through a message-based system.
9
+
10
+ ## Files
11
+
12
+ ### 1. hello_world.rb
13
+
14
+ This file demonstrates a basic AI agent implementation using the Agent99 framework.
15
+
16
+ - Class: `HelloWorld < Agent99::Base`
17
+ - Functionality: Responds to "hello world" requests
18
+ - Key methods:
19
+ - `receive_request`: Handles incoming requests
20
+ - `validate_request`: Validates the request against a schema
21
+ - `process`: Generates the response
22
+
23
+ ### 2. hello_world_client.rb
24
+
25
+ This file shows how to create a client that interacts with the HelloWorld agent.
26
+
27
+ - Class: `HelloWorldClient < Agent99::Base`
28
+ - Functionality: Sends a request to a HelloWorld agent and processes the response
29
+ - Key methods:
30
+ - `init`: Initiates the request sending process
31
+ - `send_request`: Builds and sends the request
32
+ - `receive_response`: Handles the response from the HelloWorld agent
33
+
34
+ ### 3. hello_world_request.rb
35
+
36
+ This file defines the schema for HelloWorld requests using SimpleJsonSchemaBuilder.
37
+
38
+ - Class: `HelloWorldRequest < SimpleJsonSchemaBuilder::Base`
39
+ - Defines the structure of a valid HelloWorld request
40
+
41
+ ### 4. registry.rb
42
+
43
+ This file implements a simple registry service for AI agents using Sinatra.
44
+
45
+ - Functionality: Allows agents to register, discover other agents, and withdraw from the registry
46
+ - Endpoints:
47
+ - GET `/healthcheck`: Returns the number of registered agents
48
+ - POST `/register`: Registers a new agent
49
+ - GET `/discover`: Discovers agents by capability
50
+ - DELETE `/withdraw/:uuid`: Withdraws an agent from the registry
51
+ - GET `/`: Lists all registered agents
52
+
53
+ ## Usage
54
+
55
+ 1. Start the registry service:
56
+ ```
57
+ ruby registry.rb
58
+ ```
59
+
60
+ 2. Run the HelloWorld agent:
61
+ ```
62
+ ruby hello_world.rb
63
+ ```
64
+
65
+ 3. Run the HelloWorld client:
66
+ ```
67
+ ruby hello_world_client.rb
68
+ ```
69
+
70
+ ## Dependencies
71
+
72
+ - Ruby 3.3+
73
+ - Gems: json, json_schema, sinatra, bunny, securerandom
74
+
75
+ ## Notes
76
+
77
+ - The framework uses modern Ruby 3.3 syntax, especially for hashes and method signatures with named parameters.
78
+ - The examples demonstrate basic usage of the Agent99 framework, including request/response handling, validation, and agent discovery.
79
+ - The registry service uses an in-memory store (AGENT_REGISTRY) for simplicity, but it's recommended to use a more robust solution like SQLite for production use.
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/hello_world.rb
3
+
4
+ require 'json'
5
+ require 'json_schema'
6
+
7
+ require_relative '../lib/agent99'
8
+ require_relative 'hello_world_request'
9
+
10
+ class HelloWorld < Agent99::Base
11
+ REQUEST_SCHEMA = HelloWorldRequest.schema
12
+ # RESPONSE_SCHEMA = Agent99::RESPONSE.schema
13
+ # ERROR_SCHEMA = Agent99::ERROR.schema
14
+
15
+ # The request is in @payload
16
+ def receive_request
17
+ send_response( validate_request || process )
18
+ end
19
+
20
+ # This method validates the incoming request and returns any errors found
21
+ # or nil if there are no errors.
22
+ # It allows for returning an array of errors.
23
+ #
24
+ # TODO: consider a 4th message type of error
25
+ #
26
+ def validate_request
27
+ responses = []
28
+
29
+ # Check if the ID matches
30
+ unless id == to_uuid
31
+ logger.error <<~ERROR
32
+ #{name} received someone else's request
33
+ id: #{id} timestamp: #{timestamp}
34
+ to_uuid: #{to_uuid}
35
+ from_uuid: #{from_uuid}
36
+ event_uuid:#{event_uuid}
37
+ ERROR
38
+ responses << {
39
+ error: "Incorrect message queue for header",
40
+ details: {
41
+ id: id,
42
+ header: header
43
+ }
44
+ }
45
+ end
46
+
47
+ # Validate the incoming request body against the schema
48
+ validation_errors = validate_schema
49
+ unless validation_errors.empty?
50
+ logger.error "Validation errors: #{validation_errors}"
51
+ responses << {
52
+ error: "Invalid request",
53
+ details: validation_errors
54
+ }
55
+ end
56
+
57
+ responses.empty? ? nil : responses
58
+ end
59
+
60
+ # Returns the response value
61
+ # All response message have the same schema in that
62
+ # they have a header (all messages have headers) and
63
+ # a result element that is a String. Could it be
64
+ # a JSON string, sure but then we would need a
65
+ # RESPONSE_SCHEMA constant for the class.
66
+ def process
67
+ {
68
+ result: get(:greeting) + ' ' + get(:name)
69
+ }
70
+ end
71
+
72
+
73
+ # As a server type, HelloWorld should never receive
74
+ # a response message.
75
+ def receive_response(response)
76
+ loger.warn("Unexpected response type message: response.inspect")
77
+ end
78
+
79
+ private
80
+
81
+ # NOTE: what I'm thinking about here is similar to the
82
+ # prompt tool (aka function) callback facility
83
+ # where descriptive text is used to describe
84
+ # what the tool does.
85
+ #
86
+ # TODO: scale this idea back to just keywords
87
+ # until the registry program gets more
88
+ # stuff added to its discovery process.
89
+ #
90
+ def capabilities
91
+ %w[ greeter hello_world hello-world hello]
92
+ end
93
+ end
94
+
95
+ # Example usage
96
+ agent = HelloWorld.new
97
+ agent.run # Starts listening for messages
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/hello_world_client.rb
3
+
4
+ require 'json'
5
+ require 'json_schema'
6
+ require 'securerandom'
7
+ require_relative '../lib/agent99'
8
+
9
+ class HelloWorldClient < Agent99::Base
10
+ def init
11
+ send_request
12
+ end
13
+
14
+ def send_request
15
+ to_uuid = discover_agent(capability: 'greeter', how_many: 1).first[:uuid]
16
+
17
+ request = build_request(
18
+ to_uuid:,
19
+ greeting: 'Hey',
20
+ name: 'MadBomber'
21
+ )
22
+
23
+ result = @message_client.publish(request)
24
+ logger.info "Sent request: #{request.inspect}; status? #{result.inspect}"
25
+ end
26
+
27
+ def build_request(
28
+ to_uuid:,
29
+ greeting: 'Hello',
30
+ name: 'World'
31
+ )
32
+
33
+ {
34
+ header: {
35
+ type: 'request',
36
+ from_uuid: @id,
37
+ to_uuid:,
38
+ event_uuid: SecureRandom.uuid,
39
+ timestamp: Agent99::Timestamp.new.to_i
40
+ },
41
+ greeting:,
42
+ name:
43
+ }
44
+ end
45
+
46
+
47
+ def receive_response
48
+ logger.info "Received response: #{payload.inspect}"
49
+ result = payload[:result]
50
+
51
+ puts
52
+ puts `echo "#{result}" | boxes -d info`
53
+ puts
54
+
55
+ exit(0)
56
+ end
57
+
58
+ #####################################################
59
+ private
60
+
61
+ def capabilities
62
+ ['hello_world_client']
63
+ end
64
+ end
65
+
66
+
67
+ # Example usage
68
+ client = HelloWorldClient.new
69
+ client.run
70
+
@@ -0,0 +1,12 @@
1
+ # examples/hello_world_request.rb
2
+
3
+ require_relative '../lib/agent99/header_schema'
4
+
5
+ class HelloWorldRequest < SimpleJsonSchemaBuilder::Base
6
+ object do
7
+ object :header, schema: Agent99::HeaderSchema
8
+
9
+ string :greeting, required: false, examples: ["Hello"]
10
+ string :name, required: true, examples: ["World"]
11
+ end
12
+ end
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/registry.rb
3
+
4
+ require 'debug_me'
5
+ include DebugMe
6
+
7
+ require 'sinatra'
8
+ require 'json'
9
+ require 'bunny'
10
+ require 'securerandom'
11
+
12
+ # In-memory registry to store agent capabilities
13
+ # Array(Hash)
14
+ # TODO: change this data store to a sqlite database
15
+ # maybe with a vector search capability.
16
+ #
17
+ AGENT_REGISTRY = []
18
+
19
+ # Health check endpoint
20
+ get '/healthcheck' do
21
+ content_type :json
22
+ { agent_count: AGENT_REGISTRY.size }.to_json
23
+ end
24
+
25
+ # Endpoint to register an agent
26
+ post '/register' do
27
+ request.body.rewind
28
+ agent_info = JSON.parse(request.body.read, symbolize_names: true)
29
+
30
+ agent_name = agent_info[:name]
31
+ capabilities = agent_info[:capabilities]
32
+
33
+ agent_uuid = SecureRandom.uuid
34
+
35
+ AGENT_REGISTRY << agent_info.merge({uuid: agent_uuid})
36
+
37
+ status 201
38
+ content_type :json
39
+ { uuid: agent_uuid }.to_json
40
+ end
41
+
42
+ # Endpoint to discover agents by capability
43
+ # TODO: This is a simple keyword matcher. Looking
44
+ # => for a semantic match process.
45
+ get '/discover' do
46
+ capability = params['capability']
47
+
48
+ matching_agents = AGENT_REGISTRY.select do |agent|
49
+ agent[:capabilities].include?(capability)
50
+ end
51
+
52
+ content_type :json
53
+ matching_agents.to_json
54
+ end
55
+
56
+ # Withdraw an agent from the registry
57
+ delete '/withdraw/:uuid' do
58
+ uuid = params['uuid']
59
+ how_many = AGENT_REGISTRY.size
60
+
61
+ AGENT_REGISTRY.delete_if { |agent_info| agent_info[:uuid] == uuid }
62
+
63
+ if AGENT_REGISTRY.size == how_many
64
+ status 404 # Not Found
65
+ content_type :json
66
+ { error: "Agent with UUID #{uuid} not found." }.to_json
67
+ else
68
+ status 204 # No Content
69
+ end
70
+ end
71
+
72
+ # Display all registered agents
73
+ get '/' do
74
+ content_type :json
75
+ AGENT_REGISTRY.to_json
76
+ end
77
+
78
+ # Start the Sinatra server
79
+ if __FILE__ == $PROGRAM_NAME
80
+ Sinatra::Application.run!
81
+ end
@@ -0,0 +1,20 @@
1
+ #!/bin/bash
2
+
3
+ # Start the registry server
4
+ echo "Starting registry server..."
5
+ # Assuming the registry server is a separate process, replace with actual command
6
+ # e.g., ruby registry_server.rb &
7
+ # Replace 'registry_server_command' with the actual command to start your registry server
8
+ # Example: ruby registry_server.rb &
9
+ ./registry.rb &
10
+
11
+ # Start the HelloWorld agent
12
+ echo "Starting HelloWorld agent..."
13
+ ./hello_world.rb &
14
+
15
+ # Start the HelloWorldClient agent
16
+ echo "Starting HelloWorldClient agent..."
17
+ ./hello_world_client.rb &
18
+
19
+ # Wait for all background processes to finish
20
+ wait
@@ -0,0 +1,5 @@
1
+ module Agent99
2
+ # empty namespace
3
+ end
4
+
5
+ require_relative 'timestamp'
@@ -0,0 +1,35 @@
1
+ # lib/agent99/agent_discovery.rb
2
+
3
+ module Agent99::AgentDiscovery
4
+
5
+ # Returns the agent's capabilities (to be overridden by subclasses).
6
+ #
7
+ # @return [Array<String>] The agent's capabilities
8
+ #
9
+ def capabilities
10
+ []
11
+ end
12
+
13
+
14
+ ################################################
15
+ private
16
+
17
+ # Discovers other agents with a specific capability.
18
+ #
19
+ # @param capability [String] The capability to search for
20
+ # @param how_many [Integer] The number of agents to return
21
+ # @param all [Boolean] Whether to return all matching agents
22
+ # @return [Array<Hash>] The discovered agents
23
+ # @raise [RuntimeError] If no agents are found
24
+ #
25
+ def discover_agent(capability:, how_many: 1, all: false)
26
+ result = @registry_client.discover(capability:)
27
+
28
+ if result.empty?
29
+ logger.error "No agents found for capability: #{capability}"
30
+ raise "No agents available"
31
+ end
32
+
33
+ all ? result : result.sample(how_many)
34
+ end
35
+ end