google-adk 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 78caa57a75d4badbe474b02fd6b459273ebf517a7a1feb90e054cf196765a860
4
+ data.tar.gz: 84fa8230dbc09c97e6c6ab69d48a97f54b7f236cb44a3e6c2b948bdf9b094d57
5
+ SHA512:
6
+ metadata.gz: acd4e17b57c786a64e6589945cbd1ebd41dfa5acf9df76a6b57610fe6c6281e9e83cb0d4f9923f1a86283749c95fc3a6902b52b354cd1c677f180b1b587487a7
7
+ data.tar.gz: a4aeaf187766bcd5764ce79845a44017fe5520fa018b1824c192e850e750a3ac1b36e386e5f928efb92e615e8444f81a3fce8821ef13e8eda0e12f7bad9fe13b
data/CHANGELOG.md ADDED
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2024-12-14
9
+
10
+ ### Initial Alpha Release
11
+
12
+ **DISCLAIMER**: This is an UNOFFICIAL Ruby port of Google's Agent Development Kit (ADK). This gem is not affiliated with, endorsed by, or maintained by Google.
13
+
14
+ ### Added
15
+ - Core agent functionality with `LlmAgent` class for Gemini API integration
16
+ - `SimpleLLMAgent` for basic LLM interactions without complex features
17
+ - `FunctionTool` class for wrapping Ruby methods as callable tools
18
+ - Context management system for maintaining conversation history
19
+ - In-memory session storage for state management
20
+ - Event system foundation for streaming responses
21
+ - Comprehensive test suite with RSpec
22
+ - Full documentation with YARD
23
+
24
+ ### Known Limitations
25
+ - This is an alpha release with limited functionality
26
+ - Only Gemini API is supported (no Anthropic, OpenAI, etc.)
27
+ - Workflow agents (Sequential, Parallel, Loop) are not yet fully implemented
28
+ - No production-ready error handling or retry logic
29
+ - Limited tool integration compared to Python SDK
30
+
31
+ ### Dependencies
32
+ - Ruby >= 3.1.0
33
+ - async (~> 2.0)
34
+ - faraday (~> 2.0)
35
+ - concurrent-ruby (~> 1.2)
36
+ - google-cloud-ai_platform-v1 (~> 0.1)
37
+ - dotenv (~> 2.8)
38
+
39
+ ### Installation
40
+ ```ruby
41
+ gem 'google-adk', '~> 0.1.0'
42
+ ```
43
+
44
+ ### Example Usage
45
+ ```ruby
46
+ require 'google-adk'
47
+
48
+ agent = Google::ADK::LlmAgent.new(
49
+ model: 'gemini-2.0-flash',
50
+ name: 'assistant',
51
+ instructions: 'You are a helpful assistant'
52
+ )
53
+
54
+ response = agent.call("Hello, world!")
55
+ puts response
56
+ ```
57
+
58
+ [0.1.0]: https://github.com/yourusername/google-adk-ruby/releases/tag/v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Landon Gray
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # Google ADK (Ruby)
2
+
3
+ Ruby implementation of Google's Agent Development Kit for building AI agents.
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/google-adk.svg)](https://badge.fury.io/rb/google-adk)
6
+ [![Build Status](https://github.com/yourusername/google-adk-ruby/workflows/CI/badge.svg)](https://github.com/yourusername/google-adk-ruby/actions)
7
+
8
+ > **⚠️ DISCLAIMER: This is an UNOFFICIAL Ruby port of Google's Agent Development Kit (ADK). This gem is not affiliated with, endorsed by, or maintained by Google. It is a community-driven implementation based on the public Python ADK repository. Use at your own risk.**
9
+
10
+ ## Overview
11
+
12
+ Google ADK (Agent Development Kit) is a Ruby gem that provides a flexible framework for building, orchestrating, and deploying AI agents. It's a port of Google's Python ADK, bringing the same powerful agent capabilities to the Ruby ecosystem.
13
+
14
+ ### Key Features
15
+
16
+ - **Multiple Agent Types**: LLM agents, sequential, parallel, and loop workflow agents
17
+ - **Tool Integration**: Easy integration of custom tools and functions
18
+ - **Event-Driven Architecture**: Stream events for real-time agent interactions
19
+ - **Session Management**: Built-in session and state management
20
+ - **Flexible Orchestration**: Run agents individually or compose them into complex workflows
21
+ - **Plugin System**: Extend functionality with custom plugins
22
+
23
+ ## Installation
24
+
25
+ Add this line to your application's Gemfile:
26
+
27
+ ```ruby
28
+ gem 'google-adk'
29
+ ```
30
+
31
+ And then execute:
32
+
33
+ ```bash
34
+ bundle install
35
+ ```
36
+
37
+ Or install it yourself as:
38
+
39
+ ```bash
40
+ gem install google-adk
41
+ ```
42
+
43
+ ## Quick Start
44
+
45
+ ### Simple Agent
46
+
47
+ ```ruby
48
+ require 'google-adk'
49
+
50
+ # Create an agent
51
+ agent = Google::ADK::LlmAgent.new(
52
+ name: 'assistant',
53
+ model: 'gemini-2.0-flash',
54
+ instructions: 'You are a helpful assistant'
55
+ )
56
+
57
+ # Run with a runner
58
+ runner = Google::ADK::InMemoryRunner.new(
59
+ agent: agent,
60
+ app_name: 'my_app'
61
+ )
62
+
63
+ # Execute and stream events
64
+ runner.run(user_id: 'user123', message: 'Hello!') do |event|
65
+ puts "[#{event.author}]: #{event.content}" if event.content
66
+ end
67
+ ```
68
+
69
+ ### Using Tools
70
+
71
+ ```ruby
72
+ # Define a custom tool
73
+ calculator = Google::ADK::FunctionTool.new(
74
+ name: 'calculator',
75
+ description: 'Performs calculations',
76
+ callable: proc { |expression:| eval(expression) }
77
+ )
78
+
79
+ # Create agent with tools
80
+ agent = Google::ADK::LlmAgent.new(
81
+ name: 'math_assistant',
82
+ model: 'gemini-2.0-flash',
83
+ instructions: 'You help with math problems',
84
+ tools: [calculator]
85
+ )
86
+ ```
87
+
88
+ ### Workflow Agents
89
+
90
+ ```ruby
91
+ # Sequential workflow
92
+ research_agent = Google::ADK::LlmAgent.new(name: 'researcher', model: 'gemini-2.0-flash')
93
+ writer_agent = Google::ADK::LlmAgent.new(name: 'writer', model: 'gemini-2.0-flash')
94
+
95
+ sequential = Google::ADK::SequentialAgent.new(
96
+ name: 'report_builder',
97
+ agents: [research_agent, writer_agent]
98
+ )
99
+
100
+ # Parallel execution
101
+ parallel = Google::ADK::ParallelAgent.new(
102
+ name: 'multi_analyzer',
103
+ agents: [agent1, agent2, agent3],
104
+ aggregation_strategy: :all
105
+ )
106
+
107
+ # Loop execution
108
+ loop_agent = Google::ADK::LoopAgent.new(
109
+ name: 'refiner',
110
+ agent: refiner_agent,
111
+ max_iterations: 3
112
+ )
113
+ ```
114
+
115
+ ## Core Concepts
116
+
117
+ ### Agents
118
+
119
+ Agents are the fundamental building blocks:
120
+
121
+ - **BaseAgent**: Abstract base class for all agents
122
+ - **LlmAgent**: Language model-powered agent for conversations
123
+ - **SequentialAgent**: Executes sub-agents in sequence
124
+ - **ParallelAgent**: Executes sub-agents in parallel
125
+ - **LoopAgent**: Executes an agent in a loop
126
+
127
+ ### Events
128
+
129
+ All agent interactions produce events that flow through the system:
130
+
131
+ ```ruby
132
+ event = Google::ADK::Event.new(
133
+ invocation_id: 'inv-123',
134
+ author: 'assistant',
135
+ content: 'Hello! How can I help?'
136
+ )
137
+ ```
138
+
139
+ ### Context
140
+
141
+ Context manages conversation state and agent lifecycle:
142
+
143
+ ```ruby
144
+ context = Google::ADK::InvocationContext.new(
145
+ session: session,
146
+ agent: agent,
147
+ invocation_id: 'inv-123',
148
+ session_service: service
149
+ )
150
+ ```
151
+
152
+ ### Sessions
153
+
154
+ Sessions persist conversation history and state:
155
+
156
+ ```ruby
157
+ service = Google::ADK::InMemorySessionService.new
158
+ session = service.create_session(
159
+ app_name: 'my_app',
160
+ user_id: 'user123'
161
+ )
162
+ ```
163
+
164
+ ## Examples
165
+
166
+ See the `examples/` directory for more comprehensive examples:
167
+
168
+ - `basic_agent.rb` - Simple agent creation and usage
169
+ - `multi_agent_system.rb` - Complex multi-agent workflows
170
+ - `tool_usage.rb` - Custom tools and agent-as-tool patterns
171
+
172
+ ## Development
173
+
174
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
175
+
176
+ ```bash
177
+ # Run tests
178
+ bundle exec rake spec
179
+
180
+ # Run linter
181
+ bundle exec rubocop
182
+
183
+ # Build gem
184
+ bundle exec rake build
185
+ ```
186
+
187
+ ## Contributing
188
+
189
+ Bug reports and pull requests are welcome on GitHub at https://github.com/yourusername/google-adk-ruby
190
+
191
+ ## License
192
+
193
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/google/adk/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "google-adk"
7
+ spec.version = Google::ADK::VERSION
8
+ spec.authors = ["Landon Gray"]
9
+ spec.email = ["thedayisntgray@gmail.com"]
10
+
11
+ spec.summary = "Unofficial Ruby implementation of Google's Agent Development Kit"
12
+ spec.description = "UNOFFICIAL Ruby port of Google's Agent Development Kit (ADK). This gem is not affiliated with, endorsed by, or maintained by Google. Build, evaluate, and deploy AI agents using the ADK framework in Ruby."
13
+ spec.homepage = "https://github.com/thedayisntgray/google-adk"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.1.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/thedayisntgray/google-adk"
19
+ spec.metadata["changelog_uri"] = "https://github.com/thedayisntgray/google-adk/blob/main/CHANGELOG.md"
20
+ spec.metadata["documentation_uri"] = "https://rubydoc.info/gems/google-adk"
21
+ spec.metadata["rubygems_mfa_required"] = "true"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ spec.files = Dir.glob(%w[
25
+ lib/**/*.rb
26
+ google-adk.gemspec
27
+ README.md
28
+ CHANGELOG.md
29
+ LICENSE.txt
30
+ ])
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ # Runtime dependencies
36
+ spec.add_dependency "async", "~> 2.0"
37
+ spec.add_dependency "concurrent-ruby", "~> 1.2"
38
+ spec.add_dependency "dotenv", "~> 2.8"
39
+ spec.add_dependency "faraday", "~> 2.0"
40
+ spec.add_dependency "google-cloud-ai_platform-v1", "~> 0.1"
41
+
42
+ # Development dependencies
43
+ spec.add_development_dependency "rake", "~> 13.0"
44
+ spec.add_development_dependency "rspec", "~> 3.12"
45
+ spec.add_development_dependency "rubocop", "~> 1.50"
46
+ spec.add_development_dependency "rubocop-rspec", "~> 2.20"
47
+ spec.add_development_dependency "simplecov", "~> 0.22"
48
+ spec.add_development_dependency "vcr", "~> 6.1"
49
+ spec.add_development_dependency "webmock", "~> 3.18"
50
+ spec.add_development_dependency "yard", "~> 0.9"
51
+ end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Google
4
+ module ADK
5
+ class BaseAgent
6
+ AGENT_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_-]*$/
7
+
8
+ attr_reader :name, :description, :parent_agent, :sub_agents,
9
+ :before_agent_callback, :after_agent_callback
10
+
11
+ # Initialize a new BaseAgent
12
+ #
13
+ # @param name [String] Unique name for the agent
14
+ # @param description [String] Description of the agent's capabilities (optional)
15
+ # @param sub_agents [Array<BaseAgent>] Child agents (optional)
16
+ # @param before_agent_callback [Proc] Callback before agent execution (optional)
17
+ # @param after_agent_callback [Proc] Callback after agent execution (optional)
18
+ # @raise [ArgumentError] If name is not provided
19
+ # @raise [ConfigurationError] If name format is invalid or sub-agent names are not unique
20
+ def initialize(name:, description: nil, sub_agents: [], before_agent_callback: nil, after_agent_callback: nil)
21
+ raise ArgumentError, "name is required" if name.nil?
22
+
23
+ validate_agent_name!(name)
24
+
25
+ @name = name
26
+ @description = description
27
+ @parent_agent = nil
28
+ @sub_agents = []
29
+ @before_agent_callback = before_agent_callback
30
+ @after_agent_callback = after_agent_callback
31
+
32
+ # Set up sub-agents with validation
33
+ self.sub_agents = sub_agents
34
+ end
35
+
36
+ # Run the agent asynchronously with a text message
37
+ #
38
+ # @param message [String] The input message
39
+ # @raise [NotImplementedError] Must be implemented by subclasses
40
+ def run_async(message)
41
+ raise NotImplementedError, "Subclasses must implement #run_async"
42
+ end
43
+
44
+ # Run the agent in live mode (video/audio)
45
+ #
46
+ # @raise [NotImplementedError] Must be implemented by subclasses
47
+ def run_live
48
+ raise NotImplementedError, "Subclasses must implement #run_live"
49
+ end
50
+
51
+ # Create a copy of the agent with optional updates
52
+ #
53
+ # @param attributes [Hash] Attributes to update in the clone
54
+ # @return [BaseAgent] A new agent instance
55
+ def clone(**attributes)
56
+ # Deep clone sub_agents if present
57
+ cloned_sub_agents = if attributes.key?(:sub_agents)
58
+ attributes[:sub_agents]
59
+ else
60
+ @sub_agents.map(&:clone)
61
+ end
62
+
63
+ self.class.new(
64
+ name: attributes.fetch(:name, @name),
65
+ description: attributes.fetch(:description, @description),
66
+ sub_agents: cloned_sub_agents,
67
+ before_agent_callback: attributes.fetch(:before_agent_callback, @before_agent_callback),
68
+ after_agent_callback: attributes.fetch(:after_agent_callback, @after_agent_callback)
69
+ )
70
+ end
71
+
72
+ # Find an agent by name in the agent tree
73
+ #
74
+ # @param agent_name [String] Name of the agent to find
75
+ # @return [BaseAgent, nil] The found agent or nil
76
+ def find_agent(agent_name)
77
+ return self if @name == agent_name
78
+
79
+ @sub_agents.each do |sub_agent|
80
+ found = sub_agent.find_agent(agent_name)
81
+ return found if found
82
+ end
83
+
84
+ nil
85
+ end
86
+
87
+ # Find a direct sub-agent by name
88
+ #
89
+ # @param agent_name [String] Name of the sub-agent to find
90
+ # @return [BaseAgent, nil] The found sub-agent or nil
91
+ def find_sub_agent(agent_name)
92
+ @sub_agents.find { |agent| agent.name == agent_name }
93
+ end
94
+
95
+ # Create an agent from configuration
96
+ #
97
+ # @param config [Hash] Configuration hash
98
+ # @return [BaseAgent] New agent instance
99
+ def self.from_config(config)
100
+ config = config.transform_keys(&:to_sym)
101
+
102
+ # Recursively create sub-agents if present
103
+ sub_agents = if config[:sub_agents]
104
+ config[:sub_agents].map { |sub_config| from_config(sub_config) }
105
+ else
106
+ []
107
+ end
108
+
109
+ new(
110
+ name: config[:name],
111
+ description: config[:description],
112
+ sub_agents: sub_agents,
113
+ before_agent_callback: config[:before_agent_callback],
114
+ after_agent_callback: config[:after_agent_callback]
115
+ )
116
+ end
117
+
118
+ private
119
+
120
+ # Validate agent name format
121
+ #
122
+ # @param name [String] Agent name to validate
123
+ # @raise [ConfigurationError] If name format is invalid
124
+ def validate_agent_name!(name)
125
+ return if name.match?(AGENT_NAME_REGEX)
126
+
127
+ raise ConfigurationError,
128
+ "Agent name must match #{AGENT_NAME_REGEX.inspect}. Got: #{name.inspect}"
129
+ end
130
+
131
+ # Set sub-agents with validation
132
+ #
133
+ # @param agents [Array<BaseAgent>] Sub-agents to set
134
+ # @raise [ConfigurationError] If sub-agent names are not unique
135
+ def sub_agents=(agents)
136
+ # Validate unique names
137
+ agent_names = agents.map(&:name)
138
+ raise ConfigurationError, "Sub-agent names must be unique" if agent_names.size != agent_names.uniq.size
139
+
140
+ # Clear existing parent references
141
+ @sub_agents.each { |agent| agent.instance_variable_set(:@parent_agent, nil) }
142
+
143
+ # Set new sub-agents and parent references
144
+ @sub_agents = agents
145
+ @sub_agents.each { |agent| agent.instance_variable_set(:@parent_agent, self) }
146
+ end
147
+ end
148
+ end
149
+ end