mirror of
https://github.com/lanqian528/chat2api.git
synced 2026-06-19 21:05:19 +08:00
323 lines
13 KiB
Python
323 lines
13 KiB
Python
import json
|
|
import random
|
|
import string
|
|
import time
|
|
import uuid
|
|
|
|
from fastapi import HTTPException
|
|
|
|
from api.chat_completions import num_tokens_from_messages, model_system_fingerprint, model_proxy, \
|
|
split_tokens_from_content
|
|
from utils.Logger import Logger
|
|
from utils.config import history_disabled, free35_base_url_list, proxy_url_list
|
|
from utils.Client import Client
|
|
|
|
moderation_message = "I'm sorry, I cannot provide or engage in any content related to pornography, violence, or any unethical material. If you have any other questions or need assistance, please feel free to let me know. I'll do my best to provide support and assistance."
|
|
|
|
|
|
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
|
|
async for chunk in response.aiter_lines():
|
|
chunk = chunk.decode("utf-8")
|
|
if end:
|
|
yield "data: [DONE]\n\n"
|
|
break
|
|
try:
|
|
if chunk == "data: [DONE]":
|
|
yield "data: [DONE]\n\n"
|
|
elif not chunk.startswith("data: "):
|
|
continue
|
|
else:
|
|
chunk_old_data = json.loads(chunk[6:])
|
|
finish_reason = None
|
|
if chunk_old_data.get("type") == "moderation":
|
|
delta = {"role": "assistant", "content": moderation_message}
|
|
finish_reason = "moderation"
|
|
end = True
|
|
elif chunk_old_data.get("message").get("status") == "in_progress":
|
|
content = chunk_old_data["message"]["content"]["parts"][0]
|
|
if not content:
|
|
delta = {"role": "assistant", "content": ""}
|
|
else:
|
|
delta = {"content": content[len_last_content:]}
|
|
len_last_content = len(content)
|
|
if completion_tokens >= max_tokens:
|
|
delta = {}
|
|
finish_reason = "length"
|
|
end = True
|
|
elif chunk_old_data.get("message").get("metadata").get("finish_details"):
|
|
delta = {}
|
|
finish_reason = "stop"
|
|
end = True
|
|
else:
|
|
continue
|
|
chunk_new_data = {
|
|
"id": chat_id,
|
|
"object": "chat.completion.chunk",
|
|
"created": created_time,
|
|
"model": model,
|
|
"choices": [
|
|
{
|
|
"index": 0,
|
|
"delta": delta,
|
|
"logprobs": None,
|
|
"finish_reason": finish_reason
|
|
}
|
|
],
|
|
"system_fingerprint": system_fingerprint
|
|
}
|
|
completion_tokens += 1
|
|
yield f"data: {json.dumps(chunk_new_data)}\n\n"
|
|
except Exception:
|
|
Logger.error(f"Error: {chunk}")
|
|
continue
|
|
|
|
|
|
async def chat_response(resp, model, prompt_tokens, max_tokens):
|
|
"""
|
|
组装对话响应
|
|
:param resp: 响应数据
|
|
:param model: 模型
|
|
:param prompt_tokens: prompt token数
|
|
:param max_tokens: 最大token数量
|
|
:return:
|
|
"""
|
|
last_resp = None
|
|
for i in reversed(resp):
|
|
if i != "data: [DONE]" and i.startswith("data: "):
|
|
try:
|
|
last_resp = json.loads(i[6:])
|
|
if not last_resp.get("message"):
|
|
raise
|
|
break
|
|
except Exception:
|
|
Logger.error(f"Error: {i}")
|
|
continue
|
|
usage, system_fingerprint, finish_reason, message = await init_param(last_resp, max_tokens, model, prompt_tokens)
|
|
return {
|
|
"id": f"chatcmpl-{''.join(random.choice(string.ascii_letters + string.digits) for _ in range(29))}",
|
|
"object": "chat.completion",
|
|
"created": int(time.time()),
|
|
"model": model,
|
|
"choices": [
|
|
{
|
|
"index": 0,
|
|
"message": message,
|
|
"logprobs": None,
|
|
"finish_reason": finish_reason
|
|
}
|
|
],
|
|
"usage": usage,
|
|
"system_fingerprint": system_fingerprint
|
|
}
|
|
|
|
|
|
async def init_param(last_resp, max_tokens, model, prompt_tokens):
|
|
"""
|
|
组装参数
|
|
:param last_resp: 最终响应
|
|
:param max_tokens: 最大token数量
|
|
:param model: 模型
|
|
:param prompt_tokens: prompt token数
|
|
:return: 对应参数
|
|
"""
|
|
if last_resp.get("type") == "moderation":
|
|
message_content = moderation_message
|
|
completion_tokens = 53
|
|
finish_reason = "stop"
|
|
else:
|
|
message_content = last_resp["message"]["content"]["parts"][0]
|
|
message_content, completion_tokens, finish_reason = split_tokens_from_content(message_content, max_tokens,
|
|
model)
|
|
message = {
|
|
"role": "assistant",
|
|
"content": message_content,
|
|
}
|
|
usage = {
|
|
"prompt_tokens": prompt_tokens,
|
|
"completion_tokens": completion_tokens,
|
|
"total_tokens": prompt_tokens + completion_tokens
|
|
}
|
|
system_fingerprint_list = model_system_fingerprint.get(model, None)
|
|
system_fingerprint = random.choice(system_fingerprint_list) if system_fingerprint_list else None
|
|
return usage, system_fingerprint, finish_reason, message
|
|
|
|
|
|
def api_messages_to_chat(api_messages):
|
|
chat_messages = []
|
|
for api_message in api_messages:
|
|
role = api_message.get('role')
|
|
content = api_message.get('content')
|
|
chat_message = {
|
|
"id": f"{uuid.uuid4()}",
|
|
"author": {"role": role},
|
|
"content": {"content_type": "text", "parts": [content]},
|
|
"metadata": {}
|
|
}
|
|
chat_messages.append(chat_message)
|
|
return chat_messages
|
|
|
|
|
|
class ChatService:
|
|
def __init__(self, data, access_token=None):
|
|
self.proxy_url = random.choice(proxy_url_list) if proxy_url_list else None
|
|
self.proxies = {
|
|
"http": self.proxy_url,
|
|
"https": self.proxy_url
|
|
} if self.proxy_url else None
|
|
self.s = Client(proxy=self.proxies)
|
|
self.free35_base_url = random.choice(free35_base_url_list)
|
|
self.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 Edg/123.0.0.0"
|
|
self.access_token = access_token
|
|
self.oai_device_id = str(uuid.uuid4())
|
|
self.chat_token = None
|
|
|
|
self.data = data
|
|
self.model = self.data.get("model", "gpt-3.5-turbo-0125")
|
|
self.api_messages = self.data.get("messages", [])
|
|
self.prompt_tokens = num_tokens_from_messages(self.api_messages, self.model)
|
|
self.max_tokens = self.data.get("max_tokens", 2147483647)
|
|
|
|
self.headers = None
|
|
self.chat_request = None
|
|
|
|
async def get_chat_requirements(self):
|
|
url = f'{self.free35_base_url}/sentinel/chat-requirements'
|
|
headers = {
|
|
'Accept': '*/*',
|
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
'Content-Type': 'application/json',
|
|
'Oai-Device-Id': self.oai_device_id,
|
|
'Oai-Language': 'en-US',
|
|
'Origin': 'https://chat.openai.com',
|
|
'Referer': 'https://chat.openai.com/',
|
|
'Sec-Fetch-Dest': 'empty',
|
|
'Sec-Fetch-Mode': 'cors',
|
|
'Sec-Fetch-Site': 'same-origin',
|
|
'User-Agent': self.user_agent
|
|
}
|
|
if self.access_token:
|
|
headers['Authorization'] = f'Bearer {self.access_token}'
|
|
try:
|
|
r = await self.s.post(url, headers=headers, json={})
|
|
if r.status_code == 200:
|
|
resp = r.json()
|
|
|
|
arkose = resp.get('arkose', {})
|
|
arkose_required = arkose.get('required')
|
|
if arkose_required:
|
|
arkose_dx = arkose.get("dx")
|
|
raise HTTPException(status_code=403, detail="Arkose required")
|
|
|
|
turnstile = resp.get('turnstile', {})
|
|
turnstile_required = turnstile.get('required')
|
|
if turnstile_required:
|
|
raise HTTPException(status_code=403, detail="Turnstile required")
|
|
|
|
self.chat_token = resp.get('token')
|
|
if not self.chat_token:
|
|
raise HTTPException(status_code=502, detail=f"Failed to get chat token: {r.text}")
|
|
return self.chat_token
|
|
else:
|
|
if "application/json" == r.headers.get("Content-Type", ""):
|
|
detail = r.json().get("detail", r.json())
|
|
else:
|
|
detail = r.content
|
|
|
|
if r.status_code == 403:
|
|
raise HTTPException(status_code=r.status_code, detail="cf-please-wait")
|
|
elif r.status_code == 429:
|
|
raise HTTPException(status_code=r.status_code, detail="rate-limit")
|
|
raise HTTPException(status_code=r.status_code, detail=detail)
|
|
|
|
except HTTPException as e:
|
|
raise HTTPException(status_code=e.status_code, detail=e.detail)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
def prepare_send_conversation(self):
|
|
self.headers = {
|
|
'Accept': 'text/event-stream',
|
|
'Accept-Encoding': 'gzip, deflate, br',
|
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
'Content-Type': 'application/json',
|
|
'Oai-Device-Id': self.oai_device_id,
|
|
'Oai-Language': 'en-US',
|
|
'Openai-Sentinel-Chat-Requirements-Token': self.chat_token,
|
|
'Origin': 'https://chat.openai.com',
|
|
'Referer': 'https://chat.openai.com/',
|
|
'Sec-Ch-Ua': '"Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
|
|
'Sec-Ch-Ua-Mobile': '?0',
|
|
'Sec-Ch-Ua-Platform': '"Windows"',
|
|
'Sec-Fetch-Dest': 'empty',
|
|
'Sec-Fetch-Mode': 'cors',
|
|
'Sec-Fetch-Site': 'same-origin',
|
|
'User-Agent': self.user_agent
|
|
}
|
|
if self.access_token:
|
|
self.headers['Authorization'] = f'Bearer {self.access_token}'
|
|
chat_messages = api_messages_to_chat(self.api_messages)
|
|
if "gpt-4" in self.data.get("model"):
|
|
model = "gpt-4"
|
|
else:
|
|
model = "text-davinci-002-render-sha"
|
|
parent_message_id = f"{uuid.uuid4()}"
|
|
websocket_request_id = f"{uuid.uuid4()}"
|
|
self.chat_request = {
|
|
"action": "next",
|
|
"messages": chat_messages,
|
|
"parent_message_id": parent_message_id,
|
|
"model": model,
|
|
"timezone_offset_min": -480,
|
|
"suggestions": [],
|
|
"history_and_training_disabled": history_disabled,
|
|
"conversation_mode": {"kind": "primary_assistant"},
|
|
"force_paragen": False,
|
|
"force_paragen_model_slug": "",
|
|
"force_nulligen": False,
|
|
"force_rate_limit": False,
|
|
"websocket_request_id": websocket_request_id,
|
|
}
|
|
return self.chat_request
|
|
|
|
async def send_conversation_for_stream(self):
|
|
url = f'{self.free35_base_url}/conversation'
|
|
model = model_proxy.get(self.model, self.model)
|
|
r = await self.s.post(url, headers=self.headers, json=self.chat_request, timeout=600, stream=True)
|
|
if r.status_code == 200:
|
|
return stream_response(r, model, self.max_tokens)
|
|
else:
|
|
rtext = await r.atext()
|
|
if "application/json" == r.headers.get("Content-Type", ""):
|
|
detail = json.loads(rtext).get("detail", json.loads(rtext))
|
|
else:
|
|
detail = rtext
|
|
if r.status_code != 200:
|
|
if r.status_code == 403:
|
|
raise HTTPException(status_code=r.status_code, detail="cf-please-wait")
|
|
raise HTTPException(status_code=r.status_code, detail=detail)
|
|
|
|
async def send_conversation(self):
|
|
url = f'{self.free35_base_url}/conversation'
|
|
model = model_proxy.get(self.model, self.model)
|
|
r = await self.s.post(url, headers=self.headers, json=self.chat_request, timeout=600)
|
|
if r.status_code == 200:
|
|
rtext = r.text.split("\n")
|
|
return await chat_response(rtext, model, self.prompt_tokens, self.max_tokens)
|
|
else:
|
|
if "application/json" == r.headers.get("Content-Type", ""):
|
|
detail = r.json().get("detail", r.json())
|
|
else:
|
|
detail = r.content
|
|
if r.status_code == 403:
|
|
raise HTTPException(status_code=r.status_code, detail="cf-please-wait")
|
|
raise HTTPException(status_code=r.status_code, detail=detail)
|
|
|
|
|