refactor: 单图方案重构 + 动态模型选择 + chat_services优化
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 12m9s
All checks were successful
构建并部署 AI Agent 服务 / deploy (push) Successful in 12m9s
## 核心改动 ### 1. 单图方案重构 - 删除了多图(self.graphs),改为单图(self.graph) - 新增 MainGraphState.current_model 字段用于运行时注入模型 - llm_call 节点改为动态选择模型(create_dynamic_llm_call_node) ### 2. chat_services 优化 - 添加 _cached_services 缓存,避免重复初始化 - 新增 get_cached_chat_services() 函数,用于单图注入 - 新增 _check_http_service_available() 统一HTTP探测逻辑 - 减少重复代码,LocalVLLMChatProvider和LocalSmallModelProvider共用探测方法 ### 3. AIAgentService 重构 - initialize() 只构建一次图,传入 chat_services 字典 - 新增 _resolve_model() 模型回退逻辑 - 新增 _build_invocation() 统一构建调用参数 - process_message() 和 process_message_stream() 改为注入 current_model - 流式处理代码拆分,增加可读性 ### 4. 新增和删除文件 - 新增:backend/app/main_graph/main_graph_builder.py(图构建) - 新增:backend/app/main_graph/subgraph_wrapper.py(子图封装) - 新增:tools/test/test_tavily_search.py(测试) - 删除:backend/app/main_graph/graph.py(旧图) - 删除:backend/app/main_graph/utils/main_graph_builder.py(旧构建器) - 删除:backend/app/main_graph/utils/__init__.py ### 5. 其他更新 - README.md:新增模型服务使用情况详解章节 - backend/app/model_services/__init__.py:新增 get_cached_chat_services 导出 ## 方案优势 - 内存优化:N张图 → 1张图 - 灵活性:运行时动态选择模型,支持同会话不同模型 - 性能:模型服务缓存,初始化仅一次 - 可维护性:减少重复代码,统一HTTP探测逻辑
This commit is contained in:
@@ -33,6 +33,21 @@ from app.config import (
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 缓存已初始化的模型字典
|
||||
_cached_services: Dict[str, BaseChatModel] | None = None
|
||||
|
||||
|
||||
def _check_http_service_available(base_url: str, api_key: str = "", timeout: float = 2.0) -> bool:
|
||||
"""通过探测 /models 端点检查 HTTP API 是否可用(内部工具函数)"""
|
||||
try:
|
||||
import httpx
|
||||
client = httpx.Client(base_url=base_url.rstrip('/'), timeout=timeout)
|
||||
headers = {"Authorization": f"Bearer {api_key}"} if api_key else {}
|
||||
resp = client.get("/models", headers=headers)
|
||||
return resp.status_code == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
class LocalVLLMChatProvider(BaseServiceProvider[BaseChatModel]):
|
||||
"""
|
||||
@@ -54,46 +69,8 @@ class LocalVLLMChatProvider(BaseServiceProvider[BaseChatModel]):
|
||||
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("/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
|
||||
# 使用统一的 HTTP 探测方法
|
||||
return _check_http_service_available(VLLM_BASE_URL, LLM_API_KEY, timeout=2.0)
|
||||
|
||||
def get_service(self) -> BaseChatModel:
|
||||
"""
|
||||
@@ -238,45 +215,8 @@ class LocalSmallModelProvider(BaseServiceProvider[BaseChatModel]):
|
||||
logger.warning("SMALL_VLLM_BASE_URL 未配置,本地小模型不可用")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 先测试主机名能否解析
|
||||
import httpx
|
||||
from urllib.parse import urlparse
|
||||
|
||||
parsed_url = urlparse(self._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"本地小模型服务无法连接: {host}:{port} - {e}")
|
||||
return False
|
||||
|
||||
# 再尝试调用简单的 API
|
||||
client = httpx.Client(base_url=self._base_url.rstrip('/'), timeout=5.0)
|
||||
headers = {}
|
||||
if self._api_key:
|
||||
headers["Authorization"] = f"Bearer {self._api_key}"
|
||||
|
||||
try:
|
||||
response = client.get("/models", headers=headers)
|
||||
if response.status_code == 200:
|
||||
logger.info(f"本地小模型服务可用: {self._model}")
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.warning(f"本地小模型服务响应异常")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.warning(f"本地小模型服务不可用: {e}")
|
||||
return False
|
||||
# 使用统一的 HTTP 探测方法
|
||||
return _check_http_service_available(self._base_url, self._api_key, timeout=2.0)
|
||||
|
||||
def get_service(self) -> BaseChatModel:
|
||||
"""获取本地小模型服务"""
|
||||
@@ -358,25 +298,18 @@ def get_chat_service() -> BaseChatModel:
|
||||
return chain.get_available_service()
|
||||
|
||||
|
||||
def get_all_chat_services() -> Dict[str, BaseChatModel]:
|
||||
"""
|
||||
获取所有可用的生成式大模型服务(用于多模型切换)
|
||||
|
||||
Returns:
|
||||
Dict[str, BaseChatModel]: 模型名称 -> ChatModel 实例 的字典
|
||||
"""
|
||||
def _init_chat_services() -> Dict[str, BaseChatModel]:
|
||||
"""实际初始化所有可用模型(仅在首次调用)"""
|
||||
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}' 不可用,跳过")
|
||||
logger.info(f"已加载模型: {name}")
|
||||
except Exception as e:
|
||||
logger.warning(f"初始化模型 '{name}' 失败: {e}")
|
||||
logger.warning(f"模型 {name} 初始化失败: {e}")
|
||||
|
||||
if not services:
|
||||
raise RuntimeError(f"没有可用的生成式大模型,尝试了: {list(CHAT_PROVIDERS.keys())}")
|
||||
@@ -384,6 +317,25 @@ def get_all_chat_services() -> Dict[str, BaseChatModel]:
|
||||
return services
|
||||
|
||||
|
||||
def get_cached_chat_services() -> Dict[str, BaseChatModel]:
|
||||
"""获取缓存的可用模型字典(用于单图动态注入)"""
|
||||
global _cached_services
|
||||
if _cached_services is None:
|
||||
_cached_services = _init_chat_services()
|
||||
return _cached_services
|
||||
|
||||
|
||||
def get_all_chat_services() -> Dict[str, BaseChatModel]:
|
||||
"""
|
||||
获取所有可用的生成式大模型服务(用于多模型切换,保留兼容性)
|
||||
新代码请使用 get_cached_chat_services() 获取缓存版本
|
||||
|
||||
Returns:
|
||||
Dict[str, BaseChatModel]: 模型名称 -> ChatModel 实例 的字典
|
||||
"""
|
||||
return get_cached_chat_services()
|
||||
|
||||
|
||||
def get_small_llm_service() -> BaseChatModel:
|
||||
"""
|
||||
获取轻量级大模型服务(用于查询改写、意图分类等简单任务)
|
||||
|
||||
Reference in New Issue
Block a user