diff --git a/examples/zero-code-examples/openai-agents/requirements.txt b/examples/zero-code-examples/openai-agents/requirements.txt new file mode 100644 index 0000000..b3bc37f --- /dev/null +++ b/examples/zero-code-examples/openai-agents/requirements.txt @@ -0,0 +1,6 @@ +openai-agents>=0.3.3 +opentelemetry-instrumentation-openai-agents-v2>=0.1.0 + +opentelemetry-sdk>=1.36.0 +opentelemetry-exporter-otlp-proto-http>=1.36.0 +python-dotenv>=1.0.0 diff --git a/examples/zero-code-examples/openai-agents/run.py b/examples/zero-code-examples/openai-agents/run.py new file mode 100644 index 0000000..6618159 --- /dev/null +++ b/examples/zero-code-examples/openai-agents/run.py @@ -0,0 +1,105 @@ +"""Run a dice-rolling OpenAI Agents SDK agent with OTLP export — no agentevals SDK. + +Demonstrates zero-code integration: any OTel-instrumented agent streams +traces to agentevals by pointing the OTLP exporter at the receiver. + +Unlike the LangChain and Strands examples, this one is fully self-contained: +the agent code lives inline with no cross-folder imports. + +Prerequisites: + 1. pip install -r requirements.txt + 2. agentevals serve --dev + 3. export OPENAI_API_KEY="your-key-here" + +Usage: + python examples/zero-code-examples/openai-agents/run.py +""" + +import os +import random + +from agents import Agent, Runner, function_tool +from dotenv import load_dotenv +from opentelemetry import trace +from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter +from opentelemetry.instrumentation.openai_agents import OpenAIAgentsInstrumentor +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor + +load_dotenv(override=True) + + +@function_tool +def roll_die(sides: int) -> int: + """Roll a die with the given number of sides and return the result.""" + return random.randint(1, sides) + + +@function_tool +def check_prime(number: int) -> bool: + """Return True if the number is prime, False otherwise.""" + if number < 2: + return False + for i in range(2, int(number**0.5) + 1): + if number % i == 0: + return False + return True + + +def main(): + if not os.getenv("OPENAI_API_KEY"): + print("OPENAI_API_KEY not set.") + return + + endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318") + print(f"OTLP endpoint: {endpoint}") + + os.environ.setdefault("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", "span_and_event") + + os.environ.setdefault( + "OTEL_RESOURCE_ATTRIBUTES", + "agentevals.eval_set_id=openai_agents_eval,agentevals.session_name=openai-agents-zero-code", + ) + + resource = Resource.create() + + tracer_provider = TracerProvider(resource=resource) + tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(), schedule_delay_millis=1000)) + trace.set_tracer_provider(tracer_provider) + + OpenAIAgentsInstrumentor().instrument() + + agent = Agent( + name="Dice Agent", + instructions="You are a helpful assistant. You can roll dice and check if numbers are prime.", + tools=[roll_die, check_prime], + ) + + test_queries = [ + "Hi! Can you help me?", + "Roll a 20-sided die for me", + "Is the number you rolled prime?", + ] + + conversation_input: list = [] + + try: + for i, query in enumerate(test_queries, 1): + print(f"\n[{i}/{len(test_queries)}] User: {query}") + + conversation_input.append({"role": "user", "content": query}) + result = Runner.run_sync(agent, conversation_input) + + agent_response = result.final_output or "" + print(f" Agent: {agent_response}") + + conversation_input = result.to_input_list() + finally: + print() + tracer_provider.force_flush() + print("All traces flushed to OTLP receiver.") + + +if __name__ == "__main__": + main() diff --git a/tests/integration/test_live_agents.py b/tests/integration/test_live_agents.py index 74c0f53..e647a22 100644 --- a/tests/integration/test_live_agents.py +++ b/tests/integration/test_live_agents.py @@ -248,6 +248,63 @@ def test_session_visible_via_api(self, live_servers): assert session_name in session_ids +@_skip_no_openai +class TestOpenAIAgentsZeroCode: + """Run the OpenAI Agents SDK zero-code OTLP example and verify session grouping.""" + + def test_session_created_with_spans(self, live_servers): + main_port, otlp_port, mgr = live_servers + session_name = "e2e-openai-agents" + + result = _run_agent( + "examples/zero-code-examples/openai-agents/run.py", + otlp_port, + session_name, + ) + assert result.returncode == 0, f"Agent failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + + wait_for_session_complete_sync(mgr, session_name, timeout=30) + session = mgr.sessions[session_name] + + assert session.is_complete + assert session.source == "otlp" + assert len(session.spans) > 0, "Expected spans from LLM calls" + + def test_invocations_extracted(self, live_servers): + main_port, otlp_port, mgr = live_servers + session_name = "e2e-openai-agents-inv" + + result = _run_agent( + "examples/zero-code-examples/openai-agents/run.py", + otlp_port, + session_name, + ) + assert result.returncode == 0, f"Agent failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + + wait_for_session_complete_sync(mgr, session_name, timeout=30) + session = mgr.sessions[session_name] + + assert len(session.invocations) > 0, "Expected extracted invocations" + + def test_session_visible_via_api(self, live_servers): + main_port, otlp_port, mgr = live_servers + session_name = "e2e-openai-agents-api" + + result = _run_agent( + "examples/zero-code-examples/openai-agents/run.py", + otlp_port, + session_name, + ) + assert result.returncode == 0 + + wait_for_session_complete_sync(mgr, session_name, timeout=30) + + resp = httpx.get(f"http://127.0.0.1:{main_port}/api/streaming/sessions") + assert resp.status_code == 200 + session_ids = [s["sessionId"] for s in resp.json()["data"]] + assert session_name in session_ids + + @_skip_no_openai class TestAgentRerun: """Verify that re-running an agent with the same session_name creates