Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions python/01-learn/21-observability-debugging/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Virtual environment
.venv/
venv/

# Jupyter
.ipynb_checkpoints/

# Python
__pycache__/
*.pyc
*.pyo

# macOS
.DS_Store

# IDE
.vscode/
.idea/
239 changes: 239 additions & 0 deletions python/01-learn/21-observability-debugging/01_tracing_setup.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "intro",
"metadata": {},
"source": [
"# Observability & Debugging — Part 1: Tracing Setup\n",
"\n",
"This notebook teaches you how to configure OpenTelemetry tracing with Strands Agents\n",
"using the built-in `StrandsTelemetry` class and run your first traced agent invocation.\n",
"\n",
"**What you'll learn:**\n",
"- Configure `StrandsTelemetry` with the console exporter\n",
"- Run an agent and observe trace output in stdout\n",
"- Capture spans programmatically for inspection\n",
"\n",
"**Prerequisites:**\n",
"- Python 3.10+\n",
"- AWS credentials configured (for Bedrock model access)\n",
"- `pip install -r requirements.txt` completed"
]
},
{
"cell_type": "markdown",
"id": "concept",
"metadata": {},
"source": [
"## How It Works\n",
"\n",
"`StrandsTelemetry` manages the global OpenTelemetry `TracerProvider`. Once configured,\n",
"every `Agent` instance automatically picks up the tracer — no explicit wiring needed.\n",
"\n",
"```\n",
"StrandsTelemetry() → creates TracerProvider\n",
" .setup_console_exporter() → attaches ConsoleSpanExporter\n",
" .setup_otlp_exporter() → attaches OTLPSpanExporter\n",
"\n",
"Agent(...) → reads global TracerProvider\n",
" agent(\"prompt\") → emits spans automatically\n",
"```\n",
"\n",
"**Important:** `StrandsTelemetry` must be configured *before* creating any `Agent`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "setup-telemetry",
"metadata": {},
"outputs": [],
"source": [
"from strands.telemetry.config import StrandsTelemetry\n",
"\n",
"# Configure telemetry with console exporter\n",
"# This prints every span to stdout as it completes\n",
"telemetry = StrandsTelemetry()\n",
"telemetry.setup_console_exporter()\n",
"\n",
"print(\"✓ Telemetry configured with console exporter\")\n",
"print(\" Every agent invocation will now produce trace output below.\")"
]
},
{
"cell_type": "markdown",
"id": "first-trace",
"metadata": {},
"source": [
"## Your First Traced Invocation\n",
"\n",
"Let's define a simple tool and create an agent. When we invoke the agent, the console\n",
"exporter will print each span as it completes."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "define-tool",
"metadata": {},
"outputs": [],
"source": [
"from strands import Agent, tool\n",
"from strands.models.bedrock import BedrockModel\n",
"\n",
"\n",
"@tool\n",
"def calculator(expression: str) -> str:\n",
" \"\"\"Evaluate a mathematical expression.\n",
"\n",
" Args:\n",
" expression: A mathematical expression to evaluate (e.g., \"42 * 17\")\n",
"\n",
" Returns:\n",
" The result of the expression as a string.\n",
" \"\"\"\n",
" try:\n",
" result = eval(expression, {\"__builtins__\": {}}, {})\n",
" return str(result)\n",
" except Exception as e:\n",
" return f\"Error: {e}\"\n",
"\n",
"\n",
"# Create agent — automatically picks up the global tracer\n",
"agent = Agent(\n",
" model=BedrockModel(model_id=\"us.amazon.nova-lite-v1:0\"),\n",
" tools=[calculator],\n",
")\n",
"\n",
"print(\"✓ Agent created with calculator tool\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "invoke-agent",
"metadata": {},
"outputs": [],
"source": [
"# Invoke the agent — trace spans will print to stdout\n",
"result = agent(\"What is 42 multiplied by 17?\")\n",
"print(f\"\\n{'='*60}\")\n",
"print(f\"Agent response: {result}\")\n",
"print(f\"{'='*60}\")\n",
"print(\"\\n↑ The trace output above shows every span that was created.\")\n",
"print(\" Look for: Agent span, Cycle span(s), Model Invoke span(s), Tool span(s)\")"
]
},
{
"cell_type": "markdown",
"id": "programmatic",
"metadata": {},
"source": [
"## Capturing Spans Programmatically\n",
"\n",
"The console exporter is great for visual inspection. For programmatic analysis,\n",
"we can create a simple span collector that stores spans in a list."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "memory-exporter",
"metadata": {},
"outputs": [],
"source": [
"from opentelemetry.sdk.trace.export import SimpleSpanProcessor, SpanExporter, SpanExportResult\n",
"from opentelemetry import trace\n",
"\n",
"\n",
"class SpanCollector(SpanExporter):\n",
" \"\"\"Simple in-memory span collector for tutorial use.\"\"\"\n",
"\n",
" def __init__(self):\n",
" self._spans = []\n",
"\n",
" def export(self, spans):\n",
" self._spans.extend(spans)\n",
" return SpanExportResult.SUCCESS\n",
"\n",
" def get_finished_spans(self):\n",
" return list(self._spans)\n",
"\n",
" def clear(self):\n",
" self._spans = []\n",
"\n",
" def shutdown(self):\n",
" self._spans = []\n",
"\n",
"\n",
"# Add span collector alongside the console exporter\n",
"span_collector = SpanCollector()\n",
"provider = trace.get_tracer_provider()\n",
"if hasattr(provider, \"add_span_processor\"):\n",
" provider.add_span_processor(SimpleSpanProcessor(span_collector))\n",
" print(\"✓ Span collector added — spans will be captured for inspection\")\n",
"else:\n",
" print(\"⚠️ Could not add span processor\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "capture-and-inspect",
"metadata": {},
"outputs": [],
"source": [
"# Clear previous spans and run a fresh invocation\n",
"span_collector.clear()\n",
"_ = agent(\"What is 7 + 3?\")\n",
"\n",
"# Inspect captured spans\n",
"spans = span_collector.get_finished_spans()\n",
"print(f\"\\n📊 Captured {len(spans)} spans:\\n\")\n",
"\n",
"for span in spans:\n",
" attrs = span.attributes or {}\n",
" duration_ms = (\n",
" (span.end_time - span.start_time) / 1_000_000\n",
" if span.end_time and span.start_time\n",
" else 0\n",
" )\n",
" print(f\" {span.name:<25} duration={duration_ms:.0f}ms status={span.status.status_code.name}\")"
]
},
{
"cell_type": "markdown",
"id": "troubleshooting",
"metadata": {},
"source": [
"## Troubleshooting\n",
"\n",
"| Problem | Solution |\n",
"|---------|----------|\n",
"| No trace output visible | Console exporter prints spans when they *end*. Wait for the agent call to complete. |\n",
"| `ModuleNotFoundError: No module named 'opentelemetry'` | Run `pip install -r requirements.txt` |\n",
"| `No TracerProvider configured` | Ensure `StrandsTelemetry` is configured *before* creating the Agent |\n",
"| AWS credentials error | Run `aws configure` or set `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` |\n",
"\n",
"## Next\n",
"\n",
"Continue to [02_trace_hierarchy.ipynb](02_trace_hierarchy.ipynb) to understand the\n",
"Agent → Cycle → Model → Tool span hierarchy in detail."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.10.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading
Loading