acceldata-aio-tracer 0.1.0.dev1__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.
- acceldata_aio_tracer-0.1.0.dev1/PKG-INFO +301 -0
- acceldata_aio_tracer-0.1.0.dev1/README.md +286 -0
- acceldata_aio_tracer-0.1.0.dev1/pyproject.toml +20 -0
- acceldata_aio_tracer-0.1.0.dev1/setup.py +34 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/__init__.py +91 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/_opik_bridge.py +107 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/auth.py +38 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/client.py +166 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/__init__.py +0 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/adk.py +11 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/aisuite.py +3 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/anthropic.py +3 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/bedrock.py +3 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/crewai.py +3 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/dspy.py +3 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/genai.py +3 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/guardrails.py +3 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/harbor.py +3 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/haystack.py +3 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/langchain.py +19 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/litellm.py +3 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/llama_index.py +3 -0
- acceldata_aio_tracer-0.1.0.dev1/src/acceldata_aio_tracer/integrations/openai.py +3 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: acceldata-aio-tracer
|
|
3
|
+
Version: 0.1.0.dev1
|
|
4
|
+
Summary: Acceldata LLM observability SDK
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Author: Acceldata
|
|
7
|
+
Requires-Python: >=3.10,<4.0
|
|
8
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Requires-Dist: httpx
|
|
13
|
+
Requires-Dist: opik (==1.11.3)
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# acceldata-aio-tracer
|
|
17
|
+
|
|
18
|
+
LLM observability SDK by Acceldata, built on top of [Opik](https://github.com/comet-ml/opik). It:
|
|
19
|
+
|
|
20
|
+
- Adds Acceldata `accessKey` / `secretKey` authentication to every Opik HTTP call via an httpx auth hook.
|
|
21
|
+
- Re-exports the full Opik public API under the `acceldata_aio_tracer` namespace.
|
|
22
|
+
- Mirrors all Opik integrations under `acceldata_aio_tracer.integrations.<framework>` so customer code never imports from `opik` directly.
|
|
23
|
+
|
|
24
|
+
## Index
|
|
25
|
+
|
|
26
|
+
- [Installation](#installation)
|
|
27
|
+
- [Quick start](#quick-start)
|
|
28
|
+
- [Configuration](#configuration)
|
|
29
|
+
- [Signature stability](#signature-stability)
|
|
30
|
+
- [Programmatic (`configure`)](#programmatic-configure)
|
|
31
|
+
- [Explicit client (`AcceldataTracer`)](#explicit-client-acceldatatracer)
|
|
32
|
+
- [Environment variables](#environment-variables)
|
|
33
|
+
- [Auto-init from env](#auto-init-from-env)
|
|
34
|
+
- [Authentication](#authentication)
|
|
35
|
+
- [Integrations](#integrations)
|
|
36
|
+
- [Decorator-style integrations](#decorator-style-integrations)
|
|
37
|
+
- [Callback / handler-style integrations](#callback--handler-style-integrations)
|
|
38
|
+
- [Integration reference](#integration-reference)
|
|
39
|
+
- [Adding a new integration](#adding-a-new-integration)
|
|
40
|
+
- [Debugging](#debugging)
|
|
41
|
+
- [License](#license)
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install acceldata-aio-tracer
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Requires Python 3.10+. A pinned `opik` version is pulled transitively — do not install `opik` separately at a different version.
|
|
50
|
+
|
|
51
|
+
## Quick start
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import acceldata_aio_tracer as aio
|
|
55
|
+
|
|
56
|
+
aio.configure(
|
|
57
|
+
url="https://acme.acceldata.local:5443/aio",
|
|
58
|
+
access_key="...",
|
|
59
|
+
secret_key="...",
|
|
60
|
+
project_name="default",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@aio.track
|
|
64
|
+
def summarize(text: str) -> str:
|
|
65
|
+
# call an LLM, return the summary
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
summarize("hello world") # traced and sent to Acceldata
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`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.
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
There are two configuration entry points; pick based on usage style.
|
|
76
|
+
|
|
77
|
+
### Signature stability
|
|
78
|
+
|
|
79
|
+
Both `configure()` and `AcceldataTracer()` follow the same shape — three required positional-or-keyword args followed by keyword-only options:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
def configure(
|
|
83
|
+
url: str, # required, stable public name
|
|
84
|
+
access_key: str, # required, stable public name
|
|
85
|
+
secret_key: str, # required, stable public name
|
|
86
|
+
*, # everything below is keyword-only
|
|
87
|
+
project_name: Optional[str] = None, # may evolve across releases
|
|
88
|
+
debug: bool = False, # may evolve across releases
|
|
89
|
+
check_tls_certificate: bool = True, # may evolve across releases
|
|
90
|
+
) -> None: ...
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
What this means in practice:
|
|
94
|
+
|
|
95
|
+
- **`url`, `access_key`, `secret_key` are required and will not be renamed.** Pass them positionally or by name — both forms are supported indefinitely.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# Both calls are equivalent:
|
|
99
|
+
aio.configure("https://acme.acceldata.local:5443/aio", "ak", "sk")
|
|
100
|
+
aio.configure(url="https://acme.acceldata.local:5443/aio", access_key="ak", secret_key="sk")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- **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`).
|
|
104
|
+
- **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"] = "..."`).
|
|
105
|
+
|
|
106
|
+
### Programmatic (`configure`)
|
|
107
|
+
|
|
108
|
+
Use this for workflows that rely on `@track` decorators or framework integrations — anything that creates the Opik client lazily.
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
import acceldata_aio_tracer as aio
|
|
112
|
+
|
|
113
|
+
aio.configure(
|
|
114
|
+
url="https://acme.acceldata.local:5443/aio",
|
|
115
|
+
access_key="...",
|
|
116
|
+
secret_key="...",
|
|
117
|
+
project_name="default", # optional
|
|
118
|
+
debug=False, # optional, see Debugging
|
|
119
|
+
check_tls_certificate=True, # set False only for self-signed dev gateways
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`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.
|
|
124
|
+
|
|
125
|
+
### Explicit client (`AcceldataTracer`)
|
|
126
|
+
|
|
127
|
+
Use this when you want a directly-constructed client (e.g. for non-decorator usage or to hold a per-request client).
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from acceldata_aio_tracer import AcceldataTracer
|
|
131
|
+
|
|
132
|
+
client = AcceldataTracer(
|
|
133
|
+
url="https://acme.acceldata.local:5443/aio",
|
|
134
|
+
access_key="...",
|
|
135
|
+
secret_key="...",
|
|
136
|
+
)
|
|
137
|
+
# `client` is a fully-configured `opik.Opik` instance.
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
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.
|
|
141
|
+
|
|
142
|
+
### Environment variables
|
|
143
|
+
|
|
144
|
+
| Variable | Purpose |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `ACCELDATA_AIO_URL` | Acceldata gateway URL, e.g. `https://acme.acceldata.local:5443/aio` |
|
|
147
|
+
| `ACCELDATA_AIO_ACCESS_KEY` | Acceldata access key |
|
|
148
|
+
| `ACCELDATA_AIO_SECRET_KEY` | Acceldata secret key |
|
|
149
|
+
| `ACCELDATA_AIO_PROJECT_NAME` | Default project name for traces |
|
|
150
|
+
| `ACCELDATA_AIO_CHECK_TLS_CERTIFICATE` | `false` disables TLS verify on outbound Opik calls (dev / self-signed gateways only) |
|
|
151
|
+
| `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. |
|
|
152
|
+
|
|
153
|
+
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.
|
|
154
|
+
|
|
155
|
+
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):
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
[acceldata_aio_tracer] env init: URL<-ACCELDATA_AIO_URL, ACCESS_KEY<-ACCELDATA_AIO_ACCESS_KEY, SECRET_KEY<-ACCELDATA_AIO_SECRET_KEY
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Auto-init from env
|
|
162
|
+
|
|
163
|
+
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.
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
export ACCELDATA_AIO_URL="https://acme.acceldata.local:5443/aio"
|
|
167
|
+
export ACCELDATA_AIO_ACCESS_KEY="..."
|
|
168
|
+
export ACCELDATA_AIO_SECRET_KEY="..."
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
import acceldata_aio_tracer # auth hook registered, env-init summary printed to stderr
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Authentication
|
|
176
|
+
|
|
177
|
+
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:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
accessKey: <ACCESS_KEY>
|
|
181
|
+
secretKey: <SECRET_KEY>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The hook is installed once per process and applies to every `httpx.Client` Opik creates afterward.
|
|
185
|
+
|
|
186
|
+
## Integrations
|
|
187
|
+
|
|
188
|
+
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.
|
|
189
|
+
|
|
190
|
+
### Decorator-style integrations
|
|
191
|
+
|
|
192
|
+
Wrap an SDK client with `track_<framework>(...)`. Tracing is automatic from that point on.
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from openai import OpenAI
|
|
196
|
+
from acceldata_aio_tracer.integrations.openai import track_openai
|
|
197
|
+
|
|
198
|
+
client = track_openai(OpenAI())
|
|
199
|
+
client.chat.completions.create(
|
|
200
|
+
model="gpt-4o-mini",
|
|
201
|
+
messages=[{"role": "user", "content": "hi"}],
|
|
202
|
+
) # automatically traced
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
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.
|
|
206
|
+
|
|
207
|
+
### Callback / handler-style integrations
|
|
208
|
+
|
|
209
|
+
Instantiate a tracer / callback / connector and pass it to the framework's callback machinery.
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
# LangChain / LangGraph
|
|
213
|
+
from acceldata_aio_tracer.integrations.langchain import LangChainTracer
|
|
214
|
+
|
|
215
|
+
tracer = LangChainTracer()
|
|
216
|
+
chain.invoke({"input": "..."}, config={"callbacks": [tracer]})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
# Google ADK
|
|
221
|
+
from acceldata_aio_tracer.integrations.adk import ADKTracer, track_adk_agent_recursive
|
|
222
|
+
|
|
223
|
+
tracer = ADKTracer()
|
|
224
|
+
track_adk_agent_recursive(my_agent, tracer)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
# DSPy
|
|
229
|
+
import dspy
|
|
230
|
+
from acceldata_aio_tracer.integrations.dspy import DSPyCallback
|
|
231
|
+
|
|
232
|
+
dspy.configure(callbacks=[DSPyCallback()])
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
# Haystack
|
|
237
|
+
from acceldata_aio_tracer.integrations.haystack import HaystackConnector
|
|
238
|
+
|
|
239
|
+
pipeline.add_component("opik", HaystackConnector())
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
# LlamaIndex
|
|
244
|
+
from llama_index.core.callbacks import CallbackManager
|
|
245
|
+
from acceldata_aio_tracer.integrations.llama_index import LlamaIndexCallbackHandler
|
|
246
|
+
|
|
247
|
+
manager = CallbackManager([LlamaIndexCallbackHandler()])
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Integration reference
|
|
251
|
+
|
|
252
|
+
15 Opik integrations exist; 14 are mirrored here. Sagemaker is intentionally not exposed (upstream is an internal AWS auth helper with no public API).
|
|
253
|
+
|
|
254
|
+
| Integration | Import path | Exported names | Style | Renamed from upstream |
|
|
255
|
+
|---|---|---|---|---|
|
|
256
|
+
| 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` |
|
|
257
|
+
| Google ADK | `acceldata_aio_tracer.integrations.adk` | `ADKTracer`, `track_adk_agent_recursive`, `build_mermaid_graph_definition` | Callback | `OpikTracer` -> `ADKTracer` |
|
|
258
|
+
| DSPy | `acceldata_aio_tracer.integrations.dspy` | `DSPyCallback` | Callback | `OpikCallback` -> `DSPyCallback` |
|
|
259
|
+
| Haystack | `acceldata_aio_tracer.integrations.haystack` | `HaystackConnector` | Component | `OpikConnector` -> `HaystackConnector` |
|
|
260
|
+
| LlamaIndex | `acceldata_aio_tracer.integrations.llama_index` | `LlamaIndexCallbackHandler` | Callback | (already framework-named) |
|
|
261
|
+
| OpenAI | `acceldata_aio_tracer.integrations.openai` | `track_openai` | Decorator | (no rename) |
|
|
262
|
+
| Anthropic | `acceldata_aio_tracer.integrations.anthropic` | `track_anthropic` | Decorator | (no rename) |
|
|
263
|
+
| AWS Bedrock | `acceldata_aio_tracer.integrations.bedrock` | `track_bedrock` | Decorator | (no rename) |
|
|
264
|
+
| Google GenAI | `acceldata_aio_tracer.integrations.genai` | `track_genai` | Decorator | (no rename) |
|
|
265
|
+
| AISuite | `acceldata_aio_tracer.integrations.aisuite` | `track_aisuite` | Decorator | (no rename) |
|
|
266
|
+
| LiteLLM | `acceldata_aio_tracer.integrations.litellm` | `track_completion` | Decorator | (no rename) |
|
|
267
|
+
| CrewAI | `acceldata_aio_tracer.integrations.crewai` | `track_crewai` | Decorator | (no rename) |
|
|
268
|
+
| Guardrails AI | `acceldata_aio_tracer.integrations.guardrails` | `track_guardrails` | Decorator | (no rename) |
|
|
269
|
+
| Harbor | `acceldata_aio_tracer.integrations.harbor` | `track_harbor`, `reset_harbor_tracking` | Decorator | (no rename) |
|
|
270
|
+
|
|
271
|
+
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.
|
|
272
|
+
|
|
273
|
+
## Adding a new integration
|
|
274
|
+
|
|
275
|
+
When upstream Opik (`opik.integrations.<name>`) ships a new integration, mirror it here:
|
|
276
|
+
|
|
277
|
+
1. Inspect the upstream `__init__.py` for the public exports.
|
|
278
|
+
2. Create `src/acceldata_aio_tracer/integrations/<name>.py`:
|
|
279
|
+
- **Decorator-style** (`track_<name>` function): `from opik.integrations.<name> import track_<name>` + `__all__ = ("track_<name>",)`.
|
|
280
|
+
- **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`.
|
|
281
|
+
- **Class-style with framework prefix** (already framework-named): pass-through re-export.
|
|
282
|
+
3. Add a row to the [Integration reference](#integration-reference) table above.
|
|
283
|
+
4. Smoke-test: `python -c "from acceldata_aio_tracer.integrations.<name> import *"`.
|
|
284
|
+
|
|
285
|
+
The wrapper does not auto-mirror — every new upstream integration needs an explicit file.
|
|
286
|
+
|
|
287
|
+
## Debugging
|
|
288
|
+
|
|
289
|
+
To trace every Opik HTTP call (including a copy-paste `curl` reproducer for any 4xx/5xx response), enable debug mode:
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
aio.configure(url=..., access_key=..., secret_key=..., debug=True)
|
|
293
|
+
# or
|
|
294
|
+
client = AcceldataTracer(url=..., access_key=..., secret_key=..., debug=True)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
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.
|
|
298
|
+
|
|
299
|
+
## License
|
|
300
|
+
|
|
301
|
+
Apache-2.0. This SDK is a thin wrapper around [Opik](https://github.com/comet-ml/opik), which is also Apache-2.0 licensed.
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# acceldata-aio-tracer
|
|
2
|
+
|
|
3
|
+
LLM observability SDK by Acceldata, built on top of [Opik](https://github.com/comet-ml/opik). It:
|
|
4
|
+
|
|
5
|
+
- Adds Acceldata `accessKey` / `secretKey` authentication to every Opik HTTP call via an httpx auth hook.
|
|
6
|
+
- Re-exports the full Opik public API under the `acceldata_aio_tracer` namespace.
|
|
7
|
+
- Mirrors all Opik integrations under `acceldata_aio_tracer.integrations.<framework>` so customer code never imports from `opik` directly.
|
|
8
|
+
|
|
9
|
+
## Index
|
|
10
|
+
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Quick start](#quick-start)
|
|
13
|
+
- [Configuration](#configuration)
|
|
14
|
+
- [Signature stability](#signature-stability)
|
|
15
|
+
- [Programmatic (`configure`)](#programmatic-configure)
|
|
16
|
+
- [Explicit client (`AcceldataTracer`)](#explicit-client-acceldatatracer)
|
|
17
|
+
- [Environment variables](#environment-variables)
|
|
18
|
+
- [Auto-init from env](#auto-init-from-env)
|
|
19
|
+
- [Authentication](#authentication)
|
|
20
|
+
- [Integrations](#integrations)
|
|
21
|
+
- [Decorator-style integrations](#decorator-style-integrations)
|
|
22
|
+
- [Callback / handler-style integrations](#callback--handler-style-integrations)
|
|
23
|
+
- [Integration reference](#integration-reference)
|
|
24
|
+
- [Adding a new integration](#adding-a-new-integration)
|
|
25
|
+
- [Debugging](#debugging)
|
|
26
|
+
- [License](#license)
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install acceldata-aio-tracer
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Requires Python 3.10+. A pinned `opik` version is pulled transitively — do not install `opik` separately at a different version.
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import acceldata_aio_tracer as aio
|
|
40
|
+
|
|
41
|
+
aio.configure(
|
|
42
|
+
url="https://acme.acceldata.local:5443/aio",
|
|
43
|
+
access_key="...",
|
|
44
|
+
secret_key="...",
|
|
45
|
+
project_name="default",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@aio.track
|
|
49
|
+
def summarize(text: str) -> str:
|
|
50
|
+
# call an LLM, return the summary
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
summarize("hello world") # traced and sent to Acceldata
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`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.
|
|
57
|
+
|
|
58
|
+
## Configuration
|
|
59
|
+
|
|
60
|
+
There are two configuration entry points; pick based on usage style.
|
|
61
|
+
|
|
62
|
+
### Signature stability
|
|
63
|
+
|
|
64
|
+
Both `configure()` and `AcceldataTracer()` follow the same shape — three required positional-or-keyword args followed by keyword-only options:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
def configure(
|
|
68
|
+
url: str, # required, stable public name
|
|
69
|
+
access_key: str, # required, stable public name
|
|
70
|
+
secret_key: str, # required, stable public name
|
|
71
|
+
*, # everything below is keyword-only
|
|
72
|
+
project_name: Optional[str] = None, # may evolve across releases
|
|
73
|
+
debug: bool = False, # may evolve across releases
|
|
74
|
+
check_tls_certificate: bool = True, # may evolve across releases
|
|
75
|
+
) -> None: ...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
What this means in practice:
|
|
79
|
+
|
|
80
|
+
- **`url`, `access_key`, `secret_key` are required and will not be renamed.** Pass them positionally or by name — both forms are supported indefinitely.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# Both calls are equivalent:
|
|
84
|
+
aio.configure("https://acme.acceldata.local:5443/aio", "ak", "sk")
|
|
85
|
+
aio.configure(url="https://acme.acceldata.local:5443/aio", access_key="ak", secret_key="sk")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- **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`).
|
|
89
|
+
- **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"] = "..."`).
|
|
90
|
+
|
|
91
|
+
### Programmatic (`configure`)
|
|
92
|
+
|
|
93
|
+
Use this for workflows that rely on `@track` decorators or framework integrations — anything that creates the Opik client lazily.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import acceldata_aio_tracer as aio
|
|
97
|
+
|
|
98
|
+
aio.configure(
|
|
99
|
+
url="https://acme.acceldata.local:5443/aio",
|
|
100
|
+
access_key="...",
|
|
101
|
+
secret_key="...",
|
|
102
|
+
project_name="default", # optional
|
|
103
|
+
debug=False, # optional, see Debugging
|
|
104
|
+
check_tls_certificate=True, # set False only for self-signed dev gateways
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`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.
|
|
109
|
+
|
|
110
|
+
### Explicit client (`AcceldataTracer`)
|
|
111
|
+
|
|
112
|
+
Use this when you want a directly-constructed client (e.g. for non-decorator usage or to hold a per-request client).
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from acceldata_aio_tracer import AcceldataTracer
|
|
116
|
+
|
|
117
|
+
client = AcceldataTracer(
|
|
118
|
+
url="https://acme.acceldata.local:5443/aio",
|
|
119
|
+
access_key="...",
|
|
120
|
+
secret_key="...",
|
|
121
|
+
)
|
|
122
|
+
# `client` is a fully-configured `opik.Opik` instance.
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
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.
|
|
126
|
+
|
|
127
|
+
### Environment variables
|
|
128
|
+
|
|
129
|
+
| Variable | Purpose |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `ACCELDATA_AIO_URL` | Acceldata gateway URL, e.g. `https://acme.acceldata.local:5443/aio` |
|
|
132
|
+
| `ACCELDATA_AIO_ACCESS_KEY` | Acceldata access key |
|
|
133
|
+
| `ACCELDATA_AIO_SECRET_KEY` | Acceldata secret key |
|
|
134
|
+
| `ACCELDATA_AIO_PROJECT_NAME` | Default project name for traces |
|
|
135
|
+
| `ACCELDATA_AIO_CHECK_TLS_CERTIFICATE` | `false` disables TLS verify on outbound Opik calls (dev / self-signed gateways only) |
|
|
136
|
+
| `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. |
|
|
137
|
+
|
|
138
|
+
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.
|
|
139
|
+
|
|
140
|
+
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):
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
[acceldata_aio_tracer] env init: URL<-ACCELDATA_AIO_URL, ACCESS_KEY<-ACCELDATA_AIO_ACCESS_KEY, SECRET_KEY<-ACCELDATA_AIO_SECRET_KEY
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Auto-init from env
|
|
147
|
+
|
|
148
|
+
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.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
export ACCELDATA_AIO_URL="https://acme.acceldata.local:5443/aio"
|
|
152
|
+
export ACCELDATA_AIO_ACCESS_KEY="..."
|
|
153
|
+
export ACCELDATA_AIO_SECRET_KEY="..."
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
import acceldata_aio_tracer # auth hook registered, env-init summary printed to stderr
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Authentication
|
|
161
|
+
|
|
162
|
+
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:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
accessKey: <ACCESS_KEY>
|
|
166
|
+
secretKey: <SECRET_KEY>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The hook is installed once per process and applies to every `httpx.Client` Opik creates afterward.
|
|
170
|
+
|
|
171
|
+
## Integrations
|
|
172
|
+
|
|
173
|
+
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.
|
|
174
|
+
|
|
175
|
+
### Decorator-style integrations
|
|
176
|
+
|
|
177
|
+
Wrap an SDK client with `track_<framework>(...)`. Tracing is automatic from that point on.
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from openai import OpenAI
|
|
181
|
+
from acceldata_aio_tracer.integrations.openai import track_openai
|
|
182
|
+
|
|
183
|
+
client = track_openai(OpenAI())
|
|
184
|
+
client.chat.completions.create(
|
|
185
|
+
model="gpt-4o-mini",
|
|
186
|
+
messages=[{"role": "user", "content": "hi"}],
|
|
187
|
+
) # automatically traced
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
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.
|
|
191
|
+
|
|
192
|
+
### Callback / handler-style integrations
|
|
193
|
+
|
|
194
|
+
Instantiate a tracer / callback / connector and pass it to the framework's callback machinery.
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
# LangChain / LangGraph
|
|
198
|
+
from acceldata_aio_tracer.integrations.langchain import LangChainTracer
|
|
199
|
+
|
|
200
|
+
tracer = LangChainTracer()
|
|
201
|
+
chain.invoke({"input": "..."}, config={"callbacks": [tracer]})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
# Google ADK
|
|
206
|
+
from acceldata_aio_tracer.integrations.adk import ADKTracer, track_adk_agent_recursive
|
|
207
|
+
|
|
208
|
+
tracer = ADKTracer()
|
|
209
|
+
track_adk_agent_recursive(my_agent, tracer)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
# DSPy
|
|
214
|
+
import dspy
|
|
215
|
+
from acceldata_aio_tracer.integrations.dspy import DSPyCallback
|
|
216
|
+
|
|
217
|
+
dspy.configure(callbacks=[DSPyCallback()])
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
# Haystack
|
|
222
|
+
from acceldata_aio_tracer.integrations.haystack import HaystackConnector
|
|
223
|
+
|
|
224
|
+
pipeline.add_component("opik", HaystackConnector())
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
# LlamaIndex
|
|
229
|
+
from llama_index.core.callbacks import CallbackManager
|
|
230
|
+
from acceldata_aio_tracer.integrations.llama_index import LlamaIndexCallbackHandler
|
|
231
|
+
|
|
232
|
+
manager = CallbackManager([LlamaIndexCallbackHandler()])
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Integration reference
|
|
236
|
+
|
|
237
|
+
15 Opik integrations exist; 14 are mirrored here. Sagemaker is intentionally not exposed (upstream is an internal AWS auth helper with no public API).
|
|
238
|
+
|
|
239
|
+
| Integration | Import path | Exported names | Style | Renamed from upstream |
|
|
240
|
+
|---|---|---|---|---|
|
|
241
|
+
| 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` |
|
|
242
|
+
| Google ADK | `acceldata_aio_tracer.integrations.adk` | `ADKTracer`, `track_adk_agent_recursive`, `build_mermaid_graph_definition` | Callback | `OpikTracer` -> `ADKTracer` |
|
|
243
|
+
| DSPy | `acceldata_aio_tracer.integrations.dspy` | `DSPyCallback` | Callback | `OpikCallback` -> `DSPyCallback` |
|
|
244
|
+
| Haystack | `acceldata_aio_tracer.integrations.haystack` | `HaystackConnector` | Component | `OpikConnector` -> `HaystackConnector` |
|
|
245
|
+
| LlamaIndex | `acceldata_aio_tracer.integrations.llama_index` | `LlamaIndexCallbackHandler` | Callback | (already framework-named) |
|
|
246
|
+
| OpenAI | `acceldata_aio_tracer.integrations.openai` | `track_openai` | Decorator | (no rename) |
|
|
247
|
+
| Anthropic | `acceldata_aio_tracer.integrations.anthropic` | `track_anthropic` | Decorator | (no rename) |
|
|
248
|
+
| AWS Bedrock | `acceldata_aio_tracer.integrations.bedrock` | `track_bedrock` | Decorator | (no rename) |
|
|
249
|
+
| Google GenAI | `acceldata_aio_tracer.integrations.genai` | `track_genai` | Decorator | (no rename) |
|
|
250
|
+
| AISuite | `acceldata_aio_tracer.integrations.aisuite` | `track_aisuite` | Decorator | (no rename) |
|
|
251
|
+
| LiteLLM | `acceldata_aio_tracer.integrations.litellm` | `track_completion` | Decorator | (no rename) |
|
|
252
|
+
| CrewAI | `acceldata_aio_tracer.integrations.crewai` | `track_crewai` | Decorator | (no rename) |
|
|
253
|
+
| Guardrails AI | `acceldata_aio_tracer.integrations.guardrails` | `track_guardrails` | Decorator | (no rename) |
|
|
254
|
+
| Harbor | `acceldata_aio_tracer.integrations.harbor` | `track_harbor`, `reset_harbor_tracking` | Decorator | (no rename) |
|
|
255
|
+
|
|
256
|
+
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.
|
|
257
|
+
|
|
258
|
+
## Adding a new integration
|
|
259
|
+
|
|
260
|
+
When upstream Opik (`opik.integrations.<name>`) ships a new integration, mirror it here:
|
|
261
|
+
|
|
262
|
+
1. Inspect the upstream `__init__.py` for the public exports.
|
|
263
|
+
2. Create `src/acceldata_aio_tracer/integrations/<name>.py`:
|
|
264
|
+
- **Decorator-style** (`track_<name>` function): `from opik.integrations.<name> import track_<name>` + `__all__ = ("track_<name>",)`.
|
|
265
|
+
- **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`.
|
|
266
|
+
- **Class-style with framework prefix** (already framework-named): pass-through re-export.
|
|
267
|
+
3. Add a row to the [Integration reference](#integration-reference) table above.
|
|
268
|
+
4. Smoke-test: `python -c "from acceldata_aio_tracer.integrations.<name> import *"`.
|
|
269
|
+
|
|
270
|
+
The wrapper does not auto-mirror — every new upstream integration needs an explicit file.
|
|
271
|
+
|
|
272
|
+
## Debugging
|
|
273
|
+
|
|
274
|
+
To trace every Opik HTTP call (including a copy-paste `curl` reproducer for any 4xx/5xx response), enable debug mode:
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
aio.configure(url=..., access_key=..., secret_key=..., debug=True)
|
|
278
|
+
# or
|
|
279
|
+
client = AcceldataTracer(url=..., access_key=..., secret_key=..., debug=True)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
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.
|
|
283
|
+
|
|
284
|
+
## License
|
|
285
|
+
|
|
286
|
+
Apache-2.0. This SDK is a thin wrapper around [Opik](https://github.com/comet-ml/opik), which is also Apache-2.0 licensed.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "acceldata-aio-tracer"
|
|
3
|
+
version = "0.1.0.dev1"
|
|
4
|
+
description = "Acceldata LLM observability SDK"
|
|
5
|
+
authors = ["Acceldata"]
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
packages = [{ include = "acceldata_aio_tracer", from = "src" }]
|
|
9
|
+
|
|
10
|
+
[tool.poetry.dependencies]
|
|
11
|
+
python = "^3.10"
|
|
12
|
+
opik = "1.11.3"
|
|
13
|
+
httpx = "*"
|
|
14
|
+
|
|
15
|
+
[tool.poetry.group.dev.dependencies]
|
|
16
|
+
pytest = "^8.0.0"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["poetry-core"]
|
|
20
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from setuptools import setup
|
|
3
|
+
|
|
4
|
+
package_dir = \
|
|
5
|
+
{'': 'src'}
|
|
6
|
+
|
|
7
|
+
packages = \
|
|
8
|
+
['acceldata_aio_tracer', 'acceldata_aio_tracer.integrations']
|
|
9
|
+
|
|
10
|
+
package_data = \
|
|
11
|
+
{'': ['*']}
|
|
12
|
+
|
|
13
|
+
install_requires = \
|
|
14
|
+
['httpx', 'opik==1.11.3']
|
|
15
|
+
|
|
16
|
+
setup_kwargs = {
|
|
17
|
+
'name': 'acceldata-aio-tracer',
|
|
18
|
+
'version': '0.1.0.dev1',
|
|
19
|
+
'description': 'Acceldata LLM observability SDK',
|
|
20
|
+
'long_description': '# acceldata-aio-tracer\n\nLLM observability SDK by Acceldata, built on top of [Opik](https://github.com/comet-ml/opik). It:\n\n- Adds Acceldata `accessKey` / `secretKey` authentication to every Opik HTTP call via an httpx auth hook.\n- Re-exports the full Opik public API under the `acceldata_aio_tracer` namespace.\n- Mirrors all Opik integrations under `acceldata_aio_tracer.integrations.<framework>` so customer code never imports from `opik` directly.\n\n## Index\n\n- [Installation](#installation)\n- [Quick start](#quick-start)\n- [Configuration](#configuration)\n - [Signature stability](#signature-stability)\n - [Programmatic (`configure`)](#programmatic-configure)\n - [Explicit client (`AcceldataTracer`)](#explicit-client-acceldatatracer)\n - [Environment variables](#environment-variables)\n - [Auto-init from env](#auto-init-from-env)\n- [Authentication](#authentication)\n- [Integrations](#integrations)\n - [Decorator-style integrations](#decorator-style-integrations)\n - [Callback / handler-style integrations](#callback--handler-style-integrations)\n - [Integration reference](#integration-reference)\n- [Adding a new integration](#adding-a-new-integration)\n- [Debugging](#debugging)\n- [License](#license)\n\n## Installation\n\n```bash\npip install acceldata-aio-tracer\n```\n\nRequires Python 3.10+. A pinned `opik` version is pulled transitively — do not install `opik` separately at a different version.\n\n## Quick start\n\n```python\nimport acceldata_aio_tracer as aio\n\naio.configure(\n url="https://acme.acceldata.local:5443/aio",\n access_key="...",\n secret_key="...",\n project_name="default",\n)\n\n@aio.track\ndef summarize(text: str) -> str:\n # call an LLM, return the summary\n ...\n\nsummarize("hello world") # traced and sent to Acceldata\n```\n\n`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.\n\n## Configuration\n\nThere are two configuration entry points; pick based on usage style.\n\n### Signature stability\n\nBoth `configure()` and `AcceldataTracer()` follow the same shape — three required positional-or-keyword args followed by keyword-only options:\n\n```python\ndef configure(\n url: str, # required, stable public name\n access_key: str, # required, stable public name\n secret_key: str, # required, stable public name\n *, # everything below is keyword-only\n project_name: Optional[str] = None, # may evolve across releases\n debug: bool = False, # may evolve across releases\n check_tls_certificate: bool = True, # may evolve across releases\n) -> None: ...\n```\n\nWhat this means in practice:\n\n- **`url`, `access_key`, `secret_key` are required and will not be renamed.** Pass them positionally or by name — both forms are supported indefinitely.\n\n```python\n# Both calls are equivalent:\naio.configure("https://acme.acceldata.local:5443/aio", "ak", "sk")\naio.configure(url="https://acme.acceldata.local:5443/aio", access_key="ak", secret_key="sk")\n```\n\n- **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`).\n- **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"] = "..."`).\n\n### Programmatic (`configure`)\n\nUse this for workflows that rely on `@track` decorators or framework integrations — anything that creates the Opik client lazily.\n\n```python\nimport acceldata_aio_tracer as aio\n\naio.configure(\n url="https://acme.acceldata.local:5443/aio",\n access_key="...",\n secret_key="...",\n project_name="default", # optional\n debug=False, # optional, see Debugging\n check_tls_certificate=True, # set False only for self-signed dev gateways\n)\n```\n\n`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.\n\n### Explicit client (`AcceldataTracer`)\n\nUse this when you want a directly-constructed client (e.g. for non-decorator usage or to hold a per-request client).\n\n```python\nfrom acceldata_aio_tracer import AcceldataTracer\n\nclient = AcceldataTracer(\n url="https://acme.acceldata.local:5443/aio",\n access_key="...",\n secret_key="...",\n)\n# `client` is a fully-configured `opik.Opik` instance.\n```\n\nCalling `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.\n\n### Environment variables\n\n| Variable | Purpose |\n|---|---|\n| `ACCELDATA_AIO_URL` | Acceldata gateway URL, e.g. `https://acme.acceldata.local:5443/aio` |\n| `ACCELDATA_AIO_ACCESS_KEY` | Acceldata access key |\n| `ACCELDATA_AIO_SECRET_KEY` | Acceldata secret key |\n| `ACCELDATA_AIO_PROJECT_NAME` | Default project name for traces |\n| `ACCELDATA_AIO_CHECK_TLS_CERTIFICATE` | `false` disables TLS verify on outbound Opik calls (dev / self-signed gateways only) |\n| `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. |\n\nThe 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.\n\nAt 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):\n\n```\n[acceldata_aio_tracer] env init: URL<-ACCELDATA_AIO_URL, ACCESS_KEY<-ACCELDATA_AIO_ACCESS_KEY, SECRET_KEY<-ACCELDATA_AIO_SECRET_KEY\n```\n\n### Auto-init from env\n\nIf 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.\n\n```bash\nexport ACCELDATA_AIO_URL="https://acme.acceldata.local:5443/aio"\nexport ACCELDATA_AIO_ACCESS_KEY="..."\nexport ACCELDATA_AIO_SECRET_KEY="..."\n```\n\n```python\nimport acceldata_aio_tracer # auth hook registered, env-init summary printed to stderr\n```\n\n## Authentication\n\nAcceldata 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:\n\n```\naccessKey: <ACCESS_KEY>\nsecretKey: <SECRET_KEY>\n```\n\nThe hook is installed once per process and applies to every `httpx.Client` Opik creates afterward.\n\n## Integrations\n\nEvery 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.\n\n### Decorator-style integrations\n\nWrap an SDK client with `track_<framework>(...)`. Tracing is automatic from that point on.\n\n```python\nfrom openai import OpenAI\nfrom acceldata_aio_tracer.integrations.openai import track_openai\n\nclient = track_openai(OpenAI())\nclient.chat.completions.create(\n model="gpt-4o-mini",\n messages=[{"role": "user", "content": "hi"}],\n) # automatically traced\n```\n\nThe 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.\n\n### Callback / handler-style integrations\n\nInstantiate a tracer / callback / connector and pass it to the framework\'s callback machinery.\n\n```python\n# LangChain / LangGraph\nfrom acceldata_aio_tracer.integrations.langchain import LangChainTracer\n\ntracer = LangChainTracer()\nchain.invoke({"input": "..."}, config={"callbacks": [tracer]})\n```\n\n```python\n# Google ADK\nfrom acceldata_aio_tracer.integrations.adk import ADKTracer, track_adk_agent_recursive\n\ntracer = ADKTracer()\ntrack_adk_agent_recursive(my_agent, tracer)\n```\n\n```python\n# DSPy\nimport dspy\nfrom acceldata_aio_tracer.integrations.dspy import DSPyCallback\n\ndspy.configure(callbacks=[DSPyCallback()])\n```\n\n```python\n# Haystack\nfrom acceldata_aio_tracer.integrations.haystack import HaystackConnector\n\npipeline.add_component("opik", HaystackConnector())\n```\n\n```python\n# LlamaIndex\nfrom llama_index.core.callbacks import CallbackManager\nfrom acceldata_aio_tracer.integrations.llama_index import LlamaIndexCallbackHandler\n\nmanager = CallbackManager([LlamaIndexCallbackHandler()])\n```\n\n### Integration reference\n\n15 Opik integrations exist; 14 are mirrored here. Sagemaker is intentionally not exposed (upstream is an internal AWS auth helper with no public API).\n\n| Integration | Import path | Exported names | Style | Renamed from upstream |\n|---|---|---|---|---|\n| 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` |\n| Google ADK | `acceldata_aio_tracer.integrations.adk` | `ADKTracer`, `track_adk_agent_recursive`, `build_mermaid_graph_definition` | Callback | `OpikTracer` -> `ADKTracer` |\n| DSPy | `acceldata_aio_tracer.integrations.dspy` | `DSPyCallback` | Callback | `OpikCallback` -> `DSPyCallback` |\n| Haystack | `acceldata_aio_tracer.integrations.haystack` | `HaystackConnector` | Component | `OpikConnector` -> `HaystackConnector` |\n| LlamaIndex | `acceldata_aio_tracer.integrations.llama_index` | `LlamaIndexCallbackHandler` | Callback | (already framework-named) |\n| OpenAI | `acceldata_aio_tracer.integrations.openai` | `track_openai` | Decorator | (no rename) |\n| Anthropic | `acceldata_aio_tracer.integrations.anthropic` | `track_anthropic` | Decorator | (no rename) |\n| AWS Bedrock | `acceldata_aio_tracer.integrations.bedrock` | `track_bedrock` | Decorator | (no rename) |\n| Google GenAI | `acceldata_aio_tracer.integrations.genai` | `track_genai` | Decorator | (no rename) |\n| AISuite | `acceldata_aio_tracer.integrations.aisuite` | `track_aisuite` | Decorator | (no rename) |\n| LiteLLM | `acceldata_aio_tracer.integrations.litellm` | `track_completion` | Decorator | (no rename) |\n| CrewAI | `acceldata_aio_tracer.integrations.crewai` | `track_crewai` | Decorator | (no rename) |\n| Guardrails AI | `acceldata_aio_tracer.integrations.guardrails` | `track_guardrails` | Decorator | (no rename) |\n| Harbor | `acceldata_aio_tracer.integrations.harbor` | `track_harbor`, `reset_harbor_tracking` | Decorator | (no rename) |\n\nFor 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.\n\n## Adding a new integration\n\nWhen upstream Opik (`opik.integrations.<name>`) ships a new integration, mirror it here:\n\n1. Inspect the upstream `__init__.py` for the public exports.\n2. Create `src/acceldata_aio_tracer/integrations/<name>.py`:\n - **Decorator-style** (`track_<name>` function): `from opik.integrations.<name> import track_<name>` + `__all__ = ("track_<name>",)`.\n - **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`.\n - **Class-style with framework prefix** (already framework-named): pass-through re-export.\n3. Add a row to the [Integration reference](#integration-reference) table above.\n4. Smoke-test: `python -c "from acceldata_aio_tracer.integrations.<name> import *"`.\n\nThe wrapper does not auto-mirror — every new upstream integration needs an explicit file.\n\n## Debugging\n\nTo trace every Opik HTTP call (including a copy-paste `curl` reproducer for any 4xx/5xx response), enable debug mode:\n\n```python\naio.configure(url=..., access_key=..., secret_key=..., debug=True)\n# or\nclient = AcceldataTracer(url=..., access_key=..., secret_key=..., debug=True)\n```\n\nOn 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.\n\n## License\n\nApache-2.0. This SDK is a thin wrapper around [Opik](https://github.com/comet-ml/opik), which is also Apache-2.0 licensed.',
|
|
21
|
+
'author': 'Acceldata',
|
|
22
|
+
'author_email': 'None',
|
|
23
|
+
'maintainer': 'None',
|
|
24
|
+
'maintainer_email': 'None',
|
|
25
|
+
'url': 'None',
|
|
26
|
+
'package_dir': package_dir,
|
|
27
|
+
'packages': packages,
|
|
28
|
+
'package_data': package_data,
|
|
29
|
+
'install_requires': install_requires,
|
|
30
|
+
'python_requires': '>=3.10,<4.0',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
setup(**setup_kwargs)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import opik as _opik
|
|
4
|
+
from opik import * # noqa: F401, F403
|
|
5
|
+
|
|
6
|
+
from . import _opik_bridge
|
|
7
|
+
from .auth import (
|
|
8
|
+
AcceldataAuth,
|
|
9
|
+
setup_acceldata_auth,
|
|
10
|
+
setup_acceldata_auth_hook,
|
|
11
|
+
)
|
|
12
|
+
from .client import AcceldataTracer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def configure(
|
|
16
|
+
url: str,
|
|
17
|
+
access_key: str,
|
|
18
|
+
secret_key: str,
|
|
19
|
+
*,
|
|
20
|
+
project_name: Optional[str] = None,
|
|
21
|
+
debug: bool = False,
|
|
22
|
+
check_tls_certificate: bool = True,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Acceldata equivalent of opik.configure().
|
|
25
|
+
|
|
26
|
+
Registers the Acceldata auth hook so every subsequent Opik client
|
|
27
|
+
carries accessKey/secretKey headers, applies the URL/project settings,
|
|
28
|
+
and optionally installs an HTTP debug interceptor.
|
|
29
|
+
|
|
30
|
+
The first three parameters (``url``, ``access_key``, ``secret_key``) are
|
|
31
|
+
the SDK's stable public surface and may be passed positionally or by
|
|
32
|
+
name. All other options are keyword-only so future flags can be added,
|
|
33
|
+
deprecated, or removed without breaking customer call sites.
|
|
34
|
+
|
|
35
|
+
Each argument has a matching ``ACCELDATA_AIO_*`` env variable that can
|
|
36
|
+
be used instead of an explicit call. If both are present the explicit
|
|
37
|
+
argument wins; if neither, ``configure()`` cannot be skipped. Upstream
|
|
38
|
+
``OPIK_*`` names are accepted as a fallback (see README "Environment
|
|
39
|
+
variables").
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
url: Acceldata gateway URL, e.g.
|
|
43
|
+
``https://acme.acceldata.local:5443/aio``.
|
|
44
|
+
Env: ``ACCELDATA_AIO_URL`` (fallback: ``OPIK_URL_OVERRIDE``).
|
|
45
|
+
access_key: Acceldata API access key.
|
|
46
|
+
Env: ``ACCELDATA_AIO_ACCESS_KEY``
|
|
47
|
+
(fallback: ``OPIK_ACCESS_KEY``).
|
|
48
|
+
secret_key: Acceldata API secret key.
|
|
49
|
+
Env: ``ACCELDATA_AIO_SECRET_KEY``
|
|
50
|
+
(fallback: ``OPIK_SECRET_KEY``).
|
|
51
|
+
project_name: Default project name for traces.
|
|
52
|
+
Env: ``ACCELDATA_AIO_PROJECT_NAME``
|
|
53
|
+
(fallback: ``OPIK_PROJECT_NAME``).
|
|
54
|
+
debug: When True, install an httpx interceptor that logs every Opik
|
|
55
|
+
HTTP call and, on >=400 responses, dumps the request body to
|
|
56
|
+
disk and prints a copy-paste curl for replay. Off by default.
|
|
57
|
+
Env: ``ACCELDATA_AIO_DEBUG`` accepts ``1`` / ``true`` / ``yes``
|
|
58
|
+
/ ``on`` (case-insensitive) to enable. Wrapper-specific — has
|
|
59
|
+
no upstream ``OPIK_*`` fallback.
|
|
60
|
+
check_tls_certificate: When False, disables TLS certificate
|
|
61
|
+
verification for outbound HTTP calls. Use only for local dev
|
|
62
|
+
against self-signed gateways. Default True.
|
|
63
|
+
Env: ``ACCELDATA_AIO_CHECK_TLS_CERTIFICATE``
|
|
64
|
+
(fallback: ``OPIK_CHECK_TLS_CERTIFICATE``).
|
|
65
|
+
"""
|
|
66
|
+
_opik_bridge.apply_configure(
|
|
67
|
+
url=url,
|
|
68
|
+
access_key=access_key,
|
|
69
|
+
secret_key=secret_key,
|
|
70
|
+
project_name=project_name,
|
|
71
|
+
debug=debug,
|
|
72
|
+
check_tls_certificate=check_tls_certificate,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Bootstrap ACCELDATA_AIO_* env vars and auto-register the auth hook at
|
|
77
|
+
# import time. All upstream-naming details live in _opik_bridge so this
|
|
78
|
+
# module stays free of OPIK_* references.
|
|
79
|
+
_opik_bridge.bootstrap_from_env()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
__all__ = (
|
|
83
|
+
"AcceldataTracer",
|
|
84
|
+
"AcceldataAuth",
|
|
85
|
+
"configure",
|
|
86
|
+
"setup_acceldata_auth",
|
|
87
|
+
"setup_acceldata_auth_hook",
|
|
88
|
+
*_opik.__all__,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
del _opik
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Bridge between the Acceldata-branded API surface and upstream Opik internals.
|
|
2
|
+
|
|
3
|
+
Everything in this module knows about the OPIK_* env-var names and the
|
|
4
|
+
upstream Opik configuration keys. Nothing outside this module should
|
|
5
|
+
reference them — that keeps the public package surface (__init__.py,
|
|
6
|
+
configure(), etc.) brand-clean.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from .auth import setup_acceldata_auth_hook
|
|
14
|
+
from .client import _install_http_debug_interceptor
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Customer-facing AIO env var name -> upstream Opik env var name.
|
|
18
|
+
# Upstream names are an internal implementation detail and never exposed
|
|
19
|
+
# in the public API.
|
|
20
|
+
_ALIASES = (
|
|
21
|
+
("URL", "ACCELDATA_AIO_URL", "OPIK_URL_OVERRIDE"),
|
|
22
|
+
("ACCESS_KEY", "ACCELDATA_AIO_ACCESS_KEY", "OPIK_ACCESS_KEY"),
|
|
23
|
+
("SECRET_KEY", "ACCELDATA_AIO_SECRET_KEY", "OPIK_SECRET_KEY"),
|
|
24
|
+
("PROJECT_NAME", "ACCELDATA_AIO_PROJECT_NAME", "OPIK_PROJECT_NAME"),
|
|
25
|
+
("CHECK_TLS_CERTIFICATE", "ACCELDATA_AIO_CHECK_TLS_CERTIFICATE", "OPIK_CHECK_TLS_CERTIFICATE"),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _parse_truthy(value: str) -> bool:
|
|
30
|
+
"""True for common truthy string representations: 1, true, yes, on (case-insensitive)."""
|
|
31
|
+
return value.strip().lower() in ("1", "true", "yes", "on")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def bootstrap_from_env() -> None:
|
|
35
|
+
"""Translate ACCELDATA_AIO_* env vars to the upstream names Opik reads
|
|
36
|
+
natively, then auto-register the auth hook if creds are present and
|
|
37
|
+
install the HTTP debug interceptor if ``ACCELDATA_AIO_DEBUG`` is truthy.
|
|
38
|
+
|
|
39
|
+
An explicit upstream value (e.g. OPIK_URL_OVERRIDE) wins over its
|
|
40
|
+
ACCELDATA_AIO_* counterpart — escape hatch for migration, ops scripts,
|
|
41
|
+
or power-user overrides.
|
|
42
|
+
|
|
43
|
+
Prints a one-line stderr summary naming which env var fed each setting
|
|
44
|
+
(names only, never values).
|
|
45
|
+
"""
|
|
46
|
+
picked = []
|
|
47
|
+
for label, aio_name, upstream_name in _ALIASES:
|
|
48
|
+
if os.environ.get(upstream_name) is not None:
|
|
49
|
+
picked.append((label, upstream_name))
|
|
50
|
+
elif os.environ.get(aio_name) is not None:
|
|
51
|
+
os.environ[upstream_name] = os.environ[aio_name]
|
|
52
|
+
picked.append((label, aio_name))
|
|
53
|
+
|
|
54
|
+
debug_raw = os.environ.get("ACCELDATA_AIO_DEBUG")
|
|
55
|
+
debug_enabled = debug_raw is not None and _parse_truthy(debug_raw)
|
|
56
|
+
if debug_enabled:
|
|
57
|
+
picked.append(("DEBUG", "ACCELDATA_AIO_DEBUG"))
|
|
58
|
+
|
|
59
|
+
if picked:
|
|
60
|
+
summary = ", ".join(f"{label}<-{source}" for label, source in picked)
|
|
61
|
+
print(f"[acceldata_aio_tracer] env init: {summary}", file=sys.stderr)
|
|
62
|
+
else:
|
|
63
|
+
print(
|
|
64
|
+
"[acceldata_aio_tracer] env init: no ACCELDATA_AIO_* or upstream "
|
|
65
|
+
"env vars detected; call configure() to set explicitly",
|
|
66
|
+
file=sys.stderr,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
access_key = os.environ.get("OPIK_ACCESS_KEY")
|
|
70
|
+
secret_key = os.environ.get("OPIK_SECRET_KEY")
|
|
71
|
+
if access_key and secret_key:
|
|
72
|
+
setup_acceldata_auth_hook(access_key, secret_key)
|
|
73
|
+
|
|
74
|
+
if debug_enabled:
|
|
75
|
+
url = os.environ.get("OPIK_URL_OVERRIDE")
|
|
76
|
+
if url:
|
|
77
|
+
_install_http_debug_interceptor(url)
|
|
78
|
+
else:
|
|
79
|
+
print(
|
|
80
|
+
"[acceldata_aio_tracer] env init: ACCELDATA_AIO_DEBUG is truthy "
|
|
81
|
+
"but no URL configured; debug interceptor not installed",
|
|
82
|
+
file=sys.stderr,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def apply_configure(
|
|
87
|
+
*,
|
|
88
|
+
url: str,
|
|
89
|
+
access_key: str,
|
|
90
|
+
secret_key: str,
|
|
91
|
+
project_name: Optional[str],
|
|
92
|
+
debug: bool,
|
|
93
|
+
check_tls_certificate: bool,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Apply the side effects of a configure() call: write upstream env vars,
|
|
96
|
+
register the Acceldata auth hook, optionally install the HTTP debug
|
|
97
|
+
interceptor.
|
|
98
|
+
"""
|
|
99
|
+
os.environ["OPIK_URL_OVERRIDE"] = url
|
|
100
|
+
os.environ["OPIK_ACCESS_KEY"] = access_key
|
|
101
|
+
os.environ["OPIK_SECRET_KEY"] = secret_key
|
|
102
|
+
if project_name is not None:
|
|
103
|
+
os.environ["OPIK_PROJECT_NAME"] = project_name
|
|
104
|
+
setup_acceldata_auth_hook(access_key, secret_key)
|
|
105
|
+
os.environ["OPIK_CHECK_TLS_CERTIFICATE"] = "true" if check_tls_certificate else "false"
|
|
106
|
+
if debug:
|
|
107
|
+
_install_http_debug_interceptor(url)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
import opik.hooks
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AcceldataAuth(httpx.Auth):
|
|
8
|
+
def __init__(self, access_key: str, secret_key: str) -> None:
|
|
9
|
+
self.access_key = access_key
|
|
10
|
+
self.secret_key = secret_key
|
|
11
|
+
|
|
12
|
+
def auth_flow(self, request): # type: ignore
|
|
13
|
+
request.headers.pop("authorization", None)
|
|
14
|
+
request.headers["accessKey"] = self.access_key
|
|
15
|
+
request.headers["secretKey"] = self.secret_key
|
|
16
|
+
yield request
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def setup_acceldata_auth_hook(access_key: str, secret_key: str) -> None:
|
|
20
|
+
def acceldata_auth_client_hook(client: httpx.Client) -> None:
|
|
21
|
+
client.auth = AcceldataAuth(access_key, secret_key)
|
|
22
|
+
|
|
23
|
+
opik.hooks.add_httpx_client_hook(
|
|
24
|
+
opik.hooks.HttpxClientHook(
|
|
25
|
+
client_modifier=acceldata_auth_client_hook,
|
|
26
|
+
client_init_arguments=None,
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def setup_acceldata_auth(url: str, access_key: str, secret_key: str) -> None:
|
|
32
|
+
"""Boot-time setup for users of @opik.track and Opik integrations.
|
|
33
|
+
|
|
34
|
+
Call once before any tracked function runs. Subsequent Opik clients
|
|
35
|
+
(including the cached one used by @track) will pick up the auth.
|
|
36
|
+
"""
|
|
37
|
+
os.environ.setdefault("OPIK_URL_OVERRIDE", url)
|
|
38
|
+
setup_acceldata_auth_hook(access_key, secret_key)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import shlex
|
|
4
|
+
from typing import Any
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
|
|
7
|
+
from opik import Opik
|
|
8
|
+
|
|
9
|
+
from .auth import setup_acceldata_auth_hook
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_DEBUG_REQ_DUMP_PATH = "/tmp/acceldata-aio-tracer-last-failed-request.bin"
|
|
13
|
+
_debug_installed = False
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _install_http_debug_interceptor(opik_url: str) -> None:
|
|
17
|
+
"""Patch httpx.Client.send and httpx.AsyncClient.send to log every Opik
|
|
18
|
+
HTTP call. On responses with status >= 400, dumps the request body to
|
|
19
|
+
`_DEBUG_REQ_DUMP_PATH` and prints a copy-paste curl that replays the
|
|
20
|
+
request. Idempotent — safe to call multiple times.
|
|
21
|
+
"""
|
|
22
|
+
global _debug_installed
|
|
23
|
+
if _debug_installed:
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
import httpx
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger("acceldata_aio_tracer.http_trace")
|
|
29
|
+
logger.setLevel(logging.DEBUG)
|
|
30
|
+
if not logger.handlers:
|
|
31
|
+
h = logging.StreamHandler()
|
|
32
|
+
h.setFormatter(logging.Formatter(
|
|
33
|
+
"%(asctime)s %(levelname)-5s acceldata_aio_tracer.http_trace: %(message)s"
|
|
34
|
+
))
|
|
35
|
+
logger.addHandler(h)
|
|
36
|
+
logger.propagate = False
|
|
37
|
+
|
|
38
|
+
target_host = urlparse(opik_url).netloc
|
|
39
|
+
|
|
40
|
+
def is_opik_url(url) -> bool:
|
|
41
|
+
try:
|
|
42
|
+
return urlparse(str(url)).netloc == target_host
|
|
43
|
+
except Exception:
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
def fmt_body(body) -> str:
|
|
47
|
+
if body is None:
|
|
48
|
+
return "<none>"
|
|
49
|
+
if isinstance(body, (bytes, bytearray)):
|
|
50
|
+
try:
|
|
51
|
+
return body.decode("utf-8", errors="replace")[:4000]
|
|
52
|
+
except Exception:
|
|
53
|
+
return f"<{len(body)} bytes>"
|
|
54
|
+
return str(body)[:4000]
|
|
55
|
+
|
|
56
|
+
def decode_resp(resp_body) -> str:
|
|
57
|
+
if isinstance(resp_body, (bytes, bytearray)):
|
|
58
|
+
return resp_body.decode("utf-8", errors="replace")
|
|
59
|
+
return str(resp_body)
|
|
60
|
+
|
|
61
|
+
def build_replay_curl(method: str, url: str, headers: dict, body_path) -> str:
|
|
62
|
+
parts = ["curl -v --insecure -X", method, shlex.quote(str(url))]
|
|
63
|
+
for k, v in headers.items():
|
|
64
|
+
if k.lower() in ("host", "content-length"):
|
|
65
|
+
continue
|
|
66
|
+
parts += ["-H", shlex.quote(f"{k}: {v}")]
|
|
67
|
+
if body_path:
|
|
68
|
+
parts += ["--data-binary", f"@{body_path}"]
|
|
69
|
+
return " \\\n ".join(parts)
|
|
70
|
+
|
|
71
|
+
def dump_request_body(request) -> str:
|
|
72
|
+
if not request.content:
|
|
73
|
+
return ""
|
|
74
|
+
try:
|
|
75
|
+
with open(_DEBUG_REQ_DUMP_PATH, "wb") as f:
|
|
76
|
+
f.write(request.content)
|
|
77
|
+
return _DEBUG_REQ_DUMP_PATH
|
|
78
|
+
except Exception as write_err:
|
|
79
|
+
logger.warning(f"failed to dump request body: {write_err}")
|
|
80
|
+
return ""
|
|
81
|
+
|
|
82
|
+
def log_failure(request, response, resp_body, async_marker: str = "") -> None:
|
|
83
|
+
body_path = dump_request_body(request)
|
|
84
|
+
decoded = decode_resp(resp_body)[:8000]
|
|
85
|
+
curl = build_replay_curl(
|
|
86
|
+
request.method, str(request.url), dict(request.headers), body_path
|
|
87
|
+
)
|
|
88
|
+
logger.error(
|
|
89
|
+
f"<<< {async_marker}{response.status_code} {request.method} {request.url}\n"
|
|
90
|
+
f" error_body={decoded}\n"
|
|
91
|
+
f" request_body_dumped_to={body_path or '<none>'}\n"
|
|
92
|
+
f" REPLAY (paste into shell):\n{curl}"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
orig_send = httpx.Client.send
|
|
96
|
+
|
|
97
|
+
def patched_send(self, request, *args, **kwargs):
|
|
98
|
+
log_this = is_opik_url(request.url)
|
|
99
|
+
if log_this:
|
|
100
|
+
logger.debug(
|
|
101
|
+
f">>> {request.method} {request.url}\n"
|
|
102
|
+
f" headers={dict(request.headers)}\n"
|
|
103
|
+
f" body={fmt_body(request.content)}"
|
|
104
|
+
)
|
|
105
|
+
response = orig_send(self, request, *args, **kwargs)
|
|
106
|
+
if log_this:
|
|
107
|
+
try:
|
|
108
|
+
resp_body = response.read()
|
|
109
|
+
except Exception as read_err:
|
|
110
|
+
resp_body = f"<read failed: {read_err}>".encode()
|
|
111
|
+
if response.status_code >= 400:
|
|
112
|
+
log_failure(request, response, resp_body)
|
|
113
|
+
else:
|
|
114
|
+
logger.debug(
|
|
115
|
+
f"<<< {response.status_code} {request.method} {request.url}\n"
|
|
116
|
+
f" body={fmt_body(resp_body)}"
|
|
117
|
+
)
|
|
118
|
+
return response
|
|
119
|
+
|
|
120
|
+
httpx.Client.send = patched_send
|
|
121
|
+
|
|
122
|
+
orig_async_send = httpx.AsyncClient.send
|
|
123
|
+
|
|
124
|
+
async def patched_async_send(self, request, *args, **kwargs):
|
|
125
|
+
log_this = is_opik_url(request.url)
|
|
126
|
+
if log_this:
|
|
127
|
+
logger.debug(
|
|
128
|
+
f">>> [async] {request.method} {request.url}\n"
|
|
129
|
+
f" headers={dict(request.headers)}\n"
|
|
130
|
+
f" body={fmt_body(request.content)}"
|
|
131
|
+
)
|
|
132
|
+
response = await orig_async_send(self, request, *args, **kwargs)
|
|
133
|
+
if log_this:
|
|
134
|
+
try:
|
|
135
|
+
resp_body = await response.aread()
|
|
136
|
+
except Exception as read_err:
|
|
137
|
+
resp_body = f"<read failed: {read_err}>".encode()
|
|
138
|
+
if response.status_code >= 400:
|
|
139
|
+
log_failure(request, response, resp_body, async_marker="[async] ")
|
|
140
|
+
else:
|
|
141
|
+
logger.debug(
|
|
142
|
+
f"<<< [async] {response.status_code} {request.method} {request.url}\n"
|
|
143
|
+
f" body={fmt_body(resp_body)}"
|
|
144
|
+
)
|
|
145
|
+
return response
|
|
146
|
+
|
|
147
|
+
httpx.AsyncClient.send = patched_async_send
|
|
148
|
+
|
|
149
|
+
_debug_installed = True
|
|
150
|
+
logger.info(f"http debug interceptor installed for host={target_host}")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def AcceldataTracer(
|
|
154
|
+
url: str,
|
|
155
|
+
access_key: str,
|
|
156
|
+
secret_key: str,
|
|
157
|
+
*,
|
|
158
|
+
debug: bool = False,
|
|
159
|
+
check_tls_certificate: bool = True,
|
|
160
|
+
**kwargs: Any,
|
|
161
|
+
) -> Opik:
|
|
162
|
+
setup_acceldata_auth_hook(access_key, secret_key)
|
|
163
|
+
os.environ["OPIK_CHECK_TLS_CERTIFICATE"] = "true" if check_tls_certificate else "false"
|
|
164
|
+
if debug:
|
|
165
|
+
_install_http_debug_interceptor(url)
|
|
166
|
+
return Opik(host=url, **kwargs)
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from opik.integrations.langchain import (
|
|
2
|
+
LANGGRAPH_INTERRUPT_METADATA_KEY,
|
|
3
|
+
LANGGRAPH_INTERRUPT_OUTPUT_KEY,
|
|
4
|
+
LANGGRAPH_PARENT_COMMAND_METADATA_KEY,
|
|
5
|
+
LANGGRAPH_RESUME_INPUT_KEY,
|
|
6
|
+
OpikTracer as LangChainTracer,
|
|
7
|
+
extract_current_langgraph_span_data,
|
|
8
|
+
track_langgraph,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = (
|
|
12
|
+
"LangChainTracer",
|
|
13
|
+
"track_langgraph",
|
|
14
|
+
"extract_current_langgraph_span_data",
|
|
15
|
+
"LANGGRAPH_INTERRUPT_OUTPUT_KEY",
|
|
16
|
+
"LANGGRAPH_RESUME_INPUT_KEY",
|
|
17
|
+
"LANGGRAPH_INTERRUPT_METADATA_KEY",
|
|
18
|
+
"LANGGRAPH_PARENT_COMMAND_METADATA_KEY",
|
|
19
|
+
)
|