Skip to content

Commit ea2a508

Browse files
authored
feat: add Jina AI embedding provider (#245)
1 parent 390e088 commit ea2a508

File tree

13 files changed

+550
-12
lines changed

13 files changed

+550
-12
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ Create a configuration file `~/.openviking/ov.conf`:
227227
}
228228
```
229229

230-
> **Note**: For embedding models, currently only `volcengine` (Doubao) and `openai` providers are supported. For VLM models, we support multiple providers including volcengine, openai, deepseek, anthropic, gemini, moonshot, zhipu, dashscope, minimax, and more.
230+
> **Note**: For embedding models, currently `volcengine` (Doubao), `openai`, and `jina` providers are supported. For VLM models, we support multiple providers including volcengine, openai, deepseek, anthropic, gemini, moonshot, zhipu, dashscope, minimax, and more.
231231
232232
#### Configuration Examples
233233

README_CN.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,15 @@ OpenViking 支持多种模型服务:
9191
"dense": {
9292
"api_base" : "<api-endpoint>", // API 服务端点地址
9393
"api_key" : "<your-api-key>", // 模型服务的 API 密钥
94-
"provider" : "<provider-type>", // 提供商类型(volcengine 或 openai
94+
"provider" : "<provider-type>", // 提供商类型(volcengine、openaijina
9595
"dimension": 1024, // 向量维度
9696
"model" : "<model-name>" // Embedding 模型名称(如 doubao-embedding-vision-250615 或 text-embedding-3-large)
9797
}
9898
},
9999
"vlm": {
100100
"api_base" : "<api-endpoint>", // API 服务端点地址
101101
"api_key" : "<your-api-key>", // 模型服务的 API 密钥
102-
"provider" : "<provider-type>", // 提供商类型(volcengine 或 openai
102+
"provider" : "<provider-type>", // 提供商类型(volcengine、openaijina
103103
"model" : "<model-name>" // VLM 模型名称(如 doubao-seed-1-8-251228 或 gpt-4-vision-preview)
104104
}
105105
}

docs/en/api/01-overview.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ Minimal configuration example:
3535
"dense": {
3636
"api_base": "<api-endpoint>",
3737
"api_key": "<your-api-key>",
38-
"provider": "<volcengine|openai>",
38+
"provider": "<volcengine|openai|jina>",
3939
"dimension": 1024,
4040
"model": "<model-name>"
4141
}
4242
},
4343
"vlm": {
4444
"api_base": "<api-endpoint>",
4545
"api_key": "<your-api-key>",
46-
"provider": "<volcengine|openai>",
46+
"provider": "<volcengine|openai|jina>",
4747
"model": "<model-name>"
4848
}
4949
}

docs/en/faq/faq.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ Config files at the default path `~/.openviking/ov.conf` are loaded automaticall
109109
| `volcengine` | Volcengine Embedding API (Recommended) |
110110
| `openai` | OpenAI Embedding API |
111111
| `vikingdb` | VikingDB Embedding API |
112+
| `jina` | Jina AI Embedding API |
112113

113114
Supports Dense, Sparse, and Hybrid embedding modes.
114115

docs/en/guides/01-configuration.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ Embedding model configuration for vector search, supporting dense, sparse, and h
118118

119119
| Parameter | Type | Description |
120120
|-----------|------|-------------|
121-
| `provider` | str | `"volcengine"`, `"openai"`, or `"vikingdb"` |
121+
| `provider` | str | `"volcengine"`, `"openai"`, `"vikingdb"`, or `"jina"` |
122122
| `api_key` | str | API key |
123123
| `model` | str | Model name |
124124
| `dimension` | int | Vector dimension |
@@ -138,6 +138,7 @@ With `input: "multimodal"`, OpenViking can embed text, images (PNG, JPG, etc.),
138138
- `openai`: OpenAI Embedding API
139139
- `volcengine`: Volcengine Embedding API
140140
- `vikingdb`: VikingDB Embedding API
141+
- `jina`: Jina AI Embedding API
141142

142143
**vikingdb provider example:**
143144

@@ -156,6 +157,43 @@ With `input: "multimodal"`, OpenViking can embed text, images (PNG, JPG, etc.),
156157
}
157158
```
158159

