diff --git a/litellm/integrations/otel/presets/destinations.py b/litellm/integrations/otel/presets/destinations.py index b019ec899d8..b339262d6ce 100644 --- a/litellm/integrations/otel/presets/destinations.py +++ b/litellm/integrations/otel/presets/destinations.py @@ -85,11 +85,23 @@ def _weave_destination(values: Mapping[str, str]) -> OtelDestination | None: def _generic_destination(values: Mapping[str, str]) -> OtelDestination | None: """Any OTLP backend: an explicit endpoint plus raw headers. The catch-all that - makes the registry cover self-hosted collectors / Phoenix / Honeycomb / etc.""" + makes the registry cover self-hosted collectors / Phoenix / Honeycomb / etc. + + The protocol is pinned rather than left for the router to default, because this + fallback also builds destinations for named backends whose own adapter declined the + values (an ``arize`` credential carrying only ``otel_endpoint``). Leaving it unset + there made the router apply the backend's intrinsic transport -- gRPC for Arize -- + to the plain HTTP URL the admin typed, so the destination silently delivered nothing + while still being disclosed as active. + """ endpoint = values.get("otel_endpoint") if not endpoint: return None - return OtelDestination(endpoint=endpoint, headers=_parse_header_string(values.get("otel_headers", ""))) + return OtelDestination( + endpoint=endpoint, + headers=_parse_header_string(values.get("otel_headers", "")), + protocol="otlp_http", + ) _ADAPTERS: Mapping[str, Callable[[Mapping[str, str]], OtelDestination | None]] = { diff --git a/tests/test_litellm/integrations/otel/test_presets_destinations.py b/tests/test_litellm/integrations/otel/test_presets_destinations.py index f0da4c22423..18bfdbe4eb6 100644 --- a/tests/test_litellm/integrations/otel/test_presets_destinations.py +++ b/tests/test_litellm/integrations/otel/test_presets_destinations.py @@ -250,3 +250,25 @@ def test_weave_endpoint_completed_to_otel_path(): ) assert dest is not None assert dest.endpoint == expected + + +def test_generic_fallback_pins_http_even_for_a_grpc_default_backend(monkeypatch): + """Regression: an ``arize`` credential carrying only ``otel_endpoint`` is built by the + generic passthrough, but resolved under the ``arize`` name, so the router applied + Arize's intrinsic gRPC transport to the plain HTTP URL the admin typed. Nothing was + ever delivered while ``/team/info`` kept advertising the destination as active. + """ + monkeypatch.delenv("ARIZE_PROJECT_NAME", raising=False) + dest = build_destination("arize", {"otel_endpoint": "http://collector.internal:4318/v1/traces"}) + assert dest is not None + assert dest.endpoint == "http://collector.internal:4318/v1/traces" + assert dest.protocol == "otlp_http" + + +def test_adapter_built_arize_destination_keeps_its_grpc_default(monkeypatch): + """The HTTP pin belongs to the generic passthrough alone; a credential the Arize + adapter accepts still points at Arize's own gRPC endpoint.""" + monkeypatch.delenv("ARIZE_PROJECT_NAME", raising=False) + dest = build_destination("arize", {"arize_space_id": "S", "arize_api_key": "K"}) + assert dest is not None + assert dest.protocol is None