Google ADK

Trace a Google ADK agent with Neatlogs in Python or Go.

Capture a Google ADK agent's runs, model calls, and tool calls. Python wraps the Runner; Go uses an explicit private-provider config and run wrapper.

Python

Prerequisites

pip install -U "neatlogs[google-adk]" google-adk

Usage

import os
import neatlogs

neatlogs.init(api_key=os.environ["NEATLOGS_API_KEY"], workflow_name="google-adk-demo")

from google.adk.runners import Runner

# `my_agent` and `session_service` are your existing ADK objects.
runner = neatlogs.wrap(Runner(agent=my_agent, app_name="my-app", session_service=session_service))

# Drive the runner as usual — its runs are now traced.

The ADK run becomes a WORKFLOW/AGENT trace root automatically, with nested AGENT, LLM, and TOOL spans.

Sessions & end-user

Say your Google ADK agent fires off a handful of runs in a single back-and-forth and you'd like them filed under one session, tied to whoever's chatting — that's a quick win. Wrap the run in identify() and ADK's spans inherit that identity through the identity processor:

# Same session_id every turn → one session; end_user_id attributes it to your user.
with neatlogs.identify(session_id=f"conv_{conversation_id}", end_user_id=user_id):
    # drive the runner as usual — its spans inherit the neatlogs session + end-user
    runner.run(...)

Not every run is a conversation. A standalone workflow — a one-off job, not a back-and-forth — is a single trace with no turns to group. You still attribute it to the customer it ran for, sourcing the id and any metadata from your own user or request object:

Warning

A standalone workflow must not carry a session_id. Omit it entirely — Neatlogs sets session_id = trace_id, so the run is its own single-turn session, fully attributed to the end-user. Reusing one session_id across unrelated runs wrongly folds them into a single multi-turn conversation.

def run_agent(request, user):
    # Standalone workflow — attribute to the end-user; do NOT pass session_id.
    with neatlogs.identify(
        end_user_id=str(user.id),
        end_user_metadata={"plan": user.plan},
    ):
        # drive the runner as usual — its spans carry the end-user
        return runner.run(request)

For the whole picture, see Sessions and End-User Identity.

Go

Go keeps Neatlogs on a private OpenTelemetry provider, so ADK's global-provider spans are not passively exported. The supported contrib/adk integration instead injects Neatlogs-owned model/tool callbacks and carries the private trace context through an explicit run wrapper.

Prerequisites

go get github.com/neatlogs/neatlogs-go
go get github.com/neatlogs/neatlogs-go/contrib/adk

Keep both modules on the same released version.

Usage

import (
    nladk "github.com/neatlogs/neatlogs-go/contrib/adk"
    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
)

config := nladk.InstrumentConfig(llmagent.Config{
    Name:  "support_agent",
    Model: model,
    Tools: tools,
})
adkAgent, err := llmagent.New(config)
if err != nil {
    log.Fatal(err)
}

// Build runner with adkAgent, then replace runner.Run with nladk.Run.
for event, err := range nladk.Run(
    ctx, runner, userID, sessionID, message, agent.RunConfig{},
) {
    // Handle the unchanged ADK event stream.
}

InstrumentConfig records model and tool spans while preserving existing callbacks. Run creates the workflow root, sets session/end-user identity, records root input/output, and supplies the private trace context. Use both; do not add another LLM span around the instrumented model.

For remote ADK agents, use A2AHTTPClient and A2AHandler for W3C context propagation. Add A2ABeforeRequest and A2AAfterRequest when the caller also needs semantic request/response capture. These helpers do not create HTTP spans.

On this page

Ask Neatlogs AI

Answers from the docs

How can I help?

Ask anything about instrumenting, tracing, or the Neatlogs dashboard.