160+
**jina provider example:**
161+
162+
```json
163+
{
164+
"embedding": {
165+
"dense": {
166+
"provider": "jina",
167+
"api_key": "jina_xxx",
168+
"model": "jina-embeddings-v5-text-small",
169+
"dimension": 1024
170+
}
171+
}
172+
}
173+
```
174+
175+
Available Jina models:
176+
- `jina-embeddings-v5-text-small`: 677M params, 1024 dim, max seq 32768 (default)
177+
- `jina-embeddings-v5-text-nano`: 239M params, 768 dim, max seq 8192
178+
179+
Get your API key at https://jina.ai
180+
181+
**Local deployment (GGUF/MLX):** Jina embedding models are open-weight and available in GGUF and MLX formats on [Hugging Face](https://huggingface.co/jinaai). You can run them locally with any OpenAI-compatible server (e.g. llama.cpp, MLX, vLLM) and point the `api_base` to your local endpoint:
182+
183+
```json
184+
{
185+
"embedding": {
186+
"dense": {
187+
"provider": "jina",
188+
"api_key": "local",
189+
"api_base": "http://localhost:8080/v1",
190+
"model": "jina-embeddings-v5-text-nano",
191+
"dimension": 768
192+
}
193+
}
194+
}
195+
```
196+
159197
#### Sparse Embedding
160198

161199
```json

docs/zh/api/01-overview.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ export OPENVIKING_CONFIG_FILE=/path/to/ov.conf
3535
"dense": {
3636
"api_base": "<api-endpoint>",
3737
"api_key": "<your-api-key>",
38-
"provider": "<volcengine|openai>",
38+
"provider": "<volcengine|openai|jina>",
3939
"dimension": 1024,
4040
"model": "<model-name>"
4141
}
4242
},
4343
"vlm": {
4444
"api_base": "<api-endpoint>",
4545
"api_key": "<your-api-key>",
46-
"provider": "<volcengine|openai>",
46+
"provider": "<volcengine|openai|jina>",
4747
"model": "<model-name>"
4848
}
4949
}

docs/zh/faq/faq.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ pip install openviking
109109
| `volcengine` | 火山引擎 Embedding API(推荐) |
110110
| `openai` | OpenAI Embedding API |
111111
| `vikingdb` | VikingDB Embedding API |
112+
| `jina` | Jina AI Embedding API |
112113

113114
支持 Dense、Sparse 和 Hybrid 三种 Embedding 模式。
114115

docs/zh/guides/01-configuration.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ OpenViking 使用 JSON 配置文件(`ov.conf`)进行设置。配置文件支
120120

121121
| 参数 | 类型 | 说明 |
122122
|------|------|------|
123-
| `provider` | str | `"volcengine"``"openai"``"vikingdb"` |
123+
| `provider` | str | `"volcengine"``"openai"``"vikingdb"``"jina"` |
124124
| `api_key` | str | API Key |
125125
| `model` | str | 模型名称 |
126126
| `dimension` | int | 向量维度 |
@@ -140,6 +140,7 @@ OpenViking 使用 JSON 配置文件(`ov.conf`)进行设置。配置文件支
140140
- `openai`: OpenAI Embedding API
141141
- `volcengine`: 火山引擎 Embedding API
142142
- `vikingdb`: VikingDB Embedding API
143+
- `jina`: Jina AI Embedding API
143144

144145
**vikingdb provider 配置示例:**
145146

@@ -158,6 +159,43 @@ OpenViking 使用 JSON 配置文件(`ov.conf`)进行设置。配置文件支
158159
}
159160
```
160161

162+
**jina provider 配置示例:**
163+
164+
```json
165+
{
166+
"embedding": {
167+
"dense": {
168+
"provider": "jina",
169+
"api_key": "jina_xxx",
170+
"model": "jina-embeddings-v5-text-small",
171+
"dimension": 1024
172+
}
173+
}
174+
}
175+
```
176+
177+
可用 Jina 模型:
178+
- `jina-embeddings-v5-text-small`: 677M 参数, 1024 维, 最大序列长度 32768 (默认)
179+
- `jina-embeddings-v5-text-nano`: 239M 参数, 768 维, 最大序列长度 8192
180+
181+
**本地部署 (GGUF/MLX):** Jina 嵌入模型是开源的, 在 [Hugging Face](https://huggingface.co/jinaai) 上提供 GGUF 和 MLX 格式。可以使用任何 OpenAI 兼容的推理服务器 (如 llama.cpp、MLX、vLLM) 本地运行, 并将 `api_base` 指向本地端点:
182+
183+
```json
184+
{
185+
"embedding": {
186+
"dense": {
187+
"provider": "jina",
188+
"api_key": "local",
189+
"api_base": "http://localhost:8080/v1",
190+
"model": "jina-embeddings-v5-text-nano",
191+
"dimension": 768
192+
}
193+
}
194+
}
195+
```
196+
197+
获取 API Key: https://jina.ai
198+
161199
#### Sparse Embedding
162200

163201
```json

openviking/models/embedder/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
Supported providers:
1212
- OpenAI: Dense only
1313
- Volcengine: Dense, Sparse, Hybrid
14+
- Jina AI: Dense only
1415
"""
1516

1617
from openviking.models.embedder.base import (
@@ -21,6 +22,7 @@
2122
HybridEmbedderBase,
2223
SparseEmbedderBase,
2324
)
25+
from openviking.models.embedder.jina_embedders import JinaDenseEmbedder
2426
from openviking.models.embedder.openai_embedders import OpenAIDenseEmbedder
2527
from openviking.models.embedder.vikingdb_embedders import (
2628
VikingDBDenseEmbedder,
@@ -41,6 +43,8 @@
4143
"SparseEmbedderBase",
4244
"HybridEmbedderBase",
4345
"CompositeHybridEmbedder",
46+
# Jina AI implementations
47+
"JinaDenseEmbedder",
4448
# OpenAI implementations
4549
"OpenAIDenseEmbedder",
4650
# Volcengine implementations
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd.
2+
# SPDX-License-Identifier: Apache-2.0
3+
"""Jina AI Embedder Implementation"""
4+
5+
from typing import Any, Dict, List, Optional
6+
7+
import openai
8+
9+
from openviking.models.embedder.base import (
10+
DenseEmbedderBase,
11+
EmbedResult,
12+
)
13+
14+
# Default dimensions for Jina embedding models
15+
JINA_MODEL_DIMENSIONS = {
16+
"jina-embeddings-v5-text-small": 1024, # 677M params, max seq 32768
17+
"jina-embeddings-v5-text-nano": 768, # 239M params, max seq 8192
18+
}
19+
20+
21+
class JinaDenseEmbedder(DenseEmbedderBase):
22+
"""Jina AI Dense Embedder Implementation
23+
24+
Uses Jina AI embedding API via OpenAI-compatible client.
25+
Supports task-specific embeddings and Matryoshka dimension reduction.
26+
27+
Example:
28+
>>> embedder = JinaDenseEmbedder(
29+
... model_name="jina-embeddings-v5-text-small",
30+
... api_key="jina_xxx",
31+
... dimension=512,
32+
... task="retrieval.query"
33+
... )
34+
>>> result = embedder.embed("Hello world")
35+
>>> print(len(result.dense_vector))
36+
512
37+
"""
38+
39+
def __init__(
40+
self,
41+
model_name: str = "jina-embeddings-v5-text-small",
42+
api_key: Optional[str] = None,
43+
api_base: Optional[str] = None,
44+
dimension: Optional[int] = None,
45+
task: Optional[str] = None,
46+
late_chunking: Optional[bool] = None,
47+
config: Optional[Dict[str, Any]] = None,
48+
):
49+
"""Initialize Jina AI Dense Embedder
50+
51+
Args:
52+
model_name: Jina model name, defaults to jina-embeddings-v5-text-small
53+
api_key: API key, required
54+
api_base: API base URL, defaults to https://api.jina.ai/v1
55+
dimension: Dimension for Matryoshka reduction, optional
56+
task: Task type for task-specific embeddings, optional.
57+
Valid values: retrieval.query, retrieval.passage,
58+
text-matching, classification, separation
59+
late_chunking: Enable late chunking via extra_body, optional
60+
config: Additional configuration dict
61+
62+
Raises:
63+
ValueError: If api_key is not provided
64+
"""
65+
super().__init__(model_name, config)
66+
67+
self.api_key = api_key
68+
self.api_base = api_base or "https://api.jina.ai/v1"
69+
self.dimension = dimension
70+
self.task = task
71+
self.late_chunking = late_chunking
72+
73+
if not self.api_key:
74+
raise ValueError("api_key is required")
75+
76+
# Initialize OpenAI-compatible client with Jina base URL
77+
self.client = openai.OpenAI(
78+
api_key=self.api_key,
79+
base_url=self.api_base,
80+
)
81+
82+
# Determine dimension
83+
max_dim = JINA_MODEL_DIMENSIONS.get(model_name, 1024)
84+
if dimension is not None and dimension > max_dim:
85+
raise ValueError(
86+
f"Requested dimension {dimension} exceeds maximum {max_dim} for model '{model_name}'. "
87+
f"Jina models support Matryoshka dimension reduction up to {max_dim}."
88+
)
89+
self._dimension = dimension if dimension is not None else max_dim
90+
91+
def _build_extra_body(self) -> Optional[Dict[str, Any]]:
92+
"""Build extra_body dict for Jina-specific parameters"""
93+
extra_body = {}
94+
if self.task is not None:
95+
extra_body["task"] = self.task
96+
if self.late_chunking is not None:
97+
extra_body["late_chunking"] = self.late_chunking
98+
return extra_body if extra_body else None
99+
100+
def embed(self, text: str) -> EmbedResult:
101+
"""Perform dense embedding on text
102+
103+
Args:
104+
text: Input text
105+
106+
Returns:
107+
EmbedResult: Result containing only dense_vector
108+
109+
Raises:
110+
RuntimeError: When API call fails
111+
"""
112+
try:
113+
kwargs: Dict[str, Any] = {"input": text, "model": self.model_name}
114+
if self.dimension:
115+
kwargs["dimensions"] = self.dimension
116+
117+
extra_body = self._build_extra_body()
118+
if extra_body:
119+
kwargs["extra_body"] = extra_body
120+
121+
response = self.client.embeddings.create(**kwargs)
122+
vector = response.data[0].embedding
123+
124+
return EmbedResult(dense_vector=vector)
125+
except openai.APIError as e:
126+
raise RuntimeError(f"Jina API error: {e.message}") from e
127+
except Exception as e:
128+
raise RuntimeError(f"Embedding failed: {str(e)}") from e
129+
130+
def embed_batch(self, texts: List[str]) -> List[EmbedResult]:
131+
"""Batch embedding (Jina native support)
132+
133+
Args:
134+
texts: List of texts
135+
136+
Returns:
137+
List[EmbedResult]: List of embedding results
138+
139+
Raises:
140+
RuntimeError: When API call fails
141+
"""
142+
if not texts:
143+
return []
144+
145+
try:
146+
kwargs: Dict[str, Any] = {"input": texts, "model": self.model_name}
147+
if self.dimension:
148+
kwargs["dimensions"] = self.dimension
149+
150+
extra_body = self._build_extra_body()
151+
if extra_body:
152+
kwargs["extra_body"] = extra_body
153+
154+
response = self.client.embeddings.create(**kwargs)
155+
156+
return [EmbedResult(dense_vector=item.embedding) for item in response.data]
157+
except openai.APIError as e:
158+
raise RuntimeError(f"Jina API error: {e.message}") from e
159+
except Exception as e:
160+
raise RuntimeError(f"Batch embedding failed: {str(e)}") from e
161+
162+
def get_dimension(self) -> int:
163+
"""Get embedding dimension
164+
165+
Returns:
166+
int: Vector dimension
167+
"""
168+
return self._dimension
169+

0 commit comments

Comments
 (0)