Spaces:
Running
Running
File size: 16,134 Bytes
7dce25f 2dfabc3 7dce25f 2dfabc3 7dce25f e8bbc58 2dfabc3 e8bbc58 2dfabc3 d289270 ba8d0f9 e8bbc58 ba8d0f9 350ad7e ba8d0f9 350ad7e ba8d0f9 350ad7e ba8d0f9 e8bbc58 ba8d0f9 2dfabc3 350ad7e d289270 2dfabc3 d289270 e8bbc58 d289270 2dfabc3 d289270 2dfabc3 d289270 ee4a727 ab5235b e8bbc58 2934687 d289270 0458600 3fe3157 0458600 3fe3157 fdb03af 3fe3157 c130de9 0458600 c6afbdf c130de9 0458600 2934687 c6afbdf 2934687 0458600 3fe3157 0458600 3fe3157 0458600 3fe3157 0458600 3fe3157 0458600 3fe3157 0458600 d289270 2dfabc3 d289270 2dfabc3 d289270 2dfabc3 d289270 2dfabc3 d289270 2dfabc3 d289270 2dfabc3 d289270 ccc33b7 d289270 ccc33b7 d289270 2dfabc3 d289270 2dfabc3 d289270 2dfabc3 d289270 2dfabc3 d289270 ab5235b d289270 ab5235b d289270 2dfabc3 d289270 2dfabc3 d289270 2dfabc3 d289270 2dfabc3 d289270 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 | ---
title: Vectorization API
emoji: 🧠
colorFrom: blue
colorTo: indigo
sdk: docker
app_port: 7860
pinned: false
---
# Vectorization API — configurable resident models
CPU-only FastAPI inference service. **Only selected models load during startup**, then remain resident. Inference never loads model weights. No search, Qdrant, Manticore, reranking or Entity Linking.
| Task | Model | Max tokens | Output |
| --- | --- | ---: | --- |
| Standard | `intfloat/multilingual-e5-small` | 512 | 384D |
| Literature | `Qwen/Qwen3-Embedding-0.6B` | 32768 | 1024D |
| ColBERT | `lightonai/mLateOn` | 8192 | 128D per token |
| Triple retrieval | `BAAI/bge-m3` | 8192 native, 2048 server default | dense 1024D + sparse lexical + ColBERT 1024D/token |
## Runtime model selection
Set `ENABLED_MODELS` in Hugging Face Space **Settings → Variables and secrets**
(or as a container environment variable). Comma-separated names:
```dotenv
ENABLED_MODELS=e5,bge-m3
```
Supported: `e5` (384D), `qwen` (1024D), `colbert` (mLateOn 128D),
`bge-m3` (1024D dense/sparse/ColBERT). For only BGE-M3 use
`ENABLED_MODELS=bge-m3`; for all four, use
`ENABLED_MODELS=e5,qwen,colbert,bge-m3`.
When omitted, all four load (previous default). Old `BGE_M3_ENABLED=0`
is still honored **only if `ENABLED_MODELS` is unset**. With an explicit
`ENABLED_MODELS`, the list takes precedence. Empty, duplicate, or unknown
names fail startup with an explanatory error; at least one model is required.
Changing the environment requires a Space restart.
Disabled models are not instantiated or kept in RAM. Endpoints remain
defined for compatibility but return HTTP 503 if their model is disabled.
`GET /models` and `GET /openai/v1/models` show only loaded models;
`GET /health` includes `loaded_models`. The legacy `GET /model` is
E5-only and returns 503 if E5 is not loaded, as does an `/embed` request
without a model (which defaults to E5).
**Scope:** this controls *runtime loading and memory*. The Docker build
still downloads all four checkpoints and performs the mLateOn build preflight,
even if a model is disabled for runtime. Build-time selection is separate.
## BGE-M3: triple-vector embedding
The official `FlagEmbedding.BGEM3FlagModel` supports dense, sparse and
ColBERT projections in one forward pass. By default the API computes only
**dense + sparse**, skipping the ColBERT projection and its large output.
Set `return_colbert: true` in the request to compute and return all three.
These BGE-M3 ColBERT
vectors are 1024D, unrelated to the existing 128D `lightonai/mLateOn`
vectors. Do not store them in the same named Qdrant vector field.
`POST /bge-m3/encode` requires `Authorization: Bearer <API_KEY>`:
```bash
curl -sS https://smartdataalex-jupyter.hf.space/bge-m3/encode \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input":["What is RAG?", "מהי שליפה סמנטית?"],"input_type":"document","return_colbert":false}'
```
Response contains `model: "BAAI/bge-m3"`, `dense_dimensions: 1024`,
`colbert_dimensions: 1024`, `data[]` entries with `index`, `dense`
(1024 floats), `sparse.indices` (integer tokenizer IDs),
`sparse.values` (learned positive lexical weights) and `tokens_count`.
Only when `return_colbert: true`, each item additionally contains
`colbert`: a variable-length array of 1024D token embeddings.
By default `return_colbert: false`, and the projection is not computed.
The sparse `indices`/`values` structure is directly suitable for Qdrant
sparse vectors. A single request accepts 1–8 texts; inference is serial
within the shared CPU inference gate. Query/document modes require no
manual instruction prefix for BGE-M3.
BGE-M3 loading is controlled by `ENABLED_MODELS` (with legacy
`BGE_M3_ENABLED` fallback when the new variable is absent). Model
checkpoint download is pinned. `BGE_M3_MAX_TOKENS=2048` is the initial **operational** cap on
our shared 16-GiB CPU server; the underlying model supports 8192. To use
larger contexts, increase that variable (maximum 8192) after checking
CPU inference memory and latency. Overlong texts produce HTTP 422, not
silent truncation. Max total tokens per request: 8192. No change to
existing OpenAI-compatible/Voyage/ColBERT response contracts.
BGE-M3 itself does not compress or quantize its ColBERT output. Qdrant
supports storage compression for multivectors through `datatype: turbo4`
(approximately 8x smaller vs float32, requires a compatible Qdrant version).
Set `hnsw_config.m=0` for ColBERT reranking fields. These are Qdrant
collection settings, not parameters for the BGE-M3 inference endpoint;
transfer of raw ColBERT JSON is still large.
## API
Legacy endpoints require `X-API-Key`; Voyage-compatible `/v1/embeddings` uses `Authorization: Bearer <API_KEY>`. Public endpoints: `/health`, `/docs` and `/openapi.json`.
- `POST /embed`: `{"text":"Your text","type":"query"}` defaults to E5. The optional `model` accepts `e5`, `qwen`, or the corresponding full model name. The existing E5 response fields are unchanged: `text`, `type`, `tokens_count`, `embedding_model`, `dimensions`, `embedding`.
- `POST /colbert/encode`: `{"text":"Your text","type":"passage"}` returns `embedding_model`, `tokens_count`, `dimensions` and `token_embeddings` (matrix of individual 128D vectors).
- `GET /models`: readiness, dimensions, limits, normalization method, current/peak RSS and startup RAM readings for all models.
- `GET /model`: original E5-only metadata contract.
- `GET /health`: returns 200 when all configured models have completed startup.
Example (replace URL and key):
```bash
curl -sS -X POST 'http://localhost:7860/embed' \
-H 'X-API-Key: YOUR_API_KEY' -H 'Content-Type: application/json' \
-d '{"text":"What does the author mean?","type":"query","model":"qwen"}'
curl -sS -X POST 'http://localhost:7860/colbert/encode' \
-H 'X-API-Key: YOUR_API_KEY' -H 'Content-Type: application/json' \
-d '{"text":"שלום עולם","type":"passage"}'
```
E5 keeps `query: ` / `passage: ` prefixes, attention-mask-aware mean pooling, **no extra L2 normalization**. Qwen uses its official query instruction + last-token pooling + L2 normalization; documents have no instruction. PyLate mLateOn handles query and passage separately, retaining token vectors and their model-native normalization. Token limits include model markers and special tokens. The legacy API
rejects overlong inputs (HTTP 422), while E5 Voyage and OpenAI-compatible
endpoints truncate inputs exceeding 512 tokens, logging affected batch indices. A single CPU inference runs at a time; up to 16 additional HTTP requests wait
for a free inference slot (40 seconds by default). A full queue or expired wait
returns HTTP 429 with `Retry-After`. Configure `INFERENCE_MAX_WAITERS` and
`INFERENCE_WAIT_SECONDS` as needed. Dify's OpenAI-compatible embedding provider
uses a 60-second HTTP timeout, so keep the server's wait timeout below that.
## OpenAI-compatible API (separate from Voyage)
**OpenAI SDK-compatible base URL:** `https://YOUR-SPACE.hf.space/openai/v1`.
This separate namespace is necessary because the existing Voyage-compatible
`/v1/embeddings` endpoint is already in production and must retain its default
`input_type` handling. Point OpenAI-compatible custom-provider integrations
to the base URL ending in `/openai/v1`, not to the Voyage `/v1` URL.
- `POST /openai/v1/embeddings`: accepts `model`, `input` (string or string array),
optional `dimensions`, `encoding_format` (`float` or `base64`) and `user`.
Also accepts and ignores the non-standard boolean `drop_params` sent by
some OpenAI-compatible connection checks; other unknown fields remain errors.
Responds in the OpenAI `data[].embedding` / `usage.prompt_tokens` /
`usage.total_tokens` format. Only fixed native dimensions are supported.
- `GET /openai/v1/models`: dense model aliases for provider selection.
- Bearer authentication uses the **same** `API_KEY` as Voyage; no new secrets.
- Dense E5, Qwen3 and BGE-M3 (`bge-m3` or `BAAI/bge-m3`). BGE-M3
returns only its 1024D dense embedding; sparse and ColBERT are not
computed on this route. Use `/bge-m3/encode` for the hybrid API.
mLateOn ColBERT remains on `/colbert/encode` and Voyage.
- **Crucial:** this endpoint forwards raw input to the tokenizer **without adding any
query/document instructions**. Supply prefixes in the client configuration.
For **E5 only**, inputs exceeding 512 tokens are truncated from the right
to 512 tokens, including the prefix and special tokens. Short inputs are
unchanged; truncation is logged with the input index and original token count.
Qwen and BGE-M3 inputs are not truncated (BGE-M3 uses
`BGE_M3_MAX_TOKENS=2048` by default). Unsupported dimensions return an error.
For BGE-M3 in Dify: use model `bge-m3`, embedding dimensions `1024`,
and leave query/document prefixes empty. Only dense vectors are returned
to standard OpenAI-compatible clients; the native BGE-M3 endpoint keeps
dense + sparse and optional ColBERT.
For E5 configure in Dify:
- Document prefix: `passage:`
- Query prefix: `query:`
- Model max chunks per batch: start with `16` (Dify's default is `1`,
which is not this server's limit). Dify inserts a space after the prefix.
The OpenAI endpoint accepts up to 1000 strings per request, subject to
65,536 total **processed** input tokens (after E5 truncation) and internal
padding-aware microbatches. Large
requests may still exceed the client's timeout; prefer batches of 8–16 on CPU.
For Qwen3:
- Document prefix: *empty*
- Query prefix (with a newline between the lines):
```text
Instruct: Given a question, retrieve relevant passages that answer the question
Query:
```
The Qwen query prefix contains an actual newline before `Query:`; ensure the
client inserts a newline character rather than the two characters backslash+n.
Use `Query:` immediately before the question, matching the model's original
instruction template.
Example with the standard Python OpenAI SDK:
```python
from openai import OpenAI
client = OpenAI(
base_url="https://YOUR-SPACE.hf.space/openai/v1",
api_key="YOUR_API_KEY",
)
# The calling application has to apply the configured prefix.
embedding = client.embeddings.create(
model="e5",
input=["passage: Первое предложение", "passage: Второе предложение"],
)
print(len(embedding.data[0].embedding)) # 384
```
## Official Voyage AI SDK compatibility
The **unmodified Voyage Python client** can talk directly to our service.
The base URL includes `/v1` (not `/v1/embeddings`):
```python
import voyageai
client = voyageai.Client(
api_key="YOUR_API_KEY",
base_url="https://YOUR-SPACE.hf.space/v1",
)
documents = client.embed(
["Первый чанк", "Второй чанк"],
model="e5", # or "qwen"
input_type="document",
truncation=False,
)
print(documents.embeddings) # list of 384D or 1024D vectors
print(documents.total_tokens)
query = client.embed(
["Что происходило в книге?"],
model="qwen",
input_type="query",
)
```
The official client sends `POST /v1/embeddings` with Bearer authorization,
`input`, `model`, `input_type`, `truncation`, and optional output settings.
For Voyage **E5 only**, chunks longer than 512 tokens (including the injected
`query: ` or `passage: ` prefix and special tokens) are automatically truncated
from the right, identically to the OpenAI E5 endpoint. This fallback applies
even with `truncation=False`; short texts are unchanged. Qwen and ColBERT
still reject overlong inputs.
Our response implements Voyage's standard `object`/`data`/`model`/`usage`
envelope; the SDK's `EmbeddingsObject.embeddings` and `total_tokens` work.
Model names: `e5`, `qwen`, `colbert` (or the corresponding full Hugging Face IDs).
`input_type="query"` selects query instructions, `input_type="document"` selects
document/passage encoding; an omitted type defaults to document.
A single string or a list of 1–1000 strings is accepted.
`model="colbert"` is a **non-standard multi-vector extension**:
the standard Voyage client will expose `embeddings` as a 3-D array
`[document][token][128]`. This is not interchangeable with regular
Voyage dense embeddings or dense-only vector database integrations.
All three models are already loaded in RAM. Batch calls use **actual model
batch inference** where possible: E5 and Qwen run padded tensor batches,
and mLateOn uses PyLate's batch encoding. The server partitions requests
by padded token cost to protect 16-GiB CPU memory. Requests are limited to
65,536 aggregate tokens for dense vectors or 32,768 for ColBERT.
The request-wide token budget is enforced after E5 truncation; over-budget
requests and overlong Qwen/ColBERT inputs still return HTTP 422. Only float
output and the fixed model
dimensions are supported; quantization, changing dimensions, and Voyage's
other API families are not implemented.
Unmodified-SDK integration smoke test:
```bash
pip install voyageai==0.5.0
SPACE_URL=http://localhost:7860 API_TEST_KEY='your-strong-api-key' \
python scripts/smoke_voyage_sdk.py
```
## Deployment, RAM and rollback
Docker downloads immutable checkpoints at image build, and runtime operates offline. Startup fails if any of the three models cannot be loaded or exceeds the configured resident memory budget. The default `MEMORY_BUDGET_MB=15360` reserves memory within a nominal 16-GiB container. Run exactly **one** Uvicorn worker and one concurrent inference.
```bash
docker build -t vectorization:three-models .
docker run --rm -p 7860:7860 --memory 16g \
-e API_KEY='your-strong-api-key' vectorization:three-models
```
16 GiB RAM is a target, **not verified sufficient** for 32768-token Qwen inference. Validate longest contexts on the target server, including peak RSS, CPU time and stability. Do not lower the advertised token limits if tests fail.
### Tests
```bash
python -m pip install torch==2.8.0 --index-url https://download.pytorch.org/whl/cpu
python -m pip install -r requirements-dev.txt
pytest -q tests/test_app.py --junitxml=unit-results.xml
SPACE_URL=http://localhost:7860 API_TEST_KEY='your-strong-api-key' \
python scripts/smoke_live.py
```
Unit tests stub model weights. Only a real Space smoke test demonstrates end-to-end inference. For separate 16-GiB RAM/CPU benchmark runs using the downloaded Docker model weights:
```bash
docker run --rm --memory 16g --entrypoint python -e BENCH_LONG=1 \
-v "$PWD/reports:/service/reports" \
-e BENCH_REPORT=/service/reports/bench-result.json \
vectorization:three-models scripts/bench_local.py
```
The benchmark loads all three models at startup and saves timings plus process RSS after every case.
### Preserve the existing Space
GitHub Actions runs unit tests on PRs and pushes. **Automatic production deployment from `main` is opt-in:** set the GitHub repository variable `HF_AUTO_DEPLOY_ENABLED=true` and secrets `GIT_TOKEN` plus `API_TEST_KEY`. After a successful test run on each new commit to `main`, Actions backs up the current Space, deploys with commit/revision protection, and runs the old API and official Voyage SDK smoke tests. If a post-deployment smoke check fails, the workflow attempts a revision-guarded automatic restore from the backup. Manual deployment is also available from `main` through `workflow_dispatch` (`deploy=true`) with `HF_DEPLOY_ENABLED=true`. Commits to feature branches and PRs never deploy the production Space. Production runs and PR runs use separate concurrency groups, so a PR update cannot interrupt a deployment. The existing workflow first makes a complete backup of `SmartDataAlex/Jupyter`, uploads it as a CI artifact, deploys only if the Space revision matches the backup and runs real authenticated smoke tests. If the backup fails, deployment is blocked.
Manual rollback using a saved backup:
```bash
HF_TOKEN='space-scoped-write-token' python scripts/restore_space.py \
SmartDataAlex-Jupyter-<revision>.tar.gz \
--confirm-space SmartDataAlex/Jupyter
```
Store a permanent backup separately; GitHub Actions artifacts expire.
|