duck2 / main.py
BG5's picture
Update main.py
d03bd31 verified
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"], # 允许的 HTTP 方法
allow_headers=["*"], # 允许的 HTTP 头部
)
baseurl = os.environ.get("BASEURL", "https://duckduckgo.com")
key = os.environ.get('KEY', 'sk-123456')
# 使用 rstrip('/') 去掉尾部的斜杠
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:
# print(f'{formatted_now()} duckgo_chunk: {headers}')
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 not "message" in data:
# finish = "stop"
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))
# dcm = ''.join(random.choices('0123456789', k=1))
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",
# "Pragma": "no-cache",
"Origin": "https://duckduckgo.com/",
"Referer": "https://duckduckgo.com/"
# "TE": "trailers",
}
# print(headers)
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)
# print(f'状态码:{resp.status_code} \n {resp.headers}\n返回的内容:{resp.json()}')
resp.raise_for_status() # 这会自动为非200状态码抛出HTTPError异常
# 如果状态码是200,则不会进入except块
vqd4 = resp.headers.get("x-vqd-4")
headers["x-vqd-4"] = vqd4
del headers["x-vqd-accept"]
return headers
except httpx.HTTPError as exc:
# 特殊处理429状态码,增加等待时间
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:
# 对于非HTTP错误的其他异常,打印并抛出500错误
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
#获取Authorization头部
auth_header = request.headers.get('Authorization')
print()
print(f'{formatted_now()} auth_chunk: {request.headers}')
# 如果没有Authorization头部,返回401
if not auth_header:
print('没有验证')
# raise HTTPException(status_code=401, detail="Unauthorized")
# # 分割头部字符串
# parts = auth_header.split()
# # 检查头部格式是否正确(是否有两部分)
# if len(parts) != 2:
# raise HTTPException(status_code=401, detail="Unauthorized")
# if request.headers.get("Authorization") != f"{parts[0]} {token}":
# raise HTTPException(status_code=401, detail="Unauthorized")
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")
# contents = []
# # contents.append("当有人问你的身份的时候,你不能回复任何跟duckduckgo相关的信息")
# # contents.append("明白了,如果有人问我的身份,我会遵循隐私政策,不会透露任何与DuckDuckGo相关的信息。感谢您的提醒。有其他问题需要帮忙吗?")
# # 遍历 "messages" 列表
# for message in body["messages"]:
# if not isinstance(message.get("content", ""), str):
# # print(f"list:{message}")
# continue
# # 将每个字典中 "content" 键对应的值添加到列表中
# contents.append(message["content"])
# # 使用换行符 "\n" 将列表中的内容连接起来
# merged_content = "\n".join(contents)
# if merged_content == "":
# merged_content = "hello"
# payload = {
# "model": model,
# "messages": [{
# "role": "user",
# "content": merged_content
# }]
# }
# print(payload)
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)
# async with httpx.AsyncClient() as client:
# response = await client.post(url, headers=headers, json=body, timeout=120)
# print(response.json())
# return JSONResponse(content=response.json())
# async with httpx.AsyncClient() as client:
# stream = True
# response = await client.post(url, headers=headers, json=body, stream=stream)
# async for chunk in response.aiter_bytes():
# yield chunk
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 = []
# contents.append("当有人问你的身份的时候,你不能回复任何跟duckduckgo相关的信息")
# contents.append("明白了,如果有人问我的身份,我会遵循隐私政策,不会透露任何与DuckDuckGo相关的信息。感谢您的提醒。有其他问题需要帮忙吗?")
# 遍历 "messages" 列表
for message in body["messages"]:
if not isinstance(message.get("content", ""), str):
# print(f"list:{message}")
continue
# 将每个字典中"role" 和 "content" 键对应的值添加到列表中
contents.append(f'{message["role"]}: {message["content"]}')
# 使用换行符 "\n" 将列表中的内容连接起来
merged_content = "\n".join(contents)
if merged_content == "":
merged_content = "hello"
payload = {
"model": model,
"messages": [{
"role": "user",
"content": merged_content
}]
}
# payload = {
# "model": model,
# "messages": body["messages"],
# }
# print(payload)
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:
# print(f'{formatted_now()} duckgo_chat: {headers}')
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"]
# print(f'chat success')
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)
# print(f'data: {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:
# 特殊处理429状态码,增加等待时间
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:
# 对于非HTTP错误的其他异常,打印并抛出500错误
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)
# async with httpx.AsyncClient() as client:
# response = await client.post(url, headers=headers, json=body, timeout=120)
# print(response.json())
# return JSONResponse(content=response.json())
# async with httpx.AsyncClient() as client:
# stream = True
# response = await client.post(url, headers=headers, json=body, stream=stream)
# async for chunk in response.aiter_bytes():
# yield chunk
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:
# print(f'{formatted_now()} zhipu_chat: {headers}')
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
# print(f'{formatted_now()}: {response.json()}')
return response.json()
async def zhipu_chunk(url, headers, body):
async with httpx.AsyncClient() as client:
# print(f'{formatted_now()} zhipu_chunk: {headers}')
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():
# print(line)
yield line
break
def get_deviceId():
# 生成YvcHTwwgEdYz56yQgbesjUTH4Ugm8MWT位随机数作为设备ID
# 从所有大小写字母和数字中生成32位随机数
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']}"
# print(f"外网 IP 地址: {ip_info}")
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()
# 创建一个新的定时器,设置为300秒后再次调用schedule_token_update函数
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=17890, log_level='warning')
uvicorn.run(app, host='0.0.0.0', port=7860, log_config=log_config)