|
10 | 10 | "\n", |
11 | 11 | "This notebook demonstrates how to trace Google Agent Development Kit (ADK) agents with Openlayer.\n", |
12 | 12 | "\n", |
| 13 | + "## Features\n", |
| 14 | + "\n", |
| 15 | + "- **Full Agent Tracing**: Capture agent execution, LLM calls, and tool usage\n", |
| 16 | + "- **Token Usage Tracking**: Automatically captures prompt, completion, and total tokens\n", |
| 17 | + "- **All 6 ADK Callbacks**: Trace before_agent, after_agent, before_model, after_model, before_tool, after_tool\n", |
| 18 | + "- **Google Cloud Coexistence**: Use both Google Cloud telemetry (Cloud Trace) AND Openlayer simultaneously\n", |
| 19 | + "\n", |
13 | 20 | "## Prerequisites\n", |
14 | 21 | "\n", |
15 | 22 | "Install the required packages:\n", |
|
18 | 25 | "```\n" |
19 | 26 | ] |
20 | 27 | }, |
| 28 | + { |
| 29 | + "cell_type": "code", |
| 30 | + "execution_count": null, |
| 31 | + "metadata": {}, |
| 32 | + "outputs": [], |
| 33 | + "source": [ |
| 34 | + "!pip install google-adk wrapt" |
| 35 | + ] |
| 36 | + }, |
21 | 37 | { |
22 | 38 | "cell_type": "markdown", |
23 | 39 | "metadata": {}, |
|
36 | 52 | "import os\n", |
37 | 53 | "\n", |
38 | 54 | "# Openlayer configuration\n", |
39 | | - "os.environ[\"OPENLAYER_API_KEY\"] = \"your-api-key-here\"\n", |
40 | | - "os.environ[\"OPENLAYER_INFERENCE_PIPELINE_ID\"] = \"your-pipeline-id-here\"\n", |
| 55 | + "os.environ[\"OPENLAYER_API_KEY\"] = \"your-api-key\"\n", |
| 56 | + "os.environ[\"OPENLAYER_INFERENCE_PIPELINE_ID\"] = \"your-pipeline-id\"\n", |
41 | 57 | "\n", |
42 | 58 | "# Google AI API configuration (Option 1: Using Google AI Studio)\n", |
43 | 59 | "# Get your API key from: https://aistudio.google.com/apikey\n", |
44 | | - "os.environ[\"GOOGLE_API_KEY\"] = \"your-google-ai-api-key-here\"\n", |
| 60 | + "os.environ[\"GOOGLE_API_KEY\"] = \"your-google-api-key\"\n", |
45 | 61 | "\n", |
46 | 62 | "# Google Cloud Vertex AI configuration (Option 2: Using Google Cloud)\n", |
47 | 63 | "# Uncomment these if you're using Vertex AI instead of Google AI\n", |
|
56 | 72 | "source": [ |
57 | 73 | "## Enable Google ADK Tracing\n", |
58 | 74 | "\n", |
59 | | - "Enable tracing before creating any agents. This patches Google ADK globally to send traces to Openlayer:\n" |
| 75 | + "Enable tracing before creating any agents. This patches Google ADK globally to send traces to Openlayer.\n", |
| 76 | + "\n", |
| 77 | + "**Note:** By default, ADK's built-in OpenTelemetry tracing remains active, allowing you to send data to both Google Cloud (Cloud Trace, Cloud Monitoring) AND Openlayer. If you only want Openlayer, use `trace_google_adk(disable_adk_otel=True)`.\n" |
60 | 78 | ] |
61 | 79 | }, |
62 | 80 | { |
|
102 | 120 | "\n", |
103 | 121 | "# Create a basic agent\n", |
104 | 122 | "agent = LlmAgent(\n", |
105 | | - " model=\"gemini-2.0-flash-exp\",\n", |
| 123 | + " model=\"gemini-2.5-flash\",\n", |
106 | 124 | " name=\"Assistant\",\n", |
107 | 125 | " instruction=\"You are a helpful assistant. Provide concise and accurate responses.\"\n", |
108 | 126 | ")\n", |
|
190 | 208 | "\n", |
191 | 209 | "# Create agent with tools (pass functions directly)\n", |
192 | 210 | "tool_agent = LlmAgent(\n", |
193 | | - " model=\"gemini-2.0-flash-exp\",\n", |
| 211 | + " model=\"gemini-2.5-flash\",\n", |
194 | 212 | " name=\"ToolAgent\",\n", |
195 | 213 | " instruction=\"You are a helpful assistant with access to weather and calculation tools. Use them when appropriate.\",\n", |
196 | 214 | " tools=[get_weather, calculate]\n", |
|
229 | 247 | "await run_tool_agent()\n" |
230 | 248 | ] |
231 | 249 | }, |
| 250 | + { |
| 251 | + "cell_type": "markdown", |
| 252 | + "metadata": {}, |
| 253 | + "source": [ |
| 254 | + "## Example 3: Agent with All 6 Callbacks\n", |
| 255 | + "\n", |
| 256 | + "Google ADK supports 6 types of callbacks that allow you to observe, customize, and control agent behavior. Openlayer automatically traces all of them:\n", |
| 257 | + "\n", |
| 258 | + "| Callback | Description | When Called |\n", |
| 259 | + "|----------|-------------|-------------|\n", |
| 260 | + "| `before_agent_callback` | Agent pre-processing | Before the agent starts its main work |\n", |
| 261 | + "| `after_agent_callback` | Agent post-processing | After the agent finishes all its steps |\n", |
| 262 | + "| `before_model_callback` | LLM pre-call | Before sending a request to the LLM |\n", |
| 263 | + "| `after_model_callback` | LLM post-call | After receiving a response from the LLM |\n", |
| 264 | + "| `before_tool_callback` | Tool pre-execution | Before executing a tool |\n", |
| 265 | + "| `after_tool_callback` | Tool post-execution | After a tool finishes |\n", |
| 266 | + "\n", |
| 267 | + "Reference: https://google.github.io/adk-docs/callbacks/\n" |
| 268 | + ] |
| 269 | + }, |
| 270 | + { |
| 271 | + "cell_type": "code", |
| 272 | + "execution_count": null, |
| 273 | + "metadata": {}, |
| 274 | + "outputs": [], |
| 275 | + "source": [ |
| 276 | + "from typing import Any, Dict, Optional\n", |
| 277 | + "\n", |
| 278 | + "from google.adk.tools import ToolContext\n", |
| 279 | + "from google.adk.models import LlmRequest, LlmResponse\n", |
| 280 | + "from google.adk.tools.base_tool import BaseTool\n", |
| 281 | + "from google.adk.agents.callback_context import CallbackContext\n", |
| 282 | + "\n", |
| 283 | + "# ============================================================================\n", |
| 284 | + "# Define all 6 callback functions\n", |
| 285 | + "# ============================================================================\n", |
| 286 | + "\n", |
| 287 | + "# 1. Before Agent Callback\n", |
| 288 | + "# Called before the agent starts processing a request\n", |
| 289 | + "def before_agent_callback(callback_context: CallbackContext) -> Optional[Any]:\n", |
| 290 | + " \"\"\"\n", |
| 291 | + " Called before the agent starts its main work.\n", |
| 292 | + " \n", |
| 293 | + " Use cases:\n", |
| 294 | + " - Input validation\n", |
| 295 | + " - Session initialization\n", |
| 296 | + " - Logging request start\n", |
| 297 | + " - Adding default context\n", |
| 298 | + " \"\"\"\n", |
| 299 | + " print(f\"[before_agent] Agent '{callback_context.agent_name}' starting\") # noqa: T201\n", |
| 300 | + " print(f\"[before_agent] Invocation ID: {callback_context.invocation_id}\") # noqa: T201\n", |
| 301 | + " # Return None to allow the agent to proceed normally\n", |
| 302 | + " # Return a Content object to skip the agent and return that content directly\n", |
| 303 | + " return None\n", |
| 304 | + "\n", |
| 305 | + "\n", |
| 306 | + "# 2. After Agent Callback\n", |
| 307 | + "# Called after the agent finishes processing\n", |
| 308 | + "def after_agent_callback(callback_context: CallbackContext) -> Optional[Any]:\n", |
| 309 | + " \"\"\"\n", |
| 310 | + " Called after the agent has finished all its steps.\n", |
| 311 | + " \n", |
| 312 | + " Use cases:\n", |
| 313 | + " - Response post-processing\n", |
| 314 | + " - Logging request completion\n", |
| 315 | + " - Cleanup operations\n", |
| 316 | + " - Analytics\n", |
| 317 | + " \"\"\"\n", |
| 318 | + " print(f\"[after_agent] Agent '{callback_context.agent_name}' finished\") # noqa: T201\n", |
| 319 | + " # Return None to use the agent's original response\n", |
| 320 | + " # Return a Content object to replace the agent's response\n", |
| 321 | + " return None\n", |
| 322 | + "\n", |
| 323 | + "\n", |
| 324 | + "# 3. Before Model Callback\n", |
| 325 | + "# Called before each LLM call\n", |
| 326 | + "def before_model_callback(\n", |
| 327 | + " _callback_context: CallbackContext, \n", |
| 328 | + " llm_request: LlmRequest\n", |
| 329 | + ") -> Optional[LlmResponse]:\n", |
| 330 | + " \"\"\"\n", |
| 331 | + " Called before sending a request to the LLM.\n", |
| 332 | + " \n", |
| 333 | + " Use cases:\n", |
| 334 | + " - Request modification (add system prompts)\n", |
| 335 | + " - Content filtering / guardrails\n", |
| 336 | + " - Caching (return cached response)\n", |
| 337 | + " - Rate limiting\n", |
| 338 | + " \"\"\"\n", |
| 339 | + " print(f\"[before_model] Calling model: {llm_request.model}\") # noqa: T201\n", |
| 340 | + " print(f\"[before_model] Request has {len(llm_request.contents)} content items\") # noqa: T201\n", |
| 341 | + " # Return None to proceed with the LLM call\n", |
| 342 | + " # Return an LlmResponse to skip the LLM and use that response instead\n", |
| 343 | + " return None\n", |
| 344 | + "\n", |
| 345 | + "\n", |
| 346 | + "# 4. After Model Callback\n", |
| 347 | + "# Called after receiving LLM response\n", |
| 348 | + "def after_model_callback(\n", |
| 349 | + " _callback_context: CallbackContext, \n", |
| 350 | + " llm_response: LlmResponse\n", |
| 351 | + ") -> Optional[LlmResponse]:\n", |
| 352 | + " \"\"\"\n", |
| 353 | + " Called after receiving a response from the LLM.\n", |
| 354 | + " \n", |
| 355 | + " Use cases:\n", |
| 356 | + " - Response validation\n", |
| 357 | + " - Content filtering\n", |
| 358 | + " - Response transformation\n", |
| 359 | + " - Logging/analytics\n", |
| 360 | + " \"\"\"\n", |
| 361 | + " print(\"[after_model] Received response from LLM\") # noqa: T201\n", |
| 362 | + " if hasattr(llm_response, 'usage_metadata') and llm_response.usage_metadata:\n", |
| 363 | + " print(f\"[after_model] Tokens used: {llm_response.usage_metadata.total_token_count}\") # noqa: T201\n", |
| 364 | + " # Return None to use the original response\n", |
| 365 | + " # Return a modified LlmResponse to replace it\n", |
| 366 | + " return None\n", |
| 367 | + "\n", |
| 368 | + "\n", |
| 369 | + "# 5. Before Tool Callback\n", |
| 370 | + "# Called before tool execution\n", |
| 371 | + "def before_tool_callback(\n", |
| 372 | + " tool: BaseTool, \n", |
| 373 | + " args: Dict[str, Any], \n", |
| 374 | + " _tool_context: ToolContext\n", |
| 375 | + ") -> Optional[Dict]:\n", |
| 376 | + " \"\"\"\n", |
| 377 | + " Called before executing a tool.\n", |
| 378 | + " \n", |
| 379 | + " Use cases:\n", |
| 380 | + " - Argument validation\n", |
| 381 | + " - Authorization checks\n", |
| 382 | + " - Tool call logging\n", |
| 383 | + " - Mocking tool responses for testing\n", |
| 384 | + " \"\"\"\n", |
| 385 | + " print(f\"[before_tool] Executing tool: {tool.name}\") # noqa: T201\n", |
| 386 | + " print(f\"[before_tool] Arguments: {args}\") # noqa: T201\n", |
| 387 | + " # Return None to proceed with the tool execution\n", |
| 388 | + " # Return a dict to skip the tool and use that as the response\n", |
| 389 | + " return None\n", |
| 390 | + "\n", |
| 391 | + "\n", |
| 392 | + "# 6. After Tool Callback\n", |
| 393 | + "# Called after tool execution\n", |
| 394 | + "def after_tool_callback(\n", |
| 395 | + " tool: BaseTool, \n", |
| 396 | + " _args: Dict[str, Any], \n", |
| 397 | + " _tool_context: ToolContext, \n", |
| 398 | + " tool_response: Dict\n", |
| 399 | + ") -> Optional[Dict]:\n", |
| 400 | + " \"\"\"\n", |
| 401 | + " Called after a tool finishes execution.\n", |
| 402 | + " \n", |
| 403 | + " Use cases:\n", |
| 404 | + " - Response transformation\n", |
| 405 | + " - Error handling\n", |
| 406 | + " - Logging tool results\n", |
| 407 | + " - Caching responses\n", |
| 408 | + " \"\"\"\n", |
| 409 | + " print(f\"[after_tool] Tool '{tool.name}' completed\") # noqa: T201\n", |
| 410 | + " print(f\"[after_tool] Response: {tool_response}\") # noqa: T201\n", |
| 411 | + " # Return None to use the original tool response\n", |
| 412 | + " # Return a modified dict to replace the response\n", |
| 413 | + " return None\n" |
| 414 | + ] |
| 415 | + }, |
| 416 | + { |
| 417 | + "cell_type": "code", |
| 418 | + "execution_count": null, |
| 419 | + "metadata": {}, |
| 420 | + "outputs": [], |
| 421 | + "source": [ |
| 422 | + "# ============================================================================\n", |
| 423 | + "# Create agent with all callbacks\n", |
| 424 | + "# ============================================================================\n", |
| 425 | + "\n", |
| 426 | + "# Define a tool for the callback agent to use\n", |
| 427 | + "def get_current_time() -> str:\n", |
| 428 | + " \"\"\"Returns the current time.\n", |
| 429 | + " \n", |
| 430 | + " Returns:\n", |
| 431 | + " str: The current time as a formatted string.\n", |
| 432 | + " \"\"\"\n", |
| 433 | + " from datetime import datetime\n", |
| 434 | + " return f\"The current time is {datetime.now().strftime('%H:%M:%S')}\"\n", |
| 435 | + "\n", |
| 436 | + "\n", |
| 437 | + "# Use different session IDs for callback agent\n", |
| 438 | + "CALLBACK_USER_ID = \"user_789\"\n", |
| 439 | + "CALLBACK_SESSION_ID = \"session_789\"\n", |
| 440 | + "\n", |
| 441 | + "# Create agent with ALL 6 callbacks\n", |
| 442 | + "callback_agent = LlmAgent(\n", |
| 443 | + " model=\"gemini-2.5-flash\",\n", |
| 444 | + " name=\"CallbackDemoAgent\",\n", |
| 445 | + " instruction=\"You are a helpful assistant. Use the get_current_time tool when asked about time.\",\n", |
| 446 | + " tools=[get_current_time],\n", |
| 447 | + " # Register all 6 callbacks\n", |
| 448 | + " before_agent_callback=before_agent_callback,\n", |
| 449 | + " after_agent_callback=after_agent_callback,\n", |
| 450 | + " before_model_callback=before_model_callback,\n", |
| 451 | + " after_model_callback=after_model_callback,\n", |
| 452 | + " before_tool_callback=before_tool_callback,\n", |
| 453 | + " after_tool_callback=after_tool_callback,\n", |
| 454 | + ")\n", |
| 455 | + "\n", |
| 456 | + "# Create runner for callback agent\n", |
| 457 | + "callback_runner = Runner(\n", |
| 458 | + " agent=callback_agent,\n", |
| 459 | + " app_name=APP_NAME,\n", |
| 460 | + " session_service=session_service\n", |
| 461 | + ")\n", |
| 462 | + "\n", |
| 463 | + "# Define async function to run the callback agent\n", |
| 464 | + "async def run_callback_agent():\n", |
| 465 | + " # Create session\n", |
| 466 | + " await session_service.create_session(\n", |
| 467 | + " app_name=APP_NAME,\n", |
| 468 | + " user_id=CALLBACK_USER_ID,\n", |
| 469 | + " session_id=CALLBACK_SESSION_ID\n", |
| 470 | + " )\n", |
| 471 | + " \n", |
| 472 | + " # Run the agent with a query that will trigger tool use\n", |
| 473 | + " query = \"What time is it right now?\"\n", |
| 474 | + " content = types.Content(role='user', parts=[types.Part(text=query)])\n", |
| 475 | + " \n", |
| 476 | + " # Process events and get response\n", |
| 477 | + " async for event in callback_runner.run_async(\n", |
| 478 | + " user_id=CALLBACK_USER_ID,\n", |
| 479 | + " session_id=CALLBACK_SESSION_ID,\n", |
| 480 | + " new_message=content\n", |
| 481 | + " ):\n", |
| 482 | + " if event.is_final_response() and event.content:\n", |
| 483 | + " print(f\"Final Response: {event.content.parts[0].text.strip()}\") # noqa: T201\n", |
| 484 | + "\n", |
| 485 | + "# Run the async function\n", |
| 486 | + "await run_callback_agent()\n" |
| 487 | + ] |
| 488 | + }, |
| 489 | + { |
| 490 | + "cell_type": "markdown", |
| 491 | + "metadata": {}, |
| 492 | + "source": [ |
| 493 | + "### What You'll See in Openlayer\n", |
| 494 | + "\n", |
| 495 | + "After running the callback agent, you'll see in your Openlayer dashboard:\n", |
| 496 | + "\n", |
| 497 | + "1. **Agent Step** (`CallbackDemoAgent`):\n", |
| 498 | + " - Shows which callbacks are registered\n", |
| 499 | + " - Contains all nested steps in chronological order\n", |
| 500 | + "\n", |
| 501 | + "2. **All Callbacks as Siblings** (direct children of Agent):\n", |
| 502 | + " - `Callback: before_agent` - First, before any processing\n", |
| 503 | + " - `Callback: before_model` - Before each LLM call\n", |
| 504 | + " - `LLM Call: gemini-2.0-flash-exp` - The actual LLM invocation\n", |
| 505 | + " - `Callback: after_model` - After each LLM call (includes token counts)\n", |
| 506 | + " - `Callback: before_tool` - Before tool execution\n", |
| 507 | + " - `Tool: get_current_time` - The actual tool execution\n", |
| 508 | + " - `Callback: after_tool` - After tool completion\n", |
| 509 | + " - `Callback: after_agent` - Last, after all processing complete\n", |
| 510 | + "\n", |
| 511 | + "3. **Token Usage** (captured on LLM Call steps):\n", |
| 512 | + " - Prompt tokens\n", |
| 513 | + " - Completion tokens \n", |
| 514 | + " - Total tokens\n" |
| 515 | + ] |
| 516 | + }, |
232 | 517 | { |
233 | 518 | "cell_type": "markdown", |
234 | 519 | "metadata": {}, |
|
240 | 525 | "1. Go to https://app.openlayer.com\n", |
241 | 526 | "2. Navigate to your inference pipeline\n", |
242 | 527 | "3. View the traces tab to see:\n", |
243 | | - " - Agent execution steps\n", |
244 | | - " - LLM calls with token counts\n", |
245 | | - " - Tool executions with inputs and outputs\n", |
246 | | - " - Latency for each operation\n", |
247 | | - " - Complete execution hierarchy\n" |
| 528 | + " - **Agent execution steps** with nested hierarchy\n", |
| 529 | + " - **LLM calls** with token counts (prompt, completion, total)\n", |
| 530 | + " - **Tool executions** with inputs and outputs\n", |
| 531 | + " - **All 6 callback types** traced as separate steps\n", |
| 532 | + " - **Latency** for each operation\n", |
| 533 | + " - **Complete execution hierarchy** showing the flow\n", |
| 534 | + "\n", |
| 535 | + "The traces will show the hierarchy of operations in chronological order:\n", |
| 536 | + "```\n", |
| 537 | + "Agent: CallbackDemoAgent\n", |
| 538 | + "├── Callback: before_agent (CallbackDemoAgent)\n", |
| 539 | + "├── Callback: before_model (CallbackDemoAgent)\n", |
| 540 | + "├── LLM Call: gemini-2.0-flash-exp\n", |
| 541 | + "├── Callback: after_model (CallbackDemoAgent)\n", |
| 542 | + "├── Callback: before_tool (CallbackDemoAgent)\n", |
| 543 | + "├── Tool: get_current_time\n", |
| 544 | + "├── Callback: after_tool (CallbackDemoAgent)\n", |
| 545 | + "├── Callback: before_model (CallbackDemoAgent)\n", |
| 546 | + "├── LLM Call: gemini-2.0-flash-exp\n", |
| 547 | + "├── Callback: after_model (CallbackDemoAgent)\n", |
| 548 | + "└── Callback: after_agent (CallbackDemoAgent)\n", |
| 549 | + "```\n", |
| 550 | + "\n", |
| 551 | + "**Note:** All callbacks are direct children of the Agent step, appearing in chronological order alongside LLM calls and tool executions. This provides a clear timeline view of the agent execution flow.\n" |
248 | 552 | ] |
249 | 553 | }, |
250 | 554 | { |
|
285 | 589 | "name": "python", |
286 | 590 | "nbconvert_exporter": "python", |
287 | 591 | "pygments_lexer": "ipython3", |
288 | | - "version": "3.9.18" |
| 592 | + "version": "3.12.8" |
289 | 593 | } |
290 | 594 | }, |
291 | 595 | "nbformat": 4, |
|
0 commit comments