Last active
March 3, 2023 13:30
-
-
Save ZiweiXU/336cbd03bc8113f9500da5de3d1b3077 to your computer and use it in GitHub Desktop.
A simple Python client of OpenAI chat completion service (backend of ChatGPT).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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=<your 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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment