mirror of
https://github.com/lanqian528/chat2api.git
synced 2026-06-13 21:02:46 +08:00
op codes
This commit is contained in:
parent
7f45621bce
commit
f8d45dd16b
@ -6,4 +6,5 @@ ARKOSE_TOKEN_URL=https://arkose.example.com/token
|
||||
POW_DIFFICULTY=000032
|
||||
RETRY_TIMES=3
|
||||
ENABLE_GATEWAY=true
|
||||
CONVERSATION_ONLY=false
|
||||
CONVERSATION_ONLY=false
|
||||
ENABLE_LIMIT=false
|
||||
2
.github/workflows/build_docker.yml
vendored
2
.github/workflows/build_docker.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
images: lanqian528/chat2api
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=raw,value=v1.1.4
|
||||
type=raw,value=v1.1.5
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
|
||||
31
README.md
31
README.md
@ -50,25 +50,22 @@ https://t.me/chat2api
|
||||
|
||||
## 环境变量
|
||||
|
||||
每个环境变量都有默认值,如果不懂环境变量的含义,请不要设置,更不要传空值
|
||||
每个环境变量都有默认值,如果不懂环境变量的含义,请不要设置,更不要传空值,字符串无需引号
|
||||
|
||||
```
|
||||
# 安全相关
|
||||
API_PREFIX=your_prefix // API前缀密码,不设置容易被人访问,设置后需请求 /your_prefix/v1/chat/completions
|
||||
AUTHORIZATION=sk-xxxxxxxx,sk-yyyyyyyy // 先到 /tokens 上传ac或rt,请求时传入 AUTHORIZATION 可多账号轮询
|
||||
| 分类 | 变量名 | 示例值 | 描述 |
|
||||
|------|-------------------|-------------------------------------|-----------------------------------------------------------|
|
||||
| 安全相关 | API_PREFIX | your_prefix | API前缀密码,不设置容易被人访问,设置后需请求 /your_prefix/v1/chat/completions |
|
||||
| | AUTHORIZATION | sk-xxxxxxxx, sk-yyyyyyyy | 为使用多账号轮询 Tokens 设置的授权,英文逗号分隔 |
|
||||
| 请求相关 | CHATGPT_BASE_URL | https://chatgpt.com | ChatGPT网关地址,设置后会改变请求的网站,多个网关用逗号分隔 |
|
||||
| | PROXY_URL | your_first_proxy, your_second_proxy | 代理URL,多个代理用逗号分隔 |
|
||||
| | ARKOSE_TOKEN_URL | https://arkose.example.com/token | 获取Arkose token的地址 |
|
||||
| 功能相关 | HISTORY_DISABLED | true | 是否不保存聊天记录并返回 conversation_id |
|
||||
| | POW_DIFFICULTY | 00003a | 要解决的工作量证明难度 |
|
||||
| | RETRY_TIMES | 3 | 出错重试次数 |
|
||||
| | ENABLE_GATEWAY | true | 是否启用网关模式(WEBUI) |
|
||||
| | CONVERSATION_ONLY | false | 是否直接使用对话接口 |
|
||||
| | ENABLE_LIMIT | true | 开启后不尝试突破官方次数限制,尽可能防止封号 |
|
||||
|
||||
# 请求相关
|
||||
CHATGPT_BASE_URL=https://chatgpt.com // ChatGPT网关地址,设置后会改变请求的网站,多个网关用逗号分隔
|
||||
PROXY_URL=your_first_proxy, your_second_proxy // 代理url,多个代理用逗号分隔
|
||||
ARKOSE_TOKEN_URL=https://arkose.example.com/token // 获取Arkose token的地址,上文有提供说明
|
||||
|
||||
# 功能相关
|
||||
HISTORY_DISABLED=true // 是否不保存聊天记录并返回 conversation_id,true为不保存且不返回
|
||||
POW_DIFFICULTY=000032 // 要解决的工作量证明难度,字符串越小,计算时间越长,建议000032
|
||||
RETRY_TIMES=3 // 出错重试次数
|
||||
ENABLE_GATEWAY=true // 是否启用网关模式(WEBUI),true为启用
|
||||
CONVERSATION_ONLY=false // 使用的网关支持在服务端处理pow和arkose时可以开启,开启则直接使用对话接口
|
||||
```
|
||||
|
||||
## 部署
|
||||
|
||||
|
||||
15
app.py
15
app.py
@ -1,8 +1,4 @@
|
||||
import uvicorn
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
|
||||
from chat2api import app
|
||||
from chatgpt.chatLimit import clean_dict
|
||||
|
||||
log_config = uvicorn.config.LOGGING_CONFIG
|
||||
default_format = "%(asctime)s | %(levelname)s | %(message)s"
|
||||
@ -10,15 +6,4 @@ access_format = r'%(asctime)s | %(levelname)s | %(client_addr)s: %(request_line)
|
||||
log_config["formatters"]["default"]["fmt"] = default_format
|
||||
log_config["formatters"]["access"]["fmt"] = access_format
|
||||
|
||||
scheduler = BackgroundScheduler()
|
||||
|
||||
|
||||
# 用于自动更新限制access_token的字典
|
||||
# 避免过多access_token没有销毁,而堆积
|
||||
@app.on_event("startup")
|
||||
async def app_start():
|
||||
scheduler.add_job(id='updateLimit_run', func=clean_dict, trigger='cron', hour=3, minute=0)
|
||||
scheduler.start()
|
||||
|
||||
|
||||
uvicorn.run("chat2api:app", host="0.0.0.0", port=5005)
|
||||
|
||||
20
chat2api.py
20
chat2api.py
@ -1,6 +1,7 @@
|
||||
import types
|
||||
import warnings
|
||||
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from fastapi import FastAPI, Request, Depends, HTTPException, Form
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import HTMLResponse
|
||||
@ -9,7 +10,7 @@ from fastapi.templating import Jinja2Templates
|
||||
from starlette.background import BackgroundTask
|
||||
|
||||
from chatgpt.ChatService import ChatService
|
||||
from chatgpt.chatLimit import handle_request_limit
|
||||
from chatgpt.chatLimit import handle_request_limit, clean_dict
|
||||
from chatgpt.reverseProxy import chatgpt_reverse_proxy
|
||||
from utils.Logger import logger
|
||||
from utils.authorization import verify_token, token_list
|
||||
@ -21,7 +22,7 @@ warnings.filterwarnings("ignore")
|
||||
|
||||
app = FastAPI()
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
scheduler = BackgroundScheduler()
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
@ -31,9 +32,15 @@ app.add_middleware(
|
||||
)
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def app_start():
|
||||
scheduler.add_job(id='updateLimit_run', func=clean_dict, trigger='cron', hour=3, minute=0)
|
||||
scheduler.start()
|
||||
|
||||
|
||||
async def to_send_conversation(request_data, access_token):
|
||||
chat_service = ChatService(access_token)
|
||||
try:
|
||||
chat_service = ChatService(access_token)
|
||||
await chat_service.set_dynamic_data(request_data)
|
||||
await chat_service.get_chat_requirements()
|
||||
return chat_service
|
||||
@ -80,12 +87,11 @@ async def send_conversation(request: Request, token=Depends(verify_token)):
|
||||
@app.get(f"/{api_prefix}/tokens" if api_prefix else "/tokens", response_class=HTMLResponse)
|
||||
async def upload_html(request: Request):
|
||||
tokens_count = len(token_list)
|
||||
return templates.TemplateResponse("tokens.html",
|
||||
{"request": request, "api_prefix": api_prefix, "tokens_count": tokens_count})
|
||||
return templates.TemplateResponse("tokens.html", {"request": request, "api_prefix": api_prefix, "tokens_count": tokens_count})
|
||||
|
||||
|
||||
@app.post(f"/{api_prefix}/tokens/upload" if api_prefix else "/tokens/upload")
|
||||
async def upload_post(request: Request, text: str = Form(...)):
|
||||
async def upload_post(text: str = Form(...)):
|
||||
lines = text.split("\n")
|
||||
for line in lines:
|
||||
if line.strip() and not line.startswith("#"):
|
||||
@ -98,7 +104,7 @@ async def upload_post(request: Request, text: str = Form(...)):
|
||||
|
||||
|
||||
@app.post(f"/{api_prefix}/tokens/clear" if api_prefix else "/tokens/clear")
|
||||
async def upload_post(request: Request):
|
||||
async def upload_post():
|
||||
token_list.clear()
|
||||
with open("data/token.txt", "w", encoding="utf-8") as f:
|
||||
pass
|
||||
|
||||
@ -80,8 +80,7 @@ class ChatService:
|
||||
self.base_url = self.host_url + "/backend-anon"
|
||||
|
||||
await get_dpl(self)
|
||||
self.s.session.cookies.set("__Secure-next-auth.callback-url", "https%3A%2F%2Fchatgpt.com;",
|
||||
domain=self.host_url.split("://")[1], secure=True)
|
||||
self.s.session.cookies.set("__Secure-next-auth.callback-url", "https%3A%2F%2Fchatgpt.com;", domain=self.host_url.split("://")[1], secure=True)
|
||||
|
||||
async def get_wss_url(self):
|
||||
url = f'{self.base_url}/register-websocket'
|
||||
@ -126,11 +125,9 @@ class ChatService:
|
||||
if proofofwork_required:
|
||||
proofofwork_diff = proofofwork.get("difficulty")
|
||||
if proofofwork_diff <= pow_difficulty:
|
||||
raise HTTPException(status_code=403,
|
||||
detail=f"Proof of work difficulty too high: {proofofwork_diff}")
|
||||
raise HTTPException(status_code=403, detail=f"Proof of work difficulty too high: {proofofwork_diff}")
|
||||
proofofwork_seed = proofofwork.get("seed")
|
||||
self.proof_token, solved = await run_in_threadpool(get_answer_token, proofofwork_seed,
|
||||
proofofwork_diff, config)
|
||||
self.proof_token, solved = await run_in_threadpool(get_answer_token, proofofwork_seed, proofofwork_diff, config)
|
||||
if not solved:
|
||||
raise HTTPException(status_code=403, detail="Failed to solve proof of work")
|
||||
|
||||
@ -246,8 +243,7 @@ class ChatService:
|
||||
raise HTTPException(status_code=e.status_code, detail=str(e))
|
||||
url = f'{self.base_url}/conversation'
|
||||
stream = self.data.get("stream", False)
|
||||
r = await self.s.post_stream(url, headers=self.chat_headers, json=self.chat_request, timeout=10,
|
||||
stream=True)
|
||||
r = await self.s.post_stream(url, headers=self.chat_headers, json=self.chat_request, timeout=10, stream=True)
|
||||
if r.status_code != 200:
|
||||
rtext = await r.atext()
|
||||
if "application/json" == r.headers.get("Content-Type", ""):
|
||||
@ -266,9 +262,7 @@ class ChatService:
|
||||
if "text/event-stream" in content_type and stream:
|
||||
return stream_response(self, r.aiter_lines(), self.target_model, self.max_tokens)
|
||||
elif "text/event-stream" in content_type and not stream:
|
||||
return await format_not_stream_response(
|
||||
stream_response(self, r.aiter_lines(), self.target_model, self.max_tokens), self.prompt_tokens,
|
||||
self.max_tokens, self.target_model)
|
||||
return await format_not_stream_response(stream_response(self, r.aiter_lines(), self.target_model, self.max_tokens), self.prompt_tokens, self.max_tokens, self.target_model)
|
||||
elif "application/json" in content_type:
|
||||
rtext = await r.atext()
|
||||
resp = json.loads(rtext)
|
||||
@ -283,9 +277,7 @@ class ChatService:
|
||||
if stream and isinstance(wss_r, types.AsyncGeneratorType):
|
||||
return stream_response(self, wss_r, self.target_model, self.max_tokens)
|
||||
else:
|
||||
return await format_not_stream_response(
|
||||
stream_response(self, wss_r, self.target_model, self.max_tokens), self.prompt_tokens,
|
||||
self.max_tokens, self.target_model)
|
||||
return await format_not_stream_response(stream_response(self, wss_r, self.target_model, self.max_tokens), self.prompt_tokens, self.max_tokens, self.target_model)
|
||||
finally:
|
||||
if not isinstance(wss_r, types.AsyncGeneratorType):
|
||||
await self.ws.close()
|
||||
|
||||
@ -3,23 +3,20 @@ import time
|
||||
from datetime import datetime
|
||||
from utils.Logger import logger
|
||||
|
||||
# 开启线程锁
|
||||
lock = threading.Lock()
|
||||
# 对于密钥的限制
|
||||
limit_access_token = {}
|
||||
|
||||
|
||||
def check_isLimit(detail, access_token):
|
||||
if detail.get('clears_in'):
|
||||
clearTime = time.time() + detail.get('clears_in')
|
||||
initial_access_list(access_token, clearTime)
|
||||
clear_time = time.time() + detail.get('clears_in')
|
||||
initial_access_list(access_token, clear_time)
|
||||
|
||||
|
||||
def initial_access_list(key, clear_time):
|
||||
with lock:
|
||||
limit_access_token[key] = clear_time
|
||||
logger.info(
|
||||
f"{key[:40]}: Reached 429 limit, will be cleared at {datetime.fromtimestamp(clear_time).replace(second=0, microsecond=0)}")
|
||||
logger.info(f"{key[:40]}: Reached 429 limit, will be cleared at {datetime.fromtimestamp(clear_time).replace(second=0, microsecond=0)}")
|
||||
|
||||
|
||||
def remove_refresh_list(key):
|
||||
@ -40,20 +37,18 @@ async def handle_request_limit(request_data, access_token):
|
||||
remove_refresh_list(access_token)
|
||||
else:
|
||||
clear_date = datetime.fromtimestamp(limit_time).replace(second=0, microsecond=0)
|
||||
logger.info(f"Request limit exceeded. "
|
||||
f"You can continue with the default model now, "
|
||||
f"or try again after {clear_date}")
|
||||
return f"Request limit exceeded. You can continue with the default model now, or try again after {clear_date}"
|
||||
result = f"Request limit exceeded. You can continue with the default model now, or try again after {clear_date}"
|
||||
logger.info(result)
|
||||
return result
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return None
|
||||
|
||||
|
||||
# 清理函数,用于删除长时间未使用的键值对
|
||||
def clean_dict():
|
||||
logger.info(f"==========================================")
|
||||
logger.info("开始执行清理过期的limit_access_token......")
|
||||
logger.info("-" * 50)
|
||||
logger.info("Start to clean limit_access_token......")
|
||||
current_time = time.time()
|
||||
keys_to_remove = [key for key, clear_time in limit_access_token.items() if clear_time < current_time]
|
||||
for key in keys_to_remove:
|
||||
|
||||
@ -7,4 +7,4 @@ websockets
|
||||
pillow
|
||||
pybase64
|
||||
jinja2
|
||||
APScheduler
|
||||
APScheduler
|
||||
@ -28,7 +28,7 @@ pow_difficulty = os.getenv('POW_DIFFICULTY', '000032')
|
||||
retry_times = int(os.getenv('RETRY_TIMES', 3))
|
||||
enable_gateway = is_true(os.getenv('ENABLE_GATEWAY', True))
|
||||
conversation_only = is_true(os.getenv('CONVERSATION_ONLY', False))
|
||||
enable_limit = is_true(os.getenv('ENABLE_LIMIT', False))
|
||||
enable_limit = is_true(os.getenv('ENABLE_LIMIT', True))
|
||||
limit_status_code = os.getenv('LIMIT_STATUS_CODE', 429)
|
||||
|
||||
authorization_list = authorization.split(',') if authorization else []
|
||||
@ -37,7 +37,7 @@ arkose_token_url_list = arkose_token_url.split(',') if arkose_token_url else []
|
||||
proxy_url_list = proxy_url.split(',') if proxy_url else []
|
||||
|
||||
logger.info("-" * 60)
|
||||
logger.info("Chat2Api v1.1.4 | https://github.com/lanqian528/chat2api")
|
||||
logger.info("Chat2Api v1.1.5 | https://github.com/lanqian528/chat2api")
|
||||
logger.info("-" * 60)
|
||||
logger.info("Environment variables:")
|
||||
logger.info("API_PREFIX: " + str(api_prefix))
|
||||
@ -51,5 +51,4 @@ logger.info("RETRY_TIMES: " + str(retry_times))
|
||||
logger.info("ENABLE_GATEWAY: " + str(enable_gateway))
|
||||
logger.info("CONVERSATION_ONLY: " + str(conversation_only))
|
||||
logger.info("ENABLE_LIMIT: " + str(enable_limit))
|
||||
logger.info("LIMIT_STATUS_CODE: " + str(limit_status_code))
|
||||
logger.info("-" * 60)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user