From 281f899ff7aa94e419347f24baeb98ded151d21e Mon Sep 17 00:00:00 2001 From: George Weale Date: Tue, 28 Jul 2026 17:09:20 +0000 Subject: [PATCH 1/2] docs(sessions): fix async examples, imports and state claims --- docs/artifacts/index.md | 11 +++++++---- docs/events/index.md | 10 +++++----- docs/sessions/memory.md | 5 +++-- docs/sessions/session/index.md | 9 +++++---- docs/sessions/state.md | 7 ++++--- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/docs/artifacts/index.md b/docs/artifacts/index.md index 3d9e3413cf..70831c88e1 100644 --- a/docs/artifacts/index.md +++ b/docs/artifacts/index.md @@ -160,6 +160,7 @@ Understanding artifacts involves grasping a few key components: the service that * `List Artifact keys`: Lists the unique filenames of artifacts within a given scope. * `Delete Artifact`: Removes an artifact (and potentially all its versions, depending on implementation). * `List versions`: Lists all available version numbers for a specific artifact filename. + * `List artifact versions` and `Get artifact version` (Python): Return `ArtifactVersion` metadata (version number, canonical URI, MIME type, creation time, custom metadata) instead of the artifact payload. * **Configuration:** You provide an instance of an artifact service (e.g., `InMemoryArtifactService`, `GcsArtifactService`) when initializing the `Runner`. The `Runner` then makes this service available to agents and tools via the `InvocationContext`. @@ -351,6 +352,8 @@ Understanding artifacts involves grasping a few key components: the service that * **User Scope (`"user:"` prefix):** If you prefix the filename with `"user:"`, like `"user:profile.png"`, the artifact is associated only with the `app_name` and `user_id`. It can be accessed or updated from *any* session belonging to that user within the app. +* **Listing behavior:** In Python, listing artifacts from within a session returns the session-scoped filenames *and* that user's user-scoped filenames, with the `"user:"` prefix retained (e.g. `["summary.txt", "user:settings.json"]`). + === "Python" @@ -429,7 +432,7 @@ These core concepts work together to provide a flexible system for managing bina The primary way you interact with artifacts within your agent's logic (specifically within callbacks or tools) is through methods provided by the `CallbackContext` and `ToolContext` objects. These methods abstract away the underlying storage details managed by the `ArtifactService`. -*(Note: In TypeScript, `CallbackContext` and `ToolContext` are unified into a single `Context` type.)* +*(Note: In Python and TypeScript, `CallbackContext` and `ToolContext` are unified into a single `Context` type. In Python both names remain as aliases of `Context` and can be used interchangeably.)* ### Prerequisite: Configuring the `ArtifactService` @@ -553,7 +556,7 @@ Before you can use any artifact methods via the context objects, you **must** pr ### Accessing Methods -The artifact interaction methods are available directly on instances of `CallbackContext` (passed to agent and model callbacks) and `ToolContext` (passed to tool callbacks) in Python, Go, and Java and available on the unified `Context` in TypeScript. +The artifact interaction methods are available directly on instances of `CallbackContext` (passed to agent and model callbacks) and `ToolContext` (passed to tool callbacks) in Go and Java, and available on the unified `Context` in Python and TypeScript (in Python, `CallbackContext` and `ToolContext` are aliases of `Context`). #### Saving Artifacts @@ -933,7 +936,7 @@ artifact in a later turn. ```python from google.adk.tools.tool_context import ToolContext - def list_user_files_py(tool_context: ToolContext) -> str: + async def list_user_files_py(tool_context: ToolContext) -> str: """Tool to list available artifacts for the user.""" try: available_files = await tool_context.list_artifacts() @@ -1090,7 +1093,7 @@ ADK provides concrete implementations of the `BaseArtifactService` interface, of ### InMemoryArtifactService * **Storage Mechanism:** - * Python: Uses a Python dictionary (`self.artifacts`) held in the application's memory. The dictionary keys represent the artifact path, and the values are lists of `types.Part`, where each list element is a version. + * Python: Uses a Python dictionary (`self.artifacts`) held in the application's memory. The dictionary keys represent the artifact path, and the values are lists of entries, where each list element is a version holding the `types.Part` payload plus its `ArtifactVersion` metadata. * Java: Uses nested `HashMap` instances (`private final Map>>>> artifacts;`) held in memory. The keys at each level are `appName`, `userId`, `sessionId`, and `filename` respectively. The innermost `List` stores the versions of the artifact, where the list index corresponds to the version number. * **Key Features:** * **Simplicity:** Requires no external setup or dependencies beyond the core ADK library. diff --git a/docs/events/index.md b/docs/events/index.md index bba8ac3ea9..965f45d3a3 100644 --- a/docs/events/index.md +++ b/docs/events/index.md @@ -157,7 +157,7 @@ In essence, the entire process, from a user's query to the agent's final answer, As a developer, you'll primarily interact with the stream of events yielded by the `Runner`. Here's how to understand and extract information from them: !!! Note - The specific parameters or method names for the primitives may vary slightly by SDK language (e.g., `event.content()` in Python, `event.content().get().parts()` in Java). Refer to the language-specific API documentation for details. + The specific parameters or method names for the primitives may vary slightly by SDK language (e.g., the `event.content` attribute in Python, `event.content().get().parts()` in Java). Refer to the language-specific API documentation for details. ### Identifying Event Origin and Type @@ -733,9 +733,9 @@ The `event.actions` object signals changes that occurred or should occur. Always Use the built-in helper method `event.is_final_response()` to identify events suitable for display as the agent's complete output for a turn. -* **Purpose:** Filters out intermediate steps (like tool calls, partial streaming text, internal state updates) from the final user-facing message(s). +* **Purpose:** Filters out intermediate steps (like tool calls and partial streaming text) from the final user-facing message(s). * **When `True`?** - 1. The event contains a tool result (`function_response`) and `skip_summarization` is `True`. + 1. The `skip_summarization` action is `True`. In Python this flag alone is enough; the event does not need to carry a tool result (`function_response`). 2. The event contains a tool call (`function_call`) for a tool marked as `is_long_running=True`. In Java, check if the `longRunningToolIds` list is empty: * `event.longRunningToolIds().isPresent() && !event.longRunningToolIds().get().isEmpty()` is `true`. 3. OR, **all** of the following are met: @@ -1018,7 +1018,7 @@ Here are concise examples of typical events you might see in the stream: // actions might have skip_summarization=True } ``` -* **State/Artifact Update Only:** (`is_final_response() == False`) +* **State/Artifact Update Only:** (`is_final_response() == True`: no function call or function response, and not partial) ```json { "author": "InternalUpdater", @@ -1039,7 +1039,7 @@ Here are concise examples of typical events you might see in the stream: "actions": {"transfer_to_agent": "BillingAgent"} // Added by framework } ``` -* **Loop Escalation Signal:** (`is_final_response() == False`) +* **Loop Escalation Signal:** (`is_final_response() == True`: the `escalate` action does not affect the check) ```json { "author": "CheckerAgent", diff --git a/docs/sessions/memory.md b/docs/sessions/memory.md index 14e68bf6a3..375ff0fae8 100644 --- a/docs/sessions/memory.md +++ b/docs/sessions/memory.md @@ -52,8 +52,9 @@ to decide which is the best fit for your agent. | **Dependencies** | None. | Google Cloud Project, Agent Platform API | Google Cloud Project, Knowledge Engine, the Agent Platform SDK (optional install). | | **When to use it** | When you want to search across multiple sessions’ chat histories for prototyping. | When you want your agent to remember and learn from past interactions. | When you already have RAG infrastructure or want to retrieve over raw conversation transcripts. | -`VertexAiRagMemoryService` is only exported from `google.adk.memory` when the -Agent Platform SDK is installed. Memory Bank and RAG-backed memory are +You can always import `VertexAiRagMemoryService` from `google.adk.memory`, but +constructing it raises `ImportError` unless the Agent Platform SDK is installed +(`pip install google-adk[gcp]`). Memory Bank and RAG-backed memory are documented in [Memory Bank](#memory-bank) and [RAG Memory](#rag-memory) below. diff --git a/docs/sessions/session/index.md b/docs/sessions/session/index.md index f082d9f204..af9dbbeb9f 100644 --- a/docs/sessions/session/index.md +++ b/docs/sessions/session/index.md @@ -247,7 +247,7 @@ the storage backend that best suits your needs: * **Persistence:** Yes. Data is managed reliably and scalably via [Agent Runtime](/deploy/agent-runtime/). * **Requires:** - * A Google Cloud project (`pip install vertexai`) + * A Google Cloud project (`pip install google-adk[gcp]`) * A Google Cloud storage bucket that can be configured by this [step](https://cloud.google.com/vertex-ai/docs/pipelines/configure-project#storage). * An Agent Runtime resource name/ID that can setup following this @@ -261,7 +261,7 @@ the storage backend that best suits your needs: === "Python" ```py - # Requires: pip install google-adk[vertexai] + # Requires: pip install google-adk[gcp] # Plus GCP setup and authentication from google.adk.sessions import VertexAiSessionService @@ -272,7 +272,7 @@ the storage backend that best suits your needs: session_service = VertexAiSessionService(project=PROJECT_ID, location=LOCATION) # Use REASONING_ENGINE_APP_NAME when calling service methods, e.g.: - # session_service = await session_service.create_session(app_name=REASONING_ENGINE_APP_NAME, ...) + # session = await session_service.create_session(app_name=REASONING_ENGINE_APP_NAME, ...) ``` === "Go" @@ -331,7 +331,8 @@ For more information on connecting to Google Cloud from ADK agents, see * **How it works:** Connects to a relational database (e.g., PostgreSQL, MySQL, SQLite) to store session data persistently in tables. * **Persistence:** Yes. Data survives application restarts. -* **Requires:** A configured database. +* **Requires:** A configured database and the `db` extra + (`pip install google-adk[db]`). * **Best for:** Applications needing reliable, persistent storage that you manage yourself. diff --git a/docs/sessions/state.md b/docs/sessions/state.md index 3169319a28..89954ff842 100644 --- a/docs/sessions/state.md +++ b/docs/sessions/state.md @@ -54,6 +54,7 @@ Prefixes on state keys define their scope and persistence behavior, especially w * **Scope:** Tied to the `user_id`, shared across *all* sessions for that user (within the same `app_name`). * **Persistence:** Persistent with `Database` or `VertexAI`. (Stored by `InMemory` but lost on restart). * **Use Cases:** User preferences (e.g., `'user:theme'`), profile details (e.g., `'user:name'`). + * **Reading without a session:** In Python, `await session_service.get_user_state(app_name=..., user_id=...)` returns the user-scoped keys with the `user:` prefix stripped, so you can read them before a session exists. * **Example:** `session.state['user:preferred_language'] = 'fr'` * **`app:` Prefix (App State):** @@ -553,7 +554,7 @@ For more complex scenarios (updating multiple keys, non-string values, specific *(Note: In TypeScript, this is done via the unified `Context` type.)* -Modifying state within agent callbacks (e.g., `on_before_agent_call`, `on_after_agent_call`) or tool functions is best done using the `state` attribute of the `CallbackContext` or `ToolContext` provided to your function. +Modifying state within agent callbacks (e.g., `before_agent_callback`, `after_agent_callback`) or tool functions is best done using the `state` attribute of the `CallbackContext` or `ToolContext` provided to your function. * `callback_context.state['my_key'] = my_value` * `tool_context.state['my_key'] = my_value` @@ -568,7 +569,7 @@ For more comprehensive details on context objects, refer to the [Context documen ```python # In an agent callback or tool function - from google.adk.agents import CallbackContext # or ToolContext + from google.adk.agents.callback_context import CallbackContext # or ToolContext def my_callback_or_tool_function(context: CallbackContext, # Or ToolContext # ... other parameters ... @@ -646,7 +647,7 @@ For more comprehensive details on context objects, refer to the [Context documen * Reads the `state_delta` from the event's `actions`. * Applies these changes to the state managed by the `SessionService`, correctly handling prefixes and persistence based on the service type. * Updates the session's `last_update_time`. -* Ensures thread-safety for concurrent updates. +* Serializes concurrent updates to the same session where the service supports it: `DatabaseSessionService` takes a per-session lock, while `InMemorySessionService` is not safe for multi-threaded use. ### ⚠️ A Warning About Direct State Modification From 58ba5f332118eb71d9e72d74d7567539a4d83730 Mon Sep 17 00:00:00 2001 From: George Weale Date: Thu, 30 Jul 2026 00:22:20 +0000 Subject: [PATCH 2/2] docs(sessions): correct event id/timestamp ownership and artifact service list --- docs/artifacts/index.md | 25 ++++++++++++++++++++++++- docs/events/index.md | 6 +++--- docs/sessions/memory.md | 2 +- docs/sessions/session/index.md | 3 ++- docs/sessions/state.md | 14 ++++++++------ 5 files changed, 38 insertions(+), 12 deletions(-) diff --git a/docs/artifacts/index.md b/docs/artifacts/index.md index 70831c88e1..1a627a3781 100644 --- a/docs/artifacts/index.md +++ b/docs/artifacts/index.md @@ -1168,6 +1168,29 @@ ADK provides concrete implementations of the `BaseArtifactService` interface, of ```kotlin --8<-- "examples/kotlin/snippets/artifacts/ArtifactExamples.kt:in_memory_service" ``` +### FileArtifactService + +* **Storage Mechanism:** Stores artifacts on the local filesystem, beneath a root directory you choose. Each version of an artifact is a directory holding the payload file plus a `metadata.json` sidecar, so the tree can be inspected with ordinary file tools. +* **Key Features:** + * **Persistence:** Artifacts survive application restarts, as long as the root directory does. + * **No external dependencies:** Needs no cloud project, credentials, or network access, only a writable directory. The root directory is created on instantiation if it does not exist. +* **Use Cases:** + * Local development and single-machine deployments that need artifacts to outlive the process. + * In Python, this is also the service behind `--artifact_service_uri file://` for `adk web` / `adk run`. +* **Instantiation:** + + === "Python" + + ```python + from google.adk.artifacts import FileArtifactService + + # Point it at the directory that should hold the artifact tree + file_service_py = FileArtifactService(root_dir="/path/to/adk-artifacts") + + # Then pass it to the Runner + # runner = Runner(..., artifact_service=file_service_py) + ``` + ### GcsArtifactService @@ -1176,7 +1199,7 @@ ADK provides concrete implementations of the `BaseArtifactService` interface, of * **Key Features:** * **Persistence:** Artifacts stored in GCS persist across application restarts and deployments. * **Scalability:** Leverages the scalability and durability of Google Cloud Storage. - * **Versioning:** Explicitly stores each version as a distinct GCS object. The `saveArtifact` method in `GcsArtifactService`. + * **Versioning:** Explicitly stores each version as a distinct GCS object. In Python, `save_artifact` assigns the next version number (the first version is `0`) rather than overwriting an existing object. * **Permissions Required:** The application environment needs appropriate credentials (e.g., Application Default Credentials) and IAM permissions to read from and write to the specified GCS bucket. * **Use Cases:** * Production environments requiring persistent artifact storage. diff --git a/docs/events/index.md b/docs/events/index.md index 965f45d3a3..f8c5429406 100644 --- a/docs/events/index.md +++ b/docs/events/index.md @@ -8,7 +8,7 @@ Events are the fundamental units of information flow within the Agent Developmen ## What Events Are and Why They Matter -An `Event` in ADK is an immutable record representing a specific point in the agent's execution. It captures user messages, agent replies, requests to use tools (function calls), tool results, state changes, control signals, and errors. +An `Event` in ADK is a record representing a specific point in the agent's execution. It captures user messages, agent replies, requests to use tools (function calls), tool results, state changes, control signals, and errors. === "Python" Technically, it's an instance of the `google.adk.events.Event` class, which builds upon the basic `LlmResponse` structure by adding essential ADK-specific metadata and an `actions` payload. @@ -736,7 +736,7 @@ Use the built-in helper method `event.is_final_response()` to identify events su * **Purpose:** Filters out intermediate steps (like tool calls and partial streaming text) from the final user-facing message(s). * **When `True`?** 1. The `skip_summarization` action is `True`. In Python this flag alone is enough; the event does not need to carry a tool result (`function_response`). - 2. The event contains a tool call (`function_call`) for a tool marked as `is_long_running=True`. In Java, check if the `longRunningToolIds` list is empty: + 2. The event's `long_running_tool_ids` is non-empty, meaning a tool marked as `is_long_running=True` was called. In Python this list alone is enough; the event does not need to carry the tool call (`function_call`) itself. In Java, check if the `longRunningToolIds` list is empty: * `event.longRunningToolIds().isPresent() && !event.longRunningToolIds().get().isEmpty()` is `true`. 3. OR, **all** of the following are met: * No function calls (`get_function_calls()` is empty). @@ -955,7 +955,7 @@ Events are created at different points and processed systematically by the frame 2. **Runner Receives:** The main `Runner` executing the agent receives the event. 3. **SessionService Processing:** The `Runner` sends the event to the configured `SessionService`. This is a critical step: * **Applies Deltas:** The service merges `event.actions.state_delta` into `session.state` and updates internal records based on `event.actions.artifact_delta`. (Note: The actual artifact *saving* usually happened earlier when `context.save_artifact` was called). - * **Finalizes Metadata:** Assigns a unique `event.id` if not present, may update `event.timestamp`. + * **Event Metadata:** In Python, `event.id` and `event.timestamp` are already populated when the `Event` object is constructed (a random `id` if none was supplied, and the current time), so the service does not assign them; it records the event as it received it. * **Persists to History:** Appends the processed event to the `session.events` list. 4. **External Yield:** The `Runner` yields (Python) or returns/emits (Java) the processed event outwards to the calling application (e.g., the code that invoked `runner.run_async`). diff --git a/docs/sessions/memory.md b/docs/sessions/memory.md index 375ff0fae8..d89a700f66 100644 --- a/docs/sessions/memory.md +++ b/docs/sessions/memory.md @@ -612,7 +612,7 @@ The memory workflow includes the following steps: 6. **Results Returned:** The `MemoryService` searches its store, using keyword matching or semantic search, and returns matching snippets as a `SearchMemoryResponse` containing a list of `MemoryEntry` objects, each - holding `content`, and all optional: `author`, `timestamp`, and + holding `content`, and all optional: `id`, `author`, `timestamp`, and `custom_metadata`. 7. **Agent Uses Results:** The tool returns these results to the agent, usually as part of the context or function response. The agent can then use this diff --git a/docs/sessions/session/index.md b/docs/sessions/session/index.md index af9dbbeb9f..ff829a5731 100644 --- a/docs/sessions/session/index.md +++ b/docs/sessions/session/index.md @@ -247,7 +247,8 @@ the storage backend that best suits your needs: * **Persistence:** Yes. Data is managed reliably and scalably via [Agent Runtime](/deploy/agent-runtime/). * **Requires:** - * A Google Cloud project (`pip install google-adk[gcp]`) + * A Google Cloud project. + * The `gcp` extra (`pip install google-adk[gcp]`). * A Google Cloud storage bucket that can be configured by this [step](https://cloud.google.com/vertex-ai/docs/pipelines/configure-project#storage). * An Agent Runtime resource name/ID that can setup following this diff --git a/docs/sessions/state.md b/docs/sessions/state.md index 89954ff842..65754c2758 100644 --- a/docs/sessions/state.md +++ b/docs/sessions/state.md @@ -54,7 +54,7 @@ Prefixes on state keys define their scope and persistence behavior, especially w * **Scope:** Tied to the `user_id`, shared across *all* sessions for that user (within the same `app_name`). * **Persistence:** Persistent with `Database` or `VertexAI`. (Stored by `InMemory` but lost on restart). * **Use Cases:** User preferences (e.g., `'user:theme'`), profile details (e.g., `'user:name'`). - * **Reading without a session:** In Python, `await session_service.get_user_state(app_name=..., user_id=...)` returns the user-scoped keys with the `user:` prefix stripped, so you can read them before a session exists. + * **Reading without a session:** In Python, `await session_service.get_user_state(app_name=..., user_id=...)` returns the user-scoped keys with the `user:` prefix stripped, so you can read them before a session exists. `VertexAiSessionService` is the exception: it always raises `NotImplementedError`, because the Agent Runtime API does not expose user state independently of a session. There, enumerate sessions with `list_sessions` and call `get_session` on each result instead. * **Example:** `session.state['user:preferred_language'] = 'fr'` * **`app:` Prefix (App State):** @@ -341,8 +341,9 @@ This is the simplest method for saving an agent's final text response directly i print(f"Initial state: {session.state}") # --- Run the Agent --- - # Runner handles calling append_event, which uses the output_key - # to automatically create the state_delta. + # The agent uses the output_key to put its response into the event's + # state_delta; the Runner hands that event to append_event, which + # applies the delta to the session state. user_message = Content(parts=[Part(text="Hello")]) for event in runner.run(user_id=user_id, session_id=session_id, @@ -419,7 +420,7 @@ This is the simplest method for saving an agent's final text response directly i --8<-- "examples/java/snippets/src/main/java/state/GreetingAgentExample.java:full_code" ``` -Behind the scenes, the `Runner` uses the `output_key` to create the necessary `EventActions` with a `state_delta` and calls `append_event`. +Behind the scenes, the agent itself uses the `output_key` to write the response into the `state_delta` of the `EventActions` on the event it yields; the `Runner` then passes that event to the `SessionService`'s `append_event`, which applies the delta. **2\. The Standard Way: `EventActions.state_delta` (for Complex Updates)** @@ -552,7 +553,7 @@ For more complex scenarios (updating multiple keys, non-string values, specific **3. Via `CallbackContext` or `ToolContext` (Recommended for Callbacks and Tools)** -*(Note: In TypeScript, this is done via the unified `Context` type.)* +*(Note: In Python and TypeScript, `CallbackContext` and `ToolContext` are unified into a single `Context` type. In Python both names remain as aliases of `Context` and can be used interchangeably.)* Modifying state within agent callbacks (e.g., `before_agent_callback`, `after_agent_callback`) or tool functions is best done using the `state` attribute of the `CallbackContext` or `ToolContext` provided to your function. @@ -569,7 +570,8 @@ For more comprehensive details on context objects, refer to the [Context documen ```python # In an agent callback or tool function - from google.adk.agents.callback_context import CallbackContext # or ToolContext + from google.adk.agents.callback_context import CallbackContext + # or, equivalently: from google.adk.tools.tool_context import ToolContext def my_callback_or_tool_function(context: CallbackContext, # Or ToolContext # ... other parameters ...