Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ LoongSuite Python Agent 同时也是上游 [OTel Python Agent](https://github.co
| [LiteLLM](https://github.com/BerriAI/litellm) | [GUIDE](instrumentation-loongsuite/loongsuite-instrumentation-litellm/README.md) | [PyPI](https://pypi.org/project/loongsuite-instrumentation-litellm/) |
| [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) | [GUIDE](instrumentation-loongsuite/loongsuite-instrumentation-mcp/README.md) | in dev |
| [Mem0](https://github.com/mem0ai/mem0) | [GUIDE](instrumentation-loongsuite/loongsuite-instrumentation-mem0/README.md) | [PyPI](https://pypi.org/project/loongsuite-instrumentation-mem0/) |
| [OpenAI Agents SDK](https://github.com/openai/openai-agents-python) | [GUIDE](instrumentation-loongsuite/loongsuite-instrumentation-openai-agents/README.rst) | in dev |

**发行版与辅助组件:**

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Source tree: [`instrumentation-loongsuite/`](instrumentation-loongsuite).
| [LiteLLM](https://github.com/BerriAI/litellm) | [GUIDE](instrumentation-loongsuite/loongsuite-instrumentation-litellm/README.md) | [PyPI](https://pypi.org/project/loongsuite-instrumentation-litellm/) |
| [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) | [GUIDE](instrumentation-loongsuite/loongsuite-instrumentation-mcp/README.md) | in dev |
| [Mem0](https://github.com/mem0ai/mem0) | [GUIDE](instrumentation-loongsuite/loongsuite-instrumentation-mem0/README.md) | [PyPI](https://pypi.org/project/loongsuite-instrumentation-mem0/) |
| [OpenAI Agents SDK](https://github.com/openai/openai-agents-python) | [GUIDE](instrumentation-loongsuite/loongsuite-instrumentation-openai-agents/README.rst) | in dev |

**Distro and helpers:**

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Changelog
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format of the changelog file should follow the established conventions. Please refer to:
https://github.com/alibaba/loongsuite-python-agent/blob/main/instrumentation-loongsuite/loongsuite-instrumentation-dashscope/CHANGELOG.md


All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Added

- Initialize the instrumentation for OpenAI Agents SDK
([#161](https://github.com/alibaba/loongsuite-python-agent/pull/161))
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
OpenTelemetry OpenAI Agents SDK Instrumentation
Copy link
Copy Markdown
Collaborator

@Cirilla-zmh Cirilla-zmh Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add loongsuite-instrumentation-openai-agents into README.md and README-zh.md at the root path of this project.

================================================

|pypi|

.. |pypi| image:: https://badge.fury.io/py/loongsuite-instrumentation-openai-agents.svg
:target: https://pypi.org/project/loongsuite-instrumentation-openai-agents/

This library provides automatic instrumentation for the
`OpenAI Agents SDK <https://github.com/openai/openai-agents-python>`_,
capturing telemetry data for agent runs, tool executions, LLM generations,
handoffs, and guardrails.

Installation
------------

::

pip install loongsuite-instrumentation-openai-agents

Usage
-----

.. code-block:: python

from opentelemetry.instrumentation.openai_agents import OpenAIAgentsInstrumentor

OpenAIAgentsInstrumentor().instrument()

# Your OpenAI Agents SDK code works as normal
from agents import Agent, Runner

agent = Agent(name="assistant", instructions="You are helpful.")
result = Runner.run_sync(agent, "Hello!")

The instrumentation automatically captures:

- Agent invocation spans (``invoke_agent``)
- Tool execution spans (``execute_tool``)
- LLM generation spans (``chat``)
- Agent handoff spans
- Guardrail execution spans

References
----------

* `OpenTelemetry Project <https://opentelemetry.io/>`_
* `OpenAI Agents SDK documentation <https://openai.github.io/openai-agents-python/>`_
* `OpenTelemetry GenAI Semantic Conventions <https://opentelemetry.io/docs/specs/semconv/gen-ai/>`_
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "loongsuite-instrumentation-openai-agents"
dynamic = ["version"]
description = "LoongSuite OpenAI Agents SDK instrumentation"
readme = "README.rst"
license = "Apache-2.0"
requires-python = ">=3.10"
authors = [
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add your information here?

{ name = "OpenTelemetry Authors", email = "cncf-opentelemetry-contributors@lists.cncf.io" },
{ name = "minimAluminiumalism", email = "caixuesen@outlook.com" },
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"opentelemetry-api ~= 1.37",
"opentelemetry-instrumentation ~= 0.58b0",
"opentelemetry-semantic-conventions ~= 0.58b0",
"opentelemetry-util-genai",
]

[project.optional-dependencies]
instruments = [
"openai-agents >= 0.0.7",
]

[project.entry-points.opentelemetry_instrumentor]
openai_agents = "opentelemetry.instrumentation.openai_agents:OpenAIAgentsInstrumentor"

[project.urls]
Homepage = "https://github.com/alibaba/loongsuite-python-agent/tree/main/instrumentation-loongsuite/loongsuite-instrumentation-openai-agents"
Repository = "https://github.com/alibaba/loongsuite-python-agent"

[tool.hatch.version]
path = "src/opentelemetry/instrumentation/openai_agents/version.py"

[tool.hatch.build.targets.sdist]
include = [
"/src",
"/tests",
]

[tool.hatch.build.targets.wheel]
packages = ["src/opentelemetry"]

[tool.pytest.ini_options]
asyncio_mode = "auto"
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
OpenTelemetry OpenAI Agents SDK Instrumentation
================================================

This package provides automatic instrumentation for the OpenAI Agents SDK,
capturing telemetry data for agent runs, tool executions, LLM generations,
handoffs, and guardrails.

Usage
-----

Basic instrumentation::

from opentelemetry.instrumentation.openai_agents import (
OpenAIAgentsInstrumentor,
)

OpenAIAgentsInstrumentor().instrument()

from agents import Agent, Runner

agent = Agent(name="assistant", instructions="You are helpful.")
result = Runner.run_sync(agent, "Hello!")

The instrumentation leverages the SDK's built-in ``TracingProcessor``
interface to register an OpenTelemetry bridge, so all native SDK tracing
points are automatically captured without monkey-patching.
"""

import logging
import os
from typing import Any, Collection, Optional

from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.instrumentation.openai_agents.package import (
_instruments,
)
from opentelemetry.instrumentation.openai_agents.version import (
__version__,
)
from opentelemetry.util.genai.extended_handler import (
ExtendedTelemetryHandler,
)

logger = logging.getLogger(__name__)

_ENV_CAPTURE_CONTENT = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT"


def _should_capture_content() -> bool:
val = os.environ.get(_ENV_CAPTURE_CONTENT, "").strip().lower()
return val not in ("false", "0", "no", "off", "")


class OpenAIAgentsInstrumentor(BaseInstrumentor):
"""Instrumentor for the OpenAI Agents SDK.

Registers an OpenTelemetry-aware ``TracingProcessor`` with the
SDK's global trace provider so that every agent run, tool call,
LLM generation, handoff, and guardrail execution is automatically
exported as an OTel span.
"""

_handler: Optional[ExtendedTelemetryHandler] = None
_processor: Optional[Any] = None

def instrumentation_dependencies(self) -> Collection[str]:
return _instruments

def _instrument(self, **kwargs: Any) -> None:
tracer_provider = kwargs.get("tracer_provider")
meter_provider = kwargs.get("meter_provider")
logger_provider = kwargs.get("logger_provider")

OpenAIAgentsInstrumentor._handler = ExtendedTelemetryHandler(
tracer_provider=tracer_provider,
meter_provider=meter_provider,
logger_provider=logger_provider,
)

capture_content = _should_capture_content()

from agents.tracing import ( # noqa: PLC0415
add_trace_processor,
)

from opentelemetry.instrumentation.openai_agents._processor import ( # noqa: PLC0415
OTelTracingProcessor,
)

processor = OTelTracingProcessor(
handler=OpenAIAgentsInstrumentor._handler,
capture_content=capture_content,
)
OpenAIAgentsInstrumentor._processor = processor
add_trace_processor(processor)

def _uninstrument(self, **kwargs: Any) -> None:
processor = OpenAIAgentsInstrumentor._processor
if processor is None:
return

try:
from agents.tracing.setup import ( # noqa: PLC0415
get_trace_provider,
)

provider = get_trace_provider()
if hasattr(provider, "_multi_processor"):
mp = provider._multi_processor
if hasattr(mp, "_processors"):
procs = mp._processors
if processor in procs:
procs.remove(processor)
except Exception as e:
logger.debug("Failed to remove processor: %s", e)

Comment on lines +117 to +131
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_uninstrument() removes the processor by reaching into private SDK internals (provider._multi_processor._processors). This is brittle across SDK versions and may silently fail (leaving the processor registered and causing duplicate spans on re-instrumentation). If the Agents SDK provides a public removal API, prefer that; otherwise consider making the failure mode explicit (e.g., warn once) and/or add defensive checks around processor duplication on _instrument().

Suggested change
try:
from agents.tracing.setup import ( # noqa: PLC0415
get_trace_provider,
)
provider = get_trace_provider()
if hasattr(provider, "_multi_processor"):
mp = provider._multi_processor
if hasattr(mp, "_processors"):
procs = mp._processors
if processor in procs:
procs.remove(processor)
except Exception as e:
logger.debug("Failed to remove processor: %s", e)
removed = False
# Prefer a public removal API from the Agents SDK if available.
try:
from agents.tracing import ( # type: ignore[attr-defined] # noqa: PLC0415
remove_trace_processor,
)
except Exception: # ImportError or absence of public API
remove_trace_processor = None # type: ignore[assignment]
if remove_trace_processor is not None: # type: ignore[truthy-function]
try:
remove_trace_processor(processor) # type: ignore[call-arg]
removed = True
except Exception as e:
logger.warning(
"Failed to remove OpenAI Agents tracing processor via "
"public API: %s. Falling back to SDK internals.",
e,
)
# Fallback: best-effort removal via SDK internals, with explicit warnings
if not removed:
try:
from agents.tracing.setup import ( # noqa: PLC0415
get_trace_provider,
)
provider = get_trace_provider()
if hasattr(provider, "_multi_processor"):
mp = provider._multi_processor
if hasattr(mp, "_processors"):
procs = mp._processors
if processor in procs:
procs.remove(processor)
removed = True
if not removed:
logger.warning(
"OpenAI Agents tracing processor could not be "
"located in the trace provider; it may still be "
"registered, which can lead to duplicate spans on "
"re-instrumentation."
)
except Exception as e:
logger.warning(
"Failed to remove OpenAI Agents tracing processor via "
"trace provider internals; the processor may still be "
"registered, which can lead to duplicate spans on "
"re-instrumentation: %s",
e,
)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OpenAI Agents SDK does not expose a public API to remove a trace processor. add_trace_processor exists but there is no corresponding remove_trace_processor.
The private attribute fallback with hasattr guards is the only viable approach.

processor.shutdown()
OpenAIAgentsInstrumentor._processor = None
OpenAIAgentsInstrumentor._handler = None


__all__ = [
"__version__",
"OpenAIAgentsInstrumentor",
]
Loading
Loading