Skip to main content

Drex for Haystack

Drex decision-model components for Haystack, deepset's open-source Python framework for AI pipelines, agents, and retrieval-augmented generation (RAG). Maintained by Nace AI.

Haystack connects components for tasks such as retrieving documents, calling models, and routing results. This package adds Drex decisions and evaluators to those pipelines. Start with the Haystack documentation if you are new to the framework.

Drex models don't generate text. They answer typed questions about a text, JSON or images in one request and return calibrated probabilities:

  • choice picks one label from a set of options,
  • score rates the input on ordered levels,
  • noul gives the probability that a yes/no statement is true.

This package brings them to Haystack pipelines:

Component What it does
NaceTextRouter Routes a text (and optional images) to one output per label, with a low_confidence fallback
NaceDocumentClassifier Answers questions about each document and stores the answers in its metadata
NaceFaithfulnessEvaluator Judges whether answers are supported by their contexts, like Haystack's FaithfulnessEvaluator
NaceContextRelevanceEvaluator Judges whether retrieved contexts help answer the question, like ContextRelevanceEvaluator

The router and classifier mirror deepset's typesafe-haystack integration for Jev, so a Jev pipeline switches to Drex with a few edits (see Switching from TypeSafe).

Installation

Python 3.11 or later is required. Install the published package in a virtual environment:

python -m venv .venv
source .venv/bin/activate  # Windows PowerShell: .venv\Scripts\Activate.ps1
python -m pip install nace-haystack

Create an API key at console.nace.ai, then set it in your shell:

export NACE_API_KEY="your-nace-api-key"
# Windows PowerShell: $env:NACE_API_KEY="your-nace-api-key"

The examples below call the hosted Drex API and consume your account's credit. Keep the key out of source code and notebook outputs. See pricing for current rates.

Usage

Quickstart: route one request

Save this snippet as route_text.py and run python route_text.py in the environment above. This text-only example pins drex-v2.0; use drex-max-v1.1 when you need images.

from haystack_integrations.components.routers.nace import NaceTextRouter

router = NaceTextRouter(
    model="drex-v2.0",
    labels={
        "billing": "Payments, invoices, refunds and charges",
        "technical": "Bugs, crashes and errors in the product",
    },
    instructions="Which team should handle this support request?",
    min_confidence=0.6,
    include_probabilities=True,
)

print(router.run(text="I was charged twice for my subscription."))
# Illustrative output; probabilities vary:
# {'billing': 'I was charged twice for my subscription.',
#  'probabilities': {'type': 'choice', 'choice': 'billing', 'confidence': 0.97,
#                    'probabilities': {'billing': 0.97, 'technical': 0.03}}}

Texts whose answer is less confident than min_confidence go to the low_confidence output, for a fallback such as an LLM or a person. With include_probabilities=True, a probabilities output carries the full answer, so thresholds stay in your pipeline.

choice is the selected label, confidence is its probability, and probabilities contains every label's probability. A result of 0.97 is not a guarantee that the decision is correct. The 0.6 threshold is an example: choose your threshold using representative labeled inputs and the cost of a wrong route. Raise it when uncertain requests should go to review more often. Low confidence uses the fallback output; API failures raise an error rather than automatically using that output.

Connect the decision to a complete pipeline

Run the routing example to see all three outputs connected to downstream handlers:

support request → Drex router → billing handler
                             → technical handler
                             → human-review handler (below the confidence threshold)

The example uses Pipeline.add_component and Pipeline.connect. Only the selected handler runs; it returns a ticket record naming its queue, alongside the original Drex probabilities. It does not send emails or create real tickets. Replace the handlers with your own actions in an application. The ambiguous sample may take different routes on different runs, depending on its confidence.

The scripts and notebook use files from this repository, which are not installed by pip install nace-haystack. Clone the repo and run the example from its root:

git clone https://github.com/nace-ai/nace-haystack.git
cd nace-haystack
python examples/route_with_fallback.py

Classify documents

from haystack import Document

from haystack_integrations.components.classifiers.nace import NaceDocumentClassifier

classifier = NaceDocumentClassifier(
    model="drex-v2.0",
    questions={
        "department": {
            "type": "choice",
            "instructions": "Which department should handle this ticket?",
            "criteria": {"billing": None, "technical": None, "sales": None},
        },
        "urgency": {
            "type": "score",
            "instructions": "How urgent is this ticket?",
            "criteria": ["can wait", "this week", "today"],
        },
        "refund": {"type": "noul", "instructions": "Is the customer asking for a refund?"},
    },
)

result = classifier.run(documents=[Document(content="I was charged twice, please refund me today.")])
print(result["documents"][0].meta["nace"]["department"])
# {'type': 'choice', 'choice': 'billing', 'confidence': 0.97, 'probabilities': {...}}

Each document is one request carrying every question, with up to max_workers requests (default 3) at once. Documents that can't be classified are returned in failed_documents with the reason in meta["classification_error"].

Evaluate RAG answers

from haystack_integrations.components.evaluators.nace import NaceFaithfulnessEvaluator

evaluator = NaceFaithfulnessEvaluator(model="drex-v2.0")
result = evaluator.run(
    questions=["Who won the race?"],
    contexts=[["Ada won the 2026 city race."]],
    predicted_answers=["Ada won the race."],
)
print(result["score"], result["individual_scores"])

Both evaluators take the same inputs and return the same outputs (score, individual_scores, results, meta) as Haystack's built-in LLM evaluators. Each item is one Drex request, and its score is the probability that the answer is faithful (or that the contexts are relevant). score is the mean over the successful items, while individual_scores keeps one value per item. If you set raise_on_failure=False, a failed item is NaN and is excluded from the mean; check results for errors before using an aggregate.

To evaluate retrieval before generating an answer:

from haystack_integrations.components.evaluators.nace import NaceContextRelevanceEvaluator

evaluator = NaceContextRelevanceEvaluator(model="drex-v2.0")
result = evaluator.run(
    questions=["Who won the race?"],
    contexts=[["Ada won the 2026 city race."]],
)
print(result["score"], result["individual_scores"])

Run the full support-email example and cookbook

The support-email pipeline classifies eight synthetic emails into document metadata, filters refund requests, routes emails and an invoice image, and checks draft replies against a refund policy. From the cloned repository root:

python -m pip install pillow
python examples/support_email_pipeline.py

Pillow draws the synthetic invoice and shrinks large images. The example generates its own invoice file, so you do not need to upload a customer document.

The cookbook notebook walks through the same workflow step by step. Launch Jupyter from the shell where NACE_API_KEY is set:

python -m pip install pillow jupyterlab
python -m jupyterlab notebooks/nace_haystack_cookbook.ipynb

Select the virtual environment's Python kernel. The notebook reads sample files from the checkout; run it from the repository root or the notebooks/ directory.

Models

The default model is drex-max-v1.1 (Drex Max v1.1), which reads text, JSON and images. The default moves to the newest Drex model in package releases, and a new model can shift probabilities, so pin model= in production if your pipeline relies on thresholds.

drex-v2.0 reads text only and bills fewer tokens on short texts: for a short email with three questions, we measured 74 input tokens on drex-v2.0 and 165 on drex-max-v1.1 (2026-10-29). See Drex's limits for questions per request and state sizes. Any model ID is sent as given, so new Drex models work without a new release of this package.

Images

The router and the evaluators take images: ImageContent, ByteStream, or files uploaded to Drex with nace-sdk (client.documents.upload(...)). Prompts refer to them as "Image 1", "Image 2" and so on.

from haystack.dataclasses import ImageContent

from haystack_integrations.components.routers.nace import NaceTextRouter

router = NaceTextRouter(
    model="drex-max-v1.1",
    labels={"invoice": "A bill or invoice", "receipt": "A proof of payment", "other": None},
    instructions="What kind of document is Image 1?",
)
router.run(text="A customer attached a scan.", images=[ImageContent.from_file_path("scan.jpg")])

Images too big to send in one request are shrunk automatically, with a logged warning. Drex Max reads images at about 1.6 megapixels (1,638,400 pixels), and an image is never shrunk below about 1 megapixel: if the text leaves too little room for the images, you get a clear error instead of a blurry image. Photos are turned upright using their EXIF orientation before shrinking. Shrinking needs pip install pillow; to skip it, downscale earlier with ImageFileToImageContent(size=(2048, 2048)) or PDFToImageContent(size=(2048, 2048)). PDFs must be converted to images first.

Other backends

Set api_base_url and an explicit provider key to use Drex through OpenRouter or another TypeSafe-compatible host. OpenRouter takes inline images only.

from haystack.utils import Secret
from haystack_integrations.components.routers.nace import NaceTextRouter

router = NaceTextRouter(
    labels=["billing", "technical"],
    model="nace-ai/drex-max",
    api_base_url="https://openrouter.ai/api/v1",
    api_key=Secret.from_env_var("OPENROUTER_API_KEY"),
)

