From 4d7abae245d6e16a1db4f28a7a9e6e88f7d86f80 Mon Sep 17 00:00:00 2001 From: lintsinghua Date: Fri, 19 Dec 2025 16:37:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(embedding):=20=E6=94=AF=E6=8C=81=20OpenAI?= =?UTF-8?q?=20=E5=85=BC=E5=AE=B9=20API=20=E5=B9=B6=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 OpenAI 提供商描述以支持兼容 API 服务商 - 前端添加兼容 API 使用引导说明 - 后端 QwenEmbedding 添加 API 密钥验证和错误处理 --- .../app/api/v1/endpoints/embedding_config.py | 4 +- backend/app/services/rag/embeddings.py | 60 ++++++++++++------- .../src/components/agent/EmbeddingConfig.tsx | 21 ++++++- 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/backend/app/api/v1/endpoints/embedding_config.py b/backend/app/api/v1/endpoints/embedding_config.py index 156ea8e..ada0e6c 100644 --- a/backend/app/api/v1/endpoints/embedding_config.py +++ b/backend/app/api/v1/endpoints/embedding_config.py @@ -76,8 +76,8 @@ class TestEmbeddingResponse(BaseModel): EMBEDDING_PROVIDERS: List[EmbeddingProvider] = [ EmbeddingProvider( id="openai", - name="OpenAI", - description="OpenAI 官方嵌入模型,高质量、稳定", + name="OpenAI (兼容 DeepSeek/Moonshot/智谱 等)", + description="OpenAI 官方或兼容 API,填写自定义端点可接入其他服务商", models=[ "text-embedding-3-small", "text-embedding-3-large", diff --git a/backend/app/services/rag/embeddings.py b/backend/app/services/rag/embeddings.py index 75dd881..c51e7f4 100644 --- a/backend/app/services/rag/embeddings.py +++ b/backend/app/services/rag/embeddings.py @@ -486,6 +486,12 @@ class QwenEmbedding(EmbeddingProvider): or getattr(settings, "QWEN_API_KEY", None) or settings.LLM_API_KEY ) + # 🔥 API 密钥验证 + if not self.api_key: + raise ValueError( + "Qwen embedding requires API key. " + "Set EMBEDDING_API_KEY, QWEN_API_KEY or LLM_API_KEY environment variable." + ) # DashScope 兼容 OpenAI 的 embeddings 端点 self.base_url = base_url or "https://dashscope.aliyuncs.com/compatible-mode/v1" self.model = model @@ -502,41 +508,51 @@ class QwenEmbedding(EmbeddingProvider): async def embed_texts(self, texts: List[str]) -> List[EmbeddingResult]: if not texts: return [] - + # 与 OpenAI 接口保持一致的截断策略 max_length = 8191 truncated_texts = [text[:max_length] for text in texts] - + headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", } - + payload = { "model": self.model, "input": truncated_texts, "encoding_format": "float", } - + url = f"{self.base_url.rstrip('/')}/embeddings" - - async with httpx.AsyncClient(timeout=60) as client: - response = await client.post(url, headers=headers, json=payload) - response.raise_for_status() - data = response.json() - - usage = data.get("usage", {}) or {} - total_tokens = usage.get("total_tokens") or usage.get("prompt_tokens") or 0 - - results: List[EmbeddingResult] = [] - for item in data.get("data", []): - results.append(EmbeddingResult( - embedding=item["embedding"], - tokens_used=total_tokens // max(len(texts), 1), - model=self.model, - )) - - return results + + try: + async with httpx.AsyncClient(timeout=60) as client: + response = await client.post(url, headers=headers, json=payload) + response.raise_for_status() + data = response.json() + + usage = data.get("usage", {}) or {} + total_tokens = usage.get("total_tokens") or usage.get("prompt_tokens") or 0 + + results: List[EmbeddingResult] = [] + for item in data.get("data", []): + results.append(EmbeddingResult( + embedding=item["embedding"], + tokens_used=total_tokens // max(len(texts), 1), + model=self.model, + )) + + return results + except httpx.HTTPStatusError as e: + logger.error(f"Qwen embedding API error: {e.response.status_code} - {e.response.text}") + raise RuntimeError(f"Qwen embedding API failed: {e.response.status_code}") from e + except httpx.RequestError as e: + logger.error(f"Qwen embedding network error: {e}") + raise RuntimeError(f"Qwen embedding network error: {e}") from e + except Exception as e: + logger.error(f"Qwen embedding unexpected error: {e}") + raise RuntimeError(f"Qwen embedding failed: {e}") from e class EmbeddingService: diff --git a/frontend/src/components/agent/EmbeddingConfig.tsx b/frontend/src/components/agent/EmbeddingConfig.tsx index e7fc629..564cc74 100644 --- a/frontend/src/components/agent/EmbeddingConfig.tsx +++ b/frontend/src/components/agent/EmbeddingConfig.tsx @@ -434,7 +434,7 @@ export default function EmbeddingConfigPanel() { {/* 说明 */} -
+

关于嵌入模型 @@ -445,6 +445,25 @@ export default function EmbeddingConfigPanel() {

  • • 推荐使用 OpenAI text-embedding-3-small 或本地 Ollama
  • • 向量维度影响存储空间和检索精度
  • + + {/* OpenAI 兼容 API 引导 */} +
    +

    + + 使用 OpenAI 兼容 API +

    +

    + 许多服务商提供 OpenAI 兼容的 API,可以直接使用 openai 作为提供商: +

    +
      +
    • DeepSeek: 端点填写 https://api.deepseek.com/v1
    • +
    • Moonshot: 端点填写 https://api.moonshot.cn/v1
    • +
    • 智谱 GLM: 端点填写 https://open.bigmodel.cn/api/paas/v4
    • +
    +

    + 提示:选择 openai 提供商,填入对应服务的 API Key 和自定义端点即可 +

    +
    );