# acceldata-aio-tracer

LLM observability SDK by Acceldata, built on top of [Opik](https://github.com/comet-ml/opik). It:

- Adds Acceldata `accessKey` / `secretKey` authentication to every Opik HTTP call via an httpx auth hook.
- Re-exports the full Opik public API under the `acceldata_aio_tracer` namespace.
- Mirrors all Opik integrations under `acceldata_aio_tracer.integrations.<framework>` so customer code never imports from `opik` directly.

## Index

- [Installation](#installation)
- [Quick start](#quick-start)
- [Configuration](#configuration)
  - [Signature stability](#signature-stability)
  - [Programmatic (`configure`)](#programmatic-configure)
  - [Explicit client (`AcceldataTracer`)](#explicit-client-acceldatatracer)
  - [Environment variables](#environment-variables)
  - [Auto-init from env](#auto-init-from-env)
- [Authentication](#authentication)
- [Integrations](#integrations)
  - [Decorator-style integrations](#decorator-style-integrations)
  - [Callback / handler-style integrations](#callback--handler-style-integrations)
  - [Integration reference](#integration-reference)
- [Adding a new integration](#adding-a-new-integration)
- [Debugging](#debugging)
- [License](#license)

## Installation

```bash
pip install acceldata-aio-tracer
```

Requires Python 3.10+. A pinned `opik` version is pulled transitively — do not install `opik` separately at a different version.

## Quick start

```python
import acceldata_aio_tracer as aio

aio.configure(
    url="https://acme.acceldata.local:5443/aio",
    access_key="...",
    secret_key="...",
    project_name="default",
)

@aio.track
def summarize(text: str) -> str:
    # call an LLM, return the summary
    ...

summarize("hello world")  # traced and sent to Acceldata
```

`aio.track` is the standard Opik `@track` decorator re-exported under the wrapper namespace. All other top-level Opik APIs (`opik.Opik`, `opik.flush`, etc.) are also available as `aio.Opik`, `aio.flush`, and so on.

## Configuration

There are two configuration entry points; pick based on usage style.

### Signature stability

Both `configure()` and `AcceldataTracer()` follow the same shape — three required positional-or-keyword args followed by keyword-only options:

```python
def configure(
    url: str,                             # required, stable public name
    access_key: str,                      # required, stable public name
    secret_key: str,                      # required, stable public name
    *,                                    # everything below is keyword-only
    project_name: Optional[str] = None,   # may evolve across releases
    debug: bool = False,                  # may evolve across releases
    check_tls_certificate: bool = True,   # may evolve across releases
) -> None: ...
```

What this means in practice:

- **`url`, `access_key`, `secret_key` are required and will not be renamed.** Pass them positionally or by name — both forms are supported indefinitely.

```python
# Both calls are equivalent:
aio.configure("https://acme.acceldata.local:5443/aio", "ak", "sk")
aio.configure(url="https://acme.acceldata.local:5443/aio", access_key="ak", secret_key="sk")
```

- **All other options are keyword-only.** New options may be added, deprecated, or removed across releases without breaking existing call sites. Always pass them by name (`debug=True`, not `True`).
- **No partial calls.** All three required args must be supplied on every call. To change only a kwarg later (e.g. project name), either call `configure()` again with the full triple, or set the underlying env var directly (`os.environ["OPIK_PROJECT_NAME"] = "..."`).

### Programmatic (`configure`)

Use this for workflows that rely on `@track` decorators or framework integrations — anything that creates the Opik client lazily.

```python
import acceldata_aio_tracer as aio

aio.configure(
    url="https://acme.acceldata.local:5443/aio",
    access_key="...",
    secret_key="...",
    project_name="default",       # optional
    debug=False,                  # optional, see Debugging
    check_tls_certificate=True,   # set False only for self-signed dev gateways
)
```

`configure()` sets the env vars Opik reads natively (`OPIK_URL_OVERRIDE`, `OPIK_ACCESS_KEY`, `OPIK_SECRET_KEY`, `OPIK_PROJECT_NAME`) and registers the Acceldata auth hook so every subsequent Opik client carries the auth headers.

### Explicit client (`AcceldataTracer`)

Use this when you want a directly-constructed client (e.g. for non-decorator usage or to hold a per-request client).

```python
from acceldata_aio_tracer import AcceldataTracer

client = AcceldataTracer(
    url="https://acme.acceldata.local:5443/aio",
    access_key="...",
    secret_key="...",
)
# `client` is a fully-configured `opik.Opik` instance.
```

Calling `AcceldataTracer(...)` also registers the auth hook globally, so any other Opik client created later in the process (including those used by integrations) inherits the auth.

### Environment variables

| Variable | Purpose |
|---|---|
| `ACCELDATA_AIO_URL` | Acceldata gateway URL, e.g. `https://acme.acceldata.local:5443/aio` |
| `ACCELDATA_AIO_ACCESS_KEY` | Acceldata access key |
| `ACCELDATA_AIO_SECRET_KEY` | Acceldata secret key |
| `ACCELDATA_AIO_PROJECT_NAME` | Default project name for traces |
| `ACCELDATA_AIO_CHECK_TLS_CERTIFICATE` | `false` disables TLS verify on outbound Opik calls (dev / self-signed gateways only) |
| `ACCELDATA_AIO_DEBUG` | `true` / `1` / `yes` / `on` (case-insensitive) installs the HTTP debug interceptor at import time. Off by default. Wrapper-specific — no upstream fallback. |

The wrapper also respects the upstream Opik names (`OPIK_URL_OVERRIDE`, `OPIK_ACCESS_KEY`, `OPIK_SECRET_KEY`, `OPIK_PROJECT_NAME`, `OPIK_CHECK_TLS_CERTIFICATE`) as a fallback. If both an `ACCELDATA_AIO_*` and its `OPIK_*` counterpart are set, the `OPIK_*` value wins — useful as an escape hatch for migration, power-user overrides, or ops scripts that already reference Opik names.

At import time the wrapper prints a one-line summary to `stderr` listing which logical setting was picked from which env var name (names only, never values):

```
[acceldata_aio_tracer] env init: URL<-ACCELDATA_AIO_URL, ACCESS_KEY<-ACCELDATA_AIO_ACCESS_KEY, SECRET_KEY<-ACCELDATA_AIO_SECRET_KEY
```

### Auto-init from env

If both `ACCELDATA_AIO_ACCESS_KEY` and `ACCELDATA_AIO_SECRET_KEY` (or their `OPIK_*` fallbacks) are present when the package is imported, the Acceldata auth hook is registered automatically — no `configure()` call needed.

```bash
export ACCELDATA_AIO_URL="https://acme.acceldata.local:5443/aio"
export ACCELDATA_AIO_ACCESS_KEY="..."
export ACCELDATA_AIO_SECRET_KEY="..."
```

```python
import acceldata_aio_tracer  # auth hook registered, env-init summary printed to stderr
```

## Authentication

Acceldata uses `accessKey` and `secretKey` HTTP headers instead of bearer tokens. The auth hook attaches these headers to every Opik HTTP request and removes any pre-existing `Authorization` header to avoid conflicts:

```
accessKey: <ACCESS_KEY>
secretKey: <SECRET_KEY>
```

The hook is installed once per process and applies to every `httpx.Client` Opik creates afterward.

## Integrations

Every Opik integration is mirrored under `acceldata_aio_tracer.integrations.<framework>`. Two usage styles exist depending on the framework — pick based on the [reference table](#integration-reference) below.

### Decorator-style integrations

Wrap an SDK client with `track_<framework>(...)`. Tracing is automatic from that point on.

```python
from openai import OpenAI
from acceldata_aio_tracer.integrations.openai import track_openai

client = track_openai(OpenAI())
client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "hi"}],
)  # automatically traced
```

The same shape applies to Anthropic, AWS Bedrock, Google GenAI, AISuite, LiteLLM, CrewAI, Guardrails AI, and Harbor — substitute the framework name in both the import and the function call.

### Callback / handler-style integrations

Instantiate a tracer / callback / connector and pass it to the framework's callback machinery.

```python
# LangChain / LangGraph
from acceldata_aio_tracer.integrations.langchain import LangChainTracer

tracer = LangChainTracer()
chain.invoke({"input": "..."}, config={"callbacks": [tracer]})
```

```python
# Google ADK
from acceldata_aio_tracer.integrations.adk import ADKTracer, track_adk_agent_recursive

tracer = ADKTracer()
track_adk_agent_recursive(my_agent, tracer)
```

```python
# DSPy
import dspy
from acceldata_aio_tracer.integrations.dspy import DSPyCallback

dspy.configure(callbacks=[DSPyCallback()])
```

```python
# Haystack
from acceldata_aio_tracer.integrations.haystack import HaystackConnector

pipeline.add_component("opik", HaystackConnector())
```

```python
# LlamaIndex
from llama_index.core.callbacks import CallbackManager
from acceldata_aio_tracer.integrations.llama_index import LlamaIndexCallbackHandler

manager = CallbackManager([LlamaIndexCallbackHandler()])
```

### Integration reference

15 Opik integrations exist; 14 are mirrored here. Sagemaker is intentionally not exposed (upstream is an internal AWS auth helper with no public API).

| Integration | Import path | Exported names | Style | Renamed from upstream |
|---|---|---|---|---|
| LangChain / LangGraph | `acceldata_aio_tracer.integrations.langchain` | `LangChainTracer`, `track_langgraph`, `extract_current_langgraph_span_data`, `LANGGRAPH_INTERRUPT_OUTPUT_KEY`, `LANGGRAPH_RESUME_INPUT_KEY`, `LANGGRAPH_INTERRUPT_METADATA_KEY`, `LANGGRAPH_PARENT_COMMAND_METADATA_KEY` | Callback | `OpikTracer` -> `LangChainTracer` |
| Google ADK | `acceldata_aio_tracer.integrations.adk` | `ADKTracer`, `track_adk_agent_recursive`, `build_mermaid_graph_definition` | Callback | `OpikTracer` -> `ADKTracer` |
| DSPy | `acceldata_aio_tracer.integrations.dspy` | `DSPyCallback` | Callback | `OpikCallback` -> `DSPyCallback` |
| Haystack | `acceldata_aio_tracer.integrations.haystack` | `HaystackConnector` | Component | `OpikConnector` -> `HaystackConnector` |
| LlamaIndex | `acceldata_aio_tracer.integrations.llama_index` | `LlamaIndexCallbackHandler` | Callback | (already framework-named) |
| OpenAI | `acceldata_aio_tracer.integrations.openai` | `track_openai` | Decorator | (no rename) |
| Anthropic | `acceldata_aio_tracer.integrations.anthropic` | `track_anthropic` | Decorator | (no rename) |
| AWS Bedrock | `acceldata_aio_tracer.integrations.bedrock` | `track_bedrock` | Decorator | (no rename) |
| Google GenAI | `acceldata_aio_tracer.integrations.genai` | `track_genai` | Decorator | (no rename) |
| AISuite | `acceldata_aio_tracer.integrations.aisuite` | `track_aisuite` | Decorator | (no rename) |
| LiteLLM | `acceldata_aio_tracer.integrations.litellm` | `track_completion` | Decorator | (no rename) |
| CrewAI | `acceldata_aio_tracer.integrations.crewai` | `track_crewai` | Decorator | (no rename) |
| Guardrails AI | `acceldata_aio_tracer.integrations.guardrails` | `track_guardrails` | Decorator | (no rename) |
| Harbor | `acceldata_aio_tracer.integrations.harbor` | `track_harbor`, `reset_harbor_tracking` | Decorator | (no rename) |

For per-integration usage details (model versions, async patterns, edge cases, advanced config), refer to the [Opik documentation](https://www.comet.com/docs/opik/) — wrapper imports are drop-in equivalents.

## Adding a new integration

When upstream Opik (`opik.integrations.<name>`) ships a new integration, mirror it here:

1. Inspect the upstream `__init__.py` for the public exports.
2. Create `src/acceldata_aio_tracer/integrations/<name>.py`:
   - **Decorator-style** (`track_<name>` function): `from opik.integrations.<name> import track_<name>` + `__all__ = ("track_<name>",)`.
   - **Class-style with Opik prefix** (`OpikTracer`, `OpikCallback`, `OpikConnector`): rename to a framework-named class. E.g. `from opik.integrations.<name> import OpikTracer as <Framework>Tracer`.
   - **Class-style with framework prefix** (already framework-named): pass-through re-export.
3. Add a row to the [Integration reference](#integration-reference) table above.
4. Smoke-test: `python -c "from acceldata_aio_tracer.integrations.<name> import *"`.

The wrapper does not auto-mirror — every new upstream integration needs an explicit file.

## Debugging

To trace every Opik HTTP call (including a copy-paste `curl` reproducer for any 4xx/5xx response), enable debug mode:

```python
aio.configure(url=..., access_key=..., secret_key=..., debug=True)
# or
client = AcceldataTracer(url=..., access_key=..., secret_key=..., debug=True)
```

On a failed request, the request body is dumped to `/tmp/acceldata-aio-tracer-last-failed-request.bin` and a replay-ready `curl` command is printed to the `acceldata_aio_tracer.http_trace` logger.

## License

Apache-2.0. This SDK is a thin wrapper around [Opik](https://github.com/comet-ml/opik), which is also Apache-2.0 licensed.