Skip to content

Commit a46e08b

Browse files
authored
Merge pull request #13 from nagkumar91/main
Add GenAI observability lab samples
2 parents 0b7ce18 + 26f84d1 commit a46e08b

File tree

6 files changed

+1084
-0
lines changed

6 files changed

+1084
-0
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Trace Agent Creation and Invocation\n",
8+
"This notebook illustrates how to emit OpenTelemetry spans for agent workflows using the [GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/).\n",
9+
"\n",
10+
"We simulate creating the Cora retail agent and invoking it through Azure AI Foundry so you can see which attributes to populate for `create_agent`, `invoke_agent`, and nested tool execution spans."
11+
]
12+
},
13+
{
14+
"cell_type": "markdown",
15+
"metadata": {},
16+
"source": [
17+
"## 1. Configure a tracer\n",
18+
"Use the OpenTelemetry SDK with a console exporter so you can inspect the span payload before sending it to a backend."
19+
]
20+
},
21+
{
22+
"cell_type": "code",
23+
"execution_count": null,
24+
"metadata": {
25+
"tags": []
26+
},
27+
"outputs": [],
28+
"source": [
29+
"from opentelemetry import trace\n",
30+
"from opentelemetry.sdk.trace import TracerProvider\n",
31+
"from opentelemetry.sdk.trace.export import SimpleSpanProcessor, ConsoleSpanExporter\n",
32+
"from opentelemetry.trace import SpanKind\n",
33+
"import json\n",
34+
"\n",
35+
"provider = TracerProvider()\n",
36+
"trace.set_tracer_provider(provider)\n",
37+
"console_exporter = ConsoleSpanExporter()\n",
38+
"provider.add_span_processor(SimpleSpanProcessor(console_exporter))\n",
39+
"\n",
40+
"tracer = trace.get_tracer(\"labs.5.observability.agent\")\n",
41+
"print(\"Tracer ready — spans will be written to stdout.\")"
42+
]
43+
},
44+
{
45+
"cell_type": "markdown",
46+
"metadata": {},
47+
"source": [
48+
"## 2. Emit spans that follow the GenAI conventions\n",
49+
"Populate the required and recommended attributes for the agent lifecycle.\n",
50+
"- `create_agent` spans describe provisioning remote agents.\n",
51+
"- `invoke_agent` spans describe the conversational request/response.\n",
52+
"- Nested `execute_tool` spans show how tool calls can be linked into the trace."
53+
]
54+
},
55+
{
56+
"cell_type": "code",
57+
"execution_count": null,
58+
"metadata": {
59+
"tags": []
60+
},
61+
"outputs": [],
62+
"source": [
63+
"conversation_id = \"retail-session-001\"\n",
64+
"system_prompt = (\"You are Cora, a polite, factual, and helpful Zava retail assistant. \"
65+
"\"Answer with concise, markdown-friendly responses.\")\n",
66+
"customer_prompt = \"What's the best primer for bathroom cabinets?\"\n",
67+
"agent_reply = (\"🎨 Great choice! Zava GripLock Primer at $24 seals moisture. \"
68+
"\"Need brushes too?\")\n",
69+
"\n",
70+
"with tracer.start_as_current_span(\"create_agent cora-retail-agent\", kind=SpanKind.CLIENT) as span:\n",
71+
" span.set_attribute(\"gen_ai.provider.name\", \"azure.ai.inference\")\n",
72+
" span.set_attribute(\"gen_ai.operation.name\", \"create_agent\")\n",
73+
" span.set_attribute(\"gen_ai.agent.name\", \"cora-retail-agent\")\n",
74+
" span.set_attribute(\"gen_ai.agent.id\", \"agents/cora-retail-agent\")\n",
75+
" span.set_attribute(\"gen_ai.agent.description\", \"Cora retail assistant for Zava DIY customers.\")\n",
76+
" span.set_attribute(\"gen_ai.request.model\", \"gpt-4o-mini\")\n",
77+
" span.set_attribute(\"gen_ai.system_instructions\", system_prompt)\n",
78+
" span.set_attribute(\"server.address\", \"cora-agents.eastus2.inference.ai.azure.com\")\n",
79+
" span.set_attribute(\"azure.resource_provider.namespace\", \"Microsoft.CognitiveServices\")\n",
80+
"\n",
81+
"with tracer.start_as_current_span(\"invoke_agent cora-retail-agent\", kind=SpanKind.CLIENT) as span:\n",
82+
" span.set_attribute(\"gen_ai.provider.name\", \"azure.ai.inference\")\n",
83+
" span.set_attribute(\"gen_ai.operation.name\", \"invoke_agent\")\n",
84+
" span.set_attribute(\"gen_ai.agent.name\", \"cora-retail-agent\")\n",
85+
" span.set_attribute(\"gen_ai.agent.id\", \"agents/cora-retail-agent\")\n",
86+
" span.set_attribute(\"gen_ai.request.model\", \"gpt-4o-mini\")\n",
87+
" span.set_attribute(\"gen_ai.request.max_tokens\", 400)\n",
88+
" span.set_attribute(\"gen_ai.request.temperature\", 0.2)\n",
89+
" span.set_attribute(\"gen_ai.request.top_p\", 0.95)\n",
90+
" span.set_attribute(\"gen_ai.request.frequency_penalty\", 0.0)\n",
91+
" span.set_attribute(\"gen_ai.request.presence_penalty\", 0.0)\n",
92+
" span.set_attribute(\"gen_ai.response.model\", \"gpt-4o-mini\")\n",
93+
" span.set_attribute(\"gen_ai.response.finish_reasons\", [\"stop\"])\n",
94+
" span.set_attribute(\"gen_ai.response.id\", \"resp-8b5c\")\n",
95+
" span.set_attribute(\"gen_ai.usage.input_tokens\", 152)\n",
96+
" span.set_attribute(\"gen_ai.usage.output_tokens\", 38)\n",
97+
" span.set_attribute(\"gen_ai.conversation.id\", conversation_id)\n",
98+
" span.set_attribute(\"gen_ai.system_instructions\", system_prompt)\n",
99+
" span.set_attribute(\"gen_ai.input.messages\", json.dumps([\n",
100+
" {\"role\": \"system\", \"content\": system_prompt},\n",
101+
" {\"role\": \"user\", \"content\": customer_prompt}\n",
102+
" ], ensure_ascii=False))\n",
103+
" span.set_attribute(\"gen_ai.output.messages\", json.dumps([\n",
104+
" {\"role\": \"assistant\", \"content\": agent_reply}\n",
105+
" ], ensure_ascii=False))\n",
106+
" span.set_attribute(\"server.address\", \"cora-agents.eastus2.inference.ai.azure.com\")\n",
107+
" span.set_attribute(\"server.port\", 443)\n",
108+
"\n",
109+
" with tracer.start_as_current_span(\"execute_tool product_search\", kind=SpanKind.INTERNAL) as tool_span:\n",
110+
" tool_span.set_attribute(\"gen_ai.operation.name\", \"execute_tool\")\n",
111+
" tool_span.set_attribute(\"gen_ai.tool.name\", \"product_search\")\n",
112+
" tool_span.set_attribute(\"gen_ai.tool.type\", \"function\")\n",
113+
" tool_span.set_attribute(\"gen_ai.tool.call.id\", \"call-42\")\n",
114+
" tool_span.set_attribute(\"gen_ai.tool.call.arguments\", json.dumps({\n",
115+
" \"sku\": \"PRIMER-4821\",\n",
116+
" \"query\": customer_prompt\n",
117+
" }))\n",
118+
" tool_span.set_attribute(\"gen_ai.tool.call.result\", json.dumps({\n",
119+
" \"name\": \"Zava GripLock Primer\",\n",
120+
" \"price\": 24.0,\n",
121+
" \"available_stock\": 67\n",
122+
" }))\n",
123+
"\n",
124+
"print(\"Done — inspect the console output to see each span and its attributes.\")"
125+
]
126+
}
127+
],
128+
"metadata": {
129+
"kernelspec": {
130+
"display_name": "Python 3",
131+
"language": "python",
132+
"name": "python3"
133+
},
134+
"language_info": {
135+
"name": "python",
136+
"pygments_lexer": "ipython3"
137+
}
138+
},
139+
"nbformat": 4,
140+
"nbformat_minor": 5
141+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Collect Agent Spans Locally\n",
8+
"Learn how to capture the spans produced by the agent workflow so you can validate telemetry locally before exporting to Azure Monitor or another backend."
9+
]
10+
},
11+
{
12+
"cell_type": "markdown",
13+
"metadata": {},
14+
"source": [
15+
"## 1. Configure an in-memory exporter\n",
16+
"This setup mirrors what you would ship in production, but keeps traces in memory for quick inspection. Add an OTLP exporter alongside it when you are ready to emit to a collector."
17+
]
18+
},
19+
{
20+
"cell_type": "code",
21+
"execution_count": null,
22+
"metadata": {},
23+
"outputs": [],
24+
"source": [
25+
"from opentelemetry import trace\n",
26+
"from opentelemetry.sdk.resources import Resource\n",
27+
"from opentelemetry.sdk.trace import TracerProvider\n",
28+
"from opentelemetry.sdk.trace.export import InMemorySpanExporter, SimpleSpanProcessor\n",
29+
"from opentelemetry.trace import SpanKind\n",
30+
"import json\n",
31+
"\n",
32+
"resource = Resource.create({\"service.name\": \"cora-agent-demo\", \"service.namespace\": \"ignite25\"})\n",
33+
"provider = TracerProvider(resource=resource)\n",
34+
"trace.set_tracer_provider(provider)\n",
35+
"memory_exporter = InMemorySpanExporter()\n",
36+
"provider.add_span_processor(SimpleSpanProcessor(memory_exporter))\n",
37+
"\n",
38+
"tracer = trace.get_tracer(\"labs.5.observability.snapshot\")\n",
39+
"print(\"Tracer initialised with in-memory exporter.\")"
40+
]
41+
},
42+
{
43+
"cell_type": "markdown",
44+
"metadata": {},
45+
"source": [
46+
"## 2. Record a synthetic agent interaction\n",
47+
"The helper below reuses the same attribute set you saw in the previous notebook."
48+
]
49+
},
50+
{
51+
"cell_type": "code",
52+
"execution_count": null,
53+
"metadata": {},
54+
"outputs": [],
55+
"source": [
56+
"def record_agent_interaction():\n",
57+
" system_prompt = (\"You are Cora, a polite, factual, and helpful Zava retail assistant. \"
58+
"\"Answer with concise, markdown-friendly responses.\")\n",
59+
" customer_prompt = \"Do you have a satin finish paint that dries fast?\"\n",
60+
" agent_reply = (\"⚡ Absolutely! Zava SwiftCoat Satin at $32 dries in 30 minutes. \"
61+
"\"Want rollers too?\")\n",
62+
"\n",
63+
" with tracer.start_as_current_span(\"invoke_agent cora-retail-agent\", kind=SpanKind.CLIENT) as span:\n",
64+
" span.set_attribute(\"gen_ai.provider.name\", \"azure.ai.inference\")\n",
65+
" span.set_attribute(\"gen_ai.operation.name\", \"invoke_agent\")\n",
66+
" span.set_attribute(\"gen_ai.agent.name\", \"cora-retail-agent\")\n",
67+
" span.set_attribute(\"gen_ai.request.model\", \"gpt-4o-mini\")\n",
68+
" span.set_attribute(\"gen_ai.request.max_tokens\", 256)\n",
69+
" span.set_attribute(\"gen_ai.request.temperature\", 0.3)\n",
70+
" span.set_attribute(\"gen_ai.request.top_p\", 0.9)\n",
71+
" span.set_attribute(\"gen_ai.response.model\", \"gpt-4o-mini\")\n",
72+
" span.set_attribute(\"gen_ai.response.finish_reasons\", [\"stop\"])\n",
73+
" span.set_attribute(\"gen_ai.response.id\", \"resp-31f1\")\n",
74+
" span.set_attribute(\"gen_ai.usage.input_tokens\", 98)\n",
75+
" span.set_attribute(\"gen_ai.usage.output_tokens\", 41)\n",
76+
" span.set_attribute(\"gen_ai.input.messages\", json.dumps([\n",
77+
" {\"role\": \"system\", \"content\": system_prompt},\n",
78+
" {\"role\": \"user\", \"content\": customer_prompt}\n",
79+
" ], ensure_ascii=False))\n",
80+
" span.set_attribute(\"gen_ai.output.messages\", json.dumps([\n",
81+
" {\"role\": \"assistant\", \"content\": agent_reply}\n",
82+
" ], ensure_ascii=False))\n",
83+
" span.set_attribute(\"server.address\", \"cora-agents.eastus2.inference.ai.azure.com\")\n",
84+
" span.set_attribute(\"server.port\", 443)\n",
85+
"\n",
86+
" with tracer.start_as_current_span(\"execute_tool inventory_lookup\", kind=SpanKind.INTERNAL) as tool_span:\n",
87+
" tool_span.set_attribute(\"gen_ai.operation.name\", \"execute_tool\")\n",
88+
" tool_span.set_attribute(\"gen_ai.tool.name\", \"inventory_lookup\")\n",
89+
" tool_span.set_attribute(\"gen_ai.tool.type\", \"function\")\n",
90+
" tool_span.set_attribute(\"gen_ai.tool.call.id\", \"call-17\")\n",
91+
" tool_span.set_attribute(\"gen_ai.tool.call.arguments\", json.dumps({\n",
92+
" \"product_type\": \"paint\",\n",
93+
" \"finish\": \"satin\"\n",
94+
" }))\n",
95+
" tool_span.set_attribute(\"gen_ai.tool.call.result\", json.dumps({\n",
96+
" \"sku\": \"PAINT-FAST-221\",\n",
97+
" \"name\": \"Zava SwiftCoat Satin\",\n",
98+
" \"inventory\": 142\n",
99+
" }))\n",
100+
"\n",
101+
"record_agent_interaction()\n",
102+
"print(\"Interaction recorded.\")"
103+
]
104+
},
105+
{
106+
"cell_type": "markdown",
107+
"metadata": {},
108+
"source": [
109+
"## 3. Inspect the captured spans\n",
110+
"The span objects expose attributes, timing, and resource metadata. Convert them into dictionaries to confirm the payload matches the spec before shipping to a collector."
111+
]
112+
},
113+
{
114+
"cell_type": "code",
115+
"execution_count": null,
116+
"metadata": {},
117+
"outputs": [],
118+
"source": [
119+
"captured = memory_exporter.get_finished_spans()\n",
120+
"print(f\"Captured {len(captured)} spans\")\n",
121+
"\n",
122+
"def span_to_dict(span):\n",
123+
" return {\n",
124+
" \"name\": span.name,\n",
125+
" \"context\": {\"span_id\": span.context.span_id, \"trace_id\": span.context.trace_id},\n",
126+
" \"kind\": span.kind.name,\n",
127+
" \"attributes\": dict(span.attributes),\n",
128+
" \"resource\": dict(span.resource.attributes),\n",
129+
" \"status\": span.status.status_code.name\n",
130+
" }\n",
131+
"\n",
132+
"span_snapshots = [span_to_dict(span) for span in captured]\n",
133+
"for snapshot in span_snapshots:\n",
134+
" print(json.dumps(snapshot, indent=2, ensure_ascii=False))\n",
135+
"\n",
136+
"# Reset exporter so repeated notebook runs do not duplicate results\n",
137+
"memory_exporter.clear()\n",
138+
"print(\"Exporter cleared.\")"
139+
]
140+
},
141+
{
142+
"cell_type": "markdown",
143+
"metadata": {},
144+
"source": [
145+
"### Optional: Wire an OTLP exporter\n",
146+
"When you are ready to integrate with Azure Monitor, add an OTLP exporter (gRPC or HTTP) alongside the in-memory exporter.\n",
147+
"\n",
148+
"```python\n",
149+
"from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter\n",
150+
"provider.add_span_processor(BatchSpanProcessor(\n",
151+
" OTLPSpanExporter(\n",
152+
" endpoint=\"https://<region>.monitor.azure.com/v2/track\",\n",
153+
" headers={\"Authorization\": \"Bearer <token>\"}\n",
154+
" )\n",
155+
")\n",
156+
"))\n",
157+
"```\n",
158+
"\n",
159+
"Replace the endpoint and headers with the values provided by your Azure Monitor workspace."
160+
]
161+
}
162+
],
163+
"metadata": {
164+
"kernelspec": {
165+
"display_name": "Python 3",
166+
"language": "python",
167+
"name": "python3"
168+
},
169+
"language_info": {
170+
"name": "python",
171+
"pygments_lexer": "ipython3"
172+
}
173+
},
174+
"nbformat": 4,
175+
"nbformat_minor": 5
176+
}

0 commit comments

Comments
 (0)