---
title: "Swap Adapters"
description: "How to replace XI Lucent's embedder, vector store, chunking strategy, scorer, or any other component without touching the rest of the pipeline."
published: 2026-05-14T12:11:34.532763+00:00
updated: 2026-05-14T12:11:34.532763+00:00
tags: ["adapters", "customization", "guide", "lucent"]
url: https://xiobjects.com/docs/xio/lucent/guides/swap-adapters
source: XI Objects
---

<!-- xion:doctype xion+markdown -->
<!-- xion:metadata
{
  "version": "1.0",
  "content_type": "application/xion\u002Bmarkdown",
  "source_type": "xi-content/doc",
  "generator": "xio-content-publisher/1.0.0",
  "generated": "2026-05-14T12:10:20.6374972\u002B00:00",
  "encoding": "utf-8",
  "render_intent": "markdown",
  "title": "Swap Adapters",
  "slug": "xio/lucent/guides/swap-adapters",
  "copyright": "\u00A9 2026 XI Objects Inc"
}
-->

# Swap Adapters

Every component in Lucent sits behind an interface. The `AddLucent` registration builder gives you methods to replace any component independently. Swapping the embedder doesn't require touching the vector store; swapping the vector store doesn't require touching the chunker.

## How the registration methods work

Some adapters are singletons in the pipeline (one embedder, one vector store, one retrieval strategy) while others are additive (multiple chunking strategies and pipeline observers are all invoked). The `Add*` methods reflect this:

| Method | Behavior |
|--------|----------|
| `AddEmbedder<T>()` | Replaces the embedder. Last call wins. |
| `AddVectorStore<T>()` | Replaces the vector store. Last call wins. |
| `AddTextSearchStore<T>()` | Replaces the text search store. Last call wins. |
| `AddContentDetector<T>()` | Replaces the content detector. Last call wins. |
| `AddRetrievalStrategy<T>()` | Replaces the retrieval strategy. Last call wins. |
| `AddScorer<T>()` | Replaces the scorer. Last call wins. |
| `AddChunkingStrategy<T>()` | Adds to the strategy set. All registered strategies coexist, routed by content type. |
| `AddDocumentDecomposer<T>()` | Adds to the decomposer set. Routed by content type. |
| `AddPipelineObserver<T>()` | Adds to the observer set. All observers are called on every pipeline event. |

Calling `AddEmbedder` twice means the second call wins. If you want to combine multiple embedders with content-type routing, implement a custom `IEmbedder` that delegates.

## Replace the embedder

The default `OnnxEmbedder` runs locally with no external dependencies. If your deployment needs a different model or a hosted embedding service, swap it out:

```csharp
services.AddLucent(opts =>
{
    opts.AddEmbedder<OpenAiEmbedder>();
});
```

**Important:** Changing the embedder invalidates all stored vectors. Lucent detects this automatically: when the current embedder's `ModelId` differs from the model stored in the document registry, it re-embeds every chunk on next ingest. Existing queries will still work against old vectors until documents are re-ingested.

If you're building a custom embedder, implement `IEmbedder`:

```csharp
public class MyEmbedder : IEmbedder
{
    public int Dimensions => 1536;
    public string ModelId => "my-model-v1";

    public Task<float[]> EmbedAsync(string text, CancellationToken ct) => /* ... */;
    public Task<IReadOnlyList<float[]>> EmbedBatchAsync(IReadOnlyList<string> texts, CancellationToken ct) => /* ... */;
}

services.AddLucent(opts =>
{
    opts.AddEmbedder<MyEmbedder>();
});
```

## Replace the vector store

The default `SqliteVectorStore` is suitable for most deployments. For very large collections or multi-node setups, swap to an external store:

```csharp
services.AddLucent(opts =>
{
    opts.AddVectorStore<QdrantVectorStore>();
});
```

Custom vector stores implement `IVectorStore`. The interface handles upsert, cosine KNN search, document registry operations, signed source storage, and export/import.

## Add a chunking strategy

Chunking strategies are routed by content type. Registering a custom strategy for a specific type doesn't affect how other types are handled:

