nexus-llmops 0.1.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.
- nexus_llmops-0.1.0/.gitignore +56 -0
- nexus_llmops-0.1.0/LICENSE +21 -0
- nexus_llmops-0.1.0/PKG-INFO +137 -0
- nexus_llmops-0.1.0/README.md +113 -0
- nexus_llmops-0.1.0/nexus_agent/__init__.py +38 -0
- nexus_llmops-0.1.0/nexus_llmops/__init__.py +121 -0
- nexus_llmops-0.1.0/nexus_llmops/client.py +509 -0
- nexus_llmops-0.1.0/nexus_llmops/config.py +65 -0
- nexus_llmops-0.1.0/nexus_llmops/decorators.py +220 -0
- nexus_llmops-0.1.0/nexus_llmops/exporters/__init__.py +1 -0
- nexus_llmops-0.1.0/nexus_llmops/exporters/http.py +58 -0
- nexus_llmops-0.1.0/nexus_llmops/exporters/stdout.py +31 -0
- nexus_llmops-0.1.0/nexus_llmops/models.py +206 -0
- nexus_llmops-0.1.0/pyproject.toml +41 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
.coverage
|
|
14
|
+
coverage.xml
|
|
15
|
+
htmlcov/
|
|
16
|
+
|
|
17
|
+
# Env / secrets
|
|
18
|
+
.env
|
|
19
|
+
*.local
|
|
20
|
+
|
|
21
|
+
# Node / dashboard
|
|
22
|
+
node_modules/
|
|
23
|
+
dashboard/dist/
|
|
24
|
+
.vite/
|
|
25
|
+
|
|
26
|
+
# ML / data artifacts
|
|
27
|
+
mlruns/
|
|
28
|
+
*.pkl
|
|
29
|
+
*.faiss
|
|
30
|
+
# regenerable embedding cache (keep trained model under data/classifier_model/)
|
|
31
|
+
data/synthetic_embeddings.npy
|
|
32
|
+
data/master_embeddings.npy
|
|
33
|
+
|
|
34
|
+
# OS / editor
|
|
35
|
+
.DS_Store
|
|
36
|
+
Thumbs.db
|
|
37
|
+
.idea/
|
|
38
|
+
.vscode/
|
|
39
|
+
|
|
40
|
+
# Experiment caches and cost ledgers are local state, not outputs; results/ is the
|
|
41
|
+
# only tracked output (DECISIONS D48). data/nexusbench/ is benchmark data, tracked.
|
|
42
|
+
data/phase2/
|
|
43
|
+
data/phase4/
|
|
44
|
+
|
|
45
|
+
# Phase 5 local state and private exports (never commit).
|
|
46
|
+
runs/
|
|
47
|
+
logs/
|
|
48
|
+
*.jsonl
|
|
49
|
+
*.patch
|
|
50
|
+
frozen_patches_export.tgz
|
|
51
|
+
data/phase5/runs/
|
|
52
|
+
data/phase6/runs/
|
|
53
|
+
|
|
54
|
+
# CI stages exact model revisions locally; inference weights are not source.
|
|
55
|
+
/cross-encoder/
|
|
56
|
+
/sentence-transformers/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sankalp Rajeev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: nexus-llmops
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Nexus: fail-open agent tracing for the Nexus failure-detection and fix-verification service
|
|
5
|
+
Project-URL: Homepage, https://github.com/sankalp-rajeev/Nexus
|
|
6
|
+
Project-URL: Repository, https://github.com/sankalp-rajeev/Nexus
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/sankalp-rajeev/Nexus/issues
|
|
8
|
+
Author-email: Sankalp Rajeev <srajeev@umich.edu>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,ai,crewai,langchain,llmops,mlops,observability,tracing
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: pydantic>=2.7
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# nexus-llmops
|
|
26
|
+
|
|
27
|
+
Python SDK for [Nexus](https://github.com/sankalp-rajeev/Nexus): fail-open agent tracing for
|
|
28
|
+
the Nexus failure-detection and fix-verification service.
|
|
29
|
+
|
|
30
|
+
The SDK records what your agent pipeline does (LLM calls, tool calls, retrieval, memory reads
|
|
31
|
+
and writes, agent steps) as spans and sends them to a Nexus collector. It does no analysis
|
|
32
|
+
itself: failure detection (loops, tool misfires, context bleed, plan drift, hallucination) runs
|
|
33
|
+
in the Nexus service, which you run separately. See the main repository for how.
|
|
34
|
+
|
|
35
|
+
> **Status: alpha, `0.1.0`.** The API can still change in later `0.x` releases.
|
|
36
|
+
|
|
37
|
+
- Distribution: `nexus-llmops`. Import name: `nexus_llmops`.
|
|
38
|
+
- Python 3.11+. Dependencies: `httpx` and `pydantic` only (no OpenTelemetry, no protobuf).
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install nexus-llmops
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Minimal example
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import nexus_llmops as nexus
|
|
50
|
+
|
|
51
|
+
nexus.init(agent_name="my-pipeline", collector_url="http://localhost:8000")
|
|
52
|
+
|
|
53
|
+
@nexus.trace(span_type="tool_call")
|
|
54
|
+
def search(query: str) -> str:
|
|
55
|
+
return "results for " + query
|
|
56
|
+
|
|
57
|
+
@nexus.trace(span_type="agent_step")
|
|
58
|
+
def run(task: str) -> str:
|
|
59
|
+
return search(task)
|
|
60
|
+
|
|
61
|
+
run("find the order")
|
|
62
|
+
nexus.flush() # before a short-lived script exits
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`collector_url` is the collector's address: `http://localhost:8000` for the full stack
|
|
66
|
+
(`docker-compose.yml`), `http://127.0.0.1:18000` for lite mode (`docker-compose.lite.yml`).
|
|
67
|
+
|
|
68
|
+
Spans nest automatically (trace and parent IDs propagate via `contextvars`). Three ways to
|
|
69
|
+
instrument:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# 1. decorator (bare or with arguments; sync and async)
|
|
73
|
+
@nexus.trace
|
|
74
|
+
def plan(task: str) -> list[str]: ...
|
|
75
|
+
|
|
76
|
+
@nexus.trace(span_type="llm_call", model="my-model")
|
|
77
|
+
def call_llm(prompt: str) -> str: ...
|
|
78
|
+
|
|
79
|
+
# 2. context manager
|
|
80
|
+
with nexus.span("tool_call", input=query, tool="search") as s:
|
|
81
|
+
s.set_output(search_tool.run(query))
|
|
82
|
+
|
|
83
|
+
# 3. explicit start/end
|
|
84
|
+
span_id = nexus.start_span("agent_step", step="retrieve")
|
|
85
|
+
nexus.end_span(span_id, output=result)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Fail-open: your agent is never in Nexus's way
|
|
89
|
+
|
|
90
|
+
- Nexus only wraps your call to capture timing, output and exceptions. Exceptions from your code
|
|
91
|
+
always propagate, after being recorded. Nexus's own failures never do.
|
|
92
|
+
- If the collector is down or slow, your program keeps running and finishes normally. Spans that
|
|
93
|
+
cannot be delivered are dropped and **counted**, not retried forever.
|
|
94
|
+
- With default asynchronous export, capturing a span only enqueues it; it does not wait for HTTP.
|
|
95
|
+
Each failed export batch is retried **once** on the background thread after a jittered
|
|
96
|
+
100-300 ms backoff, then dropped and counted. That covers brief transient failures. During a
|
|
97
|
+
sustained outage batches are dropped (with a throttled warning) and memory stays capped. There
|
|
98
|
+
is no requeue and no disk persistence, so this is **not a zero-loss guarantee**. A retried batch
|
|
99
|
+
is delivered at least once, so a collector can see a span twice; span IDs are stable.
|
|
100
|
+
- The queue is bounded two ways, dropping the incoming newest span when exceeded:
|
|
101
|
+
`max_queue_size` (default 10,000 spans) and `max_queue_bytes` (default 32 MiB; the byte size is
|
|
102
|
+
an estimate, not exact process memory). Zero or negative limits raise `ValueError`; unbounded
|
|
103
|
+
queues are not allowed.
|
|
104
|
+
- `nexus.shutdown(timeout=None)` (default `shutdown_timeout_ms`, 5000) drains for at most that
|
|
105
|
+
long, then discards what is still queued and counts it as `dropped_shutdown`. `flush()` has no
|
|
106
|
+
deadline; synchronous export and `flush()` can wait for HTTP attempts and never retry.
|
|
107
|
+
|
|
108
|
+
The client returned by `nexus.init(...)` exposes process-lifetime counters in `client.stats`:
|
|
109
|
+
`produced`, `exported`, `queued`, `pending`, `export_retried` (retry attempts), and the losses
|
|
110
|
+
`dropped_queue_full`, `dropped_bytes_cap`, `export_failed` (spans lost after a failed export and
|
|
111
|
+
its retry) and `dropped_shutdown`, whose sum is `dropped_total`. When idle,
|
|
112
|
+
`produced == exported + dropped_total + queued`. Counters report what happened; they are not
|
|
113
|
+
durable delivery guarantees.
|
|
114
|
+
|
|
115
|
+
## Trace completion
|
|
116
|
+
|
|
117
|
+
When the outermost (root) span of a trace ends, the SDK sets `metadata.final = True` on it, so
|
|
118
|
+
the Nexus service can finish and classify the trace right away instead of waiting for an idle
|
|
119
|
+
timeout. Child spans are unchanged. A child span that is delivered after its root has already
|
|
120
|
+
arrived (for example a retried batch) is dropped by the service and counted there.
|
|
121
|
+
|
|
122
|
+
## Compatibility alias
|
|
123
|
+
|
|
124
|
+
`nexus_agent` is an alias for `nexus_llmops`: the same module objects under the old name (the
|
|
125
|
+
Nexus research code uses it). New code should import `nexus_llmops`.
|
|
126
|
+
|
|
127
|
+
## Not included
|
|
128
|
+
|
|
129
|
+
- No LangChain or other framework callbacks yet (planned, not implemented).
|
|
130
|
+
- No OpenTelemetry exporter here: an application that already uses OpenTelemetry can send OTLP
|
|
131
|
+
directly to the Nexus collector's `/v1/traces` endpoint; that converter is server-side.
|
|
132
|
+
|
|
133
|
+
## Links
|
|
134
|
+
|
|
135
|
+
- [Platform and docs](https://github.com/sankalp-rajeev/Nexus)
|
|
136
|
+
- [Issues](https://github.com/sankalp-rajeev/Nexus/issues)
|
|
137
|
+
- License: MIT (see `LICENSE` in the distribution)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# nexus-llmops
|
|
2
|
+
|
|
3
|
+
Python SDK for [Nexus](https://github.com/sankalp-rajeev/Nexus): fail-open agent tracing for
|
|
4
|
+
the Nexus failure-detection and fix-verification service.
|
|
5
|
+
|
|
6
|
+
The SDK records what your agent pipeline does (LLM calls, tool calls, retrieval, memory reads
|
|
7
|
+
and writes, agent steps) as spans and sends them to a Nexus collector. It does no analysis
|
|
8
|
+
itself: failure detection (loops, tool misfires, context bleed, plan drift, hallucination) runs
|
|
9
|
+
in the Nexus service, which you run separately. See the main repository for how.
|
|
10
|
+
|
|
11
|
+
> **Status: alpha, `0.1.0`.** The API can still change in later `0.x` releases.
|
|
12
|
+
|
|
13
|
+
- Distribution: `nexus-llmops`. Import name: `nexus_llmops`.
|
|
14
|
+
- Python 3.11+. Dependencies: `httpx` and `pydantic` only (no OpenTelemetry, no protobuf).
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install nexus-llmops
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Minimal example
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import nexus_llmops as nexus
|
|
26
|
+
|
|
27
|
+
nexus.init(agent_name="my-pipeline", collector_url="http://localhost:8000")
|
|
28
|
+
|
|
29
|
+
@nexus.trace(span_type="tool_call")
|
|
30
|
+
def search(query: str) -> str:
|
|
31
|
+
return "results for " + query
|
|
32
|
+
|
|
33
|
+
@nexus.trace(span_type="agent_step")
|
|
34
|
+
def run(task: str) -> str:
|
|
35
|
+
return search(task)
|
|
36
|
+
|
|
37
|
+
run("find the order")
|
|
38
|
+
nexus.flush() # before a short-lived script exits
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`collector_url` is the collector's address: `http://localhost:8000` for the full stack
|
|
42
|
+
(`docker-compose.yml`), `http://127.0.0.1:18000` for lite mode (`docker-compose.lite.yml`).
|
|
43
|
+
|
|
44
|
+
Spans nest automatically (trace and parent IDs propagate via `contextvars`). Three ways to
|
|
45
|
+
instrument:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
# 1. decorator (bare or with arguments; sync and async)
|
|
49
|
+
@nexus.trace
|
|
50
|
+
def plan(task: str) -> list[str]: ...
|
|
51
|
+
|
|
52
|
+
@nexus.trace(span_type="llm_call", model="my-model")
|
|
53
|
+
def call_llm(prompt: str) -> str: ...
|
|
54
|
+
|
|
55
|
+
# 2. context manager
|
|
56
|
+
with nexus.span("tool_call", input=query, tool="search") as s:
|
|
57
|
+
s.set_output(search_tool.run(query))
|
|
58
|
+
|
|
59
|
+
# 3. explicit start/end
|
|
60
|
+
span_id = nexus.start_span("agent_step", step="retrieve")
|
|
61
|
+
nexus.end_span(span_id, output=result)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Fail-open: your agent is never in Nexus's way
|
|
65
|
+
|
|
66
|
+
- Nexus only wraps your call to capture timing, output and exceptions. Exceptions from your code
|
|
67
|
+
always propagate, after being recorded. Nexus's own failures never do.
|
|
68
|
+
- If the collector is down or slow, your program keeps running and finishes normally. Spans that
|
|
69
|
+
cannot be delivered are dropped and **counted**, not retried forever.
|
|
70
|
+
- With default asynchronous export, capturing a span only enqueues it; it does not wait for HTTP.
|
|
71
|
+
Each failed export batch is retried **once** on the background thread after a jittered
|
|
72
|
+
100-300 ms backoff, then dropped and counted. That covers brief transient failures. During a
|
|
73
|
+
sustained outage batches are dropped (with a throttled warning) and memory stays capped. There
|
|
74
|
+
is no requeue and no disk persistence, so this is **not a zero-loss guarantee**. A retried batch
|
|
75
|
+
is delivered at least once, so a collector can see a span twice; span IDs are stable.
|
|
76
|
+
- The queue is bounded two ways, dropping the incoming newest span when exceeded:
|
|
77
|
+
`max_queue_size` (default 10,000 spans) and `max_queue_bytes` (default 32 MiB; the byte size is
|
|
78
|
+
an estimate, not exact process memory). Zero or negative limits raise `ValueError`; unbounded
|
|
79
|
+
queues are not allowed.
|
|
80
|
+
- `nexus.shutdown(timeout=None)` (default `shutdown_timeout_ms`, 5000) drains for at most that
|
|
81
|
+
long, then discards what is still queued and counts it as `dropped_shutdown`. `flush()` has no
|
|
82
|
+
deadline; synchronous export and `flush()` can wait for HTTP attempts and never retry.
|
|
83
|
+
|
|
84
|
+
The client returned by `nexus.init(...)` exposes process-lifetime counters in `client.stats`:
|
|
85
|
+
`produced`, `exported`, `queued`, `pending`, `export_retried` (retry attempts), and the losses
|
|
86
|
+
`dropped_queue_full`, `dropped_bytes_cap`, `export_failed` (spans lost after a failed export and
|
|
87
|
+
its retry) and `dropped_shutdown`, whose sum is `dropped_total`. When idle,
|
|
88
|
+
`produced == exported + dropped_total + queued`. Counters report what happened; they are not
|
|
89
|
+
durable delivery guarantees.
|
|
90
|
+
|
|
91
|
+
## Trace completion
|
|
92
|
+
|
|
93
|
+
When the outermost (root) span of a trace ends, the SDK sets `metadata.final = True` on it, so
|
|
94
|
+
the Nexus service can finish and classify the trace right away instead of waiting for an idle
|
|
95
|
+
timeout. Child spans are unchanged. A child span that is delivered after its root has already
|
|
96
|
+
arrived (for example a retried batch) is dropped by the service and counted there.
|
|
97
|
+
|
|
98
|
+
## Compatibility alias
|
|
99
|
+
|
|
100
|
+
`nexus_agent` is an alias for `nexus_llmops`: the same module objects under the old name (the
|
|
101
|
+
Nexus research code uses it). New code should import `nexus_llmops`.
|
|
102
|
+
|
|
103
|
+
## Not included
|
|
104
|
+
|
|
105
|
+
- No LangChain or other framework callbacks yet (planned, not implemented).
|
|
106
|
+
- No OpenTelemetry exporter here: an application that already uses OpenTelemetry can send OTLP
|
|
107
|
+
directly to the Nexus collector's `/v1/traces` endpoint; that converter is server-side.
|
|
108
|
+
|
|
109
|
+
## Links
|
|
110
|
+
|
|
111
|
+
- [Platform and docs](https://github.com/sankalp-rajeev/Nexus)
|
|
112
|
+
- [Issues](https://github.com/sankalp-rajeev/Nexus/issues)
|
|
113
|
+
- License: MIT (see `LICENSE` in the distribution)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Compatibility alias: ``nexus_agent`` is ``nexus_llmops``.
|
|
2
|
+
|
|
3
|
+
The distribution ``nexus-llmops`` ships its code as the ``nexus_llmops`` package. The Nexus
|
|
4
|
+
research code predates that name and imports ``nexus_agent``, so this package keeps the old
|
|
5
|
+
name working without a second copy of anything:
|
|
6
|
+
|
|
7
|
+
* ``sys.modules["nexus_agent"]`` becomes the real ``nexus_llmops`` module object;
|
|
8
|
+
* every submodule and subpackage of ``nexus_llmops`` (found by walking the package, not from a
|
|
9
|
+
hard-coded list) is registered as ``nexus_agent.<name>``, pointing at the same module object.
|
|
10
|
+
|
|
11
|
+
So ``nexus_agent.models.Span is nexus_llmops.models.Span``, global state (the client set by
|
|
12
|
+
``init()``) is shared, and ``import nexus_agent.models`` as the very first import gives the
|
|
13
|
+
same objects. There are no re-export copies and no deprecation warning in 0.1.x; new code
|
|
14
|
+
should import ``nexus_llmops``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import importlib
|
|
20
|
+
import pkgutil
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
import nexus_llmops as _real
|
|
24
|
+
|
|
25
|
+
_OLD = __name__ # "nexus_agent"
|
|
26
|
+
_NEW = _real.__name__ # "nexus_llmops"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _alias_submodules() -> None:
|
|
30
|
+
for info in pkgutil.walk_packages(_real.__path__, prefix=_NEW + "."):
|
|
31
|
+
module = importlib.import_module(info.name)
|
|
32
|
+
sys.modules[_OLD + info.name[len(_NEW):]] = module
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_alias_submodules()
|
|
36
|
+
# Replace this module with the real package. The import system returns whatever is in
|
|
37
|
+
# sys.modules once this file has run, so ``import nexus_agent`` yields ``nexus_llmops``.
|
|
38
|
+
sys.modules[_OLD] = _real
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""nexus_llmops (dist: nexus-llmops) — fail-open agent tracing for the Nexus service.
|
|
2
|
+
|
|
3
|
+
``nexus_agent`` is a compatibility alias for this package (same module objects).
|
|
4
|
+
|
|
5
|
+
Quickstart::
|
|
6
|
+
|
|
7
|
+
import nexus_llmops as nexus
|
|
8
|
+
nexus.init(agent_name="my-pipeline")
|
|
9
|
+
|
|
10
|
+
@nexus.trace(span_type="llm_call", model="gemini-2.0-flash")
|
|
11
|
+
def call_llm(prompt: str) -> str:
|
|
12
|
+
...
|
|
13
|
+
|
|
14
|
+
nexus.flush() # before process exit in short-lived scripts
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import atexit
|
|
20
|
+
import logging
|
|
21
|
+
import threading
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from nexus_llmops import models # noqa: F401
|
|
25
|
+
from nexus_llmops.config import NexusConfig
|
|
26
|
+
from nexus_llmops.client import NexusClient
|
|
27
|
+
|
|
28
|
+
__version__ = "0.1.0"
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger("nexus_llmops")
|
|
31
|
+
|
|
32
|
+
_client: NexusClient | None = None
|
|
33
|
+
_lock = threading.Lock()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def init(
|
|
37
|
+
agent_name: str,
|
|
38
|
+
collector_url: str = "http://localhost:8000",
|
|
39
|
+
environment: str = "development",
|
|
40
|
+
async_export: bool = True,
|
|
41
|
+
export_timeout_ms: int = 200,
|
|
42
|
+
debug: bool = False,
|
|
43
|
+
**kwargs: Any,
|
|
44
|
+
) -> NexusClient:
|
|
45
|
+
"""Initialize the global Nexus client. Safe to call once per process."""
|
|
46
|
+
global _client
|
|
47
|
+
with _lock:
|
|
48
|
+
if _client is not None:
|
|
49
|
+
logger.warning("nexus: already initialized; reusing existing client")
|
|
50
|
+
return _client
|
|
51
|
+
config = NexusConfig(
|
|
52
|
+
agent_name=agent_name,
|
|
53
|
+
collector_url=collector_url,
|
|
54
|
+
environment=environment,
|
|
55
|
+
async_export=async_export,
|
|
56
|
+
export_timeout_ms=export_timeout_ms,
|
|
57
|
+
debug=debug,
|
|
58
|
+
**kwargs,
|
|
59
|
+
)
|
|
60
|
+
_client = NexusClient(config)
|
|
61
|
+
atexit.register(_client.shutdown)
|
|
62
|
+
return _client
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_client() -> NexusClient | None:
|
|
66
|
+
"""Return the active client, or None if init() was never called."""
|
|
67
|
+
return _client
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def is_initialized() -> bool:
|
|
71
|
+
return _client is not None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def shutdown(timeout: float | None = None) -> None:
|
|
75
|
+
"""Drain (for at most ``timeout`` s; default ``shutdown_timeout_ms``) and stop the exporter.
|
|
76
|
+
|
|
77
|
+
Spans still queued at the deadline are counted ``dropped_shutdown``. Resets global state.
|
|
78
|
+
"""
|
|
79
|
+
global _client
|
|
80
|
+
with _lock:
|
|
81
|
+
if _client is not None:
|
|
82
|
+
_client.shutdown(timeout)
|
|
83
|
+
_client = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def flush() -> None:
|
|
87
|
+
"""Force-export all queued spans (call before process exit in scripts)."""
|
|
88
|
+
if _client is not None:
|
|
89
|
+
_client.flush()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def start_span(span_type: Any, agent_name: str | None = None, **metadata: Any) -> str | None:
|
|
93
|
+
if _client is None:
|
|
94
|
+
return None
|
|
95
|
+
return _client.start_span(span_type, agent_name=agent_name, **metadata)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def end_span(span_id: str | None, output: Any = None, metadata: dict | None = None) -> None:
|
|
99
|
+
if _client is None or span_id is None:
|
|
100
|
+
return
|
|
101
|
+
_client.end_span(span_id, output=output, metadata=metadata)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# Imported last to avoid a circular import (decorators import get_client).
|
|
105
|
+
from nexus_llmops.decorators import span, trace # noqa: E402
|
|
106
|
+
|
|
107
|
+
__all__ = [
|
|
108
|
+
"__version__",
|
|
109
|
+
"models",
|
|
110
|
+
"NexusConfig",
|
|
111
|
+
"NexusClient",
|
|
112
|
+
"init",
|
|
113
|
+
"get_client",
|
|
114
|
+
"is_initialized",
|
|
115
|
+
"shutdown",
|
|
116
|
+
"flush",
|
|
117
|
+
"trace",
|
|
118
|
+
"span",
|
|
119
|
+
"start_span",
|
|
120
|
+
"end_span",
|
|
121
|
+
]
|