0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
142 lines
5.6 KiB
Python
142 lines
5.6 KiB
Python
"""Tests for Langfuse/OTEL tracing helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from opentelemetry.sdk.resources import Resource
|
|
from opentelemetry.sdk.trace import TracerProvider
|
|
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
|
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
|
|
|
|
from headroom.observability import (
|
|
HeadroomTracer,
|
|
LangfuseTracingConfig,
|
|
get_langfuse_tracing_status,
|
|
reset_headroom_tracing,
|
|
set_headroom_tracer,
|
|
)
|
|
from headroom.transforms.pipeline import TransformPipeline
|
|
|
|
|
|
def test_langfuse_tracing_config_builds_trace_endpoint() -> None:
|
|
config = LangfuseTracingConfig(
|
|
enabled=True,
|
|
public_key="pk-lf-test",
|
|
secret_key="sk-lf-test",
|
|
base_url="https://cloud.langfuse.com",
|
|
service_name="headroom-proxy",
|
|
)
|
|
|
|
assert config.endpoint == "https://cloud.langfuse.com/api/public/otel/v1/traces"
|
|
assert config.headers["x-langfuse-ingestion-version"] == "4"
|
|
assert config.headers["Authorization"].startswith("Basic ")
|
|
assert "sk-lf-test" not in repr(config)
|
|
|
|
|
|
def test_transform_pipeline_emits_trace_spans() -> None:
|
|
exporter = InMemorySpanExporter()
|
|
provider = TracerProvider(resource=Resource.create({"service.name": "headroom-test"}))
|
|
provider.add_span_processor(SimpleSpanProcessor(exporter))
|
|
set_headroom_tracer(HeadroomTracer(tracer_provider=provider))
|
|
|
|
try:
|
|
pipeline = TransformPipeline(transforms=[])
|
|
messages = [{"role": "user", "content": "hello world"}]
|
|
pipeline.apply(messages, model="gpt-4o", model_limit=1024)
|
|
|
|
spans = exporter.get_finished_spans()
|
|
assert len(spans) == 1
|
|
span = spans[0]
|
|
assert span.name == "headroom.compression.pipeline"
|
|
assert span.attributes["headroom.model"] == "gpt-4o"
|
|
assert span.attributes["headroom.tokens.before"] >= 1
|
|
assert span.attributes["headroom.tokens.after"] >= 1
|
|
finally:
|
|
reset_headroom_tracing()
|
|
|
|
|
|
def test_pipeline_span_emits_gen_ai_request_model() -> None:
|
|
"""The compression-pipeline span carries the v1 OTel GenAI semconv descriptor
|
|
(gen_ai.request.model) alongside headroom.*, so it groups by the standard
|
|
schema. operation.name / provider.name / usage.* are intentionally v2."""
|
|
exporter = InMemorySpanExporter()
|
|
provider = TracerProvider(resource=Resource.create({"service.name": "headroom-test"}))
|
|
provider.add_span_processor(SimpleSpanProcessor(exporter))
|
|
set_headroom_tracer(HeadroomTracer(tracer_provider=provider))
|
|
|
|
try:
|
|
pipeline = TransformPipeline(transforms=[])
|
|
pipeline.apply(
|
|
[{"role": "user", "content": "hello world"}],
|
|
model="claude-3-5-sonnet-20241022",
|
|
model_limit=1024,
|
|
)
|
|
|
|
span = exporter.get_finished_spans()[0]
|
|
assert span.name == "headroom.compression.pipeline"
|
|
assert span.attributes["gen_ai.request.model"] == "claude-3-5-sonnet-20241022"
|
|
# v1 deliberately emits ONLY request.model — no operation/provider/usage
|
|
# (each is inaccurate at this span; see pipeline.py).
|
|
assert "gen_ai.operation.name" not in span.attributes
|
|
assert "gen_ai.provider.name" not in span.attributes
|
|
assert "gen_ai.usage.input_tokens" not in span.attributes
|
|
finally:
|
|
reset_headroom_tracing()
|
|
|
|
|
|
def test_pipeline_span_omits_request_model_when_model_missing() -> None:
|
|
"""gen_ai.request.model is omitted (not set to an empty string) when no model
|
|
is provided — never emit a blank standard attribute."""
|
|
exporter = InMemorySpanExporter()
|
|
provider = TracerProvider(resource=Resource.create({"service.name": "headroom-test"}))
|
|
provider.add_span_processor(SimpleSpanProcessor(exporter))
|
|
set_headroom_tracer(HeadroomTracer(tracer_provider=provider))
|
|
|
|
try:
|
|
pipeline = TransformPipeline(transforms=[])
|
|
pipeline.apply([{"role": "user", "content": "hi"}], model="", model_limit=1024)
|
|
span = exporter.get_finished_spans()[0]
|
|
assert span.name == "headroom.compression.pipeline"
|
|
assert "gen_ai.request.model" not in span.attributes
|
|
finally:
|
|
reset_headroom_tracing()
|
|
|
|
|
|
def test_pipeline_runs_with_metrics_disabled() -> None:
|
|
"""record_metrics=False takes the nullcontext (no-span) path: the run still
|
|
returns a valid result and emits no spans (guards that building span_attributes
|
|
with the gen_ai key never breaks the non-recording path)."""
|
|
exporter = InMemorySpanExporter()
|
|
provider = TracerProvider(resource=Resource.create({"service.name": "headroom-test"}))
|
|
provider.add_span_processor(SimpleSpanProcessor(exporter))
|
|
set_headroom_tracer(HeadroomTracer(tracer_provider=provider))
|
|
|
|
try:
|
|
pipeline = TransformPipeline(transforms=[])
|
|
result = pipeline.apply(
|
|
[{"role": "user", "content": "hi"}],
|
|
model="gpt-4o",
|
|
model_limit=1024,
|
|
record_metrics=False,
|
|
)
|
|
assert result.messages # pipeline produced output
|
|
assert exporter.get_finished_spans() == ()
|
|
finally:
|
|
reset_headroom_tracing()
|
|
|
|
|
|
def test_langfuse_tracing_status_defaults_to_unconfigured() -> None:
|
|
reset_headroom_tracing()
|
|
status = get_langfuse_tracing_status()
|
|
assert status["configured"] is False
|
|
assert status["enabled"] is False
|
|
|
|
|
|
def test_langfuse_tracing_requires_explicit_enable(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "pk-lf-test")
|
|
monkeypatch.setenv("LANGFUSE_SECRET_KEY", "sk-lf-test")
|
|
|
|
config = LangfuseTracingConfig.from_env(default_service_name="headroom-proxy")
|
|
|
|
assert config.enabled is False
|