e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
118 lines
4.1 KiB
Python
118 lines
4.1 KiB
Python
"""Embedding endpoint URL helpers.
|
|
|
|
Embedding adapters post to the configured URL exactly. These helpers keep the
|
|
user-visible Settings value aligned with provider-specific endpoint paths.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from urllib.parse import urlparse
|
|
|
|
EMBEDDING_PROVIDER_ALIASES = {
|
|
"google": "gemini",
|
|
"huggingface": "custom",
|
|
"lm_studio": "vllm",
|
|
"llama_cpp": "vllm",
|
|
"openai_compatible": "custom",
|
|
}
|
|
|
|
EMBEDDING_PROVIDER_LABELS = {
|
|
"openai": "OpenAI",
|
|
"gemini": "Gemini",
|
|
"openrouter": "OpenRouter",
|
|
"jina": "Jina",
|
|
"vllm": "vLLM / LM Studio",
|
|
"siliconflow": "SiliconFlow",
|
|
"ollama": "Ollama",
|
|
"cohere": "Cohere",
|
|
}
|
|
|
|
EMBEDDING_PROVIDER_DEFAULT_ENDPOINTS = {
|
|
"openai": "https://api.openai.com/v1/embeddings",
|
|
"gemini": "https://generativelanguage.googleapis.com/v1beta/openai/embeddings",
|
|
"openrouter": "https://openrouter.ai/api/v1/embeddings",
|
|
"cohere": "https://api.cohere.com/v2/embed",
|
|
"jina": "https://api.jina.ai/v1/embeddings",
|
|
"ollama": "http://localhost:11434/api/embed",
|
|
"vllm": "http://localhost:8000/v1/embeddings",
|
|
"siliconflow": "https://api.siliconflow.cn/v1/embeddings",
|
|
"aliyun": (
|
|
"https://dashscope.aliyuncs.com/api/v1/services/embeddings/"
|
|
"multimodal-embedding/multimodal-embedding"
|
|
),
|
|
}
|
|
|
|
EMBEDDING_PROVIDERS_REQUIRING_EMBEDDINGS_PATH = {
|
|
"openai",
|
|
"gemini",
|
|
"openrouter",
|
|
"jina",
|
|
"vllm",
|
|
"siliconflow",
|
|
}
|
|
|
|
|
|
def canonical_embedding_provider_name(name: str | None) -> str:
|
|
value = str(name or "").strip().lower().replace("-", "_")
|
|
return EMBEDDING_PROVIDER_ALIASES.get(value, value)
|
|
|
|
|
|
def _same_origin_url(url: str, path: str) -> str:
|
|
parsed = urlparse(url if "://" in url else f"http://{url}")
|
|
if parsed.scheme and parsed.netloc:
|
|
return f"{parsed.scheme}://{parsed.netloc}{path}"
|
|
return url.rstrip("/") + path
|
|
|
|
|
|
def normalize_embedding_endpoint_for_display(provider: str | None, base_url: str | None) -> str:
|
|
"""Return the full endpoint URL that should be shown and saved in Settings."""
|
|
provider_name = canonical_embedding_provider_name(provider)
|
|
url = str(base_url or "").strip()
|
|
if not url:
|
|
return EMBEDDING_PROVIDER_DEFAULT_ENDPOINTS.get(provider_name, "")
|
|
|
|
trimmed = url.rstrip("/")
|
|
if provider_name in EMBEDDING_PROVIDERS_REQUIRING_EMBEDDINGS_PATH:
|
|
if trimmed.endswith("/embeddings"):
|
|
return trimmed
|
|
if trimmed.endswith("/v1"):
|
|
return f"{trimmed}/embeddings"
|
|
if provider_name == "ollama":
|
|
if trimmed.endswith("/api/embed"):
|
|
return trimmed
|
|
if trimmed.endswith("/api"):
|
|
return f"{trimmed}/embed"
|
|
parsed = urlparse(trimmed if "://" in trimmed else f"http://{trimmed}")
|
|
if parsed.scheme and parsed.netloc and parsed.path in {"", "/"}:
|
|
return _same_origin_url(trimmed, "/api/embed")
|
|
if provider_name == "cohere":
|
|
if trimmed.endswith("/embed"):
|
|
return trimmed
|
|
if trimmed.endswith("/v2"):
|
|
return f"{trimmed}/embed"
|
|
return url
|
|
|
|
|
|
def embedding_endpoint_validation_error(provider: str | None, base_url: str | None) -> str | None:
|
|
"""Validate that known providers use the exact endpoint path shown to users."""
|
|
provider_name = canonical_embedding_provider_name(provider)
|
|
url = str(base_url or "").strip()
|
|
if not url:
|
|
return "Embedding endpoint URL is empty."
|
|
|
|
parsed = urlparse(url if "://" in url else f"http://{url}")
|
|
path = parsed.path.rstrip("/")
|
|
label = EMBEDDING_PROVIDER_LABELS.get(provider_name, provider_name or "Embedding provider")
|
|
|
|
if provider_name in EMBEDDING_PROVIDERS_REQUIRING_EMBEDDINGS_PATH:
|
|
if not path.endswith("/embeddings"):
|
|
return f"{label} embedding endpoint must end with /embeddings."
|
|
elif provider_name == "ollama":
|
|
if path != "/api/embed":
|
|
return "Ollama embedding endpoint must be the full /api/embed URL."
|
|
elif provider_name == "cohere":
|
|
if not path.endswith("/embed"):
|
|
return "Cohere embedding endpoint must end with /embed."
|
|
|
|
return None
|