errorbar-tracing 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ node_modules/
2
+ dist/
3
+ __pycache__/
4
+ *.egg-info/
5
+ .venv/
6
+ .pytest_cache/
7
+
8
+ .env
9
+ *.env
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.5
2
+ Name: errorbar-tracing
3
+ Version: 0.2.0
4
+ Summary: errorbar tracing — standard OpenTelemetry, curated. One install, one line; eject anytime, your spans don't change.
5
+ Project-URL: Repository, https://github.com/omnia-v/omnia-tracing
6
+ License-Expression: Apache-2.0
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27
9
+ Requires-Dist: opentelemetry-instrumentation-anthropic>=0.30
10
+ Requires-Dist: opentelemetry-instrumentation-google-generativeai>=0.30
11
+ Requires-Dist: opentelemetry-instrumentation-langchain>=0.30
12
+ Requires-Dist: opentelemetry-instrumentation-openai>=0.30
13
+ Requires-Dist: opentelemetry-sdk>=1.27
14
+ Description-Content-Type: text/markdown
15
+
16
+ # errorbar-tracing
17
+
18
+ **Standard OpenTelemetry, curated.** One install, one line, and your LLM traffic streams to [errorbar](https://platform.omnia-voice.com) — where you grade it, calibrate a judge against your own standards, and find out **with confidence intervals** whether a cheaper model holds up on your production traffic.
19
+
20
+ This package contains **no instrumentation code of its own**. It pins and configures the ecosystem's standard OpenTelemetry instrumentations — which gives it a property no other tracing SDK offers: **you can uninstall it without losing your instrumentation.** The identical setup in vanilla OTel is documented below; your spans are byte-for-byte the same either way, and nothing proprietary ever goes on the wire.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pip install errorbar-tracing
26
+ ```
27
+
28
+ ## Use
29
+
30
+ Call once at startup, **before constructing any LLM client**:
31
+
32
+ ```python
33
+ from errorbar_tracing import setup
34
+
35
+ tracing = setup() # reads ERRORBAR_API_KEY and ERRORBAR_TAG
36
+ print(tracing.instrumented) # e.g. ['openai', 'anthropic'] — only what's installed
37
+ ```
38
+
39
+ Short-lived scripts should call `tracing.shutdown()` before exit to flush pending spans; long-running servers can skip it.
40
+
41
+ ## What gets captured
42
+
43
+ OpenAI, Anthropic, Gemini, and LangChain calls — automatically, and **only for libraries actually installed** (the `instrumented` list tells you exactly which). Successful calls, streamed calls, and **failed** calls (stored as ERROR trace structure — the most valuable signal there is, and the one status-code dashboards can't see).
44
+
45
+ Your inference does **not** move: requests keep going to your current provider; only trace telemetry flows to errorbar.
46
+
47
+ ## Configuration
48
+
49
+ | Env var | Meaning | Default |
50
+ | --- | --- | --- |
51
+ | `ERRORBAR_API_KEY` | errorbar API key — **required**; `setup()` raises rather than exporting nowhere silently | — |
52
+ | `ERRORBAR_TAG` | Population tag: one tag = one evaluation population in errorbar | unset |
53
+ | `ERRORBAR_OTLP_ENDPOINT` | OTLP/HTTP traces endpoint | `https://gateway.errorbar.ai/v1/traces` |
54
+ | `OTEL_SERVICE_NAME` | Standard OTel service name | unset |
55
+
56
+ All options can also be passed to `setup()` directly; explicit options beat env vars.
57
+
58
+ ## The eject guarantee
59
+
60
+ Remove this package and wire the same standard pieces yourself — identical spans, same endpoint, nothing lost:
61
+
62
+ ```python
63
+ import os
64
+ from opentelemetry.sdk.resources import Resource
65
+ from opentelemetry.sdk.trace import TracerProvider
66
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
67
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
68
+ from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
69
+
70
+ provider = TracerProvider(
71
+ resource=Resource.create({"service.name": "my-service", "omnia.tag": "my-agent"})
72
+ )
73
+ provider.add_span_processor(
74
+ BatchSpanProcessor(
75
+ OTLPSpanExporter(
76
+ endpoint="https://gateway.errorbar.ai/v1/traces",
77
+ headers={"Authorization": f"Bearer {os.environ['ERRORBAR_API_KEY']}"},
78
+ )
79
+ )
80
+ )
81
+ AnthropicInstrumentor().instrument(tracer_provider=provider)
82
+ # ...and the other instrumentors for whichever libraries you use
83
+ ```
84
+
85
+ Already emitting OpenTelemetry (Pydantic AI, an existing OTel setup)? You don't need this package at all — three env vars point your existing exporter at errorbar. See the [OTLP ingest reference](https://docs.omnia-voice.com/reference/otlp-ingest).
86
+
87
+ ## Privacy
88
+
89
+ Span **structure** is always stored. Model-call **content** (prompts/completions) is stored only if your errorbar workspace has request logging enabled, under your retention window, with the same scrubbing and size caps as gateway traffic.
90
+
91
+ ## Verify your setup — get a receipt, not a hope
92
+
93
+ ```bash
94
+ ERRORBAR_API_KEY=sk_... sh -c "$(curl -fsSL https://platform.omnia-voice.com/setup.sh)"
95
+ ```
96
+
97
+ Proves the key works, confirms traces are actually landing, and names your one next step. Instrumentation that fails silently is the industry default; this is the alternative.
98
+
99
+ ## Links
100
+
101
+ - [Docs](https://docs.omnia-voice.com/reference/tracing-sdk) · [Platform](https://platform.omnia-voice.com) · [Node package](https://www.npmjs.com/package/@omnia-voice/tracing)
102
+
103
+ Apache-2.0
@@ -0,0 +1,88 @@
1
+ # errorbar-tracing
2
+
3
+ **Standard OpenTelemetry, curated.** One install, one line, and your LLM traffic streams to [errorbar](https://platform.omnia-voice.com) — where you grade it, calibrate a judge against your own standards, and find out **with confidence intervals** whether a cheaper model holds up on your production traffic.
4
+
5
+ This package contains **no instrumentation code of its own**. It pins and configures the ecosystem's standard OpenTelemetry instrumentations — which gives it a property no other tracing SDK offers: **you can uninstall it without losing your instrumentation.** The identical setup in vanilla OTel is documented below; your spans are byte-for-byte the same either way, and nothing proprietary ever goes on the wire.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install errorbar-tracing
11
+ ```
12
+
13
+ ## Use
14
+
15
+ Call once at startup, **before constructing any LLM client**:
16
+
17
+ ```python
18
+ from errorbar_tracing import setup
19
+
20
+ tracing = setup() # reads ERRORBAR_API_KEY and ERRORBAR_TAG
21
+ print(tracing.instrumented) # e.g. ['openai', 'anthropic'] — only what's installed
22
+ ```
23
+
24
+ Short-lived scripts should call `tracing.shutdown()` before exit to flush pending spans; long-running servers can skip it.
25
+
26
+ ## What gets captured
27
+
28
+ OpenAI, Anthropic, Gemini, and LangChain calls — automatically, and **only for libraries actually installed** (the `instrumented` list tells you exactly which). Successful calls, streamed calls, and **failed** calls (stored as ERROR trace structure — the most valuable signal there is, and the one status-code dashboards can't see).
29
+
30
+ Your inference does **not** move: requests keep going to your current provider; only trace telemetry flows to errorbar.
31
+
32
+ ## Configuration
33
+
34
+ | Env var | Meaning | Default |
35
+ | --- | --- | --- |
36
+ | `ERRORBAR_API_KEY` | errorbar API key — **required**; `setup()` raises rather than exporting nowhere silently | — |
37
+ | `ERRORBAR_TAG` | Population tag: one tag = one evaluation population in errorbar | unset |
38
+ | `ERRORBAR_OTLP_ENDPOINT` | OTLP/HTTP traces endpoint | `https://gateway.errorbar.ai/v1/traces` |
39
+ | `OTEL_SERVICE_NAME` | Standard OTel service name | unset |
40
+
41
+ All options can also be passed to `setup()` directly; explicit options beat env vars.
42
+
43
+ ## The eject guarantee
44
+
45
+ Remove this package and wire the same standard pieces yourself — identical spans, same endpoint, nothing lost:
46
+
47
+ ```python
48
+ import os
49
+ from opentelemetry.sdk.resources import Resource
50
+ from opentelemetry.sdk.trace import TracerProvider
51
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
52
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
53
+ from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
54
+
55
+ provider = TracerProvider(
56
+ resource=Resource.create({"service.name": "my-service", "omnia.tag": "my-agent"})
57
+ )
58
+ provider.add_span_processor(
59
+ BatchSpanProcessor(
60
+ OTLPSpanExporter(
61
+ endpoint="https://gateway.errorbar.ai/v1/traces",
62
+ headers={"Authorization": f"Bearer {os.environ['ERRORBAR_API_KEY']}"},
63
+ )
64
+ )
65
+ )
66
+ AnthropicInstrumentor().instrument(tracer_provider=provider)
67
+ # ...and the other instrumentors for whichever libraries you use
68
+ ```
69
+
70
+ Already emitting OpenTelemetry (Pydantic AI, an existing OTel setup)? You don't need this package at all — three env vars point your existing exporter at errorbar. See the [OTLP ingest reference](https://docs.omnia-voice.com/reference/otlp-ingest).
71
+
72
+ ## Privacy
73
+
74
+ Span **structure** is always stored. Model-call **content** (prompts/completions) is stored only if your errorbar workspace has request logging enabled, under your retention window, with the same scrubbing and size caps as gateway traffic.
75
+
76
+ ## Verify your setup — get a receipt, not a hope
77
+
78
+ ```bash
79
+ ERRORBAR_API_KEY=sk_... sh -c "$(curl -fsSL https://platform.omnia-voice.com/setup.sh)"
80
+ ```
81
+
82
+ Proves the key works, confirms traces are actually landing, and names your one next step. Instrumentation that fails silently is the industry default; this is the alternative.
83
+
84
+ ## Links
85
+
86
+ - [Docs](https://docs.omnia-voice.com/reference/tracing-sdk) · [Platform](https://platform.omnia-voice.com) · [Node package](https://www.npmjs.com/package/@omnia-voice/tracing)
87
+
88
+ Apache-2.0
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "errorbar-tracing"
7
+ version = "0.2.0"
8
+ description = "errorbar tracing — standard OpenTelemetry, curated. One install, one line; eject anytime, your spans don't change."
9
+ license = "Apache-2.0"
10
+ readme = "README.md"
11
+ requires-python = ">=3.10"
12
+ dependencies = [
13
+ "opentelemetry-sdk>=1.27",
14
+ "opentelemetry-exporter-otlp-proto-http>=1.27",
15
+ "opentelemetry-instrumentation-openai>=0.30",
16
+ "opentelemetry-instrumentation-anthropic>=0.30",
17
+ "opentelemetry-instrumentation-google-generativeai>=0.30",
18
+ "opentelemetry-instrumentation-langchain>=0.30",
19
+ ]
20
+
21
+ [project.urls]
22
+ Repository = "https://github.com/omnia-v/omnia-tracing"
23
+
24
+ [dependency-groups]
25
+ dev = ["pytest>=8"]
26
+
27
+ [tool.hatch.build.targets.sdist]
28
+ # Explicit allowlist: hatchling's default packs the project DIR, which once
29
+ # shipped a local .env inside a published sdist. Never again — list what
30
+ # ships; everything else stays home.
31
+ include = ["src/errorbar_tracing", "tests", "README.md", "pyproject.toml"]
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/errorbar_tracing"]
@@ -0,0 +1,128 @@
1
+ """errorbar tracing — standard OpenTelemetry, curated.
2
+
3
+ One install, one line; eject anytime, your spans don't change. This package
4
+ contains NO instrumentation code of its own: it pins and configures standard,
5
+ ecosystem-maintained OpenTelemetry pieces. The identical setup without this
6
+ package is documented in docs/eject.md.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ from dataclasses import dataclass, field
13
+
14
+ DEFAULT_ENDPOINT = "https://gateway.errorbar.ai/v1/traces"
15
+ TAG_ATTRIBUTE = "omnia.tag"
16
+
17
+ __all__ = ["setup", "resolve_config", "ResolvedConfig", "Tracing", "DEFAULT_ENDPOINT", "TAG_ATTRIBUTE"]
18
+
19
+
20
+ @dataclass
21
+ class ResolvedConfig:
22
+ endpoint: str
23
+ headers: dict[str, str]
24
+ service_name: str | None
25
+ resource_attributes: dict[str, str] = field(default_factory=dict)
26
+
27
+
28
+ def resolve_config(
29
+ api_key: str | None = None,
30
+ tag: str | None = None,
31
+ service_name: str | None = None,
32
+ endpoint: str | None = None,
33
+ env: dict[str, str] | None = None,
34
+ ) -> ResolvedConfig:
35
+ """Pure configuration assembly — unit-testable without starting a pipeline."""
36
+ e = os.environ if env is None else env
37
+ key = api_key or e.get("ERRORBAR_API_KEY") or e.get("OMNIA_API_KEY")
38
+ if not key:
39
+ raise ValueError(
40
+ "errorbar-tracing: no API key. Pass setup(api_key=...) or set ERRORBAR_API_KEY (OMNIA_API_KEY still works). "
41
+ "Refusing to start a tracer that exports nowhere."
42
+ )
43
+ resource_attributes: dict[str, str] = {}
44
+ resolved_tag = tag or e.get("ERRORBAR_TAG") or e.get("OMNIA_TAG")
45
+ if resolved_tag:
46
+ resource_attributes[TAG_ATTRIBUTE] = resolved_tag
47
+ return ResolvedConfig(
48
+ endpoint=endpoint
49
+ or e.get("ERRORBAR_OTLP_ENDPOINT")
50
+ or e.get("OMNIA_OTLP_ENDPOINT")
51
+ or DEFAULT_ENDPOINT,
52
+ headers={"Authorization": f"Bearer {key}"},
53
+ service_name=service_name or e.get("OTEL_SERVICE_NAME"),
54
+ resource_attributes=resource_attributes,
55
+ )
56
+
57
+
58
+ class Tracing:
59
+ """Handle returned by setup(): shutdown() flushes and stops.
60
+
61
+ `instrumented` names the libraries actually being traced — the ones both
62
+ installed in this environment and successfully instrumented."""
63
+
64
+ def __init__(self, provider, instrumented: list[str]) -> None:
65
+ self._provider = provider
66
+ self.instrumented = instrumented
67
+
68
+ def shutdown(self) -> None:
69
+ self._provider.shutdown()
70
+
71
+
72
+ def setup(
73
+ api_key: str | None = None,
74
+ tag: str | None = None,
75
+ service_name: str | None = None,
76
+ endpoint: str | None = None,
77
+ ) -> Tracing:
78
+ """Start standard OpenTelemetry tracing, exporting to errorbar.
79
+
80
+ Call ONCE, at startup, before constructing LLM clients. Instruments
81
+ OpenAI, Anthropic, Gemini and LangChain via the ecosystem's standard
82
+ instrumentation packages.
83
+ """
84
+ config = resolve_config(api_key, tag, service_name, endpoint)
85
+
86
+ # Imports live here, not module top: `import errorbar_tracing` must stay
87
+ # side-effect free so resolve_config is usable (and testable) alone.
88
+ from opentelemetry.sdk.resources import Resource
89
+ from opentelemetry.sdk.trace import TracerProvider
90
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
91
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
92
+
93
+ attrs: dict[str, str] = dict(config.resource_attributes)
94
+ if config.service_name:
95
+ attrs["service.name"] = config.service_name
96
+ provider = TracerProvider(resource=Resource.create(attrs))
97
+ provider.add_span_processor(
98
+ BatchSpanProcessor(
99
+ OTLPSpanExporter(endpoint=config.endpoint, headers=config.headers)
100
+ )
101
+ )
102
+
103
+ # Each instrumentation package imports its TARGET library at import time,
104
+ # so instrument only what this environment actually has — an
105
+ # Anthropic-only app must not be forced to install `openai`.
106
+ candidates = (
107
+ ("openai", "opentelemetry.instrumentation.openai", "OpenAIInstrumentor"),
108
+ ("anthropic", "opentelemetry.instrumentation.anthropic", "AnthropicInstrumentor"),
109
+ (
110
+ "google-generativeai",
111
+ "opentelemetry.instrumentation.google_generativeai",
112
+ "GoogleGenerativeAiInstrumentor",
113
+ ),
114
+ ("langchain", "opentelemetry.instrumentation.langchain", "LangchainInstrumentor"),
115
+ )
116
+ instrumented: list[str] = []
117
+ import importlib
118
+
119
+ for name, module_path, class_name in candidates:
120
+ try:
121
+ instrumentor = getattr(importlib.import_module(module_path), class_name)()
122
+ except ImportError:
123
+ continue # target library not installed — nothing to trace
124
+ if not instrumentor.is_instrumented_by_opentelemetry:
125
+ instrumentor.instrument(tracer_provider=provider)
126
+ instrumented.append(name)
127
+
128
+ return Tracing(provider, instrumented)
@@ -0,0 +1,60 @@
1
+ import pytest
2
+
3
+ from errorbar_tracing import DEFAULT_ENDPOINT, TAG_ATTRIBUTE, resolve_config
4
+
5
+
6
+ def test_refuses_without_api_key():
7
+ with pytest.raises(ValueError, match="ERRORBAR_API_KEY"):
8
+ resolve_config(env={})
9
+
10
+
11
+ def test_defaults_to_gateway_with_bearer():
12
+ c = resolve_config(api_key="sk_x", env={})
13
+ assert c.endpoint == DEFAULT_ENDPOINT
14
+ assert c.headers["Authorization"] == "Bearer sk_x"
15
+
16
+
17
+ def test_reads_env():
18
+ c = resolve_config(
19
+ env={
20
+ "OMNIA_API_KEY": "sk_env",
21
+ "OMNIA_TAG": "checkout-agent",
22
+ "OMNIA_OTLP_ENDPOINT": "https://other.example/v1/traces",
23
+ "OTEL_SERVICE_NAME": "svc",
24
+ }
25
+ )
26
+ assert c.headers["Authorization"] == "Bearer sk_env"
27
+ assert c.resource_attributes[TAG_ATTRIBUTE] == "checkout-agent"
28
+ assert c.endpoint == "https://other.example/v1/traces"
29
+ assert c.service_name == "svc"
30
+
31
+
32
+ def test_options_beat_env():
33
+ c = resolve_config(
34
+ api_key="sk_opt",
35
+ tag="t2",
36
+ env={"OMNIA_API_KEY": "sk_env", "OMNIA_TAG": "t1"},
37
+ )
38
+ assert c.headers["Authorization"] == "Bearer sk_opt"
39
+ assert c.resource_attributes[TAG_ATTRIBUTE] == "t2"
40
+
41
+
42
+ def test_no_tag_means_no_attribute():
43
+ c = resolve_config(api_key="k", env={})
44
+ assert c.resource_attributes == {}
45
+
46
+
47
+ def test_errorbar_env_names_take_precedence_and_omnia_still_works():
48
+ c = resolve_config(
49
+ env={
50
+ "ERRORBAR_API_KEY": "sk_new",
51
+ "OMNIA_API_KEY": "sk_old",
52
+ "ERRORBAR_TAG": "new-tag",
53
+ "ERRORBAR_OTLP_ENDPOINT": "https://new.example/v1/traces",
54
+ }
55
+ )
56
+ assert c.headers["Authorization"] == "Bearer sk_new"
57
+ assert c.resource_attributes[TAG_ATTRIBUTE] == "new-tag"
58
+ assert c.endpoint == "https://new.example/v1/traces"
59
+ legacy = resolve_config(env={"OMNIA_API_KEY": "sk_old"})
60
+ assert legacy.headers["Authorization"] == "Bearer sk_old"