Skip to content

Commit 7138ee4

Browse files
committed
优化
1 parent cecd3b5 commit 7138ee4

34 files changed

Lines changed: 1236 additions & 1180 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ graph TD
7171
7272
subgraph "核心应用 (Core Application)"
7373
FastAPI_App["api_utils/app.py (FastAPI 应用)"]
74-
Routes["api_utils/routes.py (路由处理)"]
74+
Routes["api_utils/routers/* (路由处理)"]
7575
RequestProcessor["api_utils/request_processor.py (请求处理)"]
7676
AuthUtils["api_utils/auth_utils.py (认证管理)"]
7777
PageController["browser_utils/page_controller.py (页面控制)"]

api_utils/__init__.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,19 @@
88
create_app
99
)
1010

11-
# 路由处理器
12-
from .routes import (
13-
read_index,
14-
get_css,
15-
get_js,
16-
get_api_info,
17-
health_check,
18-
list_models,
19-
chat_completions,
20-
cancel_request,
21-
get_queue_status,
22-
websocket_log_endpoint
23-
)
11+
# 路由处理器(改为从 routers 聚合导入)
12+
from .routers import (
13+
read_index,
14+
get_css,
15+
get_js,
16+
get_api_info,
17+
health_check,
18+
list_models,
19+
chat_completions,
20+
cancel_request,
21+
get_queue_status,
22+
websocket_log_endpoint
23+
)
2424

2525
# 工具函数
2626
from .utils import (
@@ -75,4 +75,4 @@
7575
'_process_request_refactored',
7676
# 队列工作器
7777
'queue_worker'
78-
]
78+
]

api_utils/app.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from playwright.async_api import Browser as AsyncBrowser, Playwright as AsyncPlaywright
1919

2020
# --- FIX: Replaced star import with explicit imports ---
21-
from config import NO_PROXY_ENV, EXCLUDED_MODELS_FILENAME
21+
from config import NO_PROXY_ENV, EXCLUDED_MODELS_FILENAME, get_environment_variable
2222

2323
# --- models模块导入 ---
2424
from models import WebSocketConnectionManager
@@ -70,10 +70,10 @@
7070
STREAM_PROCESS = None
7171

