mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
feat: ProxyNode 代理节点管理系统与 OpenAI Responses API 解析增强
ProxyNode 系统:新增 aether-proxy(Rust)海外 VPS 代理组件,后端实现节点注册/心跳/ HMAC 认证/健康检测调度器/模块化集成,前端新增代理节点管理页面。ProxyConfig 支持 node_id 模式,http_client 支持 HMAC 签名代理 URL 构建与 TTL 缓存。 OpenAI CLI 解析器:适配 Responses API 格式,支持 input_tokens/output_tokens 提取、 output[].content[].text 文本解析、response.completed 流式事件 usage 嵌套结构。
This commit is contained in:
5
src/services/proxy_node/__init__.py
Normal file
5
src/services/proxy_node/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Proxy node services."""
|
||||
|
||||
from .health_scheduler import ProxyNodeHealthScheduler, get_proxy_node_health_scheduler
|
||||
|
||||
__all__ = ["ProxyNodeHealthScheduler", "get_proxy_node_health_scheduler"]
|
||||
103
src/services/proxy_node/health_scheduler.py
Normal file
103
src/services/proxy_node/health_scheduler.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
ProxyNode 心跳检测调度器
|
||||
|
||||
定期检查 proxy_nodes 的 last_heartbeat_at,更新节点状态:
|
||||
- elapsed > interval * 3 -> unhealthy
|
||||
- elapsed > interval * 10 -> offline
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from src.core.logger import logger
|
||||
from src.database import create_session
|
||||
from src.models.database import ProxyNode, ProxyNodeStatus
|
||||
from src.services.system.scheduler import get_scheduler
|
||||
|
||||
|
||||
class ProxyNodeHealthScheduler:
|
||||
"""代理节点心跳检测调度器"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.running = False
|
||||
|
||||
async def start(self) -> Any:
|
||||
if self.running:
|
||||
logger.warning("ProxyNodeHealthScheduler already running")
|
||||
return
|
||||
|
||||
self.running = True
|
||||
logger.info("ProxyNodeHealthScheduler started")
|
||||
|
||||
scheduler = get_scheduler()
|
||||
scheduler.add_interval_job(
|
||||
self._scheduled_check,
|
||||
seconds=30,
|
||||
job_id="proxy_node_health_check",
|
||||
name="代理节点心跳检测",
|
||||
)
|
||||
|
||||
# 启动时立即执行一次
|
||||
await self._check_heartbeats()
|
||||
|
||||
async def stop(self) -> Any:
|
||||
if not self.running:
|
||||
return
|
||||
self.running = False
|
||||
logger.info("ProxyNodeHealthScheduler stopped")
|
||||
|
||||
async def _scheduled_check(self) -> None:
|
||||
await self._check_heartbeats()
|
||||
|
||||
async def _check_heartbeats(self) -> None:
|
||||
db = create_session()
|
||||
try:
|
||||
now = datetime.now(timezone.utc)
|
||||
nodes = db.query(ProxyNode).filter(ProxyNode.status != ProxyNodeStatus.OFFLINE).all()
|
||||
if not nodes:
|
||||
return
|
||||
|
||||
changed = 0
|
||||
for node in nodes:
|
||||
interval = int(node.heartbeat_interval or 30)
|
||||
last = node.last_heartbeat_at
|
||||
|
||||
if last is None:
|
||||
new_status = ProxyNodeStatus.OFFLINE
|
||||
else:
|
||||
elapsed = (now - last).total_seconds()
|
||||
if elapsed > interval * 10:
|
||||
new_status = ProxyNodeStatus.OFFLINE
|
||||
elif elapsed > interval * 3:
|
||||
new_status = ProxyNodeStatus.UNHEALTHY
|
||||
else:
|
||||
new_status = ProxyNodeStatus.ONLINE
|
||||
|
||||
if node.status != new_status:
|
||||
node.status = new_status
|
||||
node.updated_at = now
|
||||
changed += 1
|
||||
|
||||
if changed:
|
||||
db.commit()
|
||||
logger.info("ProxyNode 心跳状态已更新: {} 个节点", changed)
|
||||
except Exception as e:
|
||||
try:
|
||||
db.rollback()
|
||||
except Exception:
|
||||
pass
|
||||
logger.exception("ProxyNode 心跳检测失败: {}", e)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
_proxy_node_health_scheduler: ProxyNodeHealthScheduler | None = None
|
||||
|
||||
|
||||
def get_proxy_node_health_scheduler() -> ProxyNodeHealthScheduler:
|
||||
global _proxy_node_health_scheduler
|
||||
if _proxy_node_health_scheduler is None:
|
||||
_proxy_node_health_scheduler = ProxyNodeHealthScheduler()
|
||||
return _proxy_node_health_scheduler
|
||||
@@ -704,6 +704,7 @@ class TaskService:
|
||||
from src.core.exceptions import (
|
||||
ConcurrencyLimitError,
|
||||
EmbeddedErrorException,
|
||||
ProxyNodeUnavailableError,
|
||||
ThinkingSignatureException,
|
||||
UpstreamClientException,
|
||||
)
|
||||
@@ -744,6 +745,20 @@ class TaskService:
|
||||
)
|
||||
return "break"
|
||||
|
||||
if isinstance(cause, ProxyNodeUnavailableError):
|
||||
# ProxyNode 不可用属于“配置明确指定但不可达/不可用”的情况,
|
||||
# 在当前候选上重试通常没有意义,直接切换到下一个候选更合理。
|
||||
logger.warning(" [{}] 代理节点不可用,切换候选: {}", request_id, str(cause))
|
||||
RequestCandidateService.mark_candidate_failed(
|
||||
db=self.db,
|
||||
candidate_id=candidate_record_id,
|
||||
error_type=type(cause).__name__,
|
||||
error_message=extract_error_message(cause),
|
||||
latency_ms=elapsed_ms,
|
||||
concurrent_requests=captured_key_concurrent,
|
||||
)
|
||||
return "break"
|
||||
|
||||
if isinstance(cause, EmbeddedErrorException):
|
||||
error_message = cause.error_message or ""
|
||||
embedded_status = cause.error_code or 200
|
||||
|
||||
Reference in New Issue
Block a user