Metadata-Version: 2.4
Name: kubit-otel
Version: 0.5.0
Summary: OpenTelemetry exporter for Kubit analytics.
Author-email: Kubit Inc <support@kubit.ai>
License-Expression: LicenseRef-Kubit-Proprietary
Project-URL: Homepage, https://www.kubit.ai
Project-URL: Documentation, https://docs.kubit.ai
Keywords: opentelemetry,otel,kubit,kinesis,observability,llm-ops,analytics,genai,tracing
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: opentelemetry-api>=1.20.0
Requires-Dist: opentelemetry-sdk>=1.20.0
Requires-Dist: boto3>=1.28.0
Requires-Dist: httpx>=0.24.0
Provides-Extra: openai
Requires-Dist: opentelemetry-instrumentation-openai>=0.1.0; extra == "openai"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-mock>=3.0; extra == "dev"
Requires-Dist: moto[kinesis]>=4.0; extra == "dev"
Dynamic: license-file

# kubit-otel

OpenTelemetry exporter for Kubit analytics.

## Install

```bash
pip install kubit-otel
```

## Quick start

```python
from kubit_otel import configure
from opentelemetry import trace

configure(api_key="rg.v1.xxx", service_name="my-app")
tracer = trace.get_tracer("my-app")

with tracer.start_as_current_span("chat.completion") as span:
    span.set_attribute("gen_ai.request.model", "gpt-4o")
    span.set_attribute("gen_ai.prompt", "Hello, world!")
    span.set_attribute("gen_ai.completion", "Hi there!")
    span.set_attribute("gen_ai.usage.input_tokens", 10)
    span.set_attribute("gen_ai.usage.output_tokens", 5)
```

Spans are exported to Kubit using standard OpenTelemetry GenAI semantic
conventions. Each trace's root span is recorded as the trace; every span
(including the root) is recorded as an observation under it. `gen_ai.*`
attributes are mapped to first-class, queryable fields for model name,
prompt/completion, token counts, and cost.

### Works alongside other OTel-based SDKs

`configure()` detects whether a real `TracerProvider` is already installed
as the global OTel provider. If so, it attaches `KubitSpanProcessor` to
that provider and merges in your resource attributes — it does **not**
replace the existing provider. You can call `configure()` before or after
other OTel-based libraries (Langfuse, OpenLLMetry, an OTel distro, …) and
every span will reach both sinks.

If you want explicit "attach only, never register" behavior, use
`attach()`:

```python
from kubit_otel import attach

# Must be called after another library has installed a real provider.
attach(api_key="rg.v1.xxx")
```

## Supported attributes

| OpenTelemetry attribute | Purpose |
|---|---|
| `gen_ai.request.model` / `gen_ai.response.model` | Model name |
| `gen_ai.prompt` / `gen_ai.content.prompt` | Input prompt |
| `gen_ai.completion` / `gen_ai.content.completion` | Output completion |
| `gen_ai.usage.input_tokens` | Input token count |
| `gen_ai.usage.output_tokens` | Output token count |
| `gen_ai.usage.cost` | Total cost (USD) |
| `session.id` | Conversation session id |
| `enduser.id` | End-user id |

## Span filtering

By default, only LLM-relevant spans are forwarded to Kubit. A span is exported if it:

- was created by the Kubit SDK tracer (`kubit-sdk`),
- carries any `gen_ai.*` semantic-convention attribute, or
- comes from a known LLM instrumentation scope (OpenInference, Langfuse, Vercel AI SDK, …).

This keeps HTTP/DB/framework auto-instrumentation noise out of your Kubit workspace without extra configuration.

### Extend the default filter

```python
from kubit_otel import configure, is_default_export_span

configure(
    api_key="rg.v1.xxx",
    should_export_span=lambda span: (
        is_default_export_span(span)
        or (
            span.instrumentation_scope is not None
            and span.instrumentation_scope.name.startswith("my_framework")
        )
    ),
)
```

### Full override

```python
configure(
    api_key="rg.v1.xxx",
    should_export_span=lambda span: span.name.startswith("llm."),
)
```

### Export everything

```python
configure(api_key="rg.v1.xxx", should_export_span=lambda _span: True)
```

## Python compatibility

Python 3.9+

## License

Proprietary — see [LICENSE](./LICENSE).
