|
|
import os |
|
|
from datetime import datetime |
|
|
from fastapi import FastAPI, HTTPException, Request, Depends |
|
|
import httpx |
|
|
import json |
|
|
from starlette.middleware.cors import CORSMiddleware |
|
|
from starlette.responses import StreamingResponse, JSONResponse |
|
|
import random |
|
|
import asyncio |
|
|
import threading |
|
|
import string |
|
|
|
|
|
import logging |
|
|
|
|
|
app = FastAPI() |
|
|
app.add_middleware( |
|
|
CORSMiddleware, |
|
|
allow_origins=["*"], |
|
|
allow_credentials=True, |
|
|
allow_methods=["GET", "POST", "PUT", "OPTIONS"], |
|
|
allow_headers=["*"], |
|
|
) |
|
|
baseurl = os.environ.get("BASEURL", "https://duckduckgo.com") |
|
|
key = os.environ.get('KEY', 'sk-123456') |
|
|
|
|
|
baseurl = baseurl.rstrip('/') |
|
|
device_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k=32)) |
|
|
print(f'device_id: {device_id}') |
|
|
apiToken ='' |
|
|
|
|
|
async def chat_chunk(body, headers): |
|
|
chat_url = f"{baseurl}/duckchat/v1/chat" |
|
|
n = 0 |
|
|
while True: |
|
|
n += 1 |
|
|
if n > 6: |
|
|
raise HTTPException(status_code=response.status_code, detail="Failed to stream chat") |
|
|
async with httpx.AsyncClient() as client: |
|
|
|
|
|
async with client.stream("POST", chat_url, json=body, headers=headers, timeout=120) as response: |
|
|
if response.status_code != 200: |
|
|
print(f'retry to stream chat') |
|
|
sec = ''.join(random.choices('123', k=1)) |
|
|
await asyncio.sleep(int(sec)) |
|
|
headers = await get_headers() |
|
|
continue |
|
|
async for line in response.aiter_lines(): |
|
|
if line.startswith("data: [DONE]"): |
|
|
char_data["choices"][0]["finish_reason"] = "stop" |
|
|
char_data["choices"][0]["delta"] = {} |
|
|
yield f"data: {json.dumps(char_data, ensure_ascii=False)}\n\n" |
|
|
yield f"data: [DONE]\n\n" |
|
|
return |
|
|
choices = [] |
|
|
if line.startswith('data: '): |
|
|
chunk = line[6:] |
|
|
data = json.loads(chunk) |
|
|
delta = {} |
|
|
if data.get("role") and data.get("id"): |
|
|
delta = {"role": "assistant"} |
|
|
else: |
|
|
if data.get("message", "") == "": |
|
|
continue |
|
|
delta["content"] = data.get("message", "") |
|
|
|
|
|
finish = None |
|
|
|
|
|
|
|
|
if "id" in data: |
|
|
chatid = data["id"] |
|
|
chatid = chatid.replace("msg_", "chatcmpl-") |
|
|
choices.append( |
|
|
{"index": 0, "delta": delta, "finish_reason": finish}) |
|
|
char_data = {"id": chatid, "object": "chat.completion.chunk", |
|
|
"model": data.get("model", "claude-3-haiku-20240307"), |
|
|
"created": data["created"], "choices": choices} |
|
|
yield f"data: {json.dumps(char_data, ensure_ascii=False)}\n\n" |
|
|
|
|
|
|
|
|
async def get_headers(): |
|
|
ver = ''.join(random.choices('0123456789', k=3)) |
|
|
ver2 = ''.join(random.choices('0123456789', k=3)) |
|
|
ver3 = ''.join(random.choices('0123456789', k=3)) |
|
|
ver4 = ''.join(random.choices('0123456789', k=1)) |
|
|
ver5 = ''.join(random.choices('0123456789', k=1)) |
|
|
ver6 = ''.join(random.choices('0123456789', k=1)) |
|
|
ver7 = ''.join(random.choices('0123456789', k=8)) |
|
|
|
|
|
headers = { |
|
|
"User-Agent": f"Mozilla/{ver}.{ver4} (X11; Ubuntu; Linux x86_64; rv:{ver2}.{ver5}) Gecko/{ver7} Firefox/" |
|
|
f"{ver3}.{ver6}", |
|
|
"Accept": "text/event-stream", |
|
|
"Accept-Language": "de,en-US;q=0.7,en;q=0.3", |
|
|
"Accept-Encoding": "gzip, deflate, br", |
|
|
"Content-Type": "application/json", |
|
|
"Connection": "keep-alive", |
|
|
"Cookie": "dcs=1; dcm=5", |
|
|
"Sec-Fetch-Dest": "empty", |
|
|
"Sec-Fetch-Mode": "cors", |
|
|
"Sec-Fetch-Site": "same-origin", |
|
|
|
|
|
"Origin": "https://duckduckgo.com/", |
|
|
"Referer": "https://duckduckgo.com/" |
|
|
|
|
|
} |
|
|
|
|
|
return headers |
|
|
|
|
|
|
|
|
async def get_xvqd(): |
|
|
status_url = f"{baseurl}/duckchat/v1/status" |
|
|
headers = await get_headers() |
|
|
headers["Accept"] = "*/*" |
|
|
headers["x-vqd-accept"] = "1" |
|
|
|
|
|
max_retries = 3 |
|
|
retry_delay = 2 |
|
|
|
|
|
for attempt in range(max_retries): |
|
|
try: |
|
|
async with httpx.AsyncClient() as client: |
|
|
resp = await client.get(status_url, headers=headers, timeout=60) |
|
|
|
|
|
resp.raise_for_status() |
|
|
|
|
|
vqd4 = resp.headers.get("x-vqd-4") |
|
|
headers["x-vqd-4"] = vqd4 |
|
|
del headers["x-vqd-accept"] |
|
|
return headers |
|
|
except httpx.HTTPError as exc: |
|
|
|
|
|
print(f"Retrying after too many requests to get xvqd: {resp.status_code}({attempt + 1}/{max_retries})") |
|
|
await asyncio.sleep(retry_delay) |
|
|
headers = await get_headers() |
|
|
headers["x-vqd-accept"] = "1" |
|
|
retry_delay *= 2 |
|
|
except Exception as e: |
|
|
|
|
|
print(f"Retrying after An unexpected error occurred to get xvqd: ({attempt + 1}/{max_retries})") |
|
|
await asyncio.sleep(retry_delay) |
|
|
headers = await get_headers() |
|
|
headers["x-vqd-accept"] = "1" |
|
|
retry_delay *= 2 |
|
|
|
|
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to get xvqd after multiple retries") |
|
|
|
|
|
async def auth(request: Request): |
|
|
|
|
|
token = key |
|
|
|
|
|
auth_header = request.headers.get('Authorization') |
|
|
print() |
|
|
print(f'{formatted_now()} auth_chunk: {request.headers}') |
|
|
|
|
|
if not auth_header: |
|
|
print('没有验证') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return "Authorized" |
|
|
@app.post("/v1/chat/completions") |
|
|
async def chat_completions(req: Request, authorized: str = Depends(auth)): |
|
|
body = await req.json() |
|
|
usermodel = body.get("model", "o3-mini") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if 'glm' in usermodel.lower(): |
|
|
try: |
|
|
|
|
|
url = 'https://open.bigmodel.cn/api/paas/v4/chat/completions' |
|
|
headers = {'Content-Type': 'application/json', |
|
|
'Authorization': f'Bearer {apiToken}' |
|
|
} |
|
|
if body.get("stream", False): |
|
|
return StreamingResponse(zhipu_chunk(url, headers, body), media_type="text/event-stream") |
|
|
else: |
|
|
return await zhipu_chat(url, headers, body) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
except json.JSONDecodeError as e: |
|
|
raise HTTPException(status_code=400, detail=str(e)) |
|
|
except httpx.ReadTimeout: |
|
|
raise HTTPException(status_code=408, detail="Request Timeout") |
|
|
elif 'embedding' in usermodel.lower(): |
|
|
try: |
|
|
|
|
|
url = 'https://open.bigmodel.cn/api/paas/v4/embeddings' |
|
|
headers = {'Content-Type': 'application/json', |
|
|
'Authorization': f'Bearer {apiToken}' |
|
|
} |
|
|
return await zhipu_chat(url, headers, body) |
|
|
except json.JSONDecodeError as e: |
|
|
raise HTTPException(status_code=400, detail=str(e)) |
|
|
|
|
|
else: |
|
|
chat_url = f"{baseurl}/duckchat/v1/chat" |
|
|
body = await req.json() |
|
|
headers = await get_xvqd() |
|
|
usermodel = body.get("model", "gpt-4o-mini") |
|
|
if "gpt" in usermodel.lower(): |
|
|
model = "gpt-4o-mini" |
|
|
elif "claude" in usermodel.lower(): |
|
|
model = "claude-3-haiku-20240307" |
|
|
elif "mixtral" in usermodel.lower(): |
|
|
model = "mistralai/Mixtral-8x7B-Instruct-v0.1" |
|
|
elif "llama" in usermodel.lower(): |
|
|
model = "meta-llama/Llama-3.3-70B-Instruct-Turbo" |
|
|
else: |
|
|
model = "gpt-4o-mini" |
|
|
contents = [] |
|
|
|
|
|
|
|
|
|
|
|
for message in body["messages"]: |
|
|
if not isinstance(message.get("content", ""), str): |
|
|
|
|
|
continue |
|
|
|
|
|
contents.append(f'{message["role"]}: {message["content"]}') |
|
|
|
|
|
merged_content = "\n".join(contents) |
|
|
if merged_content == "": |
|
|
merged_content = "hello" |
|
|
payload = { |
|
|
"model": model, |
|
|
"messages": [{ |
|
|
"role": "user", |
|
|
"content": merged_content |
|
|
}] |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if body.get("stream", False): |
|
|
return StreamingResponse(chat_chunk(payload, headers), media_type="text/event-stream") |
|
|
|
|
|
max_retries = 3 |
|
|
retry_delay = 2 |
|
|
for attempt in range(max_retries): |
|
|
try: |
|
|
async with httpx.AsyncClient() as client: |
|
|
|
|
|
resp = await client.post(chat_url, json=payload, headers=headers, timeout=60) |
|
|
resp.raise_for_status() |
|
|
if resp.status_code == 200: |
|
|
xvqd_headers = headers |
|
|
xvqd_headers["content-type"] = "application/json" |
|
|
xvqd_headers["x-vqd-4"] = resp.headers["x-vqd-4"] |
|
|
|
|
|
response_content = "" |
|
|
choices = [] |
|
|
chatid = "" |
|
|
data = {} |
|
|
async for line in resp.aiter_lines(): |
|
|
try: |
|
|
if line.startswith("data: [DONE]"): |
|
|
break |
|
|
except httpx.StreamClosed: |
|
|
|
|
|
break |
|
|
if line.startswith('data: '): |
|
|
chunk = line[6:] |
|
|
data = json.loads(chunk) |
|
|
|
|
|
if "id" in data: |
|
|
chatid = data["id"] |
|
|
chatid = chatid.replace("msg_", "chatcmpl-") |
|
|
response_content += data.get("message", "") |
|
|
else: |
|
|
continue |
|
|
choices.append({"message": {"role": "assistant", "content": response_content}}) |
|
|
total_chars = 0 |
|
|
for message in body.get("messages"): |
|
|
total_chars += len(message["content"]) |
|
|
usage = { |
|
|
"prompt_tokens": total_chars, |
|
|
"completion_tokens": len(response_content), |
|
|
"total_tokens": total_chars + len(response_content) |
|
|
} |
|
|
re_data = {"id": chatid, "object": "chat.completion", |
|
|
"created": data.get("created"), "model": usermodel, "choices": choices, "usage": usage} |
|
|
|
|
|
return JSONResponse(content=re_data,headers=xvqd_headers) |
|
|
except httpx.HTTPError as exc: |
|
|
|
|
|
print(f"Retrying after too many requests ({attempt + 1}/{max_retries})") |
|
|
await asyncio.sleep(retry_delay) |
|
|
headers = await get_xvqd() |
|
|
retry_delay *= 2 |
|
|
except Exception as e: |
|
|
|
|
|
print(f"Retrying after An unexpected error occurred: {e} ({attempt + 1}/{max_retries})") |
|
|
await asyncio.sleep(retry_delay) |
|
|
headers = await get_xvqd() |
|
|
retry_delay *= 2 |
|
|
raise HTTPException(status_code=500, detail=f"Failed to get xvqd") |
|
|
|
|
|
@app.get("/") |
|
|
async def read_root(): |
|
|
return {"Hello world!超高并发gpt-4o-mini,o3-mini,Claude3,GLM全系列,Llama 3.3 70B"} |
|
|
|
|
|
|
|
|
@app.get("/v1/models") |
|
|
async def read_models(): |
|
|
return { |
|
|
"object": "list", |
|
|
"data": [ |
|
|
{ |
|
|
"id": "o3-mini", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "gpt-4o-mini", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "claude-3-haiku-20240307", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "meta-llama/Llama-3.3-70B-Instruct-Turbo", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "Mixtral-8x7B-Instruct-v0.1", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "glm-4", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "glm-4-plus", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "glm-4v-plus", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "glm-4v-plus-0111", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "GLM-Zero-Preview", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "CogView-4", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "CogVideoX-2", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "GLM-4-Voice", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "CharGLM-4", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
{ |
|
|
"id": "Emohaa", |
|
|
"object": "model", |
|
|
"created": 1666666666, |
|
|
"owned_by": "system" |
|
|
}, |
|
|
] |
|
|
} |
|
|
|
|
|
|
|
|
@app.post("/api/paas/v4/chat/completions") |
|
|
async def zhipu_chat_completions(req: Request, authorized: str = Depends(auth)): |
|
|
try: |
|
|
|
|
|
body = await req.json() |
|
|
url = 'https://open.bigmodel.cn/api/paas/v4/chat/completions' |
|
|
headers = {'Content-Type': 'application/json', |
|
|
'Authorization': f'Bearer {apiToken}' |
|
|
} |
|
|
if body.get("stream", False): |
|
|
return StreamingResponse(zhipu_chunk(url, headers, body), media_type="text/event-stream") |
|
|
else: |
|
|
return await zhipu_chat(url, headers, body) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
except json.JSONDecodeError as e: |
|
|
raise HTTPException(status_code=400, detail=str(e)) |
|
|
except httpx.ReadTimeout: |
|
|
raise HTTPException(status_code=408, detail="Request Timeout") |
|
|
|
|
|
@app.post("/api/paas/v4/embeddings") |
|
|
async def zhipu_embeddings(req: Request, authorized: str = Depends(auth)): |
|
|
body = await req.json() |
|
|
url = 'https://open.bigmodel.cn/api/paas/v4/embeddings' |
|
|
headers = {'Content-Type': 'application/json', |
|
|
'Authorization': f'Bearer {apiToken}' |
|
|
} |
|
|
return await zhipu_chat(url, headers, body) |
|
|
|
|
|
|
|
|
def formatted_now(): |
|
|
|
|
|
now = datetime.now() |
|
|
return now.strftime("%Y-%m-%d %H:%M:%S") |
|
|
|
|
|
async def zhipu_chat(url, headers, body): |
|
|
while True: |
|
|
|
|
|
async with httpx.AsyncClient() as client: |
|
|
response = await client.post(url, headers=headers, json=body, timeout=120) |
|
|
if response.status_code == 401: |
|
|
schedule_token_update() |
|
|
continue |
|
|
break |
|
|
|
|
|
|
|
|
return response.json() |
|
|
|
|
|
|
|
|
async def zhipu_chunk(url, headers, body): |
|
|
async with httpx.AsyncClient() as client: |
|
|
|
|
|
while True: |
|
|
async with client.stream("POST", url, json=body, headers=headers, timeout=120) as response: |
|
|
if response.status_code == 401: |
|
|
schedule_token_update() |
|
|
continue |
|
|
async for line in response.aiter_text(): |
|
|
|
|
|
yield line |
|
|
break |
|
|
|
|
|
|
|
|
def get_deviceId(): |
|
|
|
|
|
|
|
|
characters = string.ascii_letters + string.digits |
|
|
global device_id |
|
|
device_id = ''.join(random.choices(characters, k=32)) |
|
|
print(f'{formatted_now()}: device_id: {device_id}') |
|
|
return device_id |
|
|
|
|
|
|
|
|
def get_token(): |
|
|
global device_id |
|
|
while True: |
|
|
url = f'https://api2.immersivetranslate.com/big-model/get-token?deviceId={device_id}' |
|
|
headers = {'Content-Type': 'application/json', |
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'} |
|
|
response = httpx.get(url, headers=headers) |
|
|
if response.status_code == 200: |
|
|
global apiToken |
|
|
apiToken = response.json().get('apiToken') |
|
|
print(f'apiToken: {apiToken}') |
|
|
return apiToken |
|
|
else: |
|
|
print(f'{formatted_now()} Error getting API token: {response.text}') |
|
|
device_id = get_deviceId() |
|
|
continue |
|
|
|
|
|
|
|
|
def get_external_ip(): |
|
|
try: |
|
|
response = httpx.get('https://ipinfo.io/json') |
|
|
response.raise_for_status() |
|
|
ip_info = response.json() |
|
|
ip_info2 = f"ip:{ip_info['ip']} city:{ip_info['city']} region:{ip_info['region']} country:{ip_info['country']} timezone:{ip_info['timezone']}" |
|
|
|
|
|
logging.error(ip_info2) |
|
|
logging.shutdown() |
|
|
return ip_info['ip'] |
|
|
except Exception as e: |
|
|
print(f"获取外网 IP 地址时出错: {e}") |
|
|
return None |
|
|
def schedule_token_update(): |
|
|
get_token() |
|
|
|
|
|
threading.Timer(300, schedule_token_update).start() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
log_config = { |
|
|
"version": 1, |
|
|
"disable_existing_loggers": False, |
|
|
"formatters": { |
|
|
"default": { |
|
|
"format": "%(asctime)s - %(levelname)s - %(message)s", |
|
|
}, |
|
|
}, |
|
|
"handlers": { |
|
|
"default": { |
|
|
"class": "logging.StreamHandler", |
|
|
"formatter": "default", |
|
|
}, |
|
|
}, |
|
|
"loggers": { |
|
|
"uvicorn": {"handlers": ["default"], "level": "INFO"}, |
|
|
"uvicorn.error": {"level": "INFO"}, |
|
|
"uvicorn.access": {"handlers": ["default"], "level": "INFO", "propagate": False}, |
|
|
}, |
|
|
} |
|
|
get_external_ip() |
|
|
import uvicorn |
|
|
|
|
|
schedule_token_update() |
|
|
|
|
|
uvicorn.run(app, host='0.0.0.0', port=7860, log_config=log_config) |
|
|
|