Set OPENROUTER_API_KEY in your environment before running the router. Use a separate provider environment variable for other hosts; NACE_API_KEY is reserved for Nace's production API. Custom destinations require an explicit api_key, and an empty key is refused before a request is sent. Ambient Vercel bypass headers are removed from requests to other providers, and redirects to a different origin are refused.

The base URL is resolved when the component is constructed, then saved with the pipeline. Changing NACE_BASE_URL later does not redirect an existing component. To change providers in a saved pipeline, update both its base URL and its provider key reference. Older saved pipelines that send NACE_API_KEY to a custom host must switch to a separate provider key reference before loading.

Errors and retries

Requests that hit a rate limit (429) or an overloaded model (529) are retried, waiting as long as Drex asks. Other errors are raised as nace-sdk's error classes with a readable message. The message is capped at 500 characters, with space reserved for recovery guidance. The SDK adds the status and request ID when rendering the exception; the original structured body and issues remain available for inspection. For example:

422 from Drex: the request is invalid
  questions.department.criteria: must have at least 2 labels

Switching from TypeSafe

The router and classifier take the same parameters and produce the same outputs as TypeSafeTextRouter and TypeSafeDocumentClassifier.

In Python code that uses Jev's default model, change the import, the class name and the API key:

-from haystack_integrations.components.routers.typesafe import TypeSafeTextRouter
+from haystack_integrations.components.routers.nace import NaceTextRouter

-router = TypeSafeTextRouter(labels=..., min_confidence=0.6)
+router = NaceTextRouter(labels=..., min_confidence=0.6)
-export TYPESAFE_API_KEY=...
+export NACE_API_KEY=...

The classifier writes answers to meta["nace"] by default. If your code reads meta["typesafe"] (in a filter or a MetadataRouter rule, for example), pass metadata_field="typesafe" to keep the old key.

Questions built with typesafe_sdk objects (Choice, Score, Noul) work as they are; you can switch them to nace_sdk's classes of the same names when convenient. Where your code catches TypeSafeError, catch nace_sdk.NaceError instead.

In a saved YAML pipeline, make three edits per component:

-    type: haystack_integrations.components.routers.typesafe.text_router.TypeSafeTextRouter
+    type: haystack_integrations.components.routers.nace.text_router.NaceTextRouter
...
-        - TYPESAFE_API_KEY
+        - NACE_API_KEY
...
-      model: jev-latest
+      model: drex-max-v1.1

The classifier's type becomes haystack_integrations.components.classifiers.nace.document_classifier.NaceDocumentClassifier. A saved pipeline keeps metadata_field: typesafe, so answers stay where your filters read them. Model names aren't translated: Drex rejects jev-latest with a message listing its models.

Drex's own guide covers the API side: Migrate from TypeSafe.

License

Apache-2.0

The router, document classifier and their tests are adapted from deepset's Apache-2.0-licensed typesafe-haystack integration. The original deepset copyright notices are retained alongside Nace's notices for its modifications.

Metadata

Release files for nace-haystack 0.1.1

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

Source distribution (sdist)

Source distribution for nace-haystack 0.1.1
File Size Uploaded Provenance Actions
nace_haystack-0.1.1.tar.gz 59.3 kB With provenance Details for nace_haystack-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for nace-haystack 0.1.1
File Interpreter ABI Platform Actions
nace_haystack-0.1.1-py3-none-any.whl Python 3 none any Details for nace_haystack-0.1.1-py3-none-any.whl

Total release size: 102.9 kB

Release files / nace_haystack-0.1.1.tar.gz

Download URL nace_haystack-0.1.1.tar.gz
Size 59.3 kB
Tags Source
SHA-256 checksum
How to use checksums
fb86a39d0d6aface7f58b13ff13f37b7a0b76136e0233678f875a24e4ab51345
BLAKE2b-256 checksum
How to use checksums
5f15c3bdfcc0d81d69a2287af71f06d1497733e103e4d17f27f6a1ae0b210c0f
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 / nace_haystack-0.1.1-py3-none-any.whl

Download URL nace_haystack-0.1.1-py3-none-any.whl
Size 43.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
29e5b01c7c9e00dd69ece454071547aced4c596f267b307d6a3034610c6c5602
BLAKE2b-256 checksum
How to use checksums
fa7b0eb4bb985a2f4dbb4e7b0e84feb088a9a58c0e879ca454a6c63ee3c827e3
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 nace-haystack 0.1.1

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 nace_haystack-0.1.1.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