7272
# --- Lifespan Context Manager ---
73-
def _setup_logging():
74-
import server
75-
log_level_env = os.environ.get('SERVER_LOG_LEVEL', 'INFO')
76-
redirect_print_env = os.environ.get('SERVER_REDIRECT_PRINT', 'false')
73+
def _setup_logging():
74+
import server
75+
log_level_env = get_environment_variable('SERVER_LOG_LEVEL', 'INFO')
76+
redirect_print_env = get_environment_variable('SERVER_REDIRECT_PRINT', 'false')
7777
server.log_ws_manager = WebSocketConnectionManager()
7878
return setup_server_logging(
7979
logger_instance=server.logger,
@@ -91,11 +91,11 @@ def _initialize_globals():
9191
auth_utils.initialize_keys()
9292
server.logger.info("API keys and global locks initialized.")
9393

94-
def _initialize_proxy_settings():
95-
import server
96-
STREAM_PORT = os.environ.get('STREAM_PORT')
94+
def _initialize_proxy_settings():
95+
import server
96+
STREAM_PORT = get_environment_variable('STREAM_PORT')
9797
if STREAM_PORT == '0':
98-
PROXY_SERVER_ENV = os.environ.get('HTTPS_PROXY') or os.environ.get('HTTP_PROXY')
98+
PROXY_SERVER_ENV = get_environment_variable('HTTPS_PROXY') or get_environment_variable('HTTP_PROXY')
9999
else:
100100
PROXY_SERVER_ENV = f"http://127.0.0.1:{STREAM_PORT or 3120}/"
101101

@@ -109,10 +109,14 @@ def _initialize_proxy_settings():
109109

110110
async def _start_stream_proxy():
111111
import server
112-
STREAM_PORT = os.environ.get('STREAM_PORT')
112+
STREAM_PORT = get_environment_variable('STREAM_PORT')
113113
if STREAM_PORT != '0':
114114
port = int(STREAM_PORT or 3120)
115-
STREAM_PROXY_SERVER_ENV = os.environ.get('UNIFIED_PROXY_CONFIG') or os.environ.get('HTTPS_PROXY') or os.environ.get('HTTP_PROXY')
115+
STREAM_PROXY_SERVER_ENV = (
116+
get_environment_variable('UNIFIED_PROXY_CONFIG')
117+
or get_environment_variable('HTTPS_PROXY')
118+
or get_environment_variable('HTTP_PROXY')
119+
)
116120
server.logger.info(f"Starting STREAM proxy on port {port} with upstream proxy: {STREAM_PROXY_SERVER_ENV}")
117121
server.STREAM_QUEUE = multiprocessing.Queue()
118122
server.STREAM_PROCESS = multiprocessing.Process(target=stream.start, args=(server.STREAM_QUEUE, port, STREAM_PROXY_SERVER_ENV))
@@ -141,8 +145,8 @@ async def _initialize_browser_and_page():
141145
server.is_playwright_ready = True
142146
server.logger.info("Playwright started.")
143147

144-
ws_endpoint = os.environ.get('CAMOUFOX_WS_ENDPOINT')
145-
launch_mode = os.environ.get('LAUNCH_MODE', 'unknown')
148+
ws_endpoint = get_environment_variable('CAMOUFOX_WS_ENDPOINT')
149+
launch_mode = get_environment_variable('LAUNCH_MODE', 'unknown')
146150

147151
if not ws_endpoint and launch_mode != "direct_debug_no_browser":
148152
raise ValueError("CAMOUFOX_WS_ENDPOINT environment variable is missing.")
@@ -213,7 +217,7 @@ async def lifespan(app: FastAPI):
213217
await _start_stream_proxy()
214218
await _initialize_browser_and_page()
215219

216-
launch_mode = os.environ.get('LAUNCH_MODE', 'unknown')
220+
launch_mode = get_environment_variable('LAUNCH_MODE', 'unknown')
217221
if server.is_page_ready or launch_mode == "direct_debug_no_browser":
218222
server.worker_task = asyncio.create_task(queue_worker())
219223
logger.info("Request processing worker started.")
@@ -300,12 +304,13 @@ def create_app() -> FastAPI:
300304
app.add_middleware(APIKeyAuthMiddleware)
301305

302306
# 注册路由
303-
from .routes import (
304-
read_index, get_css, get_js, get_api_info,
305-
health_check, list_models, chat_completions,
306-
cancel_request, get_queue_status, websocket_log_endpoint,
307-
get_api_keys, add_api_key, test_api_key, delete_api_key
308-
)
307+
# Import aggregated modular routers
308+
from .routers import (
309+
read_index, get_css, get_js, get_api_info,
310+
health_check, list_models, chat_completions,
311+
cancel_request, get_queue_status, websocket_log_endpoint,
312+
get_api_keys, add_api_key, test_api_key, delete_api_key
313+
)
309314
from fastapi.responses import FileResponse
310315

311316
app.get("/", response_class=FileResponse)(read_index)
@@ -325,4 +330,4 @@ def create_app() -> FastAPI:
325330
app.post("/api/keys/test")(test_api_key)
326331
app.delete("/api/keys")(delete_api_key)
327332

328-
return app
333+
return app

api_utils/error_utils.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from fastapi import HTTPException
2+
from typing import Optional, Dict
3+
4+
5+
def http_error(status_code: int, detail: str, headers: Optional[Dict[str, str]] = None) -> HTTPException:
6+
return HTTPException(status_code=status_code, detail=detail, headers=headers or None)
7+
8+
9+
def client_cancelled(req_id: str, message: str = "Request cancelled.") -> HTTPException:
10+
return http_error(499, f"[{req_id}] {message}")
11+
12+
13+
def client_disconnected(req_id: str, stage: str = "") -> HTTPException:
14+
suffix = f" during {stage}" if stage else ""
15+
return http_error(499, f"[{req_id}] Client disconnected{suffix}.")
16+
17+
18+
def processing_timeout(req_id: str, message: str = "Processing timed out.") -> HTTPException:
19+
return http_error(504, f"[{req_id}] {message}")
20+
21+
22+
def bad_request(req_id: str, message: str) -> HTTPException:
23+
return http_error(400, f"[{req_id}] {message}")
24+
25+
26+
def server_error(req_id: str, message: str) -> HTTPException:
27+
return http_error(500, f"[{req_id}] {message}")
28+
29+
30+
def upstream_error(req_id: str, message: str) -> HTTPException:
31+
# 502 Bad Gateway for upstream/playwright failures
32+
return http_error(502, f"[{req_id}] {message}")
33+
34+
35+
def service_unavailable(req_id: str, retry_after_seconds: int = 30) -> HTTPException:
36+
return http_error(503, f"[{req_id}] 服务当前不可用。请稍后重试。", headers={"Retry-After": str(retry_after_seconds)})
37+

api_utils/mcp_adapter.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import json
2+
import os
3+
from typing import Dict, Any
4+
import asyncio
5+
6+
import httpx
7+
8+
9+
def _normalize_endpoint(ep: str) -> str:
10+
if not ep:
11+
raise RuntimeError('MCP HTTP endpoint not provided')
12+
return ep.rstrip('/')
13+
14+
15+
async def execute_mcp_tool(name: str, params: Dict[str, Any]) -> str:
16+
"""
17+
Minimal MCP-over-HTTP adapter:
18+
- POST {MCP_HTTP_ENDPOINT}/tools/execute with {name, arguments}
19+
- Returns JSON string.
20+
Compatible with servers exposing MCP-like HTTP interface.
21+
"""
22+
ep = os.environ.get('MCP_HTTP_ENDPOINT')
23+
if not ep:
24+
raise RuntimeError('MCP_HTTP_ENDPOINT not configured')
25+
url = f"{_normalize_endpoint(ep)}/tools/execute"
26+
payload = {"name": name, "arguments": params}
27+
headers = {"Content-Type": "application/json"}
28+
timeout = float(os.environ.get('MCP_HTTP_TIMEOUT', '15'))
29+
async with httpx.AsyncClient(timeout=timeout) as client:
30+
resp = await client.post(url, json=payload, headers=headers)
31+
resp.raise_for_status()
32+
try:
33+
data = resp.json()
34+
except Exception:
35+
data = {"raw": resp.text}
36+
return json.dumps(data, ensure_ascii=False)
37+
38+
39+
async def execute_mcp_tool_with_endpoint(endpoint: str, name: str, params: Dict[str, Any]) -> str:
40+
url = f"{_normalize_endpoint(endpoint)}/tools/execute"
41+
payload = {"name": name, "arguments": params}
42+
headers = {"Content-Type": "application/json"}
43+
timeout = float(os.environ.get('MCP_HTTP_TIMEOUT', '15'))
44+
async with httpx.AsyncClient(timeout=timeout) as client:
45+
resp = await client.post(url, json=payload, headers=headers)
46+
resp.raise_for_status()
47+
try:
48+
data = resp.json()
49+
except Exception:
50+
data = {"raw": resp.text}
51+
return json.dumps(data, ensure_ascii=False)
52+
53+
54+
# Synchronous helpers for use inside threads
55+
def execute_mcp_tool_sync(name: str, params: Dict[str, Any]) -> str:
56+
ep = os.environ.get('MCP_HTTP_ENDPOINT')
57+
if not ep:
58+
raise RuntimeError('MCP_HTTP_ENDPOINT not configured')
59+
url = f"{_normalize_endpoint(ep)}/tools/execute"
60+
payload = {"name": name, "arguments": params}
61+
headers = {"Content-Type": "application/json"}
62+
timeout = float(os.environ.get('MCP_HTTP_TIMEOUT', '15'))
63+
with httpx.Client(timeout=timeout) as client:
64+
resp = client.post(url, json=payload, headers=headers)
65+
resp.raise_for_status()
66+
try:
67+
data = resp.json()
68+
except Exception:
69+
data = {"raw": resp.text}
70+
return json.dumps(data, ensure_ascii=False)
71+
72+
73+
def execute_mcp_tool_with_endpoint_sync(endpoint: str, name: str, params: Dict[str, Any]) -> str:
74+
url = f"{_normalize_endpoint(endpoint)}/tools/execute"
75+
payload = {"name": name, "arguments": params}
76+
headers = {"Content-Type": "application/json"}
77+
timeout = float(os.environ.get('MCP_HTTP_TIMEOUT', '15'))
78+
with httpx.Client(timeout=timeout) as client:
79+
resp = client.post(url, json=payload, headers=headers)
80+
resp.raise_for_status()
81+
try:
82+
data = resp.json()
83+
except Exception:
84+
data = {"raw": resp.text}
85+
return json.dumps(data, ensure_ascii=False)

api_utils/model_switching.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@ async def analyze_model_requirements(req_id: str, context: RequestContext, reque
1717
if parsed_model_list:
1818
valid_model_ids = [m.get("id") for m in parsed_model_list]
1919
if requested_model_id not in valid_model_ids:
20-
raise HTTPException(
21-
status_code=400,
22-
detail=f"[{req_id}] Invalid model '{requested_model_id}'. Available models: {', '.join(valid_model_ids)}",
23-
)
20+
from .error_utils import bad_request
21+
raise bad_request(req_id, f"Invalid model '{requested_model_id}'. Available models: {', '.join(valid_model_ids)}")
2422

2523
context['model_id_to_use'] = requested_model_id
2624
if current_ai_studio_model_id != requested_model_id:
@@ -60,7 +58,8 @@ async def _handle_model_switch_failure(req_id: str, page: AsyncPage, model_id_to
6058
import server
6159
logger.warning(f"[{req_id}] ❌ 模型切换至 {model_id_to_use} 失败。")
6260
server.current_ai_studio_model_id = model_before_switch
63-
raise HTTPException(status_code=422, detail=f"[{req_id}] 未能切换到模型 '{model_id_to_use}'。请确保模型可用。")
61+
from .error_utils import http_error
62+
raise http_error(422, f"[{req_id}] 未能切换到模型 '{model_id_to_use}'。请确保模型可用。")
6463

6564

6665
async def handle_parameter_cache(req_id: str, context: RequestContext) -> None:
@@ -76,4 +75,3 @@ async def handle_parameter_cache(req_id: str, context: RequestContext) -> None:
7675
logger.info(f"[{req_id}] 模型已更改,参数缓存失效。")
7776
page_params_cache.clear()
7877
page_params_cache["last_known_model_id_for_params"] = current_ai_studio_model_id
79-

api_utils/page_response.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ async def locate_response_elements(page: AsyncPage, req_id: str, logger, check_c
1818
await expect_async(response_element).to_be_attached(timeout=90000)
1919
logger.info(f"[{req_id}] 响应元素已定位。")
2020
except (PlaywrightAsyncError, asyncio.TimeoutError) as locate_err:
21-
raise HTTPException(status_code=502, detail=f"[{req_id}] 定位AI Studio响应元素失败: {locate_err}")
21+
from .error_utils import upstream_error
22+
raise upstream_error(req_id, f"定位AI Studio响应元素失败: {locate_err}")
2223
except Exception as locate_exc:
23-
raise HTTPException(status_code=500, detail=f"[{req_id}] 定位响应元素时意外错误: {locate_exc}")
24-
24+
from .error_utils import server_error
25+
raise server_error(req_id, f"定位响应元素时意外错误: {locate_exc}")

0 commit comments

Comments
 (0)