ExAgent.Providers.JinaV5 (ExAgent v0.3.0)

Copy Markdown View Source

Jina embeddings v5 - an embeddings-only provider for a self-hosted server.

provider = ExAgent.Providers.JinaV5.new(base_url: System.fetch_env!("JINA_URL"))

{:ok, docs} =
  ExAgent.embed(provider, chunks, task: :retrieval, args: [prompt_name: :document])

{:ok, query} =
  ExAgent.embed(provider, "who supervises processes?",
    task: :retrieval,
    args: [prompt_name: :query]
  )

It implements ExAgent.Provider.embed/3 only; chat/3 returns {:error, %ExAgent.Error{type: :unsupported}}.

Behind Modal's proxy auth

ExAgent.Providers.JinaV5.new(
  base_url: System.fetch_env!("MODAL_JINA_URL"),
  api_key: System.fetch_env!("MODAL_API_KEY"),
  headers: [
    {"Modal-Key", System.fetch_env!("MODAL_KEY")},
    {"Modal-Secret", System.fetch_env!("MODAL_SECRET")}
  ]
)

Tasks are v5's, not this library's

v5 takes four task names, and the query/document distinction moved out of the task and into a separate prompt_name:

Taskprompt_name
:retrievalrequired - :query for the search side, :document for the indexed side
:text_matchingrejected
:clusteringrejected
:classificationrejected

This is why the module is named for the version. v3 and v4 spelled the same thing as a single "retrieval.query" / "retrieval.passage" task, so one provider covering both would have to lie about one of them.

Encode the two retrieval sides differently

Asymmetric retrieval needs it. Embedding your documents and your queries with the same prompt_name degrades recall invisibly, and the fix is a full re-embed - so :retrieval without a prompt_name is an error here rather than a default.

Dimensions

1024 by default, Matryoshka-truncatable to 32, 64, 128, 256, 512, or 768. The server truncates and re-normalizes; pass args: [normalize: false] to get the raw truncated vector instead, which is not unit length.

The server contract

Verified against a running deployment. POST {base_url}/embed:

{"texts": ["..."], "task": "retrieval", "prompt_name": "document",
 "dimensions": 256, "normalize": true}

answering

{"model": "...", "task": "...", "prompt_name": "...",
 "dimensions": 256, "input_count": 1, "embeddings": [[...]]}

Unknown body fields are rejected by the server, which is why :args is a closed allowlist rather than a passthrough. Batches are capped at 512 inputs.

This is not the shape of Jina's hosted api.jina.ai service, which speaks an OpenAI-style /v1/embeddings with model/input and a data[].embedding response. Pointing this provider at the hosted API will not work.

Summary

Functions

Returns the task atoms this provider accepts.

Creates a provider with validated options and an initialized Req client.

Types

t()

@type t() :: %ExAgent.Providers.JinaV5{
  api_key: String.t() | nil,
  base_url: String.t(),
  headers: [{String.t(), String.t()}],
  model: String.t(),
  req: Req.Request.t() | nil
}

Functions

embedding_tasks()

@spec embedding_tasks() :: [ExAgent.Embeddings.task()]

Returns the task atoms this provider accepts.

Examples

iex> ExAgent.Providers.JinaV5.embedding_tasks()
[:retrieval, :text_matching, :clustering, :classification]

new(opts)

@spec new(keyword()) :: t()

Creates a provider with validated options and an initialized Req client.

Options

  • :base_url (String.t/0) - Required. Root URL of the v5 server; requests go to #{base_url}/embed

  • :api_key - Sugar for an Authorization: Bearer header The default value is nil.

  • :model (String.t/0) - Recorded on the result when the server does not report its own The default value is "jina-embeddings-v5-text-small".

  • :headers (list of term/0) - Extra request headers, e.g. Modal's Modal-Key / Modal-Secret The default value is [].