Skip to content

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_name starting with ainexxo- → internal embedding-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_path
  • image: description, extracted_text, image_base64, legend, header_path
  • table: 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-m3dense, sparse
  • ainexxo-e5dense
  • ainexxo-spladesparse
  • External LiteLLM models:
  • currently dense only

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_ and chunk_ are reserved — a field_name starting 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_type across entries when dense and sparse vectors need different inputs.
  • Each field_name is the explicit coupling point with the Vector Store: it becomes the source value in the field_mappings of POST /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: pendingprocessingcompleted / 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

{ "bucket_uid": "bkt_uuid" }

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.