#!/usr/bin/env python3 """ GPT-3.5 client for Python 3.6+. Usage: python gpt.py [command] [options] [text] Commands: another-word Find another word with GPT-3. qa Ask a single question then exit. q Alias for qa. rephrase Rephrase text with GPT-3. interactive Start an interactive chat with GPT-3. talk Alias for interactive. Options: -h, --help - Print help message. Examples: python gpt.py qa "What is the meaning of life?" python gpt.py rephrase "I am a cat." python gpt.py another-word "cat" python gpt.py interactive python gpt.py talk Commands for an interactive session: !help Print this help message. !quit Exit the program. !usage Print the total usage in this session. !clear Clear the context. !save Save the context to a file. !load Load the context from a file. !save_dialog Save the dialog to a file. All context is autosaved to $HOME/.gpt_cli/autosave. Copyright (c) 2023 Ziwei Xu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import sys from sys import exit import os import readline import json import uuid from argparse import ArgumentParser from datetime import datetime try: import openai except ImportError: print('Please install the OpenAI Python library:') print('pip install openai') exit(1) MODEL = 'gpt-3.5-turbo' MAX_TOKENS = 3096 USD_PER_TOKEN = 0.000002 CONTEXT_AUTOSAVE = True CONTEXT_AUTOSAVE_DIR = os.path.join(os.environ['HOME'], '.gpt_cli/autosave') if CONTEXT_AUTOSAVE: if not os.path.exists(CONTEXT_AUTOSAVE_DIR): os.makedirs(CONTEXT_AUTOSAVE_DIR) try: openai.api_key = os.environ['OPENAI_API_KEY'] except KeyError: help_str = """Please set the OPENAI_API_KEY environment variable. You can get your API key from https://platform.openai.com/account/api-keys You can set the environment variable by running: export OPENAI_API_KEY=""" print(help_str) exit(1) def timestamp(): return datetime.now().strftime('%Y-%m-%d %H:%M:%S') class GPTClient(object): def __init__(self) -> None: self.context = [] self.total_usage = 0 # generate session name self.session_name = str(uuid.uuid4()) # parse command parser = ArgumentParser( description='Chat with GPT-3', usage='%(prog)s [command] [options] [text]') parser.add_argument( 'command', nargs='?', help='q, qa, rephrase, another-word, interactive, talk') args = parser.parse_args(sys.argv[1:2]) if not args.command or not hasattr(self, args.command): print('Missing or unrecognized command') parser.print_help() exit(1) # run command getattr(self, args.command)() def q(self): self.qa() def qa(self): parser = ArgumentParser(description='Ask GPT-3 a question') parser.add_argument( 'text', help='Ask a single question then exit.') args = parser.parse_args(sys.argv[2:]) self.context.append({"role": "user", "datetime": timestamp(), "content": args.text}) self._chat() def rephrase(self): parser = ArgumentParser(description='Rephrase text with GPT-3.') parser.add_argument('text', help='Text to rephrase') args = parser.parse_args(sys.argv[2:]) prompt = "Rephrase text. Use British English. Make it fluent and concise. Keep its original meaning. The text is: " self.context.append({"role": "user", "datetime": timestamp(), "content": f'{prompt} \"{args.text}\"'}) self._chat() def another_word(self): parser = ArgumentParser(description='Find another word with GPT-3.') parser.add_argument('text', help='Text to rephrase') args = parser.parse_args(sys.argv[2:]) prompt = "Find another word for: " self.context.append({"role": "user", "datetime": timestamp(), "content": f'{prompt} {args.text}'}) self._chat() def _save_context(self, fn='context.json', verbose=True): with open(f'{fn}.json', 'w') as f: json.dump(self.context, f) if verbose: print(f'Context saved to {fn}.json') def _save_dialog(self, fn='dialog.txt', verbose=True): with open(f'{fn}.txt', 'w') as f: for message in self.context: f.write(f'{message["role"]} ({message["datetime"]}):\n') for line in message["content"].splitlines(): f.write(f'\t{line}\n') f.write('\n') if verbose: print(f'Context saved to {fn}.txt') print('Note that this is only intended for viewing.') def _load_context(self, fn='context.json', verbose=True): fn = input('Enter filename: ') with open(f'{fn}', 'r') as f: self.context = json.load(f) if verbose: print(f'Context loaded from {fn}') def _chat(self): # keep role and content only context = [{"role": message["role"], "content": message["content"]} for message in self.context] response = openai.ChatCompletion.create( model=MODEL, messages=context, n=1, max_tokens=MAX_TOKENS, ) for choice in response['choices']: print() print(f'🤖 GPT-3:') print(choice['message']['content'].lstrip('\n')) self.context.append({"role": "assistant", "datetime": timestamp(), "content": choice['message']['content'].lstrip('\n')}) self.total_usage += response['usage']['total_tokens'] # Save context if CONTEXT_AUTOSAVE: path = os.path.join(CONTEXT_AUTOSAVE_DIR, self.session_name) self._save_context(f'{path}', verbose=False) def __del__(self): self._print_usage() def _print_usage(self): print(f'Session ID {self.session_name}: {self.total_usage} tokens ({USD_PER_TOKEN * self.total_usage:.4f} USD).') def talk(self): self.interactive() def interactive_print_help(self): help_str = """Commands: !help Print this help message. !quit Exit the program. !usage Print the total usage in this session. !clear Clear the context. !save Save the context to a file. !load Load the context from a file. !save_dialog Save the dialog to a file for viewing. """ print(help_str) def interactive(self): print('This is an interactive chat with GPT-3.') print('Type !help to see a list of commands.') print('Type !quit or Ctrl-D to exit.') print() session_name = input( f'Name this session or hit ENTER to skip [{self.session_name}]: ') if session_name: self.session_name = session_name command_registry = { 'help': self.interactive_print_help, 'quit': exit, 'usage': self._print_usage, 'clear': lambda: self.context.clear(), 'save': lambda fn=f'{self.session_name}': self._save_context(fn), 'load': self._load_context, 'save_dialog': lambda fn=f'{self.session_name}': self._save_dialog(fn), } # The main loop while True: print() text = input(f'🧠 You: ') # Check for commands if text.startswith('!'): command = text[1:].split(' ')[0] if command in command_registry: command_registry[command]() else: print('Unrecognized command.') continue # Add message to context self.context.append( {"role": "user", "datetime": timestamp(), "content": text}) self._chat() if __name__ == '__main__': try: GPTClient() except EOFError: exit(0)