Skip to content

Commit 454662b

Browse files
authored
Add ADK instrumentation sample (#418)
* Add ADK instrumentation sample * Cleanup files * Add missing headers * Cleanup files * Create per session ephemeral database * Split env files into two * Address feedback comments * Make create_database_tool return structured response
1 parent b56379b commit 454662b

12 files changed

Lines changed: 3197 additions & 0 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,6 @@ cython_debug/
146146
# Sphinx
147147
_build/
148148
_autosummary/
149+
150+
# Ephemeral Databases
151+
*.db
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.13

samples/adk-sql-agent/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# OpenTelemetry ADK instrumentation example
2+
3+
<!-- TODO: link to devsite doc once it is published -->
4+
5+
This sample is an ADK agent instrumented with OpenTelemetry to send traces and logs with GenAI
6+
prompts and responses, and metrics to Google Cloud Observability.
7+
8+
The Agent is a SQL expert that has full access to an ephemeral SQLite database. The database is
9+
initially empty.
10+
11+
## APIs and Permissions
12+
13+
Enable the relevant Cloud Observability APIs if they aren't already enabled.
14+
```sh
15+
gcloud services enable aiplatform.googleapis.com telemetry.googleapis.com logging.googleapis.com monitoring.googleapis.com cloudtrace.googleapis.com
16+
```
17+
18+
This sample writes to Cloud Logging, Cloud Monitoring, and Cloud Trace. Grant yourself the
19+
following roles to run the example:
20+
- `roles/logging.logWriter` – see https://cloud.google.com/logging/docs/access-control#permissions_and_roles
21+
- `roles/monitoring.metricWriter` – see https://cloud.google.com/monitoring/access-control#predefined_roles
22+
- `roles/telemetry.writer` – see https://cloud.google.com/trace/docs/iam#telemetry-roles
23+
24+
## Running the example
25+
26+
The sample can easily be run in Cloud Shell. You can also use
27+
[Application Default Credentials][ADC] locally. Clone and set environment variables:
28+
```sh
29+
git clone https://github.com/GoogleCloudPlatform/opentelemetry-operations-python.git
30+
cd opentelemetry-operations-python/samples/adk-sql-agent
31+
```
32+
33+
Configure the environment and run the sample:
34+
```sh
35+
python -m venv venv/
36+
source venv/bin/activate
37+
pip install -r requirements.txt
38+
env $(cat opentelemetry.env | xargs) python main.py
39+
```
40+
41+
Alternatively if you have [`uv`](https://docs.astral.sh/uv/) installed:
42+
43+
```sh
44+
uv run --env-file opentelemetry.env main.py
45+
```
46+
47+
## Viewing the results
48+
49+
To view the generated traces with [Generative AI
50+
events](https://cloud.google.com/trace/docs/finding-traces#view_generative_ai_events) in the
51+
GCP console, use the [Trace Explorer](https://cloud.google.com/trace/docs/finding-traces). Filter for spans with OpenTelemetry Service: `adk-sql-agent`.
52+
53+
[ADC]: https://cloud.google.com/docs/authentication/application-default-credentials

samples/adk-sql-agent/main.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
import google.auth
17+
import google.auth.transport.requests
18+
import grpc
19+
from google.auth.transport.grpc import AuthMetadataPlugin
20+
from opentelemetry import _events as events
21+
from opentelemetry import _logs as logs
22+
from opentelemetry import metrics, trace
23+
from opentelemetry.exporter.cloud_logging import CloudLoggingExporter
24+
from opentelemetry.exporter.cloud_monitoring import CloudMonitoringMetricsExporter
25+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
26+
OTLPSpanExporter,
27+
)
28+
from opentelemetry.instrumentation.sqlite3 import SQLite3Instrumentor
29+
from opentelemetry.instrumentation.google_genai import GoogleGenAiSdkInstrumentor
30+
from opentelemetry.instrumentation.vertexai import VertexAIInstrumentor
31+
from opentelemetry.sdk._events import EventLoggerProvider
32+
from opentelemetry.sdk._logs import LoggerProvider
33+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
34+
from opentelemetry.sdk.metrics import MeterProvider
35+
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
36+
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
37+
from opentelemetry.sdk.trace import TracerProvider
38+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
39+
import os
40+
41+
import uvicorn
42+
from fastapi import FastAPI
43+
from google.adk.cli.fast_api import get_fast_api_app
44+
45+
# Get the directory where main.py is located
46+
AGENT_DIR = os.path.dirname(os.path.abspath(__file__))
47+
# Example session DB URL (e.g., SQLite)
48+
SESSION_DB_URL = "sqlite:///./sessions.db"
49+
# Example allowed origins for CORS
50+
ALLOWED_ORIGINS = ["http://localhost", "http://localhost:8080", "*"]
51+
# Set web=True if you intend to serve a web interface, False otherwise
52+
SERVE_WEB_INTERFACE = True
53+
54+
# [START opentelemetry_adk_otel_setup]
55+
def setup_opentelemetry() -> None:
56+
credentials, project_id = google.auth.default()
57+
resource = Resource.create(
58+
attributes={
59+
SERVICE_NAME: "adk-sql-agent",
60+
# The project to send spans to
61+
"gcp.project_id": project_id,
62+
}
63+
)
64+
65+
# Set up OTLP auth
66+
request = google.auth.transport.requests.Request()
67+
auth_metadata_plugin = AuthMetadataPlugin(credentials=credentials, request=request)
68+
channel_creds = grpc.composite_channel_credentials(
69+
grpc.ssl_channel_credentials(),
70+
grpc.metadata_call_credentials(auth_metadata_plugin),
71+
)
72+
73+
# Set up OpenTelemetry Python SDK
74+
tracer_provider = TracerProvider(resource=resource)
75+
tracer_provider.add_span_processor(
76+
BatchSpanProcessor(
77+
OTLPSpanExporter(
78+
credentials=channel_creds,
79+
endpoint="https://telemetry.googleapis.com:443/v1/traces",
80+
)
81+
)
82+
)
83+
trace.set_tracer_provider(tracer_provider)
84+
85+
logger_provider = LoggerProvider(resource=resource)
86+
logger_provider.add_log_record_processor(
87+
BatchLogRecordProcessor(CloudLoggingExporter())
88+
)
89+
logs.set_logger_provider(logger_provider)
90+
91+
event_logger_provider = EventLoggerProvider(logger_provider)
92+
events.set_event_logger_provider(event_logger_provider)
93+
94+
reader = PeriodicExportingMetricReader(CloudMonitoringMetricsExporter())
95+
meter_provider = MeterProvider(metric_readers=[reader], resource=resource)
96+
metrics.set_meter_provider(meter_provider)
97+
98+
# Load instrumentors
99+
SQLite3Instrumentor().instrument()
100+
VertexAIInstrumentor().instrument()
101+
GoogleGenAiSdkInstrumentor().instrument()
102+
103+
104+
# [END opentelemetry_adk_otel_setup]
105+
106+
107+
def main() -> None:
108+
# Make sure to set:
109+
# OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
110+
# OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
111+
# in order to full prompts and responses and logs messages.
112+
# For this sample, these can be set by loading the `main.env` file.
113+
setup_opentelemetry()
114+
115+
# Call the function to get the FastAPI app instance.
116+
# Ensure that the agent director name is the name of directory containing agent subdirectories,
117+
# where each subdirectory represents a single agent with __init__.py and agent.py files.
118+
# For this example this would be the current directory containing main.py.
119+
# Note: Calling this method attempts to set the global tracer provider, which has already been
120+
# set by the setup_opentelemetry() function.
121+
app = get_fast_api_app(
122+
agents_dir=AGENT_DIR,
123+
session_service_uri=SESSION_DB_URL,
124+
allow_origins=ALLOWED_ORIGINS,
125+
web=SERVE_WEB_INTERFACE,
126+
)
127+
128+
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
129+
130+
131+
if __name__ == "__main__":
132+
main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
2+
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
[project]
16+
name = "adk-sql-agent"
17+
version = "0.1.0"
18+
description = "An ADK agent that can run queries on an ephemeral SQLite database"
19+
readme = "README.md"
20+
requires-python = ">=3.12"
21+
dependencies = [
22+
"google-adk>=1.6.1",
23+
"opentelemetry-exporter-gcp-logging>=1.9.0a0",
24+
"opentelemetry-exporter-gcp-monitoring>=1.9.0a0",
25+
"opentelemetry-exporter-otlp-proto-grpc>=1.33.0",
26+
"opentelemetry-instrumentation-google-genai>=0.2b0",
27+
"opentelemetry-instrumentation-httpx>=0.54b0",
28+
"opentelemetry-instrumentation-requests>=0.54b0",
29+
"opentelemetry-instrumentation-sqlite3>=0.54b0",
30+
"opentelemetry-instrumentation-vertexai>=2.0b0",
31+
]

0 commit comments

Comments
 (0)