```csharp
services.AddLucent(opts =>
{
    // Built-in strategies for prose and code
    opts.AddChunkingStrategy<SemanticChunker>();
    opts.AddChunkingStrategy<StructuralChunker>();

    // Custom strategy for a proprietary format
    opts.AddChunkingStrategy<MyCustomFormatChunker>();
});
```

Your custom strategy declares which content types it handles via `IChunkingStrategy.ContentTypes`. If two strategies claim the same content type, the last registered one wins.

## Add a cross-encoder scorer

The default `NoOpScorer` is a pass-through. For higher precision at the top of your ranked list, swap in `CrossEncoderScorer`:

```csharp
services.AddLucent(opts =>
{
    opts.AddScorer<CrossEncoderScorer>();
});
```

`CrossEncoderScorer` runs a (query, chunk) pair through an ONNX cross-encoder model. It's slower than the default RRF pass, but re-ranking the top-10 or top-20 candidates typically adds only tens of milliseconds. The additional provisioning step downloads the cross-encoder model at startup.

## Add pipeline observers

Observers are called at every pipeline stage on both ingestion and retrieval. They're useful for telemetry, custom logging, and debugging:

```csharp
public class MyTelemetryObserver : IPipelineObserver
{
    public Task OnContentDetected(string collectionId, string documentId, ContentDetectionResult result, TimeSpan duration) =>
        /* emit metric */ Task.CompletedTask;

    public Task OnChunked(string collectionId, string documentId, IReadOnlyList<Chunk> chunks, TimeSpan duration) =>
        /* emit metric */ Task.CompletedTask;

    // ... other pipeline events
}

services.AddLucent(opts =>
{
    opts.AddPipelineObserver<MyTelemetryObserver>();
});
```

Multiple observers are all called; the order matches registration order. Observer failures are caught and logged as warnings (`event_id: 9001`), so a broken observer doesn't take down the pipeline.

## Configure resilience for hosted embedders

Lucent ships a resilience layer for embedders that call external services. It adds Polly-based retries with exponential backoff and a circuit breaker:

```csharp
services.AddLucent();
services.AddLucentResilientEmbedder(o =>
{
    o.RetryCount = 3;
    o.CircuitBreakerFailureRatio = 0.5;
});
```

Caller errors (invalid arguments, malformed documents) are never retried. Only transient transport failures trigger the retry policy.

## Disable full-text indexing

If you don't need BM25 keyword search and want to skip FTS5 indexing at ingest time:

```csharp
services.AddLucent(opts =>
{
    opts.AddTextSearchStore<NullTextSearchStore>();
    opts.AddRetrievalStrategy<VectorOnlyStrategy>();
});
```

Both changes are needed: the text search store removal stops indexing at ingest, and the retrieval strategy swap stops the FTS5 path from being queried.

## Next Steps

- [Chunking Strategies](/docs/xio/lucent/concepts/chunking) — understand what each built-in strategy does before replacing it
- [Hybrid Search](/docs/xio/lucent/concepts/hybrid-search) — how the retrieval strategy and scorer interact
- [Ingestion & Retrieval Pipeline](/docs/xio/lucent/concepts/pipeline) — the full pipeline flow that your adapters plug into
<!-- xion:trust
{
  "v": 1,
  "canon_v": 1,
  "ctx": "xiobjects.com/content",
  "hash_blake3_hex": "00315256f9b7e3f2c78b351c52d5d60bc65667a32084503511b3c9541ee97335",
  "hash_sha256_hex": null,
  "sig_alg": "ed25519",
  "sig_b64": "hfLe3Jfdshv0IblOOD1xFhA0UNvGfbjJgukgIVr62NPoh4iZwFtJsaNG8Rjydiw1wlV9eAtHeg-oJ3qjkjKECw",
  "pubkey_b64": "h-awvV8Rn-juph_c2Y7UH5A6e7NaFia3zBiMrJUOMOo",
  "x509_chain_pem": [
    "-----BEGIN CERTIFICATE-----\r\nMIIB9DCCAaagAwIBAgIQBrrNsmRlBvKQdA4idEliJjAFBgMrZXAwLjEsMCoGA1UE\r\nAwwjWEkgT2JqZWN0cyBJbmMgQ29udHJvbCBJbnRlcm1lZGlhdGUwHhcNMjYwNTEz\r\nMjI0NjA1WhcNMjYwNjEyMjI0NjA1WjBLMR4wHAYDVQQDDBV4aW8tY29udGVudC1w\r\ndWJsaXNoZXIxFzAVBgNVBAoMDlhJIE9iamVjdHMgSW5jMRAwDgYDVQQLDAdDb250\r\nZW50MCowBQYDK2VwAyEAh\u002BawvV8Rn\u002Bjuph/c2Y7UH5A6e7NaFia3zBiMrJUOMOqj\r\ngbwwgbkwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI\r\nKwYBBQUHAyQwZQYDVR0jBF4wXIAUOym3mFmw/qs1fgKrujCkxhrTk7KhLqQsMCox\r\nKDAmBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0GCFFJgN/ix\r\nQn72H6h3T5lEr9f8lJQFMB0GA1UdDgQWBBS1LSJi5\u002BeqBq8h974Ht9HTgIcdgTAF\r\nBgMrZXADQQCKjXbPwnk/DZHmLQstUWRzU6GSf\u002BSHTXTTZCtRLbmJKxT17Qlbpexc\r\nsRgdSpxNWpJPe9Fr4vwhRkESMqMIpgQO\r\n-----END CERTIFICATE-----\r\n",
    "-----BEGIN CERTIFICATE-----\r\nMIIByDCCAXqgAwIBAgIUUmA3\u002BLFCfvYfqHdPmUSv1/yUlAUwBQYDK2VwMCoxKDAm\r\nBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0EwHhcNMjUxMTAy\r\nMDMxNzEyWhcNMzAxMTAxMDMxNzEyWjAuMSwwKgYDVQQDDCNYSSBPYmplY3RzIElu\r\nYyBDb250cm9sIEludGVybWVkaWF0ZTAqMAUGAytlcAMhAFSS/pggSRmTcAMko7uc\r\nATH8OHgxVymd5mBFlPXbJkgio4GtMIGqMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD\r\nVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQ7KbeYWbD\u002BqzV\u002BAqu6MKTGGtOTsjBlBgNV\r\nHSMEXjBcgBQAZRTDswSVORu\u002BkUOKX6WvrOvmQKEupCwwKjEoMCYGA1UEAwwfSW5z\r\ndGl0dXRlIG9mIFByb3ZlbmFuY2UgUm9vdCBDQYIUJqoJlpiSFg\u002B7W5IJLMrLttgR\r\nQp4wBQYDK2VwA0EA5FOht7YOsVRPp/FOKMQ\u002B3Mo9JxrvGR3ylKWAWNm6OUV7N3DB\r\nI9cD62wU5I0d0EKDBy0CX9DnoqUyxv5yguraAA==\r\n-----END CERTIFICATE-----\r\n",
    "-----BEGIN CERTIFICATE-----\r\nMIIBaTCCARugAwIBAgIUJqoJlpiSFg\u002B7W5IJLMrLttgRQp4wBQYDK2VwMCoxKDAm\r\nBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0EwHhcNMjUxMTAy\r\nMDMwNTEyWhcNMzUxMDMxMDMwNTEyWjAqMSgwJgYDVQQDDB9JbnN0aXR1dGUgb2Yg\r\nUHJvdmVuYW5jZSBSb290IENBMCowBQYDK2VwAyEAEWNZl\u002Br3IC7\u002BgBh90Yo1kWk1\r\npZCVzVuFdFT7qBBU8W2jUzBRMB0GA1UdDgQWBBQAZRTDswSVORu\u002BkUOKX6WvrOvm\r\nQDAfBgNVHSMEGDAWgBQAZRTDswSVORu\u002BkUOKX6WvrOvmQDAPBgNVHRMBAf8EBTAD\r\nAQH/MAUGAytlcANBAO6QeydOFNrN75qNyftggYudsxMyl4w9qWkSdZ6hlhrRcbSr\r\niG9Si0kbrIJOwYB/LTBU0RM4Rl\u002Bo9PM3Qp0mPwo=\r\n-----END CERTIFICATE-----\r\n"
  ],
  "key_id": "SDyVO7FvlAM-6CvQ62VZYOBO7JADFqLquUunUABRgKg",
  "created_at": "2026-05-14T12:10:20Z"
}
-->