From 269b076de33b2963c8bfecb5433de442785485db Mon Sep 17 00:00:00 2001 From: LanQian <5499636+LanQian528@users.noreply.github.com> Date: Sat, 6 Apr 2024 15:05:57 +0800 Subject: [PATCH] fix repeat history --- Dockerfile | 13 ++----- README.md | 8 +++-- chat2api.py | 14 ++++---- chatgpt/ChatService.py | 79 +++++++++++++++-------------------------- requirements.txt | 2 +- utils/shared_session.py | 23 ------------ 6 files changed, 45 insertions(+), 94 deletions(-) delete mode 100644 utils/shared_session.py diff --git a/Dockerfile b/Dockerfile index a208da6..ffad672 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,10 @@ -FROM python:3.11-slim as builder - -WORKDIR /build - -COPY requirements.txt . -RUN pip install --no-cache-dir --prefix=/install -r requirements.txt - -FROM python:3.11-slim +FROM python:3.11-alpine WORKDIR /app -COPY --from=builder /install /usr/local +COPY . /app -COPY . . +RUN pip install --no-cache-dir -r requirements.txt EXPOSE 5005 diff --git a/README.md b/README.md index cea8048..732bf23 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # CHAT2API -免费的GPT3.5 api +🌟无需账号即可使用免费、无限的gpt3.5,目前只能转gpt3.5 -### 注:仅ip属地支持免登录使用ChatGpt可以使用 +🔍以假乱真,回复格式与真实api完全一致,支持max_tokens,stream等参数 ## Deploy @@ -52,6 +52,10 @@ curl --location 'http://127.0.0.1:5005/v1/chat/completions' \ "stream": true }' ``` +## 常见问题 + +- 当返回错误代码`403`时:这意味着当前IP地址被 CF 盾拦截,请尝试更换IP地址,或者在环境变量 `PROXY_URL` 中设置代理。 +- 来自`Xiaofei`的礼物:将环境变量设置为 `CHATGPT_BASE_URL=http://api.angelxf.me:8080/api` ,可无视CF盾和IP问题。 ## 高级设置 diff --git a/chat2api.py b/chat2api.py index bd3bb07..87e3f3c 100644 --- a/chat2api.py +++ b/chat2api.py @@ -1,12 +1,9 @@ import warnings -from fastapi import FastAPI, Request, Depends +from fastapi import FastAPI, Request from fastapi.responses import StreamingResponse, JSONResponse from chatgpt.ChatService import ChatService -from utils.shared_session import create_shared_async_session - -warnings.filterwarnings('ignore') app = FastAPI() @@ -15,13 +12,16 @@ chat_requirements_token_list = [] @app.post("/v1/chat/completions") -# async def send_conversation(request: Request, session=Depends(create_shared_async_session)): async def send_conversation(request: Request): - data = await request.json() - chat_service = ChatService() try: + data = await request.json() + except Exception: + return JSONResponse({"error": "Invalid JSON body"}, status_code=400) + try: + chat_service = ChatService() await chat_service.get_chat_requirements() except: + chat_service = ChatService() await chat_service.get_chat_requirements() chat_service.prepare_send_conversation(data) stream = data.get("stream", False) diff --git a/chatgpt/ChatService.py b/chatgpt/ChatService.py index d3c3b03..d095257 100644 --- a/chatgpt/ChatService.py +++ b/chatgpt/ChatService.py @@ -3,9 +3,8 @@ import random import string import time import uuid -from contextlib import aclosing -from curl_cffi import requests +import httpx from fastapi import HTTPException from api.chat_completions import num_tokens_from_messages, model_system_fingerprint, model_proxy, \ @@ -18,27 +17,22 @@ async def stream_response(response, model, max_tokens): chat_id = f"chatcmpl-{''.join(random.choice(string.ascii_letters + string.digits) for _ in range(29))}" system_fingerprint_list = model_system_fingerprint.get(model, None) system_fingerprint = random.choice(system_fingerprint_list) if system_fingerprint_list else None + created_time = int(time.time()) completion_tokens = -1 len_last_content = 0 end = False - status = None async for chunk in response.aiter_lines(): if end: yield f"data: [DONE]\n\n" break try: - chunk = chunk.decode("utf-8") if chunk == "data: [DONE]": yield f"data: [DONE]\n\n" elif not chunk.startswith("data: "): continue else: chunk_old_data = json.loads(chunk[6:]) - if chunk_old_data["message"]["status"] == "in_progress": - status = "in_progress" - if status != "in_progress": - continue - if not chunk_old_data["message"]["author"]["role"] == "assistant": + if not chunk_old_data["message"]["status"] == "in_progress" and not chunk_old_data["message"]["metadata"].get("finish_details", {}): continue content = chunk_old_data["message"]["content"]["parts"][0] if not content: @@ -47,7 +41,7 @@ async def stream_response(response, model, max_tokens): delta = {"content": content[len_last_content:]} len_last_content = len(content) finish_reason = None - if chunk_old_data["message"]["end_turn"]: + if chunk_old_data["message"]["metadata"].get("finish_details", {}): delta = {} finish_reason = "stop" end = True @@ -55,13 +49,10 @@ async def stream_response(response, model, max_tokens): delta = {} finish_reason = "length" end = True - if completion_tokens > max_tokens: - yield f"data: [DONE]\n\n" - break chunk_new_data = { "id": chat_id, "object": "chat.completion.chunk", - "created": int(time.time()), + "created": created_time, "model": model, "choices": [ { @@ -83,9 +74,10 @@ async def stream_response(response, model, max_tokens): async def chat_response(resp, model, prompt_tokens, max_tokens): last_resp = None - for i in resp: + for i in reversed(resp): if i != "" and i != "data: [DONE]" and i.startswith("data: "): last_resp = i + break resp = json.loads(last_resp[6:]) chat_id = f"chatcmpl-{''.join(random.choice(string.ascii_letters + string.digits) for _ in range(29))}" @@ -143,11 +135,7 @@ def api_messages_to_chat(api_messages): class ChatService: def __init__(self, session=None): - self.session = session - self.proxies = { - "http": proxy_url, - "https": proxy_url, - } + self.s = httpx.AsyncClient(proxies=proxy_url) self.user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" self.oai_device_id = str(uuid.uuid4()) self.chat_token = None @@ -170,16 +158,14 @@ class ChatService: 'sec-fetch-site': 'same-origin', 'user-agent': self.user_agent } - # r = await self.session.post(url, headers=headers, json={}, proxies=self.proxies) - async with requests.AsyncSession() as s: - r = await s.post(url, headers=headers, json={}, proxies=self.proxies) - if r.status_code != 200: - raise HTTPException(status_code=r.status_code, detail=r.text) - else: - self.chat_token = r.json().get('token') - if not self.chat_token: - raise HTTPException(status_code=500, detail="Chat token not found") - return self.chat_token + r = await self.s.post(url, headers=headers, json={}) + if r.status_code != 200: + raise HTTPException(status_code=r.status_code, detail=r.text) + else: + self.chat_token = r.json().get('token') + if not self.chat_token: + raise HTTPException(status_code=500, detail="Chat token not found") + return self.chat_token def prepare_send_conversation(self, data): self.headers = { @@ -225,21 +211,12 @@ class ChatService: model = model_proxy.get(model, model) max_tokens = data.get("max_tokens", 2147483647) - # async with aclosing(await self.session.post(url, headers=self.headers, json=self.chat_request, proxies=self.proxies, stream=True)) as r: - - # async with self.session.stream("POST", url, headers=self.headers, json=self.chat_request, proxies=self.proxies) as r: - # async for chunk in r.aiter_content(): - # print("Status: ", r.status_code) - # assert r.status_code == 200 - # print("CHUNK", chunk) - # yield chunk - - async with requests.AsyncSession() as s: - r = await s.post(url, headers=self.headers, json=self.chat_request, proxies=self.proxies, stream=True) - if r.status_code != 200: - raise HTTPException(status_code=r.status_code, detail=r.text) - async for chunk in stream_response(r, model, max_tokens): - yield chunk + r = await self.s.send( + self.s.build_request("POST", url, headers=self.headers, json=self.chat_request, timeout=600), stream=True) + if r.status_code != 200: + raise HTTPException(status_code=r.status_code, detail=r.text) + async for chunk in stream_response(r, model, max_tokens): + yield chunk async def send_conversation(self, data): url = f'{chatgpt_base_url}/conversation' @@ -249,9 +226,9 @@ class ChatService: prompt_tokens = num_tokens_from_messages(api_messages, model) max_tokens = data.get("max_tokens", 2147483647) - async with requests.AsyncSession() as s: - r = await s.post(url, headers=self.headers, json=self.chat_request, proxies=self.proxies, stream=True) - if r.status_code != 200: - raise HTTPException(status_code=r.status_code, detail=r.text) - resp = (await r.atext()).split("\n") - return await chat_response(resp, model, prompt_tokens, max_tokens) + r = await self.s.send( + self.s.build_request("POST", url, headers=self.headers, json=self.chat_request, timeout=600), stream=False) + if r.status_code != 200: + raise HTTPException(status_code=r.status_code, detail=r.text) + resp = r.text.split("\n") + return await chat_response(resp, model, prompt_tokens, max_tokens) diff --git a/requirements.txt b/requirements.txt index 1121e13..f541fe0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ fastapi -curl_cffi +httpx uvicorn tiktoken \ No newline at end of file diff --git a/utils/shared_session.py b/utils/shared_session.py deleted file mode 100644 index 0db1e7e..0000000 --- a/utils/shared_session.py +++ /dev/null @@ -1,23 +0,0 @@ -import random -import time - -from curl_cffi import CurlHttpVersion, requests -from curl_cffi.requests import AsyncSession - - -async def create_shared_async_session(): - browsers = ["chrome120", "chrome99_android"] - selected_browsers = random.choice(browsers) - async with AsyncSession( - impersonate=selected_browsers, - http_version=CurlHttpVersion.V2TLS, timeout=600) as session: - yield session - - -def create_shared_session(): - random.seed(int(time.time())) - browsers = ["chrome120", "chrome99_android"] - selected_browsers = random.choice(browsers) - session = requests.Session(impersonate=selected_browsers, - http_version=CurlHttpVersion.V2TLS, timeout=600) - return session