I've been casually using OpenAI's ChatGPT to describe confusing pieces of code, generate ideas for coding solutions, and just for fun. The web UI is really great; it saves your conversations and gives the impression of a back-and-forth conversation with the model. However, it doesn't allow very long text to be sent via the UI.
I wanted to see how ChatGPT could be used to describe the contents of a pdf, specifically
a pdf describing Charles Forgy's Rete Algorithm. The document is quite long, so I couldn't
copy-and-paste it in the UI due to the previously mentioned length limits. After a bit of
searching, I discovered
an article describing using ChatGPT from python to summarize pdfs.
While trying the sample code and getting something working, I discovered
OpenAI's HTTP API
which would let me use two of my favorite tools to talk to ChatGPT:
curl and
jq.
After writing a simple shell script that could pass a question and receive an answer from
ChatGPT, I decided to have some fun and use ChatGPT to generate a typical "usage"
message for my new ask.sh
script. Here's some example output of that script:
$ ./ask.sh Usage: ./ask.sh [OPENAI_API_PROMPT] Description: This script uses the OpenAI API to generate responses to a given prompt. The prompt can be provided as an optional argument. If not provided, the default prompt will be used. Environment Variable: OPENAI_API_KEY - This variable should be set to your OpenAI API key. It is required for this script to function properly. Examples of Usage: 1. ./ask.sh "What is the meaning of life?" 2. OPENAI_API_KEY=your_api_key ./ask.sh "What is the capital of France?" 3. export OPENAI_API_KEY=your_api_key; ./ask.sh "How do I bake a cake?" $ ./ask.sh "What is the meaning of life?" As an AI language model, I don't have personal beliefs or opinions. The meaning of life is a philosophical and existential question that has been debated for centuries. It is subjective and varies from person to person. Some believe that the meaning of life is to seek happiness, while others believe it is to fulfill a higher purpose or to achieve spiritual enlightenment. Ultimately, the answer to this question is a personal one that each individual must discover for themselves.
In this article, I'll describe a shell script that can be used to send a question
to and receive an answer from OpenAI's ChatGPT. I'll cover some very basic shell
scripting, and I'll describe the curl
and jq
programs
I use regularly. Finally, I'll talk a little about what some people are calling
"prompt engineering," which refers to tweaking prompts (or questions) sent to
ChatGPT to refine responses received.
If you run a distribution of Linux, this part is quite easy: we'll create a file called
ask.sh
, add a line to it that describes the "interpreter" that'll be used
to "run" the program we write in it, and finally make it executable.
$ touch ./ask.sh $ echo '#!/usr/bin/env sh' > ./ask.sh $ echo "echo 'hello, world'" >> ./ask.sh $ chmod +x ./ask.sh $ ./ask.sh hello, world
We first use touch
to create an empty file in the current directory called
ask.sh
. We then use echo
to print some text into that file.
This text describes the "interpreter" that'll run our script. In this case: we're using
the interpreter invoked by sh
. We then write another command onto the next
line of the file by using >>
. This command will simply print "hello,
world" to our terminal. chmod +x ./ask.sh
will make our file executable.
Finally, we run our command and see its output.
curl
to Talk to ChatGPT
Now that we have a file that can be executed, let's change the contents of the file
so that we use ChatGPT's HTTP API. The program curl
makes this very easy
for us. It can be used to send an HTTP request to a given URL with a given payload.
According to OpenAI's website,
the curl command that would send a prompt to ChatGPT is as follows:
curl https://api.openai.com/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "Say this is a test!"}], "temperature": 0.7 }'
Note the $OPENAI_API_KEY
part. This is a variable in shell scripting,
and it will hold an
OpenAI API Key generated via OpenAI's website.
We'll set this in our environment so that we don't need to remember our API key.
On my computer, I can put the following into either my ~/.bashrc
or
my ~/.bash_profile
file to set this key automatically:
export OPENAI_API_KEY="Your API key here!"
Now whenever you open a new terminal, this environment variable will be set. To use it in
your current terminal, do . ~/.bashrc
(or similar file name).
Alright, let's run the above curl command in our terminal. Here's what I get:
$ curl https://api.openai.com/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "Say this is a test!"}], "temperature": 0.7 }' {"id":"chatcmpl-7FwnymAAoWvId6SJhrRntHV5xL6DP","object":"chat.completion","created":1684035322,"model":"gpt-3.5-turbo-0301","usage":{"prompt_tokens":14,"completion_tokens":5,"total_tokens":19},"choices":[{"message":{"role":"assistant","content":"This is a test!"},"finish_reason":"stop","index":0}]}
The curl
command results in some JSON printing to our terminal. It's a little
difficult to read, so we'll use jq
to format it nicely:
$ curl https://api.openai.com/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "Say this is a test!"}], "temperature": 0.7 }' | jq % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 432 100 300 100 132 49 21 0:00:06 0:00:06 --:--:-- 106 { "id": "chatcmpl-7FwpZAyQTfW7wTbDyMxKabhDn9tkm", "object": "chat.completion", "created": 1684035421, "model": "gpt-3.5-turbo-0301", "usage": { "prompt_tokens": 14, "completion_tokens": 5, "total_tokens": 19 }, "choices": [ { "message": { "role": "assistant", "content": "This is a test!" }, "finish_reason": "stop", "index": 0 } ] }
curl
outputs some extra information about the HTTP request when we do this.
We'll use the -s
flag the next time we try this command to silence this
output. Additionally,
we'll use jq
to specify
that we're only really interested in the "content"
returned
from ChatGPT:
$ curl -s https://api.openai.com/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "Say this is a test!"}], "temperature": 0.7 }' | jq '.choices | first | .message.content' "This is a test!"
jq
makes it very easy to work with JSON. In this last command, we use it
to specify that we only want the first element of the "choices"
array in
the response from ChatGPT. We also specify that we want the "content"
property in the "message"
object.
Finally, let's replace the echo
command in our ask.sh
file
with that command:
#!/usr/bin/env sh curl -s https://api.openai.com/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "Say this is a test!"}], "temperature": 0.7 }' | jq '.choices | first | .message.content'
Now we'll run it:
$ ./ask.sh "This is a test!"
In order to pass arguments to a shell script, we need to change the contents of the file
very slightly. To get the first, second, or even third arguments passed to a shell script
as in ./ask.sh one two three
, you'd use the variables $1
,
$2
, and $3
respectively. We'll only allow one argument, so we'll
replace the hard coded message we send to ChatGPT with $1
:
#!/usr/bin/env sh curl -s https://api.openai.com/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d "{ \"model\": \"gpt-3.5-turbo\", \"messages\": [{\"role\": \"user\", \"content\": \"$1\"}], \"temperature\": 0.7 }" | jq '.choices | first | .message.content'
We change from using single quotes '
to "
double quotes.
This lets us use the variable $1
in the string of text, but it also means
we must "escape" the other double quotes inside of the string. Let's test this out:
$ ./ask.sh "How many ounces are in a pound" "There are 16 ounces in a pound."
This is fine, but escaping all of those double quotes is a little cumbersome.
Fortunately, jq
has the ability to build JSON for us. Update your
ask.sh
file like so:
#!/usr/bin/env sh OPENAI_API_PAYLOAD=$(echo '{}' | jq --arg openai_api_prompt "$1" \ '{ "model": "gpt-3.5-turbo", "messages": [{ "role": "user", "content": $openai_api_prompt }], "temperature": 0.5 }') curl -s https://api.openai.com/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d "$OPENAI_API_PAYLOAD" | jq -r '.choices | first | .message.content'
We use jq
to build our JSON payload that we send to ChatGPT. We use the
--arg
flag to tell jq
to replace the variable
openai_api_prompt
with $1
in the JSON it creates.
We do echo '{}' |
to feed an empty JSON object into jq
. Then,
in our curl
command, we replace the hard-coded and manually escaped
string with the output of the earlier jq
command. Finally, we pass
the flag -r
to our final call to jq
. This lets us request
"raw" output; this way, jq
will not return the response from ChatGPT
surrounded with double quotes. Let's give this a try:
$ ./ask.sh "what is 5x5" 5x5 is equal to 25.
Let's say it's the user's first time using our script. We want to provide a helpful usage message for them if they run the script without a prompt. We can ask ChatGPT to write this message for us by sending the contents of the script along with a prompt to explain the script's usage:
#!/usr/bin/env sh OPENAI_API_PROMPT="Output a typical Usage help message for the following shell script called ask.sh:\n$(cat "$0")\nBe sure to describe usage of the environment variable OPENAI_API_KEY.\nExamples of Usage provided will not use questions that would reference the contents or usage of this script." if [ -n "$1" ] then OPENAI_API_PROMPT="$1" fi OPENAI_API_PAYLOAD=$(echo '{}' | jq --arg openai_api_prompt "$OPENAI_API_PROMPT" \ '{ "model": "gpt-3.5-turbo", "messages": [{ "role": "user", "content": $openai_api_prompt }], "temperature": 0.5 }') curl -s https://api.openai.com/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d "$OPENAI_API_PAYLOAD" | jq -r '.choices | first | .message.content'
We start off by building a prompt that we'll send to ChatGPT if a first argument is not
provided when this script is called. In it, we'll specify that we want it to output
the Usage of this script. We then pass the contents of our script using
cat "$0"
. Just like $1
, this references the first (0 indexed)
word used to run our script. We also specify that we do not want examples to contain
references to the script itself; if a user sends "How do I use this script?",
ChatGPT won't know since it doesn't have the contents of the script itself.
The output looks something like this:
$ ./ask.sh Usage: ./ask.sh [OPENAI_API_PROMPT] Description: This script uses the OpenAI API to generate responses to a given prompt. The prompt can be provided as an optional argument. If not provided, the default prompt will be used. Environment Variable: OPENAI_API_KEY - This variable should be set to your OpenAI API key. It is required for this script to function properly. Examples of Usage: 1. ./ask.sh "What is the meaning of life?" 2. OPENAI_API_KEY=your_api_key ./ask.sh "What is the capital of France?" 3. export OPENAI_API_KEY=your_api_key; ./ask.sh "How do I bake a cake?"
The default prompt took some
tweaking before it came out right.
I found that ChatGPT would sometimes return mentions of the
script itself in the examples it provided if it was not explicitly told not to do so.
ChatGPT also wanted to explain how to add the
contents of the script to a file, turn it into an executable, then run it (kind of
like how I did with this article). I needed to specify that I was looking for a
"typical Usage help message." I also needed to specifically ask for an explanation of
OPENAI_API_KEY
. Sometimes, ChatGPT would leave mention of this variable
out of its Usage message entirely.
This fine tuning and tweaking is what's known as "prompt engineering." Over time, it'll become a skill to refine prompts sent to AI tools such as ChatGPT. Just like crafting the perfect phrase in your favorite search engine to get exactly what you need, your ability to refine queries made to ChatGPT will be a part of future engineer's toolboxes.
We've learned how to create a shell script, issue http requests to servers using
curl
, parse and create JSON using jq
, and, finally,
fine tune prompts made to ChatGPT. I hope you've found this helpful in some way, or
at least enjoyed the journey. Happy coding!
- ryjo