After a bit of AI hiatus, I noticed that llama 3.0 models were released and wanted to try the models. Sure enough, after a week the weights we re available at the official site. However, the Docker image hasn't been used in a while and I wanted to upgrade it without losing the models.
There was almost no information on this available online yet, and even the
ollama docker documentation is quite non-existent — maybe for seasoned
Docker users it is obvious what needs to be done? But not for me, so let's see
if I can manage it.
Upgrading the docker image
First, let's just upgrade the ollama/ollama image:
$ sudo docker pull ollama/ollama
This is nice, but the currently running container is still the old one. Let's stop it:
$ sudo docker stop ollama
Checking the location of the files
I remember I set a custom directory to store the models. Let's check where it is:
As can be seen, the models are stored in /mnt/scratch/docker/volumes/ollama/_data. Let's make a hard-linked copy
of the files into another folder, to make sure we don't lose them:
Continuing the awesome and not so unique stream of ideas on what to do with
ChatGPT, here's a bit modified take to my previous post on self-running ChatGPT generated Python code.
This time, let's do a shell script that takes a description of what you want as a shell command, and returns just that command. Here's how it will work:
$ shai find latest 3 files
46 total tokens
ls -lt | head -n 3
$ ls -lt | head -n 3
total 1233
-rwxrwxrwx 1 root root 5505 huhti 4 2023 python-chatgpt-ai.md
-rwxrwxrwx 1 root root 10416 maalis 26 2023 golang-sveltekit-chatgpt.md
As seen, this time I'm not running the command automatically, but just returning the command. This is a bit safer, as you can inspect the command before running it. The script is quite simple:
#!/usr/bin/env python
import sys, os
from openai import OpenAI
from configparser import ConfigParser
# Read the config file openai.ini from same directory as this script
path = os.path.dirname(os.path.realpath(__file__))
config = ConfigParser()
config.read(path + '/openai.ini')
client = OpenAI(api_key=config['openai']['api_key'])
prompt = ' '.join(sys.argv[1:])
role = ('You are Ubuntu linux shell helper. Given a question, '
'answer with just a shell command, nothing else.')
chat_completion = client.chat.completions.create(
messages=[ { "role": "system", "content": role },
{ "role": "user", "content": prompt } ],
model = config['shai']['model']
)
print(chat_completion.usage.total_tokens, 'tokens:')
print(chat_completion.choices[0].message.content)
I decided GPT 3.5 Turbo is a good model for this, as it should be able to handle shell commands quite well. You also need to have a openai.ini file in the same directory as the script, with the following content:
[openai]
api_key = sk-xxx
[shai]
model = gpt-3.5-turbo
To use it, just install the OpenAI Python package with pip install openai, and then you can use the script like this:
$ chmod +x shai
$ ./shai find latest 3 files
And putting the script in your path, you can use it like any other shell
command. Enjoy!
I previously covered how to run your own ChatGPT script with Python and Golang. But what if you want to create a script that automatically runs the ChatGPT generated code? That is what I will cover in this post. The idea is really simple:
Create a script that asks for user input
Pass the input to ChatGPT
Run the ChatGPT generated code
NOTE: Please read the caveats at the end of the post before using this!
Calling The OpenAI API
First step is simple, just call the OpenAI API with the user input. If "-v" is given as the first argument, print the raw answer and usage statistics as well.
#!/usr/bin/python3
import openai
import sys
openai.api_key = 'sk-xxx'
verbose = sys.argv[1] == '-v'
prompt = ' '.join(sys.argv[2 if verbose else 1:])
resp = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are Python code generator. Answer with just the Python code."},
{"role": "user", "content": prompt},
]
)
data = resp['choices'][0]['message']['content']
if verbose: print(data, 'Usage was:', resp['usage'], sep='\n')
Parsing the Code From Response
A rather simplistic implementation looks for set of start tokens and end tokens and returns the code between them. This is not perfect, but it works as long as the ChatGPT response does not contain multiple code blocks.
# Extract lines of code from the response tagged with ``` or ```python
lines = []
ok_start = ['```', '```python', '```py', '```python3', '```py3']
started = False
for line in data.splitlines():
if not started:
if line.strip() in ok_start:
started = True
else:
if line.strip() == '```': break
lines.append(line)
Running The Code
The code is then written to a temporary file and executed. After running, the file is deleted.
import os
import tempfile
# Create a temporary file
with tempfile.NamedTemporaryFile(mode='w', delete=False) as tf:
tf.write("#!/usr/bin/env python3\n")
# Write lines to the file
for line in lines:
tf.write(line + '\n')
# Make the file executable
os.chmod(tf.name, 0o755)
# Run the file with python3
os.system(tf.name)
# Delete the file
os.unlink(tf.name)
Trying It Out
The whole script is available here for your convenience. If you are on Linux, you can chmod +x the script and run it directly. Otherwise, you can run it with python3 pyai.py:
Omitting the -v argument will only run the generated code:
$ pyai print out 10 first primes
2
3
5
7
11
13
17
19
23
29
$ pyai calculate sha1 checksum of file pyai
SHA1 checksum of pyai: db5ab334ca2af7508460d5ade8dac7fedd48bcf0
$ sha1sum pyai
db5ab334ca2af7508460d5ade8dac7fedd48bcf0 pyai
Python has pretty powerful libraries, so you can access the web as well:
$ pyai print out longest h1 tag content text from https://codeandlife.com
Using a Soundcard as an Oscilloscope to Decode 433 MHz Smart Power Plug Remote Control Signals
$ # If you want to know what the code (and usage) was, use -v in the beginning
$ pyai -v print out longest h1 tag content text from https://codeandlife.com
```python
import requests
from bs4 import BeautifulSoup
url = "https://codeandlife.com"
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")
h1_list = soup.find_all("h1")
longest_h1 = max(h1_list, key=lambda x: len(x.text))
print(longest_h1.text.strip())
```
Usage was:
{
"completion_tokens": 76,
"prompt_tokens": 41,
"total_tokens": 117
}
Using a Soundcard as an Oscilloscope to Decode 433 MHz Smart Power Plug Remote Control Signals
Yes you can read files too:
$ pyai print out contents of /etc/passwd
# And the contents of /etc/passwd come here :D
$ pyai read /etc/passwd, split by colon and print the largest 3rd entry value
65534
Conclusions and Caveats
While the code is simple and has many limitations, it is staggeringly powerful. You can do pretty much anything you can do with Python with this. But the downside is it's like the "I'm feeling lucky" version of scripting. You don't know what the code is going to do. You can't even see the code before running it. So, use this with caution! Potential drawbacks may include, but are not limited to:
Racking up your OpenAI API usage and costing you money
ChatGPT getting it wrong and the code not working
Code working but doing something you didn't expect
Overwriting (important) files
Getting really malicious code that does something bad
Currently code injection to LLMs is pretty hard, but it is not unthinkable that someone will find a way to do it. So, please be careful when using this. I am not responsible for any damage caused by this script!
OpenAI came out with ChatGPT, and wow, that is quite something. What is also remarkable is the
load the ChatGPT client is under, and how often it is "experiencing high demand".
Or just requires you to prove you are human and log in again.
You can get ChatGPT Plus for $20 a month, but hey, you can also get chat experience for $0.002 per 1000 tokens. To hit that monthly fee, you need to use 10 M tokens, which is not that far from 10 M words. That is pretty heavy use...
Using OpenAI ChatGPT (gpt-3.5-turbo) through Python API
To use the ChatGPT API, at its simplest form with Python3 you just pip install openai and create a short script:
#!/usr/bin/python3
import openai
import sys
openai.api_key = 'sk-yourkeyhere'
if len(sys.argv) < 2:
prompt = input('Prompt: ')
else:
prompt = ' '.join(sys.argv[1:])
resp = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a programming expert giving advice to a colleague."},
{"role": "user", "content": prompt}
]
)
print(resp['choices'][0]['message']['content'])
print('Usage was', resp['usage'])
You need to create credentials at OpenAI platform, enter your credit card and set a warning and hard treshold for monthly billing (I set mine to $4 and $10, respectively). But after filling your API key to the script, you can just run it:
$ python3 chat.py What is the capital of Alaska
The capital of Alaska is Juneau. However, I believe you were looking for programming advice. What specifically are you working on and what kind of advice are you seeking?
Usage was {
"completion_tokens": 34,
"prompt_tokens": 30,
"total_tokens": 64
}
Now that is pretty nice, but we can do better!
Golang client with SvelteKit frontend
In my previous Golang+SvelteKit GUI post I explored how to create a Go application acting as a web server and making a user interface with SvelteKit:
Golang has high performance and excellent set of libraries to accomplish many tasks
Cross-platform support out of the box with compact executables
SvelteKit is fast to develop as a frontend, requiring very low amount of code for rich interactive UIs
OpenAI does not produce it's own Go library, but that API as well documented and shabaronov has made an excellent Golang OpenAI API library that makes calling the API simple. It even supports GPT4, so if you have access to that, you can create a GPT4 chat client as well.
Without further ado, here's the Github repository for my GoChatGPT client. You can basically git clone://github.com/jokkebk/gochatgpt and follow the instructions in README.md to get it running, it's about 4 commands all in all.
Let's look a bit what the code does!
Golang ChatGPT Backend
Main method of the backend is nothing too complex:
Serve the SvelteKit GUI from static folder (including index.html when user requests /).
Have a chat endpoint at /chat that takes a JSON object with chat messages and passes it to OpenAI API.
Return the OpenAI [ChatGPT] response as a string to calling client.
func main() {
// Get the API key from the environment variable OPENAI_API_KEY
apiKey := os.Getenv("OPENAI_API_KEY")
client := openai.NewClient(apiKey)
http.Handle("/", http.FileServer(http.Dir("static")))
http.HandleFunc("/chat", func(w http.ResponseWriter, r *http.Request) {
var messages []openai.ChatCompletionMessage
err := json.NewDecoder(r.Body).Decode(&messages)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
ans, shouldReturn := chatGPT(client, messages)
if shouldReturn {
http.Error(w, "ChatGPT error", http.StatusBadRequest)
return
}
// Serialize the response as JSON and write it back to the client
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(ans))
})
address := "localhost:1234"
log.Printf("Starting server, go to http://%s/ to try it out!", address)
http.ListenAndServe(address, nil)
}
With the rather straightforward OpenAI, the chatGPT() function is nothing to write home about either – we get the chat messages, put them into ChatCompletion object, pass
to API and (hopefully) return the top answer (or an empty error if it failed):
Compared to Arduino Uno, the RP2040 has major advantages for this project:
Much higher clock frequency of 133 MHz means there's cycles to spare even at ~1 Mhz rates
Relatively vast SRAM memory, 264 kB vs. 2 kB
Native C SDK that is rather easy to work with
I'm using the Seeed XIAO RP2040 for this project. It is extremely compact and has a nice USB-C interface. You can see the wiring, it's just 3.3V and GND to the receiver (which luckily did work fine with that voltage) and signal to GPIO pin 0.
Note that while RP2040 pinout has 5V supply line, the GPIO pins are not 5V tolerant, so you should not power a 5V receiver and directly connect it to pin 0. A voltage divider is strongly recommended to avoid longer term damage to the RP2040 pin.
Setting up RP2040 programming environment
I basically followed the Getting started guide that was linked from the Pico SDK Github to get the Blink example working. After that, it was quite simple to set up a new project following the "Quick-start your own project", setting up CMakeLists.txt like this:
cmake_minimum_required(VERSION 3.13)
# initialize the SDK based on PICO_SDK_PATH
# note: this must happen before project()
include(pico_sdk_import.cmake)
project(joonas-pico)
# initialize the Raspberry Pi Pico SDK
pico_sdk_init()
# rest of your project
add_subdirectory(logic_analyze)
$ mkdir logic_analyze
$ cd logic_analyze
$ wget https://codeandlife.com/images/2023/logic-analyze-pico.zip
$ unzip logic_analyze-pico.zip
$ mkdir build
$ cd build
$ export PICO_SDK_PATH=../../pico-sdk
$ cmake ..
$ make
Note that this assumes you placed the example directory logic_analyze alongside your pico-sdk directory.
After running make, you should find the logic.uf2 file under logic_analyze directory and you can just drag and drop it to your RP2040 when it is in USB drive mode.
C Code for Recording GPIO Changes
The code is basically combination of what I did for Arduino and Raspberry Pi, and the hello_gpio_irq and hello_timer examples. Basic logic:
Setup stdio_init_all() (over USB, necessary definitions to enable that in CMakeLists.txt file) and wait until stdio_usb_connected() returns true.
Loop forever, asking the user over serial (USB) to press a key to start recording
Clear receive buffer
Set alarm timeout of 5 seconds to end recording if buffer hasn't been filled
Set up GPIO interrupt triggers on rising and falling edges of pin 0
In the interrupt handler, record time elapsed since last edge using time_us_64()
Once timeout is reached or buffer has been filled, disable GPIO interrupt and print out received timings.
Here's the main method:
int main() {
stdio_init_all();
while(!stdio_usb_connected());
while(true) {
printf("Press any key to start.\n");
getchar_timeout_us(100*1000000);
printf("Started!\n");
for(int i = 0; i < BUFSIZE; i++) buf[i] = 0; // Clear
pos = 0;
timer_fired = false;
add_alarm_in_ms(5000, alarm_callback, NULL, false);
gpio_set_irq_enabled_with_callback(0,
GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL,
true, &gpio_callback);
while(!timer_fired && pos <= BUFSIZE) tight_loop_contents();
gpio_set_irq_enabled(0, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, false);
printf("Ended.\n");
for(int i = 0; i < pos; i++) printEdge(buf[i], i&1);
}
}
Alarm and interrupt callbacks are rather straightforward:
The security of sensitive information is of utmost importance today. One way to enhance the security of
stored passwords is by using PBKDF2 with SHA256 HMAC, a cryptographic algorithm that adds an extra layer of protection if the password hashes are compromised. I covered recently how to calculate PBKDF2 yourself with Python, but today needed to do the same with Javascript.
CryptoJS documentation on PBKDF2 is as scarce as everything on this library, and trying out the 256 bit key example with Node.js gives the following output:
$ node
Welcome to Node.js v19.6.0.
Type ".help" for more information.
> const crypto = require('crypto-js')
undefined
> const key = crypto.PBKDF2("password", "salt", {keySize: 256/32, iterations: 4096})
undefined
> crypto.enc.Hex.stringify(key)
'4b007901b765489abead49d926f721d065a429c12e463f6c4cd79401085b03db'
Now let's recall what Python gave here:
$ python
Python 3.10.7 (tags/v3.10.7:6cc6b13, Sep 5 2022, 14:08:36) [MSC v.1933 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import hashlib
>>> hashlib.pbkdf2_hmac('sha256', b'password', b'salt', 4096).hex()
'c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a'
>>>
Uh-oh, they don't match! Now looking at pbkdf2.js in the CryptoJS Github source, we can see that the algorithm defaults to SHA-1:
Seeing how keySize and iterations are overridden, we only need to locate the SHA256 in proper namespace to guide the implementation to use SHA256 HMAC instead:
$ node
Welcome to Node.js v19.6.0.
Type ".help" for more information.
> const crypto = require('crypto-js')
undefined
> const key = crypto.PBKDF2("password", "salt", {keySize: 256/32,
... iterations: 4096, hasher: crypto.algo.SHA256})
undefined
> crypto.enc.Hex.stringify(key)
'c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a'
Awesome! Now we are ready to rock'n'roll with a proper hash function.