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() { {/* 说明 */} -
+
+ 许多服务商提供 OpenAI 兼容的 API,可以直接使用 openai 作为提供商: +
+https://api.deepseek.com/v1https://api.moonshot.cn/v1https://open.bigmodel.cn/api/paas/v4+ 提示:选择 openai 提供商,填入对应服务的 API Key 和自定义端点即可 +
+