Support downloading configuration files from external URLs.
Browse files
main.py
CHANGED
@@ -4,38 +4,25 @@ import httpx
|
|
4 |
import secrets
|
5 |
from contextlib import asynccontextmanager
|
6 |
|
|
|
7 |
from fastapi import FastAPI, HTTPException, Depends
|
|
|
|
|
8 |
|
9 |
from models import RequestModel
|
10 |
-
from utils import
|
11 |
from request import get_payload
|
12 |
from response import fetch_response, fetch_response_stream
|
13 |
|
14 |
from typing import List, Dict
|
15 |
from urllib.parse import urlparse
|
16 |
-
from fastapi.responses import StreamingResponse, JSONResponse
|
17 |
-
from fastapi.middleware.cors import CORSMiddleware
|
18 |
|
19 |
@asynccontextmanager
|
20 |
async def lifespan(app: FastAPI):
|
21 |
# 启动时的代码
|
22 |
timeout = httpx.Timeout(connect=15.0, read=10.0, write=30.0, pool=30.0)
|
23 |
app.state.client = httpx.AsyncClient(timeout=timeout)
|
24 |
-
|
25 |
-
import yaml
|
26 |
-
# 新增: 从环境变量获取配置URL并拉取配置
|
27 |
-
config_url = os.environ.get('CONFIG_URL')
|
28 |
-
if config_url:
|
29 |
-
try:
|
30 |
-
response = await app.state.client.get(config_url)
|
31 |
-
response.raise_for_status()
|
32 |
-
config_data = yaml.safe_load(response.text)
|
33 |
-
# 更新配置
|
34 |
-
global config, api_keys_db, api_list
|
35 |
-
config, api_keys_db, api_list = update_config(config_data)
|
36 |
-
except Exception as e:
|
37 |
-
logger.error(f"Error fetching or parsing config from {config_url}: {str(e)}")
|
38 |
-
|
39 |
yield
|
40 |
# 关闭时的代码
|
41 |
await app.state.client.aclose()
|
@@ -96,6 +83,10 @@ class ModelRequestHandler:
|
|
96 |
self.last_provider_index = -1
|
97 |
|
98 |
def get_matching_providers(self, model_name, token):
|
|
|
|
|
|
|
|
|
99 |
api_index = api_list.index(token)
|
100 |
provider_rules = []
|
101 |
|
@@ -127,6 +118,10 @@ class ModelRequestHandler:
|
|
127 |
return provider_list
|
128 |
|
129 |
async def request_model(self, request: RequestModel, token: str):
|
|
|
|
|
|
|
|
|
130 |
model_name = request.model
|
131 |
matching_providers = self.get_matching_providers(model_name, token)
|
132 |
# print("matching_providers", json.dumps(matching_providers, indent=4, ensure_ascii=False))
|
@@ -164,6 +159,16 @@ class ModelRequestHandler:
|
|
164 |
|
165 |
model_handler = ModelRequestHandler()
|
166 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
167 |
@app.post("/v1/chat/completions")
|
168 |
async def request_model(request: RequestModel, token: str = Depends(verify_api_key)):
|
169 |
return await model_handler.request_model(request, token)
|
@@ -174,7 +179,7 @@ async def options_handler():
|
|
174 |
|
175 |
@app.post("/v1/models")
|
176 |
async def list_models(token: str = Depends(verify_api_key)):
|
177 |
-
models = post_all_models(token)
|
178 |
return JSONResponse(content={
|
179 |
"object": "list",
|
180 |
"data": models
|
@@ -182,7 +187,7 @@ async def list_models(token: str = Depends(verify_api_key)):
|
|
182 |
|
183 |
@app.get("/v1/models")
|
184 |
async def list_models():
|
185 |
-
models = get_all_models()
|
186 |
return JSONResponse(content={
|
187 |
"object": "list",
|
188 |
"data": models
|
|
|
4 |
import secrets
|
5 |
from contextlib import asynccontextmanager
|
6 |
|
7 |
+
from fastapi.middleware.cors import CORSMiddleware
|
8 |
from fastapi import FastAPI, HTTPException, Depends
|
9 |
+
from fastapi.responses import StreamingResponse, JSONResponse
|
10 |
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
11 |
|
12 |
from models import RequestModel
|
13 |
+
from utils import error_handling_wrapper, get_all_models, post_all_models, load_config
|
14 |
from request import get_payload
|
15 |
from response import fetch_response, fetch_response_stream
|
16 |
|
17 |
from typing import List, Dict
|
18 |
from urllib.parse import urlparse
|
|
|
|
|
19 |
|
20 |
@asynccontextmanager
|
21 |
async def lifespan(app: FastAPI):
|
22 |
# 启动时的代码
|
23 |
timeout = httpx.Timeout(connect=15.0, read=10.0, write=30.0, pool=30.0)
|
24 |
app.state.client = httpx.AsyncClient(timeout=timeout)
|
25 |
+
app.state.config, app.state.api_keys_db, app.state.api_list = await load_config(app)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
yield
|
27 |
# 关闭时的代码
|
28 |
await app.state.client.aclose()
|
|
|
83 |
self.last_provider_index = -1
|
84 |
|
85 |
def get_matching_providers(self, model_name, token):
|
86 |
+
config = app.state.config
|
87 |
+
# api_keys_db = app.state.api_keys_db
|
88 |
+
api_list = app.state.api_list
|
89 |
+
|
90 |
api_index = api_list.index(token)
|
91 |
provider_rules = []
|
92 |
|
|
|
118 |
return provider_list
|
119 |
|
120 |
async def request_model(self, request: RequestModel, token: str):
|
121 |
+
config = app.state.config
|
122 |
+
# api_keys_db = app.state.api_keys_db
|
123 |
+
api_list = app.state.api_list
|
124 |
+
|
125 |
model_name = request.model
|
126 |
matching_providers = self.get_matching_providers(model_name, token)
|
127 |
# print("matching_providers", json.dumps(matching_providers, indent=4, ensure_ascii=False))
|
|
|
159 |
|
160 |
model_handler = ModelRequestHandler()
|
161 |
|
162 |
+
# 安全性依赖
|
163 |
+
security = HTTPBearer()
|
164 |
+
|
165 |
+
def verify_api_key(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
166 |
+
api_list = app.state.api_list
|
167 |
+
token = credentials.credentials
|
168 |
+
if token not in api_list:
|
169 |
+
raise HTTPException(status_code=403, detail="Invalid or missing API Key")
|
170 |
+
return token
|
171 |
+
|
172 |
@app.post("/v1/chat/completions")
|
173 |
async def request_model(request: RequestModel, token: str = Depends(verify_api_key)):
|
174 |
return await model_handler.request_model(request, token)
|
|
|
179 |
|
180 |
@app.post("/v1/models")
|
181 |
async def list_models(token: str = Depends(verify_api_key)):
|
182 |
+
models = post_all_models(token, app.state.config, app.state.api_list)
|
183 |
return JSONResponse(content={
|
184 |
"object": "list",
|
185 |
"data": models
|
|
|
187 |
|
188 |
@app.get("/v1/models")
|
189 |
async def list_models():
|
190 |
+
models = get_all_models(config=app.state.config)
|
191 |
return JSONResponse(content={
|
192 |
"object": "list",
|
193 |
"data": models
|
utils.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1 |
-
import yaml
|
2 |
import json
|
3 |
from fastapi import HTTPException, Depends
|
4 |
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
@@ -22,24 +21,48 @@ def update_config(config_data):
|
|
22 |
return config_data, api_keys_db, api_list
|
23 |
|
24 |
# 读取YAML配置文件
|
25 |
-
def load_config():
|
|
|
26 |
try:
|
27 |
with open('./api.yaml', 'r') as f:
|
28 |
# 判断是否为空文件
|
29 |
conf = yaml.safe_load(f)
|
|
|
30 |
if conf:
|
31 |
-
|
32 |
else:
|
33 |
-
logger.error("配置文件 'api.yaml' 为空。请检查文件内容。")
|
34 |
-
|
35 |
except FileNotFoundError:
|
36 |
logger.error("配置文件 'api.yaml' 未找到。请确保文件存在于正确的位置。")
|
37 |
-
|
38 |
except yaml.YAMLError:
|
39 |
logger.error("配置文件 'api.yaml' 格式不正确。请检查 YAML 格式。")
|
40 |
-
|
41 |
-
|
42 |
-
config
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
|
44 |
def ensure_string(item):
|
45 |
if isinstance(item, (bytes, bytearray)):
|
@@ -86,7 +109,7 @@ async def error_handling_wrapper(generator, status_code=200):
|
|
86 |
# 处理生成器为空的情况
|
87 |
return async_generator(["data: {'error': 'No data returned'}\n\n"])
|
88 |
|
89 |
-
def post_all_models(token):
|
90 |
all_models = []
|
91 |
unique_models = set()
|
92 |
|
@@ -141,7 +164,7 @@ def post_all_models(token):
|
|
141 |
|
142 |
return all_models
|
143 |
|
144 |
-
def get_all_models():
|
145 |
all_models = []
|
146 |
unique_models = set()
|
147 |
|
@@ -157,13 +180,4 @@ def get_all_models():
|
|
157 |
}
|
158 |
all_models.append(model_info)
|
159 |
|
160 |
-
return all_models
|
161 |
-
|
162 |
-
# 安全性依赖
|
163 |
-
security = HTTPBearer()
|
164 |
-
|
165 |
-
def verify_api_key(credentials: HTTPAuthorizationCredentials = Depends(security)):
|
166 |
-
token = credentials.credentials
|
167 |
-
if token not in api_list:
|
168 |
-
raise HTTPException(status_code=403, detail="Invalid or missing API Key")
|
169 |
-
return token
|
|
|
|
|
1 |
import json
|
2 |
from fastapi import HTTPException, Depends
|
3 |
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
|
21 |
return config_data, api_keys_db, api_list
|
22 |
|
23 |
# 读取YAML配置文件
|
24 |
+
async def load_config(app):
|
25 |
+
import yaml
|
26 |
try:
|
27 |
with open('./api.yaml', 'r') as f:
|
28 |
# 判断是否为空文件
|
29 |
conf = yaml.safe_load(f)
|
30 |
+
# conf = None
|
31 |
if conf:
|
32 |
+
config, api_keys_db, api_list = update_config(conf)
|
33 |
else:
|
34 |
+
# logger.error("配置文件 'api.yaml' 为空。请检查文件内容。")
|
35 |
+
config, api_keys_db, api_list = [], [], []
|
36 |
except FileNotFoundError:
|
37 |
logger.error("配置文件 'api.yaml' 未找到。请确保文件存在于正确的位置。")
|
38 |
+
config, api_keys_db, api_list = [], [], []
|
39 |
except yaml.YAMLError:
|
40 |
logger.error("配置文件 'api.yaml' 格式不正确。请检查 YAML 格式。")
|
41 |
+
config, api_keys_db, api_list = [], [], []
|
42 |
+
|
43 |
+
if config != []:
|
44 |
+
return config, api_keys_db, api_list
|
45 |
+
|
46 |
+
import os
|
47 |
+
# 新增: 从环境变量获取配置URL并拉取配置
|
48 |
+
config_url = os.environ.get('CONFIG_URL')
|
49 |
+
if config_url:
|
50 |
+
try:
|
51 |
+
response = await app.state.client.get(config_url)
|
52 |
+
# logger.info(f"Fetching config from {response.text}")
|
53 |
+
response.raise_for_status()
|
54 |
+
config_data = yaml.safe_load(response.text)
|
55 |
+
# 更新配置
|
56 |
+
# logger.info(config_data)
|
57 |
+
if config_data:
|
58 |
+
config, api_keys_db, api_list = update_config(config_data)
|
59 |
+
else:
|
60 |
+
logger.error(f"Error fetching or parsing config from {config_url}")
|
61 |
+
config, api_keys_db, api_list = [], [], []
|
62 |
+
except Exception as e:
|
63 |
+
logger.error(f"Error fetching or parsing config from {config_url}: {str(e)}")
|
64 |
+
config, api_keys_db, api_list = [], [], []
|
65 |
+
return config, api_keys_db, api_list
|
66 |
|
67 |
def ensure_string(item):
|
68 |
if isinstance(item, (bytes, bytearray)):
|
|
|
109 |
# 处理生成器为空的情况
|
110 |
return async_generator(["data: {'error': 'No data returned'}\n\n"])
|
111 |
|
112 |
+
def post_all_models(token, config, api_list):
|
113 |
all_models = []
|
114 |
unique_models = set()
|
115 |
|
|
|
164 |
|
165 |
return all_models
|
166 |
|
167 |
+
def get_all_models(config):
|
168 |
all_models = []
|
169 |
unique_models = set()
|
170 |
|
|
|
180 |
}
|
181 |
all_models.append(model_info)
|
182 |
|
183 |
+
return all_models
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|