Skip to main content

langchain-nace

LangChain integration for Drex, Nace's decision models. Drex reads text, JSON, LangChain messages and (on Drex Max) images, and returns typed answers with calibrated probabilities instead of generated text. Use it to route requests, pick models, or check tool calls before they run.

pip install langchain-nace                  # classifier, router, LangGraph edge
pip install "langchain-nace[experimental]"  # + agent middleware
export NACE_API_KEY=nace_sk_...

Quickstart

from langchain_nace import Choice, NaceClassifier, Noul, Score

classifier = NaceClassifier()  # model="drex-max" by default

response = classifier.invoke(
    {
        "state": "I was charged twice for March and I'm annoyed. Please refund one.",
        "questions": {
            "refund": Noul(instructions="Is the customer asking for a refund?"),
            "team": Choice(
                instructions="Which team should handle this?",
                criteria={"billing": "Payments and refunds", "technical": "Bugs"},
            ),
            "mood": Score(
                instructions="How upset is the customer?", criteria=["calm", "annoyed", "furious"]
            ),
        },
    }
)
response.nouls["refund"].noul  # 0.98: probability of yes
response.choices["team"].probabilities  # {"billing": 0.99, "technical": 0.01}
response.scores["mood"].score  # 1.1: expected level

All questions about one state go in one request. Answers keep their full probabilities, so thresholds stay in your code. ainvoke, batch and abatch work as on any Runnable. batch runs at most 8 requests at once unless you set max_concurrency.

