Class: Google::ADK::LlmAgent

Inherits:
BaseAgent show all
Defined in:
lib/google/adk/agents/llm_agent.rb

Overview

LLM-powered agent that can use tools and interact with language models

Constant Summary

Constants inherited from BaseAgent

BaseAgent::AGENT_NAME_REGEX

Instance Attribute Summary collapse

Attributes inherited from BaseAgent

#after_agent_callback, #before_agent_callback, #description, #name, #parent_agent, #sub_agents

Instance Method Summary collapse

Methods inherited from BaseAgent

#clone, #find_agent, #find_sub_agent, from_config, #run_live

Constructor Details

#initialize(name:, model: nil, instructions: nil, description: nil, tools: [], sub_agents: [], include_from_children: [], inherit_parent_model: false, before_model_callback: nil, after_model_callback: nil, before_tool_callback: nil, after_tool_callback: nil, before_agent_callback: nil, after_agent_callback: nil, code_executor: nil, planner: nil) ⇒ LlmAgent

Initialize an LLM agent

Parameters:

  • name (String)

    Agent name

  • model (String, nil) (defaults to: nil)

    Model name (e.g., “gemini-2.0-flash”)

  • instructions (String) (defaults to: nil)

    System instructions (can include variables)

  • description (String) (defaults to: nil)

    Agent description

  • tools (Array) (defaults to: [])

    Tools available to the agent

  • sub_agents (Array<BaseAgent>) (defaults to: [])

    Child agents

  • include_from_children (Array<String>) (defaults to: [])

    What to include from children

  • inherit_parent_model (Boolean) (defaults to: false)

    Whether to inherit parent’s model

  • before_model_callback (Proc) (defaults to: nil)

    Called before model invocation

  • after_model_callback (Proc) (defaults to: nil)

    Called after model invocation

  • before_tool_callback (Proc) (defaults to: nil)

    Called before tool execution

  • after_tool_callback (Proc) (defaults to: nil)

    Called after tool execution

  • code_executor (Object) (defaults to: nil)

    Optional code executor

  • planner (Object) (defaults to: nil)

    Optional planner

Raises:

  • (ArgumentError)

    If model is required but not provided



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/google/adk/agents/llm_agent.rb', line 34

def initialize(name:, model: nil, instructions: nil, description: nil,
               tools: [], sub_agents: [], include_from_children: [],
               inherit_parent_model: false,
               before_model_callback: nil, after_model_callback: nil,
               before_tool_callback: nil, after_tool_callback: nil,
               before_agent_callback: nil, after_agent_callback: nil,
               code_executor: nil, planner: nil)
  super(
    name: name,
    description: description,
    sub_agents: sub_agents,
    before_agent_callback: before_agent_callback,
    after_agent_callback: after_agent_callback
  )

  raise ArgumentError, "model is required" if model.nil? && !inherit_parent_model

  @model = model
  @instructions = instructions
  @tools = tools
  @include_from_children = include_from_children
  @inherit_parent_model = inherit_parent_model
  @before_model_callback = before_model_callback
  @after_model_callback = after_model_callback
  @before_tool_callback = before_tool_callback
  @after_tool_callback = after_tool_callback
  @code_executor = code_executor
  @planner = planner
end

Instance Attribute Details

#after_model_callbackObject (readonly)

Returns the value of attribute after_model_callback.



13
14
15
# File 'lib/google/adk/agents/llm_agent.rb', line 13

def after_model_callback
  @after_model_callback
end

#after_tool_callbackObject (readonly)

Returns the value of attribute after_tool_callback.



13
14
15
# File 'lib/google/adk/agents/llm_agent.rb', line 13

def after_tool_callback
  @after_tool_callback
end

#before_model_callbackObject (readonly)

Returns the value of attribute before_model_callback.



13
14
15
# File 'lib/google/adk/agents/llm_agent.rb', line 13

def before_model_callback
  @before_model_callback
end

#before_tool_callbackObject (readonly)

Returns the value of attribute before_tool_callback.



13
14
15
# File 'lib/google/adk/agents/llm_agent.rb', line 13

def before_tool_callback
  @before_tool_callback
end

#code_executorObject (readonly)

Returns the value of attribute code_executor.



13
14
15
# File 'lib/google/adk/agents/llm_agent.rb', line 13

def code_executor
  @code_executor
end

#include_from_childrenObject (readonly)

Returns the value of attribute include_from_children.



13
14
15
# File 'lib/google/adk/agents/llm_agent.rb', line 13

def include_from_children
  @include_from_children
end

#inherit_parent_modelObject (readonly)

Returns the value of attribute inherit_parent_model.



13
14
15
# File 'lib/google/adk/agents/llm_agent.rb', line 13

def inherit_parent_model
  @inherit_parent_model
end

#instructionsObject (readonly)

Returns the value of attribute instructions.



13
14
15
# File 'lib/google/adk/agents/llm_agent.rb', line 13

def instructions
  @instructions
end

#modelObject (readonly)

Returns the value of attribute model.



13
14
15
# File 'lib/google/adk/agents/llm_agent.rb', line 13

def model
  @model
end

#plannerObject (readonly)

