Files
freedak 641e33b834 feat: QYLAW 法律法规知识库
- 语义检索(FAISS + embedding)+ 精确查找(法规名+条号)
- RAG 问答(SSE 流式,支持 thinking 折叠显示)
- 法规浏览(原文阅读)
- 历史记录(检索+对话持久化到 SQLite)
- 设置页(系统提示词/模板/LLM 参数可配置)
- 检索质量评估脚本

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-07 14:55:25 +08:00

80 lines
2.2 KiB
Python

"""embedding 服务客户端 — 调用 114:8003 Qwen3-Embedding 服务"""
import asyncio
import logging
from typing import List
import httpx
from app.config import EMBEDDING_URL, EMBEDDING_MODEL, EMBEDDING_BATCH_SIZE
logger = logging.getLogger(__name__)
_client: httpx.AsyncClient | None = None
def _get_client() -> httpx.AsyncClient:
"""复用 httpx 客户端"""
global _client
if _client is None or _client.is_closed:
_client = httpx.AsyncClient(timeout=60)
return _client
async def embed_batch(texts: List[str], batch_size: int = EMBEDDING_BATCH_SIZE) -> List[List[float]]:
"""批量向量化
Args:
texts: 文本列表
batch_size: 每批大小,默认 32
Returns:
向量列表,顺序与输入一致
Raises:
RuntimeError: embedding 服务不可用或返回错误
"""
if not texts:
return []
client = _get_client()
all_vecs: List[List[float]] = []
for i in range(0, len(texts), batch_size):
batch = texts[i : i + batch_size]
payload = {
"model": EMBEDDING_MODEL,
"input": batch,
}
# 重试 3 次,指数退避
last_err = None
for attempt in range(3):
try:
resp = await client.post(
f"{EMBEDDING_URL}/embeddings",
json=payload,
)
resp.raise_for_status()
data = resp.json()
# vLLM embeddings 响应格式: {data: [{embedding: [...]}], ...}
vecs = [item["embedding"] for item in data["data"]]
all_vecs.extend(vecs)
last_err = None
break
except Exception as e:
last_err = e
wait = 2 ** attempt
logger.warning(f"embedding 批次 {i//batch_size}{attempt+1} 次失败: {e}, {wait}s 后重试")
await asyncio.sleep(wait)
if last_err is not None:
raise RuntimeError(f"embedding 服务调用失败(重试 3 次): {last_err}")
return all_vecs
async def embed_text(text: str) -> List[float]:
"""单条文本向量化"""
vecs = await embed_batch([text])
return vecs[0]