KaThaNg commited on
Commit
204c20d
·
verified ·
1 Parent(s): 1f13653

Update proxy_server.py

Browse files
Files changed (1) hide show
  1. 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 # Import để phân tích URL
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: Hide full target endpoint URL from logs ---
54
- # Parse the URL to get only the hostname for logging
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 depending on config
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
- messages.append({"role": "system", "content": system_prompt})
 
 
 
 
 
 
 
 
 
 
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
- # --- FIX: Avoid logging potentially large/sensitive request data ---
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
- # --- FIX: 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
- # --- End Fix ---
376
 
377
  try:
378
- # --- FIX: Hide full target endpoint URL and payload from logs ---
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, but can be overridden here
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
- # --- FIX: Avoid logging full response data ---
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
- error_detail_text = e.response.text # Use .text instead of await .aread() as body is likely read
 
 
 
 
 
440
 
441
- # --- FIX: 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
- # --- 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
- # --- FIX: Hide target endpoint URL from timeout log ---
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
- # --- FIX: Hide target endpoint URL from request error log ---
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