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.
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Google
4
+ module ADK
5
+ # Agent that executes sub-agents sequentially
6
+ class SequentialAgent < BaseAgent
7
+ attr_reader :agents
8
+
9
+ # Initialize a sequential agent
10
+ #
11
+ # @param name [String] Agent name
12
+ # @param description [String] Agent description (optional)
13
+ # @param agents [Array<BaseAgent>] Agents to execute in order
14
+ # @param before_agent_callback [Proc] Callback before agent execution
15
+ # @param after_agent_callback [Proc] Callback after agent execution
16
+ # @raise [ArgumentError] If no agents provided
17
+ def initialize(name:, agents:, description: nil,
18
+ before_agent_callback: nil, after_agent_callback: nil)
19
+ raise ArgumentError, "Sequential agent requires at least one agent" if agents.empty?
20
+
21
+ super(
22
+ name: name,
23
+ description: description || "Executes #{agents.length} agents sequentially",
24
+ sub_agents: agents,
25
+ before_agent_callback: before_agent_callback,
26
+ after_agent_callback: after_agent_callback
27
+ )
28
+
29
+ @agents = agents
30
+ end
31
+
32
+ # Run agents sequentially
33
+ #
34
+ # @param message [String] Initial message
35
+ # @param context [InvocationContext] Invocation context
36
+ # @yield [Event] Events during execution
37
+ def run_async(message, context: nil)
38
+ Enumerator.new do |yielder|
39
+ invocation_id = context&.invocation_id || "seq-#{SecureRandom.uuid}"
40
+
41
+ # Yield start event
42
+ start_event = Event.new(
43
+ invocation_id: invocation_id,
44
+ author: @name,
45
+ content: "Starting sequential execution with #{@agents.length} agents"
46
+ )
47
+ yielder << start_event
48
+
49
+ # Execute each agent in sequence
50
+ current_input = message
51
+ @agents.each_with_index do |agent, index|
52
+ begin
53
+ # Yield progress event
54
+ progress_event = Event.new(
55
+ invocation_id: invocation_id,
56
+ author: @name,
57
+ content: "Executing agent #{index + 1}/#{@agents.length}: #{agent.name}"
58
+ )
59
+ yielder << progress_event
60
+
61
+ # Run the agent
62
+ agent_output = nil
63
+ if agent.respond_to?(:run_async)
64
+ agent.run_async(current_input, context: context).each do |event|
65
+ yielder << event
66
+ # Capture last content as output for next agent
67
+ agent_output = event.content if event.content
68
+ end
69
+ else
70
+ # For agents that don't implement run_async
71
+ error_event = Event.new(
72
+ invocation_id: invocation_id,
73
+ author: @name,
74
+ content: "Agent #{agent.name} does not implement run_async"
75
+ )
76
+ yielder << error_event
77
+ agent_output = current_input
78
+ end
79
+
80
+ # Use this agent's output as next agent's input
81
+ current_input = agent_output || current_input
82
+
83
+ rescue StandardError => e
84
+ # Handle errors gracefully
85
+ error_event = Event.new(
86
+ invocation_id: invocation_id,
87
+ author: @name,
88
+ content: "Error in agent #{agent.name}: #{e.message}"
89
+ )
90
+ yielder << error_event
91
+
92
+ # Continue with original input if agent failed
93
+ current_input = message
94
+ end
95
+ end
96
+
97
+ # Yield completion event
98
+ end_event = Event.new(
99
+ invocation_id: invocation_id,
100
+ author: @name,
101
+ content: "Completed sequential execution. Final output: #{current_input}"
102
+ )
103
+ yielder << end_event
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "json"
5
+
6
+ module Google
7
+ module ADK
8
+ # Client for interacting with Google's Gemini API
9
+ class GeminiClient
10
+ API_BASE_URL = "https://generativelanguage.googleapis.com"
11
+
12
+ attr_reader :api_key
13
+
14
+ def initialize(api_key: nil)
15
+ @api_key = api_key || ENV["GEMINI_API_KEY"]
16
+ raise ConfigurationError, "GEMINI_API_KEY not set" unless @api_key
17
+
18
+ @client = Faraday.new(API_BASE_URL) do |conn|
19
+ conn.request :json
20
+ conn.response :json
21
+ conn.adapter Faraday.default_adapter
22
+ end
23
+ end
24
+
25
+ # Generate content using Gemini API
26
+ #
27
+ # @param model [String] Model name (e.g., "gemini-2.0-flash")
28
+ # @param messages [Array<Hash>] Conversation messages
29
+ # @param tools [Array<Hash>] Available tools (optional)
30
+ # @param system_instruction [String] System instruction (optional)
31
+ # @return [Hash] API response
32
+ def generate_content(model:, messages:, tools: nil, system_instruction: nil)
33
+ url = "/v1beta/models/#{model}:generateContent"
34
+
35
+ payload = {
36
+ contents: format_messages(messages),
37
+ generationConfig: {
38
+ temperature: 0.7,
39
+ topK: 40,
40
+ topP: 0.95,
41
+ maxOutputTokens: 8192
42
+ }
43
+ }
44
+
45
+ payload[:systemInstruction] = { parts: [{ text: system_instruction }] } if system_instruction
46
+ payload[:tools] = [{ functionDeclarations: tools }] if tools && !tools.empty?
47
+
48
+ response = @client.post("#{url}?key=#{@api_key}") do |req|
49
+ req.body = payload
50
+ end
51
+
52
+ handle_response(response)
53
+ end
54
+
55
+ private
56
+
57
+ # Format messages for Gemini API
58
+ def format_messages(messages)
59
+ messages.map do |msg|
60
+ if msg[:parts]
61
+ # Already formatted
62
+ msg
63
+ else
64
+ # Convert simple text messages
65
+ {
66
+ role: msg[:role] == "assistant" ? "model" : msg[:role],
67
+ parts: [{ text: msg[:content] }]
68
+ }
69
+ end
70
+ end
71
+ end
72
+
73
+ # Handle API response
74
+ def handle_response(response)
75
+ case response.status
76
+ when 200
77
+ response.body
78
+ when 400
79
+ raise Error, "Bad request: #{response.body.dig('error', 'message') || response.body}"
80
+ when 401
81
+ raise ConfigurationError, "Invalid API key"
82
+ when 429
83
+ raise Error, "Rate limit exceeded"
84
+ else
85
+ raise Error, "API error (#{response.status}): #{response.body}"
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Google
4
+ module ADK
5
+ # Configuration for context caching
6
+ class ContextCacheConfig
7
+ attr_accessor :min_tokens, :ttl_seconds, :cache_intervals
8
+
9
+ # Initialize cache configuration
10
+ #
11
+ # @param min_tokens [Integer] Minimum tokens to cache (default: 1024)
12
+ # @param ttl_seconds [Integer] Cache TTL in seconds (default: 300)
13
+ # @param cache_intervals [Array<Integer>] Cache intervals (default: [])
14
+ def initialize(min_tokens: 1024, ttl_seconds: 300, cache_intervals: [])
15
+ @min_tokens = min_tokens
16
+ @ttl_seconds = ttl_seconds
17
+ @cache_intervals = cache_intervals
18
+ end
19
+ end
20
+
21
+ # Configuration for agent run
22
+ class RunConfig
23
+ attr_accessor :max_tokens, :temperature, :context_window_compression,
24
+ :max_steps, :timeout_seconds
25
+
26
+ # Initialize run configuration
27
+ #
28
+ # @param max_tokens [Integer] Maximum tokens for response (optional)
29
+ # @param temperature [Float] LLM temperature (default: 0.7)
30
+ # @param context_window_compression [Boolean] Enable compression (default: false)
31
+ # @param max_steps [Integer] Maximum execution steps (optional)
32
+ # @param timeout_seconds [Integer] Execution timeout (optional)
33
+ def initialize(max_tokens: nil, temperature: 0.7,
34
+ context_window_compression: false,
35
+ max_steps: nil, timeout_seconds: nil)
36
+ @max_tokens = max_tokens
37
+ @temperature = temperature
38
+ @context_window_compression = context_window_compression
39
+ @max_steps = max_steps
40
+ @timeout_seconds = timeout_seconds
41
+ end
42
+ end
43
+
44
+ # Read-only context for accessing state
45
+ class ReadonlyContext
46
+ attr_reader :invocation_id, :agent_name, :state
47
+
48
+ # Initialize readonly context
49
+ #
50
+ # @param invocation_id [String] Unique invocation ID
51
+ # @param agent_name [String] Current agent name
52
+ # @param state [Hash] Current state (will be frozen)
53
+ def initialize(invocation_id:, agent_name:, state:)
54
+ @invocation_id = invocation_id
55
+ @agent_name = agent_name
56
+ @state = state.freeze
57
+ end
58
+ end
59
+
60
+ # Context for callbacks with mutable state
61
+ class CallbackContext < ReadonlyContext
62
+ attr_reader :session
63
+
64
+ # Initialize callback context
65
+ #
66
+ # @param invocation_id [String] Unique invocation ID
67
+ # @param agent_name [String] Current agent name
68
+ # @param session [Object] Session object with state
69
+ def initialize(invocation_id:, agent_name:, session:)
70
+ @invocation_id = invocation_id
71
+ @agent_name = agent_name
72
+ @session = session
73
+ end
74
+
75
+ # Get mutable state from session
76
+ #
77
+ # @return [Hash] Mutable state hash
78
+ def state
79
+ @session.state
80
+ end
81
+
82
+ # Update multiple state values
83
+ #
84
+ # @param updates [Hash] Key-value pairs to update
85
+ def update_state(updates)
86
+ state.merge!(updates)
87
+ end
88
+ end
89
+
90
+ # Context for tool execution
91
+ class ToolContext < CallbackContext
92
+ attr_reader :auth_service, :artifact_service, :memory_service
93
+
94
+ # Initialize tool context
95
+ #
96
+ # @param invocation_id [String] Unique invocation ID
97
+ # @param agent_name [String] Current agent name
98
+ # @param session [Object] Session object
99
+ # @param auth_service [Object] Authentication service (optional)
100
+ # @param artifact_service [Object] Artifact service (optional)
101
+ # @param memory_service [Object] Memory service (optional)
102
+ def initialize(invocation_id:, agent_name:, session:,
103
+ auth_service: nil, artifact_service: nil, memory_service: nil)
104
+ super(invocation_id: invocation_id, agent_name: agent_name, session: session)
105
+ @auth_service = auth_service
106
+ @artifact_service = artifact_service
107
+ @memory_service = memory_service
108
+ end
109
+
110
+ # Request authentication
111
+ #
112
+ # @param auth_type [String] Type of authentication
113
+ # @param options [Hash] Authentication options
114
+ def request_auth(auth_type, **options)
115
+ raise AgentError, "Auth service not available" unless @auth_service
116
+
117
+ @auth_service.request_auth(auth_type, options)
118
+ end
119
+
120
+ # List artifacts
121
+ #
122
+ # @return [Array] List of artifacts
123
+ def list_artifacts
124
+ return [] unless @artifact_service
125
+
126
+ @artifact_service.list_artifacts
127
+ end
128
+
129
+ # Search memory
130
+ #
131
+ # @param query [String] Search query
132
+ # @return [Array] Search results
133
+ def search_memory(query)
134
+ return [] unless @memory_service
135
+
136
+ @memory_service.search(query)
137
+ end
138
+ end
139
+
140
+ # Full invocation context for agent execution
141
+ class InvocationContext
142
+ attr_reader :session, :agent, :invocation_id, :session_service,
143
+ :artifact_service, :memory_service, :agent_states,
144
+ :context_cache_config, :run_config
145
+
146
+ # Initialize invocation context
147
+ #
148
+ # @param session [Object] Current session
149
+ # @param agent [BaseAgent] Current agent
150
+ # @param invocation_id [String] Unique invocation ID
151
+ # @param session_service [Object] Session service
152
+ # @param artifact_service [Object] Artifact service (optional)
153
+ # @param memory_service [Object] Memory service (optional)
154
+ # @param context_cache_config [ContextCacheConfig] Cache config (optional)
155
+ # @param run_config [RunConfig] Run configuration (optional)
156
+ def initialize(session:, agent:, invocation_id:, session_service:,
157
+ artifact_service: nil, memory_service: nil,
158
+ context_cache_config: nil, run_config: nil)
159
+ @session = session
160
+ @agent = agent
161
+ @invocation_id = invocation_id
162
+ @session_service = session_service
163
+ @artifact_service = artifact_service
164
+ @memory_service = memory_service
165
+ @agent_states = {}
166
+ @context_cache_config = context_cache_config || ContextCacheConfig.new
167
+ @run_config = run_config || RunConfig.new
168
+ end
169
+
170
+ # Get agent state
171
+ #
172
+ # @param agent_name [String] Agent name
173
+ # @return [Hash] Agent state or empty hash
174
+ def get_agent_state(agent_name)
175
+ @agent_states[agent_name] || {}
176
+ end
177
+
178
+ # Update agent state
179
+ #
180
+ # @param agent_name [String] Agent name
181
+ # @param state [Hash] New state
182
+ def update_agent_state(agent_name, state)
183
+ @agent_states[agent_name] = state
184
+ end
185
+
186
+ # Get current session state
187
+ #
188
+ # @return [Hash] Session state
189
+ def state
190
+ @session.state
191
+ end
192
+
193
+ # Update session state
194
+ #
195
+ # @param updates [Hash] State updates
196
+ def update_state(updates)
197
+ @session.state.merge!(updates)
198
+ end
199
+
200
+ # Add event to session
201
+ #
202
+ # @param event [Event] Event to add
203
+ def add_event(event)
204
+ @session.events << event
205
+ end
206
+
207
+ # Get conversation history
208
+ #
209
+ # @return [Array<Event>] Event history
210
+ def events
211
+ @session.events
212
+ end
213
+
214
+ # Create callback context
215
+ #
216
+ # @return [CallbackContext] Callback context
217
+ def to_callback_context
218
+ CallbackContext.new(
219
+ invocation_id: @invocation_id,
220
+ agent_name: @agent.name,
221
+ session: @session
222
+ )
223
+ end
224
+
225
+ # Create tool context
226
+ #
227
+ # @param auth_service [Object] Auth service (optional)
228
+ # @return [ToolContext] Tool context
229
+ def to_tool_context(auth_service: nil)
230
+ ToolContext.new(
231
+ invocation_id: @invocation_id,
232
+ agent_name: @agent.name,
233
+ session: @session,
234
+ auth_service: auth_service,
235
+ artifact_service: @artifact_service,
236
+ memory_service: @memory_service
237
+ )
238
+ end
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "time"
5
+ require "set"
6
+
7
+ module Google
8
+ module ADK
9
+ # Represents a function call in an event
10
+ class FunctionCall
11
+ attr_reader :id, :name, :arguments
12
+
13
+ # Initialize a function call
14
+ #
15
+ # @param id [String] Unique identifier for the call (optional)
16
+ # @param name [String] Function name
17
+ # @param arguments [Hash] Function arguments
18
+ def initialize(id: nil, name:, arguments:)
19
+ @id = id || "call-#{SecureRandom.uuid}"
20
+ @name = name
21
+ @arguments = arguments
22
+ end
23
+
24
+ # Convert to hash
25
+ #
26
+ # @return [Hash] Hash representation
27
+ def to_h
28
+ {
29
+ id: @id,
30
+ name: @name,
31
+ arguments: @arguments
32
+ }
33
+ end
34
+ end
35
+
36
+ # Represents a function response in an event
37
+ class FunctionResponse
38
+ attr_reader :id, :name, :response, :is_error
39
+
40
+ # Initialize a function response
41
+ #
42
+ # @param id [String] ID of the function call this responds to
43
+ # @param name [String] Function name
44
+ # @param response [Hash] Function response
45
+ # @param is_error [Boolean] Whether this is an error response
46
+ def initialize(id:, name:, response:, is_error: false)
47
+ @id = id
48
+ @name = name
49
+ @response = response
50
+ @is_error = is_error
51
+ end
52
+
53
+ # Convert to hash
54
+ #
55
+ # @return [Hash] Hash representation
56
+ def to_h
57
+ {
58
+ id: @id,
59
+ name: @name,
60
+ response: @response,
61
+ is_error: @is_error
62
+ }
63
+ end
64
+ end
65
+
66
+ # Manages agent actions and state changes
67
+ class EventActions
68
+ attr_accessor :state_delta, :agent_state, :artifact_delta,
69
+ :transfer_to_agent, :escalate, :skip_summarization,
70
+ :end_of_agent, :requested_auth_configs,
71
+ :requested_tool_confirmations, :rewind_before_invocation_id,
72
+ :compaction
73
+
74
+ # Initialize event actions
75
+ #
76
+ # @param state_delta [Hash] State changes (optional)
77
+ # @param agent_state [Hash] Agent-specific state (optional)
78
+ # @param artifact_delta [Hash] Artifact changes (optional)
79
+ # @param transfer_to_agent [String] Agent to transfer to (optional)
80
+ # @param escalate [Boolean] Whether to escalate to parent (optional)
81
+ # @param skip_summarization [Boolean] Skip LLM summarization (optional)
82
+ # @param end_of_agent [Boolean] End agent lifecycle (optional)
83
+ def initialize(state_delta: {}, agent_state: nil, artifact_delta: {},
84
+ transfer_to_agent: nil, escalate: false,
85
+ skip_summarization: false, end_of_agent: false,
86
+ requested_auth_configs: nil, requested_tool_confirmations: nil,
87
+ rewind_before_invocation_id: nil, compaction: nil)
88
+ @state_delta = state_delta
89
+ @agent_state = agent_state
90
+ @artifact_delta = artifact_delta
91
+ @transfer_to_agent = transfer_to_agent
92
+ @escalate = escalate
93
+ @skip_summarization = skip_summarization
94
+ @end_of_agent = end_of_agent
95
+ @requested_auth_configs = requested_auth_configs
96
+ @requested_tool_confirmations = requested_tool_confirmations
97
+ @rewind_before_invocation_id = rewind_before_invocation_id
98
+ @compaction = compaction
99
+ end
100
+
101
+ # Convert to hash, excluding nil and empty values
102
+ #
103
+ # @return [Hash] Hash representation
104
+ def to_h
105
+ result = {}
106
+ result[:state_delta] = @state_delta unless @state_delta.empty?
107
+ result[:agent_state] = @agent_state unless @agent_state.nil?
108
+ result[:artifact_delta] = @artifact_delta unless @artifact_delta.empty?
109
+ result[:transfer_to_agent] = @transfer_to_agent unless @transfer_to_agent.nil?
110
+ result[:escalate] = @escalate if @escalate
111
+ result[:skip_summarization] = @skip_summarization if @skip_summarization
112
+ result[:end_of_agent] = @end_of_agent if @end_of_agent
113
+ result[:requested_auth_configs] = @requested_auth_configs unless @requested_auth_configs.nil?
114
+ result[:requested_tool_confirmations] = @requested_tool_confirmations unless @requested_tool_confirmations.nil?
115
+ result[:rewind_before_invocation_id] = @rewind_before_invocation_id unless @rewind_before_invocation_id.nil?
116
+ result[:compaction] = @compaction unless @compaction.nil?
117
+ result
118
+ end
119
+ end
120
+
121
+ # Represents an event in agent-user conversation
122
+ class Event
123
+ attr_reader :id, :invocation_id, :author, :timestamp, :content,
124
+ :function_calls, :function_responses, :long_running_tool_ids,
125
+ :branch, :actions
126
+
127
+ # Initialize an event
128
+ #
129
+ # @param id [String] Unique event identifier (optional)
130
+ # @param invocation_id [String] Invocation ID
131
+ # @param author [String] Event author (user or agent name)
132
+ # @param timestamp [Time] Event timestamp (optional)
133
+ # @param content [String] Event content (optional)
134
+ # @param function_calls [Array<FunctionCall>] Function calls (optional)
135
+ # @param function_responses [Array<FunctionResponse>] Function responses (optional)
136
+ # @param long_running_tool_ids [Set] Long-running tool IDs (optional)
137
+ # @param branch [String] Conversation branch (optional)
138
+ # @param actions [EventActions] Event actions (optional)
139
+ def initialize(invocation_id:, author:, id: nil, timestamp: nil,
140
+ content: nil, function_calls: [], function_responses: [],
141
+ long_running_tool_ids: nil, branch: nil, actions: nil)
142
+ @id = id || self.class.new_id
143
+ @invocation_id = invocation_id
144
+ @author = author
145
+ @timestamp = timestamp || Time.now
146
+ @content = content
147
+ @function_calls = function_calls
148
+ @function_responses = function_responses
149
+ @long_running_tool_ids = long_running_tool_ids || Set.new
150
+ @branch = branch
151
+ @actions = actions
152
+ end
153
+
154
+ # Check if this is a final response
155
+ #
156
+ # @return [Boolean] True if final response
157
+ def is_final_response?
158
+ return true if @author == "user"
159
+ return true if @actions.nil?
160
+
161
+ @actions.transfer_to_agent.nil?
162
+ end
163
+
164
+ # Generate a new event ID
165
+ #
166
+ # @return [String] New UUID-based ID
167
+ def self.new_id
168
+ SecureRandom.uuid
169
+ end
170
+
171
+ # Convert to hash representation
172
+ #
173
+ # @return [Hash] Hash representation
174
+ def to_h
175
+ result = {
176
+ id: @id,
177
+ invocation_id: @invocation_id,
178
+ author: @author,
179
+ timestamp: @timestamp.iso8601,
180
+ content: @content,
181
+ function_calls: @function_calls.map(&:to_h),
182
+ function_responses: @function_responses.map(&:to_h),
183
+ long_running_tool_ids: @long_running_tool_ids.to_a
184
+ }
185
+ result[:branch] = @branch unless @branch.nil?
186
+ result[:actions] = @actions.to_h if @actions
187
+ result
188
+ end
189
+ end
190
+ end
191
+ end