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:
| Task | prompt_name |
|---|---|
:retrieval | required - :query for the search side, :document for the indexed side |
:text_matching | rejected |
:clustering | rejected |
:classification | rejected |
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
Functions
@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]
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 anAuthorization: Bearerheader The default value isnil.: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 ofterm/0) - Extra request headers, e.g. Modal'sModal-Key/Modal-SecretThe default value is[].