268 lines
8.3 KiB
Python
268 lines
8.3 KiB
Python
"""
|
||
生成式大模型服务模块
|
||
|
||
本模块提供统一的生成式大模型服务获取接口,支持多种模型:
|
||
1. Local VLLM 服务:本地 gemma-4-E4B-it 模型
|
||
2. Zhipu AI:智谱 glm-5.1 模型
|
||
3. DeepSeek:deepseek-v4-pro 模型
|
||
|
||
主要功能:
|
||
- LocalVLLMChatProvider:本地 VLLM 服务提供者
|
||
- ZhipuChatProvider:智谱 API 服务提供者
|
||
- DeepSeekChatProvider:DeepSeek API 服务提供者
|
||
- get_chat_service():获取默认服务(带自动降级)
|
||
- get_all_chat_services():获取所有可用模型服务(用于多模型切换)
|
||
"""
|
||
|
||
import logging
|
||
from typing import Dict, Callable
|
||
from langchain_core.language_models import BaseChatModel
|
||
|
||
from .base import (
|
||
BaseServiceProvider,
|
||
FallbackServiceChain,
|
||
SingletonServiceManager
|
||
)
|
||
from app.config import (
|
||
VLLM_BASE_URL,
|
||
LLM_API_KEY,
|
||
ZHIPUAI_API_KEY,
|
||
DEEPSEEK_API_KEY
|
||
)
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class LocalVLLMChatProvider(BaseServiceProvider[BaseChatModel]):
|
||
"""
|
||
本地 VLLM 生成式大模型服务提供者
|
||
"""
|
||
|
||
def __init__(self, model: str = "gemma-4-E4B-it"):
|
||
super().__init__("local_vllm_chat")
|
||
self._model = model
|
||
|
||
def is_available(self) -> bool:
|
||
"""
|
||
检查本地 VLLM 服务是否可用
|
||
|
||
Returns:
|
||
bool: 服务是否可用
|
||
"""
|
||
if not VLLM_BASE_URL:
|
||
logger.warning("VLLM_BASE_URL 未配置")
|
||
return False
|
||
|
||
try:
|
||
# 先测试主机名能否解析
|
||
import httpx
|
||
from urllib.parse import urlparse
|
||
|
||
parsed_url = urlparse(VLLM_BASE_URL)
|
||
host = parsed_url.hostname
|
||
port = parsed_url.port or (80 if parsed_url.scheme == 'http' else 443)
|
||
|
||
# 测试能否建立 TCP 连接(快速失败)
|
||
import socket
|
||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
sock.settimeout(2.0)
|
||
try:
|
||
sock.connect((host, port))
|
||
sock.close()
|
||
except Exception as e:
|
||
logger.warning(f"本地 VLLM 服务无法连接: {host}:{port} - {e}")
|
||
return False
|
||
|
||
# 再尝试调用简单的 API(比如 models 接口)
|
||
client = httpx.Client(base_url=VLLM_BASE_URL.rstrip('/'), timeout=5.0)
|
||
headers = {}
|
||
if LLM_API_KEY:
|
||
headers["Authorization"] = f"Bearer {LLM_API_KEY}"
|
||
|
||
try:
|
||
response = client.get("/v1/models", headers=headers)
|
||
if response.status_code == 200:
|
||
logger.info(f"本地 VLLM 服务可用: {self._model}")
|
||
return True
|
||
except Exception:
|
||
pass
|
||
|
||
# 如果 /v1/models 失败,也认为服务不可用
|
||
logger.warning(f"本地 VLLM 服务响应异常")
|
||
return False
|
||
except Exception as e:
|
||
logger.warning(f"本地 VLLM 服务不可用: {e}")
|
||
return False
|
||
|
||
def get_service(self) -> BaseChatModel:
|
||
"""
|
||
获取本地 VLLM 服务
|
||
|
||
Returns:
|
||
BaseChatModel: LangChain 兼容的 ChatModel 实例
|
||
"""
|
||
if self._service_instance is None:
|
||
from langchain_openai import ChatOpenAI
|
||
from pydantic import SecretStr
|
||
|
||
self._service_instance = ChatOpenAI(
|
||
base_url=VLLM_BASE_URL,
|
||
api_key=SecretStr(LLM_API_KEY) if LLM_API_KEY else SecretStr(""),
|
||
model=self._model,
|
||
timeout=60.0,
|
||
max_retries=2,
|
||
streaming=True,
|
||
)
|
||
return self._service_instance
|
||
|
||
|
||
class ZhipuChatProvider(BaseServiceProvider[BaseChatModel]):
|
||
"""
|
||
智谱 AI 生成式大模型服务提供者
|
||
"""
|
||
|
||
def __init__(self, model: str = "glm-5.1"):
|
||
super().__init__("zhipu_chat")
|
||
self._model = model
|
||
|
||
def is_available(self) -> bool:
|
||
"""
|
||
检查智谱 AI 服务是否可用
|
||
|
||
Returns:
|
||
bool: 服务是否可用
|
||
"""
|
||
if not ZHIPUAI_API_KEY:
|
||
logger.warning("ZHIPUAI_API_KEY 未配置")
|
||
return False
|
||
|
||
try:
|
||
logger.info(f"智谱 AI 服务配置正确,准备使用: {self._model}")
|
||
return True
|
||
except Exception as e:
|
||
logger.warning(f"智谱 AI 服务不可用: {e}")
|
||
return False
|
||
|
||
def get_service(self) -> BaseChatModel:
|
||
"""
|
||
获取智谱 AI 服务
|
||
|
||
Returns:
|
||
BaseChatModel: LangChain 兼容的 ChatModel 实例
|
||
"""
|
||
if self._service_instance is None:
|
||
from langchain_community.chat_models import ChatZhipuAI
|
||
|
||
self._service_instance = ChatZhipuAI(
|
||
model=self._model,
|
||
api_key=ZHIPUAI_API_KEY,
|
||
temperature=0.1,
|
||
max_tokens=4096,
|
||
timeout=120.0,
|
||
max_retries=3,
|
||
streaming=True,
|
||
)
|
||
return self._service_instance
|
||
|
||
|
||
class DeepSeekChatProvider(BaseServiceProvider[BaseChatModel]):
|
||
"""
|
||
DeepSeek 生成式大模型服务提供者
|
||
"""
|
||
|
||
def __init__(self, model: str = "deepseek-v4-pro"):
|
||
super().__init__("deepseek_chat")
|
||
self._model = model
|
||
|
||
def is_available(self) -> bool:
|
||
"""
|
||
检查 DeepSeek 服务是否可用
|
||
|
||
Returns:
|
||
bool: 服务是否可用
|
||
"""
|
||
if not DEEPSEEK_API_KEY:
|
||
logger.warning("DEEPSEEK_API_KEY 未配置")
|
||
return False
|
||
|
||
try:
|
||
logger.info(f"DeepSeek 服务配置正确,准备使用: {self._model}")
|
||
return True
|
||
except Exception as e:
|
||
logger.warning(f"DeepSeek 服务不可用: {e}")
|
||
return False
|
||
|
||
def get_service(self) -> BaseChatModel:
|
||
"""
|
||
获取 DeepSeek 服务
|
||
|
||
Returns:
|
||
BaseChatModel: LangChain 兼容的 ChatModel 实例
|
||
"""
|
||
if self._service_instance is None:
|
||
from langchain_openai import ChatOpenAI
|
||
from pydantic import SecretStr
|
||
|
||
self._service_instance = ChatOpenAI(
|
||
base_url="https://api.deepseek.com",
|
||
api_key=SecretStr(DEEPSEEK_API_KEY),
|
||
model=self._model,
|
||
temperature=0.1,
|
||
max_tokens=4096,
|
||
timeout=60.0,
|
||
max_retries=2,
|
||
streaming=True,
|
||
)
|
||
return self._service_instance
|
||
|
||
|
||
# 全局服务映射表 - 名称 -> Provider
|
||
CHAT_PROVIDERS: Dict[str, Callable[[], BaseServiceProvider[BaseChatModel]]] = {
|
||
"local": lambda: LocalVLLMChatProvider(),
|
||
"zhipu": lambda: ZhipuChatProvider(),
|
||
"deepseek": lambda: DeepSeekChatProvider(),
|
||
}
|
||
|
||
|
||
def get_chat_service() -> BaseChatModel:
|
||
"""
|
||
获取默认的生成式大模型服务(带自动降级)
|
||
优先顺序: local -> zhipu -> deepseek
|
||
|
||
Returns:
|
||
BaseChatModel: LangChain 兼容的 ChatModel 实例
|
||
"""
|
||
def _create_chain():
|
||
primary = LocalVLLMChatProvider()
|
||
fallbacks = [ZhipuChatProvider(), DeepSeekChatProvider()]
|
||
return FallbackServiceChain(primary, fallbacks)
|
||
|
||
chain = SingletonServiceManager.get_or_create("chat_service_chain", _create_chain)
|
||
return chain.get_available_service()
|
||
|
||
|
||
def get_all_chat_services() -> Dict[str, BaseChatModel]:
|
||
"""
|
||
获取所有可用的生成式大模型服务(用于多模型切换)
|
||
|
||
Returns:
|
||
Dict[str, BaseChatModel]: 模型名称 -> ChatModel 实例 的字典
|
||
"""
|
||
services = {}
|
||
|
||
for name, provider_factory in CHAT_PROVIDERS.items():
|
||
try:
|
||
provider = provider_factory()
|
||
if provider.is_available():
|
||
logger.info(f"模型 '{name}' 可用")
|
||
services[name] = provider.get_service()
|
||
else:
|
||
logger.warning(f"模型 '{name}' 不可用,跳过")
|
||
except Exception as e:
|
||
logger.warning(f"初始化模型 '{name}' 失败: {e}")
|
||
|
||
if not services:
|
||
raise RuntimeError(f"没有可用的生成式大模型,尝试了: {list(CHAT_PROVIDERS.keys())}")
|
||
|
||
return services
|