Skip to content

Commit 03ea4f0

Browse files
Add tests for agent_context: propagation, nesting, cleanup, multi-agent
12 new tests covering: - Basic agent_id propagation to spans - All fields (id, name, version) - Nested span propagation - Context cleanup after exit - Inner context overrides outer - Sequential multi-agent (routing pattern) - Nesting with conversation_context - Nesting with workflow_context - Triple nesting (conversation + agent + workflow) - Multi-agent handoff within same conversation - No-span edge case Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ebdeda0 commit 03ea4f0

1 file changed

Lines changed: 171 additions & 0 deletions

File tree

tests/test_context.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
99

1010
from last9_genai import (
11+
agent_context,
1112
conversation_context,
1213
workflow_context,
1314
propagate_attributes,
@@ -249,6 +250,176 @@ def test_deeply_nested_contexts(self, tracer_setup):
249250
assert spans[0].attributes["workflow.id"] == "wf_level_1"
250251

251252

253+
class TestAgentContext:
254+
"""Test agent_context() context manager"""
255+
256+
def test_agent_context_basic(self, tracer_setup):
257+
"""Test basic agent_context with just agent_id"""
258+
tracer, memory_exporter = tracer_setup
259+
260+
with agent_context(agent_id="agent_123"):
261+
with tracer.start_as_current_span("test_span"):
262+
pass
263+
264+
spans = memory_exporter.get_finished_spans()
265+
assert len(spans) == 1
266+
assert spans[0].attributes[GenAIAttributes.AGENT_ID] == "agent_123"
267+
268+
def test_agent_context_with_all_fields(self, tracer_setup):
269+
"""Test agent_context with id, name, and version"""
270+
tracer, memory_exporter = tracer_setup
271+
272+
with agent_context(agent_id="bot_v2", agent_name="Support Bot", agent_version="2.0"):
273+
with tracer.start_as_current_span("test_span"):
274+
pass
275+
276+
spans = memory_exporter.get_finished_spans()
277+
assert len(spans) == 1
278+
assert spans[0].attributes[GenAIAttributes.AGENT_ID] == "bot_v2"
279+
assert spans[0].attributes[GenAIAttributes.AGENT_NAME] == "Support Bot"
280+
assert spans[0].attributes[GenAIAttributes.AGENT_VERSION] == "2.0"
281+
282+
def test_agent_context_propagates_to_nested_spans(self, tracer_setup):
283+
"""Test that agent context propagates to all nested spans"""
284+
tracer, memory_exporter = tracer_setup
285+
286+
with agent_context(agent_id="nested_agent", agent_name="Nested"):
287+
with tracer.start_as_current_span("parent"):
288+
with tracer.start_as_current_span("child"):
289+
pass
290+
291+
spans = memory_exporter.get_finished_spans()
292+
assert len(spans) == 2
293+
assert spans[0].attributes[GenAIAttributes.AGENT_ID] == "nested_agent"
294+
assert spans[1].attributes[GenAIAttributes.AGENT_ID] == "nested_agent"
295+
296+
def test_agent_context_cleanup(self, tracer_setup):
297+
"""Test that agent context is cleaned up after exit"""
298+
tracer, memory_exporter = tracer_setup
299+
300+
with agent_context(agent_id="temp_agent"):
301+
context = get_current_context()
302+
assert context["agent_id"] == "temp_agent"
303+
304+
context = get_current_context()
305+
assert "agent_id" not in context or context.get("agent_id") != "temp_agent"
306+
307+
def test_agent_context_override(self, tracer_setup):
308+
"""Test that inner agent context overrides outer"""
309+
tracer, memory_exporter = tracer_setup
310+
311+
with agent_context(agent_id="outer_agent", agent_name="Outer"):
312+
with agent_context(agent_id="inner_agent", agent_name="Inner"):
313+
with tracer.start_as_current_span("test_span"):
314+
pass
315+
316+
spans = memory_exporter.get_finished_spans()
317+
assert len(spans) == 1
318+
assert spans[0].attributes[GenAIAttributes.AGENT_ID] == "inner_agent"
319+
assert spans[0].attributes[GenAIAttributes.AGENT_NAME] == "Inner"
320+
321+
def test_multi_agent_sequential(self, tracer_setup):
322+
"""Test sequential agent contexts (multi-agent routing)"""
323+
tracer, memory_exporter = tracer_setup
324+
325+
with agent_context(agent_id="router", agent_name="Router"):
326+
with tracer.start_as_current_span("route"):
327+
pass
328+
329+
with agent_context(agent_id="handler", agent_name="Handler"):
330+
with tracer.start_as_current_span("handle"):
331+
pass
332+
333+
spans = memory_exporter.get_finished_spans()
334+
assert len(spans) == 2
335+
assert spans[0].attributes[GenAIAttributes.AGENT_ID] == "router"
336+
assert spans[1].attributes[GenAIAttributes.AGENT_ID] == "handler"
337+
338+
def test_agent_with_conversation_context(self, tracer_setup):
339+
"""Test agent nested inside conversation"""
340+
tracer, memory_exporter = tracer_setup
341+
342+
with conversation_context(conversation_id="conv_abc", user_id="user_1"):
343+
with agent_context(agent_id="agent_xyz", agent_name="Bot"):
344+
with tracer.start_as_current_span("test_span"):
345+
pass
346+
347+
spans = memory_exporter.get_finished_spans()
348+
assert len(spans) == 1
349+
assert spans[0].attributes[GenAIAttributes.CONVERSATION_ID] == "conv_abc"
350+
assert spans[0].attributes["user.id"] == "user_1"
351+
assert spans[0].attributes[GenAIAttributes.AGENT_ID] == "agent_xyz"
352+
assert spans[0].attributes[GenAIAttributes.AGENT_NAME] == "Bot"
353+
354+
def test_agent_with_workflow_context(self, tracer_setup):
355+
"""Test agent nested with workflow"""
356+
tracer, memory_exporter = tracer_setup
357+
358+
with agent_context(agent_id="rag_agent", agent_name="RAG"):
359+
with workflow_context(workflow_id="retrieval", workflow_type="rag"):
360+
with tracer.start_as_current_span("test_span"):
361+
pass
362+
363+
spans = memory_exporter.get_finished_spans()
364+
assert len(spans) == 1
365+
assert spans[0].attributes[GenAIAttributes.AGENT_ID] == "rag_agent"
366+
assert spans[0].attributes["workflow.id"] == "retrieval"
367+
assert spans[0].attributes["workflow.type"] == "rag"
368+
369+
def test_agent_conversation_workflow_triple_nesting(self, tracer_setup):
370+
"""Test all three contexts nested together"""
371+
tracer, memory_exporter = tracer_setup
372+
373+
with conversation_context(conversation_id="session_1"):
374+
with agent_context(agent_id="agent_1", agent_name="Agent", agent_version="1.0"):
375+
with workflow_context(workflow_id="wf_1"):
376+
with tracer.start_as_current_span("test_span"):
377+
pass
378+
379+
spans = memory_exporter.get_finished_spans()
380+
assert len(spans) == 1
381+
assert spans[0].attributes[GenAIAttributes.CONVERSATION_ID] == "session_1"
382+
assert spans[0].attributes[GenAIAttributes.AGENT_ID] == "agent_1"
383+
assert spans[0].attributes[GenAIAttributes.AGENT_NAME] == "Agent"
384+
assert spans[0].attributes[GenAIAttributes.AGENT_VERSION] == "1.0"
385+
assert spans[0].attributes["workflow.id"] == "wf_1"
386+
387+
def test_multi_agent_in_conversation(self, tracer_setup):
388+
"""Test multiple agents within same conversation (handoff pattern)"""
389+
tracer, memory_exporter = tracer_setup
390+
391+
with conversation_context(conversation_id="session_handoff"):
392+
with agent_context(agent_id="router_v1", agent_name="Router"):
393+
with tracer.start_as_current_span("classify"):
394+
pass
395+
396+
with agent_context(agent_id="support_v2", agent_name="Support"):
397+
with tracer.start_as_current_span("respond"):
398+
pass
399+
400+
spans = memory_exporter.get_finished_spans()
401+
assert len(spans) == 2
402+
403+
# Both have same conversation, different agents
404+
assert spans[0].attributes[GenAIAttributes.CONVERSATION_ID] == "session_handoff"
405+
assert spans[0].attributes[GenAIAttributes.AGENT_ID] == "router_v1"
406+
assert spans[0].attributes[GenAIAttributes.AGENT_NAME] == "Router"
407+
408+
assert spans[1].attributes[GenAIAttributes.CONVERSATION_ID] == "session_handoff"
409+
assert spans[1].attributes[GenAIAttributes.AGENT_ID] == "support_v2"
410+
assert spans[1].attributes[GenAIAttributes.AGENT_NAME] == "Support"
411+
412+
def test_agent_context_no_span(self, tracer_setup):
413+
"""Test agent_context works even without spans"""
414+
tracer, memory_exporter = tracer_setup
415+
416+
with agent_context(agent_id="no_span_agent"):
417+
pass
418+
419+
spans = memory_exporter.get_finished_spans()
420+
assert len(spans) == 0
421+
422+
252423
class TestPropagateAttributes:
253424
"""Test propagate_attributes() context manager"""
254425

0 commit comments

Comments
 (0)