Python Bridge — Use NexArt from Python
Use NexArt from Python via a thin bridge to the canonical JavaScript SDK, shipped inside @nexart/agent-kit (>=0.5.2).
Why a bridge and not a native SDK
NexArt's core promise is vendor-independent verification: a Certified Execution Record produced by any NexArt tool MUST be verifiable by any other NexArt tool, by an independent third party, and by the public verify.nexart.io endpoint, all producing byte-identical hashes. That guarantee depends on a single canonical implementation of hashing, canonicalization, and signing. Maintaining a parallel native Python SDK would introduce two implementations of the same cryptographic surface, and the smallest divergence between them - one library sorting object keys differently, one floating point edge case, one Unicode normalization choice - silently breaks cross-tool verification.
The bridge resolves this by keeping the JavaScript SDK as the single source of truth and exposing a Python-shaped surface on top of it. The tradeoff is explicit and honest: Python users must carry a Node.js runtime dependency. In exchange, every CER produced from Python is byte-identical to one produced from JavaScript, and the verification story stays unified.
Requirements
- Node.js >= 18
- Python >= 3.10
@nexart/agent-kit>= 0.5.2 installed via npm- Python-only Docker images need Node added to the base image. Full deployment guidance lives in
PYTHON_BRIDGE_INSTRUCTIONS.mdinside the npm package.
Performance
The bridge ships two transports. Pick by call volume.
BridgeOneShot— spawns a fresh Node subprocess per call. Roughly 130ms per invocation. Conservative default. Use for scripts, batch jobs, low-frequency certification.BridgePersistent— keeps a long-lived Node worker alive and dispatches calls over its stdio. Roughly 0.4ms per invocation - about a 300x speedup. Use for servers, hot loops, and per-request certification.
Start with BridgeOneShot until you measure that the subprocess cost matters. Switching is a one-line change.
API
Every bridge function takes the bridge instance as its first positional argument. Without it, the function has no Node process to dispatch to. The shape mirrors @nexart/agent-kit's JS API, with Python casing.
from nexart_bridge.nexart_bridge import (
BridgeOneShot,
certify_decision,
verify_cer,
)
br = BridgeOneShot()
bundle = certify_decision(
br,
decision="Recommend product X to user 123",
output="product_x",
provider="openai",
model="gpt-4o",
)
result = verify_cer(br, bundle, verificationMode="detailed")
if result["status"] != "VERIFIED":
raise RuntimeError(f"CER verification failed: {result['reasonCodes']}")
print("Certified:", bundle["certificateHash"])
print("Status: ", result["status"])For default (non-detailed) verification, omit verificationMode:
ok = verify_cer(br, bundle) # returns boolean-shaped resultWhere the bridge lives
The bridge is not published on PyPI. It is shipped as two files inside the @nexart/agent-kit npm package, at:
node_modules/@nexart/agent-kit/python-bridge/
├── nexart_bridge.mjs # Node-side dispatcher
├── nexart_bridge.py # Python-side client
└── PYTHON_BRIDGE_INSTRUCTIONS.md # full setup referenceSetup is three steps:
- Install the npm package in a directory accessible to your Python process:
npm install @nexart/agent-kit@>=0.5.2. - Copy both
nexart_bridge.mjsandnexart_bridge.pyfromnode_modules/@nexart/agent-kit/python-bridge/into anexart_bridge/folder inside your Python project. - Import with
from nexart_bridge.nexart_bridge import .... Full instructions, including container deployment, are inPYTHON_BRIDGE_INSTRUCTIONS.md.
Do not pip install anything. There is no PyPI package and none is planned.
Verification parity
Bundles produced through the bridge are byte-identical to bundles produced by the JavaScript SDK directly. The same certificateHash falls out of either path, and the same signatures verify. That means a Python-produced CER verifies cleanly with:
verify.nexart.io/c/{certificateHash}- The JavaScript SDK (
verifyAiCerBundleDetailed) - The NexArt CLI
- The bridge's own
verify_cer
All paths agree because all paths run the same canonical implementation underneath.