-
Notifications
You must be signed in to change notification settings - Fork 754
feat(openai-agents): initial instrumentation; collect OpenAI agent traces and metrics #2966
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
adharshctr
wants to merge
75
commits into
traceloop:main
Choose a base branch
from
adharshctr:openai_agent_tracing
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 21 commits
Commits
Show all changes
75 commits
Select commit
Hold shift + click to select a range
6427318
Initialized the openai agent instrumentor
adharshctr 6c5d8ac
Added the init and version file
adharshctr 16fd549
Naming update
adharshctr e1846f1
Added instrumentation for openai agents
adharshctr a54e1c9
Added the lock file
adharshctr 7b6deb0
Naming change
adharshctr aea78cc
Change naming
adharshctr 4794b48
Naming change
adharshctr f045353
Added a sample app openai agent using litellm
adharshctr 99fc730
Added the documentation
adharshctr 25886d2
Added tests
adharshctr da48804
Upgraded the versions of openai agenst
adharshctr 7213f73
Add litellm dependency
adharshctr 91e0c4b
Added tests
adharshctr 83787d9
Added tests
adharshctr dd58935
Done suggested change
adharshctr 94b756f
Done suggested change
adharshctr a61e95f
Done suggested change
adharshctr 0c4ebca
Done the suggested change
adharshctr 45d5159
Done linting
adharshctr cbfbf2a
Refactor the code
adharshctr a8558d2
Added the tests
adharshctr b8d9c82
Added tests
adharshctr 347dca8
Added the vcrpy dependency
adharshctr 33e4b41
Updated the poetry
adharshctr bcf4ff4
Updated the dependencies
adharshctr 20680b2
Used the agentic semantic conventions
adharshctr fd9035e
Used the agentic semantic conventions
adharshctr 6d52258
Done code clean up
adharshctr 73b274b
Updated the suggestion
adharshctr 853fb1f
Updated the suggestion
adharshctr 357d612
Updated the suggestion
adharshctr 0ddeaf1
Updated the suggestion
adharshctr 0ef5203
Recorded the tests
adharshctr dc4ffe7
Revert "Recorded the tests"
adharshctr fecb65c
recorded the tests
adharshctr c39998b
Added Histograms for token usage and duration
adharshctr 8ee706b
Added the cassette yaml
adharshctr ab61c64
recorded the test
adharshctr 3f2b637
recorded the test
adharshctr 99f1050
recorded the test
adharshctr 3bfa955
recorded the test
adharshctr 4b4f954
recorded the test
adharshctr 936f003
recorded the test
adharshctr de10094
Removed api key
adharshctr ce0b86f
Added tests with WatsonX
adharshctr 9d5ad24
Added tests with WatsonX
adharshctr 508f13d
Recorded the tests
adharshctr 5727e52
Recorded the tests
adharshctr 042d1da
Recorded the tests
adharshctr 16e3929
Recorded the tests
adharshctr 6d66754
Recorded the tests
adharshctr 33281ff
recorded the tests
adharshctr bcf51c9
updated the python version
adharshctr d79d133
Rerecord the tests
adharshctr 85645c5
Rerecord the tests
adharshctr a2d944c
Rerecord the tests
adharshctr 5b81d6c
Replaced the test agent with Simple openai agent
adharshctr f487eaf
Done linting
adharshctr b39c453
Recoeded the tests with fix
adharshctr 1f64ccf
Recoeded the tests with fix
adharshctr 20a125e
Recoeded the tests with fix
adharshctr 95f0dc8
Recorded the tests
adharshctr 8614d0a
Updated the instrument version from 0.0.19
adharshctr 3e7f061
Updated the module
adharshctr 5a8f2c7
Updated the poetry
adharshctr 0c5c2dd
Updated the tests
adharshctr 13e6f9a
Updated the tests
adharshctr 8c754e0
Merge branch 'main' into openai_agent_tracing
nirga 38f6f5c
Renamed the folder
adharshctr 1a28f64
Updated the documentation and removed a test
adharshctr 7a2f4fc
Recorded the tests
adharshctr de84e74
Updated the attributes with valid semantic convention
adharshctr 3bd7d93
Merge branch 'main' into openai_agent_tracing
nirga effbc61
Addressed the comments
adharshctr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
packages/opentelemetry-instrumentation-openai_agents/.flake8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[flake8] | ||
exclude = | ||
.git, | ||
__pycache__, | ||
build, | ||
dist, | ||
.tox, | ||
venv, | ||
.venv, | ||
.pytest_cache | ||
max-line-length = 120 |
1 change: 1 addition & 0 deletions
1
packages/opentelemetry-instrumentation-openai_agents/.python-version
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
3.10 |
33 changes: 33 additions & 0 deletions
33
packages/opentelemetry-instrumentation-openai_agents/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# OpenTelemetry OpenAI Agents Instrumentation | ||
|
||
<a href="https://pypi.org/project/opentelemetry-instrumentation-openai_agents/"> | ||
<img src="https://badge.fury.io/py/opentelemetry-instrumentation-openai_agents.svg"> | ||
</a> | ||
|
||
This library enables tracing of agentic workflows implemented using the [OpenAI Agents framework](https://github.com/openai/openai-agents-python), allowing visibility into agent reasoning, tool usage, and decision-making steps. | ||
|
||
## Installation | ||
|
||
```bash | ||
pip install opentelemetry-instrumentation-openai_agents | ||
``` | ||
|
||
## Example usage | ||
|
||
```python | ||
from opentelemetry.instrumentation.openai_agents import OpenAIAgentsInstrumentor | ||
|
||
OpenAIAgentsInstrumentor().instrument() | ||
``` | ||
|
||
## Privacy | ||
|
||
**By default, this instrumentation logs prompts, completions, and embeddings to span attributes**. This gives you a clear visibility into how your LLM application is working, and can make it easy to debug and evaluate the quality of the outputs. | ||
|
||
However, you may want to disable this logging for privacy reasons, as they may contain highly sensitive data from your users. You may also simply want to reduce the size of your traces. | ||
|
||
To disable logging, set the `TRACELOOP_TRACE_CONTENT` environment variable to `false`. | ||
|
||
```bash | ||
TRACELOOP_TRACE_CONTENT=false | ||
``` |
225 changes: 225 additions & 0 deletions
225
...try-instrumentation-openai_agents/opentelemetry/instrumentation/openai_agents/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
"""OpenTelemetry OpenAI Agents instrumentation""" | ||
|
||
from typing import Collection | ||
from wrapt import wrap_function_wrapper | ||
from opentelemetry.trace import SpanKind, get_tracer, Tracer | ||
from opentelemetry.trace.status import Status, StatusCode | ||
from opentelemetry.instrumentation.utils import unwrap | ||
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor | ||
from opentelemetry.instrumentation.openai_agents.version import __version__ | ||
from opentelemetry.semconv_ai import SpanAttributes | ||
from .utils import set_span_attribute | ||
|
||
|
||
_instruments = ("openai-agents >= 0.0.2",) | ||
|
||
|
||
class OpenAIAgentsInstrumentor(BaseInstrumentor): | ||
"""An instrumentor for OpenAI Agents SDK.""" | ||
|
||
def instrumentation_dependencies(self) -> Collection[str]: | ||
return _instruments | ||
|
||
def _instrument(self, **kwargs): | ||
tracer_provider = kwargs.get("tracer_provider") | ||
tracer = get_tracer(__name__, __version__, tracer_provider) | ||
|
||
wrap_function_wrapper( | ||
"agents.run", "Runner._get_new_response", _wrap_agent_run(tracer) | ||
) | ||
|
||
def _uninstrument(self, **kwargs): | ||
unwrap("agents.run.Runner", "_get_new_response") | ||
|
||
|
||
def with_tracer_wrapper(func): | ||
|
||
def _with_tracer(tracer): | ||
def wrapper(wrapped, instance, args, kwargs): | ||
return func(tracer, wrapped, instance, args, kwargs) | ||
|
||
return wrapper | ||
|
||
return _with_tracer | ||
|
||
|
||
@with_tracer_wrapper | ||
async def _wrap_agent_run(tracer: Tracer, wrapped, instance, args, kwargs): | ||
agent = args[0] | ||
run_config = args[7] if len(args) > 7 else None | ||
prompt_list = args[2] if len(args) > 2 else None | ||
agent_name = getattr(agent, "name", "agent") | ||
model_name = getattr(getattr(agent, "model", None), "model", | ||
"unknown_model") | ||
|
||
with tracer.start_as_current_span( | ||
f"openai_agents.{agent_name}", | ||
kind=SpanKind.INTERNAL, | ||
attributes={ | ||
SpanAttributes.LLM_SYSTEM: "openai", | ||
SpanAttributes.LLM_REQUEST_MODEL: model_name, | ||
}, | ||
) as span: | ||
try: | ||
extract_agent_details(agent, span) | ||
set_model_settings_span_attributes(agent, span) | ||
extract_run_config_details(run_config, span) | ||
|
||
response = await wrapped(*args, **kwargs) | ||
|
||
if isinstance(prompt_list, list): | ||
set_prompt_attributes(span, prompt_list) | ||
set_response_content_span_attribute(response, span) | ||
set_token_usage_span_attributes(response, span) | ||
|
||
span.set_status(Status(StatusCode.OK)) | ||
return response | ||
|
||
except Exception as e: | ||
span.set_status(Status(StatusCode.ERROR, str(e))) | ||
raise | ||
|
||
|
||
def extract_agent_details(test_agent, span): | ||
if test_agent is None: | ||
return | ||
agent = getattr(test_agent, "agent", test_agent) | ||
if agent is None: | ||
return | ||
|
||
attributes = {} | ||
agent_dict = vars(agent) | ||
|
||
for key, value in agent_dict.items(): | ||
if value is not None and isinstance(value, (str, int, float, bool)): | ||
attributes[f"openai.agent.{key}"] = value | ||
# Optional: handle known short lists | ||
elif isinstance(value, list) and len(value) != 0: | ||
attributes[f"openai.agent.{key}_count"] = len(value) | ||
|
||
if attributes: | ||
span.set_attributes(attributes) | ||
|
||
|
||
def set_model_settings_span_attributes(agent, span): | ||
|
||
if not hasattr(agent, "model_settings") or agent.model_settings is None: | ||
return | ||
|
||
model_settings = agent.model_settings | ||
settings_dict = vars(model_settings) | ||
|
||
key_to_span_attr = { | ||
"max_tokens": SpanAttributes.LLM_REQUEST_MAX_TOKENS, | ||
"temperature": SpanAttributes.LLM_REQUEST_TEMPERATURE, | ||
"top_p": SpanAttributes.LLM_REQUEST_TOP_P, | ||
} | ||
|
||
for key, value in settings_dict.items(): | ||
if value is not None: | ||
span_attr = key_to_span_attr.get(key, f"openai.agent.model.{key}") | ||
span.set_attribute(span_attr, value) | ||
|
||
|
||
def extract_run_config_details(run_config, span): | ||
if run_config is None: | ||
return | ||
|
||
config_dict = vars(run_config) | ||
attributes = {} | ||
|
||
for key, value in config_dict.items(): | ||
|
||
if value is not None and isinstance(value, (str, int, float, bool)): | ||
attributes[f"openai.agent.{key}"] = value | ||
elif isinstance(value, list) and len(value) != 0: | ||
attributes[f"openai.agent.{key}_count"] = len(value) | ||
|
||
if attributes: | ||
span.set_attributes(attributes) | ||
|
||
|
||
def set_prompt_attributes(span, message_history): | ||
if not message_history: | ||
return | ||
|
||
for i, msg in enumerate(message_history): | ||
if isinstance(msg, dict): | ||
role = msg.get("role", "user") | ||
content = msg.get("content", None) | ||
set_span_attribute( | ||
span, | ||
f"{SpanAttributes.LLM_PROMPTS}.{i}.role", | ||
role, | ||
) | ||
set_span_attribute( | ||
span, | ||
f"{SpanAttributes.LLM_PROMPTS}.{i}.content", | ||
content, | ||
) | ||
|
||
|
||
def set_response_content_span_attribute(response, span): | ||
if hasattr(response, "output") and isinstance(response.output, list): | ||
roles = [] | ||
types = [] | ||
contents = [] | ||
|
||
for output_message in response.output: | ||
role = getattr(output_message, "role", None) | ||
msg_type = getattr(output_message, "type", None) | ||
|
||
if role: | ||
roles.append(role) | ||
if msg_type: | ||
types.append(msg_type) | ||
|
||
if hasattr(output_message, "content") and \ | ||
isinstance(output_message.content, list): | ||
for content_item in output_message.content: | ||
if hasattr(content_item, "text"): | ||
contents.append(content_item.text) | ||
|
||
if roles: | ||
set_span_attribute( | ||
span, | ||
f"{SpanAttributes.LLM_COMPLETIONS}.roles", | ||
roles, | ||
) | ||
if types: | ||
set_span_attribute( | ||
span, | ||
f"{SpanAttributes.LLM_COMPLETIONS}.types", | ||
types, | ||
) | ||
if contents: | ||
set_span_attribute( | ||
span, f"{SpanAttributes.LLM_COMPLETIONS}.contents", contents | ||
) | ||
|
||
|
||
def set_token_usage_span_attributes(response, span): | ||
if hasattr(response, "usage"): | ||
usage = response.usage | ||
input_tokens = getattr(usage, "input_tokens", None) | ||
output_tokens = getattr(usage, "output_tokens", None) | ||
total_tokens = getattr(usage, "total_tokens", None) | ||
|
||
if input_tokens is not None: | ||
set_span_attribute( | ||
span, | ||
SpanAttributes.LLM_USAGE_PROMPT_TOKENS, | ||
input_tokens, | ||
) | ||
if output_tokens is not None: | ||
set_span_attribute( | ||
span, | ||
SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, | ||
output_tokens, | ||
) | ||
if total_tokens is not None: | ||
set_span_attribute( | ||
span, | ||
SpanAttributes.LLM_USAGE_TOTAL_TOKENS, | ||
total_tokens, | ||
) |
16 changes: 16 additions & 0 deletions
16
...emetry-instrumentation-openai_agents/opentelemetry/instrumentation/openai_agents/utils.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import os | ||
from opentelemetry import context as context_api | ||
|
||
|
||
def set_span_attribute(span, name, value): | ||
if value is not None: | ||
if value != "": | ||
span.set_attribute(name, value) | ||
return | ||
|
||
|
||
def should_send_prompts(): | ||
return ( | ||
os.getenv("TRACELOOP_TRACE_CONTENT") or "true" | ||
).lower() == "true" or \ | ||
context_api.get_value("override_enable_content_tracing") |
1 change: 1 addition & 0 deletions
1
...etry-instrumentation-openai_agents/opentelemetry/instrumentation/openai_agents/version.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__version__ = "0.40.7" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5d ago there was a new release: https://github.com/openai/openai-agents-python/releases/tag/v0.1.0
The instrumentation point
Runner._get_new_response
is available up to version0.0.18
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@thisthat They had changed the the class name to AgentRunner.Right?