local-inference-service / src /openapi_examples.py
fashxp's picture lukmzig's picture
BPT adjustments: batched embedding API, normalized vectors, resource limits, lint/type/test gate (#3)
ed962ac
Raw History Blame Contribute Delete
6.83 kB
# -------------------------------------------------------------------
# This source file is available under the terms of the
# Pimcore Open Core License (POCL)
# Full copyright and license information is available in
# LICENSE.md which is distributed with this source code.
#
# @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
# @license Pimcore Open Core License (POCL)
# -------------------------------------------------------------------
"""Request/response samples for the embedding endpoints, kept out of `main.py` so the routing
stays readable.
These are meant to be *runnable*: pick one in the `/docs` "Try it out" dropdown and send it as-is.
That is why the models named here are small, standard-architecture ones rather than the multi-GB
examples elsewhere in the docs, and why the image sample carries a real (tiny) PNG instead of a
`"base64_encoded_image_string"` placeholder. `tests/test_openapi_examples.py` validates every
sample against the request model, so a sample cannot drift out of sync with the schema.
"""
from typing import Any, Union
# A real 16x16 PNG, so the image samples below can be executed unmodified.
SAMPLE_PNG_BASE64 = (
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGUlEQVR4nGNUTX7NQApgIkn1qIZRDUNKAwCg0AGTz0Gc9wAAAABJRU5ErkJggg=="
)
# ~90 MB, no custom code, downloads in seconds — the right default for a first request.
_SMALL_TEXT_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
_SMALL_IMAGE_MODEL = "google/siglip-base-patch16-224"
TEXT_EMBEDDING_EXAMPLES: dict[str, Any] = {
"single": {
"summary": "One string (start here)",
"description": (
f"Smallest useful request. `{_SMALL_TEXT_MODEL}` is a few hundred MB and needs no "
"custom code, so it loads on first call without any configuration."
),
"value": {"model": _SMALL_TEXT_MODEL, "input": "a blue sports car"},
},
"batch": {
"summary": "Several strings in one call",
"description": (
"A list is embedded in a single forward pass, which is markedly faster than one request "
"per string. `data` comes back in input order, each entry carrying its `index`."
),
"value": {
"model": _SMALL_TEXT_MODEL,
"input": ["a blue sports car", "ein blaues Sportauto", "a recipe for onion soup"],
},
},
"instruction_prefixed": {
"summary": "Nomic, with its task prefix",
"description": (
"Models in the Nomic family expect an instruction prefix on every input — "
"`search_query: ` for the thing being searched with, `search_document: ` for the things "
"being searched over. The service does not add it for you; embed queries and documents "
"with matching prefixes or the two spaces will not line up. This model carries custom "
"code, so its namespace must appear in `TRUSTED_CODE_ORGS` (it does by default)."
),
"value": {
"model": "nomic-ai/nomic-embed-text-v1.5",
"input": ["search_query: a blue sports car"],
},
},
"openai_compatible": {
"summary": "With the OpenAI compatibility fields",
"description": (
"`encoding_format` selects the wire format and only `'float'` is supported here; `user` "
"is an identifier you invent for abuse attribution and is accepted but ignored. Both "
"exist so a client written against OpenAI's /v1/embeddings works unchanged. Sending "
"`encoding_format: 'base64'` or any `dimensions` value is refused with a 422 rather "
"than silently ignored."
),
"value": {
"model": _SMALL_TEXT_MODEL,
"input": "a blue sports car",
"encoding_format": "float",
"user": "pimcore-bpt-tenant-42",
},
},
}
IMAGE_EMBEDDING_EXAMPLES: dict[str, Any] = {
"single": {
"summary": "One base64 image (runnable as-is)",
"description": (
"The `input` value is the base64 of the image bytes. The sample below is a real 16x16 "
"PNG, so this request works unmodified — swap in your own base64 to try a real asset."
),
"value": {"model": _SMALL_IMAGE_MODEL, "input": SAMPLE_PNG_BASE64},
},
"batch": {
"summary": "Several images in one call",
"description": (
"Images are embedded in one forward pass. An image that cannot be decoded is skipped: "
"it is omitted from `data`, so its `index` is missing rather than shifting the rest."
),
"value": {"model": _SMALL_IMAGE_MODEL, "input": [SAMPLE_PNG_BASE64, SAMPLE_PNG_BASE64]},
},
"data_url": {
"summary": "A data: URL",
"description": (
"A `data:image/...;base64,` prefix is accepted and stripped, so values copied straight "
"out of a browser or an HTML `src` attribute work without editing."
),
"value": {"model": _SMALL_IMAGE_MODEL, "input": f"data:image/png;base64,{SAMPLE_PNG_BASE64}"},
},
}
# Shared by both endpoints — the response shape is identical.
# `Union[int, str]` rather than `int | str`: module-level annotations are evaluated at runtime, and
# PEP 604 unions need Python 3.10 — the Dockerfile and CI both run 3.9.
EMBEDDINGS_RESPONSES: dict[Union[int, str], dict[str, Any]] = {
200: {
"description": "Embeddings in input order. Every vector is L2-normalized (length 1).",
"content": {
"application/json": {
"example": {
"object": "list",
"data": [
{"object": "embedding", "index": 0, "embedding": [0.0163, -0.0412, 0.0967]},
{"object": "embedding", "index": 1, "embedding": [-0.0271, 0.0538, 0.0114]},
],
"model": _SMALL_TEXT_MODEL,
"usage": {"prompt_tokens": 0, "total_tokens": 0},
}
}
},
},
400: {
"description": "The `model` value is not a well-formed Hugging Face repo id.",
"content": {
"application/json": {
"example": {
"detail": (
"Invalid model id '../../etc/passwd'. Expected a Hugging Face repo id such "
"as 'namespace/model' or a canonical model name."
)
}
}
},
},
404: {
"description": "The model id is well-formed but could not be loaded (unknown, gated, or "
"unavailable offline).",
"content": {
"application/json": {"example": {"detail": "Model 'acme/does-not-exist' could not be loaded: ..."}}
},
},
}