Skip to content

Commit 96ca62c

Browse files
Add 0078 Jina embed + rerank wire mapping (#209)
Add JinaEmbeddingProvider (/v1/embeddings) and JinaRerankProvider (/v1/rerank) realizing the retrieval-provider 0078 Jina wire mapping: the input_type -> task closed set (query -> retrieval.query, document -> retrieval.passage; an unrecognized value is rejected pre-send), token usage reported (total_tokens -> input_tokens), and the vendor error map (429 -> provider_rate_limit, 422 -> provider_invalid_request). The rerank document echo unwraps Jina's TextDoc object form ({"text": ...}) as well as a bare string, since the live API returns the object shape when return_documents is true. Reuse the general wire-capture conformance harness (un-defer fixtures 018-022) and add an expected_wire_headers subset check so the Bearer auth assertions are live. Env-gated live tests cover embed, rerank, and the return_documents echo.
1 parent b1b655e commit 96ca62c

7 files changed

Lines changed: 1386 additions & 20 deletions

File tree

conformance.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -779,10 +779,11 @@ since = "0.16.0"
779779
note = "Retrieval-provider TEI (Text Embeddings Inference) wire mapping (§8 / §8.1) -- the first concrete §8 mapping. EmbeddingRuntimeConfig gains the declared input_type field (§2 / §3, the cross-vendor query/document knob; absent = symmetric), flowed into EmbeddingEvent.request_params with absence-is-meaningful semantics (graph-engine §6). Two bundled providers ship: TeiEmbeddingProvider posts POST {base_url}/embed with {inputs[, prompt_name][, dimensions]} -- input_type is realized as TEI's native prompt_name looked up from the construction input_type -> prompt_name map (server-side prompts), or a client-side query_prefix / document_prefix prepend when only prefixes are configured; absent input_type sends neither (byte-identical to the symmetric path). TEI /embed returns a bare vector array in input order and no usage object, so EmbeddingResponse.usage = null (never fabricated). TeiRerankProvider posts POST {base_url}/rerank with {query, texts, truncate: false, return_text} -- texts maps directly onto documents, return_documents -> return_text; it parses the bare [{index, score, text?}] response, reads the text echo only when present, sorts by score descending, and carries response_id + usage null. It implements the MANDATORY rerank chunk-and-stitch (§8.1): when len(documents) exceeds the construction chunk_size (default 32, TEI's max-client-batch-size) it splits into consecutive <= chunk_size slices, issues one /rerank per slice (same query), re-bases each chunk's response index to its absolute position, concatenates, then applies §6 -- a GLOBAL sort by score descending and top_k AFTER the stitch, never per-chunk. truncate: false is sent explicitly on /rerank (fail-loud); the resulting TEI over-length error (HTTP 413 / 422) maps to provider_invalid_request (§7), joined by 400; other statuses map like the sibling providers (401/403 auth, 429 rate_limit, 404 invalid_model, else unavailable). Both providers dispatch the typed EmbeddingEvent / RerankEvent (graph-engine §6) per call, mutually exclusive with the failure variant. genai_system = 'tei'. Conformance fixtures 013-017 pass, driven through a new general wire-capture harness (mapping directive; tei_embedding_provider / tei_rerank_provider construction blocks; expected_wire_request as a single body or a per-chunk list + expected_wire_request_count + expected_wire_request_absent_keys, asserting the exact wire key set and the /embed / /rerank path) reused by the deferred 0078 / 0079 / 0090 / 0091 fixtures. The embed client-side chunk-and-stitch (0092, fixture 038) stays deferred."
780780

781781
# Spec v0.73.0 (proposal 0078). Retrieval-provider Jina hosted wire
782-
# mapping (§8.2). Not-yet; retrieval-provider fixtures 018-022 defer
783-
# with it.
782+
# mapping (§8.2), in the §8 mapping family established by 0077.
784783
[proposals."0078"]
785-
status = "not-yet"
784+
status = "implemented"
785+
since = "0.16.0"
786+
note = "Retrieval-provider Jina hosted wire mapping (§8 / §8.2) -- a §8 mapping in the family established by 0077. Two bundled providers ship, distinct instances (one model each) sharing the hosted endpoint: base_url defaults to https://api.jina.ai (origin only; the provider appends /v1/embeddings / /v1/rerank), and the required api_key is sent as Authorization: Bearer <key> (§8.2 *Construction*). JinaEmbeddingProvider posts POST {base_url}/v1/embeddings with {model, input, task?, dimensions?, truncate: false} -- input_type is realized as Jina's native `task` per the closed set (query -> retrieval.query, document -> retrieval.passage); absent input_type omits `task` (the symmetric default), and an unrecognized input_type is a pre-send provider_invalid_request (§7, no request issued). dimensions -> Jina's dimensions (Matryoshka) when set; truncate: false is sent explicitly (fail-loud). It parses the object envelope {model, usage: {total_tokens}, data: [{index, embedding}]}, orders the vectors by data[i].index into input order, and maps usage.total_tokens -> EmbeddingUsage.input_tokens (Jina reports usage, unlike TEI's null -- a record when the body surfaces it, never fabricated). JinaRerankProvider posts POST {base_url}/v1/rerank with {model, query, documents, top_n?, return_documents, truncation: false} -- documents maps directly onto the string array (no per-document wrapping), top_n <- top_k (omitted when None), and return_documents is sent EXPLICITLY (Jina's wire default is true but OA's is False, §2, so the mapping sends the resolved OA value). truncation: false is sent explicitly (fail-loud). It parses {model, usage: {total_tokens}, results: [{index, relevance_score, document?}]} (Cohere-shaped relevance_score), reads the document echo only when present (never auto-filled), sorts by relevance_score descending, enforces the §6 invariants (valid index, no duplicate, len(results) <= top_k), and maps usage.total_tokens -> RerankUsage.input_tokens with search_units null (Jina meters by tokens). raw is the verbatim response dict on both surfaces. Errors map per §8.2: 401/403 -> provider_authentication, 429 -> provider_rate_limit (NOT provider_unavailable), 404 -> provider_invalid_model, 422 (over-length / malformed) -> provider_invalid_request, else -> provider_unavailable. Jina enforces no per-call embed cap (server-side batching), so there is no client-side chunk-and-stitch. Both providers dispatch the typed EmbeddingEvent / RerankEvent (graph-engine §6) per call, mutually exclusive with the failure variant; input_type flows into EmbeddingEvent.request_params with absence-is-meaningful semantics, and RerankEvent.output_results is populated unconditionally on success (proposal 0089). genai_system = 'jina'. Conformance fixtures 018-022 pass, driven through the general wire-capture harness (0077) extended with an expected_wire_headers subset assertion locking the Authorization: Bearer <api_key> header on the outbound request."
786787

787788
# Spec v0.74.0 (proposal 0079). Retrieval-provider OpenAI-compatible
788789
# embeddings wire mapping (§8.3). Not-yet; retrieval-provider fixtures

src/openarmature/retrieval/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
validate_rerank_response,
1818
)
1919
from .providers.cohere import CohereRerankProvider
20+
from .providers.jina import JinaEmbeddingProvider, JinaRerankProvider
2021
from .providers.openai import OpenAIEmbeddingProvider
2122
from .providers.tei import TeiEmbeddingProvider, TeiRerankProvider
2223
from .response import (
@@ -35,6 +36,8 @@
3536
"EmbeddingResponse",
3637
"EmbeddingRuntimeConfig",
3738
"EmbeddingUsage",
39+
"JinaEmbeddingProvider",
40+
"JinaRerankProvider",
3841
"OpenAIEmbeddingProvider",
3942
"RerankProvider",
4043
"RerankResponse",

src/openarmature/retrieval/providers/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
from __future__ import annotations
44

55
from .cohere import CohereRerankProvider
6+
from .jina import JinaEmbeddingProvider, JinaRerankProvider
67
from .openai import OpenAIEmbeddingProvider
78
from .tei import TeiEmbeddingProvider, TeiRerankProvider
89

910
__all__ = [
1011
"CohereRerankProvider",
12+
"JinaEmbeddingProvider",
13+
"JinaRerankProvider",
1114
"OpenAIEmbeddingProvider",
1215
"TeiEmbeddingProvider",
1316
"TeiRerankProvider",

0 commit comments

Comments
 (0)