> This page is for NeMo Relay, version dev.
> For other versions, use one of these documentation indexes:
> - Latest (v0.10.4) (default): https://docs.nvidia.com/nemo/relay/latest/llms.txt
> - dev: https://docs.nvidia.com/nemo/relay/dev/llms.txt
> - v0.11.0: https://docs.nvidia.com/nemo/relay/v0.11.0/llms.txt
> - v0.10.4: https://docs.nvidia.com/nemo/relay/v0.10.4/llms.txt
> - v0.9.4: https://docs.nvidia.com/nemo/relay/v0.9.4/llms.txt
> - v0.8.4: https://docs.nvidia.com/nemo/relay/v0.8.4/llms.txt
> - v0.7.3: https://docs.nvidia.com/nemo/relay/v0.7.3/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/nemo/relay/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/nemo/relay/_mcp/server.

# Google ADK Quick Start

> Record one Google ADK agent run with NeMo Relay.

## Goal

Run a Google ADK agent and save its activity to a local file. NeMo Relay
records the agent run, model calls, and tool calls. This example checks that
the agent calls a tool and that Relay saves the events.

The file uses Agent Trajectory Observability Format (ATOF), Relay's event format.

## Prerequisites

You need:

* Python 3.11 or newer
* `uv`
* A [Gemini API key](https://aistudio.google.com/apikey)

## Install

Create a project. Install NeMo Relay with its Google ADK extra.
If you already have a `uv` project, run only the `uv add` command in that directory.

```bash
mkdir relay-google-adk-quickstart
cd relay-google-adk-quickstart
uv init
uv add "nemo-relay[google-adk]==0.11.0"
```

Set your Gemini API key in the current shell.

#### Unix shell

```bash
export GOOGLE_API_KEY="your-api-key"
```

#### Windows PowerShell

```powershell
$env:GOOGLE_API_KEY = "your-api-key"
```

> **Note**
>
> This example uses [`gemini-3.5-flash`](https://ai.google.dev/gemini-api/docs/models)
> and the Gemini API. To use another supported model, change the agent's `model`
> argument and set that provider's credentials. For provider setup, refer to the
> [Google ADK Integration Guide](/nemo/relay/dev/supported-integrations/google-adk#setup).

## Configure

Create a file named `plugins.toml` in your project directory. This configuration
saves Relay events to `logs/events.jsonl`.

```toml title={"plugins.toml"}
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

version = 1

[policy]
unknown_component = "error"
unknown_field = "error"
unsupported_value = "error"

[[components]]
kind = "observability"
enabled = true

[components.config]
version = 4
enable_full_payloads = false

[components.config.atof]
enabled = true

[[components.config.atof.sinks]]
type = "file"
output_directory = "logs"
filename = "events.jsonl"
mode = "overwrite"

```

> **Warning**
>
> ATOF output can contain sensitive prompts, responses, tool arguments, and tool
> results. Protect `logs/events.jsonl`, even with `enable_full_payloads = false`.

## Run

The example gives an agent one tool, `emit_marker`. The tool returns a fixed
message so you can check that it ran. The agent then returns a final answer.
Relay records the run and saves the events.

The tool and the `check_*` functions help verify this example. You don't need
them in your own app. For an example with a weather tool, refer to the
[Google ADK Integration Guide](/nemo/relay/dev/supported-integrations/google-adk#usage-example).

Save the following code as `quickstart.py` beside `plugins.toml`.

```python title={"quickstart.py"}
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

import asyncio
import json
from collections.abc import Sequence
from pathlib import Path

from google.adk.agents import Context, LlmAgent
from google.adk.apps import App
from google.adk.events import Event
from google.adk.models.llm_request import LlmRequest
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types

import nemo_relay
from nemo_relay.integrations.google_adk import NemoRelayPlugin


def emit_marker() -> str:
    """Return quick-start validation marker."""
    return "RELAY_QUICKSTART_TOOL_OK"


def require_marker_tool(callback_context: Context, llm_request: LlmRequest) -> None:
    """Force tool call until tool result exists, then disable tools."""
    tool_used = any(
        part.function_response is not None for content in llm_request.contents for part in content.parts or []
    )
    mode = types.FunctionCallingConfigMode.NONE if tool_used else types.FunctionCallingConfigMode.ANY
    llm_request.config.tool_config = types.ToolConfig(function_calling_config=types.FunctionCallingConfig(mode=mode))


root_agent = LlmAgent(
    name="marker_agent",
    model="gemini-3.5-flash",
    instruction="Call emit_marker exactly once. Keep the final answer brief.",
    tools=[emit_marker],
    before_model_callback=require_marker_tool,
)
app = App(name="relay_quickstart", root_agent=root_agent, plugins=[NemoRelayPlugin()])


def check_tool_result(events: Sequence[Event]) -> None:
    """Raise unless run produced exactly one marker tool result."""
    results = [
        part.function_response.response
        for event in events
        if event.content is not None
        for part in event.content.parts or []
        if part.function_response is not None
    ]
    if results != [{"result": "RELAY_QUICKSTART_TOOL_OK"}]:
        raise RuntimeError(f"Expected one marker tool result, got {results}")


def check_final_response(events: Sequence[Event]) -> None:
    """Raise unless last event is final model response containing response marker."""
    final = events[-1]
    if not final.is_final_response() or final.content is None:
        raise RuntimeError("Expected final model response as last event")
    text = "".join(part.text for part in final.content.parts or [] if part.text is not None and not part.thought)
    if "RELAY_QUICKSTART_RESPONSE_OK" not in text:
        raise RuntimeError(f"Final response lacks RELAY_QUICKSTART_RESPONSE_OK: {text!r}")


def check_atof_records(path: Path) -> None:
    """Raise unless ATOF file pairs start and end records for `adk-request`, `emit_marker`, and an LLM call."""
    records: list[dict[str, object]] = [json.loads(line) for line in path.read_text().splitlines()]
    if not records or any(
        record.get("atof_version") != "0.1" or record.get("kind") not in {"scope", "mark"} for record in records
    ):
        raise RuntimeError("Expected nonempty ATOF 0.1 event file")
    scopes = [record for record in records if record["kind"] == "scope"]
    starts = {record["uuid"]: record for record in scopes if record["scope_category"] == "start"}
    paired = {
        (record["category"], record["name"])
        for record in scopes
        if record["scope_category"] == "end"
        and record["uuid"] in starts
        and all(record[key] == starts[record["uuid"]][key] for key in ("name", "category", "parent_uuid"))
    }
    categories = {category for category, _ in paired}
    if not {("agent", "adk-request"), ("tool", "emit_marker")} <= paired or "llm" not in categories:
        raise RuntimeError(f"Expected paired adk-request, emit_marker, and LLM scopes, got {paired}")


async def main() -> None:
    """Run instrumented agent, then check tool result, final response, and ATOF records."""
    async with nemo_relay.plugin.activate({}, "plugins.toml"):
        try:
            async with Runner(app=app, session_service=InMemorySessionService()) as runner:
                session = await runner.session_service.create_session(app_name=app.name, user_id="quickstart-user")
                message = types.UserContent(
                    "Use one available tool to produce RELAY_QUICKSTART_TOOL_OK. "
                    "Then include RELAY_QUICKSTART_RESPONSE_OK in your final response."
                )
                with nemo_relay.scope.scope("adk-request", nemo_relay.ScopeType.Agent):
                    events = [
                        event
                        async for event in runner.run_async(
                            user_id=session.user_id, session_id=session.id, new_message=message
                        )
                    ]
        finally:
            await nemo_relay.subscribers.flush_async()

    check_tool_result(events)
    print("RELAY_QUICKSTART_TOOL_OK")
    check_final_response(events)
    print("RELAY_QUICKSTART_RESPONSE_OK")
    check_atof_records(Path("logs/events.jsonl"))
    print("RELAY_QUICKSTART_EVENTS_OK")


if __name__ == "__main__":
    asyncio.run(main())

```

Run the example from your project directory.

```bash
uv run python quickstart.py
```

## Verify

The program checks the tool result, the final answer, and the saved events.
It also checks that the request, tool call, and model call have matching start
and end events. A successful run prints these messages in order:

```text
RELAY_QUICKSTART_TOOL_OK
RELAY_QUICKSTART_RESPONSE_OK
RELAY_QUICKSTART_EVENTS_OK
```

> **Note**
>
> The model can use different words around `RELAY_QUICKSTART_RESPONSE_OK`.
> The check only requires that message to appear in the final answer.

### Inspect the Recorded Activity

Print the details of one completed model call from the event file.

```bash
uv run python -c "
import json
from pathlib import Path
events = [json.loads(line) for line in Path('logs/events.jsonl').read_text().splitlines()]
call = next(event for event in events if event.get('scope_category') == 'end' and event.get('category') == 'llm')
fields = ('name', 'category', 'category_profile', 'uuid', 'parent_uuid', 'timestamp', 'metadata')
print(json.dumps({key: call.get(key) for key in fields}, indent=2))
"
```

Use these fields to understand the call:

| Field                             | Meaning                                                       |
| --------------------------------- | ------------------------------------------------------------- |
| `category`                        | The kind of activity, such as a model call                    |
| `name`                            | The name of the activity                                      |
| `uuid`                            | The ID shared by the start and end events                     |
| `parent_uuid`                     | The ID of the activity that contains this call                |
| `timestamp`                       | The time of the event                                         |
| `category_profile` and `metadata` | Details such as the model name or call status, when available |

For example, a model call's `parent_uuid` links it to the agent that made the call.

## For More Details

Refer to:

* [Google ADK Integration Guide](/nemo/relay/dev/supported-integrations/google-adk)
* [ATOF exporter](/nemo/relay/dev/configure-plugins/observability/atof)