|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +Agent identity tracking example |
| 4 | +
|
| 5 | +Demonstrates tracking agent identity using OTel GenAI semantic conventions |
| 6 | +(gen_ai.agent.id, gen_ai.agent.name, gen_ai.agent.version). |
| 7 | +
|
| 8 | +This is useful for multi-agent systems where you need to attribute spans |
| 9 | +to specific agents and correlate their interactions. |
| 10 | +""" |
| 11 | + |
| 12 | +import sys |
| 13 | +import os |
| 14 | + |
| 15 | +sys.path.append(os.path.dirname(os.path.dirname(__file__))) |
| 16 | + |
| 17 | +import time |
| 18 | +from opentelemetry import trace |
| 19 | +from opentelemetry.sdk.trace import TracerProvider |
| 20 | +from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter |
| 21 | +from last9_genai import ( |
| 22 | + Last9SpanProcessor, |
| 23 | + ModelPricing, |
| 24 | + agent_context, |
| 25 | + conversation_context, |
| 26 | + workflow_context, |
| 27 | +) |
| 28 | + |
| 29 | + |
| 30 | +def setup_tracing(): |
| 31 | + """Set up OpenTelemetry tracing with Last9 auto-enrichment""" |
| 32 | + provider = TracerProvider() |
| 33 | + trace.set_tracer_provider(provider) |
| 34 | + |
| 35 | + console_exporter = ConsoleSpanExporter() |
| 36 | + provider.add_span_processor(BatchSpanProcessor(console_exporter)) |
| 37 | + |
| 38 | + custom_pricing = { |
| 39 | + "gpt-4o": ModelPricing(input=2.50, output=10.0), |
| 40 | + "gpt-4o-mini": ModelPricing(input=0.15, output=0.60), |
| 41 | + } |
| 42 | + l9_processor = Last9SpanProcessor(custom_pricing=custom_pricing) |
| 43 | + provider.add_span_processor(l9_processor) |
| 44 | + |
| 45 | + return trace.get_tracer(__name__) |
| 46 | + |
| 47 | + |
| 48 | +def simulate_llm_call(tracer, model: str, prompt: str) -> dict: |
| 49 | + """Simulate an LLM API call""" |
| 50 | + with tracer.start_span("gen_ai.chat.completions") as span: |
| 51 | + time.sleep(0.05) |
| 52 | + span.set_attribute("gen_ai.request.model", model) |
| 53 | + span.set_attribute("gen_ai.operation.name", "chat") |
| 54 | + span.set_attribute("gen_ai.usage.input_tokens", len(prompt.split()) * 2) |
| 55 | + span.set_attribute("gen_ai.usage.output_tokens", 50) |
| 56 | + return {"response": f"Response to: {prompt[:40]}..."} |
| 57 | + |
| 58 | + |
| 59 | +def single_agent_example(): |
| 60 | + """Basic agent context example""" |
| 61 | + tracer = setup_tracing() |
| 62 | + |
| 63 | + print("\n--- Example 1: Single agent tracking ---\n") |
| 64 | + |
| 65 | + with agent_context(agent_id="support_bot_v2", agent_name="Support Bot", agent_version="2.0"): |
| 66 | + result = simulate_llm_call(tracer, "gpt-4o", "Help me with my order") |
| 67 | + print(f" Response: {result['response']}") |
| 68 | + |
| 69 | + print("\n Span attributes:") |
| 70 | + print(" gen_ai.agent.id = 'support_bot_v2'") |
| 71 | + print(" gen_ai.agent.name = 'Support Bot'") |
| 72 | + print(" gen_ai.agent.version = '2.0'") |
| 73 | + |
| 74 | + |
| 75 | +def multi_agent_routing_example(): |
| 76 | + """Multi-agent system with routing""" |
| 77 | + tracer = setup_tracing() |
| 78 | + |
| 79 | + print("\n--- Example 2: Multi-agent routing ---\n") |
| 80 | + |
| 81 | + with conversation_context(conversation_id="session_abc", user_id="user_42"): |
| 82 | + # Router agent classifies intent |
| 83 | + with agent_context(agent_id="router_v1", agent_name="Router Agent"): |
| 84 | + intent = simulate_llm_call(tracer, "gpt-4o-mini", "Classify: refund my order") |
| 85 | + print(f" Router: {intent['response']}") |
| 86 | + |
| 87 | + # Specialist agent handles the request |
| 88 | + with agent_context( |
| 89 | + agent_id="refund_agent_v3", agent_name="Refund Agent", agent_version="3.1" |
| 90 | + ): |
| 91 | + response = simulate_llm_call(tracer, "gpt-4o", "Process refund for order #12345") |
| 92 | + print(f" Refund Agent: {response['response']}") |
| 93 | + |
| 94 | + print("\n Router spans: gen_ai.agent.id='router_v1', conversation_id='session_abc'") |
| 95 | + print(" Refund spans: gen_ai.agent.id='refund_agent_v3', conversation_id='session_abc'") |
| 96 | + |
| 97 | + |
| 98 | +def agent_with_workflow_example(): |
| 99 | + """Agent context nested with workflow context""" |
| 100 | + tracer = setup_tracing() |
| 101 | + |
| 102 | + print("\n--- Example 3: Agent + workflow nesting ---\n") |
| 103 | + |
| 104 | + with conversation_context(conversation_id="session_xyz"): |
| 105 | + with agent_context(agent_id="rag_agent", agent_name="RAG Agent", agent_version="1.0"): |
| 106 | + with workflow_context(workflow_id="retrieval_pipeline", workflow_type="rag"): |
| 107 | + simulate_llm_call(tracer, "gpt-4o-mini", "Expand query: best restaurants") |
| 108 | + simulate_llm_call(tracer, "gpt-4o", "Synthesize answer from documents") |
| 109 | + print(" RAG pipeline completed") |
| 110 | + |
| 111 | + print("\n All spans have:") |
| 112 | + print(" gen_ai.conversation.id = 'session_xyz'") |
| 113 | + print(" gen_ai.agent.id = 'rag_agent'") |
| 114 | + print(" workflow.id = 'retrieval_pipeline'") |
| 115 | + |
| 116 | + |
| 117 | +if __name__ == "__main__": |
| 118 | + print("Last9 GenAI - Agent Identity Tracking (OTel Semantic Conventions)") |
| 119 | + print("=" * 70) |
| 120 | + |
| 121 | + try: |
| 122 | + single_agent_example() |
| 123 | + multi_agent_routing_example() |
| 124 | + agent_with_workflow_example() |
| 125 | + |
| 126 | + trace.get_tracer_provider().force_flush(timeout_millis=5000) |
| 127 | + |
| 128 | + print("\n" + "=" * 70) |
| 129 | + print("All agent tracking examples completed!") |
| 130 | + print("\nAttributes follow OTel GenAI semantic conventions:") |
| 131 | + print(" gen_ai.agent.id - Unique agent identifier") |
| 132 | + print(" gen_ai.agent.name - Human-readable name") |
| 133 | + print(" gen_ai.agent.version - Agent version") |
| 134 | + print("\nSee: https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/") |
| 135 | + |
| 136 | + except Exception as e: |
| 137 | + print(f"Error: {e}") |
| 138 | + import traceback |
| 139 | + |
| 140 | + traceback.print_exc() |
0 commit comments