Skip to content

Commit 3f99d96

Browse files
Merge pull request #51 from agentevals-dev/feautre/adk-zero-code-example
Add ADK zero code example
2 parents 5d28f1e + b389e27 commit 3f99d96

4 files changed

Lines changed: 159 additions & 1 deletion

File tree

examples/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ The OTLP receiver runs on port 4318 (standard OTLP HTTP port) and accepts both `
2020
|---------|-----------|-------------|
2121
| [zero-code-examples/langchain/](./zero-code-examples/langchain/) | LangChain | OpenAI |
2222
| [zero-code-examples/strands/](./zero-code-examples/strands/) | Strands | OpenAI |
23+
| [zero-code-examples/adk/](./zero-code-examples/adk/) | Google ADK | Gemini |
2324

2425
This approach works with any framework that has OTel instrumentation: LangChain, Strands, Google ADK, etc. If your framework already emits OTel spans, you only need to add `OTLPSpanExporter` (and `OTLPLogExporter` if it uses GenAI log-based content delivery).
2526

@@ -92,6 +93,7 @@ Detection checks for `gen_ai.request.model` / `gen_ai.input.messages` (GenAI sem
9293
|---------|-----------|-------------|-----------------|-----------------|
9394
| [zero-code-examples/langchain/](./zero-code-examples/langchain/) | LangChain | OpenAI | GenAI semconv (logs) | Standard OTLP export |
9495
| [zero-code-examples/strands/](./zero-code-examples/strands/) | Strands | OpenAI | GenAI semconv (events) | Standard OTLP export |
96+
| [zero-code-examples/adk/](./zero-code-examples/adk/) | Google ADK | Gemini | ADK built-in | Standard OTLP export |
9597
| [langchain_agent](./langchain_agent/) | LangChain | OpenAI | GenAI semconv (logs) | SDK WebSocket |
9698
| [strands_agent](./strands_agent/) | Strands | OpenAI | GenAI semconv (events) | SDK WebSocket |
9799
| [dice_agent](./dice_agent/) | Google ADK | Gemini | ADK built-in | SDK WebSocket |
@@ -190,6 +192,7 @@ cd ui && npm run dev
190192
# Zero-code OTLP (recommended):
191193
python examples/zero-code-examples/langchain/run.py
192194
python examples/zero-code-examples/strands/run.py
195+
python examples/zero-code-examples/adk/run.py
193196

194197
# SDK examples:
195198
python examples/sdk_example/context_manager_example.py
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
google-adk>=1.0.0
2+
3+
opentelemetry-sdk>=1.36.0
4+
opentelemetry-exporter-otlp-proto-http>=1.36.0
5+
python-dotenv>=1.0.0
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""Run the ADK dice agent with standard OTLP export -- no agentevals SDK.
2+
3+
Demonstrates zero-code integration: ADK auto-instruments itself through
4+
the global TracerProvider, so the only setup needed is a standard
5+
OTLPSpanExporter pointing at the agentevals receiver.
6+
7+
Unlike the LangChain and Strands zero-code examples, ADK needs no
8+
instrumentor call, no LoggerProvider, and no special environment variables.
9+
ADK puts message content directly on span attributes (gcp.vertex.agent.*).
10+
11+
Prerequisites:
12+
1. pip install -r requirements.txt
13+
2. agentevals serve --dev
14+
3. export GOOGLE_API_KEY="your-key-here"
15+
16+
Usage:
17+
python examples/zero-code-examples/adk/run.py
18+
"""
19+
20+
import asyncio
21+
import os
22+
import sys
23+
24+
from dotenv import load_dotenv
25+
from google.adk.runners import InMemoryRunner
26+
from google.genai import types
27+
from opentelemetry import trace
28+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
29+
from opentelemetry.sdk.resources import Resource
30+
from opentelemetry.sdk.trace import TracerProvider
31+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
32+
33+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "dice_agent"))
34+
from agent import dice_agent
35+
36+
load_dotenv(override=True)
37+
38+
39+
async def main():
40+
if not os.getenv("GOOGLE_API_KEY"):
41+
print("GOOGLE_API_KEY not set.")
42+
return
43+
44+
endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318")
45+
print(f"OTLP endpoint: {endpoint}")
46+
47+
os.environ.setdefault(
48+
"OTEL_RESOURCE_ATTRIBUTES",
49+
"agentevals.eval_set_id=dice_agent_eval,agentevals.session_name=adk-zero-code",
50+
)
51+
52+
resource = Resource.create()
53+
54+
tracer_provider = TracerProvider(resource=resource)
55+
tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(), schedule_delay_millis=1000))
56+
trace.set_tracer_provider(tracer_provider)
57+
58+
app_name = "dice_agent_app"
59+
user_id = "demo_user"
60+
61+
runner = InMemoryRunner(agent=dice_agent, app_name=app_name)
62+
session = await runner.session_service.create_session(app_name=app_name, user_id=user_id)
63+
64+
test_queries = [
65+
"Hi! Can you help me?",
66+
"Roll a 20-sided die for me",
67+
"Is the number you rolled prime?",
68+
]
69+
70+
for i, query in enumerate(test_queries, 1):
71+
print(f"\n[{i}/{len(test_queries)}] User: {query}")
72+
73+
content = types.Content(role="user", parts=[types.Part.from_text(text=query)])
74+
75+
agent_response = ""
76+
async for event in runner.run_async(user_id=user_id, session_id=session.id, new_message=content):
77+
if event.content.parts and event.content.parts[0].text:
78+
agent_response = event.content.parts[0].text
79+
80+
print(f" Agent: {agent_response}")
81+
82+
print()
83+
tracer_provider.force_flush()
84+
print("All traces flushed to OTLP receiver.")
85+
86+
87+
if __name__ == "__main__":
88+
asyncio.run(main())

tests/integration/test_live_agents.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
exercising the full pipeline including BatchSpanProcessor/BatchLogRecordProcessor
55
flush timing, session grouping, and invocation extraction.
66
7-
Requires API keys (OPENAI_API_KEY for LangChain/Strands).
7+
Requires API keys (OPENAI_API_KEY for LangChain/Strands, GOOGLE_API_KEY for ADK).
88
Skipped when keys are not available.
99
1010
Tests are synchronous because:
@@ -33,6 +33,11 @@
3333
reason="OPENAI_API_KEY not set",
3434
)
3535

36+
_skip_no_google = pytest.mark.skipif(
37+
not os.environ.get("GOOGLE_API_KEY"),
38+
reason="GOOGLE_API_KEY not set",
39+
)
40+
3641

3742
def _run_agent(
3843
script: str,
@@ -186,6 +191,63 @@ def test_session_visible_via_api(self, live_servers):
186191
assert session_name in session_ids
187192

188193

194+
@_skip_no_google
195+
class TestAdkZeroCode:
196+
"""Run the ADK zero-code OTLP example and verify session grouping."""
197+
198+
def test_session_created_spans_only(self, live_servers):
199+
main_port, otlp_port, mgr = live_servers
200+
session_name = "e2e-adk"
201+
202+
result = _run_agent(
203+
"examples/zero-code-examples/adk/run.py",
204+
otlp_port,
205+
session_name,
206+
)
207+
assert result.returncode == 0, f"Agent failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"
208+
209+
wait_for_session_complete_sync(mgr, session_name, timeout=30)
210+
session = mgr.sessions[session_name]
211+
212+
assert session.is_complete
213+
assert session.source == "otlp"
214+
assert len(session.spans) > 0, "Expected spans from ADK agent"
215+
216+
def test_invocations_extracted(self, live_servers):
217+
main_port, otlp_port, mgr = live_servers
218+
session_name = "e2e-adk-inv"
219+
220+
result = _run_agent(
221+
"examples/zero-code-examples/adk/run.py",
222+
otlp_port,
223+
session_name,
224+
)
225+
assert result.returncode == 0, f"Agent failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"
226+
227+
wait_for_session_complete_sync(mgr, session_name, timeout=30)
228+
session = mgr.sessions[session_name]
229+
230+
assert len(session.invocations) > 0, "Expected extracted invocations"
231+
232+
def test_session_visible_via_api(self, live_servers):
233+
main_port, otlp_port, mgr = live_servers
234+
session_name = "e2e-adk-api"
235+
236+
result = _run_agent(
237+
"examples/zero-code-examples/adk/run.py",
238+
otlp_port,
239+
session_name,
240+
)
241+
assert result.returncode == 0
242+
243+
wait_for_session_complete_sync(mgr, session_name, timeout=30)
244+
245+
resp = httpx.get(f"http://127.0.0.1:{main_port}/api/streaming/sessions")
246+
assert resp.status_code == 200
247+
session_ids = [s["sessionId"] for s in resp.json()["data"]]
248+
assert session_name in session_ids
249+
250+
189251
@_skip_no_openai
190252
class TestAgentRerun:
191253
"""Verify that re-running an agent with the same session_name creates

0 commit comments

Comments
 (0)