Switching from langchain-typesafe: change langchain_typesafe to langchain_nace in your imports, TypeSafeClassifier to NaceClassifier, and set NACE_API_KEY (used only for the Nace hosts listed under Backends; for any other base_url, pass that provider's key as api_key=). Everything else keeps its name and arguments:

langchain-typesafe langchain-nace
TypeSafeClassifier NaceClassifier (default model drex-max)
Noul, Choice, Score, NoulCriteria, ClassifierRequest, ClassifierResponse same names; nouls, choices, scores, usage, request_id as before
experimental.middleware.ModelRouterMiddleware, ModelChoice same names (also available as NaceModelRouterMiddleware)
experimental.middleware.AutoModeMiddleware(tools=, instructions=, criteria=) same names and arguments (also available as NaceAutoModeMiddleware)
TYPESAFE_API_KEY, TYPESAFE_BASE_URL NACE_API_KEY, NACE_BASE_URL
TypeSafe*Error Nace*Error; both inherit LangChain's Model*Error classes
client=, async_client= (httpx2) http_client=, http_async_client= (httpx)

Models

The default is drex-max: it reads images, and takes up to 16 questions per request. For text-only work with more questions per request (up to 512), use NaceClassifier(model="drex-v2.0"). Any other model id is accepted and sent as given (drex-v1.5, drex-latest, and models released later); Drex enforces each model's limits, and errors tell you what to change. Pin a default for your app with model= or NACE_DEFAULT_DECISION_MODEL. The package default moves to the newest flagship with each Drex release, so pin a model if you need results that never change.

Images (Drex Max)

max_ = NaceClassifier()  # drex-max
max_.invoke(
    {
        "state": "Two product photos.",
        "images": [red_png_bytes, "data:image/jpeg;base64,..."],
        "questions": {
            "damaged": Choice(
                instructions="Which image shows damage?",
                criteria={"Image 1": None, "Image 2": None},
            )
        },
    }
)

Images can be bytes, paths, base64, data URLs, LangChain image blocks ({"type": "image", "base64": ...}, source_type: "base64", image_url), or files uploaded with nace_sdk ({"type": "workspace_file", ...}). Images inside LangChain messages in state, whether message objects or {"role": ..., "content": [...]} dicts, are moved to images automatically and referred to as "Image N". Drex doesn't fetch URLs, so remote image URLs are refused. Sending images to a text-only model (drex-v2.0, drex-v1.5) raises NaceImageModelError before anything is sent; images are never dropped silently.

Routing

from langchain_nace import NaceRouter

router = NaceRouter(
    classifier=classifier,
    question=Choice(instructions="Which team?", criteria={"billing": None, "technical": None}),
    min_confidence=0.6,
    fallback="human",
)
chain = router.branch(
    {"billing": billing_chain, "technical": tech_chain, "human": escalate}, default=escalate
)

LangGraph: nace_conditional_edge(classifier, question, state=..., min_confidence=0.7) is a path function for add_conditional_edges. Below the threshold it returns "human_review", so that node can interrupt() for a person. See examples/langgraph_router.py.

Agent middleware (experimental)

from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
from langchain_nace.experimental.middleware import (
    ModelChoice,
    NaceAutoModeMiddleware,
    NaceModelRouterMiddleware,
)

router = NaceModelRouterMiddleware(
    choices={
        "fast": ModelChoice("openai:gpt-5-mini", "Short lookups"),
        "strong": ModelChoice("anthropic:claude-opus-5-5", "Multi-step changes"),
    },
    instructions="Pick the cheapest model that can do this safely.",
)
gate = NaceAutoModeMiddleware(screenshots=True)  # drex-max reads the screenshots
agent = create_agent(
    "openai:gpt-5-mini", tools=tools, middleware=[router, gate], checkpointer=InMemorySaver()
)

NaceModelRouterMiddleware picks the model once per run. Below min_confidence (0.6), or if Drex can't be reached, it uses escalate_to (the last choice by default). The decision and probabilities are kept in state under nace_model_route.

NaceAutoModeMiddleware checks each tool call before it runs. In one request Drex reads:

  • the user's original request, kept however long the run gets;
  • the latest user message and the last 20 messages;
  • the tool call and its description;
  • with screenshots=True and Drex Max, the newest screenshots.

It answers three questions: a risk score, "destructive?" and "did the user ask for this?". The default ThresholdPolicy then decides:

  • block at risk ≥ 0.75, or when the call is destructive and the user didn't ask for it;
  • ask a person at risk ≥ 0.4, or when the call is destructive and the user asked for it;
  • allow otherwise.

Asking uses a LangGraph interrupt. Resume it with {"type": "approve"}, {"type": "reject", "message": ...} or {"type": "edit", "args": {...}}. If Drex fails, the call is blocked (on_error="block"). Questions, thresholds and the policy are all replaceable. Write your own GatePolicy, or subclass GateDecision.

examples/support_agent.py runs four live scenarios: an allowed read, a blocked unrequested delete, a person approving a requested delete, and a screenshot-based block on a computer-use click.

Backends

Backend base_url Notes
Nace (default) https://console.nace.ai NACE_BASE_URL
OpenRouter https://openrouter.ai/api/v1 models nace-ai/drex-v2.0, nace-ai/drex-max; images are sent the way OpenRouter expects
Vercel AI Gateway / TypeSafe-compatible hosts the host's System One root same request format as Nace

Every request sends User-Agent and X-Nace-Client: langchain-nace/<version>. On OpenRouter it also sends HTTP-Referer and X-OpenRouter-Title.

NACE_API_KEY is used automatically only over HTTPS on port 443 at console.nace.ai, console.test.nace.ai, console.stage.nace.ai and their legacy drex equivalents. Other destinations, including localhost, require an explicit provider key:

classifier = NaceClassifier(
    base_url="https://openrouter.ai/api/v1",
    api_key=os.environ["OPENROUTER_API_KEY"],
)

Without an explicit api_key, a custom base_url or NACE_BASE_URL raises ValueError when the classifier is created. This includes the default classifier built by NaceRouter, nace_conditional_edge and the middleware, so pass classifier=NaceClassifier(base_url=..., api_key=...) for custom hosts.

Vercel bypass headers are removed from requests to other destinations, even when an older nace-sdk adds NACE_VERCEL_PROTECTION_BYPASS automatically. Serialized custom-provider classifiers refer to OPENROUTER_API_KEY for OpenRouter, or NACE_CUSTOM_API_KEY for other hosts, instead of NACE_API_KEY.

Versions 0.1.0–0.1.2 could send NACE_API_KEY to a custom base_url when no explicit key was supplied, and could send a Vercel bypass token even with an explicit provider key. Upgrade to 0.1.3 or later. If you used that configuration with real credentials, replace and revoke those credentials; upgrading does not revoke them or remove copies held by another provider.

Custom-provider classifiers serialized by 0.1.0–0.1.2 may still reference NACE_API_KEY. Recreate those classifiers with 0.1.3 or later using the correct provider key, then serialize them again. Do not reload an old custom-provider manifest with your Nace key supplied through secrets_map or secrets_from_env=True. Upgrading the package cannot change secret references already stored in older manifests.

Errors

Errors also inherit LangChain's Model*Error classes, and messages never include your state:

Error When
NaceAuthenticationError 401
NaceInsufficientCreditError 402, with the Billing link
NaceUnprocessableEntityError 422; lists each issue's path: message
NaceRateLimitError 429, after retries
NaceOverloadedError 529, after retries
NaceAPIConnectionError / NaceAPITimeoutError network problems

429, 529 and 5xx responses are retried (max_retries=2), waiting as long as retry-after-ms asks. The timeout is 60 s, because Drex may take up to 55 s.

Tracing

Calls show up in LangSmith as llm runs (ls_provider="nace") with the questions, the full answers and probabilities, token usage and the Drex request id. The router and middlewares add their thresholds to the run metadata.

Development

uv sync --all-extras --all-groups
make test lint typecheck                 # offline: fake Drex server
NACE_API_KEY_LIVE=nace_sk_... make integration_tests   # live contract tests

Metadata

Release files for langchain-nace 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for langchain-nace 0.1.3
File Size Uploaded Provenance Actions
langchain_nace-0.1.3.tar.gz 29.2 kB With provenance Details for langchain_nace-0.1.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for langchain-nace 0.1.3
File Interpreter ABI Platform Actions
langchain_nace-0.1.3-py3-none-any.whl Python 3 none any Details for langchain_nace-0.1.3-py3-none-any.whl

Total release size: 66.1 kB

Release files / langchain_nace-0.1.3.tar.gz

Download URL langchain_nace-0.1.3.tar.gz
Size 29.2 kB
Tags Source
SHA-256 checksum
How to use checksums
39dae73fe762e199ac527f2f75f3d2dc87419fd4da32de0398b8ec8c5a153cfb
BLAKE2b-256 checksum
How to use checksums
8435683582290879cbe07cf1017715d7931f91167afd505256ab94c9be64b8f6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

Release files / langchain_nace-0.1.3-py3-none-any.whl

Download URL langchain_nace-0.1.3-py3-none-any.whl
Size 36.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
95554a581e8a8eee89b6059b3629b6b3dc43fc3761126095efef47dc6d1c00ef
BLAKE2b-256 checksum
How to use checksums
1781ea795947046f9ce3ca012138a8d7f6eae6a5a7316002181957da8c8f01c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

Security for langchain-nace 0.1.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for langchain_nace-0.1.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 30, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page