Spaces:
Paused
Paused
Update proxy_server.py
Browse files- proxy_server.py +29 -32
proxy_server.py
CHANGED
|
@@ -12,7 +12,7 @@ from fastapi.responses import StreamingResponse, JSONResponse
|
|
| 12 |
from fastapi.middleware.cors import CORSMiddleware
|
| 13 |
from loguru import logger
|
| 14 |
from typing import AsyncGenerator, Set, Optional, Dict, Any, List
|
| 15 |
-
from urllib.parse import urlparse #
|
| 16 |
|
| 17 |
# --- Logging Configuration ---
|
| 18 |
logger.remove()
|
|
@@ -50,19 +50,12 @@ async def lifespan(app: FastAPI):
|
|
| 50 |
timeout_config = httpx.Timeout(connect=CONNECT_TIMEOUT, read=READ_TIMEOUT, write=WRITE_TIMEOUT, pool=POOL_TIMEOUT)
|
| 51 |
proxy_config = {"http://": HTTP_PROXY, "https://": HTTP_PROXY} if HTTP_PROXY else None
|
| 52 |
|
| 53 |
-
# --- FIX:
|
| 54 |
-
|
| 55 |
-
try:
|
| 56 |
-
parsed_url = urlparse(OPENAI_API_ENDPOINT)
|
| 57 |
-
target_host = parsed_url.netloc # e.g., api.openai.com
|
| 58 |
-
except Exception:
|
| 59 |
-
target_host = "[Invalid Target URL]" # Handle potential parsing errors
|
| 60 |
-
|
| 61 |
-
logger.info(f"Initializing httpx client. Target Host: {target_host}") # Log only the host
|
| 62 |
# --- End Fix ---
|
| 63 |
|
| 64 |
if proxy_config:
|
| 65 |
-
logger.info(f"Using outbound proxy: {HTTP_PROXY}") # Proxy URL might still be sensitive
|
| 66 |
if not OPENAI_API_KEY:
|
| 67 |
logger.warning("OPENAI_API_KEY is not set. Requests to the target endpoint might fail if it requires authentication.")
|
| 68 |
if not VALID_API_KEYS:
|
|
@@ -128,7 +121,17 @@ def claude_request_to_openai_payload(claude_request: Dict[str, Any]) -> Dict[str
|
|
| 128 |
messages = []
|
| 129 |
system_prompt = claude_request.get("system")
|
| 130 |
if system_prompt:
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
for msg in claude_request.get("messages", []):
|
| 134 |
role = msg.get("role")
|
|
@@ -158,9 +161,7 @@ def claude_request_to_openai_payload(claude_request: Dict[str, Any]) -> Dict[str
|
|
| 158 |
# Add other relevant parameter mappings here (e.g., presence_penalty, frequency_penalty)
|
| 159 |
}
|
| 160 |
|
| 161 |
-
# --- FIX: Avoid logging potentially large/sensitive payload ---
|
| 162 |
# logger.debug("Converted Claude request to OpenAI payload.") # Keep this simple
|
| 163 |
-
# --- End Fix ---
|
| 164 |
return openai_payload
|
| 165 |
|
| 166 |
def openai_response_to_claude_response(openai_response: Dict[str, Any], claude_request_id: str) -> Dict[str, Any]:
|
|
@@ -346,9 +347,8 @@ async def proxy_claude_to_openai(request: Request):
|
|
| 346 |
request_id = f"msg_{uuid.uuid4().hex[:24]}" # Generate a unique ID for logging/tracking
|
| 347 |
try:
|
| 348 |
claude_request_data = await request.json()
|
| 349 |
-
#
|
| 350 |
logger.info(f"[{request_id}] Received request. Stream: {claude_request_data.get('stream', False)}. Model: {claude_request_data.get('model')}")
|
| 351 |
-
# --- End Fix ---
|
| 352 |
except json.JSONDecodeError:
|
| 353 |
logger.error(f"[{request_id}] Invalid JSON received in request body.")
|
| 354 |
return create_error_response(400, "invalid_request_error", "Invalid JSON data in request body.")
|
|
@@ -369,15 +369,13 @@ async def proxy_claude_to_openai(request: Request):
|
|
| 369 |
# Add other headers if needed
|
| 370 |
}
|
| 371 |
if OPENAI_API_KEY:
|
| 372 |
-
#
|
| 373 |
# logger.debug(f"[{request_id}] Adding Authorization header to upstream request.") # Log presence, not value
|
| 374 |
headers["Authorization"] = f"Bearer {OPENAI_API_KEY}"
|
| 375 |
-
# --- End Fix ---
|
| 376 |
|
| 377 |
try:
|
| 378 |
-
#
|
| 379 |
logger.debug(f"[{request_id}] Sending request to upstream API...")
|
| 380 |
-
# --- End Fix ---
|
| 381 |
|
| 382 |
# Build the request to the target endpoint
|
| 383 |
target_request = client.build_request(
|
|
@@ -385,8 +383,7 @@ async def proxy_claude_to_openai(request: Request):
|
|
| 385 |
url=OPENAI_API_ENDPOINT,
|
| 386 |
headers=headers,
|
| 387 |
json=openai_payload,
|
| 388 |
-
# Timeout is handled globally by the client
|
| 389 |
-
# timeout=httpx.Timeout(READ_TIMEOUT)
|
| 390 |
)
|
| 391 |
|
| 392 |
# Send the request and handle the response
|
|
@@ -411,10 +408,8 @@ async def proxy_claude_to_openai(request: Request):
|
|
| 411 |
logger.info(f"[{request_id}] Upstream response is non-streaming. Converting.")
|
| 412 |
# FIX: Remove await here
|
| 413 |
openai_response_data = response.json()
|
| 414 |
-
#
|
| 415 |
-
# logger.debug(f"[{request_id}] Non-streaming response from OpenAI: {json.dumps(openai_response_data)}")
|
| 416 |
logger.debug(f"[{request_id}] Received non-streaming response from upstream.")
|
| 417 |
-
# --- End Fix ---
|
| 418 |
try:
|
| 419 |
claude_response_data = openai_response_to_claude_response(openai_response_data, request_id)
|
| 420 |
return JSONResponse(content=claude_response_data)
|
|
@@ -436,11 +431,15 @@ async def proxy_claude_to_openai(request: Request):
|
|
| 436 |
error_detail_text = json.dumps(error_detail) # Convert back to string for logging snippet
|
| 437 |
except json.JSONDecodeError:
|
| 438 |
# If not JSON, read as text
|
| 439 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
|
| 441 |
-
#
|
| 442 |
logger.error(f"[{request_id}] HTTP error from target endpoint ({status_code}). Response snippet: {error_detail_text[:200]}...")
|
| 443 |
-
# --- End Fix ---
|
| 444 |
|
| 445 |
# Map common HTTP errors to Claude error types
|
| 446 |
# Use generic messages in production to avoid leaking upstream details
|
|
@@ -461,15 +460,13 @@ async def proxy_claude_to_openai(request: Request):
|
|
| 461 |
return create_error_response(status_code, err_type, msg)
|
| 462 |
|
| 463 |
except httpx.TimeoutException:
|
| 464 |
-
#
|
| 465 |
logger.error(f"[{request_id}] Request to target endpoint timed out ({READ_TIMEOUT}s).")
|
| 466 |
-
# --- End Fix ---
|
| 467 |
return create_error_response(504, "api_error", "Gateway Timeout: Request to upstream API timed out.")
|
| 468 |
except httpx.RequestError as e:
|
| 469 |
-
#
|
| 470 |
# The exception 'e' might contain the URL, so log a generic message
|
| 471 |
logger.error(f"[{request_id}] Network error connecting to target endpoint: {type(e).__name__}")
|
| 472 |
-
# --- End Fix ---
|
| 473 |
return create_error_response(502, "api_error", f"Bad Gateway: Network error connecting to upstream API.")
|
| 474 |
except Exception as e:
|
| 475 |
logger.exception(f"[{request_id}] Unexpected error during proxy operation: {e}") # Use logger.exception to include traceback
|
|
|
|
| 12 |
from fastapi.middleware.cors import CORSMiddleware
|
| 13 |
from loguru import logger
|
| 14 |
from typing import AsyncGenerator, Set, Optional, Dict, Any, List
|
| 15 |
+
# from urllib.parse import urlparse # Removed: No longer needed for logging
|
| 16 |
|
| 17 |
# --- Logging Configuration ---
|
| 18 |
logger.remove()
|
|
|
|
| 50 |
timeout_config = httpx.Timeout(connect=CONNECT_TIMEOUT, read=READ_TIMEOUT, write=WRITE_TIMEOUT, pool=POOL_TIMEOUT)
|
| 51 |
proxy_config = {"http://": HTTP_PROXY, "https://": HTTP_PROXY} if HTTP_PROXY else None
|
| 52 |
|
| 53 |
+
# --- FIX: Completely hide target endpoint info from logs ---
|
| 54 |
+
logger.info("Initializing httpx client for upstream requests.") # Generic message
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
# --- End Fix ---
|
| 56 |
|
| 57 |
if proxy_config:
|
| 58 |
+
logger.info(f"Using outbound proxy: {HTTP_PROXY}") # Proxy URL might still be sensitive
|
| 59 |
if not OPENAI_API_KEY:
|
| 60 |
logger.warning("OPENAI_API_KEY is not set. Requests to the target endpoint might fail if it requires authentication.")
|
| 61 |
if not VALID_API_KEYS:
|
|
|
|
| 121 |
messages = []
|
| 122 |
system_prompt = claude_request.get("system")
|
| 123 |
if system_prompt:
|
| 124 |
+
# Ensure system prompt content is a string
|
| 125 |
+
if isinstance(system_prompt, list):
|
| 126 |
+
# Combine text blocks if system prompt is a list (like in Claude's format)
|
| 127 |
+
system_content = "\n".join(block.get("text", "") for block in system_prompt if block.get("type") == "text")
|
| 128 |
+
elif isinstance(system_prompt, str):
|
| 129 |
+
system_content = system_prompt
|
| 130 |
+
else:
|
| 131 |
+
system_content = "" # Handle other unexpected types if necessary
|
| 132 |
+
if system_content:
|
| 133 |
+
messages.append({"role": "system", "content": system_content})
|
| 134 |
+
|
| 135 |
|
| 136 |
for msg in claude_request.get("messages", []):
|
| 137 |
role = msg.get("role")
|
|
|
|
| 161 |
# Add other relevant parameter mappings here (e.g., presence_penalty, frequency_penalty)
|
| 162 |
}
|
| 163 |
|
|
|
|
| 164 |
# logger.debug("Converted Claude request to OpenAI payload.") # Keep this simple
|
|
|
|
| 165 |
return openai_payload
|
| 166 |
|
| 167 |
def openai_response_to_claude_response(openai_response: Dict[str, Any], claude_request_id: str) -> Dict[str, Any]:
|
|
|
|
| 347 |
request_id = f"msg_{uuid.uuid4().hex[:24]}" # Generate a unique ID for logging/tracking
|
| 348 |
try:
|
| 349 |
claude_request_data = await request.json()
|
| 350 |
+
# Avoid logging potentially large/sensitive request data
|
| 351 |
logger.info(f"[{request_id}] Received request. Stream: {claude_request_data.get('stream', False)}. Model: {claude_request_data.get('model')}")
|
|
|
|
| 352 |
except json.JSONDecodeError:
|
| 353 |
logger.error(f"[{request_id}] Invalid JSON received in request body.")
|
| 354 |
return create_error_response(400, "invalid_request_error", "Invalid JSON data in request body.")
|
|
|
|
| 369 |
# Add other headers if needed
|
| 370 |
}
|
| 371 |
if OPENAI_API_KEY:
|
| 372 |
+
# Avoid logging API key
|
| 373 |
# logger.debug(f"[{request_id}] Adding Authorization header to upstream request.") # Log presence, not value
|
| 374 |
headers["Authorization"] = f"Bearer {OPENAI_API_KEY}"
|
|
|
|
| 375 |
|
| 376 |
try:
|
| 377 |
+
# Hide full target endpoint URL and payload from logs
|
| 378 |
logger.debug(f"[{request_id}] Sending request to upstream API...")
|
|
|
|
| 379 |
|
| 380 |
# Build the request to the target endpoint
|
| 381 |
target_request = client.build_request(
|
|
|
|
| 383 |
url=OPENAI_API_ENDPOINT,
|
| 384 |
headers=headers,
|
| 385 |
json=openai_payload,
|
| 386 |
+
# Timeout is handled globally by the client
|
|
|
|
| 387 |
)
|
| 388 |
|
| 389 |
# Send the request and handle the response
|
|
|
|
| 408 |
logger.info(f"[{request_id}] Upstream response is non-streaming. Converting.")
|
| 409 |
# FIX: Remove await here
|
| 410 |
openai_response_data = response.json()
|
| 411 |
+
# Avoid logging full response data
|
|
|
|
| 412 |
logger.debug(f"[{request_id}] Received non-streaming response from upstream.")
|
|
|
|
| 413 |
try:
|
| 414 |
claude_response_data = openai_response_to_claude_response(openai_response_data, request_id)
|
| 415 |
return JSONResponse(content=claude_response_data)
|
|
|
|
| 431 |
error_detail_text = json.dumps(error_detail) # Convert back to string for logging snippet
|
| 432 |
except json.JSONDecodeError:
|
| 433 |
# If not JSON, read as text
|
| 434 |
+
try:
|
| 435 |
+
# Read text synchronously since the body is likely already loaded for non-streaming error responses
|
| 436 |
+
error_detail_text = e.response.text
|
| 437 |
+
except Exception:
|
| 438 |
+
logger.warning(f"[{request_id}] Could not read error response body as text.")
|
| 439 |
+
|
| 440 |
|
| 441 |
+
# Log error snippet, avoid full potentially sensitive detail
|
| 442 |
logger.error(f"[{request_id}] HTTP error from target endpoint ({status_code}). Response snippet: {error_detail_text[:200]}...")
|
|
|
|
| 443 |
|
| 444 |
# Map common HTTP errors to Claude error types
|
| 445 |
# Use generic messages in production to avoid leaking upstream details
|
|
|
|
| 460 |
return create_error_response(status_code, err_type, msg)
|
| 461 |
|
| 462 |
except httpx.TimeoutException:
|
| 463 |
+
# Hide target endpoint URL from timeout log
|
| 464 |
logger.error(f"[{request_id}] Request to target endpoint timed out ({READ_TIMEOUT}s).")
|
|
|
|
| 465 |
return create_error_response(504, "api_error", "Gateway Timeout: Request to upstream API timed out.")
|
| 466 |
except httpx.RequestError as e:
|
| 467 |
+
# Hide target endpoint URL from request error log
|
| 468 |
# The exception 'e' might contain the URL, so log a generic message
|
| 469 |
logger.error(f"[{request_id}] Network error connecting to target endpoint: {type(e).__name__}")
|
|
|
|
| 470 |
return create_error_response(502, "api_error", f"Bad Gateway: Network error connecting to upstream API.")
|
| 471 |
except Exception as e:
|
| 472 |
logger.exception(f"[{request_id}] Unexpected error during proxy operation: {e}") # Use logger.exception to include traceback
|