| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 |
- from __future__ import annotations
- import hashlib
- from typing import Any
- import httpx
- from .config import settings
- class EmbeddingUnavailable(RuntimeError):
- pass
- def embedding_fingerprint(content: str) -> str:
- """文本或模型配置变化时生成新的向量指纹。"""
- payload = (
- f"{settings.embedding_base_url}\0{settings.embedding_model}\0"
- f"{settings.embedding_dimensions}\0{content}"
- )
- return hashlib.sha256(payload.encode("utf-8")).hexdigest()
- class OpenAICompatibleEmbeddingClient:
- """调用 OpenAI 兼容的 Embedding 接口。"""
- @property
- def configured(self) -> bool:
- return bool(
- settings.embedding_api_key
- and settings.embedding_base_url
- and settings.embedding_model
- )
- async def embed(self, inputs: list[str]) -> list[list[float]]:
- """批量生成向量,并校验返回数量和维度。"""
- if not self.configured:
- raise EmbeddingUnavailable(
- "未配置 EMBEDDING_API_KEY / EMBEDDING_BASE_URL / EMBEDDING_MODEL"
- )
- if not inputs:
- return []
- payload: dict[str, Any] = {
- "model": settings.embedding_model,
- "input": inputs,
- "encoding_format": "float",
- }
- if settings.embedding_dimensions:
- payload["dimensions"] = settings.embedding_dimensions
- async with httpx.AsyncClient(timeout=60) as client:
- response = await client.post(
- f"{settings.embedding_base_url}/embeddings",
- headers={"Authorization": f"Bearer {settings.embedding_api_key}"},
- json=payload,
- )
- response.raise_for_status()
- body = response.json()
- data = body.get("data")
- if not isinstance(data, list):
- raise EmbeddingUnavailable("Embedding API 返回中缺少 data 数组")
- ordered = sorted(data, key=lambda item: item.get("index", 0))
- vectors = [item.get("embedding") for item in ordered]
- if len(vectors) != len(inputs) or not all(isinstance(item, list) for item in vectors):
- raise EmbeddingUnavailable("Embedding API 返回的向量数量不匹配")
- if settings.embedding_dimensions and any(
- len(vector) != settings.embedding_dimensions for vector in vectors
- ):
- raise EmbeddingUnavailable(
- f"Embedding 向量维度与配置不一致,期望 {settings.embedding_dimensions}"
- )
- return [[float(value) for value in vector] for vector in vectors]
- embedding_client = OpenAICompatibleEmbeddingClient()
|