diff --git a/pyproject.toml b/pyproject.toml
index 1b16eb854..388bf2331 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -112,6 +112,8 @@ dependencies = [
"mem0ai>=0.1.26,<1.0.0",
"aiofile>=3.9.0,<4.0.0",
"sseclient-py==1.8.0",
+ "arize-phoenix-otel>=0.6.1",
+ "openinference-instrumentation-langchain>=0.1.29",
]
[project.urls]
diff --git a/src/backend/base/langflow/services/tracing/arize_phoenix.py b/src/backend/base/langflow/services/tracing/arize_phoenix.py
new file mode 100644
index 000000000..6e4b03f96
--- /dev/null
+++ b/src/backend/base/langflow/services/tracing/arize_phoenix.py
@@ -0,0 +1,324 @@
+from __future__ import annotations
+
+import json
+import math
+import os
+import traceback
+import types
+from datetime import datetime, timezone
+from typing import TYPE_CHECKING, Any
+
+from langchain_core.documents import Document
+from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
+from loguru import logger
+from openinference.semconv.trace import OpenInferenceMimeTypeValues, SpanAttributes
+from opentelemetry.semconv.trace import SpanAttributes as OTELSpanAttributes
+from opentelemetry.trace import Span, Status, StatusCode
+from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
+from typing_extensions import override
+
+from langflow.schema.data import Data
+from langflow.schema.message import Message
+from langflow.services.tracing.base import BaseTracer
+
+if TYPE_CHECKING:
+ from collections.abc import Sequence
+ from uuid import UUID
+
+ from langchain.callbacks.base import BaseCallbackHandler
+ from opentelemetry.propagators.textmap import CarrierT
+ from opentelemetry.util.types import AttributeValue
+
+ from langflow.graph.vertex.base import Vertex
+ from langflow.services.tracing.schema import Log
+
+
+class ArizePhoenixTracer(BaseTracer):
+ flow_id: str
+
+ def __init__(self, trace_name: str, trace_type: str, project_name: str, trace_id: UUID):
+ """Initializes the ArizePhoenixTracer instance and sets up a root span."""
+ self.trace_name = trace_name
+ self.trace_type = trace_type
+ self.project_name = project_name
+ self.trace_id = trace_id
+ self.flow_id = trace_name.split(" - ")[-1]
+
+ try:
+ self._ready = self.setup_arize_phoenix()
+ if not self._ready:
+ return
+
+ self.tracer = self.tracer_provider.get_tracer(__name__)
+ self.propagator = TraceContextTextMapPropagator()
+ self.carrier: dict[Any, CarrierT] = {}
+
+ with self.tracer.start_as_current_span(
+ name=self.flow_id,
+ start_time=self._get_current_timestamp(),
+ ) as root_span:
+ root_span.set_attribute(SpanAttributes.OPENINFERENCE_SPAN_KIND, self.trace_type)
+ root_span.set_status(Status(StatusCode.OK))
+ self.propagator.inject(carrier=self.carrier)
+
+ self.child_spans: dict[str, Span] = {}
+
+ except Exception: # noqa: BLE001
+ logger.opt(exception=True).debug("Error setting up Arize/Phoenix tracer")
+ self._ready = False
+
+ @property
+ def ready(self):
+ """Indicates if the tracer is ready for usage."""
+ return self._ready
+
+ def setup_arize_phoenix(self) -> bool:
+ """Configures Arize/Phoenix specific environment variables and registers the tracer provider."""
+ arize_phoenix_batch = os.getenv("ARIZE_PHOENIX_BATCH", "False").lower() in (
+ "true",
+ "t",
+ "yes",
+ "y",
+ "1",
+ )
+
+ # Arize Config
+ arize_api_key = os.getenv("ARIZE_API_KEY", None)
+ arize_space_id = os.getenv("ARIZE_SPACE_ID", None)
+ arize_collector_endpoint = os.getenv("ARIZE_COLLECTOR_ENDPOINT", "https://otlp.arize.com")
+ enable_arize_tracing = bool(arize_api_key and arize_space_id)
+ arize_endpoint = f"{arize_collector_endpoint}/v1"
+ arize_headers = {
+ "api_key": arize_api_key,
+ "space_id": arize_space_id,
+ "authorization": f"Bearer {arize_api_key}",
+ }
+
+ # Phoenix Config
+ phoenix_api_key = os.getenv("PHOENIX_API_KEY", None)
+ phoenix_collector_endpoint = os.getenv("PHOENIX_COLLECTOR_ENDPOINT", "https://app.phoenix.arize.com")
+ enable_phoenix_tracing = bool(phoenix_api_key)
+ phoenix_endpoint = f"{phoenix_collector_endpoint}/v1/traces"
+ phoenix_headers = {
+ "api_key": phoenix_api_key,
+ "authorization": f"Bearer {phoenix_api_key}",
+ }
+
+ if not (enable_arize_tracing or enable_phoenix_tracing):
+ return False
+
+ try:
+ from phoenix.otel import (
+ PROJECT_NAME,
+ BatchSpanProcessor,
+ GRPCSpanExporter,
+ HTTPSpanExporter,
+ Resource,
+ SimpleSpanProcessor,
+ TracerProvider,
+ )
+
+ project_name = self.project_name or self.flow_id
+ attributes = {PROJECT_NAME: project_name, "model_id": project_name}
+ resource = Resource.create(attributes=attributes)
+ tracer_provider = TracerProvider(resource=resource, verbose=False)
+ span_processor = BatchSpanProcessor if arize_phoenix_batch else SimpleSpanProcessor
+
+ if enable_arize_tracing:
+ tracer_provider.add_span_processor(
+ span_processor=span_processor(
+ span_exporter=GRPCSpanExporter(endpoint=arize_endpoint, headers=arize_headers),
+ )
+ )
+
+ if enable_phoenix_tracing:
+ tracer_provider.add_span_processor(
+ span_processor=span_processor(
+ span_exporter=HTTPSpanExporter(
+ endpoint=phoenix_endpoint,
+ headers=phoenix_headers,
+ ),
+ )
+ )
+
+ self.tracer_provider = tracer_provider
+ except ImportError:
+ logger.exception(
+ "Could not import arize-phoenix-otel. Please install it with `pip install arize-phoenix-otel`."
+ )
+ return False
+
+ try:
+ from openinference.instrumentation.langchain import LangChainInstrumentor
+
+ LangChainInstrumentor().instrument(tracer_provider=self.tracer_provider, skip_dep_check=True)
+ except ImportError:
+ logger.exception(
+ "Could not import LangChainInstrumentor."
+ "Please install it with `pip install openinference-instrumentation-langchain`."
+ )
+ return False
+
+ return True
+
+ @override
+ def add_trace(
+ self,
+ trace_id: str,
+ trace_name: str,
+ trace_type: str,
+ inputs: dict[str, Any],
+ metadata: dict[str, Any] | None = None,
+ vertex: Vertex | None = None,
+ ) -> None:
+ """Adds a trace span, attaching inputs and metadata as attributes."""
+ if not self._ready:
+ return
+
+ span_context = self.propagator.extract(carrier=self.carrier)
+ child_span = self.tracer.start_span(
+ name=trace_name,
+ context=span_context,
+ start_time=self._get_current_timestamp(),
+ )
+
+ if trace_type == "prompt":
+ child_span.set_attribute(SpanAttributes.OPENINFERENCE_SPAN_KIND, "chain")
+ else:
+ child_span.set_attribute(SpanAttributes.OPENINFERENCE_SPAN_KIND, trace_type)
+
+ if "session_id" in inputs and len(inputs["session_id"]) > 0 and inputs["session_id"] != self.flow_id:
+ child_span.set_attribute(SpanAttributes.SESSION_ID, inputs["session_id"])
+ else:
+ child_span.set_attribute(SpanAttributes.SESSION_ID, self.flow_id)
+
+ processed_inputs = self._convert_to_arize_phoenix_types(inputs) if inputs else {}
+ if processed_inputs:
+ child_span.set_attribute(SpanAttributes.INPUT_VALUE, self._safe_json_dumps(processed_inputs))
+ child_span.set_attribute(SpanAttributes.INPUT_MIME_TYPE, OpenInferenceMimeTypeValues.JSON.value)
+
+ processed_metadata = self._convert_to_arize_phoenix_types(metadata) if metadata else {}
+ if processed_metadata:
+ for key, value in processed_metadata.items():
+ child_span.set_attribute(f"{SpanAttributes.METADATA}.{key}", value)
+
+ self.child_spans[trace_id] = child_span
+
+ @override
+ def end_trace(
+ self,
+ trace_id: str,
+ trace_name: str,
+ outputs: dict[str, Any] | None = None,
+ error: Exception | None = None,
+ logs: Sequence[Log | dict] = (),
+ ) -> None:
+ """Ends a trace span, attaching outputs, errors, and logs as attributes."""
+ if not self._ready or trace_id not in self.child_spans:
+ return
+
+ child_span = self.child_spans[trace_id]
+
+ processed_outputs = self._convert_to_arize_phoenix_types(outputs) if outputs else {}
+ if processed_outputs:
+ child_span.set_attribute(SpanAttributes.OUTPUT_VALUE, self._safe_json_dumps(processed_outputs))
+ child_span.set_attribute(SpanAttributes.OUTPUT_MIME_TYPE, OpenInferenceMimeTypeValues.JSON.value)
+
+ logs_dicts = [log if isinstance(log, dict) else log.model_dump() for log in logs]
+ processed_logs = (
+ self._convert_to_arize_phoenix_types({log.get("name"): log for log in logs_dicts}) if logs else {}
+ )
+ if processed_logs:
+ for key, value in processed_logs.items():
+ child_span.set_attribute(f"logs.{key}", value)
+
+ if error:
+ error_string = self._error_to_string(error)
+ child_span.set_status(Status(StatusCode.ERROR, error_string))
+ child_span.set_attribute("error.message", error_string)
+
+ if isinstance(error, Exception):
+ child_span.record_exception(error)
+ else:
+ exception_type = error.__class__.__name__
+ exception_message = str(error)
+ if not exception_message:
+ exception_message = repr(error)
+ attributes: dict[str, AttributeValue] = {
+ OTELSpanAttributes.EXCEPTION_TYPE: exception_type,
+ OTELSpanAttributes.EXCEPTION_MESSAGE: exception_message,
+ OTELSpanAttributes.EXCEPTION_ESCAPED: False,
+ OTELSpanAttributes.EXCEPTION_STACKTRACE: error_string,
+ }
+ child_span.add_event(name="exception", attributes=attributes)
+ else:
+ child_span.set_status(Status(StatusCode.OK))
+
+ child_span.end(end_time=self._get_current_timestamp())
+ self.child_spans.pop(trace_id)
+
+ @override
+ def end(
+ self,
+ inputs: dict[str, Any],
+ outputs: dict[str, Any],
+ error: Exception | None = None,
+ metadata: dict[str, Any] | None = None,
+ ) -> None:
+ """Ends tracing with the specified inputs, outputs, errors, and metadata as attributes."""
+ if not self._ready:
+ return
+
+ def _convert_to_arize_phoenix_types(self, io_dict: dict[str, Any]):
+ """Converts data types to Arize/Phoenix compatible formats."""
+ return {key: self._convert_to_arize_phoenix_type(value) for key, value in io_dict.items()}
+
+ def _convert_to_arize_phoenix_type(self, value):
+ """Recursively converts a value to a Arize/Phoenix compatible type."""
+ if isinstance(value, dict):
+ for key, _value in value.copy().items():
+ _value = self._convert_to_arize_phoenix_type(_value)
+ value[key] = _value
+
+ elif isinstance(value, list):
+ value = [self._convert_to_arize_phoenix_type(v) for v in value]
+
+ elif isinstance(value, Message):
+ value = value.text
+
+ elif isinstance(value, Data):
+ value = value.get_text()
+
+ elif isinstance(value, (BaseMessage | HumanMessage | SystemMessage)):
+ value = value.content
+
+ elif isinstance(value, Document):
+ value = value.page_content
+
+ elif isinstance(value, (types.GeneratorType | types.NoneType)):
+ value = str(value)
+
+ elif isinstance(value, float) and not math.isfinite(value):
+ value = "NaN"
+
+ return value
+
+ def _error_to_string(self, error: Exception | None):
+ """Converts an error to a string with traceback details."""
+ error_message = None
+ if error:
+ string_stacktrace = traceback.format_exception(error)
+ error_message = f"{error.__class__.__name__}: {error}\n\n{string_stacktrace}"
+ return error_message
+
+ def _get_current_timestamp(self) -> int:
+ """Gets the current UTC timestamp in nanoseconds."""
+ return int(datetime.now(timezone.utc).timestamp() * 1_000_000_000)
+
+ def _safe_json_dumps(self, obj: Any, **kwargs: Any) -> str:
+ """A convenience wrapper around `json.dumps` that ensures that any object can be safely encoded."""
+ return json.dumps(obj, default=str, ensure_ascii=False, **kwargs)
+
+ def get_langchain_callback(self) -> BaseCallbackHandler | None:
+ """Returns the LangChain callback handler if applicable."""
+ return None
diff --git a/src/backend/base/langflow/services/tracing/service.py b/src/backend/base/langflow/services/tracing/service.py
index bd146dbe3..90119558a 100644
--- a/src/backend/base/langflow/services/tracing/service.py
+++ b/src/backend/base/langflow/services/tracing/service.py
@@ -40,6 +40,12 @@ def _get_langfuse_tracer():
return LangFuseTracer
+def _get_arize_phoenix_tracer():
+ from langflow.services.tracing.arize_phoenix import ArizePhoenixTracer
+
+ return ArizePhoenixTracer
+
+
class TracingService(Service):
name = "tracing_service"
@@ -111,6 +117,7 @@ class TracingService(Service):
await asyncio.to_thread(self._initialize_langsmith_tracer)
await asyncio.to_thread(self._initialize_langwatch_tracer)
await asyncio.to_thread(self._initialize_langfuse_tracer)
+ await asyncio.to_thread(self._initialize_arize_phoenix_tracer)
except Exception as e: # noqa: BLE001
logger.debug(f"Error initializing tracers: {e}")
@@ -145,6 +152,16 @@ class TracingService(Service):
trace_id=self.run_id,
)
+ def _initialize_arize_phoenix_tracer(self) -> None:
+ self.project_name = os.getenv("ARIZE_PHOENIX_PROJECT", "Langflow")
+ arize_phoenix_tracer = _get_arize_phoenix_tracer()
+ self._tracers["arize_phoenix"] = arize_phoenix_tracer(
+ trace_name=self.run_name,
+ trace_type="chain",
+ project_name=self.project_name,
+ trace_id=self.run_id,
+ )
+
def set_run_name(self, name: str) -> None:
self.run_name = name
diff --git a/src/frontend/src/icons/Arize/Arize.jsx b/src/frontend/src/icons/Arize/Arize.jsx
new file mode 100644
index 000000000..04b9e626c
--- /dev/null
+++ b/src/frontend/src/icons/Arize/Arize.jsx
@@ -0,0 +1,20 @@
+const SvgArize = (props) => (
+
+);
+
+export default SvgArize;
diff --git a/src/frontend/src/icons/Arize/arize.svg b/src/frontend/src/icons/Arize/arize.svg
new file mode 100644
index 000000000..57b6ca936
--- /dev/null
+++ b/src/frontend/src/icons/Arize/arize.svg
@@ -0,0 +1,6 @@
+
+
+
diff --git a/src/frontend/src/icons/Arize/index.tsx b/src/frontend/src/icons/Arize/index.tsx
new file mode 100644
index 000000000..c47357430
--- /dev/null
+++ b/src/frontend/src/icons/Arize/index.tsx
@@ -0,0 +1,8 @@
+import React, { forwardRef } from "react";
+import SvgArize from "./Arize";
+
+export const ArizeIcon = forwardRef>(
+ (props, ref) => {
+ return ;
+ },
+);
diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts
index f6e4eb9d7..e54ed5bea 100644
--- a/src/frontend/src/utils/styleUtils.ts
+++ b/src/frontend/src/utils/styleUtils.ts
@@ -233,6 +233,7 @@ import { FaApple, FaDiscord, FaGithub } from "react-icons/fa";
import { AWSIcon } from "../icons/AWS";
import { AirbyteIcon } from "../icons/Airbyte";
import { AnthropicIcon } from "../icons/Anthropic";
+import { ArizeIcon } from "../icons/Arize";
import { AssemblyAIIcon } from "../icons/AssemblyAI";
import { AstraDBIcon } from "../icons/AstraDB";
import { AzureIcon } from "../icons/Azure";
@@ -704,6 +705,7 @@ export const nodeIconsLucide: iconsType = {
SerpSearch: SerpSearchIcon,
SearchAPI: SearchAPIIcon,
Wikipedia: WikipediaIcon,
+ Arize: ArizeIcon,
//Node Icons
model_specs: FileSliders,
diff --git a/uv.lock b/uv.lock
index af859f27d..a88935f79 100644
--- a/uv.lock
+++ b/uv.lock
@@ -215,6 +215,23 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 },
]
+[[package]]
+name = "arize-phoenix-otel"
+version = "0.6.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "openinference-semantic-conventions" },
+ { name = "opentelemetry-exporter-otlp" },
+ { name = "opentelemetry-proto" },
+ { name = "opentelemetry-sdk" },
+ { name = "opentelemetry-semantic-conventions" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b7/70/41eef869b46882aae54e9b8c757223da93160633bba82752158a77b1566a/arize_phoenix_otel-0.6.1.tar.gz", hash = "sha256:f5f7a257159f49b398dfe2ba45bb70e22766cdf61bb365c0c47dc55f329bfe95", size = 9691 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9c/f3/f2390a9d7b3ee46b28a8ccee6510a489ae0974f31b51c67d79cab4c45ef8/arize_phoenix_otel-0.6.1-py3-none-any.whl", hash = "sha256:fc00dd847a248cece5f20dffeb8fabfab72c1c38f198bcee4260618c6af1d8a5", size = 10783 },
+]
+
[[package]]
name = "asgi-lifespan"
version = "2.1.0"
@@ -3522,6 +3539,7 @@ version = "1.1.1"
source = { editable = "." }
dependencies = [
{ name = "aiofile" },
+ { name = "arize-phoenix-otel" },
{ name = "assemblyai" },
{ name = "astra-assistants", extra = ["tools"] },
{ name = "atlassian-python-api" },
@@ -3578,6 +3596,7 @@ dependencies = [
{ name = "networkx" },
{ name = "nltk" },
{ name = "numexpr" },
+ { name = "openinference-instrumentation-langchain" },
{ name = "opensearch-py" },
{ name = "pgvector" },
{ name = "pyarrow" },
@@ -3666,6 +3685,7 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "aiofile", specifier = ">=3.9.0,<4.0.0" },
+ { name = "arize-phoenix-otel", specifier = ">=0.6.1" },
{ name = "assemblyai", specifier = ">=0.33.0,<1.0.0" },
{ name = "astra-assistants", extras = ["tools"], specifier = "~=2.2.6" },
{ name = "atlassian-python-api", specifier = ">=3.41.16,<4.0.0" },
@@ -3730,6 +3750,7 @@ requires-dist = [
{ name = "networkx", specifier = ">=3.1,<4.0.0" },
{ name = "nltk", specifier = ">=3.9.1,<4.0.0" },
{ name = "numexpr", specifier = ">=2.8.6,<3.0.0" },
+ { name = "openinference-instrumentation-langchain", specifier = ">=0.1.29" },
{ name = "opensearch-py", specifier = ">=2.7.1,<3.0.0" },
{ name = "pgvector", specifier = ">=0.2.3,<1.0.0" },
{ name = "pyarrow", specifier = ">=14.0.0,<18.0.0" },
@@ -4931,6 +4952,46 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/39/1e/9dc3ccee95d0e16e54e353d3c355bb7cc506d56a2dbb0a07bc739cc48eac/openai-1.52.0-py3-none-any.whl", hash = "sha256:0c249f20920183b0a2ca4f7dba7b0452df3ecd0fa7985eb1d91ad884bc3ced9c", size = 386947 },
]
+[[package]]
+name = "openinference-instrumentation"
+version = "0.1.19"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "openinference-semantic-conventions" },
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-sdk" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f6/96/b2c98e52caf35d1f9f1e5499646498667afbf17155c7ea8e990956bca98b/openinference_instrumentation-0.1.19.tar.gz", hash = "sha256:48e91e655dfe49a320f898bb1ec9e68d919e2e222b152cd33366fc94502e9a27", size = 11801 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/dd/5c/5632de07c9ec5a18f9f16c1b50814cfee46cdefca9ff9417e859a42c38f8/openinference_instrumentation-0.1.19-py3-none-any.whl", hash = "sha256:f1a6e31c0c7668f5e3849f7d4a0156409456f35ea1cb19d7495b31f2f5b7d3e5", size = 14569 },
+]
+
+[[package]]
+name = "openinference-instrumentation-langchain"
+version = "0.1.29"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "openinference-instrumentation" },
+ { name = "openinference-semantic-conventions" },
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-instrumentation" },
+ { name = "opentelemetry-semantic-conventions" },
+ { name = "wrapt" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/10/d2/185e4dd18431f35faba90482f511328d283e71ed12737e050664f7b5efeb/openinference_instrumentation_langchain-0.1.29.tar.gz", hash = "sha256:f6c10079c91f810cff39ff24c278e41d16df0c3706b230e1859f46cad20f8e0b", size = 45868 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/eb/ba/b0855a5f9bb565e462d85ecfad63d108c4c4bc209a34850623273f7dbcef/openinference_instrumentation_langchain-0.1.29-py3-none-any.whl", hash = "sha256:9faaedb62f90ca8099b48c0b9ded97fe109a646b756f4bf7c98479b500a8446d", size = 17043 },
+]
+
+[[package]]
+name = "openinference-semantic-conventions"
+version = "0.1.12"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5f/ae/0bf5e0dcda695fd66428da05880e137c67f6fcbc494b31edec72635dc9e1/openinference_semantic_conventions-0.1.12.tar.gz", hash = "sha256:f1a378429873128892f44505a994e2b29277a8dc1be9a56b1726b1069d70dcdc", size = 9240 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f0/fb/ea6c04bb12b2e5ade3faf32b24d45ffce9a21a1468ea179de96ceec1fb3d/openinference_semantic_conventions-0.1.12-py3-none-any.whl", hash = "sha256:3489262a148ba25b3203e500dedcf8955f0c68072f7e45db3a958c0d12585e62", size = 9061 },
+]
+
[[package]]
name = "opensearch-py"
version = "2.7.1"
@@ -4960,6 +5021,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/1f/737dcdbc9fea2fa96c1b392ae47275165a7c641663fbb08a8d252968eed2/opentelemetry_api-1.27.0-py3-none-any.whl", hash = "sha256:953d5871815e7c30c81b56d910c707588000fff7a3ca1c73e6531911d53065e7", size = 63970 },
]
+[[package]]
+name = "opentelemetry-exporter-otlp"
+version = "1.27.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-exporter-otlp-proto-grpc" },
+ { name = "opentelemetry-exporter-otlp-proto-http" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/fc/d3/8156cc14e8f4573a3572ee7f30badc7aabd02961a09acc72ab5f2c789ef1/opentelemetry_exporter_otlp-1.27.0.tar.gz", hash = "sha256:4a599459e623868cc95d933c301199c2367e530f089750e115599fccd67cb2a1", size = 6166 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/59/6d/95e1fc2c8d945a734db32e87a5aa7a804f847c1657a21351df9338bd1c9c/opentelemetry_exporter_otlp-1.27.0-py3-none-any.whl", hash = "sha256:7688791cbdd951d71eb6445951d1cfbb7b6b2d7ee5948fac805d404802931145", size = 7001 },
+]
+
[[package]]
name = "opentelemetry-exporter-otlp-proto-common"
version = "1.27.0"