- name
- multimodal-embedding-serving-user
- description
- Deploy and consume the Multimodal Embedding Serving microservice — bring it up with setup.sh + docker compose (from a repo clone, or by fetching those same files from GitHub when no clone exists) using the prebuilt intel/multimodal-embedding-serving image, embed text/images/videos over REST on port 9777, choose among 19 models (CLIP/SigLIP/MobileCLIP/CN-CLIP/Blip2/ QwenText), or integrate in-process via the Python SDK wheel. Use when an app needs embeddings for similarity search or retrieval. Not for modifying the service's source — that is multimodal-embedding-serving-dev.
# Multimodal Embedding Serving — User
Run and call the embedding service. **Run commands yourself** and relay
output. REST base URL: `http://localhost:9777` (host port hardcoded by
setup.sh; container 8000).
## When to Use
- Deploy the embedding service (Docker Compose or standalone) and confirm health
- Embed text, images, or videos over REST on port 9777
- Choose or switch among the CLIP/SigLIP/MobileCLIP/CN-CLIP/Blip2/QwenText models
- Integrate embeddings in-process via the Python SDK wheel
- Diagnose 400/422 errors or model-capability mismatches
## Example Prompts
Sample Problem-solving scenarios this skill handles end-to-end:
| Example | Problem it solves |
|---|---|
| [image-similarity-finder.md](./example-prompts/image-similarity-finder.md) | Find visually/semantically similar images in a local folder |
| [text-to-image-search.md](./example-prompts/text-to-image-search.md) | Search an image folder with natural-language queries ("Google Lens" for local media) |
| [image-duplicate-detector.md](./example-prompts/image-duplicate-detector.md) | Flag duplicate / near-duplicate images in a folder for cleanup, QC, and dataset deduplication |
## Docs & deploy files — with or without a clone
All paths below are relative to `microservices/multimodal-embedding-serving/`
in the
[edge-ai-libraries](https://github.com/open-edge-platform/edge-ai-libraries)
repo. **No clone?** Fetch any of them from GitHub raw:
```
https://raw.githubusercontent.com/open-edge-platform/edge-ai-libraries/main/microservices/multimodal-embedding-serving/<path>
```
Load these existing docs only when needed:
| Resource | Load when… |
|---|---|
| `docs/user-guide/api-reference.md` + `docs/user-guide/api-docs/openapi.yaml` | building non-text payloads (image/video, base64, `segment_config`) or parsing responses/errors |
| `docs/user-guide/supported-models.md` | choosing or switching models (dimensions, modalities, language, size) |
| `docs/user-guide/sdk-usage.md` + `docs/user-guide/wheel-installation.md` | integrating in-process via the Python SDK wheel |
| `docs/user-guide/get-started.md` | more curl examples and env-var tables |
| `setup.sh`, `docker/compose.yaml` | the deploy artifacts used below |
## 1. Context routing — repo clone or standalone? REST or SDK?
- **In-process Python integration wanted** (no separate server): the service
doubles as an SDK — build the wheel with `poetry build` (needs the repo) and
use `get_model_handler(...)` + `EmbeddingModel`; see
`docs/user-guide/sdk-usage.md`. Rule of thumb: default to REST; pick the SDK
when a Python process embeds heavily and an HTTP hop per item would
dominate. Note: MobileCLIP/Blip2 extras exist only in the Docker image, and
the wheel is not on PyPI.
- Otherwise detect a clone:
```bash
[ -f setup.sh ] && grep -q 'name = "multimodal-embedding-serving"' pyproject.toml 2>/dev/null \
&& echo REPO || echo STANDALONE
```
**REPO** → Step 2 from the microservice root. **STANDALONE** → fetch the two
deploy files, then the exact same Step 2:
```bash
RAW=https://raw.githubusercontent.com/open-edge-platform/edge-ai-libraries/main/microservices/multimodal-embedding-serving
mkdir -p embedding-serving/docker && cd embedding-serving
curl -fsSL $RAW/setup.sh -o setup.sh
curl -fsSL $RAW/docker/compose.yaml -o docker/compose.yaml
```
Already running (`curl -sf localhost:9777/health`) → Step 3.
## 2. Bring-up (identical in both contexts)
1. Pick a model — default `CLIP/clip-vit-b-32` (512-dim, text+image+video).
Trade-offs: `docs/user-guide/supported-models.md`.
2. `setup.sh` must be **sourced** and hard-fails without
`EMBEDDING_MODEL_NAME`. `REGISTRY_URL=intel` selects the prebuilt image;
`--no-build` prevents a source build. Run in the background — first start
downloads the model:
```bash
bash -c 'export EMBEDDING_MODEL_NAME="CLIP/clip-vit-b-32" REGISTRY_URL=intel TAG=latest \
&& source setup.sh && docker compose -f docker/compose.yaml up -d --no-build'
```
Intel GPU: also `export EMBEDDING_DEVICE=GPU` (setup.sh then auto-enables
OpenVINO + THROUGHPUT mode).
3. Wait for readiness:
```bash
until curl -sf http://localhost:9777/health; do sleep 5; done
```
## 3. Capability check (mandatory before non-text inputs)
```bash
curl -s http://localhost:9777/model/capabilities
```
QwenText models are **text-only** — image/video requests return 400. `GET
/model/current` shows the exact loaded model id to use in requests.
## 4. Embed
Text (single string or list of strings):
```bash
curl -s http://localhost:9777/embeddings -H 'Content-Type: application/json' -d '{
"model": "CLIP/clip-vit-b-32",
"input": {"type": "text", "text": "a red truck at a loading dock"},
"encoding_format": "float"
}'
```
Response: `{"embedding": [...]}` — a flat vector for text/image; a **list of
per-frame vectors** for video inputs.
- `model` must equal the loaded model (else 400).
- Image: `{"type":"image_url","image_url":"https://…"}` (plain string, not a
nested object) or `image_base64`. Video: `video_url`/`video_base64`/
`video_frames` with `segment_config` (`num_frames` default 64,
`extraction_fps`, `frame_indexes`) — full shapes and examples:
`docs/user-guide/api-reference.md`.
## 5. Stop / clean
- `docker compose -f docker/compose.yaml down`
- Volumes `ov-models` (model caches) and `data-prep` persist; removing them
forces re-downloads — **confirm with the user first**.
## Troubleshooting
| Symptom | Likely cause → action |
|---|---|
| `source setup.sh` prints ERROR and stops | `EMBEDDING_MODEL_NAME` not exported → export it first |
| No response on 9777 | still starting/downloading → `docker logs -f multimodal-embedding-serving` |
| 400 "model mismatch" | request `model` ≠ loaded model → `GET /model/current` |
| 400 unsupported modality on image/video | text-only model (QwenText) → switch model or send text |
| First non-text request slow | lazy OpenVINO conversion/compile → expected once |
| 422 on `/embeddings` | malformed `input` union → check shapes in `docs/user-guide/api-reference.md` |
| Port 9777 busy | stop the conflicting service (`EMBEDDING_SERVER_PORT` is hardcoded by setup.sh) |
Ver en GitHub