From 09d3849539f8d022c6ac58d64f187c33241408ee Mon Sep 17 00:00:00 2001
From: Sebastian Husch Lee <10526848+sjrl@users.noreply.github.com>
Date: Mon, 5 Oct 2026 12:31:14 +0200
Subject: [PATCH] docs: add TypeSafe integration documentation pages (#13030)
---
.../docs/pipeline-components/classifiers.mdx | 1 +
.../typesafedocumentclassifier.mdx | 202 ++++++++++++++++++
.../docs/pipeline-components/routers.mdx | 3 +-
.../routers/typesafetextrouter.mdx | 146 +++++++++++++
docs-website/sidebars.js | 2 +
.../pipeline-components/classifiers.mdx | 1 +
.../typesafedocumentclassifier.mdx | 202 ++++++++++++++++++
.../pipeline-components/routers.mdx | 3 +-
.../routers/typesafetextrouter.mdx | 146 +++++++++++++
.../version-3.3-sidebars.json | 6 +-
10 files changed, 708 insertions(+), 4 deletions(-)
create mode 100644 docs-website/docs/pipeline-components/classifiers/typesafedocumentclassifier.mdx
create mode 100644 docs-website/docs/pipeline-components/routers/typesafetextrouter.mdx
create mode 100644 docs-website/versioned_docs/version-3.3/pipeline-components/classifiers/typesafedocumentclassifier.mdx
create mode 100644 docs-website/versioned_docs/version-3.3/pipeline-components/routers/typesafetextrouter.mdx
diff --git a/docs-website/docs/pipeline-components/classifiers.mdx b/docs-website/docs/pipeline-components/classifiers.mdx
index fc4a76d637..0f534161f1 100644
--- a/docs-website/docs/pipeline-components/classifiers.mdx
+++ b/docs-website/docs/pipeline-components/classifiers.mdx
@@ -13,3 +13,4 @@ Use Classifiers to classify your documents by specific traits and update the met
| --- | --- |
| [DocumentLanguageClassifier](classifiers/documentlanguageclassifier.mdx) | Classify documents by language. |
| [TransformersZeroShotDocumentClassifier](classifiers/transformerszeroshotdocumentclassifier.mdx) | Classify the documents based on the provided labels. |
+| [TypeSafeDocumentClassifier](classifiers/typesafedocumentclassifier.mdx) | Answer typed questions about documents with a TypeSafe System One model and store the answers in their metadata. |
diff --git a/docs-website/docs/pipeline-components/classifiers/typesafedocumentclassifier.mdx b/docs-website/docs/pipeline-components/classifiers/typesafedocumentclassifier.mdx
new file mode 100644
index 0000000000..ade1ff4081
--- /dev/null
+++ b/docs-website/docs/pipeline-components/classifiers/typesafedocumentclassifier.mdx
@@ -0,0 +1,202 @@
+---
+title: "TypeSafeDocumentClassifier"
+id: typesafedocumentclassifier
+slug: "/typesafedocumentclassifier"
+description: "Answers typed questions about each document with a TypeSafe System One model, such as Jev, and stores the answers in its metadata."
+---
+
+# TypeSafeDocumentClassifier
+
+Answers typed questions about each document with a TypeSafe System One model, such as Jev, and stores the answers in its metadata.
+
+
+
+| | |
+| --- | --- |
+| **Most common position in a pipeline** | Before a [MetadataRouter](../routers/metadatarouter.mdx) |
+| **Mandatory init variables** | `questions`: The questions to answer for every document, keyed by question ID
`api_key`: The TypeSafe API key. Can be set with the `TYPESAFE_API_KEY` env var. |
+| **Mandatory run variables** | `documents`: A list of documents to classify |
+| **Output variables** | `documents`: The classified documents, with the answers in their metadata
`failed_documents`: The documents that couldn't be classified |
+| **API reference** | [TypeSafe](/reference/integrations-typesafe) |
+| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/typesafe |
+| **Package name** | `typesafe-haystack` |
+
+
+
+## Overview
+
+`TypeSafeDocumentClassifier` sends each document to a [TypeSafe](https://typesafe.ai) System One model and stores the answers in the document's metadata. System One models such as Jev don't generate text. They answer every question about a document in one request and return calibrated probabilities.
+
+Each question has one of three types:
+
+- `choice`: picks one label from `criteria`, a dict of label to description (or `None`).
+- `score`: rates the text on the ordered levels in `criteria`, a list of level descriptions.
+- `noul`: returns the probability that the yes/no question in `instructions` is true.
+
+You can write questions as dicts or use the `Choice`, `Score`, and `Noul` objects from the `typesafe-sdk` package. See the [TypeSafe documentation](https://docs.typesafe.ai/primitives) for the full question and answer format.
+
+The answers are stored under `meta["typesafe"]`, keyed by question ID. You can change the field name with `metadata_field`. The component classifies the document's `content` by default. To classify a metadata field instead, set `classification_field`.
+
+Documents without text to classify are returned in `failed_documents` with the reason in `meta["classification_error"]`. So are documents whose request failed, unless you set `raise_on_failure=True`, in which case the first failed request raises its error.
+
+Each document is sent as its own request. `max_workers` sets how many requests run at once, in both `run` and `run_async`.
+
+### Authentication
+
+The component reads the API key from the `TYPESAFE_API_KEY` environment variable by default. You can also pass it at initialization with `api_key`:
+
+```python
+from haystack.utils import Secret
+from haystack_integrations.components.classifiers.typesafe import (
+ TypeSafeDocumentClassifier,
+)
+
+classifier = TypeSafeDocumentClassifier(
+ questions={
+ "refund": {
+ "type": "noul",
+ "instructions": "Is the customer asking for a refund?",
+ }
+ },
+ api_key=Secret.from_token(""),
+)
+```
+
+### Running models locally with Ollaya
+
+The component works with any server that implements the TypeSafe API, such as [Ollaya](https://ollaya.dev), which runs open decision models on your own machine. Set `api_base_url` to the server and `model` to one of its models. Ollaya accepts any non-empty API key unless it is configured with one.
+
+```shell
+docker run -d --name ollaya -p 11435:11435 -v ollaya:/home/ollaya/.ollaya ghcr.io/ollaya-dev/ollaya
+docker exec ollaya ollaya pull laya:en
+```
+
+```python
+from haystack.utils import Secret
+from haystack_integrations.components.classifiers.typesafe import (
+ TypeSafeDocumentClassifier,
+)
+
+classifier = TypeSafeDocumentClassifier(
+ questions={
+ "refund": {
+ "type": "noul",
+ "instructions": "Is the customer asking for a refund?",
+ }
+ },
+ model="laya:en",
+ api_key=Secret.from_token("local"),
+ api_base_url="http://localhost:11435",
+)
+```
+
+## Usage
+
+Install the `typesafe-haystack` package to use the `TypeSafeDocumentClassifier`:
+
+```shell
+pip install typesafe-haystack
+```
+
+### On its own
+
+```python
+from haystack import Document
+from haystack_integrations.components.classifiers.typesafe import (
+ TypeSafeDocumentClassifier,
+)
+
+classifier = TypeSafeDocumentClassifier(
+ 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["typesafe"])
+# {'department': {'type': 'choice', 'choice': 'billing', 'confidence': 0.9516,
+# 'probabilities': {'billing': 0.9677, 'technical': 0.0221, 'sales': 0.0102}},
+# 'urgency': {'type': 'score', 'score': 1.948, 'confidence': 0.9389,
+# 'legend': {'0': 'can wait', '1': 'this week', '2': 'today'},
+# 'probabilities': {'0': 0.0113, '1': 0.0295, '2': 0.9593}},
+# 'refund': {'type': 'noul', 'noul': 0.9498}}
+```
+
+### In a pipeline
+
+The following pipeline classifies support tickets by department and sends each one to a different output of a `MetadataRouter`, which matches on the nested answer field:
+
+```python
+from haystack import Document, Pipeline
+from haystack.components.routers import MetadataRouter
+from haystack_integrations.components.classifiers.typesafe import (
+ TypeSafeDocumentClassifier,
+)
+
+labels = ["billing", "technical"]
+
+pipeline = Pipeline()
+pipeline.add_component(
+ "classifier",
+ TypeSafeDocumentClassifier(
+ questions={
+ "department": {
+ "type": "choice",
+ "instructions": "Which department should handle this ticket?",
+ "criteria": dict.fromkeys(labels),
+ }
+ },
+ ),
+)
+pipeline.add_component(
+ "router",
+ MetadataRouter(
+ rules={
+ label: {
+ "field": "meta.typesafe.department.choice",
+ "operator": "==",
+ "value": label,
+ }
+ for label in labels
+ }
+ ),
+)
+pipeline.connect("classifier.documents", "router.documents")
+
+result = pipeline.run(
+ {
+ "classifier": {
+ "documents": [
+ Document(content="I was charged twice for my subscription."),
+ Document(
+ content="The app crashes every time I open the settings page."
+ ),
+ ]
+ }
+ }
+)
+print(
+ {
+ output: [document.content for document in documents]
+ for output, documents in result["router"].items()
+ }
+)
+# {'billing': ['I was charged twice for my subscription.'],
+# 'technical': ['The app crashes every time I open the settings page.'],
+# 'unmatched': []}
+```
diff --git a/docs-website/docs/pipeline-components/routers.mdx b/docs-website/docs/pipeline-components/routers.mdx
index 3a7459869c..4d4c16b185 100644
--- a/docs-website/docs/pipeline-components/routers.mdx
+++ b/docs-website/docs/pipeline-components/routers.mdx
@@ -19,4 +19,5 @@ Routers is a group of components that route queries or documents to other compon
| [MetadataRouter](routers/metadatarouter.mdx) | Routes documents based on their metadata field values. |
| [TextLanguageRouter](routers/textlanguagerouter.mdx) | Routes queries based on their language. |
| [TransformersTextRouter](routers/transformerstextrouter.mdx) | Routes text input to various output connections based on a model-defined categorization label. |
-| [TransformersZeroShotTextRouter](routers/transformerszeroshottextrouter.mdx) | Routes text input to various output connections based on user-defined categorization label. |
\ No newline at end of file
+| [TransformersZeroShotTextRouter](routers/transformerszeroshottextrouter.mdx) | Routes text input to various output connections based on user-defined categorization label. |
+| [TypeSafeTextRouter](routers/typesafetextrouter.mdx) | Routes text input to the output of the label a TypeSafe System One model picks for it. |
diff --git a/docs-website/docs/pipeline-components/routers/typesafetextrouter.mdx b/docs-website/docs/pipeline-components/routers/typesafetextrouter.mdx
new file mode 100644
index 0000000000..0dc236c60a
--- /dev/null
+++ b/docs-website/docs/pipeline-components/routers/typesafetextrouter.mdx
@@ -0,0 +1,146 @@
+---
+title: "TypeSafeTextRouter"
+id: typesafetextrouter
+slug: "/typesafetextrouter"
+description: "Routes a text to the output of the label a TypeSafe System One model, such as Jev, picks for it."
+---
+
+# TypeSafeTextRouter
+
+Routes a text to the output of the label a TypeSafe System One model, such as Jev, picks for it.
+
+
+
+| | |
+| --- | --- |
+| **Most common position in a pipeline** | Flexible |
+| **Mandatory init variables** | `labels`: The labels to route between
`api_key`: The TypeSafe API key. Can be set with the `TYPESAFE_API_KEY` env var. |
+| **Mandatory run variables** | `text`: The text to route |
+| **Output variables** | `
+
+## Overview
+
+`TypeSafeTextRouter` asks a [TypeSafe](https://typesafe.ai) System One model a single `choice` question over `labels` and sends the text to the output of the label it picks. System One models such as Jev don't generate text. They answer in one request and return calibrated probabilities.
+
+Pass `labels` as a list of label names, or as a dict of label to description to tell the model what each label means. `instructions` sets the question the model answers, for example "Which team should handle this support request?".
+
+When you set `min_confidence`, the component gets an extra `low_confidence` output. Texts whose answer has a `confidence` below the threshold go there instead, so you can send uncertain texts to a fallback such as an LLM or a human. Good thresholds depend on the model and the labels, so pick one based on your own data. See TypeSafe's guide to [confidence-gated routing](https://docs.typesafe.ai/patterns/confidence-routing).
+
+### Authentication
+
+The component reads the API key from the `TYPESAFE_API_KEY` environment variable by default. You can also pass it at initialization with `api_key`:
+
+```python
+from haystack.utils import Secret
+from haystack_integrations.components.routers.typesafe import TypeSafeTextRouter
+
+router = TypeSafeTextRouter(
+ labels=["billing", "technical"], api_key=Secret.from_token("")
+)
+```
+
+### Running models locally with Ollaya
+
+The component works with any server that implements the TypeSafe API, such as [Ollaya](https://ollaya.dev), which runs open decision models on your own machine. Set `api_base_url` to the server and `model` to one of its models. Ollaya accepts any non-empty API key unless it is configured with one.
+
+```shell
+docker run -d --name ollaya -p 11435:11435 -v ollaya:/home/ollaya/.ollaya ghcr.io/ollaya-dev/ollaya
+docker exec ollaya ollaya pull laya:en
+```
+
+```python
+from haystack.utils import Secret
+from haystack_integrations.components.routers.typesafe import TypeSafeTextRouter
+
+router = TypeSafeTextRouter(
+ labels=["billing", "technical"],
+ model="laya:en",
+ api_key=Secret.from_token("local"),
+ api_base_url="http://localhost:11435",
+)
+```
+
+## Usage
+
+Install the `typesafe-haystack` package to use the `TypeSafeTextRouter`:
+
+```shell
+pip install typesafe-haystack
+```
+
+### On its own
+
+```python
+from haystack_integrations.components.routers.typesafe import TypeSafeTextRouter
+
+router = TypeSafeTextRouter(
+ 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,
+)
+
+print(router.run(text="I was charged twice for my subscription."))
+# {'billing': 'I was charged twice for my subscription.'}
+```
+
+### In a pipeline
+
+The following pipeline routes a support question to the help articles of the team that handles it. Each label's output goes to its own retriever:
+
+```python
+from haystack import Document, Pipeline
+from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
+from haystack.document_stores.in_memory import InMemoryDocumentStore
+from haystack_integrations.components.routers.typesafe import TypeSafeTextRouter
+
+billing_store = InMemoryDocumentStore()
+billing_store.write_documents(
+ [
+ Document(
+ content="If you were charged twice, contact support and we refund the duplicate charge within 5 business days."
+ )
+ ]
+)
+technical_store = InMemoryDocumentStore()
+technical_store.write_documents(
+ [
+ Document(
+ content="If the app crashes on startup, update to the latest version and clear the app cache."
+ )
+ ]
+)
+
+pipeline = Pipeline()
+pipeline.add_component(
+ "router",
+ TypeSafeTextRouter(
+ labels={
+ "billing": "Payments, invoices, refunds and charges",
+ "technical": "Bugs, crashes and errors in the product",
+ },
+ instructions="Which team should handle this support request?",
+ ),
+)
+pipeline.add_component(
+ "billing_retriever", InMemoryBM25Retriever(document_store=billing_store)
+)
+pipeline.add_component(
+ "technical_retriever", InMemoryBM25Retriever(document_store=technical_store)
+)
+pipeline.connect("router.billing", "billing_retriever.query")
+pipeline.connect("router.technical", "technical_retriever.query")
+
+result = pipeline.run(
+ {"router": {"text": "I was charged twice, how do I get my money back?"}}
+)
+print(result["billing_retriever"]["documents"][0].content)
+# If you were charged twice, contact support and we refund the duplicate charge within 5 business days.
+```
diff --git a/docs-website/sidebars.js b/docs-website/sidebars.js
index 7776cc885f..fae56d2f42 100644
--- a/docs-website/sidebars.js
+++ b/docs-website/sidebars.js
@@ -246,6 +246,7 @@ export default {
items: [
'pipeline-components/classifiers/documentlanguageclassifier',
'pipeline-components/classifiers/transformerszeroshotdocumentclassifier',
+ 'pipeline-components/classifiers/typesafedocumentclassifier',
],
},
{
@@ -685,6 +686,7 @@ export default {
'pipeline-components/routers/textlanguagerouter',
'pipeline-components/routers/transformerstextrouter',
'pipeline-components/routers/transformerszeroshottextrouter',
+ 'pipeline-components/routers/typesafetextrouter',
],
},
{
diff --git a/docs-website/versioned_docs/version-3.3/pipeline-components/classifiers.mdx b/docs-website/versioned_docs/version-3.3/pipeline-components/classifiers.mdx
index fc4a76d637..0f534161f1 100644
--- a/docs-website/versioned_docs/version-3.3/pipeline-components/classifiers.mdx
+++ b/docs-website/versioned_docs/version-3.3/pipeline-components/classifiers.mdx
@@ -13,3 +13,4 @@ Use Classifiers to classify your documents by specific traits and update the met
| --- | --- |
| [DocumentLanguageClassifier](classifiers/documentlanguageclassifier.mdx) | Classify documents by language. |
| [TransformersZeroShotDocumentClassifier](classifiers/transformerszeroshotdocumentclassifier.mdx) | Classify the documents based on the provided labels. |
+| [TypeSafeDocumentClassifier](classifiers/typesafedocumentclassifier.mdx) | Answer typed questions about documents with a TypeSafe System One model and store the answers in their metadata. |
diff --git a/docs-website/versioned_docs/version-3.3/pipeline-components/classifiers/typesafedocumentclassifier.mdx b/docs-website/versioned_docs/version-3.3/pipeline-components/classifiers/typesafedocumentclassifier.mdx
new file mode 100644
index 0000000000..ade1ff4081
--- /dev/null
+++ b/docs-website/versioned_docs/version-3.3/pipeline-components/classifiers/typesafedocumentclassifier.mdx
@@ -0,0 +1,202 @@
+---
+title: "TypeSafeDocumentClassifier"
+id: typesafedocumentclassifier
+slug: "/typesafedocumentclassifier"
+description: "Answers typed questions about each document with a TypeSafe System One model, such as Jev, and stores the answers in its metadata."
+---
+
+# TypeSafeDocumentClassifier
+
+Answers typed questions about each document with a TypeSafe System One model, such as Jev, and stores the answers in its metadata.
+
+
+
+| | |
+| --- | --- |
+| **Most common position in a pipeline** | Before a [MetadataRouter](../routers/metadatarouter.mdx) |
+| **Mandatory init variables** | `questions`: The questions to answer for every document, keyed by question ID
`api_key`: The TypeSafe API key. Can be set with the `TYPESAFE_API_KEY` env var. |
+| **Mandatory run variables** | `documents`: A list of documents to classify |
+| **Output variables** | `documents`: The classified documents, with the answers in their metadata
`failed_documents`: The documents that couldn't be classified |
+| **API reference** | [TypeSafe](/reference/integrations-typesafe) |
+| **GitHub link** | https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/typesafe |
+| **Package name** | `typesafe-haystack` |
+
+
+
+## Overview
+
+`TypeSafeDocumentClassifier` sends each document to a [TypeSafe](https://typesafe.ai) System One model and stores the answers in the document's metadata. System One models such as Jev don't generate text. They answer every question about a document in one request and return calibrated probabilities.
+
+Each question has one of three types:
+
+- `choice`: picks one label from `criteria`, a dict of label to description (or `None`).
+- `score`: rates the text on the ordered levels in `criteria`, a list of level descriptions.
+- `noul`: returns the probability that the yes/no question in `instructions` is true.
+
+You can write questions as dicts or use the `Choice`, `Score`, and `Noul` objects from the `typesafe-sdk` package. See the [TypeSafe documentation](https://docs.typesafe.ai/primitives) for the full question and answer format.
+
+The answers are stored under `meta["typesafe"]`, keyed by question ID. You can change the field name with `metadata_field`. The component classifies the document's `content` by default. To classify a metadata field instead, set `classification_field`.
+
+Documents without text to classify are returned in `failed_documents` with the reason in `meta["classification_error"]`. So are documents whose request failed, unless you set `raise_on_failure=True`, in which case the first failed request raises its error.
+
+Each document is sent as its own request. `max_workers` sets how many requests run at once, in both `run` and `run_async`.
+
+### Authentication
+
+The component reads the API key from the `TYPESAFE_API_KEY` environment variable by default. You can also pass it at initialization with `api_key`:
+
+```python
+from haystack.utils import Secret
+from haystack_integrations.components.classifiers.typesafe import (
+ TypeSafeDocumentClassifier,
+)
+
+classifier = TypeSafeDocumentClassifier(
+ questions={
+ "refund": {
+ "type": "noul",
+ "instructions": "Is the customer asking for a refund?",
+ }
+ },
+ api_key=Secret.from_token(""),
+)
+```
+
+### Running models locally with Ollaya
+
+The component works with any server that implements the TypeSafe API, such as [Ollaya](https://ollaya.dev), which runs open decision models on your own machine. Set `api_base_url` to the server and `model` to one of its models. Ollaya accepts any non-empty API key unless it is configured with one.
+
+```shell
+docker run -d --name ollaya -p 11435:11435 -v ollaya:/home/ollaya/.ollaya ghcr.io/ollaya-dev/ollaya
+docker exec ollaya ollaya pull laya:en
+```
+
+```python
+from haystack.utils import Secret
+from haystack_integrations.components.classifiers.typesafe import (
+ TypeSafeDocumentClassifier,
+)
+
+classifier = TypeSafeDocumentClassifier(
+ questions={
+ "refund": {
+ "type": "noul",
+ "instructions": "Is the customer asking for a refund?",
+ }
+ },
+ model="laya:en",
+ api_key=Secret.from_token("local"),
+ api_base_url="http://localhost:11435",
+)
+```
+
+## Usage
+
+Install the `typesafe-haystack` package to use the `TypeSafeDocumentClassifier`:
+
+```shell
+pip install typesafe-haystack
+```
+
+### On its own
+
+```python
+from haystack import Document
+from haystack_integrations.components.classifiers.typesafe import (
+ TypeSafeDocumentClassifier,
+)
+
+classifier = TypeSafeDocumentClassifier(
+ 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["typesafe"])
+# {'department': {'type': 'choice', 'choice': 'billing', 'confidence': 0.9516,
+# 'probabilities': {'billing': 0.9677, 'technical': 0.0221, 'sales': 0.0102}},
+# 'urgency': {'type': 'score', 'score': 1.948, 'confidence': 0.9389,
+# 'legend': {'0': 'can wait', '1': 'this week', '2': 'today'},
+# 'probabilities': {'0': 0.0113, '1': 0.0295, '2': 0.9593}},
+# 'refund': {'type': 'noul', 'noul': 0.9498}}
+```
+
+### In a pipeline
+
+The following pipeline classifies support tickets by department and sends each one to a different output of a `MetadataRouter`, which matches on the nested answer field:
+
+```python
+from haystack import Document, Pipeline
+from haystack.components.routers import MetadataRouter
+from haystack_integrations.components.classifiers.typesafe import (
+ TypeSafeDocumentClassifier,
+)
+
+labels = ["billing", "technical"]
+
+pipeline = Pipeline()
+pipeline.add_component(
+ "classifier",
+ TypeSafeDocumentClassifier(
+ questions={
+ "department": {
+ "type": "choice",
+ "instructions": "Which department should handle this ticket?",
+ "criteria": dict.fromkeys(labels),
+ }
+ },
+ ),
+)
+pipeline.add_component(
+ "router",
+ MetadataRouter(
+ rules={
+ label: {
+ "field": "meta.typesafe.department.choice",
+ "operator": "==",
+ "value": label,
+ }
+ for label in labels
+ }
+ ),
+)
+pipeline.connect("classifier.documents", "router.documents")
+
+result = pipeline.run(
+ {
+ "classifier": {
+ "documents": [
+ Document(content="I was charged twice for my subscription."),
+ Document(
+ content="The app crashes every time I open the settings page."
+ ),
+ ]
+ }
+ }
+)
+print(
+ {
+ output: [document.content for document in documents]
+ for output, documents in result["router"].items()
+ }
+)
+# {'billing': ['I was charged twice for my subscription.'],
+# 'technical': ['The app crashes every time I open the settings page.'],
+# 'unmatched': []}
+```
diff --git a/docs-website/versioned_docs/version-3.3/pipeline-components/routers.mdx b/docs-website/versioned_docs/version-3.3/pipeline-components/routers.mdx
index 3a7459869c..4d4c16b185 100644
--- a/docs-website/versioned_docs/version-3.3/pipeline-components/routers.mdx
+++ b/docs-website/versioned_docs/version-3.3/pipeline-components/routers.mdx
@@ -19,4 +19,5 @@ Routers is a group of components that route queries or documents to other compon
| [MetadataRouter](routers/metadatarouter.mdx) | Routes documents based on their metadata field values. |
| [TextLanguageRouter](routers/textlanguagerouter.mdx) | Routes queries based on their language. |
| [TransformersTextRouter](routers/transformerstextrouter.mdx) | Routes text input to various output connections based on a model-defined categorization label. |
-| [TransformersZeroShotTextRouter](routers/transformerszeroshottextrouter.mdx) | Routes text input to various output connections based on user-defined categorization label. |
\ No newline at end of file
+| [TransformersZeroShotTextRouter](routers/transformerszeroshottextrouter.mdx) | Routes text input to various output connections based on user-defined categorization label. |
+| [TypeSafeTextRouter](routers/typesafetextrouter.mdx) | Routes text input to the output of the label a TypeSafe System One model picks for it. |
diff --git a/docs-website/versioned_docs/version-3.3/pipeline-components/routers/typesafetextrouter.mdx b/docs-website/versioned_docs/version-3.3/pipeline-components/routers/typesafetextrouter.mdx
new file mode 100644
index 0000000000..0dc236c60a
--- /dev/null
+++ b/docs-website/versioned_docs/version-3.3/pipeline-components/routers/typesafetextrouter.mdx
@@ -0,0 +1,146 @@
+---
+title: "TypeSafeTextRouter"
+id: typesafetextrouter
+slug: "/typesafetextrouter"
+description: "Routes a text to the output of the label a TypeSafe System One model, such as Jev, picks for it."
+---
+
+# TypeSafeTextRouter
+
+Routes a text to the output of the label a TypeSafe System One model, such as Jev, picks for it.
+
+
+
+| | |
+| --- | --- |
+| **Most common position in a pipeline** | Flexible |
+| **Mandatory init variables** | `labels`: The labels to route between
`api_key`: The TypeSafe API key. Can be set with the `TYPESAFE_API_KEY` env var. |
+| **Mandatory run variables** | `text`: The text to route |
+| **Output variables** | `
+
+## Overview
+
+`TypeSafeTextRouter` asks a [TypeSafe](https://typesafe.ai) System One model a single `choice` question over `labels` and sends the text to the output of the label it picks. System One models such as Jev don't generate text. They answer in one request and return calibrated probabilities.
+
+Pass `labels` as a list of label names, or as a dict of label to description to tell the model what each label means. `instructions` sets the question the model answers, for example "Which team should handle this support request?".
+
+When you set `min_confidence`, the component gets an extra `low_confidence` output. Texts whose answer has a `confidence` below the threshold go there instead, so you can send uncertain texts to a fallback such as an LLM or a human. Good thresholds depend on the model and the labels, so pick one based on your own data. See TypeSafe's guide to [confidence-gated routing](https://docs.typesafe.ai/patterns/confidence-routing).
+
+### Authentication
+
+The component reads the API key from the `TYPESAFE_API_KEY` environment variable by default. You can also pass it at initialization with `api_key`:
+
+```python
+from haystack.utils import Secret
+from haystack_integrations.components.routers.typesafe import TypeSafeTextRouter
+
+router = TypeSafeTextRouter(
+ labels=["billing", "technical"], api_key=Secret.from_token("")
+)
+```
+
+### Running models locally with Ollaya
+
+The component works with any server that implements the TypeSafe API, such as [Ollaya](https://ollaya.dev), which runs open decision models on your own machine. Set `api_base_url` to the server and `model` to one of its models. Ollaya accepts any non-empty API key unless it is configured with one.
+
+```shell
+docker run -d --name ollaya -p 11435:11435 -v ollaya:/home/ollaya/.ollaya ghcr.io/ollaya-dev/ollaya
+docker exec ollaya ollaya pull laya:en
+```
+
+```python
+from haystack.utils import Secret
+from haystack_integrations.components.routers.typesafe import TypeSafeTextRouter
+
+router = TypeSafeTextRouter(
+ labels=["billing", "technical"],
+ model="laya:en",
+ api_key=Secret.from_token("local"),
+ api_base_url="http://localhost:11435",
+)
+```
+
+## Usage
+
+Install the `typesafe-haystack` package to use the `TypeSafeTextRouter`:
+
+```shell
+pip install typesafe-haystack
+```
+
+### On its own
+
+```python
+from haystack_integrations.components.routers.typesafe import TypeSafeTextRouter
+
+router = TypeSafeTextRouter(
+ 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,
+)
+
+print(router.run(text="I was charged twice for my subscription."))
+# {'billing': 'I was charged twice for my subscription.'}
+```
+
+### In a pipeline
+
+The following pipeline routes a support question to the help articles of the team that handles it. Each label's output goes to its own retriever:
+
+```python
+from haystack import Document, Pipeline
+from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
+from haystack.document_stores.in_memory import InMemoryDocumentStore
+from haystack_integrations.components.routers.typesafe import TypeSafeTextRouter
+
+billing_store = InMemoryDocumentStore()
+billing_store.write_documents(
+ [
+ Document(
+ content="If you were charged twice, contact support and we refund the duplicate charge within 5 business days."
+ )
+ ]
+)
+technical_store = InMemoryDocumentStore()
+technical_store.write_documents(
+ [
+ Document(
+ content="If the app crashes on startup, update to the latest version and clear the app cache."
+ )
+ ]
+)
+
+pipeline = Pipeline()
+pipeline.add_component(
+ "router",
+ TypeSafeTextRouter(
+ labels={
+ "billing": "Payments, invoices, refunds and charges",
+ "technical": "Bugs, crashes and errors in the product",
+ },
+ instructions="Which team should handle this support request?",
+ ),
+)
+pipeline.add_component(
+ "billing_retriever", InMemoryBM25Retriever(document_store=billing_store)
+)
+pipeline.add_component(
+ "technical_retriever", InMemoryBM25Retriever(document_store=technical_store)
+)
+pipeline.connect("router.billing", "billing_retriever.query")
+pipeline.connect("router.technical", "technical_retriever.query")
+
+result = pipeline.run(
+ {"router": {"text": "I was charged twice, how do I get my money back?"}}
+)
+print(result["billing_retriever"]["documents"][0].content)
+# If you were charged twice, contact support and we refund the duplicate charge within 5 business days.
+```
diff --git a/docs-website/versioned_sidebars/version-3.3-sidebars.json b/docs-website/versioned_sidebars/version-3.3-sidebars.json
index 7b4c9e6635..41979fc0ac 100644
--- a/docs-website/versioned_sidebars/version-3.3-sidebars.json
+++ b/docs-website/versioned_sidebars/version-3.3-sidebars.json
@@ -241,7 +241,8 @@
},
"items": [
"pipeline-components/classifiers/documentlanguageclassifier",
- "pipeline-components/classifiers/transformerszeroshotdocumentclassifier"
+ "pipeline-components/classifiers/transformerszeroshotdocumentclassifier",
+ "pipeline-components/classifiers/typesafedocumentclassifier"
]
},
{
@@ -680,7 +681,8 @@
"pipeline-components/routers/metadatarouter",
"pipeline-components/routers/textlanguagerouter",
"pipeline-components/routers/transformerstextrouter",
- "pipeline-components/routers/transformerszeroshottextrouter"
+ "pipeline-components/routers/transformerszeroshottextrouter",
+ "pipeline-components/routers/typesafetextrouter"
]
},
{