JeffJing's picture
Upload 9 files
9560b45
"""
Official API for ChatGPT
"""
import asyncio
import json
import os
import sys
import httpx
import requests
import tiktoken
from OpenAIAuth.OpenAIAuth import OpenAIAuth
ENCODER = tiktoken.get_encoding("gpt2")
def get_max_tokens(prompt: str) -> int:
"""
Get the max tokens for a prompt
"""
return 4000 - len(ENCODER.encode(prompt))
class Message:
"""
A single exchange between the user and the bot
"""
def __init__(self, text: str, author: str) -> None:
self.text: str = text
self.author: str = author
class Conversation:
"""
A single conversation
"""
def __init__(self) -> None:
self.messages: list[Message] = []
CONVERSATION_BUFFER: int = int(os.environ.get("CONVERSATION_BUFFER") or 1500)
class Conversations:
"""
Conversation handler
"""
def __init__(self) -> None:
self.conversations: dict[str][Conversation] = {}
def add_message(self, message: Message, conversation_id: str) -> None:
"""
Adds a message to a conversation
"""
if conversation_id not in self.conversations:
self.conversations[conversation_id] = Conversation()
self.conversations[conversation_id].messages.append(message)
def get(self, conversation_id: str) -> str:
"""
Builds a conversation string from a conversation id
"""
if conversation_id not in self.conversations:
return ""
# Build conversation string from messages and check if it's too long
conversation = ""
for message in self.conversations[conversation_id].messages:
conversation += f"{message.author}: {message.text}<|im_sep|>\n\n"
if len(ENCODER.encode(conversation)) > 4000 - CONVERSATION_BUFFER:
self.purge_history(conversation_id)
return self.get(conversation_id)
return conversation
def purge_history(self, conversation_id: str, num: int = 1):
"""
Remove oldest messages from a conversation
"""
if conversation_id not in self.conversations:
return
self.conversations[conversation_id].messages = self.conversations[
conversation_id
].messages[num:]
def rollback(self, conversation_id: str, num: int = 1):
"""
Remove latest messages from a conversation
"""
if conversation_id not in self.conversations:
return
self.conversations[conversation_id].messages = self.conversations[
conversation_id
].messages[:-num]
def remove(self, conversation_id: str) -> None:
"""
Removes a conversation
"""
if conversation_id in self.conversations:
del self.conversations[conversation_id]
BASE_PROMPT = (
os.environ.get("BASE_PROMPT")
or """You are ChatGPT, a large language model by OpenAI. Respond conversationally\n\n\n"""
)
PROXY_URL = os.environ.get("PROXY_URL") or "https://chat.duti.tech"
class Chatbot:
"""
Handles everything seamlessly
"""
def __init__(
self,
email: str,
password: str,
paid: bool = False,
proxy=None,
insecure: bool = False,
session_token: str = None,
) -> None:
self.proxy = proxy
self.email: str = email
self.password: str = password
self.session_token = session_token
self.insecure: bool = insecure
self.api_key: str
self.paid: bool = paid
self.conversations = Conversations()
self.login(email, password, proxy, insecure, session_token)
async def ask(self, prompt: str, conversation_id: str = None) -> dict:
"""
Gets a response from the API
"""
if conversation_id is None:
conversation_id = "default"
self.conversations.add_message(
Message(prompt, "User"),
conversation_id=conversation_id,
)
conversation: str = self.conversations.get(conversation_id)
# Build request body
body = self.__get_config()
body["prompt"] = BASE_PROMPT + conversation + "ChatGPT: "
body["max_tokens"] = get_max_tokens(conversation)
async with httpx.AsyncClient(proxies=self.proxy if self.proxy else None).stream(
method="POST",
url=PROXY_URL + "/completions",
data=json.dumps(body),
headers={"Authorization": f"Bearer {self.api_key}"},
timeout=1080,
) as response:
full_result = ""
async for line in response.aiter_lines():
if response.status_code == 429:
print("error: " + "Too many requests")
raise Exception("Too many requests")
elif response.status_code == 523:
print(
"error: "
+ "Origin is unreachable. Ensure that you are authenticated and are using the correct pricing model.",
)
raise Exception(
"Origin is unreachable. Ensure that you are authenticated and are using the correct pricing model.",
)
elif response.status_code == 503:
print("error: " + "OpenAI error!")
raise Exception("OpenAI error!")
elif response.status_code != 200:
print("error: " + "Unknown error")
raise Exception("Unknown error")
line = line.strip()
if line == "\n" or line == "":
continue
if line == "data: [DONE]":
break
try:
# Remove "data: " from the start of the line
data = json.loads(line[6:])
if data is None:
continue
full_result += data["choices"][0]["text"].replace("<|im_end|>", "")
if "choices" not in data:
continue
yield data
except json.JSONDecodeError:
continue
self.conversations.add_message(
Message(full_result, "ChatGPT"),
conversation_id=conversation_id,
)
def __get_config(self) -> dict:
return {
"temperature": float(os.environ.get("TEMPERATURE") or 0.5),
"top_p": float(os.environ.get("TOP_P") or 1),
"stop": ["<|im_end|>", "<|im_sep|>"],
"presence_penalty": float(os.environ.get("PRESENCE_PENALTY") or 1.0),
"paid": self.paid,
"stream": True,
}
def login(self, email, password, proxy, insecure, session_token) -> None:
"""
Login to the API
"""
if not insecure:
auth = OpenAIAuth(email_address=email, password=password, proxy=proxy)
if session_token:
auth.session_token = session_token
auth.get_access_token()
self.api_key = auth.access_token
if self.api_key is None:
self.session_token = None
self.login(email, password, proxy, insecure, None)
return
auth.begin()
self.session_token = auth.session_token
self.api_key = auth.access_token
else:
auth_request = requests.post(
PROXY_URL + "/auth",
json={"email": email, "password": password},
timeout=10,
)
self.api_key = auth_request.json()["accessToken"]
def get_input(prompt):
"""
Multi-line input
"""
# Display the prompt
print(prompt, end="")
# Initialize an empty list to store the input lines
lines = []
# Read lines of input until the user enters an empty line
while True:
line = input()
if line == "":
break
lines.append(line)
# Join the lines, separated by newlines, and store the result
user_input = "\n".join(lines)
# Return the input
return user_input
async def main():
"""
Testing main function
"""
import argparse
print(
"""
ChatGPT - A command-line interface to OpenAI's ChatGPT (https://chat.openai.com/chat)
Repo: github.com/acheong08/ChatGPT
""",
)
parser = argparse.ArgumentParser()
parser.add_argument(
"-e",
"--email",
help="Your OpenAI email address",
required=False,
)
parser.add_argument(
"-p",
"--password",
help="Your OpenAI password",
required=False,
)
parser.add_argument(
"--paid",
help="Use the paid API",
action="store_true",
)
parser.add_argument(
"--proxy",
help="Use a proxy",
required=False,
type=str,
default=None,
)
parser.add_argument(
"--insecure-auth",
help="Use an insecure authentication method to bypass OpenAI's geo-blocking",
action="store_true",
)
parser.add_argument(
"--session_token",
help="Alternative to email and password authentication. Use this if you have Google/Microsoft account.",
required=False,
)
args = parser.parse_args()
if (args.email is None or args.password is None) and args.session_token is None:
print("error: " + "Please provide your email and password")
return
print("Logging in...")
chatbot = Chatbot(
args.email,
args.password,
paid=args.paid,
proxy=args.proxy,
insecure=args.insecure_auth,
session_token=args.session_token,
)
print("Logged in\n")
print("Type '!help' to show a full list of commands")
print("Press enter twice to submit your question.\n")
def commands(command: str) -> bool:
if command == "!help":
print(
"""
!help - Show this help message
!reset - Clear the current conversation
!rollback <int> - Remove the latest <int> messages from the conversation
!exit - Exit the program
""",
)
elif command == "!reset":
chatbot.conversations.remove("default")
print("Conversation cleared")
elif command.startswith("!rollback"):
try:
num = int(command.split(" ")[1])
chatbot.conversations.rollback("default", num)
print(f"Removed {num} messages from the conversation")
except IndexError:
print("Please specify the number of messages to remove")
except ValueError:
print("Please specify a valid number of messages to remove")
elif command == "!exit":
print("Exiting...")
sys.exit(0)
else:
return False
return True
try:
while True:
prompt = get_input("\nYou:\n")
if prompt.startswith("!"):
if commands(prompt):
continue
print("ChatGPT:")
async for line in chatbot.ask(prompt=prompt):
result = line["choices"][0]["text"].replace("<|im_end|>", "")
print(result, end="")
sys.stdout.flush()
print()
except KeyboardInterrupt:
print("Exiting...")
sys.exit(0)
if __name__ == "__main__":
asyncio.run(main())