Skip to content

Commit 09f8e64

Browse files
committed
Add OpenAI Agents content capture instrumentation
2 parents 17ba274 + 935f7dc commit 09f8e64

30 files changed

Lines changed: 4513 additions & 825 deletions

File tree

instrumentation-genai/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
| --------------- | ------------------ | --------------- | -------------- |
44
| [opentelemetry-instrumentation-google-genai](./opentelemetry-instrumentation-google-genai) | google-genai >= 1.0.0 | No | development
55
| [opentelemetry-instrumentation-langchain](./opentelemetry-instrumentation-langchain) | langchain >= 0.3.21 | No | development
6-
| [opentelemetry-instrumentation-openai-agents](./opentelemetry-instrumentation-openai-agents) | openai-agents >= 0.3.3 | No | development
6+
| [opentelemetry-instrumentation-openai-agents](./opentelemetry-instrumentation-openai-agents) | openai-agents >= 0.3.3 | Yes | development
77
| [opentelemetry-instrumentation-openai-v2](./opentelemetry-instrumentation-openai-v2) | openai >= 1.26.0 | Yes | development
88
| [opentelemetry-instrumentation-vertexai](./opentelemetry-instrumentation-vertexai) | google-cloud-aiplatform >= 1.64 | No | development
9-
| [opentelemetry-instrumentation-weaviate](./opentelemetry-instrumentation-weaviate) | weaviate-client >= 3.0.0,<5.0.0 | No | development
9+
| [opentelemetry-instrumentation-weaviate](./opentelemetry-instrumentation-weaviate) | weaviate-client >= 3.0.0,<5.0.0 | No | development

instrumentation-genai/opentelemetry-instrumentation-openai-agents/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Added
11+
12+
- Initial implementation of OpenAI Agents instrumentation
13+
([#3762](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3762))
1014
- Initial barebones package skeleton: minimal instrumentor stub, version module,
1115
and packaging metadata/entry point.
1216
([#3805](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3805))
@@ -19,3 +23,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1923
multi-agent workflows.
2024
- Extend test coverage around message and tool payload capture.
2125
- Depend on `opentelemetry-util-genai` for structured content serialization.
26+
27+
### Changed
28+
29+
### Deprecated
30+
31+
### Removed
32+
33+
### Fixed
34+
35+
### Security
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
OpenTelemetry OpenAI Agents Instrumentation
2+
===========================================
3+
4+
|pypi|
5+
6+
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-openai-agents.svg
7+
:target: https://pypi.org/project/opentelemetry-instrumentation-openai-agents/
8+
9+
This library instruments the `OpenAI Agents framework <https://openai.github.io/openai-agents-python/>`_
10+
to trace requests and log messages flowing through agents. It also captures operation duration and
11+
token usage as metrics.
12+
13+
Installation
14+
------------
15+
16+
::
17+
18+
pip install opentelemetry-instrumentation-openai-agents
19+
20+
Dependency note
21+
---------------
22+
23+
This instrumentation integrates with the OpenAI Agents framework via the
24+
`openai-agents <https://pypi.org/project/openai-agents/>`_ package. Ensure
25+
``openai-agents>=0.3.2`` is installed in environments where agent events are
26+
emitted; otherwise, the instrumentor will load but skip processor setup.
27+
28+
Usage
29+
-----
30+
31+
.. code:: python
32+
33+
from openai import OpenAI
34+
from opentelemetry.instrumentation.openai_agents import OpenAIAgentsInstrumentor
35+
36+
OpenAIAgentsInstrumentor().instrument()
37+
38+
# Your OpenAI agents code here
39+
client = OpenAI()
40+
41+
API
42+
---
43+
44+
The `opentelemetry-instrumentation-openai-agents` package provides automatic instrumentation for the OpenAI Agents framework.
45+
46+
Configuration
47+
--------------
48+
49+
This instrumentation captures content, metrics, and events by default with no additional configuration required.
50+
If you are installing and setting up this tracing library, the assumption is you want full capture.
51+
52+
References
53+
----------
54+
55+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
56+
* `OpenAI Python API Library <https://pypi.org/project/openai/>`_
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# OpenAI Agents Instrumentation Configuration Example
2+
# ===================================================
3+
4+
# Copy this file to your project and modify as needed
5+
# Set these environment variables to configure the instrumentation
6+
7+
# Enable/disable content capture (request/response bodies)
8+
# Default: false
9+
10+
# Standard OpenTelemetry configuration
11+
# Set your service name
12+
OTEL_SERVICE_NAME=my-openai-agents-service
13+
14+
# Set your service version
15+
OTEL_SERVICE_VERSION=1.0.0
16+
17+
# Set your resource attributes
18+
OTEL_RESOURCE_ATTRIBUTES=service.name=my-openai-agents-service,service.version=1.0.0
19+
20+
# Configure exporters (example for OTLP)
21+
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
22+
# OTEL_EXPORTER_OTLP_HEADERS=api-key=your-api-key
23+
24+
# Configure trace exporters
25+
# OTEL_TRACES_EXPORTER=otlp
26+
27+
# Configure metrics exporters
28+
# OTEL_METRICS_EXPORTER=otlp
29+
30+
# Configure logs exporters
31+
# OTEL_LOGS_EXPORTER=otlp
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# OpenAI Agents Instrumentation Examples
2+
3+
This directory contains examples of how to use the OpenAI Agents instrumentation.
4+
5+
## Files
6+
7+
- `quickstart.py` - Minimal OpenTelemetry setup and OpenAI chat sample.
8+
- `basic_usage.py` - Tool-calling airline demo (no Azure Monitor).
9+
- `mcp_hotel_capture_all.py` - MCP HTTP hotel finder sample (no Azure Monitor).
10+
- `enhanced_travel_planner.py` - Complex orchestration travel planner (console exporter).
11+
- `trace_collectors.py` - Showcase of Console, OTLP gRPC/HTTP, and Azure Monitor exporters.
12+
- `.env.example` - Example environment variables configuration.
13+
14+
## Basic Usage
15+
16+
1. Install the package:
17+
```bash
18+
pip install opentelemetry-instrumentation-openai-agents
19+
```
20+
21+
2. Set up your environment variables (copy `.env.example` to `.env` and modify as needed):
22+
```bash
23+
cp .env.example .env
24+
```
25+
26+
3. Run the basic example:
27+
```bash
28+
python examples/quickstart.py
29+
```
30+
31+
## Configuration
32+
33+
Content, metrics, and events are captured by default.
34+
35+
## Integration with Agent Frameworks
36+
37+
This instrumentation is designed to work with OpenAI-based agent frameworks. Common use cases include:
38+
39+
- Multi-agent systems
40+
- Conversational AI applications
41+
- Automated reasoning systems
42+
- Task-oriented agents
43+
44+
## Observability Features
45+
46+
The instrumentation provides:
47+
48+
- **Distributed tracing** - Track requests across agent interactions
49+
- **Metrics collection** - Monitor token usage, latency, and error rates
50+
- **Content capture** - Optional logging of request/response content
51+
- **Error tracking** - Automatic error status recording
52+
53+
## Security Considerations
54+
55+
When enabling content capture, be aware that:
56+
57+
- Request and response content may contain sensitive information
58+
- Content is included in telemetry data
59+
- Ensure your telemetry backend has appropriate security measures
60+
- Consider data retention policies for captured content
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
from __future__ import annotations as _annotations
2+
3+
import asyncio
4+
5+
#######################################
6+
import os
7+
import random
8+
import uuid
9+
10+
from agents import (
11+
Agent,
12+
HandoffOutputItem,
13+
ItemHelpers,
14+
MessageOutputItem,
15+
RunContextWrapper,
16+
Runner,
17+
ToolCallItem,
18+
ToolCallOutputItem,
19+
TResponseInputItem,
20+
function_tool,
21+
handoff,
22+
set_default_openai_client,
23+
trace,
24+
)
25+
from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
26+
from dotenv import load_dotenv
27+
from openai import AsyncAzureOpenAI
28+
from pydantic import BaseModel
29+
30+
from opentelemetry.instrumentation.openai_agents import (
31+
OpenAIAgentsInstrumentor,
32+
)
33+
34+
load_dotenv()
35+
36+
37+
OpenAIAgentsInstrumentor().instrument()
38+
39+
custom_client = AsyncAzureOpenAI(
40+
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
41+
azure_deployment="gpt-4.1-mini",
42+
api_version=os.environ["AZURE_OPENAI_API_VERSION"],
43+
api_key=os.environ["AZURE_OPENAI_API_KEY"],
44+
)
45+
46+
set_default_openai_client(custom_client, use_for_tracing=False)
47+
48+
#######################################
49+
50+
51+
### CONTEXT
52+
53+
54+
class AirlineAgentContext(BaseModel):
55+
passenger_name: str | None = None
56+
confirmation_number: str | None = None
57+
seat_number: str | None = None
58+
flight_number: str | None = None
59+
60+
61+
### TOOLS
62+
63+
64+
@function_tool(
65+
name_override="faq_lookup_tool",
66+
description_override="Lookup frequently asked questions.",
67+
)
68+
async def faq_lookup_tool(question: str) -> str:
69+
if "bag" in question or "baggage" in question:
70+
return (
71+
"You are allowed to bring one bag on the plane. "
72+
"It must be under 50 pounds and 22 inches x 14 inches x 9 inches."
73+
)
74+
elif "seats" in question or "plane" in question:
75+
return (
76+
"There are 120 seats on the plane. "
77+
"There are 22 business class seats and 98 economy seats. "
78+
"Exit rows are rows 4 and 16. "
79+
"Rows 5-8 are Economy Plus, with extra legroom. "
80+
)
81+
elif "wifi" in question:
82+
return "We have free wifi on the plane, join Airline-Wifi"
83+
return "I'm sorry, I don't know the answer to that question."
84+
85+
86+
@function_tool
87+
async def update_seat(
88+
context: RunContextWrapper[AirlineAgentContext],
89+
confirmation_number: str,
90+
new_seat: str,
91+
) -> str:
92+
"""
93+
Update the seat for a given confirmation number.
94+
95+
Args:
96+
confirmation_number: The confirmation number for the flight.
97+
new_seat: The new seat to update to.
98+
"""
99+
# Update the context based on the customer's input
100+
context.context.confirmation_number = confirmation_number
101+
context.context.seat_number = new_seat
102+
# Ensure that the flight number has been set by the incoming handoff
103+
assert (
104+
context.context.flight_number is not None
105+
), "Flight number is required"
106+
return f"Updated seat to {new_seat} for confirmation number {confirmation_number}"
107+
108+
109+
### HOOKS
110+
111+
112+
async def on_seat_booking_handoff(
113+
context: RunContextWrapper[AirlineAgentContext],
114+
) -> None:
115+
flight_number = f"FLT-{random.randint(100, 999)}"
116+
context.context.flight_number = flight_number
117+
118+
119+
### AGENTS
120+
121+
faq_agent = Agent[AirlineAgentContext](
122+
name="FAQ Agent",
123+
handoff_description="A helpful agent that can answer questions about the airline.",
124+
instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
125+
You are an FAQ agent. If you are speaking to a customer, you probably were transferred to from the triage agent.
126+
Use the following routine to support the customer.
127+
# Routine
128+
1. Identify the last question asked by the customer.
129+
2. Use the faq lookup tool to answer the question. Do not rely on your own knowledge.
130+
3. If you cannot answer the question, transfer back to the triage agent.""",
131+
tools=[faq_lookup_tool],
132+
)
133+
134+
seat_booking_agent = Agent[AirlineAgentContext](
135+
name="Seat Booking Agent",
136+
handoff_description="A helpful agent that can update a seat on a flight.",
137+
instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
138+
You are a seat booking agent. If you are speaking to a customer, you probably were transferred to from the triage agent.
139+
Use the following routine to support the customer.
140+
# Routine
141+
1. Ask for their confirmation number.
142+
2. Ask the customer what their desired seat number is.
143+
3. Use the update seat tool to update the seat on the flight.
144+
If the customer asks a question that is not related to the routine, transfer back to the triage agent. """,
145+
tools=[update_seat],
146+
)
147+
148+
triage_agent = Agent[AirlineAgentContext](
149+
name="Triage Agent",
150+
handoff_description="A triage agent that can delegate a customer's request to the appropriate agent.",
151+
instructions=(
152+
f"{RECOMMENDED_PROMPT_PREFIX} "
153+
"You are a helpful triaging agent. You can use your tools to delegate questions to other appropriate agents."
154+
),
155+
handoffs=[
156+
faq_agent,
157+
handoff(agent=seat_booking_agent, on_handoff=on_seat_booking_handoff),
158+
],
159+
)
160+
161+
faq_agent.handoffs.append(triage_agent)
162+
seat_booking_agent.handoffs.append(triage_agent)
163+
164+
165+
### RUN
166+
167+
168+
async def main():
169+
current_agent: Agent[AirlineAgentContext] = triage_agent
170+
input_items: list[TResponseInputItem] = []
171+
context = AirlineAgentContext()
172+
173+
# Normally, each input from the user would be an API request to your app, and you can wrap the request in a trace()
174+
# Here, we'll just use a random UUID for the conversation ID
175+
conversation_id = uuid.uuid4().hex[:16]
176+
177+
while True:
178+
user_input = input("Enter your message: ")
179+
with trace("Customer service", group_id=conversation_id):
180+
input_items.append({"content": user_input, "role": "user"})
181+
result = await Runner.run(
182+
current_agent, input_items, context=context
183+
)
184+
185+
for new_item in result.new_items:
186+
agent_name = new_item.agent.name
187+
if isinstance(new_item, MessageOutputItem):
188+
print(
189+
f"{agent_name}: {ItemHelpers.text_message_output(new_item)}"
190+
)
191+
elif isinstance(new_item, HandoffOutputItem):
192+
print(
193+
f"Handed off from {new_item.source_agent.name} to {new_item.target_agent.name}"
194+
)
195+
elif isinstance(new_item, ToolCallItem):
196+
print(f"{agent_name}: Calling a tool")
197+
elif isinstance(new_item, ToolCallOutputItem):
198+
print(f"{agent_name}: Tool call output: {new_item.output}")
199+
else:
200+
print(
201+
f"{agent_name}: Skipping item: {new_item.__class__.__name__}"
202+
)
203+
input_items = result.to_input_list()
204+
current_agent = result.last_agent
205+
206+
207+
if __name__ == "__main__":
208+
asyncio.run(main())

0 commit comments

Comments
 (0)