# Context
Supported in ADKPython v0.1.0TypeScript v0.2.0Go v0.1.0Java v0.1.0
In the Agent Development Kit (ADK), *context* refers to the crucial bundle of information available to your agent and its tools during specific operations. Think of it as the necessary background knowledge and resources needed to handle a current task or conversation turn effectively. Agents often need more than just the latest user message to perform well. Context is essential because it enables: 1. **Maintaining State:** Remembering details across multiple steps in a conversation (e.g., user preferences, previous calculations, items in a shopping cart). This is primarily managed through **session state**. 2. **Passing Data:** Sharing information discovered or generated in one step (like an LLM call or a tool execution) with subsequent steps. Session state is key here too. 3. **Accessing Services:** Interacting with framework capabilities like: * **Artifact Storage:** Saving or loading files or data blobs (like PDFs, images, configuration files) associated with the session. * **Memory:** Searching for relevant information from past interactions or external knowledge sources connected to the user. * **Authentication:** Requesting and retrieving credentials needed by tools to access external APIs securely. 4. **Identity and Tracking:** Knowing which agent is currently running (`agent.name`) and uniquely identifying the current request-response cycle (`invocation_id`) for logging and debugging. 5. **Tool-Specific Actions:** Enabling specialized operations within tools, such as requesting authentication or searching memory, which require access to the current interaction's details. The central piece holding all this information together for a single, complete user-request-to-final-response cycle (an **invocation**) is the `InvocationContext`. However, you typically won't create or manage this object directly. The ADK framework creates it when an invocation starts (e.g., via `runner.run_async`) and passes the relevant contextual information implicitly to your agent code, callbacks, and tools. === "Python" ```python # How the framework provides context from google.adk import Runner # 1. You initialize a Runner with your agent and services runner = Runner( app_name="my_app", agent=my_root_agent, session_service=my_session_service, artifact_service=my_artifact_service, ) # 2. You call run_async with the user input # Note: run_async is an asynchronous generator yielding Events. # The framework internally creates an InvocationContext and passes it # implicitly to your agent code, callbacks, and tools. async for event in runner.run_async( user_id="user123", session_id="session456", new_message=user_message ): print(event.stringify_content()) # As a developer, you work with the context objects provided in method arguments. ``` === "TypeScript" ```typescript /* Conceptual Pseudocode: How the framework provides context (Internal Logic) */ const runner = new InMemoryRunner({ agent: myRootAgent }); const session = await runner.sessionService.createSession({ ... }); const userMessage = createUserContent(...); // --- Inside runner.runAsync(...) --- // 1. Framework creates the main context for this specific run const invocationContext = new InvocationContext({ invocationId: "unique-id-for-this-run", session: session, userContent: userMessage, agent: myRootAgent, // The starting agent sessionService: runner.sessionService, pluginManager: runner.pluginManager, // ... other necessary fields ... }); // // 2. Framework calls the agent's run method, passing the context implicitly await myRootAgent.runAsync(invocationContext); // --- End Internal Logic --- // As a developer, you work with the context objects provided in method arguments. ``` === "Go" ```go /* Conceptual Pseudocode: How the framework provides context (Internal Logic) */ --8<-- "examples/go/snippets/context/main.go:conceptual_runner_example" ``` === "Java" ```java /* How the framework provides context */ InMemoryRunner runner = new InMemoryRunner(agent); Session session = runner .sessionService() .createSession(runner.appName(), USER_ID, initialState, SESSION_ID ) .blockingGet(); try (Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8)) { while (true) { System.out.print("\nYou > "); String userInput = scanner.nextLine(); if ("quit".equalsIgnoreCase(userInput)) { break; } Content userMsg = Content.fromParts(Part.fromText(userInput)); Flowable events = runner.runAsync(session.userId(), session.id(), userMsg); System.out.print("\nAgent > "); events.blockingForEach(event -> System.out.print(event.stringifyContent())); } } ``` ## The Different types of Context While `InvocationContext` acts as the comprehensive internal container, ADK provides specialized context objects tailored to specific situations. This ensures you have the right tools and permissions for the task at hand without needing to handle the full complexity of the internal context everywhere. Here are the different "flavors" you'll encounter: 1. **`InvocationContext`** * **Where Used:** Received as the `ctx` argument directly within an agent's core implementation methods (`_run_async_impl`, `_run_live_impl`). * **Purpose:** Provides access to the *entire* state of the current invocation. This is the most comprehensive context object. * **Key Contents:** Direct access to `session` (including `state` and `events`), the current `agent` instance, `invocation_id`, initial `user_content`, references to configured services (`artifact_service`, `memory_service`, `session_service`), and fields related to live/streaming modes. * **Use Case:** Primarily used when the agent's core logic needs direct access to the overall session or services, though often state and artifact interactions are delegated to callbacks/tools which use their own contexts. Also used to control the invocation itself (e.g., setting `ctx.end_invocation = True`). === "Python" ```python # Agent implementation receiving InvocationContext from google.adk.agents import BaseAgent from google.adk.agents.invocation_context import InvocationContext from google.adk.events import Event from typing import AsyncGenerator class MyAgent(BaseAgent): async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]: # Direct access example agent_name = ctx.agent.name session_id = ctx.session.id print(f"Agent {agent_name} running in session {session_id} for invocation {ctx.invocation_id}") # ... agent logic using ctx ... yield # ... event ... ``` === "TypeScript" ```typescript // Pseudocode: Agent implementation receiving InvocationContext import { BaseAgent, InvocationContext, Event } from '@google/adk'; class MyAgent extends BaseAgent { async *runAsyncImpl(ctx: InvocationContext): AsyncGenerator { // Direct access example const agentName = ctx.agent.name; const sessionId = ctx.session.id; console.log(`Agent ${agentName} running in session ${sessionId} for invocation ${ctx.invocationId}`); // ... agent logic using ctx ... yield; // ... event ... } } ``` === "Go" ```go import ( "google.golang.org/adk/agent" "google.golang.org/adk/session" ) --8<-- "examples/go/snippets/context/main.go:invocation_context_agent" ``` === "Java" ```java // Example: Agent implementation receiving InvocationContext import com.google.adk.agents.BaseAgent; import com.google.adk.agents.InvocationContext; import com.google.adk.events.Event; import io.reactivex.rxjava3.core.Flowable; public class MyAgent extends BaseAgent { @Override protected Flowable runAsyncImpl(InvocationContext invocationContext) { // Direct access example String agentName = invocationContext.agent().name(); String sessionId = invocationContext.session().id(); String invocationId = invocationContext.invocationId(); System.out.println("Agent " + agentName + " running in session " + sessionId + " for invocation " + invocationId); // ... agent logic using invocationContext ... return Flowable.empty(); } } ``` 2. **`ReadonlyContext`** * **Where Used:** Provided in scenarios where only read access to basic information is needed and mutation is disallowed (e.g., `InstructionProvider` functions). It's also the base class for other contexts. * **Purpose:** Offers a safe, read-only view of fundamental contextual details. * **Key Contents:** `invocation_id`, `agent_name`, and a read-only *view* of the current `state`. === "Python" ```python # Example: Instruction provider receiving ReadonlyContext from google.adk.agents.readonly_context import ReadonlyContext def my_instruction_provider(context: ReadonlyContext) -> str: # Read-only access example # The state property provides a read-only MappingProxyType view of the state user_tier = context.state.get("user_tier", "standard") # context.state['new_key'] = 'value' # TypeError: 'mappingproxy' object does not support item assignment return f"Process the request for a {user_tier} user." ``` === "TypeScript" ```typescript // Pseudocode: Instruction provider receiving ReadonlyContext import { ReadonlyContext } from '@google/adk'; function myInstructionProvider(context: ReadonlyContext): string { // Read-only access example // The state object is read-only const userTier = context.state.get('user_tier') ?? 'standard'; // context.state.set('new_key', 'value'); // This would fail or throw an error return `Process the request for a ${userTier} user.`; } ``` === "Go" ```go import "google.golang.org/adk/agent" --8<-- "examples/go/snippets/context/main.go:readonly_context_instruction" ``` === "Java" ```java // Example: Instruction provider receiving ReadonlyContext import com.google.adk.agents.ReadonlyContext; public String myInstructionProvider(ReadonlyContext context) { // Read-only access example // state() returns an unmodifiable view of the session state String userTier = (String) context.state().getOrDefault("user_tier", "standard"); // context.state().put("new_key", "value"); // UnsupportedOperationException return "Process the request for a " + userTier + " user."; } ``` 3. **`CallbackContext`** * **Where Used:** Passed as `callback_context` to agent lifecycle callbacks (`before_agent_callback`, `after_agent_callback`) and model interaction callbacks (`before_model_callback`, `after_model_callback`). * **Purpose:** Facilitates inspecting and modifying state, interacting with artifacts, and accessing invocation details *specifically within callbacks*. * **Key Capabilities (Adds to `ReadonlyContext`):** * **Mutable `state` Property:** Allows reading *and writing* to session state. Changes made here (`callback_context.state['key'] = value`) are tracked and associated with the event generated by the framework after the callback. * **Artifact Methods:** `load_artifact(filename)` and `save_artifact(filename, part)` methods for interacting with the configured `artifact_service`. * Direct `user_content` access. *(Note: In TypeScript, `CallbackContext` and `ToolContext` are unified into a single `Context` type.)* === "Python" ```python # Example: Callback receiving Context (CallbackContext is unified into Context) from google.adk.agents.context import Context from google.adk.models import LlmRequest from google.genai import types from typing import Optional def my_before_model_cb(context: Context, request: LlmRequest) -> Optional[types.Content]: # Read/Write state example call_count = context.state.get("model_calls", 0) context.state["model_calls"] = call_count + 1 # Modify state (tracks delta) # Optionally load an artifact # config_part = context.load_artifact("model_config.json") print(f"Preparing model call #{call_count + 1} for invocation {context.invocation_id}") return None # Allow model call to proceed ``` === "TypeScript" ```typescript // Pseudocode: Callback receiving Context import { Context, LlmRequest } from '@google/adk'; import { Content } from '@google/genai'; function myBeforeModelCb(context: Context, request: LlmRequest): Content | undefined { // Read/Write state example const callCount = (context.state.get('model_calls') as number) || 0; context.state.set('model_calls', callCount + 1); // Modify state // Optionally load an artifact // const configPart = await context.loadArtifact('model_config.json'); console.log(`Preparing model call #${callCount + 1} for invocation ${context.invocationId}`); return undefined; // Allow model call to proceed } ``` === "Go" ```go import ( "google.golang.org/adk/agent" "google.golang.org/adk/model" ) --8<-- "examples/go/snippets/context/main.go:callback_context_callback" ``` === "Java" ```java // Example: Callback receiving CallbackContext import com.google.adk.agents.CallbackContext; import com.google.adk.models.LlmRequest; import com.google.adk.models.LlmResponse; import io.reactivex.rxjava3.core.Maybe; public Maybe myBeforeModelCb(CallbackContext callbackContext, LlmRequest request) { // Read/Write state example int callCount = (int) callbackContext.state().getOrDefault("model_calls", 0); callbackContext.state().put("model_calls", callCount + 1); // Modify state (tracks delta) // Optionally load an artifact // Maybe configPart = callbackContext.loadArtifact("model_config.json"); System.out.println("Preparing model call " + (callCount + 1) + " for invocation " + callbackContext.invocationId()); return Maybe.empty(); // Allow model call to proceed } ``` 4. **`ToolContext`** * **Where Used:** Passed as `tool_context` to the functions backing `FunctionTool`s and to tool execution callbacks (`before_tool_callback`, `after_tool_callback`). * **Purpose:** Provides everything `CallbackContext` does, plus specialized methods essential for tool execution, like handling authentication, searching memory, and listing artifacts. * **Key Capabilities (Adds to `CallbackContext`):** * **Authentication Methods:** `request_credential(auth_config)` to trigger an auth flow, and `get_auth_response(auth_config)` to retrieve credentials provided by the user/system. * **Artifact Listing:** `list_artifacts()` to discover available artifacts in the session. * **Memory Search:** `search_memory(query)` to query the configured `memory_service`. * **`function_call_id` Property:** Identifies the specific function call from the LLM that triggered this tool execution, crucial for linking authentication requests or responses back correctly. * **`actions` Property:** Direct access to the `EventActions` object for this step, allowing the tool to signal state changes, auth requests, etc. === "Python" ```python # Example: Tool function receiving ToolContext from google.adk.tools import ToolContext from typing import Dict, Any # Assume this function is wrapped by a FunctionTool def search_external_api(query: str, tool_context: ToolContext) -> Dict[str, Any]: api_key = tool_context.state.get("api_key") if not api_key: # Define required auth config # auth_config = AuthConfig(...) # tool_context.request_credential(auth_config) # Request credentials # Use the 'actions' property to signal the auth request has been made # tool_context.actions.requested_auth_configs[tool_context.function_call_id] = auth_config return {"status": "Auth Required"} # Use the API key... print(f"Tool executing for query '{query}' using API key. Invocation: {tool_context.invocation_id}") # Optionally search memory or list artifacts # relevant_docs = tool_context.search_memory(f"info related to {query}") # available_files = tool_context.list_artifacts() return {"result": f"Data for {query} fetched."} ``` === "TypeScript" ```typescript // Pseudocode: Tool function receiving Context import { Context } from '@google/adk'; // __Assume this function is wrapped by a FunctionTool__ function searchExternalApi(query: string, context: Context): { [key: string]: string } { const apiKey = context.state.get('api_key') as string; if (!apiKey) { // Define required auth config // const authConfig = new AuthConfig(...); // context.requestCredential(authConfig); // Request credentials // The 'actions' property is now automatically updated by requestCredential return { status: 'Auth Required' }; } // Use the API key... console.log(`Tool executing for query '${query}' using API key. Invocation: ${context.invocationId}`); // Optionally search memory or list artifacts // Note: accessing services like memory/artifacts is typically async in TS, // so you would need to mark this function 'async' if you reused them. // context.searchMemory(`info related to ${query}`).then(...) // context.listArtifacts().then(...) return { result: `Data for ${query} fetched.` }; } ``` === "Go" ```go import "google.golang.org/adk/tool" --8<-- "examples/go/snippets/context/main.go:tool_context_tool" ``` === "Java" ```java // Example: Tool function receiving ToolContext import com.google.adk.tools.ToolContext; import java.util.Map; // Assume this function is wrapped by a FunctionTool public Map searchExternalApi(String query, ToolContext toolContext) { String apiKey = (String) toolContext.state().getOrDefault("api_key", ""); if (apiKey.isEmpty()) { // Define required auth config // authConfig = AuthConfig(...); // toolContext.requestCredential(authConfig); // Request credentials // Use the 'actions' property to signal the auth request has been made return Map.of("status", "Auth Required"); } // Use the API key... System.out.println("Tool executing for query " + query + " using API key."); // Optionally list artifacts // Single> availableFiles = toolContext.listArtifacts(); return Map.of("result", "Data for " + query + " fetched"); } ``` Understanding these different context objects and when to use them is key to effectively managing state, accessing services, and controlling the flow of your ADK application. The next section will detail common tasks you can perform using these contexts. ## Common Tasks Using Context Now that you understand the different context objects, let's focus on how to use them for common tasks when building your agents and tools. ### Accessing Information You'll frequently need to read information stored within the context. * **Reading Session State:** Access data saved in previous steps or user/app-level settings. Use dictionary-like access on the `state` property. === "Python" ```python # Example: In a Tool function from google.adk.tools import ToolContext def my_tool(tool_context: ToolContext, **kwargs): user_pref = tool_context.state.get("user_display_preference", "default_mode") api_endpoint = tool_context.state.get("app:api_endpoint") # Read app-level state if user_pref == "dark_mode": # ... apply dark mode logic ... pass print(f"Using API endpoint: {api_endpoint}") # ... rest of tool logic ... # Example: In a Callback function from google.adk.agents.context import Context def my_callback(context: Context, **kwargs): last_tool_result = context.state.get("temp:last_api_result") # Read temporary state if last_tool_result: print(f"Found temporary result from last tool: {last_tool_result}") # ... callback logic ... ``` === "TypeScript" ```typescript // Pseudocode: In a Tool function import { Context } from '@google/adk'; async function myTool(context: Context) { const userPref = context.state.get('user_display_preference', 'default_mode'); const apiEndpoint = context.state.get('app:api_endpoint'); // Read app-level state if (userPref === 'dark_mode') { // ... apply dark mode logic ... } console.log(`Using API endpoint: ${apiEndpoint}`); // ... rest of tool logic ... } // Pseudocode: In a Callback function import { Context } from '@google/adk'; function myCallback(context: Context) { const lastToolResult = context.state.get('temp:last_api_result'); // Read temporary state if (lastToolResult) { console.log(`Found temporary result from last tool: ${lastToolResult}`); } // ... callback logic ... } ``` === "Go" ```go import ( "google.golang.org/adk/agent" "google.golang.org/adk/session" "google.golang.org/adk/tool" "google.golang.org/genai" ) --8<-- "examples/go/snippets/context/main.go:accessing_state_tool" --8<-- "examples/go/snippets/context/main.go:accessing_state_callback" ``` === "Java" ```java // Example: In a Tool function import com.google.adk.tools.ToolContext; public void myTool(ToolContext toolContext) { String userPref = (String) toolContext.state().getOrDefault("user_display_preference", "default_mode"); String apiEndpoint = (String) toolContext.state().get("app:api_endpoint"); // Read app-level state if ("dark_mode".equals(userPref)) { // ... apply dark mode logic ... } System.out.println("Using API endpoint: " + apiEndpoint); // ... rest of tool logic ... } // Example: In a Callback function import com.google.adk.agents.CallbackContext; public void myCallback(CallbackContext callbackContext) { String lastToolResult = (String) callbackContext.state().get("temp:last_api_result"); // Read temporary state if (lastToolResult != null && !lastToolResult.isEmpty()) { System.out.println("Found temporary result from last tool: " + lastToolResult); } // ... callback logic ... } ``` * **Getting Current Identifiers:** Useful for logging or custom logic based on the current operation. === "Python" ```python # Example: In any context (ToolContext shown) from google.adk.tools import ToolContext def log_tool_usage(tool_context: ToolContext, **kwargs): agent_name = tool_context.agent_name inv_id = tool_context.invocation_id func_call_id = getattr(tool_context, 'function_call_id', 'N/A') # Specific to ToolContext print(f"Log: Invocation={inv_id}, Agent={agent_name}, FunctionCallID={func_call_id} - Tool Executed.") ``` === "TypeScript" ```typescript // Pseudocode: In any context import { Context } from '@google/adk'; function logToolUsage(context: Context) { const agentName = context.agentName; const invId = context.invocationId; const functionCallId = context.functionCallId ?? 'N/A'; // Available when executing a tool console.log(`Log: Invocation=${invId}, Agent=${agentName}, FunctionCallID=${functionCallId} - Tool Executed.`); } ``` === "Go" ```go import "google.golang.org/adk/tool" --8<-- "examples/go/snippets/context/main.go:accessing_ids" ``` === "Java" ```java // Example: In any context (ToolContext shown) import com.google.adk.tools.ToolContext; public void logToolUsage(ToolContext toolContext) { String agentName = toolContext.agentName(); String invId = toolContext.invocationId(); String functionCallId = toolContext.functionCallId().orElse("N/A"); // Specific to ToolContext System.out.println("Log: Invocation= " + invId + " Agent= " + agentName + " FunctionCallID= " + functionCallId); } ``` * **Accessing the Initial User Input:** Refer back to the message that started the current invocation. === "Python" ```python # Example: In a Callback from google.adk.agents.context import Context def check_initial_intent(context: Context, **kwargs): initial_text = "N/A" if context.user_content and context.user_content.parts: initial_text = context.user_content.parts[0].text or "Non-text input" print(f"This invocation started with user input: '{initial_text}'") # Example: In an Agent's _run_async_impl # async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]: # if ctx.user_content and ctx.user_content.parts: # initial_text = ctx.user_content.parts[0].text # print(f"Agent logic remembering initial query: {initial_text}") # ... ``` === "TypeScript" ```typescript // Pseudocode: In a Callback import { Context } from '@google/adk'; function checkInitialIntent(context: Context) { let initialText = 'N/A'; const userContent = context.userContent; if (userContent?.parts?.length) { initialText = userContent.parts[0].text ?? 'Non-text input'; } console.log(`This invocation started with user input: '${initialText}'`); } ``` === "Go" ```go import ( "google.golang.org/adk/agent" "google.golang.org/genai" ) --8<-- "examples/go/snippets/context/main.go:accessing_initial_user_input" ``` === "Java" ```java // Example: In a Callback import com.google.adk.agents.CallbackContext; import com.google.genai.types.Content; public void checkInitialIntent(CallbackContext callbackContext) { String initialText = "N/A"; if (callbackContext.userContent().isPresent() && callbackContext.userContent().get().parts() != null && !callbackContext.userContent().get().parts().get().isEmpty()) { initialText = callbackContext.userContent().get().parts().get().get(0).text().orElse("Non-text input"); // ... System.out.println("This invocation started with user input: " + initialText); } } ``` ### Managing State State is crucial for memory and data flow. When you modify state using `CallbackContext` or `ToolContext`, the changes are automatically tracked and persisted by the framework. * **How it Works:** Writing to `callback_context.state['my_key'] = my_value` or `tool_context.state['my_key'] = my_value` adds this change to the `EventActions.state_delta` associated with the current step's event. The `SessionService` then applies these deltas when persisting the event. * **Passing Data Between Tools** === "Python" ```python # Example: Tool 1 - Fetches user ID from google.adk.tools import ToolContext import uuid def get_user_profile(tool_context: ToolContext) -> dict: user_id = str(uuid.uuid4()) # Simulate fetching ID # Save the ID to state for the next tool tool_context.state["temp:current_user_id"] = user_id return {"profile_status": "ID generated"} # Example: Tool 2 - Uses user ID from state def get_user_orders(tool_context: ToolContext) -> dict: user_id = tool_context.state.get("temp:current_user_id") if not user_id: return {"error": "User ID not found in state"} print(f"Fetching orders for user ID: {user_id}") # ... logic to fetch orders using user_id ... return {"orders": ["order123", "order456"]} ``` === "TypeScript" ```typescript // Pseudocode: Tool 1 - Fetches user ID import { Context } from '@google/adk'; import { v4 as uuidv4 } from 'uuid'; function getUserProfile(context: Context): Record { const userId = uuidv4(); // Simulate fetching ID // Save the ID to state for the next tool context.state.set('temp:current_user_id', userId); return { profile_status: 'ID generated' }; } // Pseudocode: Tool 2 - Uses user ID from state function getUserOrders(context: Context): Record { const userId = context.state.get('temp:current_user_id'); if (!userId) { return { error: 'User ID not found in state' }; } console.log(`Fetching orders for user ID: ${userId}`); // ... logic to fetch orders using user_id ... return { orders: ['order123', 'order456'] }; } ``` === "Go" ```go import "google.golang.org/adk/tool" --8<-- "examples/go/snippets/context/main.go:passing_data_tool1" --8<-- "examples/go/snippets/context/main.go:passing_data_tool2" ``` === "Java" ```java // Example: Tool 1 - Fetches user ID import com.google.adk.tools.ToolContext; import java.util.Map; import java.util.UUID; public Map getUserProfile(ToolContext toolContext) { String userId = UUID.randomUUID().toString(); // Save the ID to state for the next tool toolContext.state().put("temp:current_user_id", userId); return Map.of("profile_status", "ID generated"); } // Example: Tool 2 - Uses user ID from state public Map getUserOrders(ToolContext toolContext) { String userId = (String) toolContext.state().get("temp:current_user_id"); if (userId == null || userId.isEmpty()) { return Map.of("error", "User ID not found in state"); } System.out.println("Fetching orders for user id: " + userId); // ... logic to fetch orders using userId ... return Map.of("orders", "order123"); } ``` * **Updating User Preferences:** === "Python" ```python # Example: Tool or Callback identifies a preference from google.adk.tools import ToolContext # Or Context def set_user_preference(tool_context: ToolContext, preference: str, value: str) -> dict: # Use 'user:' prefix for user-level state (if using a persistent SessionService) state_key = f"user:{preference}" tool_context.state[state_key] = value print(f"Set user preference '{preference}' to '{value}'") return {"status": "Preference updated"} ``` === "TypeScript" ```typescript // Pseudocode: Tool or Callback identifies a preference import { Context } from '@google/adk'; function setUserPreference(context: Context, preference: string, value: string): Record { // Use 'user:' prefix for user-level state (if using a persistent SessionService) const stateKey = `user:${preference}`; context.state.set(stateKey, value); console.log(`Set user preference '${preference}' to '${value}'`); return { status: 'Preference updated' }; } ``` === "Go" ```go import "google.golang.org/adk/tool" --8<-- "examples/go/snippets/context/main.go:updating_preferences" ``` === "Java" ```java // Example: Tool or Callback identifies a preference import com.google.adk.tools.ToolContext; // Or CallbackContext public Map setUserPreference(ToolContext toolContext, String preference, String value) { // Use 'user:' prefix for user-level state (if using a persistent SessionService) String stateKey = "user:" + preference; toolContext.state().put(stateKey, value); System.out.println("Set user preference '" + preference + "' to '" + value + "'"); return Map.of("status", "Preference updated"); } ``` * **State Prefixes:** While basic state is session-specific, prefixes like `app:` and `user:` can be used with persistent `SessionService` implementations (like `DatabaseSessionService` or `VertexAiSessionService`) to indicate broader scope (app-wide or user-wide across sessions). `temp:` can denote data only relevant within the current invocation. ### Working with Artifacts Use artifacts to handle files or large data blobs associated with the session. Common use case: processing uploaded documents. * **Document Summarizer Example Flow:** 1. **Ingest Reference (e.g., in a Setup Tool or Callback):** Save the *path or URI* of the document, not the entire content, as an artifact. === "Python" ```python # Example: In a callback or initial tool from google.adk.agents.context import Context # Or ToolContext from google.genai import types def save_document_reference(context: Context, file_path: str) -> None: # Assume file_path is something like "gs://my-bucket/docs/report.pdf" or "/local/path/to/report.pdf" try: # Create a Part containing the path/URI text artifact_part = types.Part.from_text(file_path) version = context.save_artifact("document_to_summarize.txt", artifact_part) print(f"Saved document reference '{file_path}' as artifact version {version}") # Store the filename in state if needed by other tools context.state["temp:doc_artifact_name"] = "document_to_summarize.txt" except ValueError as e: print(f"Error saving artifact: {e}") # E.g., Artifact service not configured except Exception as e: print(f"Unexpected error saving artifact reference: {e}") # Example usage: # save_document_reference(context, "gs://my-bucket/docs/report.pdf") ``` === "TypeScript" ```typescript // Pseudocode: In a callback or initial tool import { Context } from '@google/adk'; import type { Part } from '@google/genai'; async function saveDocumentReference(context: Context, filePath: string) { // Assume filePath is something like "gs://my-bucket/docs/report.pdf" or "/local/path/to/report.pdf" try { // Create a Part containing the path/URI text const artifactPart: Part = { text: filePath }; const version = await context.saveArtifact('document_to_summarize.txt', artifactPart); console.log(`Saved document reference '${filePath}' as artifact version ${version}`); // Store the filename in state if needed by other tools context.state.set('temp:doc_artifact_name', 'document_to_summarize.txt'); } catch (e) { console.error(`Unexpected error saving artifact reference: ${e}`); } } // Example usage: // saveDocumentReference(context, "gs://my-bucket/docs/report.pdf"); ``` === "Go" ```go import ( "google.golang.org/adk/tool" "google.golang.org/genai" ) --8<-- "examples/go/snippets/context/main.go:artifacts_save_ref" ``` === "Java" ```java // Example: In a callback or initial tool import com.google.adk.agents.CallbackContext; import com.google.genai.types.Content; import com.google.genai.types.Part; import java.util.Optional; public void saveDocumentReference(CallbackContext context, String filePath) { // Assume file_path is something like "gs://my-bucket/docs/report.pdf" or "/local/path/to/report.pdf" try { // Create a Part containing the path/URI text Part artifactPart = Part.fromText(filePath); Optional version = context.saveArtifact("document_to_summarize.txt", artifactPart); System.out.println("Saved document reference" + filePath + " as artifact version " + version.orElse(-1)); // Store the filename in state if needed by other tools context.state().put("temp:doc_artifact_name", "document_to_summarize.txt"); } catch (Exception e) { System.out.println("Unexpected error saving artifact reference: " + e); } } // Example usage: // saveDocumentReference(context, "gs://my-bucket/docs/report.pdf") ``` 2. **Summarizer Tool:** Load the artifact to get the path/URI, read the actual document content using appropriate libraries, summarize, and return the result. === "Python" ```python # Example: In the Summarizer tool function from google.adk.tools import ToolContext from google.genai import types # Assume libraries like google.cloud.storage or built-in open are available # Assume a 'summarize_text' function exists # from my_summarizer_lib import summarize_text def summarize_document_tool(tool_context: ToolContext) -> dict: artifact_name = tool_context.state.get("temp:doc_artifact_name") if not artifact_name: return {"error": "Document artifact name not found in state."} try: # 1. Load the artifact part containing the path/URI artifact_part = tool_context.load_artifact(artifact_name) if not artifact_part or not artifact_part.text: return {"error": f"Could not load artifact or artifact has no text path: {artifact_name}"} file_path = artifact_part.text print(f"Loaded document reference: {file_path}") # 2. Read the actual document content (outside ADK context) document_content = "" if file_path.startswith("gs://"): # Example: Use GCS client library to download/read pass # Replace with actual GCS reading logic elif file_path.startswith("/"): # Example: Use local file system with open(file_path, 'r', encoding='utf-8') as f: document_content = f.read() else: return {"error": f"Unsupported file path scheme: {file_path}"} # 3. Summarize the content if not document_content: return {"error": "Failed to read document content."} # summary = summarize_text(document_content) # Call your summarization logic summary = f"Summary of content from {file_path}" # Placeholder return {"summary": summary} except ValueError as e: return {"error": f"Artifact service error: {e}"} except FileNotFoundError: return {"error": f"Local file not found: {file_path}"} ``` === "TypeScript" ```typescript // Pseudocode: In the Summarizer tool function import { Context } from '@google/adk'; async function summarizeDocumentTool(context: Context): Promise> { const artifactName = context.state.get('temp:doc_artifact_name') as string; if (!artifactName) { return { error: 'Document artifact name not found in state.' }; } try { // 1. Load the artifact part containing the path/URI const artifactPart = await context.loadArtifact(artifactName); if (!artifactPart?.text) { return { error: `Could not load artifact or artifact has no text path: ${artifactName}` }; } const filePath = artifactPart.text; console.log(`Loaded document reference: ${filePath}`); // 2. Read the actual document content (outside ADK context) let documentContent = ''; if (filePath.startsWith('gs://')) { // Example: Use GCS client library to download/read // const storage = new Storage(); // const bucket = storage.bucket('my-bucket'); // const file = bucket.file(filePath.replace('gs://my-bucket/', '')); // const [contents] = await file.download(); // documentContent = contents.toString(); } else if (filePath.startsWith('/')) { // Example: Use local file system // import { readFile } from 'fs/promises'; // documentContent = await readFile(filePath, 'utf8'); } else { return { error: `Unsupported file path scheme: ${filePath}` }; } // 3. Summarize the content if (!documentContent) { return { error: 'Failed to read document content.' }; } // const summary = summarizeText(documentContent); // Call your summarization logic const summary = `Summary of content from ${filePath}`; // Placeholder return { summary }; } catch (e) { return { error: `Error processing artifact: ${e}` }; } } ``` === "Go" ```go import "google.golang.org/adk/tool" --8<-- "examples/go/snippets/context/main.go:artifacts_summarize" ``` === "Java" ```java // Example: In the Summarizer tool function import com.google.adk.tools.ToolContext; import com.google.genai.types.Content; import com.google.genai.types.Part; import java.util.Map; import java.util.Optional; import java.io.FileNotFoundException; public Map summarizeDocumentTool(ToolContext toolContext) { String artifactName = (String) toolContext.state().get("temp:doc_artifact_name"); if (artifactName == null || artifactName.isEmpty()) { return Map.of("error", "Document artifact name not found in state."); } try { // 1. Load the artifact part containing the path/URI Optional artifactPart = toolContext.loadArtifact(artifactName); if (!artifactPart.isPresent() || !artifactPart.get().text().isPresent() || artifactPart.get().text().get().isEmpty()) { return Map.of("error", "Could not load artifact or artifact has no text path: " + artifactName); } String filePath = artifactPart.get().text().get(); System.out.println("Loaded document reference: " + filePath); // 2. Read the actual document content (outside ADK context) String documentContent = ""; if (filePath.startsWith("gs://")) { // Example: Use GCS client library to download/read into documentContent // Replace with actual GCS reading logic } else if (filePath.startsWith("/")) { // Example: Use local file system to download/read into documentContent } else { return Map.of("error", "Unsupported file path scheme: " + filePath); } // 3. Summarize the content if (documentContent.isEmpty()) { return Map.of("error", "Failed to read document content."); } // summary = summarizeText(documentContent) // Call your summarization logic String summary = "Summary of content from " + filePath; // Placeholder return Map.of("summary", summary); } catch (IllegalArgumentException e) { return Map.of("error", "Artifact service error " + e); } catch (Exception e) { return Map.of("error", "Error reading document " + e); } } ``` * **Listing Artifacts:** Discover what files are available. === "Python" ```python # Example: In a tool function from google.adk.tools import ToolContext def check_available_docs(tool_context: ToolContext) -> dict: try: artifact_keys = tool_context.list_artifacts() print(f"Available artifacts: {artifact_keys}") return {"available_docs": artifact_keys} except ValueError as e: return {"error": f"Artifact service error: {e}"} ``` === "TypeScript" ```typescript // Pseudocode: In a tool function import { Context } from '@google/adk'; async function checkAvailableDocs(context: Context): Promise> { try { const artifactKeys = await context.listArtifacts(); console.log(`Available artifacts: ${artifactKeys}`); return { available_docs: artifactKeys }; } catch (e) { return { error: `Artifact service error: ${e}` }; } } ``` === "Go" ```go import "google.golang.org/adk/tool" --8<-- "examples/go/snippets/context/main.go:artifacts_list" ``` === "Java" ```java // Example: In a tool function import com.google.adk.tools.ToolContext; import io.reactivex.rxjava3.core.Single; import java.util.List; import java.util.Map; public Map checkAvailableDocs(ToolContext toolContext) { try { Single> artifactKeys = toolContext.listArtifacts(); System.out.println("Available artifacts: " + artifactKeys.blockingGet().toString()); return Map.of("availableDocs", artifactKeys.blockingGet()); } catch (IllegalArgumentException e) { return Map.of("error", "Artifact service error: " + e); } } ``` ### Handling Tool Authentication
Supported in ADKPython v0.1.0TypeScript v0.2.0Java v0.2.0
Securely manage API keys or other credentials needed by tools. === "Python" ```python # Example: Tool requiring auth from google.adk.tools import ToolContext from google.adk.auth import AuthConfig # Assume appropriate AuthConfig is defined # Define your required auth configuration (e.g., OAuth, API Key) MY_API_AUTH_CONFIG = AuthConfig(...) AUTH_STATE_KEY = "user:my_api_credential" # Key to store retrieved credential def call_secure_api(tool_context: ToolContext, request_data: str) -> dict: # 1. Check if credential already exists in state credential = tool_context.state.get(AUTH_STATE_KEY) if not credential: # 2. If not, request it print("Credential not found, requesting...") try: tool_context.request_credential(MY_API_AUTH_CONFIG) # The framework handles yielding the event. The tool execution stops here for this turn. return {"status": "Authentication required. Please provide credentials."} except ValueError as e: return {"error": f"Auth error: {e}"} # e.g., function_call_id missing except Exception as e: return {"error": f"Failed to request credential: {e}"} # 3. If credential exists (might be from a previous turn after request) # or if this is a subsequent call after auth flow completed externally try: # Optionally, re-validate/retrieve if needed, or use directly # This might retrieve the credential if the external flow just completed auth_credential_obj = tool_context.get_auth_response(MY_API_AUTH_CONFIG) api_key = auth_credential_obj.api_key # Or access_token, etc. # Store it back in state for future calls within the session tool_context.state[AUTH_STATE_KEY] = auth_credential_obj.model_dump() # Persist retrieved credential print(f"Using retrieved credential to call API with data: {request_data}") # ... Make the actual API call using api_key ... api_result = f"API result for {request_data}" return {"result": api_result} except Exception as e: # Handle errors retrieving/using the credential print(f"Error using credential: {e}") # Maybe clear the state key if credential is invalid? # tool_context.state[AUTH_STATE_KEY] = None return {"error": "Failed to use credential"} ``` === "TypeScript" ```typescript // Pseudocode: Tool requiring auth import { Context } from '@google/adk'; // AuthConfig from ADK or custom // Define a local AuthConfig interface as it's not publicly exported by ADK interface AuthConfig { credentialKey: string; authScheme: { type: string }; // Minimal representation for the example // Add other properties if they become relevant for the example } // Define your required auth configuration (e.g., OAuth, API Key) const MY_API_AUTH_CONFIG: AuthConfig = { credentialKey: 'my-api-key', // Example key authScheme: { type: 'api-key' }, // Example scheme type }; const AUTH_STATE_KEY = 'user:my_api_credential'; // Key to store retrieved credential async function callSecureApi(context: Context, requestData: string): Promise> { // 1. Check if credential already exists in state const credential = context.state.get(AUTH_STATE_KEY); if (!credential) { // 2. If not, request it console.log('Credential not found, requesting...'); try { context.requestCredential(MY_API_AUTH_CONFIG); // The framework handles yielding the event. The tool execution stops here for this turn. return { status: 'Authentication required. Please provide credentials.' }; } catch (e) { return { error: `Auth or credential request error: ${e}` }; } } // 3. If credential exists (might be from a previous turn after request) // or if this is a subsequent call after auth flow completed externally try { // Optionally, re-validate/retrieve if needed, or use directly // This might retrieve the credential if the external flow just completed const authCredentialObj = context.getAuthResponse(MY_API_AUTH_CONFIG); const apiKey = authCredentialObj?.apiKey; // Or accessToken, etc. // Store it back in state for future calls within the session // Note: In strict TS, might need to cast or serialize authCredentialObj context.state.set(AUTH_STATE_KEY, JSON.stringify(authCredentialObj)); console.log(`Using retrieved credential to call API with data: ${requestData}`); // ... Make the actual API call using apiKey ... const apiResult = `API result for ${requestData}`; return { result: apiResult }; } catch (e) { // Handle errors retrieving/using the credential console.error(`Error using credential: ${e}`); // Maybe clear the state key if credential is invalid? // toolContext.state.set(AUTH_STATE_KEY, null); return { error: 'Failed to use credential' }; } } ``` === "Java" ```java // Example: Tool requiring auth import com.google.adk.tools.ToolContext; import java.util.Map; // Note: AuthConfig, requestCredential, and getAuthResponse are not yet // fully implemented in the Java ADK public API. // This example relies on external auth population into the session state. public class SecureApiTool { private static final String AUTH_STATE_KEY = "user:my_api_credential"; public Map callSecureApi(ToolContext context, String requestData) { // 1. Check if credential already exists in state Object credential = context.state().get(AUTH_STATE_KEY); if (credential == null) { // 2. If not, request it System.out.println("Credential not found, requesting..."); try { // context.requestCredential(MY_API_AUTH_CONFIG); // Not yet implemented in Java ADK // The framework handles yielding the event. The tool execution stops here for this turn. return Map.of("status", "Authentication required. Please provide credentials."); } catch (Exception e) { return Map.of("error", "Auth or credential request error: " + e.getMessage()); } } // 3. If credential exists (might be from a previous turn after request) // or if this is a subsequent call after auth flow completed externally try { // Optionally, re-validate/retrieve if needed, or use directly // String apiKey = context.getAuthResponse(MY_API_AUTH_CONFIG).getApiKey(); String apiKey = credential.toString(); // Simplified for example // Store it back in state for future calls within the session context.state().put(AUTH_STATE_KEY, apiKey); System.out.println("Using retrieved credential to call API with data: " + requestData); // ... Make the actual API call using apiKey ... String apiResult = "API result for " + requestData; return Map.of("result", apiResult); } catch (Exception e) { // Handle errors retrieving/using the credential System.err.println("Error using credential: " + e.getMessage()); return Map.of("error", "Failed to use credential"); } } } ``` *Remember: `request_credential` pauses the tool and signals the need for authentication. The user/system provides credentials, and on a subsequent call, `get_auth_response` (or checking state again) allows the tool to proceed.* The `tool_context.function_call_id` is used implicitly by the framework to link the request and response. ### Leveraging Memory
Supported in ADKPython v0.1.0TypeScript v0.2.0Java v0.2.0
Access relevant information from the past or external sources. === "Python" ```python # Example: Tool using memory search from google.adk.tools import ToolContext def find_related_info(tool_context: ToolContext, topic: str) -> dict: try: search_results = tool_context.search_memory(f"Information about {topic}") if search_results.results: print(f"Found {len(search_results.results)} memory results for '{topic}'") # Process search_results.results (which are SearchMemoryResponseEntry) top_result_text = search_results.results[0].text return {"memory_snippet": top_result_text} else: return {"message": "No relevant memories found."} except ValueError as e: return {"error": f"Memory service error: {e}"} # e.g., Service not configured except Exception as e: return {"error": f"Unexpected error searching memory: {e}"} ``` === "TypeScript" ```typescript // Pseudocode: Tool using memory search import { Context } from '@google/adk'; async function findRelatedInfo(context: Context, topic: string): Promise> { try { const searchResults = await context.searchMemory(`Information about ${topic}`); if (searchResults.results?.length) { console.log(`Found ${searchResults.results.length} memory results for '${topic}'`); // Process searchResults.results const topResultText = searchResults.results[0].text; return { memory_snippet: topResultText }; } else { return { message: 'No relevant memories found.' }; } } catch (e) { return { error: `Memory service error: ${e}` }; // e.g., Service not configured } } ``` === "Java" ```java // Example: Tool using memory search import com.google.adk.tools.ToolContext; import com.google.adk.memory.SearchMemoryResponse; import io.reactivex.rxjava3.core.Single; import java.util.Map; public class MemorySearchTool { public Single> findRelatedInfo(ToolContext context, String topic) { return context.searchMemory("Information about " + topic) .map(searchResults -> { if (searchResults != null && searchResults.results() != null && !searchResults.results().isEmpty()) { System.out.println("Found " + searchResults.results().size() + " memory results for '" + topic + "'"); // Process searchResults.results String topResultText = searchResults.results().get(0).text(); return Map.of("memory_snippet", topResultText); } else { return Map.of("message", "No relevant memories found."); } }) .onErrorReturnItem(Map.of("error", "Memory service error")); } } ``` ### Advanced: Direct `InvocationContext` Usage
Supported in ADKPython v0.1.0TypeScript v0.2.0Java v0.2.0
While most interactions happen via `CallbackContext` or `ToolContext`, sometimes the agent's core logic (`_run_async_impl`/`_run_live_impl`) needs direct access. === "Python" ```python # Example: Inside agent's _run_async_impl from google.adk.agents import BaseAgent from google.adk.agents.invocation_context import InvocationContext from google.adk.events import Event from typing import AsyncGenerator class MyControllingAgent(BaseAgent): async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]: # Example: Check if a specific service is available if not ctx.memory_service: print("Memory service is not available for this invocation.") # Potentially change agent behavior # Example: Early termination based on some condition if ctx.session.state.get("critical_error_flag"): print("Critical error detected, ending invocation.") ctx.end_invocation = True # Signal framework to stop processing yield Event(author=self.name, invocation_id=ctx.invocation_id, content="Stopping due to critical error.") return # Stop this agent's execution # ... Normal agent processing ... yield # ... event ... ``` === "TypeScript" ```typescript // Pseudocode: Inside agent's runAsyncImpl import { BaseAgent, InvocationContext } from '@google/adk'; import type { Event } from '@google/adk'; class MyControllingAgent extends BaseAgent { async *runAsyncImpl(ctx: InvocationContext): AsyncGenerator { // Example: Check if a specific service is available if (!ctx.memoryService) { console.log('Memory service is not available for this invocation.'); // Potentially change agent behavior } // Example: Early termination based on some condition // Direct access to state via ctx.session.state or through ctx.session.state property if wrapped if ((ctx.session.state as { 'critical_error_flag': boolean })['critical_error_flag']) { console.log('Critical error detected, ending invocation.'); ctx.endInvocation = true; // Signal framework to stop processing yield { author: this.name, invocationId: ctx.invocationId, content: { parts: [{ text: 'Stopping due to critical error.' }] } } as Event; return; // Stop this agent's execution } // ... Normal agent processing ... yield; // ... event ... } } ``` === "Java" ```java // Example: Inside agent's runAsyncImpl import com.google.adk.agents.BaseAgent; import com.google.adk.agents.InvocationContext; import com.google.adk.events.Event; import com.google.genai.types.Content; import com.google.genai.types.Part; import io.reactivex.rxjava3.core.Flowable; import java.util.List; public class MyControllingAgent extends BaseAgent { @Override protected Flowable runAsyncImpl(InvocationContext ctx) { // Example: Check if a specific service is available if (ctx.memoryService() == null) { System.out.println("Memory service is not available for this invocation."); // Potentially change agent behavior } // Example: Early termination based on some condition Boolean criticalError = (Boolean) ctx.session().state().getOrDefault("critical_error_flag", false); if (criticalError != null && criticalError) { System.out.println("Critical error detected, ending invocation."); ctx.setEndInvocation(true); // Signal framework to stop processing Event errorEvent = Event.builder() .author(name()) .invocationId(ctx.invocationId()) .content(Content.builder().parts(List.of(Part.builder().text("Stopping due to critical error.").build())).build()) .build(); return Flowable.just(errorEvent); // Stop this agent's execution } // ... Normal agent processing ... // return Flowable.just(normalEvent); return Flowable.empty(); } } ``` Setting `ctx.end_invocation = True` is a way to gracefully stop the entire request-response cycle from within the agent or its callbacks/tools (via their respective context objects which also have access to modify the underlying `InvocationContext`'s flag). ## Key Takeaways & Best Practices * **Use the Right Context:** Always use the most specific context object provided (`ToolContext` in tools/tool-callbacks, `CallbackContext` in agent/model-callbacks, `ReadonlyContext` where applicable). Use the full `InvocationContext` (`ctx`) directly in `_run_async_impl` / `_run_live_impl` only when necessary. * **State for Data Flow:** `context.state` is the primary way to share data, remember preferences, and manage conversational memory *within* an invocation. Use prefixes (`app:`, `user:`, `temp:`) thoughtfully when using persistent storage. * **Artifacts for Files:** Use `context.save_artifact` and `context.load_artifact` for managing file references (like paths or URIs) or larger data blobs. Store references, load content on demand. * **Tracked Changes:** Modifications to state or artifacts made via context methods are automatically linked to the current step's `EventActions` and handled by the `SessionService`. * **Start Simple:** Focus on `state` and basic artifact usage first. Explore authentication, memory, and advanced `InvocationContext` fields (like those for live streaming) as your needs become more complex. By understanding and effectively using these context objects, you can build more sophisticated, stateful, and capable agents with ADK.