"""OpenRouter embeddings — batched, L2-normalized float32 vectors. Uses a raw HTTP POST rather than the OpenAI SDK: OpenRouter's embeddings response is not fully SDK-shaped, so the SDK parser raises "No embedding data received". The raw path (same as the sibling RAG project) is reliable. """ from __future__ import annotations import json import os import urllib.error import urllib.request import numpy as np from ._openrouter import BASE_URL, _HEADERS, get_api_key DEFAULT_EMBED_MODEL = "nvidia/llama-nemotron-embed-vl-1b-v2:free" EMBED_DIM = 2048 # llama-nemotron-embed-vl-1b-v2 def get_embed_model() -> str: return os.environ.get("OPENROUTER_EMBED_MODEL", DEFAULT_EMBED_MODEL).strip() def _post(inputs, api_key: str, model: str) -> list[list[float]]: body = json.dumps({"model": model, "input": list(inputs)}).encode() req = urllib.request.Request( f"{BASE_URL}/embeddings", data=body, headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", **_HEADERS, }, method="POST", ) try: with urllib.request.urlopen(req, timeout=120) as resp: data = json.loads(resp.read()) except urllib.error.HTTPError as exc: detail = exc.read()[:300].decode("utf-8", "ignore") raise RuntimeError(f"OpenRouter embeddings HTTP {exc.code}: {detail}") from exc # Preserve request order (OpenAI-compatible responses carry an index). rows = sorted(data["data"], key=lambda d: d.get("index", 0)) return [r["embedding"] for r in rows] def embed_texts(texts, api_key=None, model=None) -> np.ndarray: api_key = api_key or get_api_key() if not api_key: raise RuntimeError("OPENROUTER_API_KEY is not set. See the README.") vecs = _post(texts, api_key, model or get_embed_model()) arr = np.asarray(vecs, dtype=np.float32) norms = np.linalg.norm(arr, axis=1, keepdims=True) norms[norms == 0] = 1.0 return arr / norms def embed_one(text: str, **kw) -> np.ndarray: return embed_texts([text], **kw)[0]