Returns the value of attribute planner.



13
14
15
# File 'lib/google/adk/agents/llm_agent.rb', line 13

def planner
  @planner
end

#toolsObject (readonly)

Returns the value of attribute tools.



13
14
15
# File 'lib/google/adk/agents/llm_agent.rb', line 13

def tools
  @tools
end

Instance Method Details

#canonical_instructions(context) ⇒ String

Get canonical instructions with variables interpolated

Parameters:

  • context (Context)

    Current context

Returns:

  • (String)

    Processed instructions



82
83
84
85
86
87
88
89
90
91
# File 'lib/google/adk/agents/llm_agent.rb', line 82

def canonical_instructions(context)
  return "" unless @instructions

  # Simple variable interpolation from state
  processed = @instructions.dup
  context.state.each do |key, value|
    processed.gsub!("{#{key}}", value.to_s)
  end
  processed
end

#canonical_modelString

Get the canonical model to use

Returns:

  • (String)

    Model name

Raises:



68
69
70
71
72
73
74
75
76
# File 'lib/google/adk/agents/llm_agent.rb', line 68

def canonical_model
  return @model if @model

  if @inherit_parent_model && @parent_agent&.respond_to?(:canonical_model)
    return @parent_agent.canonical_model
  end

  raise ConfigurationError, "No model specified for agent #{@name}"
end

#canonical_toolsArray<BaseTool>

Get canonical tools (converted to proper tool objects)

Returns:



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/google/adk/agents/llm_agent.rb', line 96

def canonical_tools
  @tools.map do |tool|
    case tool
    when BaseTool
      tool
    when BaseAgent
      AgentTool.new(agent: tool)
    else
      # Assume it's a callable (proc/method)
      FunctionTool.new(
        name: tool_name_from_callable(tool),
        description: "Function tool",
        callable: tool
      )
    end
  end
end

#run_async(message, context: nil) {|Event| ... } ⇒ Object

Implementation of async run

Parameters:

  • message (String)

    User message

  • context (InvocationContext) (defaults to: nil)

    Invocation context

Yields:

  • (Event)

    Events during execution



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/google/adk/agents/llm_agent.rb', line 119

def run_async(message, context: nil)
  Enumerator.new do |yielder|
    begin
      # Initialize Gemini client
      client = GeminiClient.new
      
      # Build simple message for now
      messages = [{ role: "user", content: message }]
      
      # Get tools and convert to Gemini format
      tools = canonical_tools.map(&:to_gemini_schema)
      
      # Create more forceful system instruction for tools
      system_instruction = build_tool_aware_instructions(context, tools)
      
      # Call Gemini API with tools
      response = client.generate_content(
        model: canonical_model,
        messages: messages,
        tools: tools.empty? ? nil : tools,
        system_instruction: system_instruction
      )
      
      # Process response
      if response.dig("candidates", 0, "content", "parts")
        parts = response["candidates"][0]["content"]["parts"]
        
        parts.each do |part|
          if part["text"]
            # Regular text response
            event = Event.new(
              invocation_id: context&.invocation_id || "inv-#{SecureRandom.uuid}",
              author: @name,
              content: part["text"]
            )
            yielder << event
            context&.add_event(event) if context
            
          elsif part["functionCall"]
            # Tool call - execute and get result
            function_call = part["functionCall"]
            tool_name = function_call["name"]
            tool_args = function_call["args"] || {}
            
            # Execute the tool
            tool_result = execute_tool_call(tool_name, tool_args, yielder, context)
            
            # Call LLM again with tool result
            tool_messages = messages + [
              {
                role: "model",
                parts: [{ functionCall: function_call }]
              },
              {
                role: "function", 
                parts: [{
                  functionResponse: {
                    name: tool_name,
                    response: tool_result
                  }
                }]
              }
            ]
            
            follow_up_response = client.generate_content(
              model: canonical_model,
              messages: tool_messages,
              system_instruction: system_instruction
            )
            
            if follow_up_response.dig("candidates", 0, "content", "parts", 0, "text")
              final_text = follow_up_response["candidates"][0]["content"]["parts"][0]["text"]
              event = Event.new(
                invocation_id: context&.invocation_id || "inv-#{SecureRandom.uuid}",
                author: @name,
                content: final_text
              )
              yielder << event
              context&.add_event(event) if context
            end
          end
        end
      else
        # Fallback response
        event = Event.new(
          invocation_id: context&.invocation_id || "inv-#{SecureRandom.uuid}",
          author: @name,
          content: "I'm sorry, I couldn't process that request."
        )
        yielder << event
        context&.add_event(event) if context
      end
      
    rescue => e
      # Error handling
      puts "[DEBUG] Gemini error: #{e.message}" if ENV["DEBUG"]
      puts "[DEBUG] Backtrace: #{e.backtrace.first(3).join(', ')}" if ENV["DEBUG"]
      
      event = Event.new(
        invocation_id: context&.invocation_id || "inv-#{SecureRandom.uuid}",
        author: @name,
        content: "Error calling Gemini API: #{e.message}. Please check your GEMINI_API_KEY."
      )
      yielder << event
      context&.add_event(event) if context
    end
  end
end