Files
MemeMeow/services/embedding_service.py
2025-05-25 20:27:47 +08:00

113 lines
4.0 KiB
Python

import os
import sys
import time
import requests
import openai
from openai import OpenAI
import pickle
from config.settings import Config
from typing import List, Optional, Union
import numpy as np
from tqdm import tqdm
from services.utils import verify_folder
import threading
class EmbeddingService:
def __init__(self):
self.api_key = Config().api.embedding_models.api_key
self.base_url = Config().api.embedding_models.base_url
self.selected_embedding_model = Config().models.selected_embedding_model
self.embedding_cache = {}
self._get_embedding_cache()
self.cache_lock = threading.Lock()
self.client = OpenAI(api_key=self.api_key, base_url=self.base_url)
self.rpm_monitor = [0]
def refresh_config(self):
self.api_key = Config().api.embedding_models.api_key
self.base_url = Config().api.embedding_models.base_url
self.selected_embedding_model = Config().models.selected_embedding_model
self.client = OpenAI(api_key=self.api_key, base_url=self.base_url)
self.rpm_monitor = [0]
def _get_embedding_cache(self):
"""获取嵌入缓存"""
cache_file = Config().get_abs_api_cache_file()
verify_folder(cache_file)
if os.path.exists(cache_file):
with open(cache_file, 'rb') as f:
self.embedding_cache = pickle.load(f)
def is_rpm_overload(self):
"""检查RPM是否过载"""
pt = time.time()
send_count = 0
for k in self.rpm_monitor:
if pt - k > 60:
continue
send_count += 1
if send_count >= 1800:
return True
return False
def get_last_request_time(self):
"""获取最后一次请求的时间"""
return self.rpm_monitor[-1]
def save_embedding_cache(self):
"""保存嵌入缓存"""
cache_file = Config().get_abs_api_cache_file()
if sys.gettrace() is not None:
print(f'saving cache: {sum(len(i) for i in self.embedding_cache.values())}')
with open(cache_file, 'wb') as f:
pickle.dump(self.embedding_cache, f)
@staticmethod
def normalize_embedding(embedding: Union[List[float], np.ndarray]) -> np.ndarray:
"""归一化嵌入向量"""
if isinstance(embedding, list):
embedding = np.array(embedding)
return embedding / np.linalg.norm(embedding)
def get_embedding(self, text: str, key: str = None) -> np.ndarray:
"""获取文本嵌入并归一化"""
# API 模式
model_name = Config().models.embedding_models['bge-m3'].name
payload = {
"input": text,
"model": model_name,
"encoding_format": "float" # 指定返回格式
}
self.cache_lock.acquire()
if model_name in self.embedding_cache.keys() and text in self.embedding_cache[model_name].keys():
if sys.gettrace() is not None:
print(f'using cache: {model_name} {text}')
embedding = self.embedding_cache[model_name][text]
self.cache_lock.release()
else:
# 检查是否指定新的api key,如果指定则更新api key
if key is not None and key != self.api_key:
self.api_key = key
self.cache_lock.release()
try:
response = self.client.embeddings.create(**payload)
embedding = response.data[0].embedding
except openai.OpenAIError as e:
raise RuntimeError(f"API请求失败: {str(e)}\n请求参数: {payload}")
self.cache_lock.acquire()
if model_name not in self.embedding_cache.keys():
self.embedding_cache[model_name] = {}
self.embedding_cache[model_name][text] = embedding
self.rpm_monitor.append(time.time())
self.cache_lock.release()
# 确保返回新的归一化向量
return self.normalize_embedding(embedding.copy() if isinstance(embedding, np.ndarray) else embedding)