Skip to content

Commit 618db08

Browse files
committed
feat(platform): export traces via otel
1 parent 83d6450 commit 618db08

File tree

4 files changed

+247
-280
lines changed

4 files changed

+247
-280
lines changed

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ all = [
2525

2626
platform = [
2727
"any-llm-platform-client>=0.3.0",
28+
"opentelemetry-sdk>=1.27.0",
29+
"opentelemetry-exporter-otlp-proto-http>=1.27.0",
2830
]
2931

3032
perplexity = []

src/any_llm/providers/platform/platform.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ def _init_client(self, api_key: str | None = None, api_base: str | None = None,
7676

7777
self.platform_client = AnyLLMPlatformClient(
7878
any_llm_platform_url=ANY_LLM_PLATFORM_API_URL,
79-
client_name=self.client_name,
8079
)
8180

8281
@staticmethod
Lines changed: 57 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
from __future__ import annotations
22

3-
import base64
43
import os
5-
from typing import TYPE_CHECKING, Any
4+
from typing import TYPE_CHECKING
65

76
import httpx # noqa: TC002
87
from any_llm_platform_client import (
98
AnyLLMPlatformClient, # noqa: TC002
109
)
10+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
11+
from opentelemetry.sdk.resources import Resource
12+
from opentelemetry.sdk.trace import TracerProvider
13+
from opentelemetry.trace import SpanKind
14+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor, SpanExporter
1115

1216
from any_llm import __version__
1317

@@ -23,26 +27,20 @@
2327
ANY_LLM_PLATFORM_TRACE_URL = f"{_trace_base_url}{TRACE_API_PATH}"
2428

2529

26-
def _generate_trace_ids() -> tuple[str, str]:
27-
trace_id = base64.b64encode(os.urandom(16)).decode("ascii")
28-
span_id = base64.b64encode(os.urandom(8)).decode("ascii")
29-
return trace_id, span_id
30-
31-
32-
def _attribute_value(value: Any) -> dict[str, Any]:
33-
if isinstance(value, bool):
34-
return {"boolValue": value}
35-
if isinstance(value, int):
36-
return {"intValue": str(value)}
37-
if isinstance(value, float):
38-
return {"doubleValue": value}
39-
return {"stringValue": str(value)}
30+
def _build_span_exporter(access_token: str) -> SpanExporter:
31+
return OTLPSpanExporter(
32+
endpoint=ANY_LLM_PLATFORM_TRACE_URL,
33+
headers={
34+
"Authorization": f"Bearer {access_token}",
35+
"User-Agent": f"python-any-llm/{__version__}",
36+
},
37+
)
4038

4139

42-
def _append_attribute(attributes: list[dict[str, Any]], key: str, value: Any) -> None:
43-
if value is None:
44-
return
45-
attributes.append({"key": key, "value": _attribute_value(value)})
40+
def _build_tracer_provider(access_token: str) -> TracerProvider:
41+
provider = TracerProvider(resource=Resource.create({"service.name": "any-llm"}))
42+
provider.add_span_processor(SimpleSpanProcessor(_build_span_exporter(access_token)))
43+
return provider
4644

4745

4846
async def export_completion_trace(
@@ -72,70 +70,46 @@ async def export_completion_trace(
7270
"""
7371
access_token = await platform_client._aensure_valid_token(any_llm_key)
7472

75-
attributes: list[dict[str, Any]] = []
76-
_append_attribute(attributes, "gen_ai.provider.name", provider)
77-
_append_attribute(attributes, "gen_ai.request.model", request_model)
73+
provider_instance = _build_tracer_provider(access_token)
74+
tracer = provider_instance.get_tracer("any-llm", __version__)
75+
76+
span = tracer.start_span("llm.request", kind=SpanKind.CLIENT, start_time=start_time_ns)
77+
78+
span.set_attribute("gen_ai.provider.name", provider)
79+
span.set_attribute("gen_ai.request.model", request_model)
7880

7981
if completion is not None:
80-
_append_attribute(attributes, "gen_ai.response.model", completion.model)
82+
span.set_attribute("gen_ai.response.model", completion.model)
8183
usage = completion.usage
8284
if usage is not None:
83-
_append_attribute(attributes, "gen_ai.usage.input_tokens", usage.prompt_tokens)
84-
_append_attribute(attributes, "gen_ai.usage.output_tokens", usage.completion_tokens)
85-
86-
_append_attribute(attributes, "gen_ai.conversation.id", conversation_id)
87-
_append_attribute(attributes, "anyllm.client_name", client_name)
88-
_append_attribute(attributes, "anyllm.session_label", session_label)
89-
90-
_append_attribute(attributes, "anyllm.performance.time_to_first_token_ms", time_to_first_token_ms)
91-
_append_attribute(attributes, "anyllm.performance.time_to_last_token_ms", time_to_last_token_ms)
92-
_append_attribute(attributes, "anyllm.performance.total_duration_ms", total_duration_ms)
93-
_append_attribute(attributes, "anyllm.performance.tokens_per_second", tokens_per_second)
94-
_append_attribute(attributes, "anyllm.performance.chunks_received", chunks_received)
95-
_append_attribute(attributes, "anyllm.performance.avg_chunk_size", avg_chunk_size)
96-
_append_attribute(
97-
attributes,
98-
"anyllm.performance.inter_chunk_latency_variance_ms",
99-
inter_chunk_latency_variance_ms,
100-
)
101-
102-
trace_id, span_id = _generate_trace_ids()
103-
104-
payload = {
105-
"resourceSpans": [
106-
{
107-
"resource": {
108-
"attributes": [
109-
{"key": "service.name", "value": {"stringValue": "any-llm"}},
110-
]
111-
},
112-
"scopeSpans": [
113-
{
114-
"scope": {"name": "any-llm", "version": __version__},
115-
"spans": [
116-
{
117-
"traceId": trace_id,
118-
"spanId": span_id,
119-
"name": "llm.request",
120-
"kind": "SPAN_KIND_CLIENT",
121-
"startTimeUnixNano": str(start_time_ns),
122-
"endTimeUnixNano": str(end_time_ns),
123-
"attributes": attributes,
124-
}
125-
],
126-
}
127-
],
128-
}
129-
]
130-
}
131-
132-
response = await client.post(
133-
ANY_LLM_PLATFORM_TRACE_URL,
134-
json=payload,
135-
headers={
136-
"Authorization": f"Bearer {access_token}",
137-
"User-Agent": f"python-any-llm/{__version__}",
138-
"Content-Type": "application/json",
139-
},
140-
)
141-
response.raise_for_status()
85+
span.set_attribute("gen_ai.usage.input_tokens", usage.prompt_tokens)
86+
span.set_attribute("gen_ai.usage.output_tokens", usage.completion_tokens)
87+
88+
if conversation_id is not None:
89+
span.set_attribute("gen_ai.conversation.id", conversation_id)
90+
if client_name is not None:
91+
span.set_attribute("anyllm.client_name", client_name)
92+
if session_label is not None:
93+
span.set_attribute("anyllm.session_label", session_label)
94+
95+
if time_to_first_token_ms is not None:
96+
span.set_attribute("anyllm.performance.time_to_first_token_ms", time_to_first_token_ms)
97+
if time_to_last_token_ms is not None:
98+
span.set_attribute("anyllm.performance.time_to_last_token_ms", time_to_last_token_ms)
99+
if total_duration_ms is not None:
100+
span.set_attribute("anyllm.performance.total_duration_ms", total_duration_ms)
101+
if tokens_per_second is not None:
102+
span.set_attribute("anyllm.performance.tokens_per_second", tokens_per_second)
103+
if chunks_received is not None:
104+
span.set_attribute("anyllm.performance.chunks_received", chunks_received)
105+
if avg_chunk_size is not None:
106+
span.set_attribute("anyllm.performance.avg_chunk_size", avg_chunk_size)
107+
if inter_chunk_latency_variance_ms is not None:
108+
span.set_attribute(
109+
"anyllm.performance.inter_chunk_latency_variance_ms",
110+
inter_chunk_latency_variance_ms,
111+
)
112+
113+
span.end(end_time=end_time_ns)
114+
provider_instance.force_flush()
115+
provider_instance.shutdown()

0 commit comments

Comments
 (0)