Skip to content

build(a2a): add a2a deps and mitigate otel conflict #232

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

Merged
merged 2 commits into from
Jun 16, 2025
Merged
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
20 changes: 16 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ dependencies = [
"watchdog>=6.0.0,<7.0.0",
"opentelemetry-api>=1.30.0,<2.0.0",
"opentelemetry-sdk>=1.30.0,<2.0.0",
"opentelemetry-exporter-otlp-proto-http>=1.30.0,<2.0.0",
]

[project.urls]
Expand Down Expand Up @@ -78,13 +77,23 @@ ollama = [
openai = [
"openai>=1.68.0,<2.0.0",
]
otel = [
"opentelemetry-exporter-otlp-proto-http>=1.30.0,<2.0.0",
]
a2a = [
"a2a-sdk>=0.2.6",
"uvicorn>=0.34.2",
"httpx>=0.28.1",
"fastapi>=0.115.12",
"starlette>=0.46.2",
]

[tool.hatch.version]
# Tells Hatch to use your version control system (git) to determine the version.
source = "vcs"

[tool.hatch.envs.hatch-static-analysis]
features = ["anthropic", "litellm", "llamaapi", "ollama", "openai"]
features = ["anthropic", "litellm", "llamaapi", "ollama", "openai", "otel"]
dependencies = [
"mypy>=1.15.0,<2.0.0",
"ruff>=0.11.6,<0.12.0",
Expand All @@ -107,7 +116,7 @@ lint-fix = [
]

[tool.hatch.envs.hatch-test]
features = ["anthropic", "litellm", "llamaapi", "ollama", "openai"]
features = ["anthropic", "litellm", "llamaapi", "ollama", "openai", "otel"]
extra-dependencies = [
"moto>=5.1.0,<6.0.0",
"pytest>=8.0.0,<9.0.0",
Expand All @@ -123,8 +132,11 @@ extra-args = [

[tool.hatch.envs.dev]
dev-mode = true
features = ["dev", "docs", "anthropic", "litellm", "llamaapi", "ollama"]
features = ["dev", "docs", "anthropic", "litellm", "llamaapi", "ollama", "otel"]

[tool.hatch.envs.a2a]
dev-mode = true
features = ["dev", "docs", "anthropic", "litellm", "llamaapi", "ollama", "a2a"]


[[tool.hatch.envs.hatch-test.matrix]]
Expand Down
20 changes: 17 additions & 3 deletions src/strands/telemetry/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import opentelemetry.trace as trace_api
from opentelemetry import propagate
from opentelemetry.baggage.propagation import W3CBaggagePropagator
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.propagators.composite import CompositePropagator
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider as SDKTracerProvider
Expand All @@ -30,6 +29,19 @@

logger = logging.getLogger(__name__)

HAS_OTEL_EXPORTER_MODULE = False
OTEL_EXPORTER_MODULE_ERROR = (
"opentelemetry-exporter-otlp-proto-http not detected;"
"please install strands-agents with the optional 'otel' target"
"otel http exporting is currently DISABLED"
)
try:
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

HAS_OTEL_EXPORTER_MODULE = True
except ImportError:
pass


class JSONEncoder(json.JSONEncoder):
"""Custom JSON encoder that handles non-serializable types."""
Expand Down Expand Up @@ -181,7 +193,7 @@ def _initialize_tracer(self) -> None:
self.tracer_provider.add_span_processor(console_processor)

# Add OTLP exporter if endpoint is provided
if self.otlp_endpoint and self.tracer_provider:
if HAS_OTEL_EXPORTER_MODULE and self.otlp_endpoint and self.tracer_provider:
try:
# Ensure endpoint has the right format
endpoint = self.otlp_endpoint
Expand All @@ -206,6 +218,8 @@ def _initialize_tracer(self) -> None:
logger.info("endpoint=<%s> | OTLP exporter configured with endpoint", endpoint)
except Exception as e:
logger.exception("error=<%s> | Failed to configure OTLP exporter", e)
elif self.otlp_endpoint and self.tracer_provider:
logger.warning(OTEL_EXPORTER_MODULE_ERROR)

# Set as global tracer provider
trace_api.set_tracer_provider(self.tracer_provider)
Expand Down Expand Up @@ -294,7 +308,7 @@ def _end_span(
finally:
span.end()
# Force flush to ensure spans are exported
if self.tracer_provider and hasattr(self.tracer_provider, 'force_flush'):
if self.tracer_provider and hasattr(self.tracer_provider, "force_flush"):
try:
self.tracer_provider.force_flush()
except Exception as e:
Expand Down
4 changes: 2 additions & 2 deletions tests-integ/test_mcp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ def test_can_reuse_mcp_client():


@pytest.mark.skipif(
condition=os.environ.get("GITHUB_ACTIONS") == 'true',
reason="streamable transport is failing in GitHub actions, debugging if linux compatibility issue"
condition=os.environ.get("GITHUB_ACTIONS") == "true",
reason="streamable transport is failing in GitHub actions, debugging if linux compatibility issue",
)
def test_streamable_http_mcp_client():
server_thread = threading.Thread(
Expand Down
17 changes: 14 additions & 3 deletions tests/strands/telemetry/test_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ def mock_set_tracer_provider():

@pytest.fixture
def mock_otlp_exporter():
with mock.patch("strands.telemetry.tracer.OTLPSpanExporter") as mock_exporter:
with (
mock.patch("strands.telemetry.tracer.HAS_OTEL_EXPORTER_MODULE", True),
mock.patch("opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter") as mock_exporter,
):
yield mock_exporter


Expand Down Expand Up @@ -199,7 +202,11 @@ def test_initialize_tracer_with_otlp(
mock_resource.create.return_value = mock_resource_instance

# Initialize Tracer
Tracer(otlp_endpoint="http://test-endpoint")
with (
mock.patch("strands.telemetry.tracer.HAS_OTEL_EXPORTER_MODULE", True),
mock.patch("strands.telemetry.tracer.OTLPSpanExporter", mock_otlp_exporter),
):
Tracer(otlp_endpoint="http://test-endpoint")

# Verify the tracer provider was created with correct resource
mock_tracer_provider.assert_called_once_with(resource=mock_resource_instance)
Expand Down Expand Up @@ -508,7 +515,11 @@ def test_initialize_tracer_with_invalid_otlp_endpoint(
# This should not raise an exception, but should log an error

# Initialize Tracer
Tracer(otlp_endpoint="http://invalid-endpoint")
with (
mock.patch("strands.telemetry.tracer.HAS_OTEL_EXPORTER_MODULE", True),
mock.patch("strands.telemetry.tracer.OTLPSpanExporter", mock_otlp_exporter),
):
Tracer(otlp_endpoint="http://invalid-endpoint")

# Verify the tracer provider was created with correct resource
mock_tracer_provider.assert_called_once_with(resource=mock_resource_instance)
Expand Down