Skip to content

Jina (embeddings)

Jina’s embedding line covers ground no other provider on the list does: sparse vectors for hybrid search (elser-v2), multivector late interaction (jina-embeddings-v4), and binary-quantized output for storage-tight indexes — alongside the standard text + image dense retrieval models.

If embedding is the only thing you need from a provider, Jina is the broadest single surface. (Reranking is on the roadmap and uses the same auth — see reranking.)

Install

Terminal window
pnpm add @effect-uai/core @effect-uai/jina effect

Wire it up

import { Config, Effect, Layer } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import { layer as jinaEmbeddingLayer } from "@effect-uai/jina/JinaEmbedding"
const provider = Layer.unwrap(
Effect.gen(function* () {
const apiKey = yield* Config.redacted("JINA_API_KEY")
return jinaEmbeddingLayer({ apiKey })
}),
)
const mainLayer = provider.pipe(Layer.provide(FetchHttpClient.layer))

jinaEmbeddingLayer registers two service tags from one underlying implementation:

  • JinaEmbedding — the typed tag. Yield this for the full Jina task vocabulary and the narrow encoding union.
  • EmbeddingModel — the generic tag for provider-portable code. The generic registration maps cross-provider "query" / "document" to Jina’s retrieval.query / retrieval.passage.

Request shape

interface JinaEmbedRequest extends Omit<CommonEmbedRequest, "model" | "task" | "encoding"> {
readonly model: JinaEmbeddingModel
readonly task?: JinaTask
readonly encoding?: JinaEncoding
readonly dimensions?: number
}
type JinaTask =
| "retrieval.query"
| "retrieval.passage" // asymmetric retrieval
| "text-matching" // symmetric
| "code.query"
| "code.passage" // v4 code retrieval
| "classification"
| "separation" // v3 / v5
| (string & {})
type JinaEncoding = "float32" | "binary" | "sparse" | "multivector"

The (string & {}) tail on JinaTask accepts any string so newly released task names work without an SDK update.

Calling it

Late-interaction (multivector) retrieval:

import { JinaEmbedding } from "@effect-uai/jina/JinaEmbedding"
const program = Effect.gen(function* () {
const jina = yield* JinaEmbedding
return jina.embedMany({
model: "jina-embeddings-v4",
inputs: documents,
task: "retrieval.passage",
encoding: "multivector",
})
})

Binary-quantized dense retrieval:

const result =
yield *
jina.embed({
model: "jina-embeddings-v4",
input: query,
task: "retrieval.query",
encoding: "binary", // ~32× smaller than float32
})

Sparse hybrid retrieval (ELSER-style):

const result =
yield *
jina.embed({
model: "elser-v2",
input: query,
encoding: "sparse",
})
// result.embedding._tag === "sparse"
// result.embedding.weights is Record<string, number>

Score sparse vectors with Vector.sparseCosine; multivector with Vector.maxSim; dense with Vector.cosine.

Models

JinaEmbeddingModel is a literal union with a (string & {}) tail:

ModelModalitiesEncodingsNotes
jina-embeddings-v4text, imagefloat32, binary, multivectorFlagship, 32k context, LoRA-bound tasks.
jina-embeddings-v5-text-smalltextfloat32, binaryMultilingual, GGUF-quantizable.
jina-embeddings-v5-text-nanotextfloat32, binaryEdge-deployable.
jina-embeddings-v3textfloat32, binaryLegacy text-only.
jina-clip-v2image, textfloat32, binaryCLIP-style multimodal.

elser-v2 (learned sparse) is also accessible via the (string & {}) tail; pass encoding: "sparse" to receive token-keyed weights.

Reference: Jina embeddings.

Encoding support

encodingWire behaviour
float32 (default)Default JSON number[].
binaryembedding_type: "binary" — bit-packed Uint8Array.
sparseNo flag; the chosen model decides (e.g. elser-v2).
multivectorreturn_multivector: true — one vector per token.

Compatibility is checked at the response level, not via a hardcoded model-encoding table. If you ask for an encoding the chosen model doesn’t produce, the layer fails with a typed AiError.InvalidRequest (“requested encoding=… but the response contains a … embedding”). New models work without an SDK update.

Image input shapes

Jina v4 accepts URL or base64 image inputs:

  • url — passed through; Jina fetches.
  • base64 — passed through.
  • bytes — auto-encoded to base64.

Mixed content[] inputs with multiple parts are rejected — Jina’s flat input[] would lose the grouping. Split into separate inputs[] entries.

Errors

HTTP failures map to typed AiError variants:

StatusError
429AiError.RateLimited
408/504AiError.Timeout
401AiError.AuthFailed (auth)
403AiError.AuthFailed (permission)
402AiError.AuthFailed (billing)
413AiError.ContextLengthExceeded
>= 500AiError.Unavailable
other 4xxAiError.InvalidRequest

Encoding-mismatch (the chosen model didn’t produce the requested encoding) maps to AiError.InvalidRequest with param: "encoding".

See also