Skip to main content

Jina + Moorcheh

This integration uses the Jina Embeddings API with jina-embeddings-v5-text-small and Moorcheh vector namespaces to store and search vectors with ITS ranking. The model produces 1024-dimensional vectors by default (Matryoshka truncation is available via the API if you need a smaller size and match vector_dimension accordingly).

Architecture

Embedding generation

Call POST https://api.jina.ai/v1/embeddings with task retrieval.passage (index) or retrieval.query (search)

Vector storage

Store vectors in Moorcheh vector namespaces

Semantic retrieval

Query with the same model and retrieval.query for asymmetric retrieval

Authentication

Authorization: Bearer your Jina API key

Prerequisites

pip install moorcheh-sdk requests python-dotenv

.env file

MOORCHEH_API_KEY=your_moorcheh_key
JINA_API_KEY=your_jina_key

Tasks

For asymmetric retrieval, use:
TaskWhen to use
retrieval.passageChunks or documents you index
retrieval.queryUser queries at search time
Other tasks (for example text-matching, classification) are supported by the API for different workloads; keep task and model consistent between index and query for retrieval.

Vector dimensions

Default output for jina-embeddings-v5-text-small is 1024 dimensions. You can pass a dimensions field in the API request to truncate (Matryoshka); the Moorcheh namespace vector_dimension must match what you send at index and query time.

End-to-end example

The following example loads keys from .env via load_dotenv(), embeds passages and a query through the Jina API, uploads vectors to Moorcheh, and runs similarity search.
import os
import textwrap
from typing import List

import requests
from dotenv import load_dotenv
from moorcheh_sdk import MoorchehClient

load_dotenv()

MOORCHEH_API_KEY = os.getenv("MOORCHEH_API_KEY", "").strip()
JINA_API_KEY = os.getenv("JINA_API_KEY", "").strip()
if not MOORCHEH_API_KEY or not JINA_API_KEY:
    raise SystemExit("Set MOORCHEH_API_KEY and JINA_API_KEY.")

JINA_EMBEDDINGS_URL = "https://api.jina.ai/v1/embeddings"
MODEL = "jina-embeddings-v5-text-small"
VECTOR_DIMENSION = 1024

NAMESPACE = "jina-v5-small-demo"
CHUNK_SIZE = 900
CHUNK_OVERLAP = 180


def jina_embed(texts: List[str], task: str) -> List[List[float]]:
    headers = {
        "Authorization": f"Bearer {JINA_API_KEY}",
        "Content-Type": "application/json",
    }
    payload = {
        "model": MODEL,
        "task": task,
        "input": texts,
        "normalized": True,
    }
    r = requests.post(JINA_EMBEDDINGS_URL, headers=headers, json=payload, timeout=120)
    r.raise_for_status()
    body = r.json()
    items = sorted(body["data"], key=lambda x: x["index"])
    return [item["embedding"] for item in items]


def to_float_vector(values: List[float]) -> List[float]:
    return [float(x) for x in values]


def chunk_text(text: str, chunk_size: int = CHUNK_SIZE, overlap: int = CHUNK_OVERLAP) -> List[str]:
    chunks: List[str] = []
    start = 0
    while start < len(text):
        end = min(start + chunk_size, len(text))
        chunks.append(text[start:end].strip())
        if end == len(text):
            break
        start = max(end - overlap, 0)
    return [c for c in chunks if c]


def extract_text(result: dict) -> str:
    if result.get("text"):
        return str(result["text"])
    metadata = result.get("metadata") or {}
    if isinstance(metadata, dict):
        return str(metadata.get("text") or metadata.get("raw_text") or metadata.get("content") or "")
    return ""


def clean_text(text: str) -> str:
    return " ".join(str(text).split())


def print_result(idx: int, result: dict) -> None:
    metadata = result.get("metadata") or {}
    text_value = clean_text(extract_text(result))
    wrapped = textwrap.fill(text_value, width=100)
    print(f"[{idx}] id={result.get('id')}")
    print(f"score={result.get('score')} label={result.get('label')}")
    print(f"section={metadata.get('section')} source_doc_id={metadata.get('source_doc_id')}")
    print("text:")
    print(wrapped if wrapped else "(no text returned)")
    print("-" * 120)


mc = MoorchehClient(api_key=MOORCHEH_API_KEY)

try:
    mc.namespaces.create(
        namespace_name=NAMESPACE,
        type="vector",
        vector_dimension=VECTOR_DIMENSION,
    )
except Exception:
    pass

source_documents = [
    {
        "id": "guide-vector-namespaces",
        "section": "vector-namespace-best-practices",
        "text": (
            "Moorcheh vector namespaces support bring-your-own-embedding workflows. "
            "With Jina jina-embeddings-v5-text-small, use task retrieval.passage for chunks and retrieval.query for queries. "
            "Match vector_dimension to the API output size (1024 by default)."
        ),
    },
    {
        "id": "guide-search-tuning",
        "section": "semantic-search-tuning",
        "text": (
            "Use coherent chunks with overlap. Tune similarity_search threshold and top_k for your domain."
        ),
    },
]

documents = []
for doc in source_documents:
    parts = chunk_text(doc["text"])
    for idx, chunk in enumerate(parts):
        documents.append(
            {
                "id": f"{doc['id']}-chunk-{idx}",
                "text": chunk,
                "source_doc_id": doc["id"],
                "section": doc["section"],
                "chunk_index": idx,
                "total_chunks": len(parts),
            }
        )

texts = [d["text"] for d in documents]
doc_embeddings = jina_embed(texts, task="retrieval.passage")

mc.vectors.upload(
    namespace_name=NAMESPACE,
    vectors=[
        {
            "id": documents[i]["id"],
            "vector": to_float_vector(doc_embeddings[i]),
            "text": documents[i]["text"],
            "source": "jina-embeddings-v5-text-small",
            "model": MODEL,
            "task": "retrieval.passage",
            "section": documents[i]["section"],
            "source_doc_id": documents[i]["source_doc_id"],
            "chunk_index": documents[i]["chunk_index"],
            "total_chunks": documents[i]["total_chunks"],
        }
        for i in range(len(documents))
    ],
)

query = "How do I use Jina retrieval tasks with Moorcheh vector namespaces?"
query_vecs = jina_embed([query], task="retrieval.query")
query_vec = to_float_vector(query_vecs[0])

results = mc.similarity_search.query(
    namespaces=[NAMESPACE],
    query=query_vec,
    top_k=5,
    kiosk_mode=True,
    threshold=0.15,
)

print(f"namespace={NAMESPACE} total_results={len(results.get('results', []))}")
print("=" * 120)
for idx, r in enumerate(results.get("results", []), start=1):
    print_result(idx, r)

Runnable demo script

See integrations/jina/jina_moorcheh_demo.py.

Important notes

Default length is 1024 for jina-embeddings-v5-text-small. If you set dimensions in the Jina request, create the Moorcheh namespace with that same vector_dimension.
Use retrieval.passage for stored chunks and retrieval.query for search queries so the retrieval adapter matches training.
Include text on each uploaded vector so search results can return the original chunk.
Use the same model name and task pairing at index and query time.

Troubleshooting

  • 401 / auth errors: Check JINA_API_KEY and Authorization: Bearer format.
  • Dimension mismatch: Align Moorcheh vector_dimension with embedding length (default 1024).
  • Low relevance: Adjust chunking, threshold, and top_k.