tanbushi commited on
Commit
9432aa8
·
1 Parent(s): 133532e
.clinerules CHANGED
@@ -6,6 +6,7 @@ CUSTOM_INSTRUCTIONS = """
6
  if [ -z "$CONDA_DEFAULT_ENV" ] || [ "$CONDA_DEFAULT_ENV" != "airs" ]; then
7
  conda activate airs
8
  fi
 
9
  """
10
 
11
 
 
6
  if [ -z "$CONDA_DEFAULT_ENV" ] || [ "$CONDA_DEFAULT_ENV" != "airs" ]; then
7
  conda activate airs
8
  fi
9
+ # 使用中文进行交流
10
  """
11
 
12
 
api_key_sb.py CHANGED
@@ -1,5 +1,6 @@
1
  from supabase import create_client, Client
2
  import os
 
3
  from fastapi import FastAPI, Request, HTTPException, Depends, status
4
  from dotenv import load_dotenv
5
  from datetime import timezone, timedelta, datetime
@@ -10,6 +11,9 @@ load_dotenv()
10
  SUPABASE_URL = os.getenv("SUPABASE_URL")
11
  SUPABASE_KEY = os.getenv("SUPABASE_KEY")
12
 
 
 
 
13
  def get_supabase_client() -> Client:
14
  """Initializes and returns a Supabase client instance."""
15
  return create_client(SUPABASE_URL, SUPABASE_KEY)
@@ -27,14 +31,11 @@ async def get_api_key_info(model: str = None):
27
 
28
  if response.data:
29
  api_key_info = response.data[0]
30
- api_key_id = api_key_info.get('api_key_id')
31
- if api_key_id:
32
- await update_api_key_ran_at(api_key_id)
33
  return api_key_info
34
  else:
35
  return None
36
  except Exception as e:
37
- print(f"从Supabase获取API密钥失败: {e}")
38
  return None
39
 
40
  async def update_api_key_ran_at(api_key_id: str, ran_at_time: datetime = None):
@@ -51,15 +52,15 @@ async def update_api_key_ran_at(api_key_id: str, ran_at_time: datetime = None):
51
  else:
52
  current_local_time = ran_at_time.isoformat()
53
 
54
- print(f"尝试更新 API 密钥 {api_key_id} 的 ran_at 为 {current_local_time}")
55
  response = supabase.table("airs_api_keys").update({"ran_at": current_local_time}).eq("id", api_key_id).execute()
56
- print(f'API 密钥 {api_key_id} 的 ran_at 更新成功为 {current_local_time}. ')
57
 
58
  # 验证更新是否成功
59
  verification_response = supabase.from_('airs_api_keys').select('ran_at').eq('id', api_key_id).single().execute()
60
  verified_ran_at = verification_response.data.get('ran_at')
61
- print(f"验证:API 密钥 {api_key_id} 的实际 ran_at 值为 {verified_ran_at}")
62
 
63
  except Exception as e:
64
- print(f"更新API密钥运行时间失败!错误信息:{str(e)}")
65
  raise HTTPException(status_code=500, detail=f"更新API密钥运行时间失败!错误信息:{str(e)}")
 
1
  from supabase import create_client, Client
2
  import os
3
+ import logging # 导入logging模块
4
  from fastapi import FastAPI, Request, HTTPException, Depends, status
5
  from dotenv import load_dotenv
6
  from datetime import timezone, timedelta, datetime
 
11
  SUPABASE_URL = os.getenv("SUPABASE_URL")
12
  SUPABASE_KEY = os.getenv("SUPABASE_KEY")
13
 
14
+ # 配置日志
15
+ logger = logging.getLogger(__name__)
16
+
17
  def get_supabase_client() -> Client:
18
  """Initializes and returns a Supabase client instance."""
19
  return create_client(SUPABASE_URL, SUPABASE_KEY)
 
31
 
32
  if response.data:
33
  api_key_info = response.data[0]
 
 
 
34
  return api_key_info
35
  else:
36
  return None
37
  except Exception as e:
38
+ logger.error(f"从Supabase获取API密钥失败: {e}")
39
  return None
40
 
41
  async def update_api_key_ran_at(api_key_id: str, ran_at_time: datetime = None):
 
52
  else:
53
  current_local_time = ran_at_time.isoformat()
54
 
55
+ logger.info(f"尝试更新 API 密钥 {api_key_id} 的 ran_at 为 {current_local_time}")
56
  response = supabase.table("airs_api_keys").update({"ran_at": current_local_time}).eq("id", api_key_id).execute()
57
+ logger.info(f'API 密钥 {api_key_id} 的 ran_at 更新成功为 {current_local_time}. ')
58
 
59
  # 验证更新是否成功
60
  verification_response = supabase.from_('airs_api_keys').select('ran_at').eq('id', api_key_id).single().execute()
61
  verified_ran_at = verification_response.data.get('ran_at')
62
+ logger.info(f"验证:API 密钥 {api_key_id} 的实际 ran_at 值为 {verified_ran_at}")
63
 
64
  except Exception as e:
65
+ logger.error(f"更新API密钥运行时间失败!错误信息:{str(e)}")
66
  raise HTTPException(status_code=500, detail=f"更新API密钥运行时间失败!错误信息:{str(e)}")
app.py CHANGED
@@ -5,11 +5,16 @@ from fastapi.responses import Response # 导入Response
5
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials # 导入HTTPBearer和HTTPAuthorizationCredentials
6
  import httpx # 使用httpx替代requests,因为requests是同步的,而FastAPI是异步的
7
  import os, json
 
8
  from dotenv import load_dotenv
9
  from enum import Enum
10
  from proxy import do_proxy
11
  # from api_key_sb import get_api_key
12
 
 
 
 
 
13
  # 加载环境变量
14
  load_dotenv()
15
 
@@ -61,5 +66,8 @@ async def proxy_gemini(request: Request, protocol: ProtocolType, host:str, path:
61
  # 将User-Agent改为curl/8.7.1,以模拟curl请求
62
  client_headers["User-Agent"] = "curl/8.7.1"
63
 
 
 
 
64
  # 调用 do_proxy 并返回其结果,此处传的 headers 是不带 api_key 的数据
65
  return await do_proxy(real_url, request.method, client_headers, client_body)
 
5
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials # 导入HTTPBearer和HTTPAuthorizationCredentials
6
  import httpx # 使用httpx替代requests,因为requests是同步的,而FastAPI是异步的
7
  import os, json
8
+ import logging # 导入logging模块
9
  from dotenv import load_dotenv
10
  from enum import Enum
11
  from proxy import do_proxy
12
  # from api_key_sb import get_api_key
13
 
14
+ # 配置日志
15
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
16
+ logger = logging.getLogger(__name__)
17
+
18
  # 加载环境变量
19
  load_dotenv()
20
 
 
66
  # 将User-Agent改为curl/8.7.1,以模拟curl请求
67
  client_headers["User-Agent"] = "curl/8.7.1"
68
 
69
+ logger.info(f"Proxying request to: {real_url}")
70
+ logger.debug(f"Request method: {request.method}, Headers: {client_headers}")
71
+
72
  # 调用 do_proxy 并返回其结果,此处传的 headers 是不带 api_key 的数据
73
  return await do_proxy(real_url, request.method, client_headers, client_body)
memory-bank/activeContext.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Active Context
2
+
3
+ ## Current work focus
4
+ The current focus is on refining the existing codebase and improving its robustness and maintainability.
5
+
6
+ ## Recent changes
7
+ - Created the `memory-bank` directory and all core documentation files.
8
+ - Reviewed `app.py`, `proxy.py`, `api_key_sb.py`, `requirements.txt`, and `.env`.
9
+ - Removed the unnecessary call to `update_api_key_ran_at` from `api_key_sb.py`'s `get_api_key_info` function.
10
+ - Removed the redundant `finally` block in `proxy.py` for `httpx.AsyncClient()`.
11
+
12
+ ## Next steps
13
+ - All `print` statements have been replaced with Python's `logging` module.
14
+ - Consider a unified and structured error response format.
15
+
16
+ ## Active decisions and considerations
17
+ - Ensure the Memory Bank accurately reflects the current state and future direction of the project.
18
+ - Prioritize understanding the existing codebase before proposing new changes.
19
+
20
+ ## Learnings and project insights
21
+ - The project is a FastAPI application acting as a reverse proxy.
22
+ - It uses `httpx` for asynchronous HTTP requests.
23
+ - API key management is handled via Supabase, with `api_key_sb.py` providing functions to fetch and update API key information.
24
+ - The proxy implements retry logic with exponential backoff for `429 Too Many Requests` errors and updates the `ran_at` timestamp for API keys.
25
+ - Environment variables (`PROXY_API_KEY`, `SUPABASE_URL`, `SUPABASE_KEY`) are loaded from `.env` using `python-dotenv`.
26
+ - Dependencies are managed via `requirements.txt`.
27
+
28
+ ## Important patterns and preferences
29
+ - Adherence to the specified Memory Bank structure and content guidelines.
30
+
31
+ ## Learnings and project insights
32
+ - The project appears to be a Python-based FastAPI application, likely serving as a reverse proxy.
33
+ - There's an existing `conda` environment setup.
memory-bank/productContext.md ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Product Context
2
+
3
+ ## Why this project exists
4
+ The "superproxy-1" project aims to address the need for a centralized and controlled access point to various backend APIs. This helps in managing API keys, enforcing rate limits, and providing a consistent interface for client applications.
5
+
6
+ ## Problems it solves
7
+ - **API Key Management:** Centralizes API key storage and usage, reducing the risk of exposure in client-side code.
8
+ - **Rate Limiting:** Allows for global or per-client rate limiting to prevent abuse and ensure fair usage of backend services.
9
+ - **Security:** Adds an additional layer of security by obscuring direct access to backend services.
10
+ - **Simplified Integration:** Provides a single endpoint for multiple APIs, simplifying client-side integration.
11
+
12
+ ## How it should work
13
+ The proxy should receive incoming requests, validate them (e.g., check for valid API keys, apply rate limits), forward them to the appropriate backend API, and return the response to the client.
14
+
15
+ ## User experience goals
16
+ - **Reliability:** Ensure high availability and minimal latency for API requests.
17
+ - **Ease of Use:** Provide clear documentation and a straightforward integration process for developers.
18
+ - **Security:** Maintain a secure environment for API interactions.
memory-bank/progress.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Progress
2
+
3
+ ## What works
4
+ - The foundational Memory Bank structure has been established with core documentation files.
5
+ - Removed redundant `finally` block in `proxy.py` for `httpx.AsyncClient()`.
6
+
7
+ ## What's left to build
8
+ - All `print` statements have been replaced with Python's `logging` module.
9
+ - Consider a unified and structured error response format.
10
+ - Deployment strategy (Docker setup).
11
+
12
+ ## Current status
13
+ - Initial setup of project documentation is complete.
14
+ - The project's core purpose and technical stack are outlined.
15
+ - Redundant `finally` block in `proxy.py` has been successfully removed.
16
+
17
+ ## Known issues
18
+ - No specific issues identified yet, as core development has not begun.
19
+
20
+ ## Evolution of project decisions
21
+ - The decision to use FastAPI and `httpx` for asynchronous proxying is based on performance and ease of development.
22
+ - The Memory Bank structure is adopted to ensure consistent and comprehensive project documentation.
memory-bank/projectBrief.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Project Brief
2
+
3
+ This project, "superproxy-1", is a proxy service designed to handle API requests. The primary goal is to provide a flexible and robust proxy layer for various applications.
4
+
5
+ ## Core Requirements
6
+ - Act as an intermediary for API requests.
7
+ - Potentially handle authentication and authorization.
8
+ - Provide logging and monitoring capabilities.
9
+ - Be easily deployable and scalable.
10
+
11
+ ## Goals
12
+ - Enhance security by abstracting direct API access.
13
+ - Improve performance through caching or load balancing (future consideration).
14
+ - Simplify API integration for client applications.
memory-bank/systemPatterns.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # System Patterns
2
+
3
+ ## System Architecture
4
+ The "superproxy-1" is designed as a lightweight, API-gateway-like service. It will primarily function as a reverse proxy, forwarding requests to various upstream services based on defined rules.
5
+
6
+ ## Key Technical Decisions
7
+ - **Asynchronous Processing:** Utilize an asynchronous framework (e.g., FastAPI with Uvicorn) to handle concurrent requests efficiently.
8
+ - **Configuration-driven Routing:** Implement a flexible routing mechanism that can be configured to direct requests to different backend services.
9
+ - **Modular Design:** Keep the core proxy logic separate from authentication, logging, and other cross-cutting concerns to promote maintainability and extensibility.
10
+
11
+ ## Design Patterns in Use
12
+ - **Proxy Pattern:** The core of the service, acting as a surrogate for other objects (backend APIs).
13
+ - **Middleware Pattern:** For handling concerns like authentication, logging, and rate limiting before or after the main request processing.
14
+
15
+ ## Component Relationships
16
+ - **Client Applications:** Interact with the proxy service.
17
+ - **Proxy Service:** Receives requests, processes them, and forwards them to Upstream Services.
18
+ - **Upstream Services (Backend APIs):** The actual APIs that the proxy communicates with.
19
+
20
+ ## Critical Implementation Paths
21
+ - **Request Routing:** Efficiently determine the correct upstream service for an incoming request.
22
+ - **Error Handling:** Gracefully handle errors from upstream services and communicate them effectively to clients.
23
+ - **Security Measures:** Implement robust authentication and authorization mechanisms.
memory-bank/techContext.md ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Tech Context
2
+
3
+ ## Technologies Used
4
+ - **Python:** Primary programming language.
5
+ - **FastAPI:** Web framework for building the API.
6
+ - **Uvicorn:** ASGI server for running FastAPI applications.
7
+ - **`httpx`:** Asynchronous HTTP client for forwarding requests to upstream services.
8
+ - **`python-dotenv`:** For managing environment variables.
9
+
10
+ ## Development Setup
11
+ - **Conda Environment:** The project uses a `conda` environment named `airs` for dependency management.
12
+ - **`requirements.txt`:** Specifies Python dependencies.
13
+ - **Docker:** For containerization and deployment.
14
+
15
+ ## Technical Constraints
16
+ - **Performance:** The proxy should be able to handle a high volume of concurrent requests with low latency.
17
+ - **Security:** All sensitive information (e.g., API keys) must be handled securely, preferably through environment variables.
18
+ - **Scalability:** The architecture should support horizontal scaling to accommodate increased load.
19
+
20
+ ## Dependencies
21
+ - `fastapi`
22
+ - `uvicorn`
23
+ - `httpx`
24
+ - `python-dotenv`
25
+
26
+ ## Tool Usage Patterns
27
+ - **`uvicorn app:app --host 0.0.0.0 --port 7860 --reload`:** Command for local development server.
28
+ - **`conda activate airs`:** To activate the development environment.
proxy.py CHANGED
@@ -1,20 +1,21 @@
1
  import httpx
2
  import asyncio
3
  from starlette.responses import Response
4
- from api_key_sb import get_api_key_info, get_supabase_client, update_api_key_ran_at
5
- from supabase import Client
6
  from dotenv import load_dotenv
7
  import os
8
  from datetime import timezone, timedelta, datetime
9
  from fastapi import FastAPI, Request, HTTPException, Depends, status
 
10
 
11
  load_dotenv()
12
 
13
- # 获取共享的 Supabase 客户端实例
14
- supabase: Client = get_supabase_client()
15
 
16
  async def do_proxy(url: str, method: str, headers: dict, content: str, max_retries: int = 3):
17
- print("Proxy service started.", url)
18
  client = None
19
  try:
20
  async with httpx.AsyncClient() as client:
@@ -25,7 +26,7 @@ async def do_proxy(url: str, method: str, headers: dict, content: str, max_retri
25
  api_key = api_key_info.get('api_key')
26
  api_key_id = api_key_info.get('api_key_id')
27
  api_key_show = api_key[:5]+'*****'+api_key[-5:]
28
- print(f"使用API密钥:{api_key_show}")
29
  headers["Authorization"] = f"Bearer {api_key}"
30
  response = await client.request(
31
  method=method,
@@ -39,7 +40,7 @@ async def do_proxy(url: str, method: str, headers: dict, content: str, max_retri
39
  if response.status_code == 429:
40
  retry_after = response.headers.get("Retry-After", "5") # 默认5秒
41
  wait_time = int(retry_after) + attempt * 2 # 指数退避基础值
42
- print(f"⚠️ 429错误!{wait_time}秒后重试 (尝试:{attempt+1})")
43
  await asyncio.sleep(wait_time)
44
 
45
  try:
@@ -49,10 +50,11 @@ async def do_proxy(url: str, method: str, headers: dict, content: str, max_retri
49
  future_time = now_beijing + timedelta(days=1)
50
  # future_timestamp = future_time.timestamp()
51
  future_timestamp = future_time.isoformat()
52
- print('future_timestamp',future_timestamp)
53
  await update_api_key_ran_at(api_key_id, future_time)
54
- print('更新成功')
55
  except Exception as e:
 
56
  raise HTTPException(status_code=500, detail=f"更新API密钥运行时间失败!错误信息:{str(e)}")
57
  continue
58
 
@@ -64,13 +66,9 @@ async def do_proxy(url: str, method: str, headers: dict, content: str, max_retri
64
  )
65
 
66
  except httpx.HTTPStatusError as e:
67
- print(f"🚨 服务器错误 {e.response.status_code}: {e.request.url}")
68
  return Response(content=str(e), status_code=e.response.status_code)
69
 
70
  except Exception as e:
71
- print(f"🔥 致命错误: {type(e).__name__}: {str(e)}")
72
  return Response(content="Internal Server Error", status_code=500)
73
-
74
- finally:
75
- if client:
76
- await client.aclose() # 确保连接关闭
 
1
  import httpx
2
  import asyncio
3
  from starlette.responses import Response
4
+ from api_key_sb import get_api_key_info, update_api_key_ran_at, supabase as sb_client # 移除 get_supabase_client
5
+ # from supabase import Client
6
  from dotenv import load_dotenv
7
  import os
8
  from datetime import timezone, timedelta, datetime
9
  from fastapi import FastAPI, Request, HTTPException, Depends, status
10
+ import logging # 导入logging模块
11
 
12
  load_dotenv()
13
 
14
+ # 配置日志
15
+ logger = logging.getLogger(__name__)
16
 
17
  async def do_proxy(url: str, method: str, headers: dict, content: str, max_retries: int = 3):
18
+ logger.info(f"Proxy service started for URL: {url}")
19
  client = None
20
  try:
21
  async with httpx.AsyncClient() as client:
 
26
  api_key = api_key_info.get('api_key')
27
  api_key_id = api_key_info.get('api_key_id')
28
  api_key_show = api_key[:5]+'*****'+api_key[-5:]
29
+ logger.info(f"使用API密钥:{api_key_show}")
30
  headers["Authorization"] = f"Bearer {api_key}"
31
  response = await client.request(
32
  method=method,
 
40
  if response.status_code == 429:
41
  retry_after = response.headers.get("Retry-After", "5") # 默认5秒
42
  wait_time = int(retry_after) + attempt * 2 # 指数退避基础值
43
+ logger.warning(f"⚠️ 429错误!{wait_time}秒后重试 (尝试:{attempt+1})")
44
  await asyncio.sleep(wait_time)
45
 
46
  try:
 
50
  future_time = now_beijing + timedelta(days=1)
51
  # future_timestamp = future_time.timestamp()
52
  future_timestamp = future_time.isoformat()
53
+ logger.info(f'future_timestamp: {future_timestamp}')
54
  await update_api_key_ran_at(api_key_id, future_time)
55
+ logger.info('API密钥运行时间更新成功')
56
  except Exception as e:
57
+ logger.error(f"更新API密钥运行时间失败!错误信息:{str(e)}")
58
  raise HTTPException(status_code=500, detail=f"更新API密钥运行时间失败!错误信息:{str(e)}")
59
  continue
60
 
 
66
  )
67
 
68
  except httpx.HTTPStatusError as e:
69
+ logger.error(f"🚨 服务器错误 {e.response.status_code}: {e.request.url}")
70
  return Response(content=str(e), status_code=e.response.status_code)
71
 
72
  except Exception as e:
73
+ logger.error(f"🔥 致命错误: {type(e).__name__}: {str(e)}")
74
  return Response(content="Internal Server Error", status_code=500)