From 5e6ddd94f6c951f9d184bd964849b26cb73a801e Mon Sep 17 00:00:00 2001 From: "Paul \"Joey\" Clark" Date: Thu, 16 Mar 2023 14:08:37 +0800 Subject: [PATCH 01/17] Ensure GNU sed works like BSD sed --- gpt3 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gpt3 b/gpt3 index a22f4e7..3478b9b 100755 --- a/gpt3 +++ b/gpt3 @@ -41,6 +41,11 @@ set -- "${POSITIONAL[@]}" # restore positional parameter [ -z "$2" ] && MAX_TOKENS=64 || MAX_TOKENS="$2" [ -z "$OPENAI_KEY" ] && KEY="$OPENAI_API_KEY" || KEY="$OPENAI_KEY" +line_buffered="-l" +if sed --version | grep GNU >/dev/null; then + line_buffered="-u" +fi + # FIXME: Improve error handling curl -sSL -N \ -H "gpt3-cli/0.1.1 (https://github.com/CrazyPython/gpt3-cli)" \ @@ -50,6 +55,6 @@ curl -sSL -N \ --data-urlencode max_tokens="$MAX_TOKENS" \ --data-urlencode frequency_penalty="$FREQ_PENALTY" \ --data-urlencode presence_penalty="$PRES_PENALTY" \ - -H "Authorization: Bearer $KEY" | sed -l 's/^data: //' | grep --line-buffer -v '^\[DONE\]$' | jq -j --unbuffered '.choices[0].text' + -H "Authorization: Bearer $KEY" | sed $line_buffered 's/^data: //' | grep --line-buffer -v '^\[DONE\]$' | jq -j --unbuffered '.choices[0].text' # Add trailing newline echo From 671ed5d1cca46495ebd45c5baebbd9a0a50b405d Mon Sep 17 00:00:00 2001 From: "Paul \"Joey\" Clark" Date: Thu, 16 Mar 2023 14:12:30 +0800 Subject: [PATCH 02/17] Warn the user if OPENAI_API_KEY is not set --- gpt3 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gpt3 b/gpt3 index 3478b9b..a68bded 100755 --- a/gpt3 +++ b/gpt3 @@ -41,6 +41,11 @@ set -- "${POSITIONAL[@]}" # restore positional parameter [ -z "$2" ] && MAX_TOKENS=64 || MAX_TOKENS="$2" [ -z "$OPENAI_KEY" ] && KEY="$OPENAI_API_KEY" || KEY="$OPENAI_KEY" +if [ -z "$KEY" ]; then + echo "You must set OPENAI_API_KEY or OPENAI_KEY" + exit 1 +fi + line_buffered="-l" if sed --version | grep GNU >/dev/null; then line_buffered="-u" From 5922a33d0f45099f4c0c928d6fc7f6331c95bf12 Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Thu, 16 Mar 2023 15:14:54 +0800 Subject: [PATCH 03/17] Add code to call ChatGPT API --- gpt3 | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/gpt3 b/gpt3 index a68bded..1d4406e 100755 --- a/gpt3 +++ b/gpt3 @@ -41,6 +41,8 @@ set -- "${POSITIONAL[@]}" # restore positional parameter [ -z "$2" ] && MAX_TOKENS=64 || MAX_TOKENS="$2" [ -z "$OPENAI_KEY" ] && KEY="$OPENAI_API_KEY" || KEY="$OPENAI_KEY" +PROMPT="$1" + if [ -z "$KEY" ]; then echo "You must set OPENAI_API_KEY or OPENAI_KEY" exit 1 @@ -52,14 +54,58 @@ if sed --version | grep GNU >/dev/null; then fi # FIXME: Improve error handling -curl -sSL -N \ - -H "gpt3-cli/0.1.1 (https://github.com/CrazyPython/gpt3-cli)" \ - -G https://api.openai.com/v1/engines/${ENGINE}/completions/browser_stream \ - --data-urlencode prompt="$1" \ - --data-urlencode temperature="$TEMPERATURE" \ - --data-urlencode max_tokens="$MAX_TOKENS" \ - --data-urlencode frequency_penalty="$FREQ_PENALTY" \ - --data-urlencode presence_penalty="$PRES_PENALTY" \ - -H "Authorization: Bearer $KEY" | sed $line_buffered 's/^data: //' | grep --line-buffer -v '^\[DONE\]$' | jq -j --unbuffered '.choices[0].text' -# Add trailing newline -echo + +call_chat_api() { + request_data=$( +cat << !!! + { + "model": "${ENGINE}", + "messages": [{"role": "user", "content": "${PROMPT}"}], + "temperature": ${TEMPERATURE} + } +!!! + ) + + # Not a GET request + curl -sSL -N \ + https://api.openai.com/v1/chat/completions \ + -H "gpt3-cli-joeytwiddle/0.2.0 (https://github.com/CrazyPython/gpt3-cli)" \ + -H "Authorization: Bearer $KEY" \ + -H "Content-Type: application/json" \ + -d "$request_data" | + jq -j --unbuffered '.choices[0].message.content' + # Add trailing newline + echo + + # Example error response: + # { + # "error": { + # "message": "you must provide a model parameter", + # "type": "invalid_request_error", + # "param": null, + # "code": null + # } + # } +} + +call_old_api() { + curl -sSL -N \ + -G https://api.openai.com/v1/engines/${ENGINE}/completions/browser_stream \ + -H "gpt3-cli-joeytwiddle/0.2.0 (https://github.com/CrazyPython/gpt3-cli)" \ + -H "Authorization: Bearer $KEY" \ + --data-urlencode model="$ENGINE" \ + --data-urlencode prompt="$PROMPT" \ + --data-urlencode temperature="$TEMPERATURE" \ + --data-urlencode max_tokens="$MAX_TOKENS" \ + --data-urlencode frequency_penalty="$FREQ_PENALTY" \ + --data-urlencode presence_penalty="$PRES_PENALTY" | + sed $line_buffered 's/^data: //' | grep --line-buffer -v '^\[DONE\]$' | jq -j --unbuffered '.choices[0].text' + # Add trailing newline + echo +} + +if [[ "$ENGINE" =~ "gpt-3.5-turbo" ]]; then + call_chat_api +else + call_old_api +fi From 8185c66eb6cb704a533f6022eca44c5a60e9c788 Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Fri, 17 Mar 2023 12:34:36 +0800 Subject: [PATCH 04/17] Default to text-davinci-003 --- gpt3 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gpt3 b/gpt3 index 1d4406e..35ddfe3 100755 --- a/gpt3 +++ b/gpt3 @@ -2,6 +2,11 @@ set -ef -o pipefail # Inherit defaults from env variables for easy scripting +# Available engines (models) are listed here: https://platform.openai.com/docs/models +# Apparently text-davinci-003 costs a lot more, and is less accurate, but I found it much faster than gpt-3.5-turbo! +[ -z "$ENGINE" ] && ENGINE="text-davinci-003" +#[ -z "$ENGINE" ] && ENGINE="gpt-3.5-turbo" + [ -z "$ENGINE" ] && ENGINE=davinci [ -z "$TEMPERATURE" ] && TEMPERATURE=0.5 [ -z "$FREQ_PENALTY" ] && FREQ_PENALTY=0 From 812766464883a5e0fc9f76e28b135c5c16a2910c Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Fri, 17 Mar 2023 12:34:44 +0800 Subject: [PATCH 05/17] Increase default response size (tokens) --- gpt3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpt3 b/gpt3 index 35ddfe3..ab18512 100755 --- a/gpt3 +++ b/gpt3 @@ -43,7 +43,7 @@ case $key in esac done set -- "${POSITIONAL[@]}" # restore positional parameter -[ -z "$2" ] && MAX_TOKENS=64 || MAX_TOKENS="$2" +[ -z "$2" ] && MAX_TOKENS=256 || MAX_TOKENS="$2" [ -z "$OPENAI_KEY" ] && KEY="$OPENAI_API_KEY" || KEY="$OPENAI_KEY" PROMPT="$1" From b5e444009b087c75f4c9e33613f8f872f5061da9 Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Fri, 17 Mar 2023 12:48:12 +0800 Subject: [PATCH 06/17] Add convenient scripts ask-gpt3 and ask-chatgpt --- ask-chatgpt | 46 ++++++++++++++++++++++++++++++++++++++++++++++ ask-gpt3 | 8 ++++++++ 2 files changed, 54 insertions(+) create mode 100755 ask-chatgpt create mode 100755 ask-gpt3 diff --git a/ask-chatgpt b/ask-chatgpt new file mode 100755 index 0000000..35ab447 --- /dev/null +++ b/ask-chatgpt @@ -0,0 +1,46 @@ +#!/usr/bin/env sh +set -e + +cd "$(dirname "$(realpath "$0")")" + +[ -z "$COLUMNS" ] && COLUMNS="$(tput cols)" + +ENGINE="gpt-3.5-turbo" exec ./gpt3 "$*" | + + # Trim all empty lines? + #trimempty | + # Trim empty lines at the start of the stream + #sed -e '/./,$!d' | + # Trim empty lines at the start and end of the stream. + # (It also squeezes repeated empty lines in the middle of the stream. To prevent that, we could use a counter.) + awk ' + BEGIN { + started = 0; + empty = 0; + } + { + if (NF) { + if (started && empty) { + print ""; + } + empty = 0; + started = 1; + print + } else { + empty = 1; + } + } + ' | + + # When reaching the width of the window, flow words onto the next line instead of breaking them + if command -v fold >/dev/null 2>&1 + then fold -s -w "$COLUMNS" + else cat + fi | + + # Assume the output is markdown, and colourise it + if command -v bat >/dev/null 2>&1 + then bat --style=plain --force-colorization --language=markdown + else cat + fi + diff --git a/ask-gpt3 b/ask-gpt3 new file mode 100755 index 0000000..edc48dd --- /dev/null +++ b/ask-gpt3 @@ -0,0 +1,8 @@ +#!/usr/bin/env sh +set -e + +cd "$(dirname "$(realpath "$0")")" + +ENGINE="text-davinci-003" exec ./gpt3 "$*" 256 | + # Trim empty lines at the start of the stream + sed -u -e '/./,$!d' From 7c8b388dd241d7f9ba05abb96b5290571e297d6e Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Fri, 17 Mar 2023 13:14:07 +0800 Subject: [PATCH 07/17] Show user where they can get their API key --- gpt3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpt3 b/gpt3 index ab18512..78e29b9 100755 --- a/gpt3 +++ b/gpt3 @@ -49,7 +49,7 @@ set -- "${POSITIONAL[@]}" # restore positional parameter PROMPT="$1" if [ -z "$KEY" ]; then - echo "You must set OPENAI_API_KEY or OPENAI_KEY" + echo "You must export OPENAI_KEY or OPENAI_API_KEY. Get one here: https://platform.openai.com/account/api-keys" exit 1 fi From eda360c3e617a1ca7cbb5e0998b58f6c704696d5 Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Fri, 17 Mar 2023 13:18:33 +0800 Subject: [PATCH 08/17] Simplify line buffering (-u should work on macOS and Linux) --- gpt3 | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/gpt3 b/gpt3 index 78e29b9..2fbdd23 100755 --- a/gpt3 +++ b/gpt3 @@ -53,11 +53,6 @@ if [ -z "$KEY" ]; then exit 1 fi -line_buffered="-l" -if sed --version | grep GNU >/dev/null; then - line_buffered="-u" -fi - # FIXME: Improve error handling call_chat_api() { @@ -104,7 +99,7 @@ call_old_api() { --data-urlencode max_tokens="$MAX_TOKENS" \ --data-urlencode frequency_penalty="$FREQ_PENALTY" \ --data-urlencode presence_penalty="$PRES_PENALTY" | - sed $line_buffered 's/^data: //' | grep --line-buffer -v '^\[DONE\]$' | jq -j --unbuffered '.choices[0].text' + sed -u 's/^data: //' | grep --line-buffer -v '^\[DONE\]$' | jq -j --unbuffered '.choices[0].text' # Add trailing newline echo } From 7fa4b08827cf885bd008596af0f65099816526af Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Fri, 17 Mar 2023 13:19:21 +0800 Subject: [PATCH 09/17] Update README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 64880a2..b734664 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # gpt3-cli Streaming command-line interface for OpenAI's GPT-3 + +Also supports ChatGPT (non-streaming) + +Use the `ask-gpt3` and `ask-chatgpt` scripts for prettier formatting ### Usecases * Use GPT-3 distraction-free: Just you and the terminal * Command-line remembers your past prompts and output history From 765dc69b29a698bcc77552aa443bf6a444d01405 Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Fri, 17 Mar 2023 13:23:53 +0800 Subject: [PATCH 10/17] Warn user if jq is missing --- gpt3 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gpt3 b/gpt3 index 2fbdd23..e2eb90d 100755 --- a/gpt3 +++ b/gpt3 @@ -53,6 +53,11 @@ if [ -z "$KEY" ]; then exit 1 fi +if ! command -v jq >/dev/null 2>&1; then + echo "Please install jq (Command-line JSON processor) - instructions in README" + exit 1 +fi + # FIXME: Improve error handling call_chat_api() { From 2e44aec86e4a4e39c9018b630fc8bfb91ebd54ab Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Fri, 17 Mar 2023 13:47:09 +0800 Subject: [PATCH 11/17] Minor polish --- ask-chatgpt | 15 ++++++--------- ask-gpt3 | 6 ++++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/ask-chatgpt b/ask-chatgpt index 35ab447..86ce4f2 100755 --- a/ask-chatgpt +++ b/ask-chatgpt @@ -5,27 +5,24 @@ cd "$(dirname "$(realpath "$0")")" [ -z "$COLUMNS" ] && COLUMNS="$(tput cols)" -ENGINE="gpt-3.5-turbo" exec ./gpt3 "$*" | +exec ./gpt3 --engine 'gpt-3.5-turbo' "$*" | - # Trim all empty lines? + # Trim all empty lines #trimempty | # Trim empty lines at the start of the stream #sed -e '/./,$!d' | - # Trim empty lines at the start and end of the stream. + # Trim empty lines at the start and end of the stream # (It also squeezes repeated empty lines in the middle of the stream. To prevent that, we could use a counter.) awk ' - BEGIN { - started = 0; - empty = 0; - } + BEGIN { started = 0; empty = 0 } { if (NF) { if (started && empty) { print ""; } - empty = 0; - started = 1; print + started = 1; + empty = 0; } else { empty = 1; } diff --git a/ask-gpt3 b/ask-gpt3 index edc48dd..b013df5 100755 --- a/ask-gpt3 +++ b/ask-gpt3 @@ -3,6 +3,8 @@ set -e cd "$(dirname "$(realpath "$0")")" -ENGINE="text-davinci-003" exec ./gpt3 "$*" 256 | +exec ./gpt3 --engine 'text-davinci-003' "$*" 256 | # Trim empty lines at the start of the stream - sed -u -e '/./,$!d' + # Disabled, because it stops the response from streaming + #sed -u -e '/./,$!d' + cat From 768f4bdf2ae86bd149ed44c3425b436bc4cfa529 Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Sat, 18 Mar 2023 01:17:36 +0800 Subject: [PATCH 12/17] Escape double-quotes inside content string; also unindent --- gpt3 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gpt3 b/gpt3 index e2eb90d..d33c7b4 100755 --- a/gpt3 +++ b/gpt3 @@ -63,11 +63,11 @@ fi call_chat_api() { request_data=$( cat << !!! - { - "model": "${ENGINE}", - "messages": [{"role": "user", "content": "${PROMPT}"}], - "temperature": ${TEMPERATURE} - } +{ + "model": "${ENGINE}", + "messages": [{"role": "user", "content": "${PROMPT//\"/\\\"}"}], + "temperature": ${TEMPERATURE} +} !!! ) From e14d55ce3a189c5facc5ff25923e5edb97ddd9cf Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Sat, 18 Mar 2023 01:18:18 +0800 Subject: [PATCH 13/17] Check result for errors (actually check for success) --- gpt3 | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/gpt3 b/gpt3 index d33c7b4..9852320 100755 --- a/gpt3 +++ b/gpt3 @@ -71,16 +71,23 @@ cat << !!! !!! ) + result="$( # Not a GET request curl -sSL -N \ https://api.openai.com/v1/chat/completions \ -H "gpt3-cli-joeytwiddle/0.2.0 (https://github.com/CrazyPython/gpt3-cli)" \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ - -d "$request_data" | - jq -j --unbuffered '.choices[0].message.content' - # Add trailing newline - echo + -d "$request_data" + )" + + if [[ "$result" =~ '"choices":[' ]] + then + printf "%s\n" "$result" | + jq -j --unbuffered '.choices[0].message.content' + else #[[ "$result" =~ '"error": {' ]] + printf "%s\n" "$result" + fi # Example error response: # { From 9b24477dda9cd6ed85781ce1acf7ecb3800ee7c3 Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Sat, 18 Mar 2023 01:20:53 +0800 Subject: [PATCH 14/17] Better checking for success case --- gpt3 | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/gpt3 b/gpt3 index 9852320..1338439 100755 --- a/gpt3 +++ b/gpt3 @@ -71,7 +71,7 @@ cat << !!! !!! ) - result="$( + response="$( # Not a GET request curl -sSL -N \ https://api.openai.com/v1/chat/completions \ @@ -81,12 +81,16 @@ cat << !!! -d "$request_data" )" - if [[ "$result" =~ '"choices":[' ]] - then - printf "%s\n" "$result" | + result="$( + printf "%s\n" "$response" | jq -j --unbuffered '.choices[0].message.content' - else #[[ "$result" =~ '"error": {' ]] + )" + + if [ -n "$result" ] && ! [ "$result" = "null" ] + then printf "%s\n" "$result" + else #[[ "$result" =~ '"error": {' ]] + printf "%s\n" "$response" fi # Example error response: From 8ffa473ba62ffe0758a2747067158df9299f07d4 Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Mon, 20 Mar 2023 13:05:38 +0800 Subject: [PATCH 15/17] When asking GPT3 something, use a suitable Q/A prompt --- ask-gpt3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ask-gpt3 b/ask-gpt3 index b013df5..26aefea 100755 --- a/ask-gpt3 +++ b/ask-gpt3 @@ -3,7 +3,7 @@ set -e cd "$(dirname "$(realpath "$0")")" -exec ./gpt3 --engine 'text-davinci-003' "$*" 256 | +exec ./gpt3 --engine 'text-davinci-003' "Q: $* A: " 256 | # Trim empty lines at the start of the stream # Disabled, because it stops the response from streaming #sed -u -e '/./,$!d' From b6a8f9a0ba604da494a1ae103e6489a3902a1a62 Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Thu, 23 Mar 2023 05:31:16 +0800 Subject: [PATCH 16/17] Try to detect later versions of ChatGPT too --- gpt3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpt3 b/gpt3 index 1338439..8106a47 100755 --- a/gpt3 +++ b/gpt3 @@ -120,7 +120,7 @@ call_old_api() { echo } -if [[ "$ENGINE" =~ "gpt-3.5-turbo" ]]; then +if [[ "$ENGINE" =~ "gpt-3.5-turbo" ]] || [[ "$ENGINE" == gpt-* ]]; then call_chat_api else call_old_api From 5eb9794e5c047bf5b14351f3bdf3775f107b455a Mon Sep 17 00:00:00 2001 From: Paul Joey Clark Date: Thu, 23 Mar 2023 05:33:55 +0800 Subject: [PATCH 17/17] Update README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b734664..506dbe6 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,10 @@ This concatenates the input and the prompt together, input first, prompt second, ## File upload via CLI Try [OpenAI's Python tool.](https://github.com/openai/openai-python#openai-python-library) +## See also + +- https://github.com/TheR1D/shell_gpt (Python) + ## License **N.B.** You have the full right to base any closed-source or open-source program on this software if it remains on your own, your company's, or your company's contractors computers and cloud servers. (In other words, you can use this to build a closed-source SaaS.) If you distribute a program to a third-party end-user, you are required to give them the source code. The requirement to share source code only applies when distributed to other people. This is the GPLv3's private use clause. The intent is to ensure GPT-3 developer tools remain [open-source](https://www.gnu.org/philosophy/free-sw.en.html). OpenAI asks that end-users of products that use GPT-3 products be shielded from direct API access, so this license should not impose any restriction. A copy of the private use exception is copied below: