Retrieval Providers¶
A retrieval provider is the seam between OpenArmature's graph engine and a vector or reranking backend (a hosted API like OpenAI, Cohere, or Jina, or a self-hosted Text Embeddings Inference server). The engine does not know about embeddings or rerankers; nodes call providers, providers do the wire work. For what embedding and reranking are and how they fit a pipeline, see the Retrieval concept page; this section is the catalog of what ships and how to run it.
What ships¶
Four vendors ship as reference providers. Each embedding surface and each rerank surface is a separate provider instance bound to one model:
| Provider | Embedding | Rerank | Default base_url |
|---|---|---|---|
| OpenAI-compatible | OpenAIEmbeddingProvider |
not offered | https://api.openai.com |
| Cohere | CohereEmbeddingProvider |
CohereRerankProvider |
https://api.cohere.com |
| Jina | JinaEmbeddingProvider |
JinaRerankProvider |
https://api.jina.ai |
| TEI (self-hosted) | TeiEmbeddingProvider |
TeiRerankProvider |
none (pass your instance URL) |
A few provider-specific notes:
- OpenAI-compatible is embedding-only and symmetric.
base_urlis overridable, so the same provider drives any OpenAI-shaped embedding endpoint (vLLM, LocalAI, TEI's OpenAI surface).dimensionsmaps to the wire for Matryoshka models that support truncation. - Cohere embedding requires an
input_type; the mapping sends a sensible default when you omit it. Its reranker returns scores without echoing documents, so map results back to your candidates byScoredDocument.index. - Jina realizes
input_typeas its native task field over a fixed set of values and reports token usage on both surfaces. - TEI is the self-hosted option: you pass the instance URL, it reports no usage, and its reranker chunks a large candidate pool the same way embedding chunks a large input list. See Self-hosting TEI.
For a backend none of these cover, write your own; see Authoring a Provider.
The contract¶
Retrieval is two protocols, one async method each:
from collections.abc import Sequence
from typing import Protocol
from openarmature.retrieval import (
EmbeddingResponse,
EmbeddingRuntimeConfig,
RerankResponse,
RerankRuntimeConfig,
)
class EmbeddingProvider(Protocol):
async def ready(self) -> None: ...
async def embed(
self,
input: Sequence[str],
*,
config: EmbeddingRuntimeConfig | None = None,
) -> EmbeddingResponse: ...
class RerankProvider(Protocol):
async def ready(self) -> None: ...
async def rerank(
self,
query: str,
documents: Sequence[str],
*,
top_k: int | None = None,
config: RerankRuntimeConfig | None = None,
) -> RerankResponse: ...
ready()verifies the bound model is reachable. Pre-flight check, typically called once before invoking the graph.embed()returns one vector per input string, in input order.rerank()scores the documents against the query and returns them sorted best-first.
Behaviour guarantees¶
- Input order.
embed()returnslen(input)vectors,vectors[i]being the embedding ofinput[i], regardless of how the provider paginated the request. All vectors share one dimensionality. - Arbitrary-length input.
embed()accepts any-length list; the mapping chunks under the provider's per-request cap and stitches the result.rerank()chunks a large candidate pool the same way. - Usage is a record or null.
response.usageis a token-accounting record when the provider reports one andNoneotherwise. The mapping never fabricates a record, a zero, or an estimate. - Reentrant and stateless. Safe to call concurrently from many nodes; no implicit state carries between calls. Inputs are never mutated.
- No retry on transient errors. That is middleware's job; wrap the
calling node in
RetryMiddlewareor similar.
Errors¶
Retrieval calls raise the same canonical error categories as LLM calls
(from openarmature.llm.errors), mapped from the provider's HTTP
response. The retrieval-applicable subset:
| Error | Trigger |
|---|---|
ProviderAuthentication |
401 / 403 (bad or missing key) |
ProviderRateLimit |
429 |
ProviderInvalidModel |
404 (bound model not found) |
ProviderInvalidRequest |
400 / 413 / 422 (malformed or over-length request, empty input) |
ProviderUnavailable |
5xx, network failure, timeout |
ProviderInvalidResponse |
200 OK that fails to parse, or a malformed response body |
ProviderUnavailable and ProviderRateLimit are in
TRANSIENT_CATEGORIES, the canonical "safe to retry" set the default
retry-middleware classifier uses. A malformed usage figure does not fail
the call: the vectors or scores are sound, so the count is recorded as
unknown (usage = None) rather than raising.
A minimal example¶
Direct usage of the providers, without the engine in the picture:
import asyncio
from openarmature.retrieval import CohereRerankProvider, OpenAIEmbeddingProvider
async def main() -> None:
embedder = OpenAIEmbeddingProvider(model="text-embedding-3-small", api_key="sk-...")
reranker = CohereRerankProvider(model="rerank-v3.5", api_key="...")
try:
vectors = (await embedder.embed(["the lunar south pole"])).vectors
ranked = (await reranker.rerank("water on the Moon", ["...", "..."])).results
print(len(vectors), [r.index for r in ranked])
finally:
await embedder.aclose()
await reranker.aclose()
asyncio.run(main())
In a real graph you construct the providers once at startup and let nodes
call them inside their bodies, where their EmbeddingEvent / RerankEvent
reach any attached observer.
Where to next¶
- Self-hosting TEI: run Text Embeddings Inference for
embeddings and reranking, from a first
docker runto a production deployment, and wire the TEI providers to it. - Authoring a Provider: implement the
EmbeddingProviderorRerankProviderprotocol for a backend none of the bundled providers cover. - API reference:
openarmature.retrieval: the full public surface (the providers, response types, runtime configs).