Embedding
Generates dense and/or sparse vectors for the chunks produced by Chunking, for text, images, and tables. Always operates per bucket_uid: requires chunking.msgpack to already be present on Storage (fail-fast if missing).
Model routing
Embedding vectors use a flat model contract:
model_name- optional
api_key - optional provider-specific extra fields (passed through to LiteLLM)
Routing is intentionally minimal:
model_namestarting withainexxo-→ internalembedding-svc- everything else → LiteLLM SDK
For internal Ainexxo models, omit api_key. For external LiteLLM models, pass the model name exactly as LiteLLM expects it (see the LiteLLM supported embedding models) and include the provider API key directly on the vector. External provider keys are stored encrypted server-side and are never exposed in cleartext to the job broker.
Batching
Each vector configuration's input list is split into sub-batches before issuing provider calls, so a single job over many chunks does not exceed any one provider's per-request input limit. Sub-batches for the same vector run concurrently; results are reassembled in input order, so per-chunk pairing is preserved.
batch_size is an optional per-vector field on each EmbeddingVector. It defaults to 96 (at or below the per-request input limit of most providers — Cohere 96, Voyage 128, Jina 128, OpenAI 2048); override it only when the target provider documents a higher per-request limit. Must be a positive integer. It is a processing-side knob, stripped before the payload reaches the provider — never forwarded.
Example with an OpenAI-specific override:
{
"vector_type": "dense",
"field_name": "text_dense_openai",
"model_name": "text-embedding-3-small",
"api_key": "sk_...",
"batch_size": 2048
}
Input and vector validation
The API validates both the requested inputs and the vector_type before enqueueing the job.
Allowed inputs by content_type
text:content,header_pathimage:description,extracted_text,image_base64,legend,header_pathtable:content,description,data,legend,header_path
These names match the specialised sub-chunks emitted by Chunking under embedding_chunks. header_path is carried by the parent chunk metadata and can be prepended when requested.
vector_type compatibility
- Internal models:
ainexxo-bge-m3→dense,sparseainexxo-e5→denseainexxo-splade→sparse- External LiteLLM models:
- currently
denseonly
Invalid combinations (for example ainexxo-e5 + sparse, or image + content) are rejected with 422 at the API boundary or with an explicit validation error before model execution.
field_name conventions
- The prefixes
item_andchunk_are reserved — afield_namestarting with either is rejected at the Pydantic boundary (they are the flat record namespaces used by the Vector Store). - Multiple content blocks and multiple vectors per content type can coexist in the same request. Repeat
content_typeacross entries when dense and sparse vectors need differentinputs. - Each
field_nameis the explicit coupling point with the Vector Store: it becomes thesourcevalue in thefield_mappingsofPOST /api/v1/vector-store/jobs.
Endpoints
POST /api/v1/embed/jobs
Submits an embedding job. The client sends a bucket_uid plus a flat embeddings list describing text, image, and table content blocks. Bucket ownership is validated (404 if not found or not owned); the call returns immediately with a job_uid and status: "pending" for polling.
Request
{
"bucket_uid": "bkt_uuid",
"embeddings": [
{
"content_type": "text",
"inputs": ["content"],
"vectors": [
{
"vector_type": "dense",
"field_name": "text_dense_bge",
"model_name": "ainexxo-bge-m3"
},
{
"vector_type": "sparse",
"field_name": "text_sparse_splade",
"model_name": "ainexxo-splade"
}
]
},
{
"content_type": "text",
"inputs": ["content"],
"vectors": {
"vector_type": "dense",
"field_name": "text_dense_openai",
"model_name": "text-embedding-3-small",
"api_key": "sk_openai_api_key"
}
},
{
"content_type": "image",
"inputs": ["image_base64"],
"vectors": {
"vector_type": "dense",
"field_name": "image_dense_jina",
"model_name": "jina_ai/jina-embeddings-v4",
"api_key": "jina_ai_api_key"
}
}
]
}
Response — 200
{
"success": true,
"job_uid": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"message": "Embedding job started",
"bucket_uid": "bkt_uuid"
}
GET /api/v1/embed/jobs/{bucket_uid}/{job_uid}
Polls the job status. Verifies ownership via user_uid (bucket ownership check).
Response
{
"job_uid": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"bucket_uid": "bkt_uuid",
"message": "Embedding completed successfully",
"summary": { "total_embeddings": 38, "embeddings_by_type": { "text": 30, "image": 5, "table": 3 }, "total_chunks": 14 },
"firebase_path": "uploads/{user_uid}/bkt_uuid/embeddings.msgpack",
"error": null,
"started_at": "2024-01-01T10:02:00Z",
"completed_at": "2024-01-01T10:02:12Z"
}
Status values: pending → processing → completed / failed.
POST /api/v1/embed/results
Returns a short-lived signed URL for the embedding job's main output (embeddings.msgpack). The client fetches the URL via HTTP GET before expires_at. No file bytes transit through this API.
Request
Response — wraps the shared ResultsData shape.
{
"success": true,
"message": "Results for bucket bkt_uuid",
"result": {
"bucket_uid": "bkt_uuid",
"success": true,
"expires_at": "2026-04-29T12:00:00Z",
"files": {
"embeddings.msgpack": "https://storage.googleapis.com/...?signature=..."
},
"missing_files": [],
"error": null
}
}
If the file is missing the response has success: false, files: {}, missing_files: ["embeddings.msgpack"], and error populated.
GET /api/v1/embed/models
Lists the internal Ainexxo models available with their supported vector types (dense, sparse, or both). External LiteLLM models are not listed — the client passes the LiteLLM model_name directly.
Response
{
"models": [
{ "name": "ainexxo-bge-m3", "vector_types": ["dense", "sparse"] },
{ "name": "ainexxo-e5", "vector_types": ["dense"] },
{ "name": "ainexxo-splade", "vector_types": ["sparse"] }
]
}
embeddings.msgpack structure
The file is msgpack-serialised (binary). Once decoded:
{
"bucket_uid": "bkt_uuid",
"total_embeddings": 38,
"metadata": { "...": "..." },
"embeddings": [
{
"chunk_element_id": "...",
"type": "text",
"source_file": "...",
"header_path": ["Section 1", "Subsection 1.2"],
"dense_embeddings": {
"text_dense_bge": { "id": "<chunk_element_id>__text_dense_bge", "model": "ainexxo-bge-m3", "inputs": ["content"], "dims": 1024, "vector": [0.12, 0.45, "..."] }
},
"sparse_embeddings": {
"text_sparse_splade": { "id": "<chunk_element_id>__text_sparse_splade", "model": "ainexxo-splade", "inputs": ["content"], "vector": { "1234": 0.5, "5678": 0.3 } }
}
}
]
}
Per-record, header_path is only present when the source chunk carries one; dense_embeddings / sparse_embeddings are omitted when empty. The keys of dense_embeddings / sparse_embeddings are the field_name values from the Embedding request (these are the source values to use in the Vector Store field_mappings — the explicit Embedding → Vector Store coupling). Each vector entry carries id ({chunk_element_id}__{field_name}), model (model_name from the request), inputs, vector, and — dense only — dims.