<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Kevin Coder | tutorials, thought experiments & tech ramblings]]></title><description><![CDATA[I've been programming for 15+ years, and I'm still as curious as day one. Follow along as I dive into everything from AI to Django to Golang to Linux, and whatever else interests me in between.

This isn't your typical blog. I don't care much about SEO; I'm passion-driven, so I write about whatever interests me at the time, and those interests change often! Some posts are tutorials, some are quick references, and some are just my thoughts and ramblings on the current tech landscape.]]></description><link>https://kevincoder.co.za</link><generator>RSS for Node</generator><lastBuildDate>Sat, 11 Apr 2026 05:35:07 GMT</lastBuildDate><atom:link href="https://kevincoder.co.za/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Golang: Building a windows parental control app using wails]]></title><description><![CDATA[C# is one of my favorite languages; if you're building Windows apps, you're probably better off with C#, but I'm kind of rusty, and WinForms is old school now, so I've no choice but to use WPF 😔.
The]]></description><link>https://kevincoder.co.za/golang-building-a-windows-parental-control-app-using-wails</link><guid isPermaLink="true">https://kevincoder.co.za/golang-building-a-windows-parental-control-app-using-wails</guid><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Mon, 09 Mar 2026 15:53:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6508a821cf1020be165986a9/57db2af5-d58d-4bb2-8d76-3d2cf9d7735d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>C# is one of my favorite languages; if you're building Windows apps, you're probably better off with C#, but I'm kind of rusty, and WinForms is old school now, so I've no choice but to use WPF 😔.</p>
<p>The problem is I don't like XAML, the XML-based markup language used by WPF (Windows Presentation Foundation). WPF is basically a UI framework for building Windows desktop applications, and apparently, the Dotnet crowd prefers this over WinForms these days 🤷.</p>
<p>Luckily! I discovered <a href="https://wails.io/">wails</a>, an interesting project that essentially uses a WebView to build Windows Forms UIs in CSS and JavaScript. Additionally, you can connect those UI elements to a Go backend and even access native Windows APIs, similar to how you would have a code-behind button click handler in C# WinForms.</p>
<p>It's also no secret that I'm a big fan of Golang. It's fast, it's clean, and I enjoy writing in Go all the time. I've built everything from scrapers to a full-on vector database in Go, so why not take a whack at building a desktop app too 🤷.</p>
<p>Anyway, I started experimenting with building a simple <strong>parental control app</strong>. The web can be a dangerous place, and I have an 8-year-old who loves binge-watching cartoons on my laptop after sucking the life out of every battery-operated mobile device in the house 😆.</p>
<p>So, as you can imagine, I’d like to block all the bad junk on the internet or at least filter out the obvious bad sites and inappropriate content. This article is merely an experimental log of my journey and is not meant to be used in an actual production app. You probably would be better off subscribing to one of the existing parental control apps on the market.</p>
<h2>Which approach should we use?</h2>
<p><strong>TLDR:</strong> We'll use Windows API's to get a list of active windows, then scan their titles, and finally close running programs that contain inappropriate content.</p>
<p>There are many approaches to building a solid parental control app; as mentioned, we're just experimenting for now and not looking to build anything too overly complicated. Some options we have:</p>
<ul>
<li><p><strong>A VPN service.</strong> With a VPN-based approach, all network traffic is routed through your app. This allows your app to inspect the connections being made and potentially analyze the content being accessed. In theory, this means you could decrypt HTTPS traffic and inspect full web pages, including the document body, images, videos, and other resources. As you can imagine, this is a pretty solid approach and can help you build a fairly robust system.</p>
</li>
<li><p><strong>A DNS service</strong>. While the VPN approach is the most comprehensive solution, decrypting HTTPS traffic comes with serious security risks. If your VPN implementation is compromised, it could effectively create a <strong>man-in-the-middle (MITM) attack</strong>, allowing someone to intercept sensitive user data. Instead, we could just build a lightweight DNS service that allows us to read the relevant hostnames (mywebsite.com). This way we can safely monitor incoming traffic and block bad websites without actually decrypting anything.</p>
</li>
<li><p><strong>ETC Hosts.</strong> Another simple approach that works surprisingly well. Instead of building and managing a DNS service (which is slightly more complex), we can simply modify a small system file called <code>hosts</code>, located at:</p>
<p><code>C:\Windows\System32\drivers\etc\hosts</code></p>
<p>As a web developer, I use this file all the time to map local domains during development. The same mechanism can also be used to block websites. By mapping bad domains to <code>127.0.0.1</code> any request to those domains will just be redirected back to the local machine, effectively preventing the site from loading. I actually got this working nicely in the C# version.</p>
</li>
<li><p>Window titles. Even simpler, and this is the approach we'll use in our Golang program. With this approach, you basically get a list of all running windows and inspect their title. Many websites include SEO keywords in the page title, which often contains the bad words we're looking for. By scanning these titles for certain keywords, we can easily detect when inappropriate content is being viewed and respond accordingly.</p>
</li>
</ul>
<h2>Setting up Wails</h2>
<p>Installing wails is a breeze, just like any other Go library:</p>
<pre><code class="language-shell">go install github.com/wailsapp/wails/v2/cmd/wails@latest
wails init -n parental-control-app -t vanilla-ts
</code></pre>
<p>The second line will set up a Skeleton project for you, which would look like this:</p>
<pre><code class="language-shell">├── README.md
├── app.go
├── go.mod
├── go.sum
├── main.go
├── lib
│   └── program_scanner.go
├── myproject.exe
├── parental-control-app
│   ├── README.md
│   ├── app.go
│   ├── go.mod
│   ├── go.sum
│   ├── main.go
│   └── wails.json
└── wails.json
</code></pre>
<p>I excluded the "frontend" folder from the tree structure. This is where your "UI" lives:</p>
<pre><code class="language-shell">├── index.html
├── package-lock.json
├── package.json
├── package.json.md5
├── src
│   ├── app.css
│   ├── assets
│   │   ├── fonts
│   │   │   ├── OFL.txt
│   │   │   └── nunito-v16-latin-regular.woff2
│   │   └── images
│   │       └── logo-universal.png
│   ├── main.ts
│   ├── style.css
│   └── vite-env.d.ts
├── tsconfig.json
└── wailsjs
    ├── go
    │   └── main
    │       ├── App.d.ts
    │       └── App.js
    └── runtime
        ├── package.json
        ├── runtime.d.ts
        └── runtime.js
</code></pre>
<p>I chose the vanilla template to keep things simple, but Wails does support React, Vue, and a bunch of other JS frameworks too, so pick your poison accordingly ✊</p>
<p>When you open up the <code>main.go</code> file, you'll see something like this:</p>
<pre><code class="language-go">package main

import (
	"embed"

	"github.com/wailsapp/wails/v2"
	"github.com/wailsapp/wails/v2/pkg/options"
	"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)

//go:embed all:frontend/dist
var assets embed.FS

func main() {
	// Create an instance of the app structure
	app := NewApp()

	err := wails.Run(&amp;options.App{
		Title:  "My App",
		Width:  1024,
		Height: 768,
		AssetServer: &amp;assetserver.Options{
			Assets: assets,
		},
		BackgroundColour: &amp;options.RGBA{R: 27, G: 38, B: 54, A: 1},
		OnStartup:        app.startup,
		Bind: []interface{}{
			app,
		},
	})

	if err != nil {
		println("Error:", err.Error())
	}
}
</code></pre>
<p>Fairly straightforward, this is basically setting up a standard Windows with a title, a height background, and so forth.</p>
<p>The most interesting thing in the boilerplate code is this part:</p>
<pre><code class="language-go">		Bind: []interface{}{
			app,
		},
</code></pre>
<p>This is where the JavaScript magic happens. We bind our <code>app</code> struct, so its methods become callable from the frontend JS code.</p>
<p>Let's peek inside <code>app.go</code>:</p>
<pre><code class="language-go">package main

import (
	"context"
	"fmt"
	"myproject/models"
)

// App struct
type App struct {
	ctx context.Context
}

// NewApp creates a new App application struct
func NewApp() *App {
	return &amp;App{}
}

// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
	a.ctx = ctx
}

// Greet returns a greeting for the given name
func (a *App) Greet(name string) string {
	return fmt.Sprintf("Hello %s, It's show time!", name)
}
</code></pre>
<p>Nothing fancy, but pay close attention to the "Greet" method. This is a function that is now exposed to the frontend, and you can call it from anywhere in your JS/TypeScript code just like any other function.</p>
<p>Next, look inside frontend/src/main.ts:</p>
<pre><code class="language-typescript">window.greet = function () {
    // Get name
    let name = nameElement!.value;

    // Check if the input is empty
    if (name === "") return;

    // Call App.Greet(name)
    try {
        Greet(name)
            .then((result) =&gt; {
                // Update result with data back from App.Greet()
                resultElement!.innerText = result;
            })
            .catch((err) =&gt; {
                console.error(err);
            });
    } catch (err) {
        console.error(err);
    }
};
</code></pre>
<p>Notice the "Greet" in the try catch! We are effectively calling the Greet method on our App struct 🎉, how powerful right?</p>
<h2>Okay, but where’s the native Windows stuff?</h2>
<p>We managed to create a UI in TypeScript and then fire a function in Golang, but you could just launch a headless browser and use WASM to do the same thing, right? So, what's the point?</p>
<p>Yee of little faith 😏!</p>
<p>Since we can run Go code and Go has bindings for Win32 APIs, we can effectively access almost any native functionality we need.</p>
<p>If you don't know what Win32 is, basically it's a standard set of APIs that the Windows OS heavily relies on to manage low-level systems like the UI, networking, security, and so forth. Whether you've used XP, Vista, 7, 10, or 11, you've probably noticed that decade-old programs still run perfectly. This is largely thanks to Win32, enabling Microsoft's exceptional backward compatibility.</p>
<p>Essentially, it's a collection of DLLs, or Dynamic Link Libraries. If you think of this in terms of Golang, a DLL is similar to a package containing code that you can import and use. The key difference is that DLLs are precompiled and shared across programs at runtime, rather than being compiled into your binary like Go packages, but the concept is similar.</p>
<p>Golang provides a way to interact with Win32 via this package:</p>
<pre><code class="language-go">golang.org/x/sys/windows
</code></pre>
<p>And to enable access to Win32 API's, it's as easy as follows:</p>
<pre><code class="language-go">user32 = windows.NewLazySystemDLL("user32.dll")
</code></pre>
<p>In this case, we import <code>user32.dll</code>, which is a DLL that gives us access to the general Windows UI and windowing system, this is exactly what we'll need to access running programs and essential information about those programs to decide whether it's bad content or not.</p>
<blockquote>
<p><code>NewLazySystemDLL</code> allows Go to load the DLL lazily, meaning the DLL and its functions are only loaded when they are actually needed instead of when the program starts.</p>
</blockquote>
<p>Moving along, lets setup references in Go to the functions we need to access:</p>
<pre><code class="language-go">var (
	user32                       = windows.NewLazySystemDLL("user32.dll")
	procEnumWindows              = user32.NewProc("EnumWindows")
	procIsWindowVisible          = user32.NewProc("IsWindowVisible")
	procGetWindowTextLengthW     = user32.NewProc("GetWindowTextLengthW")
	procGetWindowTextW           = user32.NewProc("GetWindowTextW")
	procGetWindowThreadProcessId = user32.NewProc("GetWindowThreadProcessId")
	procPostMessageW             = user32.NewProc("PostMessageW")
)
</code></pre>
<p>In addition to importing the <code>user32.dll</code> DLL, we’re also creating pointers to several Win32 functions. These act as a bridge between Go and the native Windows API. Once defined, we can call these functions and pass arguments to them almost as if they were normal Go functions.</p>
<ul>
<li><p><code>EnumWindows</code> - Will give us a list of running programs.</p>
</li>
<li><p><code>IsWindowVisible</code> - Checks whether a window is visible by Windows, even if it’s minimized or behind other windows.</p>
</li>
<li><p><code>GetWindowTextLengthW</code> - Basically <code>string.length</code> of a Windows title bar, this allows us to allocate a buffer of the correct size before reading the text.</p>
</li>
<li><p><code>GetWindowTextW</code> - As you probably guessed by now, this is the actual title bar text.</p>
</li>
<li><p><code>GetWindowThreadProcessId</code> - We'll use this to get the program's process ID so that we can terminate the program if we detect bad content.</p>
</li>
</ul>
<h2>A working prototype</h2>
<p>Finally, here's the full Go code for V1 of our parent control app:</p>
<pre><code class="language-go">//go:build windows

package models

import (
	"strings"
	"syscall"
	"unsafe"

	"golang.org/x/sys/windows"
)

var (
	user32                   = windows.NewLazySystemDLL("user32.dll")
	procEnumWindows          = user32.NewProc("EnumWindows")
	procIsWindowVisible      = user32.NewProc("IsWindowVisible")
	procGetWindowTextLengthW = user32.NewProc("GetWindowTextLengthW")
	procGetWindowTextW       = user32.NewProc("GetWindowTextW")
	procPostMessageW         = user32.NewProc("PostMessageW")
)

func ScanWindowTitles(hwnd uintptr, lparam uintptr) uintptr {
	vis, _, _ := procIsWindowVisible.Call(hwnd)

	if vis == 0 {
		return 1
	}

	tlen, _, _ := procGetWindowTextLengthW.Call(hwnd)

	if tlen == 0 {
		return 1
	}

	buf := make([]uint16, tlen+1)
	procGetWindowTextW.Call(hwnd, uintptr(unsafe.Pointer(&amp;buf[0])), tlen+1)
	title := windows.UTF16ToString(buf)
	if title == "" {
		return 1
	}

	if strings.Contains(title, "lotto") {
		procPostMessageW.Call(hwnd, 0x0010, 0, 0)
	}

	return 1

}

func RunBadAppChecker() error {

	windowScannerCallback := syscall.NewCallback(ScanWindowTitles)
	result, _, err := procEnumWindows.Call(windowScannerCallback, 0)
	if result == 0 {
		if err != nil &amp;&amp; err != syscall.Errno(0) {
			return err
		}
		return syscall.EINVAL
	}

	return nil
}
</code></pre>
<p>The naming convention is a bit weird; it comes from the C-style approach of Win32. Here's a breakdown block-by-block of what's going on.</p>
<p>The logic behind this code:</p>
<ul>
<li><p><code>windowScannerCallback := syscall.NewCallback(ScanWindowTitles)</code> ~ converts a Go function into a Windows-compatible callback function, essentially a pointer, so it can be passed to Win32 APIs correctly. Additionally, this prevents Golang's garbage collection from getting "confused". Since we sort of tunnelling through to native libraries, the Garbage collector isn't fully aware of its state, thus it might try to collect or move this around in memory when it shouldn't.</p>
</li>
<li><p><code>result, _, err := procEnumWindows.Call(windowScannerCallback, 0)</code> ~ We basically loop through all open windows and run this callback on each one. Similar to an array map function. The second argument <code>0</code> (of type <code>uintptr</code> ) is just an additional state argument we can pass to the function; in this case, we are not using it, so we set it to 0.</p>
</li>
</ul>
<blockquote>
<p>💡 The <code>uintptr</code> type can hold a large integer representing a memory address. In Go terms, when you pass <code>&amp;someVariable</code> to get a pointer, you're getting a memory address - <code>uintptr</code> is just that address stored as a plain integer.</p>
</blockquote>
<p>Looking inside <code>ScanWindowTitles</code> We first check if the current window is visible:</p>
<pre><code class="language-go">	vis, _, _ := procIsWindowVisible.Call(hwnd)

	if vis == 0 {
		return 1
	}
</code></pre>
<p>Next, we check if the window has a title:</p>
<pre><code class="language-go">	tlen, _, _ := procGetWindowTextLengthW.Call(hwnd)

	if tlen == 0 {
		return 1
	}
</code></pre>
<blockquote>
<p>💡 <code>hwnd</code> ~ is just a Windows convention for an ID. Think of it like HTML: you give a <code>&lt;div&gt;</code> an ID to reference it in CSS/JS. Similarly, <code>hwnd</code> is an ID that lets you reference and interact with a specific window through Windows API calls.</p>
</blockquote>
<p>At this point, we now have a window that's <code>a)</code> visible and <code>b)</code> has a title. So, this means we can scan it for bad words.</p>
<p><em>PS: Much of what you've seen thus far in this code is ceremonial C/C++, since Windows is built on these languages for historical and performance reasons. As Go developers, we're very privileged to have a high-performance language that's also clean and abstract enough without compromising too much on performance.</em></p>
<p><em>Sure, C/C++ will outperform Go in nearly all cases, but for modern computing, especially for web developers, that kind of raw performance is often overkill. Nonetheless, since we are interfacing with Win32, we've just got to swallow that hard pill of C/C++ style programming!</em></p>
<p>Anyway, getting back to our window title scanner, we have this weird-looking block of code:</p>
<pre><code class="language-go">	buf := make([]uint16, tlen+1)
	procGetWindowTextW.Call(hwnd, uintptr(unsafe.Pointer(&amp;buf[0])), tlen+1)
	title := windows.UTF16ToString(buf)
	if title == "" {
		return 1
	}
</code></pre>
<p>We create a buffer of type <code>uint16</code> - essentially an array with a fixed size of elements that will hold the characters making up the window title. Windows uses UTF-16 encoding, which encodes each character as one or two 16-bit values.</p>
<p>The length of this buffer will be <code>n+1</code> where <code>n</code> is the number of the characters in the title plus one **null terminator (**like an invisible character that has no relevance to the actual string besides being a marker). The null terminator is a bit weird for us Go developers, but this is how C-style strings work; they need a way to know where the string ends.</p>
<pre><code class="language-go">procGetWindowTextW.Call(hwnd, uintptr(unsafe.Pointer(&amp;buf[0])), tlen+1)
if title == "" {
  return 1
}
</code></pre>
<p>We're essentially extracting the window title and writing it to the buffer we created earlier - similar to an I/O file write operation. We return one if nothing came through; this is a Win32 standard way of returning status codes. In this case, we return 1, which means just skip ahead to the next window.</p>
<p>Now, we finally get to the checking for bad words part. I made this super simple, but in reality, you'd probably want to check if the value is in a list or do a DB lookup of some sort.</p>
<pre><code class="language-go">
if strings.Contains(title, "lotto") {
   procPostMessageW.Call(hwnd, 0x0010, 0, 0)
}

// close window function
</code></pre>
<p><code>0x0010</code> ~ is a hexadecimal message code (Constant in Win32 API: <code>WM_CLOSE</code>) that tells Windows we want to close that window.</p>
<p><code>PostMessageW</code> is sort of like a postman; we can send messages to the underlying WIN32 API. This API does not wait for the response, so it's essentially a message queue and asynchronous. Since older systems had memory constraints, old-school C/C++ often used constant hexadecimal or integer values to represent actions.</p>
<h2>The C# approach</h2>
<p>While this article is not really about C#, I did also experiment with the Dotnet ecosystem to see how far I can take this. It was very easy, actually 🤔 Although to be honest, I got a lot of help from Claude Code.</p>
<blockquote>
<p>❗ Beginner developers be careful! I don't suggest using AI to learn initially. I strongly suggest reading books first, and spending 3-6 months coding on your own. I use AI to learn sometimes and generate code, but I have a lot of programming experience. I may not fully understand C#'s syntax and API's, however, I understand the logic behind what is happening and can easily steer the AI in the right direction when it goes off-track.</p>
</blockquote>
<p>I didn't cover this aspect in the Go code yet, but here we go:</p>
<pre><code class="language-csharp">var hosts = File.ReadAllLines(Config.HostsPath).ToList();

hosts.RemoveAll(line =&gt; line.Contains(Config.HostsMarker));
hosts.Add($"\n{Config.HostsMarker} - START");

foreach (var url in Config.BlockedUrls) {
 hosts.Add($"127.0.0.1 {url} {Config.HostsMarker}");
 hosts.Add($"{Config.HostsMarker} - END");
 File.WriteAllLines(Config.HostsPath, hosts);
}
</code></pre>
<p>In our C# version, we open the <code>hosts</code> file and write a list of bad hostnames to the file, each entry points to 127.0.0.1 (localhost). This ensures that whenever you try to visit any of these domains in your browser, the request gets redirected to your own machine instead of the actual website.</p>
<p>Since nothing is typically running on port 80 locally, the browser will either show an error, display nothing, or render whatever service you happen to have running on that port.</p>
<p>Before writing, we also search all lines that contain our custom marker and delete them. The marker is simply a string like: <code># BAD DOMAINS</code> that we append to the end of each line, so that we can keep track of any changes we make to the file. This is important because the file can be edited by the OS or users at any time, and we want to ensure that we don't interfere with any lines not managed by the program.</p>
<p>For the title scanner, we can implement it in this way:</p>
<pre><code class="language-csharp">
foreach (var proc in Process.GetProcesses())
{
    try
    {
        var title = proc.MainWindowTitle;

        if (CheckIfBannedKeyword(title, out string? matchedKeyword))
        {
            if (matchedKeyword != null)
                BlockContent(proc.MainWindowHandle, proc.ProcessName, title, "Keyword", matchedKeyword);
        }

    }
    catch { }
}
</code></pre>
<pre><code class="language-csharp">static bool CheckIfBannedKeyword(string text, out string? matchedKeyword)
{
    matchedKeyword = null;
    if (string.IsNullOrEmpty(text)) return false;
    var lower = text.ToLower();

    foreach (var term in Config.BlockedTerms)
    {
        if (lower.Contains(term))
        {
            matchedKeyword = term;
            return true;
        }
    }

    return false;
}
</code></pre>
<pre><code class="language-csharp">static void BlockContent(IntPtr windowHandle, string browserName, string windowTitle, string triggerType, string triggerValue)
{
    if (windowHandle != IntPtr.Zero)
    {
        db.LogViolation(triggerType, triggerValue, browserName, windowTitle);

        SetForegroundWindow(windowHandle);
        Thread.Sleep(100);

        keybd_event(VK_CONTROL, 0, 0, UIntPtr.Zero);
        keybd_event(VK_W, 0, 0, UIntPtr.Zero);
        keybd_event(VK_W, 0, KEYEVENTF_KEYUP, UIntPtr.Zero);
        keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, UIntPtr.Zero);
    }
}
</code></pre>
<p>We:</p>
<ul>
<li><p>First, get a list of running processes.</p>
</li>
<li><p>Next, get the Window title and check if it contains any strings that match a word in our banned keywords list.</p>
</li>
<li><p>Finally shutdown that process.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Why AGI is a Pipe Dream and what we should build instead]]></title><description><![CDATA[I have been thinking about where we are at with AI in 2026; I think most models have now reached their intelligence ceiling. They’ll get incrementally better over time, sure, but there’s a big difference between 10% to 80% and 80% to 100%.
In most te...]]></description><link>https://kevincoder.co.za/why-agi-is-a-pipe-dream-and-what-we-should-build-instead</link><guid isPermaLink="true">https://kevincoder.co.za/why-agi-is-a-pipe-dream-and-what-we-should-build-instead</guid><category><![CDATA[tiny llm]]></category><category><![CDATA[agi]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Thu, 12 Feb 2026 08:34:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770885008482/fdf2163c-45d0-4cff-9cad-7ba4bb7b6bfc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have been thinking about where we are at with AI in 2026; I think most models have now reached their intelligence ceiling. They’ll get incrementally better over time, sure, but there’s a big difference between 10% to 80% and 80% to 100%.</p>
<p>In most tech projects, the first 80% is relatively straightforward: CRUD operations, database schema design, and boilerplate code. The real challenge lies in the remaining 20%, the core business logic, and the actual problem being solved. That part demands deep domain knowledge, experience, and sustained effort ~ things LLMs cannot simply replicate.</p>
<p>LLMs have gotten bigger, more resource-intensive, and are eating up GPUs like there’s no tomorrow! They’ve swallowed up the entire internet, but still nowhere close to replacing human beings. AGI is not coming; it’s a get-rich-quick pipe dream that was concocted by all these billionaire tech CEOs.</p>
<p>In reality, throwing more compute and data at models is not making them that much smarter. In fact, we are probably going to run out of good-quality public data because of the fall of Stack Overflow and smaller publishers that are going out of business.</p>
<p>In this article, let’s talk about a potential solution to many of these AI problems.</p>
<h2 id="heading-agi-is-not-coming">AGI is not coming</h2>
<p>Even though I’m not a data science expert, I have worked with LLMs for a better part of 3-4 years now and have done tons of research into how they work and the field in general. This gives me some basis to comment on AGI and the future of AI, I think?</p>
<p>AGI is a pipe dream because LLMs are just prediction machines at the end of the day. Don’t get me wrong, the current iteration of this tech is really amazing and works really well in certain use cases, but it’s far from perfect and nowhere near what a human can do.</p>
<p>They have no worldview or real understanding of the data. They are trained on billions of examples, questions, answers, and fragments of information. They operate by breaking sentences into tokens, mathematically comparing inputs to learned patterns, and returning a human-like response through statistical grounding.</p>
<p>It’s a fancy algorithm; this is why AGI with the current architecture is impossible.</p>
<p>Secretly, AI companies have accepted this fact. They say many things in the media, but in reality, models are becoming commodities, and AI companies are just building ecosystems around these, so that they can lock in those subscriptions and keep you in their environment.</p>
<p>Think about it, why build Claude Code, a tool for developers, if AGI is going to replace all these developers, who then is going to pay the subscription fees?</p>
<p>AGI is not coming, and LLMs are becoming commodities; so, what’s next?</p>
<h2 id="heading-agi-is-fueling-public-anxiety">AGI is fueling public anxiety</h2>
<p>People are scared of AI, let alone AGI, because every time you tune into the news, YouTube, TikTok, or some other media platform, you see big tech CEO’s making outlandish claims about how AI is so close to becoming AGI, and how they want everyone to use this tech, and if you don’t, you’re missing out and will likely be replaced by AI.</p>
<p>These claims alienate potential users rather than attracting them. Additionally, companies like Microsoft have been criticized for poor implementations of their AI offerings and basically just forcing AI into everything! even in contexts where it really adds no value, and makes the user experience suck so much more!</p>
<p>This pushes the average Joe towards two extremes: either using the technology inappropriately, such as relying on it for critical medical advice, or dismissing it altogether as yet another unreliable app that only works some of the time.</p>
<p>The truth is that AI is not as important as big tech CEOs would like you to think. Now, don’t get me wrong, it’s a powerful tool and can be really useful when used correctly, but it’s not as impactful as something like mobile phones or even the web.</p>
<p>Mobile phones are part of everyday life; we use them to text, communicate, socialize, consume media, and also as a business tool. The use cases are endless and widespread, and while I was born in a world where mobile phones didn’t originally exist, I still value this device enough to confidently say I just cannot function day-to-day without it!</p>
<p>I cannot say the same about AI. Sure, it makes my life a little easier, but its use cases are very niche, and for most people, they’re just using it as a Google++ or social media content generator, hardly life-changing!</p>
<p>I would consider it as a value-add rather than a full-blown platform on its own.</p>
<h2 id="heading-tiny-llms-are-the-future">Tiny LLMs are the future</h2>
<p>One of the biggest problems with LLMs is that they are GPU hungry. Your typical LLM, like Claude Opus, has been trained on trillions of tokens containing data from the entire public web and then some.</p>
<p>Yet I’m just using Claude code to generate some PHP or Python code; do I really need a model trained on trillions of tokens for that? The energy and infrastructure costs are significantly high, and it’s hard not to question whether this arms race toward AGI is worth it.</p>
<p>Instead, we should pursue a different approach: treat frontier models primarily as statistical engines, paired with a strong baseline of intelligence and tool-calling capabilities.</p>
<p>So basically, a kind of “Flash” or “Mini” model similar to GPT 5 MINI or Gemini 2.5 Flash. The goal would be a model capable of basic probabilistic generation and lightweight reasoning, without the cost and complexity of full frontier-scale model like Opus.</p>
<p>This will reduce the amount of GPU resources we need, making these models more scalable and affordable for mass adoption.</p>
<h2 id="heading-knowledge-packs-can-solve-most-issues">Knowledge packs can solve most issues</h2>
<p>This brings me to the central question: could tiny LLMs replace larger models like Sonnet or Opus for most use cases? The challenge is that smaller models lack the extensive knowledge corpus of their larger counterparts, constraining their performance across many tasks. They're better suited for narrow, specialized use cases.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770885052753/4eb9c593-8af9-4acd-99fc-a1fc9f4daa2d.png" alt class="image--center mx-auto" /></p>
<p>This is where knowledge packs come in. A knowledge pack is like a black box of pretrained weights; think of it like a plugin. You don’t need to know what makes up the plugin, or how it even works; all you need is a consistent API that the LLM can talk to, to retrieve the knowledge it needs as the need arises.</p>
<p>For example, a knowledge pack for the PHP programming language might be 20-30GB in size, which can comfortably fit on a modern laptop or computer. When you’re working in Laravel, you would then point the LLM to that knowledge pack, and it should naturally then scope its knowledge to that pack.</p>
<p>Whenever it needs to generate code or answer a question, it then only needs to look up information in that pack and not get confused by information that may look similar but is not related.</p>
<p>To give you a simple example: Let's take a random name: "Bob Barker." If I ask a large LLM, "Who is Bob Barker?", the model will generate a response based on patterns it learned during training from sources like LinkedIn, Wikipedia, Google searches, and countless other documents.</p>
<p>A frontier model might have 100B+ parameters because it was trained to handle everything from PHP programming to celebrity trivia to medical questions. Each time you prompt this model, it processes the input by running computations across all these parameters, requiring significant compute.</p>
<p>Now, imagine instead you have a much smaller base model, say 2B parameters, that was trained primarily on Wikipedia. This model would use a fraction of the computational resources at inference time simply because there are fewer parameters to process. It would also likely give more accurate answers because its training was concentrated on a specific domain, using a much narrower and more focused dataset (almost like fine-tuning).</p>
<p>This is where knowledge packs come in. Instead of one massive model trying to encode all human knowledge in its weights, you'd have a lean base model paired with specialized knowledge modules that can be loaded as needed. When you're asking about Bob Barker, you'd activate the "general knowledge" pack. When you're coding in Laravel, you'd load the "PHP/Laravel" pack and so on.</p>
<p>This isn't quite RAG - you're not doing runtime retrieval from a vector database, instead, you're swapping in pretrained weights or specialized model components that give the base model deep expertise in specific domains, while keeping the active computational footprint small.</p>
<p>The only problem is if you loaded a PHP knowledge pack, but the user asks a CSS question. What does the model do?</p>
<p>I think we can extend the tool-calling capabilities of these models to understand knowledge pack routing. Each knowledge pack would contain metadata that the model uses to determine how to route each prompt request. However, this mechanism would be built into the model itself and wouldn't pollute the context window (similar to how MCP can pollute the context window by publishing too many tools or resources).</p>
<h2 id="heading-knowledge-packs-can-fix-funding-roadblocks">Knowledge packs can fix funding roadblocks</h2>
<p>Whether you talk about Google AI overview or ChatGPT, it’s a reality that most of this data was kind of “stolen” from publishers, even if they cite the authors, most end-users won’t even bother visiting the actual website. Everyone is obsessed with instant gratification.</p>
<p>LLM companies used public data that publishers, developers, artists, and authors, in general, have spent thousands of hours creating; models usually obscure the information enough so that it cannot be easily associated with the original work, and when it can be, they cite the content created as a source.</p>
<p>These small content creators never get a dime for their hard work, and many have to either downsized or shut down their website entirely because they just aren’t getting enough direct traffic anymore, and this is only going to get worse as the trust in LLMs grow.</p>
<p>Allowing independent publishers to publish their own knowledge packs provides a mechanism for them to get back some of this lost revenue.</p>
<p>I, as an individual, can choose to buy a knowledge pack from an open-source shop or subscribe to a monthly subscription to get the latest content for a small fee, instead of paying $20-$100 to these big tech companies that just never contribute back to these communities or individual artisans.</p>
<p>Sure, this probably will inflate the monthly $20 Claude subscription, but with the big players on board, I’m sure we can work out a competitive funding model.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Not to be repetitive, but AGI is not coming!</p>
<p>We need to move away from this big LLM idea and focus on building smaller, narrowly focused tiny LLMs that can be paired with specialized knowledge packs. This approach offers a path toward more sustainable, affordable, and ethically sound AI.</p>
<p>One that reduces computational waste, empowers content creators, and delivers better results for specific use cases. The future of AI isn't about building a giant AGI model that replaces humans, it's about using AI as a tool, optimized to help both free and commercial users drive real value from the product in their daily lives.</p>
<p>It’s about moving past the hype and capitalist ideals and building a future with AI responsibly!</p>
]]></content:encoded></item><item><title><![CDATA[Django cheat sheet]]></title><description><![CDATA[I move around frameworks quite a bit, usually between Golang, Next.js, Laravel, and Django. So I forget stuff all the time. In this article, I want to throw together a cheat sheet of sorts with some r]]></description><link>https://kevincoder.co.za/django-cheat-sheet</link><guid isPermaLink="true">https://kevincoder.co.za/django-cheat-sheet</guid><category><![CDATA[Python]]></category><category><![CDATA[Django]]></category><category><![CDATA[#django-tailwind]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Sat, 29 Nov 2025 11:49:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6508a821cf1020be165986a9/afa5f2b1-afcd-46df-b45d-9078e41e6970.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I move around frameworks quite a bit, usually between Golang, Next.js, Laravel, and Django. So I forget stuff all the time. In this article, I want to throw together a cheat sheet of sorts with some random things I use in Django; hopefully, it’s also useful to you!</p>
<p>I’ll keep this article updated as I go along, so please consider joining my newsletter. The purpose of the newsletter is just to auto email you whenever I add new content on the blog or update existing articles.</p>
<h2>Model basics</h2>
<p>In Django, we use a model class to represent the structure of a database table, and each field represents a column in that table. When you create an instance of this class, you effectively represent one row of data from that table.</p>
<p>Note: By default, Django creates the <code>id</code> field as the primary key, so you don't need to manually set a primary key field.</p>
<pre><code class="language-python">class ContactForm(models.Model): 
   full_name = models.CharField(max_length=100) 
   message = models.CharField(max_length=500) 
   contact_number = models.CharField(max_length=15, blank=True, null=True)
   email = models.CharField(max_length=255)
   date_submitted = models.DateTimeField(auto_now_add=True, null=True)
</code></pre>
<p>Blank versus null?</p>
<ol>
<li><p><code>null</code> - Can store <code>NULL</code> in the database. If <code>False</code> Then the DB column will be created with "not null", e.g. <code>full_name VARCHAR(100) NOT NULL</code>. Django default: <code>False</code>.</p>
</li>
<li><p><code>blank</code> - Means the field can be empty in forms if set to <code>True</code> . Django default: <code>False</code></p>
</li>
</ol>
<p>When adding/changing model fields, you must run the following to generate a migration:</p>
<pre><code class="language-python">python manage.py makemigrations
</code></pre>
<p>If there are migrations, then also run:</p>
<pre><code class="language-python">python manage.py migrate
</code></pre>
<p>Run this to access your database's shell, so that you can use RAW SQL and navigate your database directly:</p>
<pre><code class="language-python">python manage.py dbshell
</code></pre>
<p>Django automatically assigns a primary key to your table, and the default field name is <code>id</code>. You can off course, change this by setting <code>primary_key=True</code> on another field in your model:</p>
<pre><code class="language-python">class Order(models.Model): 
    total_price = models.DecimalField(max_digits=18, decimal_places=2) 
    order_reference = models.CharField(max_length=50, primary_key=True)
</code></pre>
<p>If you want to represent a relationship between models, you can set this on children as follows:</p>
<pre><code class="language-python">class OrderItem(models.Model): 
    order = models.ForeignKey(Order, on_delete=models.CASCADE) 
    item_name = models.CharField(max_length=100) 
    item_price = models.DecimalField(max_digits=18, decimal_places=2) 
    quantity = models.IntegerField(default=1)

class UserProfile(models.Model): 
    user = models.OneToOneField(User, on_delete=models.CASCADE)

class Course(models.Model): 
    course_name = models.CharField(max_length=100)

class Student(models.Model): 
    student_name = models.CharField(max_length=100) 
    courses = models.ManyToManyField(Course)
</code></pre>
<p><code>models.CASCADE</code> , <code>models.DO_NOTHING</code> (more <a href="https://docs.djangoproject.com/en/6.0/ref/models/fields/#arguments">options</a> like <code>PROTECT</code> also supported) are constraits that tell Django what to do with children when you delete a parent. Cascade will delete all children, and "DO_NOTHING" will not delete anything.</p>
<p>Django will automatically slugify and create a table name using this format:</p>
<pre><code class="language-python">app_name_modelclassname

example: websites_contactform
</code></pre>
<p>You can change the table name if you need, as follows:</p>
<pre><code class="language-python">class ContactForm(models.Model):
    class Meta:
        db_table = "my_contact_form"

# table name in the db is: my_contact_form
</code></pre>
<p>User model - override the default:</p>
<pre><code class="language-python">class CustomUser(AbstractUser):
    username = None
    email = models.EmailField(unique=True, max_length=255)
    ... other fields

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    def __str__(self):
        return self.email

# Settings file
  AUTH_USER_MODEL = 'accounts.CustomUser'
</code></pre>
<h2>Model Queries</h2>
<p>When you inherit from <code>models.Model</code> Django automatically sets up a default model manager named <code>objects</code> on every model class. Model managers are the interface between your Django code and the database; they allow you to query and manipulate data in a Pythonic way. Under the hood, the manager sits on top of a complex chain of classes that handles everything from building queries to communicating with the database backend.</p>
<blockquote>
<p>⚠️ I simplified some of the SQL queries to make it easier to understand. Django optimizes and generates more explicit SQL than my simple "SELECT * " versions.</p>
</blockquote>
<p>To perform queries, you can use methods of the manager as follows:</p>
<pre><code class="language-python">users = User.objects.filter(email="test@test.com")
orders = Order.objects.filter(status="paid", order_total__gte=500)
</code></pre>
<p>The filter method is basically translating your query to something similar in SQL:</p>
<pre><code class="language-python">SELECT * FROM users where email="test@test.com"
SELECT * FROM orders where status="paid" and order_total &gt;= 500

# For simplicity, left out the full table name that Django would normally generate.
</code></pre>
<p>The <code>__gte</code> is a field lookup; these allow you to perform queries that are more advanced than a simple <code>=</code> . They support the typical math variants <code>__gt</code> , <code>__lt</code> , <code>__gte</code> , string matching: <code>__icontains</code> , in list lookups <code>__in</code> and many more operations.</p>
<p>In addition to <code>filter</code> You can also use <code>exclude</code> which also supports the same querying style but does the reverse, i.e. <code>filter</code> ensures results must match the criteria, and <code>exclude</code> ensures results don't match the relevant expressions passed to it.</p>
<p>By default <code>filter/exclude</code> will do an AND query, and you can chain multiple of these together because the manager class will create a <code>QuerySet</code> object (sort of like an in-memory <code>where</code>statement). The <code>QuerySet</code> is basically lazy-loaded, meaning Django doesn't actually fetch the data until you evaluate this by calling <code>count</code> , or perform some kind of evaluation like looping through the records.</p>
<pre><code class="language-python">filter(status="paid", order_total__gte=500)
 ---&gt; status="paid" and order_total &gt;= 500
</code></pre>
<p>You can make it a SQL <code>OR</code> by using the special <code>Q</code> object:</p>
<pre><code class="language-python">from django.db.models import Q
orders = Order.objects.filter(
    Q(status="paid") | Q(order_total__gte=500)
)

---&gt; status="paid" OR order_total &gt;= 500
</code></pre>
<h2>Aggregate queries</h2>
<p>While <code>filter</code> allows you to perform common <code>where</code> type queries in SQL, you sometimes need to do aggregates like <code>SUM</code>, <code>COUNT</code>, <code>AVG</code> etc... Count is fairly easy:</p>
<pre><code class="language-python">Order.objects.filter(
    Q(status="paid") | Q(order_total__gte=500)
).count() # just add .count at the end of any query
</code></pre>
<p>For other aggregate types, Django provides special classes for these, and a model manager method <code>aggregate</code>:</p>
<pre><code class="language-python"> from django.db.models import Avg, Max, Sum

Order.objects.aggregate(Max("order_total", default=0))
Order.objects.aggregate(Avg("order_total", default=0))
Order.objects.aggregate(Sum("order_total", default=0))
</code></pre>
<p>Aggregate will basically generate a similar query:</p>
<pre><code class="language-python">SELECT SUM(order_total) FROM orders;
# returns as a result: {"order_total__sum": 1500.00}
</code></pre>
<p>Essentially a single value, but what if you want to <code>group by</code> And count records? For example, you have a bunch of e-commerce merchants, and you want to know how many orders each merchant received. In this case, we can use <code>annotate</code>:</p>
<pre><code class="language-python"># logic
from django.db.models import Avg, Max, Sum, Count

merchants = Merchant.objects.annotate(Count("orders", distinct=True))
# Django uses the related_name field here for "orders"

for m in merchants:
    print(m.orders__count)
</code></pre>
<p>The model:</p>
<pre><code class="language-python">class Order(models.Model): 
    merchant = models.ForeignKey(Merchant, on_delete=models.DO_NOTHING, related_name="orders")
</code></pre>
<p>Example SQL:</p>
<pre><code class="language-python">SELECT merchant.*, COUNT(DISTINCT orders.id) 
FROM merchants
LEFT JOIN orders ON orders.merchant_id = merchant.id
GROUP BY merchant.id;
</code></pre>
<p><code>aggregate</code> and <code>annotate</code> operate on a <code>QuerySet</code> So you can also chain filter and exclude before aggregating/annotating your data.</p>
<pre><code class="language-python">Order.objects.filter(status="paid").aggregate(Sum("order_total", default=0))
</code></pre>
<p>You can print the RAW SQL of any QuerySet by simply printing as follows:</p>
<pre><code class="language-python">query_set = User.objects.annotate(Count("order", distinct=True))
print(query_set.query)

# Output
SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login", "auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name", "auth_user"."last_name", "auth_user"."email", "auth_user"."is_staff", "auth_user"."is_active", "auth_user"."date_joined", COUNT(DISTINCT "website_order"."order_reference") AS "order__count" FROM "auth_user" LEFT OUTER JOIN "website_order" ON ("auth_user"."id" = "website_order"."user_id") GROUP BY "auth_user"."id", "auth_user"."password", "auth_user"."last_login", "auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name", "auth_user"."last_name", "auth_user"."email", "auth_user"."is_staff", "auth_user"."is_active", "auth_user"."date_joined"
</code></pre>
<h2>Generate fake test data</h2>
<p>To generate fake data, you can use an excellent third-party library called <a href="https://faker.readthedocs.io/en/master/">Faker</a>. If you do this in a loop, Faker will automatically generate dynamic values, which is perfect for testing, as you can generate hundreds, even thousands, of fake users and other objects.</p>
<pre><code class="language-python">from faker import Faker  # pip install Faker
from django.contrib.auth.models import User

fake = Faker()

user = User.objects.create_user(
    username= fake.user_name(),
    email=fake.email(),
    first_name=fake.first_name(),
    last_name=fake.last_name(),
    password=fake.password()
)
</code></pre>
<p>These are just basic fields; the library can generate loads more with all kinds of customizations.</p>
<h2>Model managers</h2>
<p>Whenever you perform a DB query, you will notice we always chain methods to the <code>objects</code> property on our model, e.g. : <code>MyModel.objects.filter</code> . The "objects" property is an instance of a manager class. As the name suggests, this object will "manage" all the interactions between high-level API's we developers use, such as <code>filter</code>, <code>exclude</code> (etc...) and low-level Django ORM objects that eventually build SQL queries and execute them on the database server.</p>
<p>For example, we can build a custom manager that would ensure every time we use <code>filter</code>We automatically append a where clause such as: <code>team_id=x.</code></p>
<p>Instead of using:</p>
<p><code>User.objects.filter(department="Accounting")</code></p>
<p>We could use:</p>
<p><code>User.for_team.filter(department="Accounting")</code></p>
<p>In the first query, the SQL will look similar to:</p>
<p><code>SELECT * from users where department = 'accounting'</code></p>
<p>However, the second query could look like:</p>
<p><code>SELECT * from users where department = 'accounting' and team_id = 2</code></p>
<p>A model manager example:</p>
<pre><code class="language-python">class TeamManager(models.Manager):
    def get_queryset(self):
        team_id = get_team_id()
        return super().get_queryset().filter(team_id=team_id)

class User(models.Model):
    for_team = TeamManager()
</code></pre>
<p>Remember <code>queryset</code> is a lazy representation of a database query, i.e., sort of like a virtual SQL statement that Django maintains in memory while you keep chaining <code>filter</code>, <code>exclude</code> and various other model methods.</p>
<p>So essentially, when you invoke the default model manager <code>.objects</code> It sets up a blank <code>queryset</code> , and manages all the changes to it until you finally execute that query on the database server.</p>
<p>So in this case, instead of returning a blank <code>queryset</code>We give this <code>queryset</code> a where clause.</p>
<blockquote>
<p>ℹ️ <code>get_team_id()</code> under the hood, is a custom function (i.e. not part of Django) that's using a <code>ContextVar</code> We set in our middleware, but this logic is beyond the scope of managers, so I'll not go further on that for now, but just assume that, regardless of whatever this function does, it returns a <code>team_id</code>, which we then use in our queryset.</p>
</blockquote>
<h2>Simple OTP generator</h2>
<p>You can use this for 2-factor auth. Store the OTP in a temp table, and then send the user an email with the PIN for them to confirm. Encryption is for storage, so the PIN isn’t easily readable in the DB table.</p>
<pre><code class="language-python">import base64
import random
from hashlib import sha256

from cryptography.fernet import Fernet
from django.conf import settings


def generate_otp():
    return str(random.randint(100000, 999999))


def get_encryption_key():
    key_hashed = sha256(settings.SECRET_KEY.encode()).digest()
    return base64.urlsafe_b64encode(key_hashed)


def encrypt_otp(otp_code):
    key = get_encryption_key()
    fernet = Fernet(key)
    encrypted = fernet.encrypt(otp_code.encode())
    return encrypted.decode("utf-8")


def decrypt_otp(encrypted_otp):
    if not encrypted_otp:
        return None

    try:
        key = get_encryption_key()
        fernet = Fernet(key)
        decrypted = fernet.decrypt(encrypted_otp.encode())
        return decrypted.decode("utf-8")
    except Exception:
        return None
</code></pre>
<h2>Decorator for protecting views</h2>
<p>While middleware is usually better, sometimes you just need to run some logic only for a handful of views, in which case it might be easier to use a decorator.</p>
<pre><code class="language-python">def is_account_verified(view_func):
    @wraps(view_func)
    def _wrapped_view(request, *args, **kwargs):
        if not request.user.is_authenticated:
            return redirect("login")

        if request.user.user_profile.is_confirmed is False:
            request.user.user_profile.send_verify_email(request)
            return redirect("must-verify")
        response = view_func(request, *args, **kwargs)
        return response

    return _wrapped_view


####### Use in views.py #######
from accounts.decorators import is_account_verified

@is_account_verified
def change_password(request):
    pass
</code></pre>
<h2>Auth views</h2>
<p>Customizing the auth. If you want to use Django’s default auth system but not admin, you can extend and customize the existing auth to use your own template and your own custom register/login.</p>
<pre><code class="language-python">from django.contrib.auth import views as auth_views
urlpatterns = [
    path("login/", login_view, name="login"),
    path("register/", register_view, name="register"),
    path("logout/", logout_view, name="logout"),
    path(
        "password_reset/",
        auth_views.PasswordResetView.as_view(
            template_name="auth/password_reset_form.html",
            html_email_template_name="auth/emails/password_reset_email.html",
            subject_template_name="auth/emails/password_reset_subject.txt",
        ),
        name="password_reset",
    ),
    path(
        "password_reset/done/",
        auth_views.PasswordResetDoneView.as_view(
            template_name="auth/password_reset_done.html"
        ),
        name="password_reset_done",
    ),
    path(
        "reset/&lt;uidb64&gt;/&lt;token&gt;/",
        auth_views.PasswordResetConfirmView.as_view(
            template_name="auth/password_reset_confirm.html"
        ),
        name="password_reset_confirm",
    ),
    path(
        "reset/done/",
        auth_views.PasswordResetCompleteView.as_view(
            template_name="auth/password_reset_complete.html"
        ),
        name="password_reset_complete",
    )
]
</code></pre>
<pre><code class="language-python">def login_view(request):
    if request.user.is_authenticated:
        return redirect("dashboard")

    if request.method == "POST":
        username = request.POST.get("username")
        password = request.POST.get("password")
        user = authenticate(request, username=username, password=password)
        if user is not None and user.user_profile.is_active is False:
            messages.error(
                request, "Sorry, but your administrator has disabled your account."
            )
        elif user is not None:
            if user.user_profile.is_twofactor_enabled:
                request.session["2fa_user_id"] = user.id
                request.session["2fa_timestamp"] = timezone.now().isoformat()
                return redirect("send_2fa_email")
            else:
                login(request, user)
                if user.user_profile.needs_password_change:
                    return redirect("change_password")
                return redirect("dashboard")
        else:
            messages.error(request, "Invalid username or password.")

    return render(request, "auth/login.html")


def register_view(request):
    if request.user.is_authenticated:
        return redirect("dashboard")

    context = {}

    if request.method == "POST":
        email = request.POST.get("email")
        password1 = request.POST.get("password1")
        password2 = request.POST.get("password2")
        full_name = request.POST.get("name")

        context = {
            "email": email,
            "name": full_name
        }

        if password1 != password2:
            messages.error(request, "Passwords do not match.")
            return render(request, "auth/register.html", context)

        if len(password1) &lt; 8:
            messages.error(request, "Password must be at least 8 characters long.")
            return render(request, "auth/register.html", context)

        if not re.search(r"\d", password1):
            messages.error(request, "Password must include at least one number.")
            return render(request, "auth/register.html", context)

        if not re.search(r"[A-Z]", password1):
            messages.error(
                request, "Password must include at least one uppercase letter."
            )
            return render(request, "auth/register.html", context)

        if not full_name:
            messages.error(request, "Oops! Please tell us your name?")
            return render(request, "auth/register.html", context)

        # probably want a more generic message
        if User.objects.filter(email=email).exists():
            messages.error(request, "Email already in use.")
            return render(request, "auth/register.html", context)

        try:
            user = User.objects.create_user(
                email=email, password=password1
            )
            full_name = full_name.split(" ")
            if len(full_name) &gt; 1:
                user.first_name = full_name[0]
                user.last_name = " ".join(full_name[1:])
            else:
                user.first_name = full_name[0]
                user.last_name = ""

            user.save()
            messages.success(
                request, "Account created successfully. You can now log in."
            )

            return redirect("login")
        except Exception as e:
            messages.error(request, f"Error creating account: {e}")
            return render(request, "auth/register.html", context)

    return render(request, "auth/register.html", context)


def logout_view(request):
    logout(request)
    return redirect("login")
</code></pre>
<h2>Management commands</h2>
<pre><code class="language-python"># --- in app_name/management/commands
from django.core.management.base import BaseCommand


class Command(BaseCommand):
    help = "Some important task description here"

    def add_arguments(self, parser):
        parser.add_argument("--email", type=str, help="Email address of the user")

    def handle(self, *args, **options):
        email = options.get("email")
        if not email:
            email = input("Enter email address of the user: ")
        # .... rest of the code

        self.stdout.write(
            self.style.SUCCESS(f"Successfully did whatever I was suppose to do.")
        )
</code></pre>
<h2>Dead simple background tasks without Celery complication</h2>
<p>The purpose of this is to run simple tasks in the background; it’s not meant for high concurrency or complex jobs. For that, just use Celery. The beauty of this way is that there’s no external service to manage (besides the systemd script), so it’s easier to manage.</p>
<p>In an app “background_tasks”. models.py:</p>
<pre><code class="language-python">from django.db import models
from django.utils import timezone


class Statues(models.TextChoices):
    PENDING = "pending", "pending"
    IN_FLIGHT = "in_flight", "in_flight"
    FAILED = "failed", "failed"
    COMPLETE = "complete", "complete"


class Queues(models.TextChoices):
    HIGH = "high", "high"
    LOW = "low", "low"
    NORMAL = "normal", "normal"


class QueueTask(models.Model):
    job_data = models.JSONField("TASK", default=dict)
    status = models.CharField(max_length=20, choices=Statues, default=Statues.PENDING)
    scheduled_for = models.DateTimeField(default=timezone.now)
    queue = models.CharField(max_length=20, choices=Queues, default=Queues.NORMAL)
    callable = models.CharField(max_length=100, null=False, blank=False)

    @classmethod
    def queueTask(cls, job_data, callable, queue=None, scheduled_for=None):
        if callable is None:
            raise Exception(
                "Callable needs to be a task declared in background_jobs/tasks."
            )

        q = QueueTask()
        q.status = Statues.PENDING
        q.scheduled_for = timezone.now() if scheduled_for is None else scheduled_for
        queue = Queues.NORMAL if queue is None else queue
        q.callable = callable
        q.job_data = job_data
        q.save()
</code></pre>
<p>Example background_tasks/tasks/notification.py:</p>
<pre><code class="language-python">from django.conf import settings
from django.core.mail import EmailMessage
from django.utils import timezone

from background_jobs.models import QueueTask

def send_email(task: QueueTask):
    json_data = task.job_data
    json_data["to_emails"]
    for to_email in json_data["to_emails"]:
        email = EmailMessage(
            json_data["subject"],
            json_data["message"],
            (
                json_data["from"]
                if "from" in json_data.keys()
                else settings.DEFAULT_FROM_EMAIL
            ),
            [to_email],
        )
        email.content_subtype = json_data["format"] if "format" in json_data else "html"
        email.send()
</code></pre>
<p>In background_tasks/management/commands/worker.py:</p>
<pre><code class="language-python">import importlib
import logging
import time
from typing import List

from django.core.management.base import BaseCommand
from django.utils import timezone

from background_jobs.models import Queues, QueueTask, Statues

logger = logging.getLogger(__name__)


class Command(BaseCommand):
    help = "Process pending tasks from a specified queue"

    def handle(self, *args, **options):
        while True:
            try:
                tasks = QueueTask.objects.filter(
                    status=Statues.PENDING,
                    scheduled_for__lte=timezone.now(),
                    queue=Queues.NORMAL,
                ).order_by("-scheduled_for")[:20]

                for t in tasks:
                    self.execute_task(t)
            except Exception as ex:
                print(ex)
            finally:
                time.sleep(15 / 1000)

    def execute_task(self, task: QueueTask):
        """Execute a single task"""
        try:
            self.stdout.write(f"Executing task {task.id}")
            task.status = Statues.IN_FLIGHT
            task.save()

            module_name, function_name = task.callable.split(".")
            module = importlib.import_module("background_jobs.tasks." + module_name)

            getattr(module, function_name)(task)

            task.status = Statues.COMPLETE
            task.save()

            logger.info(self.style.SUCCESS(f"Task {task.id} completed successfully"))

        except Exception as e:
            task.status = Statues.FAILED
            task.save()
            logger.error(self.style.ERROR(f"Task {task.id} failed: {str(e)}"))
</code></pre>
<p>Then just set up a systemd on Ubuntu / Debian (/etc/systemd/system/worker.service):</p>
<pre><code class="language-apache">[Unit]
Description=Worker daemon for my django app queue
After=network.target

[Service]
User=pythonapp
Group=pythonapp
WorkingDirectory=/home/pythonapp/myproject
ExecStart=/home/pythonapp/.venv/bin/python manage.py worker

Restart=on-failure
RestartSec=5s

Environment=DJANGO_SETTINGS_MODULE=myproject.settings

[Install]
WantedBy=multi-user.target
</code></pre>
<p>Then in Ubuntu:</p>
<pre><code class="language-bash">systemctl daemon-reload 
systemctl enable worker.service
systemctl start worker.service
</code></pre>
<p>Check the status of the worker:</p>
<pre><code class="language-bash">systemctl status worker.service
</code></pre>
<p>Now, throughout your application, you can then queue work like so:</p>
<pre><code class="language-python">QueueTask.queueTask(
    {
        "subject": subject,
        "message": message,
        "to_emails": [self.user.username],
        "format": "html",
    },
    "notifications.send_email",
)
</code></pre>
<p>NGINX to reverse proxy to Gunicorn worker:</p>
<pre><code class="language-python">server {
    listen 80;
    real_ip_header CF-Connecting-IP;
    server_name mydomain.com www.mydomain.com;

    # Redirect HTTP to HTTPS
    return 301 https://\(host\)request_uri;
}

server {
    listen 443 ssl;
    server_name mydomain.com
    real_ip_header CF-Connecting-IP;
    # SSL configuration
    ssl_certificate /etc/ssl/mydomain.com.cert;
    ssl_certificate_key /etc/ssl/mydomain.com.key;
    ssl_protocols TLSv1.2 TLSv1.3;

    # Security headers
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Static file serving
    location /static/ {
        autoindex on;
        alias /var/www/static/;
        expires 30d;
        access_log off;
    }

    # Proxy connections to Gunicorn
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 75s;
        proxy_read_timeout 300s;
        client_max_body_size 10m;
    }

    # Logging
    access_log /var/log/nginx/mydomain.com.access.log;
    error_log /var/log/nginx/mydomain.com.error.log;
}
</code></pre>
<p><strong>Gunicorn worker config:</strong></p>
<pre><code class="language-python">[Unit]
Description=Gunicorn daemon for myproject.com
After=network.target

[Service]
User=pythonapp
Group=pythonapp
WorkingDirectory=/home/pythonapp/myproject
ExecStart=/home/pythonapp/.venv/bin/gunicorn \
          --access-logfile - \
          --workers 8 \
          --threads 2 \
          --timeout 180 \
          --max-requests 1000 \
          --max-requests-jitter 100 \
          --bind 127.0.0.1:8000 \
          myproject.wsgi:application

Restart=on-failure
RestartSec=5s

# Environment variables if needed
Environment=DJANGO_SETTINGS_MODULE=myproject.settings

[Install]
WantedBy=multi-user.target
</code></pre>
<h2>Forms - implement the change user password form</h2>
<pre><code class="language-python">from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError as DjangoValidationError
from django import forms


class CustomPasswordChangeForm(forms.Form):
    current_password = forms.CharField(
        label="Current Password", 
        widget=forms.PasswordInput(attrs={'autocomplete': 'current-password'}),
        required=True
    )
    new_password1 = forms.CharField(
        label="New Password", 
        widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
        required=True
    )
    new_password2 = forms.CharField(
        label="Confirm New Password", 
        widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
        required=True
    )

    def __init__(self, user, *args, **kwargs):
        self.user = user
        super().__init__(*args, **kwargs)

    def clean_current_password(self):
        current_password = self.cleaned_data.get("current_password")
        if not self.user.check_password(current_password):
            raise forms.ValidationError(
                "Your current password was entered incorrectly."
            )
        return current_password

    def clean_new_password1(self):
        """Validate new password against Django's validators"""
        password = self.cleaned_data.get("new_password1")
        if password:
            try:
                validate_password(password, self.user)
            except DjangoValidationError as e:
                raise forms.ValidationError(e.messages)
        return password

    def clean_new_password2(self):
        password1 = self.cleaned_data.get("new_password1")
        password2 = self.cleaned_data.get("new_password2")

        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("The two password fields didn't match.")

        return password2
    
    def clean(self):
        cleaned_data = super().clean()
        current = cleaned_data.get("current_password")
        new = cleaned_data.get("new_password1")
   
        if current == new:
            raise forms.ValidationError(
                "Your new password cannot be the same as your current password."
            )
        
        return cleaned_data

    def save(self, commit=True):
        password = self.cleaned_data["new_password1"]
        self.user.set_password(password)
        if commit:
            self.user.save()
        return self.user
</code></pre>
<pre><code class="language-python">@login_required
@require_http_methods(["POST"])
def change_password(request):
    if request.headers.get("X-Requested-With") == "XMLHttpRequest":
        form = CustomPasswordChangeForm(request.user, request.POST)
        if form.is_valid():
            form.save()
            update_session_auth_hash(request, form.user)
            return JsonResponse(
                {"success": True, "message": "Your password was successfully changed."}
            )
        else:
            return JsonResponse({"success": False, "errors": form.errors}, status=400)

    return JsonResponse({"success": False, "message": "Invalid request"}, status=400)
</code></pre>
<h2>Setting up S3 storage in settings.py</h2>
<pre><code class="language-python">INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_redis',
    'shared',
    "storages",   &lt;----
]
</code></pre>
<pre><code class="language-python">pip install django-storages
</code></pre>
<pre><code class="language-python"># Public S3 Storage Configuration
AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME', 'mybucket')
AWS_S3_REGION_NAME = os.getenv('AWS_S3_REGION_NAME', 'fsn1')
AWS_S3_ENDPOINT_URL = os.getenv('AWS_S3_ENDPOINT_URL', f'https://{AWS_S3_REGION_NAME}.your-objectstorage.com')
AWS_S3_CUSTOM_DOMAIN = os.getenv('AWS_S3_CUSTOM_DOMAIN', f'{AWS_STORAGE_BUCKET_NAME}.{AWS_S3_REGION_NAME}.your-objectstorage.com')
AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}
AWS_DEFAULT_ACL = 'public-read'
AWS_QUERYSTRING_AUTH = False
AWS_S3_FILE_OVERWRITE = False

# Private S3 Storage Configuration
PRIVATE_BUCKET_NAME = os.getenv('PRIVATE_BUCKET_NAME', 'myprivatebucket')
PRIVATE_S3_REGION_NAME = os.getenv('PRIVATE_S3_REGION_NAME', 'nbg1')
PRIVATE_S3_ENDPOINT_URL = os.getenv('PRIVATE_S3_ENDPOINT_URL', f'https://{PRIVATE_S3_REGION_NAME}.your-objectstorage.com')
PRIVATE_S3_CUSTOM_DOMAIN = os.getenv('PRIVATE_S3_CUSTOM_DOMAIN', f'{PRIVATE_BUCKET_NAME}.{PRIVATE_S3_REGION_NAME}.your-objectstorage.com')

STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3boto3.S3Boto3Storage",
        "OPTIONS": {
            "access_key": AWS_ACCESS_KEY_ID,
            "secret_key": AWS_SECRET_ACCESS_KEY,
            "bucket_name": AWS_STORAGE_BUCKET_NAME,
            "region_name": AWS_S3_REGION_NAME,
            "endpoint_url": AWS_S3_ENDPOINT_URL,
            "custom_domain": AWS_S3_CUSTOM_DOMAIN,
            "object_parameters": AWS_S3_OBJECT_PARAMETERS,
            "default_acl": AWS_DEFAULT_ACL,
            "querystring_auth": AWS_QUERYSTRING_AUTH,
            "file_overwrite": AWS_S3_FILE_OVERWRITE,
            "location": "",
        },
    },
    "private": {
        "BACKEND": "storages.backends.s3boto3.S3Boto3Storage",
        "OPTIONS": {
            "access_key": AWS_ACCESS_KEY_ID,
            "secret_key": AWS_SECRET_ACCESS_KEY,
            "bucket_name": PRIVATE_BUCKET_NAME,
            "region_name": PRIVATE_S3_REGION_NAME,
            "endpoint_url": PRIVATE_S3_ENDPOINT_URL,
            "custom_domain": PRIVATE_S3_CUSTOM_DOMAIN,
            "object_parameters": AWS_S3_OBJECT_PARAMETERS,
            "default_acl": 'private',
            "querystring_auth": True,
            "file_overwrite": AWS_S3_FILE_OVERWRITE,
            "location": "",
        },
    },
    "staticfiles": {
        "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
    },
}

MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/'
</code></pre>
<h2>Global shared template tag</h2>
<p>Sometimes you want to access branding information, and other important information in templates, so instead of hardcoding them or repeating yourself all the time. Put those settings in the settings.py and then reference them using this shared template tag.</p>
<pre><code class="language-python">TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            "builtins": [
                "shared.templatetags.global_settings_tags",  &lt;------
            ],
            'context_processors': [
                'shared.context_processors.global_settings',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

# Only allow these settings to be accessed from template tag
ALLOWED_TEMPLATE_SETTING_ACCESS = {
    'APPLICATION_LOGO',
    'APPLICATION_NAME',
    'CONTACT_EMAIL',
    'SUPPORT_URL',
    'MEDIA_URL',
    'STATIC_URL',
    # Add other settings you want templates to have access to
}
</code></pre>
<p>In shared/templatetags/global_settings_tags.py</p>
<pre><code class="language-python">from django import template
from django.conf import settings

register = template.Library()

@register.simple_tag
def get_setting(name):
    name = name.strip()
    if name not in settings.ALLOWED_TEMPLATE_SETTING_ACCESS:
        return ""

    return getattr(settings, name, "")
</code></pre>
<p>Now, anywhere in your templates, you can access:</p>
<pre><code class="language-python">   &lt;img src="{% get_setting 'APPLICATION_LOGO' %}" alt="logo" /&gt;
</code></pre>
<p>Note: You probably want to break up your settings file into multiple config files. For larger projects, I normally create a “/config” folder and split up my settings files into there.</p>
<h2>Django and Tailwind</h2>
<p>tailwind.config.js at the root of your project</p>
<pre><code class="language-javascript">/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./templates/**/*.{html,js}"
  ],
  theme: {
    extend: {
      colors: {
        primary: '#5DDED6',
        secondary: '#8b5cf6',
        dark: '#1e293b',
      },
      fontFamily: {
        sans: ['Inter', 'sans-serif'],
      },
      animation: {
        'float': 'float 3s ease-in-out infinite',
      },
      keyframes: {
        float: {
          '0%, 100%': { transform: 'translateY(0)' },
          '50%': { transform: 'translateY(-10px)' },
        }
      }
    },
  },
  plugins: [],
}
</code></pre>
<p>postcss.config.js</p>
<pre><code class="language-javascript">module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}
</code></pre>
<p>package.json:</p>
<pre><code class="language-javascript">{
  ----
  "scripts": {
    "build:css:prod": "tailwindcss -i ./static/css/app.css -o ./static/build/app.css --minify",
    "watch:css": "tailwindcss -i ./static/css/app.css -o ./static/build/app.css --watch",
    "watch:django": "python manage.py runserver",
    "dev": "npm-run-all --parallel watch:*"
  },
  "devDependencies": {
    "@fortawesome/fontawesome-free": "^6.7.2",
    "autoprefixer": "^10.4.21",
    "postcss": "^8.5.3",
    "tailwindcss": "^3.3.3"
  },
  "dependencies": {
    "npm-run-all": "^4.1.5"
  }
  ----
}
</code></pre>
<pre><code class="language-bash">npm run dev  ~ to hot reload python and tailwind
npm run build:css:prod  ~ for production builds
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Workhorse AI models you probably ignored]]></title><description><![CDATA[While the whole YouTube influencer space goes crazy about Gemini 3, I’m not so impressed. Don’t get me wrong, Gemini models are fantastic! And the V3 iteration is undoubtedly cool and capable. I’ve briefly run some tests (not benchmarks but actual re...]]></description><link>https://kevincoder.co.za/workhorse-ai-models-you-probably-ignored</link><guid isPermaLink="true">https://kevincoder.co.za/workhorse-ai-models-you-probably-ignored</guid><category><![CDATA[LLM's ]]></category><category><![CDATA[openrouter]]></category><category><![CDATA[small language model]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Thu, 20 Nov 2025 16:19:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763655645493/e937fbe1-4b9e-414d-bef3-acb01c1517c7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>While the whole YouTube influencer space goes crazy about Gemini 3, I’m not so impressed. Don’t get me wrong, Gemini models are fantastic! And the V3 iteration is undoubtedly cool and capable. I’ve briefly run some tests (not benchmarks but actual real-world tests) on Gemini 3.</p>
<p>Meh 😐, it’s okay, but it's better than GPT-5 for sure, and it’s incremental just like most other models we’ve seen this year.</p>
<p>When the dust settles and we get past all the hype, what really matters is how reliable and cost-effective these models are for day-to-day use, not just coding but also business use cases.</p>
<p>In this article, I want to talk about some lesser-known models that all the YouTubers and the media in general miss.</p>
<h2 id="heading-gemini-flash">Gemini flash</h2>
<p>My favourite model is Gemini 2.0 Flash for general machine learning and not-so-complicated tasks like RAG chatbots.</p>
<p>Flash is super fast compared to Anthropic’s Haiku and OpenAI mini models, yet feels much smarter than nearly all other mini models I have worked with.</p>
<p>I’m using Flash models for:</p>
<ul>
<li><p>Voice AI tasks.</p>
</li>
<li><p>Rag chatbots.</p>
</li>
<li><p>Categorization of products.</p>
</li>
<li><p>Rewriting search queries.</p>
</li>
<li><p>Various text generations.</p>
</li>
</ul>
<p>Back in the day, GPT4o-mini was the goat of small models, but alas, in the last year or so, Google has just woken up and dominated the small model space.</p>
<h2 id="heading-phi-3-phi-4-models">PHI-3 / PHI-4 models</h2>
<p>If you have a small 8GB - 16GB consumer GPU on hand and want to run a local model, then these PHI models are impressive. I use them for simple classification and text generation tasks. They generally perform well.</p>
<p>Sure, they are not as smart as the Gemini models, but if you use this model in combination with a semantic search or some other process, for example, categorization, you could do a semantic search to find the most relevant list of categories, then ask the model to pick the most appropriate.</p>
<p>This will save you some per-token costs and give you a decent level of accuracy.</p>
<h2 id="heading-zai-glm-46">Z.Ai GLM 4.6</h2>
<p>It’s become a common practice for developers to use AI coding assistants like Claude Sonnet, Cursor, Windsurf, and so forth.</p>
<p>I myself love Sonnet and use it often, but Sonnet can be expensive and slow at times, which really impacts your flow state, not to mention all the daily and weekly limits if you’re using one of the subscription plans.</p>
<p>I still write and plan the “complex” stuff myself, but there’s just some boilerplate 🥱 brain-dead CRUD stuff that I just don’t need to write myself anymore; in the past, I would use Sonnet for these tasks, but of late have been really impressed by how much mileage I got out of GLM 4.6.</p>
<p>Z.ai subscriptions start from $6 a month, or you can use something like OpenRouter instead, and it’s fairly capable. I’ve generated some good Python, PHP, and even Golang code with the 4.6 model (using cline in VSCode).</p>
<p>You still need to review and clean up the code to an extent, like with Sonnet, but the 4.6 model seems fairly good with clearly defined tasks, tailwind stuff, and just CRUD-type low-thinking tasks.</p>
<p>Sure, Sonnet is still better at the more complicated stuff, but anyway, complex problems are fun for me, so I just use my brain for those 🙃.</p>
<h2 id="heading-minimax-models">Minimax models</h2>
<p>Like GLM 4.6, the Minimax models are also very capable of agentic and coding tasks, and comparatively, the price is way cheaper than Sonnet. On OpenRouter, it’s like $2 per million output tokens.</p>
<p>Similar to GLM 4.6, it’s not as smart as Sonnet; however, remember that big ol brain you have! It’s usually good enough for most code generation tasks and can also be used as a smarter alternative to GPT-5 or any of the GPT models.</p>
<p>Let’s be honest, OpenAI is just not as good as it once was. Their API response times are not great, and most of these other alternative models have caught up.</p>
<h2 id="heading-openrouter">OpenRouter</h2>
<p>Much of what I am referencing here are models that I use through OpenRouter, which also happens to be one of my favourite AI platforms ever!</p>
<p>If you don’t know, OpenRouter is basically a “Router for AI”. Instead of you having to create accounts with OpenAI, Anthropic, Google, etc, you can simply register with them and buy credits. Those credits can then be used across hundreds (if not thousands) of models.</p>
<p>So, for example, in one project using just a single SDK implementation, you can swap between Gemini, Anthropic, Z.ai, OpenAI, Mistral, and many more providers by just changing the model name!</p>
<p>They also allow for multiple providers. Take, for example, GLM 4.6 has about 9-10 different companies that host the model, so essentially, if one provider is experiencing an outage, OpenRouter will automatically route your API call to one of the other providers. You can also optimize for costs to pick the cheapest provider!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763654587447/6fc58d62-b4ed-4835-a360-54f7b476e858.png" alt class="image--center mx-auto" /></p>
<p>Okay, this is sounding like an OpenRouter ad now, totally not (wouldn’t mind if they sponsored me though 😉), it’s really cool!</p>
<p>The only problem is that sometimes the API is slower compared to using the mainstream provider directly, and there is a small markup they add to your token usage to obviously make a profit, but generally, by using different providers and models, you end up saving money.</p>
]]></content:encoded></item><item><title><![CDATA[~Zod and React Hook Form]]></title><description><![CDATA[I’m learning Next.js; I know React or kinda know React since I started building React apps in 2018 or thereabout, and then stopped using it for several years. Shew! A lot has changed: Server actions, Zod, and just the whole ecosystem generally.
This ...]]></description><link>https://kevincoder.co.za/zod-and-react-hook-form</link><guid isPermaLink="true">https://kevincoder.co.za/zod-and-react-hook-form</guid><category><![CDATA[React]]></category><category><![CDATA[next js]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Sat, 30 Aug 2025 08:33:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756542808718/1d80adf8-2cf6-4610-a5dc-08715d822e35.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I’m learning Next.js; I know React or kinda know React since I started building React apps in 2018 or thereabout, and then stopped using it for several years. Shew! A lot has changed: Server actions, Zod, and just the whole ecosystem generally.</p>
<p>This will be a quick guide, purely meant as self-documentation to help me remember. I found it a bit annoying to find good documentation, it seems like the wild, wild west 🙃! many different documentation sources, and they all seem to do things differently.</p>
<blockquote>
<p>PS: if you’re a Next.js pro, feel free to suggest a better way of doing things. I’m constantly looking to follow best practices wherever I can.</p>
</blockquote>
<h2 id="heading-what-are-server-components">What are Server Components?</h2>
<p>I personally think this is a game-changer, purely because I built many SPAs and understand the pain of maintaining an API, dealing with CORS, and context switching.</p>
<p>In Next.js, you can run React code on the server side. This means that Next.js will pre-compile the React code and then send the result back to the browser. So, for example, in the past, to fetch data in a component, you would need a separate API and boilerplate code like this:</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">interface</span> Post {
  id: <span class="hljs-built_in">number</span>;
  userId: <span class="hljs-built_in">number</span>;
  title: <span class="hljs-built_in">string</span>;
  body: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [posts, setPosts] = useState&lt;Post[]&gt;([]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> loadPosts = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/posts'</span>);
        <span class="hljs-keyword">if</span> (!response.ok) {
          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`HTTP error! status: <span class="hljs-subst">${response.status}</span>`</span>);
        }
        <span class="hljs-keyword">const</span> apiPosts: Post[] = <span class="hljs-keyword">await</span> response.json();
        setPosts(apiPosts);
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Failed to fetch posts:'</span>, error);
      }
    };

    loadPosts();
  }, []);

  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      {posts.map(<span class="hljs-function"><span class="hljs-params">post</span> =&gt;</span> (
        &lt;div key={post.id}&gt;
          &lt;h3&gt;{post.title}&lt;/h3&gt;
          &lt;p&gt;{post.body}&lt;/p&gt;
        &lt;/div&gt;
      ))}
    &lt;/div&gt;
  );
}
</code></pre>
<p>It's such a pain. You have to use state and useEffect. Plus, you actually need an API. Here, I’m just using a public API, but for a real-world application, you’d need to build that API for your internal business data using Express.js or some other backend framework (You could also use Next.js API routes).</p>
<p>With <code>Server Components</code> We can greatly simplify this code; we don’t need to build API endpoints or set up new routes; we can just create a simple function and call it in our component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> Post {
  id: <span class="hljs-built_in">number</span>;
  userId: <span class="hljs-built_in">number</span>;
  title: <span class="hljs-built_in">string</span>;
  body: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">loadPosts</span>(<span class="hljs-params"></span>) :<span class="hljs-title">Promise</span>&lt;<span class="hljs-title">Post</span>[]&gt; </span>{
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/posts'</span>);
  <span class="hljs-keyword">return</span> response.json();
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> posts = <span class="hljs-keyword">await</span> loadPosts();

  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      {posts.map(<span class="hljs-function"><span class="hljs-params">post</span> =&gt;</span> (
        &lt;div key={post.id}&gt;
          &lt;h3&gt;{post.title}&lt;/h3&gt;
          &lt;p&gt;{post.body}&lt;/p&gt;
        &lt;/div&gt;
      ))}
    &lt;/div&gt;
  );
}
</code></pre>
<blockquote>
<p>You probably want to separate the API call into its own actions/xyz.ts file to keep things clean and this is an oversimplified example to demonstrate the concept, so it’s just got the bare minimum.</p>
</blockquote>
<p>As you can see, this is a lot simpler, and since the template is pre-compiled with the data from our fetch call, by the time it reaches the browser, the data is already present, so there’s no need to use <code>useEffect</code> or <code>state</code>.</p>
<p>By default, all components are <code>ServerComponents</code>, If you need to use React hooks like <code>useEffect</code>, <code>useState</code> Or any JavaScript events, then you can force the component to behave just like a regular React component by adding <code>”use client”</code> .</p>
<h2 id="heading-zod-what">Zod - what?!</h2>
<p>This is not a Pokémon or Transformer 😂 or (Zordon from Power Rangers). TypeScript is a superset of JavaScript; thus, types don’t work at runtime, since the browser cannot understand TypeScript. So when it comes to forms, you can’t actually use an <code>interface</code> to ensure the types are valid.</p>
<p>You still need validation at runtime to provide meaningful error messages and also ensure the data is in the correct format before saving to a database, hence the mighty <code>zod</code> :</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {z} <span class="hljs-keyword">from</span> <span class="hljs-string">'zod'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> UserSchema = z.object({
    name: z.string().min(<span class="hljs-number">3</span>, {<span class="hljs-string">"error"</span>: <span class="hljs-string">"Please fill in a valid name."</span>}),
    email: z.email({ pattern: z.regexes.html5Email })
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> User = z.infer&lt;<span class="hljs-keyword">typeof</span> UserSchema&gt;;
</code></pre>
<p>So basically, <code>UserSchema</code> It’s just an object with some validation rules. Now, since Zod returns a validation object, it can’t be used as a type throughout your code. For example, in my server action, I need to type the input form data so it’s usable in my code.</p>
<pre><code class="lang-typescript"><span class="hljs-string">'use server'</span>
<span class="hljs-keyword">import</span> { User, UserSchema } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/types"</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> saveUser = <span class="hljs-keyword">async</span> (formData: User) =&gt; {
   <span class="hljs-comment">// other fun stuff here</span>
}
</code></pre>
<p>To get an interface type out of Zod, simply do the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> User = z.infer&lt;<span class="hljs-keyword">typeof</span> UserSchema&gt;;
<span class="hljs-comment">// then you can use fields like user.name, user.email etc...</span>
</code></pre>
<p>Finally, you can use Zod to validate user input as follows in your server action.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> saveUser = <span class="hljs-keyword">async</span> (user: User) =&gt; {
    <span class="hljs-keyword">const</span> valid = UserSchema.safeParse(user);
    <span class="hljs-keyword">if</span> (!valid.success) {
        <span class="hljs-built_in">console</span>.log(valid.error);
        <span class="hljs-keyword">return</span>;
    }
}
</code></pre>
<h2 id="heading-dealing-with-forms-using-react-hook-form">Dealing with forms using React Hook Form</h2>
<p>Forms are one of the most common things in any app, but they’re also one of the most annoying things to deal with because you need to validate the form data, show meaningful error messages, preserve the form state, etc…</p>
<p>Luckily, there’s a powerful form library that just makes working with forms that much easier; it’s not part of Next.js by default, so you need to install it as follows:</p>
<pre><code class="lang-bash">pnpm install react-hook-form
</code></pre>
<p>Now, we can make a form to save a user (a very simplistic example). We use a client-side component in this instance because we want to show errors and prevent the form from clearing its fields. We do validation on the client first before even sending the form to the server, so it’s a much better user experience.</p>
<pre><code class="lang-typescript"><span class="hljs-string">'use client'</span>;

<span class="hljs-keyword">import</span> { saveUser } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/actions/forms"</span>;
<span class="hljs-keyword">import</span> { UserSchema, User } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/types"</span>;
<span class="hljs-keyword">import</span> {zodResolver} <span class="hljs-keyword">from</span> <span class="hljs-string">'@hookform/resolvers/zod'</span>;
<span class="hljs-keyword">import</span> {useForm} <span class="hljs-keyword">from</span> <span class="hljs-string">'react-hook-form'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> form = useForm&lt;User&gt;({
    resolver: zodResolver(UserSchema)
  });

  <span class="hljs-keyword">const</span> onSubmit = <span class="hljs-keyword">async</span> (data: User) =&gt; {
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> saveUser(data);
    alert(result);
    <span class="hljs-comment">// result can anything you want to return from the server side.</span>
  };

  <span class="hljs-keyword">return</span> (
    &lt;div&gt;
      &lt;form onSubmit={form.handleSubmit(onSubmit)}&gt;
        &lt;input {...form.register(<span class="hljs-string">'name'</span>)}
        <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
        name=<span class="hljs-string">"name"</span> 
        className=<span class="hljs-string">"bg-white border-2 mb-2 mt-2 text-slate-800"</span>
         /&gt; &lt;br /&gt;

        &lt;input 
        {...form.register(<span class="hljs-string">'email'</span>)}
        <span class="hljs-keyword">type</span>=<span class="hljs-string">"email"</span> 
        name=<span class="hljs-string">"email"</span>  
        className=<span class="hljs-string">"bg-white border-2 mb-2 mt-2 text-slate-800"</span>
        /&gt;

      &lt;br /&gt;
          {form.formState.errors.email &amp;&amp; (
          &lt;p className=<span class="hljs-string">"text-red-500"</span>&gt;{form.formState.errors.email.message}&lt;/p&gt;
        )}
        &lt;br /&gt;

      &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"submit"</span> 
        value=<span class="hljs-string">"SEND"</span> 

        className=<span class="hljs-string">"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"</span>
        /&gt;
      &lt;/form&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<p><strong>Breaking it down:</strong></p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> form = useForm&lt;User&gt;({
    resolver: zodResolver(UserSchema)
  });
</code></pre>
<p>We create a strongly typed form and link it to Zod. This ensures each field is checked against the schema. You can bind each of the form elements using this function <code>…form.register(‘name’)</code> so that when Zod runs, it can access the elements’ values and other properties.</p>
<pre><code class="lang-xml">       <span class="hljs-tag">&lt;<span class="hljs-name">input</span> {<span class="hljs-attr">...form.register</span>('<span class="hljs-attr">name</span>')}
        <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
        <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span> 
        <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-white border-2 mb-2 mt-2 text-slate-800"</span>
         /&gt;</span>
</code></pre>
<p>Here’s where the magic happens:</p>
<pre><code class="lang-typescript"> onSubmit={form.handleSubmit(onSubmit)}
</code></pre>
<p>In this call, before the data is sent to the <code>Server Action</code> function to handle saving the user, our form object will validate the Zod schema first. If there are any errors, it will not trigger the onSubmit function and instead just fill the errors in our form state, which we can then access as follows:</p>
<pre><code class="lang-typescript">      &lt;br /&gt;
          {form.formState.errors.email &amp;&amp; (
          &lt;p className=<span class="hljs-string">"text-red-500"</span>&gt;{form.formState.errors.email.message}&lt;/p&gt;
        )}
        &lt;br /&gt;
</code></pre>
<p><code>saveUser</code> ~ is what we call a <code>Server Action</code> i.e., a function that components (both client and server) can call to perform some task on the server side.</p>
<p>In my project, I created a folder called <code>actions</code> which basically contains all my server actions.</p>
<p><strong>Very Important:</strong> We need to ensure that the <code>use server</code> directive is always present in our server actions file, usually at the top of the file. This prevents the code from being bundled in the JavaScript code sent to the browser, which ensures a smaller JS file for the client to download. Furthermore, it prevents leaking any sensitive information to the browser, such as API keys and any other information you want to keep private.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use server"</span>

<span class="hljs-keyword">import</span> { User, UserSchema } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/types"</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> saveUser = <span class="hljs-keyword">async</span> (user: User) :<span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">string</span>&gt; =&gt; {
    <span class="hljs-keyword">const</span> valid = UserSchema.safeParse(user);
    <span class="hljs-keyword">if</span> (!valid.success) {
        <span class="hljs-keyword">return</span> valid.error.flatten.toString();
    }

    <span class="hljs-comment">// do some db stuff</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Success"</span>;
}
</code></pre>
<p>If you came from the jQuery era like me, you’ll realize how much cleaner this is. We don’t need a 101 <code>if</code> statements to validate each form element, we can just use proper typing and let Zod do the rest.</p>
]]></content:encoded></item><item><title><![CDATA[Hitchhiker's guide to AI for beginners]]></title><description><![CDATA[This will be more of an FAQ-style article, which is divergent from my regular content, but I wanted to write a guide for non-technical people or people unfamiliar with AI. You hear all these buzzwords]]></description><link>https://kevincoder.co.za/hitchhikers-guide-to-ai-for-beginners</link><guid isPermaLink="true">https://kevincoder.co.za/hitchhikers-guide-to-ai-for-beginners</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[AI]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Mon, 25 Aug 2025 16:11:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6508a821cf1020be165986a9/df9af083-eb67-475a-9d66-f9c3868a1a84.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This will be more of an FAQ-style article, which is divergent from my regular content, but I wanted to write a guide for non-technical people or people unfamiliar with AI. You hear all these buzzwords, and they may be scary or intriguing. You want to learn more, but have no clue where to start.</p>
<p>You are worried about AI taking your job or what an AI-powered future may look like.</p>
<p>If you fit into one of the above categories, then this article is for you.</p>
<blockquote>
<p>PS: <a href="https://en.wikipedia.org/wiki/The_Hitchhiker%27s_Guide_to_the_Galaxy">The Hitchhiker's Guide to the Galaxy - Wikipedia</a> ~ Inspired by this insanely cool movie.</p>
</blockquote>
<h2>What is AGI?</h2>
<p>Let’s get this out of the way first. Nobody seems to have a clear idea of what AGI actually means, but here’s a general guideline.</p>
<p>AGI, at its core, is a goal post shift; previously, “AI” was thought of as being the all-intelligent machine that could think on the same level or even at a higher level than human beings.</p>
<p>AI companies started calling their “chatbots” (I’m using chatbots loosely for now) AI, thus AI lost the meaning it once had, and hence why they coined this new term “AGI”.</p>
<p>AGI, therefore, simply means the following:</p>
<ul>
<li><p>A machine that can understand information within the context of the real world.</p>
</li>
<li><p>A machine that can learn and adapt to perform just about any task a human can do.</p>
</li>
</ul>
<p>Wait! Can’t ChatGPT already do this?</p>
<p>No, ChatGPT can retrieve information, it can generate some pretty cool answers, and answer you in a way that you could mistake it for a human, but in reality, this is just a clever algorithm. ChatGPT itself doesn’t understand the meaning of the text it generates.</p>
<p><strong>Here’s an example:</strong></p>
<p><em>What I asked ChatGPT: “Draw me a clock with the hands showing the time as 12 minutes past 5 pm.”</em></p>
<p><strong>What it generated:</strong></p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755352821561/e8c3f97f-a6b1-48c7-8999-48ab33f802da.png" alt="" style="display:block;margin:0 auto" />

<p>The algorithm picked up that I want an image of a clock, but it got the time horribly wrong. Instead of responding with: “I am sorry, but I cannot draw those numbers on the clock,” or something like that, it still went ahead and drew the clock.</p>
<p>This is because it doesn’t fully understand the instruction; it’s looking for similar information in its vast knowledge base and just retrieving something that’s most similar. There is some fancy science going on to understand meaning to an extent, but still, it does not truly understand anything besides pattern matching.</p>
<p>Even after I corrected ChatGPT: <em>“Uhm, but the time was wrong, please show the time 5:12 pm”,</em> I still get:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755353128149/4841af4f-5cfa-4471-890b-2d4c42fbaba1.png" alt="" style="display:block;margin:0 auto" />

<p>AGI would therefore be aware of the question hollistically, and be able to draw the correct image, and if it can’t, it would recognise that it doesn’t have that ability, and try to learn that skill or just respond with: “I don’t know how to do that yet…”.</p>
<h2><strong>What is an LLM?</strong></h2>
<p>LLM stands for “Large Language Model”. This is basically what most refer to as “AI” or “AI Chatbots”. These are powerful programs that are trained on large pieces of information from around the web; they have a “brain” filled with billions of documents and pieces of information.</p>
<p>When you ask a Chatbot a question, it’s basically doing something similar to “Google Search”. The Chatbot breaks down your question into something called “tokens”, which are mathematical representations of words.</p>
<p>These tokens now give the Chatbot a superpower that goes beyond a simple keyword search, in that it can use math to figure out the relationship of words based on how these tokens are arranged, how far apart they are from each other, and so on.</p>
<p>So essentially, it takes your question and does a similarity search to find the most relevant tokens in its knowledge base, then uses those tokens together with some clever algorithmic magic to figure out the most likely answer that would resolve your query.</p>
<p>Now, complex LLMs like ChatGPT do a lot more magic in the background than just mathematical formulas, but at the end of the day, it’s still just a program that applies an algorithm.</p>
<h2>What is a model?</h2>
<p>Now that we have an understanding of how LLMs work, this is basically a continuation of the last question: “<strong>What is an LLM?”</strong></p>
<p>AI companies like OpenAI spend months collecting data from various sources, and they clean that data by using various data science techniques to ensure it’s as accurate as possible.</p>
<p>The goal of this process is to build a knowledge base on which they can train the AI. You will notice every 6 months or so, there’s a new version of ChatGPT: “o1”, “4o”, “GPT-3”, “GPT3.5”, “GPT-5”, etc…</p>
<p>Training or teaching AI is an expensive venture; it takes time because we are dealing with billions of data points. Thus, a model is a program, a “black box” filled with layers and layers of information (known as neural networks).</p>
<p>The model provider will feed this program with the information over a period of time. The program will then store that information in its neural networks (a bit like memorizing information for an exam) and apply various mathematical calculations to make sense of the data. This process can take months; once it’s complete, the program is then frozen in time, its knowledge and understanding frozen on that dataset, thus the “GPT-5”, “o1”, etc, is just a label of the box based on which dataset it was trained on.</p>
<p>Furthermore, to clarify, if you trained the program when Obama was the president of the US, then asked the model, “Who is the current president? “ In 2022, it’s going to say “Oboma” or “My knowledge cut off is up until 2016, therefore I cannot answer that question”.</p>
<p>At every new version, the AI companies may discover new techniques and improve upon the dataset, which ultimately leads to a smarter model. This is why they name them at the end of each training cycle.</p>
<p>It may be possible that “GPT-5” is the smartest model at this point, but for a particular task, “GPT4o” might still perform better; this is why they don’t just overwrite the old model. It actually happened recently, where “GPT-5” answered questions more robotically; even though it was smarter, it seemed less human, thus people still preferred “GPT4o”.</p>
<p>While the model itself may not have memory beyond its cutoff point; most modern Chatbot systems like ChatGPT or Claude do have access to the internet and other data sources like <strong>RAG</strong>, which allows them to pull up to data information but if you discounted the model from these sources, it’s going to respond with the dataset version of it’s information.</p>
<h2>What is hallucination?</h2>
<p>I am sure you have experienced this before; if not, you will at some point when interacting with a chatbot. You ask the bot a question, it gives you a long, detailed answer full of confidence, but when you fact-check the answer, it’s either completely or partially incorrect.</p>
<p>Helliplication is basically when the algorithm predicts the answer incorrectly, which is bound to happen with current models, regardless of how smart they are.</p>
<h2><strong>What is RAG?</strong></h2>
<p>I covered this extensively throughout my blog with coding examples as well, so I won’t dive too deeply into this subject matter, but will just give you a general idea of what this means. If you are interested in a more technical understanding, please check out one of my earlier articles: <a href="https://kevincoder.co.za/how-to-build-a-pdf-chatbot-with-langchain-and-faiss">How to build a PDF chatbot with Langchain 🦜🔗 and FAISS</a></p>
<p>RAG stands for “Retrieval Augmented Generation,” which is a fancy way of saying that you can supplement the model’s memory with your own dataset.</p>
<p>For example, if I had to ask ChatGPT, “What is the price of the latest iPhone?” It gives me pricing in dollars and information from foreign sources. I am South African, and I’m more interested in the “Rand” price and local stores.</p>
<p>To fix this, I can upload a PDF via the ChatGPT interface detailing the information from local retailers, and then re-ask it the same question, and tell it to find the answer from that PDF. This time round, it’s going to give me the correct answer based on the information in that PDF.</p>
<p>This is incredibly useful to websites that use Chatbots, because if I have an e-commerce store, I can build a RAG system that feeds product data from my store to the Chatbot; thus, even though I’m still using ChatGPT via the API, I can scope its response to only come from my RAG information and suggest only products from my store.</p>
<p>Can you imagine being on takealot.com, and then getting product recommendations from amazon.co.za? This will be a disaster, giving a competitor free advertising!</p>
<p>Therefore, RAG allows us some control of the Chatbot’s knowledge retrieval.</p>
<h2>Inference and other common terminology</h2>
<p>Some common terminology you’ll often hear:</p>
<ul>
<li><p><strong>Tokens</strong>: When models ingest your prompt, they don’t read it as a sentence or word; instead, every word gets translated into a mathematical representation, and these representations are known as tokens. 1 token roughly equates to 4 characters, but this may vary depending on the word or phrase.</p>
</li>
<li><p><strong>Epoch</strong>: An epoch is one complete pass through your entire dataset. You normally would train the model via more than one epoch, because the second, third, fourth times, etc. It sees the data, it can apply optimizations, and improve its learning. Of course, too many Epochs can hurt accuracy, so this requires some balancing efforts.</p>
</li>
<li><p><strong>Inference</strong>: is when you actually ask the model a question, so this is a trained model, and each time you “prompt” the model, it needs to convert your request into tokens and do a whole bunch of math to determine the final response back. This process does require GPU resources as well, but it is not as computationally expensive as training.</p>
</li>
<li><p><strong>Model weights</strong>: After training a model, it generates something called “weights,” which are basically mathematical <strong>patterns</strong> derived from your training data. Since models are essentially predicting the next best word, phrase, or sentence, they use these weights to influence which sentences or words to return based on your input prompt.</p>
</li>
<li><p>Neural network: Inspired by the human brain, which is made up of millions of neurons. Let's call them "micro-computers" that arrange together to form a chain, a network, and together these "micro-computers" can digest, analyze, and parse information in an intelligent way, leading to some outcome like making you smile, or crying, or running, etc. In the same way, models have neural networks made up of many layers, and each layer processes and transforms the information before passing it to the next layer. Collectively, they work together to build complex patterns and ultimately generate tokens that we get back as text or images, or even video.</p>
</li>
<li><p>Model parameters: You’ll often see “7B”, “12B”, “24B”, 70B etc, next to model names. These refer to the number of parameters a model contains (in one billion units, so 7 billion, 12 billion, etc). Parameters are simply the number of weights and neural connections the model contains in its neural networks. Usually, the more parameters = smarter model, but this is not always the case because various factors can influence intelligence, including training process, quality of the dataset, and so forth.</p>
</li>
</ul>
<h2>What fine-tuning?</h2>
<p>Like OpenAI or Anthropic, you too can train your own model on your own dataset. Usually, it would be crazy expensive to start from scratch, so it’s much easier and more cost-effective to just take an existing model and train it further using your own data.</p>
<p>This process will add or replace knowledge in the existing model to make it behave in a certain way. Currently, if you talk to Claude or ChatGPT, it’s conversational, but you may want a model that just follows a particular sequence.</p>
<p>This can be useful for restaurant ordering or booking car repair appointments, or just about any kind of niche task. You can give the model a few hundred / thousand examples, teaching how you want it to respond, and then the model will follow your examples rather than generic answers.</p>
<p>The benefits of fine-tuning:</p>
<ul>
<li><p>Faster inference. With RAG, you need to query a vectorstore, then pass that information to the model. This could add some speed overhead to responses. With Fine-tuned models, you don’t necessarily need to fetch extra data since everything is already baked into the model (you can still use RAG with fine-tuned models if needed).</p>
</li>
<li><p>Cheaper inference. Not always the case, but for niche tasks, you could use much smaller models like “tinyllama” which can run on smaller GPUs.</p>
</li>
<li><p>Better performance. While RAG works well, it doesn’t always lead to accurate results. With fine-tuning, you could get much more accurate responses depending on your data and use case.</p>
</li>
</ul>
<h2>Will AI take my job?</h2>
<p><a href="https://kevincoder.co.za/how-to-build-a-pdf-chatbot-with-langchain-and-faiss">While AI can automate some tasks</a> and help humans speed up their workflow, AI is not a drop-in replacement for human beings, purely because of the distinction made in my first point of “AGI”.</p>
<p>I am not saying there won’t be job losses; of course, with every new platform shift, there will always be a percentage of jobs no longer needed. In most of these cases, it’ll just require upskilling and pivoting of the individuals, rather than mass unemployment.</p>
<p>For example, in the past, you would need a frontend developer to design landing pages. With tools like Claude code, you could potentially spin up a world-class landing page in minutes without a frontend developer.</p>
<p>Does this mean the end of all frontend developers? No! Not all websites are simple landing pages; some need multi-stage forms, integration with legacy systems, and complex workflows. AI is simply not smart enough to handle all of these.</p>
<p>Thus, these developers would probably have more time to actually sit with clients or expand their skills into other domains like backend development, or even end up making way more money! Because they can automate more of their job, thus allowing them to push out more projects in a shorter space of time.</p>
]]></content:encoded></item><item><title><![CDATA[Engineering 101: Understanding bits and bytes]]></title><description><![CDATA[In my early years, when my age was still single digits, I was tearing apart electrical things like there’s no tomorrow. I was trying to understand how they tick. How do they work? Where do sounds and pictures come from on TV? What happens when this V...]]></description><link>https://kevincoder.co.za/engineering-101-understanding-bits-and-bytes</link><guid isPermaLink="true">https://kevincoder.co.za/engineering-101-understanding-bits-and-bytes</guid><category><![CDATA[Computer Science]]></category><category><![CDATA[data structures]]></category><category><![CDATA[binary]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Sun, 18 May 2025 15:42:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747582800306/51fb2880-1ed3-431f-b2d2-cca8f922663a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my early years, when my age was still single digits, I was tearing apart electrical things like there’s no tomorrow. I was trying to understand how they tick. How do they work? Where do sounds and pictures come from on TV? What happens when this VCR thing (the device that played movies before CDs and DVDs) reads the tape?</p>
<p>Not fun for my parents, as you can imagine 🙈</p>
<p>Nonetheless, this inquisitiveness has helped me a ton throughout my career. I love building stuff, but I love tearing things apart even more! I am never content with “It just works.” I always ask why.</p>
<p>As a software engineer, this is a vital skill to have, especially now in the modern era of AI, where code is just being generated willy-nilly. Understanding computer fundamentals will help you immensely in the long run to sustain a long and fruitful career in tech.</p>
<p>The goal of this article is to make you stop and think for a second about your code and what is actually happening when you hit that “build,” “compile,” or “run” button.</p>
<blockquote>
<p>ℹ️ I will use a mixture of languages to demostrate some concepts (Usually Python and Golang), you do not need to understand these languages just yet, since the goal is to teach you how to think like an Engineer, such that you can apply this knowledge to just about any language.</p>
</blockquote>
<h2 id="heading-programming-ihttpswwww3schoolscompythondefaultasps-just-1s-and-0s-essentially-electrical-signals"><a target="_blank" href="https://www.w3schools.com/python/default.asp">Programming i</a>s just 1s and 0s, essentially electrical signals</h2>
<p>Computer hardware can only understand one language: either turn on the flow of electricity in a pathway or turn it off.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746425126749/e6b96094-0c28-4653-8f47-857115e1f403.jpeg" alt class="image--center mx-auto" /></p>
<p>Thus, regardless of the programming language you use, everything gets compiled down to machine code. Which is a set of instructions your operating system eventually passes down to the hardware in your computer as electrical signals.</p>
<p>Now, obviously, you cannot send raw 1s and 0s to the hardware; this is why we have operating systems and something known as machine code.</p>
<p>Operating systems are responsible for actually taking the machine code and converting it into electrical signals. Not all devices have an operating system; some just have smaller programs, often referred to as embedded systems. Nonetheless, they perform a similar role to that of a big OS like Windows, Linux, or Mac OS.</p>
<p>Machine code is the lowest level of software abstraction, and the last mile before code becomes electricity inside transistors. Essentially, machine code is a chain of ones and zeros, arranged together in a particular sequence.</p>
<p>Think of a computer monitor. It’s made up of millions of pixels of colors, i.e., dots. When we need to print the words “Hello World” to the screen, we send the OS machine code that may look like this: “1111 1101 1011 1111 1101 1111 1011” (Of course, I just made up this sequence for demonstration purposes).</p>
<p>Using this sequence, the system determines which pixels need to light up to form the shapes of "H-e-l-l-o W-o-r-l-d" according to the selected font, size, and color.</p>
<p>When you write a program, your code looks similar to written languages like English, albeit with a bit of math and symbols, but essentially it’s human-readable. This is by design, because we humans cannot manage thousands of 1s and 0s; it’ll be almost impossible to read and write accurately.</p>
<p>This is why we invented programming languages; they offer a high level of abstraction, allowing programmers to write code in a readable language, but also write code in a structured way so that it can be later converted into electrical signals.</p>
<p><a target="_blank" href="https://learnpython.com/?ref=ngm5zjv"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747632233875/203bae16-5986-44ec-8a44-37a9e253de74.jpeg" alt class="image--center mx-auto" /></a></p>
<h2 id="heading-translator-programs">Translator programs</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747475999201/8b4b249d-d5be-47b0-bd4c-6a72ef38aabb.jpeg" alt class="image--center mx-auto" /></p>
<p>Since programming languages aren’t written in machine code, the Operating System itself cannot understand your code. Instead, we need a translator program, something that can read your code and convert it to machine code.</p>
<p>Whenever you start working with a new programming language, you’ll often need to download and install some kind of runtime program like the Python interpreter or Node, or the JVM, and so forth.</p>
<p>While most of these runtimes come with various libraries and tools, at the heart of each runtime is a translator program of some sort.</p>
<p>There are many variations of translator programs, but generally, these are the most common:</p>
<ul>
<li><p>Compilers. In Golang, for example, the compiler runs once and generates what is known as an “executable”. This is your entire program in a binary format. To distribute your program, you no longer need the source code (the code you type in your coding editor) or the runtime to run the executable program, since it’s now machine code and can be executed directly by the Operating System.</p>
</li>
<li><p>Interpreters. In the case of languages like Python, you do not build a single executable. Instead, you need to distribute your source code files, and in order for the target machine to run them, it needs to have the Python interpreter installed. While the interpreter may convert the program to an intermediary format like “bytecode” (we’ll talk about this soon), it’s not machine code and still needs processing to become machine code. So basically, interpreters read and convert your program’s code to machine code at runtime, hence why Python is much slower than Golang in terms of execution speed.</p>
</li>
<li><p>JIT compilers. A best of both worlds, Node.js is a good example. When you distribute your program, it’s distributed as source code. When the program is first run, the JIT compiler will analyze your code and convert frequently used portions into binary code similar to a compiler. It further monitors and optimizes the code as the program is running.</p>
</li>
</ul>
<p>So, in summary, translator programs like the Golang compiler are responsible for taking code you write in your code editor and converting that to machine code, such that your operating system can then execute and eventually convert those to electrical signals.</p>
<blockquote>
<p>💡 “Parsing” is a step in the tranlation process of your code into an executable program. The translator program reads your code and analyzers it against the syntax rules of the programming language being used. During this process the parser will also throw errors if any exist in your code. Which can crash your program or cause the compiler to not generate an executable binary, depending on the type of translator program.</p>
</blockquote>
<h3 id="heading-so-what-is-byte-code">So, what is “Byte Code”</h3>
<p>In the past, interpreters were simple; they read your source code at runtime, line by line, and then converted it to binary code, which then got executed. This all happened in real-time. The problem with this approach is that it’s slow, too slow for modern times, where applications are doing much more work and have many more users running the program concurrently, especially on the web.</p>
<p>Modern interpreters, like the Python Interpreter, have a caching mechanism. Instead of constantly re-reading the source code and going through all the startup steps like parsing over and over again, the Python interpreter will transform your raw source code into “.pyc” files.</p>
<p>This is not machine code; it’s basically just an optimized version of your source that’s already gone through the initial startup steps, like parsing. It’s not yet readable by the operating system. Bytecode is designed to be readable by the Python interpreter. The interpreter still has to execute this bytecode at runtime, translating each bytecode into an instruction for the computer to execute.</p>
<p>When you “publish” your application code, that version of code won’t change until you publish an update, so the parsing (and other code processing stages) only needs to happen once, and thereafter the interpreter knows that your code is valid and has passed all the checks, thus it doesn’t need to keep redoing all these steps.</p>
<p>While interpreters are slower than compilers, they do offer the benefit of hot-swapping code. In a compiled program, you have to recompile the entire program whenever you make a change and then redeploy that binary.</p>
<p>With interpreters like the Python interpreter, you can simply change the file(s) that have been updated, and the interpreter will automatically recreate “.pyc” files only for what has changed. This makes it easier to debug code or deploy portions of your code while the program is still running (you may still need to restart the application for all parts of your program to see the update).</p>
<p>So in summary, a “translator” program (like compilers or interpreters) takes source code (structured human-readable instructions for the computer) that you type in a code editor like VSCode and converts that to machine code. Machine code is <strong>binary instructions</strong> (1s and 0s) that the computer's processor can understand and execute.</p>
<h2 id="heading-what-are-bytes">What are Bytes?</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747476562820/3f10383a-d1dc-425d-80e5-620ab1e60cfb.jpeg" alt class="image--center mx-auto" /></p>
<p><strong>Each bit (binary digit)</strong> does one very simple job: either <strong>on (1)</strong> or <strong>off (0)</strong>, letting current through or not. Alone, this doesn’t amount to much, but together in a large program with millions of 1s and 0s arranged in a certain way, that program can achieve quite complex results like playing a movie or sending an email, and so on.</p>
<p>Now, imagine you own an electronics company, and your company <strong>sells TVs</strong>:</p>
<ul>
<li><p>The <strong>customer buys a finished TV</strong>, not the list of internal components or the names of engineers who assembled it.</p>
</li>
<li><p>They don’t care which team achieved what or was responsible for which component.</p>
</li>
<li><p>What they see is the final product: <strong>the TV</strong>. They don’t even see the components inside the TV; they just see the TV as a whole.</p>
</li>
</ul>
<blockquote>
<p>💡 <strong>Abstraction</strong> is about hiding complexity and exposing only what’s necessary.</p>
</blockquote>
<p>The purpose of the above example is to make you understand what “Abstraction” is, because from here on out, when we move away from simple 1s and 0s, we start to get into the software realm, where programmers use varying levels of abstractions to ultimately achieve some result.</p>
<p>At the highest level, you write a program in a language of your choice, let’s say Python. This program consists of instructions written in human-readable code. Example:</p>
<pre><code class="lang-python">print(<span class="hljs-string">"Hello world!"</span>)
</code></pre>
<p>Eventually, the translator program will convert this into machine code (just made up for illustration purposes):</p>
<pre><code class="lang-python"><span class="hljs-number">10110000</span> <span class="hljs-number">01100001</span>
<span class="hljs-number">10110001</span> <span class="hljs-number">01100010</span>
</code></pre>
<p>When looking at the machine code closely, you’ll notice that each block of numbers is exactly 8 digits long. This is what a “Byte” is: it’s a grouping of binary numbers in sequences of eight. Each Byte is, therefore, a “box” of sorts; it stores digital information by arranging 1s and 0s in different patterns (maximum 8 bits).</p>
<p>In the case of <code>print(“Hello world!”)</code> . Each letter or symbol is represented by a byte, which could look like:</p>
<pre><code class="lang-python">p   -&gt; <span class="hljs-number">112</span> -&gt; <span class="hljs-number">01110000</span>
r   -&gt; <span class="hljs-number">114</span> -&gt; <span class="hljs-number">01110010</span>
i   -&gt; <span class="hljs-number">105</span> -&gt; <span class="hljs-number">01101001</span>
n   -&gt; <span class="hljs-number">110</span> -&gt; <span class="hljs-number">01101110</span>
t   -&gt; <span class="hljs-number">116</span> -&gt; <span class="hljs-number">01110100</span>
(   -&gt; <span class="hljs-number">40</span>  -&gt; <span class="hljs-number">00101000</span>
<span class="hljs-string">"   -&gt; 34  -&gt; 00100010
H   -&gt; 72  -&gt; 01001000
e   -&gt; 101 -&gt; 01100101
l   -&gt; 108 -&gt; 01101100
l   -&gt; 108 -&gt; 01101100
o   -&gt; 111 -&gt; 01101111
    -&gt; 32  -&gt; 00100000
w   -&gt; 119 -&gt; 01110111
o   -&gt; 111 -&gt; 01101111
r   -&gt; 114 -&gt; 01110010
l   -&gt; 108 -&gt; 01101100
d   -&gt; 100 -&gt; 01100100
!   -&gt; 33  -&gt; 00100001
"</span>   -&gt; <span class="hljs-number">34</span>  -&gt; <span class="hljs-number">00100010</span>
)   -&gt; <span class="hljs-number">41</span>  -&gt; <span class="hljs-number">00101001</span>
</code></pre>
<p>As you can see by arranging the 1s and 0s in different sequences, we can represent letters, numbers, and symbols.</p>
<p>If you look closely, we also have numbers 112, 114, 105, and so on. This is another significant thing to understand about bytes. A byte can only store numbers; you cannot put “p” into a Byte because computers only understand numbers. Thus, we assign a number to each character (this is known as character encoding, more on that later).</p>
<p>When you arrange the 1s and 0s together in a group of eight, these patterns have a maximum of 256 different combinations. Thus, a Byte can hold a number from 0 to 255.</p>
<p>In the real world, we use “Grams” to weigh things. Often in the real world, Grams numerically become too large and cumbersome to work with. It’s far easier to write and read 10 kg versus 10,000 grams.</p>
<p>Similarly, in the computing world, most programs will easily exceed 1000 Bytes. So a number like 1,073,152 Bytes can become really cumbersome to comprehend as a human; instead, we also have bigger units of measure like Kilobytes, Megabytes, Gigabytes, and so on.</p>
<p>These measures work as follows:</p>
<ul>
<li><p>1KB = 1024 Bytes</p>
</li>
<li><p>1MB = 1024 Kilobytes</p>
</li>
<li><p>1GB = 1024 MB</p>
</li>
</ul>
<p>Note: Due to marketing and varying hardware manufacturers, some use units of thousands, i.e., 1000 KB, 1000 MB, instead of 1024.</p>
<p>In summary, a “Byte” is a virtual unit of measure that is standardized across all computing systems. It groups together 8 bits (8 1s and 0s), and can be thought of as a “box” that can hold a number from 0 to 255.</p>
<h2 id="heading-what-is-character-encoding">What is character encoding?</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747578981653/3d44b6e2-4051-4c55-b21b-d909cbce5541.jpeg" alt class="image--center mx-auto" /></p>
<p>On a mobile device, using icons and GIFs is common to convey certain meanings. We do this because, well, we’re chilling and lazy to type 🙃. Why type out a whole phrase, “I am so happy right now!” when you can just send a smiley face emoji?</p>
<p>Most people worldwide, regardless of what language they speak, can understand what an emoji means because at some point, we all agreed that <strong>😃 means happy and</strong> ☹️means sad. For each of these emojis, we associated an emotion and globally accepted this meaning.</p>
<p>In computers, we know that there are just 2 digits that represent everything, either 1 or 0. When we group these two digits together, we can attach a more complex meaning to a Byte of data.</p>
<p>Still, a Byte can only represent numbers and the maximum number of 255. So, how on earth do we represent letters, let alone emojis?</p>
<p>To solve this problem, computer scientists developed character encodings. Character encodings basically associate a letter or character with a particular byte or bytes. Thus, character encodings are simply a standard of meaning for converting characters into bytes.</p>
<p>One of the earliest character encodings that worked well with the English language was known as “ASCII”. Similar to the emoji analogy, in ASCII, each character was associated with a particular number from 0 to 127, allowing the computer to capture the meaning of each character in a numerical format.</p>
<blockquote>
<p>💡 This version of ASCII we’re talking about is know as “Standard ASCII”. later versions of ASCII can also use up more than 127.</p>
</blockquote>
<p>Remembering our discussion on Bytes, you’ll recall that a Byte can store any number from 0 to 255. So, essentially, each ASCII encoding can be represented by a single Byte. Furthermore, each ASCII character only needs 7 bits out of the potential 8 bits in a byte (so at least one bit will always be off ‘0’).</p>
<blockquote>
<p>💡 Notice how everything starts at 0. This is vital for later on in your career, because this pattern persists throughout programming. Especially in data structures like Arrays, in most languages these always start at zero.</p>
</blockquote>
<p>While still used today in many systems, ASCII became very limited later when we needed to store more complex characters, such as emojis and characters from other languages, like Japanese. To solve this problem, a more robust encoding system, known as Unicode, was introduced.</p>
<p>Unicode is a tad bit more complex, like ASCII, it allows us to represent characters in a numerical form known as “code points”; however, Unicode itself is just a standard (Like a piece of paper with a list of rules to follow). You still need a system to actually store those code points inside Bytes.</p>
<p>These systems are known as encoding schemas, and there are a few varying <strong>implementations</strong>, like <strong>UTF-8</strong>, <strong>UTF-16</strong>, or <strong>UTF-32</strong>.</p>
<p>UTF-8 is the most popular; it basically uses up to 4 bytes to store characters. A simple English character would use 1 byte; however, some characters from a more complex language like Japanese can use 2-4 Bytes.</p>
<p>Encodings don’t only apply to text or characters; you also get encoding systems like the RGB system, which basically represents a colour using the 0-255 range. RGB uses 3 bytes to do this; each byte provides a value from 0 to 255 for the red, green, and blue color channels, respectively. By combining these three primary colors at different intensities, RGB encoding can represent approximately millions of different colours.</p>
<p>Some RGB examples:</p>
<pre><code class="lang-python">rgb(<span class="hljs-number">255</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>) ~ Red
rgb(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">255</span>) ~ Blue
rgb(<span class="hljs-number">0</span>, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>) ~ Green
rgb(<span class="hljs-number">238</span>, <span class="hljs-number">130</span>, <span class="hljs-number">238</span>) ~ Pink
</code></pre>
<h2 id="heading-the-math-of-binary">The Math of Binary</h2>
<p>In regular math, we are accustomed to the base 10 system, 1-10-100. Since we have a maximum of 10 digits(0-9), every time we reach the number 9, we need to “reset,” which means we add a 0 to the right, and the number on the left increases by 1.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747635152539/47eb7d1b-da79-434d-bea5-6510e9b908c5.jpeg" alt class="image--center mx-auto" /></p>
<pre><code class="lang-python"><span class="hljs-number">9</span>
<span class="hljs-number">10</span>
<span class="hljs-number">11</span>
<span class="hljs-number">12</span>
<span class="hljs-number">13</span>
<span class="hljs-number">15</span>
<span class="hljs-number">15</span>
<span class="hljs-number">16</span>
<span class="hljs-number">17</span>
<span class="hljs-number">18</span>
<span class="hljs-number">19</span>
<span class="hljs-number">20</span>
<span class="hljs-number">21</span>
</code></pre>
<p>Now, when we get to 9, we have run out of numbers. We can’t add 1 to the left because “9” is the highest digit in the numbering system. When this happens, we simply replace each “9” with a “0” and then add 1 to the far left, i.e., 10; the same applies to 99, 999, and so on.</p>
<pre><code class="lang-python"><span class="hljs-number">9</span> + <span class="hljs-number">1</span> = <span class="hljs-number">10</span>       = <span class="hljs-number">10</span>¹
<span class="hljs-number">99</span> + <span class="hljs-number">1</span> = <span class="hljs-number">100</span>     = <span class="hljs-number">10</span>²
<span class="hljs-number">999</span> + <span class="hljs-number">1</span> = <span class="hljs-number">1000</span>   = <span class="hljs-number">10</span>³
<span class="hljs-number">9999</span> + <span class="hljs-number">1</span> = <span class="hljs-number">10000</span> = <span class="hljs-number">10</span>⁴
</code></pre>
<p>Essentially, we are always working from right to left; we check the rightmost number to see if it’s 9, and if so, we apply the rules as per above. This is known as positional notation.</p>
<p>In <strong>base-10</strong>, when you reach a 9, you need to reset by adding a zero. In binary, it’s similar except you just add 1 or 0 to the left, so you effectively just increase the width of bits.</p>
<p>So to convert a binary number into a base 10 number, we use this width to determine how far this bit appears from the right (so effectively it’s position starting from 0, incrementing by 1 the further you go left).</p>
<p>Once you determine the position, this is now the power you must raise by a base of 2 to calculate the corresponding base-10 value.</p>
<p>In the case of 1 byte, the 8th number will be 2^0 (remember we always start with a zero), the 7th number 2^1, the 6th number 2². The further left you go, the higher the power. In the case of a Byte, the maximum power we can raise to is “7”, which is basically: <code>2×2×2×2×2×2×2=128</code></p>
<pre><code class="lang-python"><span class="hljs-number">2</span>^<span class="hljs-number">0</span> × <span class="hljs-number">1</span> = <span class="hljs-number">1</span>
<span class="hljs-number">2</span>^<span class="hljs-number">1</span> × <span class="hljs-number">1</span> = <span class="hljs-number">2</span>
<span class="hljs-number">2</span>^<span class="hljs-number">2</span> × <span class="hljs-number">1</span> = <span class="hljs-number">4</span>
<span class="hljs-number">2</span>^<span class="hljs-number">3</span> × <span class="hljs-number">1</span> = <span class="hljs-number">8</span>
<span class="hljs-number">2</span>^<span class="hljs-number">4</span> × <span class="hljs-number">1</span> = <span class="hljs-number">16</span>
<span class="hljs-number">2</span>^<span class="hljs-number">5</span> × <span class="hljs-number">1</span> = <span class="hljs-number">32</span>
<span class="hljs-number">2</span>^<span class="hljs-number">6</span> × <span class="hljs-number">1</span> = <span class="hljs-number">64</span>
<span class="hljs-number">2</span>^<span class="hljs-number">7</span> × <span class="hljs-number">1</span> = <span class="hljs-number">128</span>
--------------------
Total     = <span class="hljs-number">255</span>
</code></pre>
<p>The above is a calculation based on a single Byte, where all values are “1” <code>11111111</code> This is the maximum value a Byte can hold, which, as you can see, adds up to 255.</p>
<h2 id="heading-what-is-a-variable">What is a variable?</h2>
<p>Since compilers and interpreters take care of converting code into bytes, we don’t often need to worry about how the code is translated and mapped on a character level. Instead, programming languages introduce their own “box” to put stuff in, called a “variable”.</p>
<p>A variable is essentially x number of Bytes that are grouped together to hold a certain portion of information, like a word or number, or even an image. I say “x” because, unlike a Byte, which has a fixed size of 8 bits, variables can be made up of any number of Bytes.</p>
<p>The number <code>52</code> For example can fit in a single Byte. Whereas an image would take up hundreds, if not thousands, of Bytes. Regardless of the size of either of these two entities, we can still store them in a single variable:</p>
<pre><code class="lang-python">the_number_fifty_two = <span class="hljs-number">52</span>
profile_pic = <span class="hljs-string">'&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"&gt;&lt;!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--&gt;&lt;path d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512l388.6 0c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304l-91.4 0z"/&gt;&lt;/svg&gt;'</span>
</code></pre>
<p>In addition to a variable being able to store varied information, the box or size of the variable can also grow or shrink as you add or remove data. Example: when the variable is first created, we only have 2 names:</p>
<pre><code class="lang-python">names = [<span class="hljs-string">"Kevin"</span>, <span class="hljs-string">"John"</span>]
</code></pre>
<p>But, after we run the program, some process appends more names to the list, and thus our variable automatically grows in memory to accommodate more items:</p>
<pre><code class="lang-python">.... do some computation to change names ....
names = [<span class="hljs-string">"Kevin"</span>, <span class="hljs-string">"John"</span>, <span class="hljs-string">"Peter"</span>, <span class="hljs-string">"Paul"</span>, <span class="hljs-string">"David"</span>, <span class="hljs-string">"Stacey"</span>]
</code></pre>
<p>Languages like Python allow for this automatically without much effort from the programmer; however, other languages like C require you to specify the size of a variable upfront more often than not.</p>
<p>Most languages, like Python, have something called “Garbage collection”. Now, what this basically means is that the programming language will watch your variables and automatically delete the memory they occupy whenever that variable is no longer needed (known as going out of scope).</p>
<p>Garbage collection is often not available in some languages like C and C++; thus, as a programmer, you have to manually delete variables (or deallocate memory) when they are no longer needed and carefully create variables cognizant of the fact that you must take up as little memory as possible.</p>
<p>This is also why languages like C are used in game development, hardware drivers, and so on, because by manually managing memory, you can optimize your program to use as little RAM as possible and also deallocate memory as quickly as possible.</p>
<p>Since the garbage collection process itself eats up some memory and may not clear memory quickly enough, languages like Python are often used in web development and other environments where the code runs on bulky servers with lots of RAM, thus the need to minimize memory usage isn’t as important.</p>
<p>In summary, a variable is a box in memory that we use in programming languages to store information. Variables can store a wide variety of information, and each box can be of varying sizes depending on the type of variable.</p>
<p>Ultimately, each variable is just a grouping of bytes in memory. The variable can be made up of any number of Bytes (very similar to how we put 8 bits together to make one Byte, we put x number of Bytes together to make a variable).</p>
<h2 id="heading-primitive-data-types">Primitive Data Types</h2>
<p>Okay, so we learnt that in programming languages we use a variable, which essentially is a box to put data in. We also know that variables can hold a wide variety of information.</p>
<p>The problem is that programming is precise; you want to be as precise as possible. You don’t want to mix numbers and letters willy-nilly. Some languages, like PHP and Python, are a bit flexible in this regard; they are what we call dynamically typed languages.</p>
<p>In Python, this is valid (although never a good idea):</p>
<pre><code class="lang-python">my_name = <span class="hljs-string">"Billy"</span>
my_name = <span class="hljs-number">123</span>
</code></pre>
<p>Now, looking at the code carefully. I created a variable “my_name” and then put the word “Billy” inside that box, then in the very next line, I replaced “Billy” with 123. Python allows this because it doesn’t enforce types by default.</p>
<p>Can you imagine working on a large financial system, and somebody mistakenly assigns text to a variable:</p>
<pre><code class="lang-python">account_balance=<span class="hljs-number">500</span>
account_balance=<span class="hljs-string">"500"</span>
</code></pre>
<p>The problem is that they may look like the same value, 500, but actually, they are not. See what happens when I try to update the account balance:</p>
<pre><code class="lang-python"><span class="hljs-meta">&gt;&gt;&gt; </span>account_balance=<span class="hljs-string">"500"</span>
<span class="hljs-meta">&gt;&gt;&gt; </span>account_balance+<span class="hljs-number">1</span>
Traceback (most recent call last):
  File <span class="hljs-string">"&lt;stdin&gt;"</span>, line <span class="hljs-number">1</span>, <span class="hljs-keyword">in</span> &lt;module&gt;
TypeError: can only concatenate str (<span class="hljs-keyword">not</span> <span class="hljs-string">"int"</span>) to str
&gt;&gt;&gt;
</code></pre>
<p>Since “500” in quotes is actually a string (a chain of characters), Python throws an error. Saying: Wait a minute! I cannot add 1 to the word “500”. “500’ is not a number!</p>
<p>In some languages like Golang, TypeScript, C#, etc, doing something like the above will just not compile. Here’s a simple Go example:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> accountBalance <span class="hljs-keyword">int</span> = <span class="hljs-number">1</span>
    fmt.Println(accountBalance)
}
</code></pre>
<p>Don’t worry so much about the syntax, such as “package”, “import”, “func”, etc I want you to zone in on this line:</p>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> accountBalance <span class="hljs-keyword">int</span> = <span class="hljs-number">1</span>
</code></pre>
<blockquote>
<p>💡 The “var” keyword simply tells the Go compiler that you are making a new variable named “accountBalance”.</p>
</blockquote>
<p>You will notice the <code>int</code> keyword. This is known as a primitive data type. The <code>int</code> data type tells the Golang compiler that this box in memory can only hold numbers, and more specifically, an integer. So when I do something like this, it will fail to compile:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> accountBalance <span class="hljs-keyword">int</span> = <span class="hljs-number">1</span>
    accountBalance = <span class="hljs-string">"pokemon"</span>
    fmt.Println(accountBalance)
}
</code></pre>
<p>The error:</p>
<pre><code class="lang-go">➜  test <span class="hljs-keyword">go</span> run main.<span class="hljs-keyword">go</span>
# command-line-arguments
./main.<span class="hljs-keyword">go</span>:<span class="hljs-number">7</span>:<span class="hljs-number">19</span>: cannot use <span class="hljs-string">"pokemon"</span> (untyped <span class="hljs-keyword">string</span> constant) as <span class="hljs-keyword">int</span> value in assignment
</code></pre>
<p>Languages like Golang are known as “statically typed” languages.</p>
<p>Essentially, data types allow us to categorize variables so that we can store information in a structured way. The goal is to separate information based on different characteristics, such as integers (whole numbers), strings (text), floating-point numbers (decimals), and other distinct types.</p>
<blockquote>
<p>💡 In the real world, you can think of data types as classifying different pieces of clothing. Just as we categorize items as "boots," "hats," or "shorts" based on their distinct characteristics and purposes, data types help us organize different kinds of information based on their unique properties and how they can be used.</p>
</blockquote>
<p>Different languages have slight variations of these primitive data types, but in general, they mostly fall into the following categories:</p>
<ul>
<li><p>Char - a single character like ‘A’.</p>
</li>
<li><p>String - a string of characters like “Hello”.</p>
</li>
<li><p>Byte - we already know this one.</p>
</li>
<li><p>Boolean - true or false</p>
</li>
<li><p>Decimal number - so basically similar to money, e.g., 120.99</p>
</li>
<li><p>Float - similar to Decimals, except they are not as precise</p>
</li>
<li><p>Integers - these are whole numbers 1,2,3,4,5, and can be negative as well, 1,-2,-3. You also get variations in size, int, int64, and so on.</p>
</li>
</ul>
<p>There’s way more to primitive data types than this simple article can cover, but hopefully, this is enough of a starting point for you to build upon.</p>
<h2 id="heading-collections-and-dictionaries">Collections and dictionaries</h2>
<p>Okay, so we learnt about variables, which are boxes in memory that you can put information into, and we learnt that each variable can have a type as well, like integers, strings, booleans, etc (primitive types).</p>
<p>In addition to these primitive types, variables can also hold more complex data types such as objects, lists, dictionaries, tuples, and more. Usually, complex data types are made up of one or more of the primitive types.</p>
<p>Let’s look at 2 of the most common types, i.e., lists and dictionaries. A list, as the name suggests, is a type that can hold a list of items:</p>
<pre><code class="lang-python">shopping_list = [<span class="hljs-string">"eggs"</span>, <span class="hljs-string">"milk"</span>, <span class="hljs-string">"butter"</span>]
</code></pre>
<p>Using Python, we created a “shopping_list” variable, which is a list of 3 items. Each list item in this case is of a primitive string type; however, the type can be just about any of the other types, including a complex type.</p>
<p>In programming, lists are very handy and used in just about every program. Some examples:</p>
<ul>
<li><p>In card games, you can use a list to store the deck of 52 cards.</p>
</li>
<li><p>In e-commerce, A list can store all items in the user’s shopping basket.</p>
</li>
<li><p>In a website’s navigation bar, a list can be used to store links to the relevant pages.</p>
</li>
</ul>
<p>Unlike the primitive data types, a list can hold multiple values (usually these values are of the same primitive type, but not always), which makes it perfect for storing related items like products in a shopping cart.</p>
<p>In other languages, lists are also referred to as “arrays”; however, arrays can also have other features not supported by a simple list. So while the data type is similar, it’s not always exactly the same type.</p>
<p>For example, in JavaScript, arrays are the same as Python lists:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> shoppingArray = [<span class="hljs-string">"eggs"</span>, <span class="hljs-string">"milk"</span>, <span class="hljs-string">"butter"</span>];
</code></pre>
<p>However, in PHP, while the type is called “array”, it’s much more powerful than a simple list since it also has features of the “dictionary” data type.</p>
<pre><code class="lang-php">$shoppingArray = [<span class="hljs-string">"eggs"</span>, <span class="hljs-string">"milk"</span>, <span class="hljs-string">"butter"</span>];
$personArray = [<span class="hljs-string">"name"</span> =&gt; <span class="hljs-string">"Kevin"</span>, <span class="hljs-string">"surname"</span> =&gt; <span class="hljs-string">"Naidoo"</span>];
</code></pre>
<p>Lists or Arrays are usually zero-indexed, meaning that when we create the Python shopping list, under the hood, the language is assigning each item a position in the list:</p>
<blockquote>
<p>💡 The <code>#</code> character in Python is a comment, meaning that the interpreter will ignore this. It’s merely there to help programmers communicate extra information to other programmers.</p>
</blockquote>
<pre><code class="lang-python">shopping_list = [<span class="hljs-string">"eggs"</span>, <span class="hljs-string">"milk"</span>, <span class="hljs-string">"butter"</span>]

<span class="hljs-comment"># Postions</span>
<span class="hljs-comment"># 0 =&gt; eggs</span>
<span class="hljs-comment"># 1 =&gt; milk</span>
<span class="hljs-comment"># 2 =&gt; butter</span>

print(shopping_list[<span class="hljs-number">0</span>])  <span class="hljs-comment"># Will print eggs</span>
print(shopping_list[<span class="hljs-number">1</span>])  <span class="hljs-comment"># Will print milk</span>
</code></pre>
<p>The number we’re accessing above to print each item is known as an “index” and basically points to the position of the item in the list from left to right. Where the furthest position to the left is zero, and you keep adding 1 to the index as you move further right.</p>
<p>Moving backwards:</p>
<pre><code class="lang-python">shopping_list = [<span class="hljs-string">"eggs"</span>, <span class="hljs-string">"milk"</span>, <span class="hljs-string">"butter"</span>]

<span class="hljs-comment"># Postions</span>
<span class="hljs-comment"># -3 =&gt; eggs</span>
<span class="hljs-comment"># -2 =&gt; milk</span>
<span class="hljs-comment"># -1 =&gt; butter</span>

print(shopping_list[<span class="hljs-number">-1</span>])  <span class="hljs-comment"># Will print butter</span>
print(shopping_list[<span class="hljs-number">-2</span>])  <span class="hljs-comment"># Will print milk</span>
</code></pre>
<p>When moving backwards, the index starts from <code>-1</code> and you add <code>-1</code> to the index each time you move towards the left. The farthest item on the left when going backwards will be <code>-3</code> In this case, it’s basically <code>total_items * -1</code>.</p>
<p>It’s not common to move backwards, so 99% of the time, you use this approach to simply get the last item when you don’t know the size of the list.</p>
<p>For example, in a deck of 52 cards, you may play some cards into a discard pile. Now, to access the last card in the deck, you can’t simply use <code>cards[51]</code>. Unless you count the cards in the deck again, you have no idea how many cards remain in the main deck.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748086775647/2f337715-c6ca-477d-a3b0-0ba5a4662e0f.png" alt class="image--center mx-auto" /></p>
<p>By using <code>cards[-1]</code> This will always point to the last item in the deck so long as you have at least one card. Thus, you can dynamically always get the last item without having to constantly recount the deck.</p>
<p>Okay, so now we know that a list contains multiple items and each item is assigned a numerical position starting from 0. This data type is great if you don’t want to label the items, but what if you need to label the items?</p>
<p>For example, in your app, you have a dropdown asking users to select their country. You don’t want to manually type out each option by hand, so behind the scenes, you use a Python list populated from a database table to build an HTML dropdown:</p>
<pre><code class="lang-python"><span class="hljs-comment"># This data will be filled from the DB, just showing two values as an example.</span>
countries = [<span class="hljs-string">"za"</span>, <span class="hljs-string">"us"</span>]
</code></pre>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">select</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"za"</span>&gt;</span>za<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"us"</span>&gt;</span>us<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>
</code></pre>
<p>The above is a simple HTML dropdown that will allow the user to select their country preference. The problem is, the user might not know their country code, and it’s far easier to select the full country name instead.</p>
<p>In the case of our dropdown, the server gets the value from the <code>value</code> field; it does not care about the label you see inside the <code>&lt;option&gt;</code> tags. In our Python code, we can therefore change the list into a dictionary (also known as a map in other languages). By doing so, we can set a custom index for each item, which will become the value set for the <code>value</code> field in our HTML.</p>
<p>Here’s how to make a dictionary:</p>
<pre><code class="lang-python"><span class="hljs-comment"># You can also put these on one line like a list, I did it this way for readability.</span>
countries = {
   <span class="hljs-string">"za"</span>: <span class="hljs-string">"South Africa"</span>,
   <span class="hljs-string">"us"</span>: <span class="hljs-string">"United States"</span>,
   <span class="hljs-string">"uk"</span>:<span class="hljs-string">"United Kingdom"</span>
}
</code></pre>
<p>A dictionary is very similar to a list, but as you can see, instead of 0,1,2,3 for the index, we set a custom index for each item, which is known as a <code>key</code>. The <code>key</code> will be of type <code>string</code> and serves as a label to better identify that piece of data.</p>
<p>Now, we can modify our dropdown to use the dictionary, so the result should be:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">select</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"za"</span>&gt;</span>South Africa<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"us"</span>&gt;</span>United States<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"uk"</span>&gt;</span>United Kingdom<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>
</code></pre>
<p>A dictionary, therefore, is much more powerful; not only can you store related information together, but you can also label data and build many interesting entities, like, for example, if you want to store information about a book: “The Pragmatic Programmer”.</p>
<pre><code class="lang-python">the_book = [
   <span class="hljs-string">"The Pragmatic Programmer"</span>,
   <span class="hljs-string">"Journeyman to Master"</span>,
    <span class="hljs-number">1999</span>, 
    <span class="hljs-string">"Pearson Education"</span>,
    <span class="hljs-number">352</span>,
    <span class="hljs-number">9780132119177</span>
]
</code></pre>
<p>We have 6 items, it’s kind of easy to guess what they are, but imagine if we had 20 items? This becomes really cumbersome. Instead, we can use a dictionary:</p>
<pre><code class="lang-python">the_book = {
   <span class="hljs-string">"title"</span>: <span class="hljs-string">"The Pragmatic Programmer"</span>,
   <span class="hljs-string">"subtitle"</span>: <span class="hljs-string">"Journeyman to Master"</span>,
    <span class="hljs-string">"Year Published"</span>: <span class="hljs-number">1999</span>, 
    <span class="hljs-string">"Publisher"</span>: <span class="hljs-string">"Pearson Education"</span>,
    <span class="hljs-string">"Number of pages"</span>: <span class="hljs-number">352</span>,
    <span class="hljs-string">"Barcode / ISBN"</span>: <span class="hljs-number">9780132119177</span>
}
</code></pre>
<p>Looking at the second item, it’s far easier to identify what each data point means just by using the dictionary’s <code>key</code>. When we want to print an item, we can do so in a similar way to that of lists, by using the <code>key</code> instead of a numerical position:</p>
<pre><code class="lang-python">print(the_book[<span class="hljs-string">'title'</span>]) <span class="hljs-comment"># The Pragmatic Programmer</span>
print(the_book[<span class="hljs-string">'Year Published'</span>]) <span class="hljs-comment"># 1999</span>
</code></pre>
<p><a target="_blank" href="https://learnpython.com/?ref=ngm5zjv"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748090476214/d6b70ba6-a9cb-462b-82d0-cba7af0ca725.jpeg" alt class="image--center mx-auto" /></a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, we learned about one of the most fundamental concepts in computer programming, i.e., data. We started at the very heart of the computer to understand how binary numbers represent electrical signals in transistors that allow the current to flow or prevent the current from flowing in a particular direction.</p>
<p>We then built upon this knowledge to learn about <code>Bytes</code> , which is a fundamental “Lego block” of modern computing. We even learnt some math, and further expanded into programmatic concepts like compilers, variables, data types, and more.</p>
<p>We’ve seen a little sneak peek into Python, Golang, and other languages.</p>
<p>All in all, this was a fun article to write; it allowed me to refresh some of these concepts I learned many, many years ago. I hope that this was an ideal primer for you, to introduce you to these important fundamentals and help you grow as a developer.</p>
<p>Use this as a springboard into the world of programming; I merely got your feet wet, but to become a good programmer and have a long-lasting career in the industry, you must keep learning and constantly dig deeper.</p>
<p>Happy coding! I’m planning on more content like this that dives deeper into the engineering aspects of programming, so please join the newsletter or check back soon.</p>
<p>Thank you so much for reading <strong>🙏</strong></p>
<p>Cheers 👋</p>
]]></content:encoded></item><item><title><![CDATA[Build a basic login system using Django]]></title><description><![CDATA[Many know and love Django admin (myself included). The problem is that it can be somewhat rigid, and well, let's be honest looks like it’s from 2010. Nevertheless, in Django, it’s stupidly simple to implement your own auth by extending the framework’...]]></description><link>https://kevincoder.co.za/build-a-basic-login-system-using-django</link><guid isPermaLink="true">https://kevincoder.co.za/build-a-basic-login-system-using-django</guid><category><![CDATA[Developer]]></category><category><![CDATA[Django]]></category><category><![CDATA[authentication]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Mon, 14 Apr 2025 16:33:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1744648515220/ebcfaff6-35b8-4dea-8ce5-95adb12fa506.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Many know and love Django admin (myself included). The problem is that it can be somewhat rigid, and well, let's be honest looks like it’s from 2010. Nevertheless, in Django, it’s stupidly simple to implement your own auth by extending the framework’s built-in views and templates.</p>
<p>In this article, let’s look at a quick-fire way of implementing an auth system in a matter of minutes.</p>
<p><a target="_blank" href="https://learnpython.com?ref=ngm5zjv"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745500536842/6103a7e0-09dd-42ce-9e93-671c157023ff.jpeg" alt class="image--center mx-auto" /></a></p>
<h2 id="heading-a-quick-django-primer">A quick Django primer</h2>
<p>This article is designed for developers who are familiar with Django, but just in case you are a complete beginner or just “rusty,” let’s go through some basics:</p>
<p><strong>Virtual Environment:</strong></p>
<p>To get started with Django development, you need a virtual environment. A virtual environment is simply an isolated Python installation that won’t interfere with the global Python installed on your system.</p>
<p>Virtual environments are especially useful on Linux machines. Since most Linux distros use Python for various core libraries, by installing your project packages at the system level, you could potentially break your whole desktop environment!</p>
<p>To set up a virtual environment for our project, run this in your terminal:</p>
<pre><code class="lang-python">mkdir django_projects
cd django_projects
python -m venv .venv
source .venv/bin/activate
</code></pre>
<p>To exit the virtual environment, just type “deactivate” in your terminal, and to go back, simply run the last command as per above, ensuring you’re in the correct folder.</p>
<p>Now let’s install Django and create our project:</p>
<pre><code class="lang-bash">pip install django
django-admin startproject authsystem
<span class="hljs-built_in">cd</span> authsystem
</code></pre>
<p>When you create a new Django project, you should see this folder structure:</p>
<pre><code class="lang-python">.
├── authsystem
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py
</code></pre>
<p>Very barebone and minimal, such is the beauty of Django! Inside your project, you will find a “manage.py” command, similar to the first “django-admin” command. This will allow you to perform various console tasks. The only difference is that “manage.py” is scoped to your project and will be aware of your project’s settings and apps.</p>
<p>Let’s create an application called “accounts”</p>
<pre><code class="lang-bash">python manage.py startapp accounts
</code></pre>
<p>Django has a very clean architecture in that you have projects and apps:</p>
<ul>
<li><p><strong>Projects</strong>: Are a collection of apps, usually your website project, or a library or framework that you're developing. They help organize related code and resources under a single umbrella, making it easier to manage dependencies, configuration, and deployments.</p>
</li>
<li><p><strong>Apps</strong>: Are like small libraries, they should have one major purpose. Example: an "accounts" app manages everything related to user accounts, a “documents” app manages everything related to parsing and storing documents and so forth.</p>
</li>
</ul>
<p>An app generally consists of:</p>
<pre><code class="lang-bash">├── __init__.py
├── admin.py
├── apps.py
├── migrations
│   └── __init__.py
├── models.py
├── tests.py
└── views.py
</code></pre>
<ul>
<li><p><strong>admin.py</strong>: We won’t use the admin panel in this tutorial, but basically registering models here will create a CRUD interface in the Django admin panel for that model.</p>
</li>
<li><p><strong>migrations</strong>: Any logic that needs to alter your database structure.</p>
</li>
<li><p>models.py: A local mapping of database tables using Django’s powerful ORM.</p>
</li>
<li><p><strong>views.py:</strong> These are actually “controllers” when thinking in “MVC” terms. The “V,” by the way, in Django is actually the controller, and “T” is the templates, thus, Django is an “MVT” framework.</p>
</li>
</ul>
<p>Django will not automatically register “apps” you generate; you’ll need to register the app manually in order to use this app within the project. Luckily, this is fairly easy to do, just add “accounts” to your settings.py file as follows:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Update authsystem/settings.py</span>

INSTALLED_APPS = [
    <span class="hljs-string">"django.contrib.admin"</span>,
    <span class="hljs-string">"django.contrib.auth"</span>,
    <span class="hljs-string">"django.contrib.contenttypes"</span>,
    <span class="hljs-string">"django.contrib.sessions"</span>,
    <span class="hljs-string">"django.contrib.messages"</span>,
    <span class="hljs-string">"django.contrib.staticfiles"</span>,
    <span class="hljs-string">"accounts"</span>            <span class="hljs-comment"># &lt;---- the app we created.</span>
]
</code></pre>
<p>While we’re in the settings file, let’s tell Django where to find our HTML templates.</p>
<p>First, let’s create the template folder. You should be at the project root at the same level as “authsystem” and “accounts”:</p>
<pre><code class="lang-python">mkdir templates
</code></pre>
<p>Next, let’s update our settings file:</p>
<pre><code class="lang-python">TEMPLATES = [
    {
        <span class="hljs-string">"BACKEND"</span>: <span class="hljs-string">"django.template.backends.django.DjangoTemplates"</span>,
        <span class="hljs-string">"DIRS"</span>: [<span class="hljs-string">f"<span class="hljs-subst">{BASE_DIR}</span>/templates"</span>], <span class="hljs-comment"># &lt;---- Add template folder here.</span>
        <span class="hljs-string">"APP_DIRS"</span>: <span class="hljs-literal">True</span>,
        <span class="hljs-string">"OPTIONS"</span>: {
            <span class="hljs-string">"context_processors"</span>: [
                <span class="hljs-string">"django.template.context_processors.debug"</span>,
                <span class="hljs-string">"django.template.context_processors.request"</span>,
                <span class="hljs-string">"django.contrib.auth.context_processors.auth"</span>,
                <span class="hljs-string">"django.contrib.messages.context_processors.messages"</span>,
            ],
        },
    },
]
</code></pre>
<p>Django is very flexible; you can place templates just about anywhere, including inside each individual app’s folder. Since the goal of this project is to be a single webapp, we are not building a library or shareable components, thus we’ll just use one templates folder at the project root.</p>
<h2 id="heading-getting-started">Getting started</h2>
<p>Great! Now that you have a project and an accounts app. Let’s first run migrations:</p>
<pre><code class="lang-bash">python manage.py migrate
</code></pre>
<p>By default, Django will use “sqlite” as its database, which makes local development much easier, as you don’t have to configure anything in order to get a database up and running. Since Django is ORM-based, it’s usually easy to just swap out the database type in production to something like PostgreSQL.</p>
<p>You will notice a whole bunch of migrations are actually run, even though we’ve not added a single model or database table???</p>
<pre><code class="lang-bash">  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
</code></pre>
<p>These are migrations for some Django admin features, including various tables for auth, such as permissions, groups, and a user table to store registered users. Sensible defaults you’ll use in nearly every project, so it’s a welcome addition almost always.</p>
<p>To get started with our custom login system, let’s add a few routes to authsystem/urls.py as follows:</p>
<blockquote>
<p>💡 A cleaner way to add urls, is to use the “accounts” urls.py file and then import the “urlpatterns” in the main urls.py. I’m putting everything in the project’s urls.py for simplicity.</p>
</blockquote>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin
<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path

<span class="hljs-keyword">from</span> accounts.views <span class="hljs-keyword">import</span> account_login, account_logout, account_register,dashboard

urlpatterns = [
    path(<span class="hljs-string">"login/"</span>, account_login, name=<span class="hljs-string">"login"</span>),
    path(<span class="hljs-string">"register/"</span>, account_register, name=<span class="hljs-string">"register"</span>),
    path(<span class="hljs-string">"logout/"</span>, account_logout, name=<span class="hljs-string">"logout"</span>),
    path(<span class="hljs-string">"dashboard/"</span>, dashboard, name=<span class="hljs-string">"dashboard"</span>),
]
</code></pre>
<p>Your IDE or code editor might complain that these functions don’t exist, just ignore those for now. We’ll add them in a bit.</p>
<h2 id="heading-building-the-login-page">Building the login page</h2>
<p>Before we get to the actual view functions, let me just list out all the imports we're going to need here so it’s easier for you to follow along instead of constantly repeating these:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> messages
<span class="hljs-keyword">from</span> django.contrib.auth <span class="hljs-keyword">import</span> authenticate, login, logout
<span class="hljs-keyword">from</span> django.contrib.auth.decorators <span class="hljs-keyword">import</span> login_required
<span class="hljs-keyword">from</span> django.contrib.auth.models <span class="hljs-keyword">import</span> User
<span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> redirect, render

<span class="hljs-keyword">from</span> .forms <span class="hljs-keyword">import</span> RegisterForm
</code></pre>
<p>Now that we have the routes in place, let’s build the login view. In your “accounts/views.py” file, add the following:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">account_login</span>(<span class="hljs-params">request</span>):</span>
    <span class="hljs-keyword">if</span> request.user.is_authenticated:
        <span class="hljs-keyword">return</span> redirect(<span class="hljs-string">"dashboard"</span>)

    <span class="hljs-keyword">if</span> request.method == <span class="hljs-string">"POST"</span>:
        username = request.POST.get(<span class="hljs-string">"username"</span>)
        password = request.POST.get(<span class="hljs-string">"password"</span>)
        user = authenticate(request, username=username, password=password)

        <span class="hljs-keyword">if</span> user <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:
            login(request, user)
            <span class="hljs-keyword">return</span> redirect(<span class="hljs-string">"dashboard"</span>)
        <span class="hljs-keyword">else</span>:
            messages.error(request, <span class="hljs-string">"Invalid username or password."</span>)

    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">"accounts/login.html"</span>)
</code></pre>
<blockquote>
<p>ℹ️ Notice I am prefixing some of these views with “account_”. This is because we import some functions from Django named “login”, “logout” and so on, thus, doing so just prevents clashing with those function names.</p>
</blockquote>
<p>We first use <code>request.user_is_authenticated</code> to check if the user is logged in, and if they are, just redirect to the user dashboard page. Otherwise, we take 1 of 2 actions:</p>
<ul>
<li><p>If the request is a POST request, it means that the user filled in their username and password. We should therefore validate those credentials. We use the auth helper function provided by Django <code>authenticate</code> , which will take care of hashing the password and comparing it with the password stored in the DB.</p>
</li>
<li><p>If it’s a GET request, just show the login form.</p>
</li>
</ul>
<p>Let’s create a folder to store all of our auth templates:</p>
<pre><code class="lang-bash">mkdir -p templates/accounts
touch templates/accounts/login.html
touch templates/accounts/register.html
touch templates/accounts/dashboard.html
</code></pre>
<p>Next, paste this into the “login.html” template</p>
<pre><code class="lang-python">{% <span class="hljs-keyword">if</span> messages %}
&lt;div <span class="hljs-class"><span class="hljs-keyword">class</span>="<span class="hljs-title">mb</span>-4"&gt;
    {% <span class="hljs-title">for</span> <span class="hljs-title">message</span> <span class="hljs-title">in</span> <span class="hljs-title">messages</span> %}
    &lt;<span class="hljs-title">div</span>&gt;
        {{ <span class="hljs-title">message</span> }}
    &lt;/<span class="hljs-title">div</span>&gt;
    {% <span class="hljs-title">endfor</span> %}
&lt;/<span class="hljs-title">div</span>&gt;
{% <span class="hljs-title">endif</span> %}

&lt;<span class="hljs-title">form</span> <span class="hljs-title">action</span>="{% <span class="hljs-title">url</span> '<span class="hljs-title">login</span>' %}" <span class="hljs-title">method</span>="<span class="hljs-title">POST</span>"&gt;
{% <span class="hljs-title">csrf_token</span> %}

&lt;<span class="hljs-title">label</span>&gt;<span class="hljs-title">Email</span> <span class="hljs-title">Address</span>&lt;/<span class="hljs-title">label</span>&gt; &lt;<span class="hljs-title">br</span> /&gt;
&lt;<span class="hljs-title">input</span> <span class="hljs-title">type</span>="<span class="hljs-title">text</span>" <span class="hljs-title">id</span>="<span class="hljs-title">username</span>" <span class="hljs-title">name</span>="<span class="hljs-title">username</span>" <span class="hljs-title">required</span> /&gt; &lt;<span class="hljs-title">br</span> /&gt;  

&lt;<span class="hljs-title">label</span>&gt;<span class="hljs-title">Password</span>&lt;/<span class="hljs-title">label</span>&gt; &lt;<span class="hljs-title">br</span> /&gt;
&lt;<span class="hljs-title">input</span> <span class="hljs-title">type</span>="<span class="hljs-title">password</span>" <span class="hljs-title">id</span>="<span class="hljs-title">password</span>" <span class="hljs-title">name</span>="<span class="hljs-title">password</span>" <span class="hljs-title">required</span> /&gt; &lt;<span class="hljs-title">br</span> /&gt;  
&lt;<span class="hljs-title">input</span> <span class="hljs-title">type</span>="<span class="hljs-title">submit</span>" <span class="hljs-title">value</span>="<span class="hljs-title">Login</span>" /&gt;
&lt;/<span class="hljs-title">form</span>&gt;</span>
</code></pre>
<p>This is a very ugly, unstyled form, but I kept it simple so you can see what’s going on. Notice we also print messages; this is another useful Django feature. It’s basically a flash message where you can pass success/failure messages between views and between the view and the template.</p>
<p>Handy for showing error messages when the user doesn’t fill in something or fills in incorrect information.</p>
<p>Also note: we include <code>{% csrf_token %}</code> which is a security mechanism to protect your forms from “Cross-Site Request Forgery” attacks. Since this is a simple 2-field form, I didn’t create a form in “forms.py”, but usually, with Django, you would create a form class when dealing with user input.</p>
<h2 id="heading-building-the-registration-view">Building the registration view</h2>
<p>For this one, we are capturing quite a few fields, therefore, we’ll use Django forms as well to manage the form handling better. Let’s start off by creating a “forms.py” in the “accounts” folder:</p>
<pre><code class="lang-bash">touch accounts/forms.py
</code></pre>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django <span class="hljs-keyword">import</span> forms
<span class="hljs-keyword">from</span> django.contrib.auth.forms <span class="hljs-keyword">import</span> UserCreationForm
<span class="hljs-keyword">from</span> django.contrib.auth.models <span class="hljs-keyword">import</span> User
<span class="hljs-keyword">from</span> django.core.exceptions <span class="hljs-keyword">import</span> ValidationError

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RegisterForm</span>(<span class="hljs-params">UserCreationForm</span>):</span>
    email = forms.EmailField(
        required=<span class="hljs-literal">True</span>,
        widget=forms.EmailInput()
    )
    first_name = forms.CharField(
        max_length=<span class="hljs-number">50</span>,
        required=<span class="hljs-literal">True</span>,
        widget=forms.TextInput()
    )
    last_name = forms.CharField(
        max_length=<span class="hljs-number">50</span>,
        required=<span class="hljs-literal">True</span>,
        widget=forms.TextInput()
    )

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        model = User
        fields = (<span class="hljs-string">'first_name'</span>, <span class="hljs-string">'last_name'</span>, <span class="hljs-string">'email'</span>, <span class="hljs-string">'password1'</span>, <span class="hljs-string">'password2'</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">save</span>(<span class="hljs-params">self, commit=True</span>):</span>
        user = super().save(commit=<span class="hljs-literal">False</span>)
        user.username = self.cleaned_data.get(<span class="hljs-string">'email'</span>)
        user.email = self.cleaned_data.get(<span class="hljs-string">'email'</span>)
        user.first_name = self.cleaned_data.get(<span class="hljs-string">'first_name'</span>)
        user.last_name = self.cleaned_data.get(<span class="hljs-string">'last_name'</span>)

        <span class="hljs-keyword">if</span> commit:
            user.save()
        <span class="hljs-keyword">return</span> user
</code></pre>
<p>Django provides a <code>UserCreationForm</code> class that will take care of validation and hashing of the password, so we don’t have to reinvent the wheel here. We override the save method to ensure that the username is set to the user’s email address. This “username” field comes from an old school way of doing things when usernames were a thing; nowadays, most just use the email as a username.</p>
<p>Also, overriding save allows you to do more customizations, such as:</p>
<ul>
<li><p>Setting a User profile. We’re using the default Django user model, but your application may need to store additional information such as birthdate, cellphone number, address, etc… In such a case, simply create a new model “UserProfile” and use the: <code>models.OneToOneField</code> relationship to link these models. Thereafter, you can store whatever extra information is needed via the save method.</p>
</li>
<li><p>Link a user to a Team. Great for SaaS type applications.</p>
</li>
</ul>
<blockquote>
<p>⚠️ It’s worth mentioning that Django’s username field has a max-length of 150 characters. This should be generally fine for most email addresses, however, you can always alter the column length using a migration. To get an idea on how, have a look in: .venv/lib/python3.12/site-packages/django/contrib/auth/migrations</p>
</blockquote>
<p>Next, let’s build our register view, in views.py:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">account_register</span>(<span class="hljs-params">request</span>):</span>
    <span class="hljs-keyword">if</span> request.user.is_authenticated:
        <span class="hljs-keyword">return</span> redirect(<span class="hljs-string">"dashboard"</span>)

    <span class="hljs-keyword">if</span> request.method == <span class="hljs-string">"POST"</span>:
        form = RegisterForm(request.POST)
        <span class="hljs-keyword">if</span> form.is_valid():
            <span class="hljs-keyword">try</span>:
                user = form.save()
                messages.success(
                    request, <span class="hljs-string">"Account created successfully. You can now log in."</span>
                )
                <span class="hljs-keyword">return</span> redirect(<span class="hljs-string">"login"</span>)
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
                messages.error(request, <span class="hljs-string">f"Error creating account: <span class="hljs-subst">{e}</span>"</span>)
    <span class="hljs-keyword">else</span>:
        form = RegisterForm()

    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">"accounts/register.html"</span>, {<span class="hljs-string">"form"</span>: form})
</code></pre>
<p>This is very similar to the login view, except we use a form this time. Finally, you need to paste this HTML in templates/accounts/register.html</p>
<pre><code class="lang-python">&lt;div&gt;
  &lt;h2&gt;Register&lt;/h2&gt;

  {% <span class="hljs-keyword">if</span> messages %}
  &lt;div&gt;
    {% <span class="hljs-keyword">for</span> message <span class="hljs-keyword">in</span> messages %}
    &lt;div&gt;
      {{ message }}
    &lt;/div&gt;
    {% endfor %}
  &lt;/div&gt;
  {% endif %}

  &lt;form  action=<span class="hljs-string">"{% url 'register' %}"</span> method=<span class="hljs-string">"post"</span> /&gt;
    {% csrf_token %}

    &lt;div&gt;
      &lt;label <span class="hljs-keyword">for</span>=<span class="hljs-string">"{{ form.first_name.id_for_label }}"</span>&gt;First Name&lt;/label&gt;
      {{ form.first_name }}
      {% <span class="hljs-keyword">if</span> form.first_name.errors %}
        &lt;div&gt;{{ form.first_name.errors }}&lt;/div&gt;
      {% endif %}
    &lt;/div&gt;

    &lt;div&gt;
      &lt;label <span class="hljs-keyword">for</span>=<span class="hljs-string">"{{ form.last_name.id_for_label }}"</span>&gt;Last Name&lt;/label&gt;
      {{ form.last_name }}
      {% <span class="hljs-keyword">if</span> form.last_name.errors %}
        &lt;div&gt;{{ form.last_name.errors }}&lt;/div&gt;
      {% endif %}
    &lt;/div&gt;

    &lt;div&gt;
      &lt;label <span class="hljs-keyword">for</span>=<span class="hljs-string">"{{ form.email.id_for_label }}"</span>&gt;Email&lt;/label&gt;
      {{ form.email }}
      {% <span class="hljs-keyword">if</span> form.email.errors %}
        &lt;div&gt;{{ form.email.errors }}&lt;/div&gt;
      {% endif %}
    &lt;/div&gt;

    &lt;div&gt;
      &lt;label <span class="hljs-keyword">for</span>=<span class="hljs-string">"{{ form.password1.id_for_label }}"</span>&gt;Password&lt;/label&gt;
      {{ form.password1 }}
      {% <span class="hljs-keyword">if</span> form.password1.errors %}
        &lt;div&gt;{{ form.password1.errors }}&lt;/div&gt;
      {% endif %}
    &lt;/div&gt;

    &lt;div&gt;
      &lt;label <span class="hljs-keyword">for</span>=<span class="hljs-string">"{{ form.password2.id_for_label }}"</span>&gt;Confirm Password&lt;/label&gt;
      {{ form.password2 }}
      {% <span class="hljs-keyword">if</span> form.password2.errors %}
        &lt;div&gt;{{ form.password2.errors }}&lt;/div&gt;
      {% endif %}
    &lt;/div&gt;

    &lt;button type=<span class="hljs-string">"submit"</span>&gt;Register&lt;/button&gt;
  &lt;/form&gt;

  &lt;div&gt;
    &lt;p&gt;Already have an account? &lt;a href=<span class="hljs-string">"{% url 'login' %}"</span>&gt;Log <span class="hljs-keyword">in</span>&lt;/a&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<h2 id="heading-the-logout-view">The Logout view</h2>
<p>Logout is the simplest view of the 3, you simply just destroy the session and redirect to the login page:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">account_logout</span>(<span class="hljs-params">request</span>):</span>
    logout(request)
    <span class="hljs-keyword">return</span> redirect(<span class="hljs-string">"login"</span>)
</code></pre>
<h2 id="heading-restricting-access">Restricting access</h2>
<p>Now that you have a basic login system in place. We need a place for the user to be redirected to after login. Let’s create the dashboard view as follows in accounts/views.py</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">dashboard</span>(<span class="hljs-params">request</span>):</span>
    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">"accounts/dashboard.html"</span>, {})
</code></pre>
<p>And some simple HTML in templates/accounts/dashboard.html:</p>
<pre><code class="lang-python">&lt;h1&gt; Welcome {{request.user.first_name}} &lt;/h1&gt;
</code></pre>
<p>One problem, though, if you visit /dashboard without logging in, you can still access this page! To restrict this page such that only logged-in users can access it, we simply need to import and use the <code>@login_required</code> decorator as follows:</p>
<pre><code class="lang-python"><span class="hljs-meta">... </span>other imports here ....
<span class="hljs-keyword">from</span> django.contrib.auth.decorators <span class="hljs-keyword">import</span> login_required

<span class="hljs-meta">@login_required(login_url="/login")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">dashboard</span>(<span class="hljs-params">request</span>):</span>
   <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">"accounts/dashboard.html"</span>, {})
</code></pre>
<p>Now, you should get redirected to the login page. Easy right?</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The Django auth system is really powerful and robust. This article was merely a gentle introduction, there’s so much more you can do, especially with authorization, decorators, and more.</p>
<p>Hopefully, this gives you a good enough start to build out your own auth system. Even though in the modern era, you probably could get some sort of boilerplate or use a 3rdy party auth service, it’s still very handy to understand the fundamentals of how auth systems work.</p>
<p><a target="_blank" href="https://learnpython.com/?tap_a=131378-305e0d&amp;ref=ngm5zjv"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745422918547/7f8ad136-c5e1-4d2d-9f4b-f0d53d43bf91.jpeg" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Demystifying the dark arts of AI Agents 🤖]]></title><description><![CDATA[Everyone is talking about AI agents; how great they are, and how they'll replace developers.
Both hold some truth, but there's no need for concern. I am not an AI expert, but for the last few years, I have worked with almost every model that exists f...]]></description><link>https://kevincoder.co.za/demystifying-the-dark-arts-of-ai-agents</link><guid isPermaLink="true">https://kevincoder.co.za/demystifying-the-dark-arts-of-ai-agents</guid><category><![CDATA[AI Agents Explained]]></category><category><![CDATA[LLM's ]]></category><category><![CDATA[ai-agent]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Mon, 10 Feb 2025 07:03:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739170915337/95c5ba7f-c6a6-47ae-ab84-a176ff0cc460.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Everyone is talking about AI agents; how great they are, and how they'll replace developers.</p>
<p>Both hold some truth, but there's no need for concern. I am not an AI expert, but for the last few years, I have worked with almost every model that exists from GPT4o to Sonnet 3.5 to even DeepSeek R1.</p>
<p>I have built complex workflows, trained classifiers, built simple chatbots, voice AI solutions, and much more. Throughout this experience, I have learned about the limitations of AI and the difference between actual functionality versus marketing hype.</p>
<p>In this article, we'll go on a mini journey, inside the deep underbelly of the AI world and explore where the technology is today and how it'll impact you as a developer in the coming years. And yes, we'll also build an Agent from scratch in Python.</p>
<blockquote>
<p>💡 Are you new to Python? Learn Python with <a target="_blank" href="https://learnpython.com/?tap_a=131378-305e0d&amp;ref=ngm5zjv">LearnPython.com</a> courses. Fun interactive courses that are based on real-life business scenarios, meaning you’ll be writing Python code and seeing the results instantly. <em>No need to install Python or other tools on your device, everything happens through your favorite web browser</em> (Sponsored content).</p>
</blockquote>
<h2 id="heading-what-is-an-ai-agent-anyway">What is an AI agent anyway?</h2>
<p>This term is thrown around all the time as if it's something out of this world. No, it ain't! It's just a program, just functions a tad bit differently.</p>
<p>Agents are just orchestration programs that can parse text (or other media formats), analyze the contextual meaning of that text using LLMs, and then execute tasks based on whatever requirement is set out in that text.</p>
<p>To clarify, take a spec for building an exchange rate calculator. The ticket might look like:</p>
<pre><code class="lang-plaintext">Design a program that will take in a dollar amount 
and generate the relevant Rand or 
Euro amount based on the current exchange rate.
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n1uywxyjkvlch19fan6z.jpg" alt="Currency converter" /></p>
<p>As a programmer, you now will build an interface with an input box. That input box will have some validation so that it ensures the end user only enters a valid number.</p>
<p>If they try something like this: "What is $500 in Euros?"</p>
<p>The validation will kick in and alert the user with a message: "Sorry, your input is invalid. Please only enter numbers"</p>
<p>As you can see, a conventional program is more strict where types are enforced and users must input data in a structured way. Agents on the other hand don't have this limitation, you simply can chat with the agent and it'll parse the natural language into something that the computer can understand.</p>
<p>Instead of the programmer explicitly writing every single pathway a program can take, agents are more flexible in that they do have some level of decision-making, enabling them to dynamically build workflows on the fly.</p>
<p>In our currency conversion program, the programmer would have to build some sort of API integration. When the user clicks calculate, behind the scenes the numerical value entered is sent to a currency conversion API, and then the API responds with the relevant conversion.</p>
<p>The programmer needs to cater to every step in this workflow because he/she needs to take that numerical value and build some kind of JSON object. Thereafter, provide some sort of authentication, followed by making a post request, then getting back the post data, parsing it and finally displaying the final result.</p>
<p>In the case of the Agent, we don't need to explicitly write this workflow. The agent can build the JSON object all on its own and read auth credentials from your ENV because it's able to leverage the power of modern LLM coding abilities.</p>
<p><strong>To give you another practical example:</strong></p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/li9jbm98213hc10m992p.png" alt="Execution plan" /></p>
<p>In the above diagram, the agent first asks the LLM for a plan of action, next, it works through that plan step-by-step. At each step, it'll go back to the LLM for any additional information or it might execute some external task like querying an API.</p>
<p>In the case of this diagram, the user asks the agent to fetch the price of a particular Amazon listing. The Agent, in turn, queries the LLM for a list of tasks to do which might look like these:</p>
<ul>
<li><p>Spin up a Python environment.</p>
</li>
<li><p>Write a script to visit the product page.</p>
</li>
<li><p>Wait for the DOM to load and scrape the HTML.</p>
</li>
<li><p>Use a regex or querySelector to find the DOM element with the price.</p>
</li>
<li><p>Extract the price and parse it into a float.</p>
</li>
<li><p>etc...</p>
</li>
</ul>
<p>When the agent runs the Python script, it may crash. This now blocks the agent from executing other steps, so it goes back to the LLM and passes the errors to the LLM for further diagnosis. This ability to self-correct is both a blessing and a curse, more on that later...</p>
<p>The LLM then returns back the fixed code, which the agent executes and continues on with its tasks.</p>
<p>In summary; agents can generate code, query LLMs, and even talk to APIs, they are semi-autonomous programs that can function without explicit instructions for every single pathway. Still, as powerful as agents are, they still need you! The programmer.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j01r5mwj8vglqpx3dtjq.jpg" alt="Love and support" /></p>
<p>Similar to how you needed your parents to provide you with a support system, education, clothing, food, and so forth... Agents need programmers to build the "Lego blocks" of functionality and define the rules of the environment in which it runs.</p>
<p>By "Lego blocks" I mean adapters, while an LLM could generate code for an API integration, this is probably not a good idea, because a) it can hallucinate and just error out half the time. and b) there's no guarantee we can trust the source API.</p>
<p>A better approach would be to build an adapter for the API of your choosing and just provide the agent with some information on how to use that adapter (LLMs usually refer to these as tools).</p>
<h2 id="heading-agents-are-only-as-good-as-the-best-llm">Agents are only as good as the best LLM</h2>
<p>Take a set of twins, they are identical in most aspects including height and body weight, schooling, etc...</p>
<p>One drives a Toyota Corolla and the other a Ferrari. They both have similar driving skills, they swap cars from time to time and they love racing each other.</p>
<p>Regardless of which brother drives which car, the Ferrari always wins.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/djeek2gp10gs6fzxtsyv.jpg" alt="Cars Racing" /></p>
<p>Why!!?</p>
<p>I won't go into the details, but clearly, the Ferrari has a much superior engine, hence why it can beat the Corolla any day without much fuss.</p>
<p>Coming back to agents. Agents do not have any reasoning capability of their own, they rely heavily on LLMs.</p>
<p>These LLMs have no measure of quality or completeness, they just generate tokens that look correct mostly but don't apply the same level of quality checking that a human would do nor do they have the same level of understanding as a human.</p>
<p>Even worse, as you keep prompting the model, it'll keep giving you some sort of response with confidence even if that response is just, well, plain garbage.</p>
<p>Throw in an agent and you got big trouble! Because the agent's execution plan is being built by the LLM. It's just going to loop through each step and just execute. Sure, agents can detect problems and try to re-ask the LLM to fix issues but since the LLM will most likely hallucinate at some point, the agent could actually get stuck in an endless loop or most likely will not complete tasks holistically.</p>
<p>This is why agents need to be programmed in a certain way with loads of guardrails. They are far from autonomous. They need constant fine-tuning, and can only be used for very niche tasks.</p>
<h2 id="heading-programmers-rule-the-world">Programmers rule the world!</h2>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ep2k1pnng14ehw5w9ps.jpg" alt="Programmers rule the world" /></p>
<p>Does Zuck really want to promote agents to mid-level software engineers? Can you imagine the agent getting stuck in an endless loop, constantly adding and deleting lines of code for 4 hours 😂 and then submitting a PR with broken code?</p>
<p>Then another agent merges that code, and 💣 half of Facebook is down for hours! Hey! It's possible, looking at the crappy code that the so-called best LLMs generate, I won't be surprised.</p>
<p>No! This is just marketing gimmicks, yes of course a company like Facebook has some percentage of developers that perform very niche tasks.</p>
<p>These tasks can then be mostly automated with agents, and maybe one or two senior engineers who supervise and merge code, allowing Facebook to let go of some percentage of their workforce. This is highly possible and most likely is happening and will continue to happen.</p>
<p>For the rest of us, agents are going to bring on more work. Someone's going to have to build those APIs and write those guardrails to prevent agents from getting stuck in endless loops.</p>
<p>Furthermore, APIs and software are constantly changing thus one needs to constantly keep the agent code in sync with the rest of the software changes and of course make those software changes.</p>
<p>Besides, I'm pretty sure salespeople are just going to sell more stuff because developers can now automate more things, thus freeing up their time to work on more stuff.</p>
<p>Finally, no tech CTO or founder is just going to hand over their AWS keys to an agent and fire all their developers. That's insane, AI is simply not at that level and probably won't reach that level anytime soon.</p>
<blockquote>
<p>PS: You should read my earlier article to learn more about "Tunnel Syndrome" <a target="_blank" href="https://kevincoder.co.za/tunnel-syndrome-ais-biggest-weakness">here</a>.</p>
</blockquote>
<h2 id="heading-ai-agents-are-the-future-just-like-the-mobile-phone">AI agents are the future just like the mobile phone</h2>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w602ms8l2ovr5f0sm852.jpg" alt="Futuristic mobile phones" /></p>
<p>Remember the doomsayers who were saying computers are going to die off because of mobile phones?</p>
<p>We developers, are the computers in this era.</p>
<p>You'll find, while yes a certain group of computer users now opt for a tablet or a mobile phone instead of a laptop, the amount of laptop and desktop users is still pretty huge.</p>
<p>This is because mobile phones are mostly communication and consumption devices, they service a slightly different market and now developers have to make everything responsive so that we can support more devices.</p>
<p>Similar to now having to build two layouts, one for mobile and one for desktop, or adding a ton of media queries to make your website responsive. The workload for developers is just going to increase because now every company is going to want to deploy some AI feature or agent.</p>
<p>Soon, agents will be the new chatbot that everybody needs, so we'd be building agents.</p>
<p>Thus agents are here to stay and may replace some jobs but ultimately will create more jobs because we going to need more developers to build and maintain these agents.</p>
<h2 id="heading-lets-build-an-agent-from-scratch">Let's build an agent from scratch</h2>
<p>Okay enough talk, let's roll up our sleeves and actually build an Agent. Before we get started, let me just stress this is a very basic example for educational purposes.</p>
<p>In the real world, you would want to error handle a lot better and probably break this up into multiple classes.</p>
<p>The agent:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> pydantic <span class="hljs-keyword">import</span> BaseModel
<span class="hljs-keyword">from</span> openai <span class="hljs-keyword">import</span> OpenAI
<span class="hljs-keyword">import</span> time

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExecutionPlan</span>(<span class="hljs-params">BaseModel</span>):</span>
    steps: list[str]

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AIAgent</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, debug=False</span>):</span>
        self.client = OpenAI()
        self.debug = debug


    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">landing_page</span>(<span class="hljs-params">self, **kwargs</span>):</span>
        prompt = kwargs[<span class="hljs-string">'prompt'</span>]
        goal = <span class="hljs-string">"build a modern landing page"</span>
        execution_plan = self.build_execution_plan(goal, prompt)
        messages = [
            {<span class="hljs-string">"role"</span>: <span class="hljs-string">"system"</span>, <span class="hljs-string">"content"</span>: <span class="hljs-string">"You must build a landing page for this user based on their requirement."</span>},
            {<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: prompt},
        ]

        <span class="hljs-keyword">for</span> step <span class="hljs-keyword">in</span> execution_plan.steps:
            <span class="hljs-keyword">if</span> self.debug:
                print(<span class="hljs-string">f"Executing step: <span class="hljs-subst">{step}</span>."</span>)
            messages.append({<span class="hljs-string">"role"</span>: <span class="hljs-string">"system"</span>, <span class="hljs-string">"content"</span>: step})
            response = self.ask_model(messages)
            <span class="hljs-keyword">if</span> self.debug:
               print(<span class="hljs-string">f"Done: <span class="hljs-subst">{response}</span>"</span>)
            messages.append({<span class="hljs-string">"role"</span>: <span class="hljs-string">"assistant"</span>, <span class="hljs-string">"content"</span>: response})

        messages.append({<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: <span class="hljs-string">"Please respond with the final design for my landing page requirement. Please return only HTML and no extra commentary."</span>})

        landing_page = self.ask_model(messages)

        <span class="hljs-keyword">with</span> open(<span class="hljs-string">"landing.html"</span>, <span class="hljs-string">"w+"</span>) <span class="hljs-keyword">as</span> f:
            f.write(landing_page)

        <span class="hljs-keyword">return</span> <span class="hljs-string">"Done. Please view landing.html in your browser."</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">invoke_action</span>(<span class="hljs-params">self, action, **kwargs</span>):</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> hasattr(self, action):
            <span class="hljs-keyword">return</span> <span class="hljs-string">"Sorry, I have no idea what to do with this request!?"</span>

        getattr(self, action)(**kwargs)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">ask_model</span>(<span class="hljs-params">self, messages, model=<span class="hljs-string">"gpt-4o-mini"</span>, temperature=<span class="hljs-number">0.7</span>, response_format=None</span>):</span>
        i = <span class="hljs-number">0</span>
        <span class="hljs-keyword">while</span> i &lt; <span class="hljs-number">3</span>:
            i += <span class="hljs-number">1</span>
            <span class="hljs-keyword">try</span>:
                <span class="hljs-keyword">if</span> response_format <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:
                    response = self.client.beta.chat.completions.parse(
                        model=model,
                        messages=messages,
                        temperature=temperature,
                        response_format=response_format
                    )
                    <span class="hljs-keyword">return</span> response.choices[<span class="hljs-number">0</span>].message.parsed
                <span class="hljs-keyword">else</span>:
                    response = self.client.chat.completions.create(
                        model=model,
                        messages=messages,
                        temperature=temperature
                    )
                    <span class="hljs-keyword">return</span> response.choices[<span class="hljs-number">0</span>].message.content

            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> ex:
                print(ex)
                time.sleep(<span class="hljs-number">2</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">intent_router</span>(<span class="hljs-params">self, user_prompt</span>) -&gt; ExecutionPlan:</span>
        prompt = <span class="hljs-string">"""
            You must analyze the following user prompt and determine which action below best describes
            the user's request. Respond only with the action e.g. [email]

            1) [email] - the user wishes to send an email.
            2) [landing_page] - the user wishes to build a landing page.
            3) [book_calendar_date] - the user wishes to book a slot in their calendar.
        """</span>

        intent = self.ask_model([
            {<span class="hljs-string">"role"</span>: <span class="hljs-string">"system"</span>, <span class="hljs-string">"content"</span>: prompt},
            {<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: user_prompt}
        ])

        <span class="hljs-keyword">if</span> self.debug:
            print(<span class="hljs-string">f"RAW intent: <span class="hljs-subst">{intent}</span>"</span>)

        <span class="hljs-keyword">return</span> intent.replace(<span class="hljs-string">"["</span>, <span class="hljs-string">""</span>).replace(<span class="hljs-string">"]"</span>, <span class="hljs-string">""</span>).strip()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">build_execution_plan</span>(<span class="hljs-params">self, goal, prompt</span>):</span>
        system_prompt = <span class="hljs-string">f"""
            Given the current goal:'<span class="hljs-subst">{goal}</span>' and the user prompt, return a step-by-step execution plan
            for an Agent to work through, so that it can adequately and efficiently fulfill the users request.
        """</span>
        messages = [
            {<span class="hljs-string">"role"</span>: <span class="hljs-string">"system"</span>, <span class="hljs-string">"content"</span>: system_prompt},
            {<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: prompt}
        ]

        <span class="hljs-keyword">return</span> self.ask_model(messages, response_format=ExecutionPlan)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span>(<span class="hljs-params">self, prompt</span>):</span>
        intent = self.intent_router(prompt)
        <span class="hljs-keyword">if</span> self.debug:
            print(<span class="hljs-string">f"Intent for: '<span class="hljs-subst">{prompt}</span>' === <span class="hljs-subst">{intent}</span>"</span>)

        <span class="hljs-keyword">return</span> self.invoke_action(intent, prompt=prompt)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    agent = AIAgent(debug=<span class="hljs-literal">True</span>)
    result = agent.run(<span class="hljs-string">"I would like to build a landing page for my plumbing business. Please include images for pixels.com and generate copy that makes sense for industry."</span>)

    print(result)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    main()
</code></pre>
<p>Let's break this up step-by-step. The first thing you want to do in the agent process is determine what the user is actually trying to do, this is very similar to routing in a traditional web application where you map a URL route to a controller.</p>
<p>In the case of our Agent, we don't have URLs, instead the LLM parsers the text prompt and determines a keyword (i.e. the "intent") that best describes what they want to achieve:</p>
<pre><code class="lang-python">
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">intent_router</span>(<span class="hljs-params">self, user_prompt</span>) -&gt; ExecutionPlan:</span>
        prompt = <span class="hljs-string">"""
            You must analyze the following user prompt and determine which action below best describes
            the user's request. Respond only with the action e.g. [email]

            1) [email] - the user wishes to send an email.
            2) [landing_page] - the user wishes to build a landing page.
            3) [book_calendar_date] - the user wishes to book a slot in their calendar.
        """</span>

        intent = self.ask_model([
            {<span class="hljs-string">"role"</span>: <span class="hljs-string">"system"</span>, <span class="hljs-string">"content"</span>: prompt},
            {<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: user_prompt}
        ])

        <span class="hljs-keyword">if</span> self.debug:
            print(<span class="hljs-string">f"RAW intent: <span class="hljs-subst">{intent}</span>"</span>)

        <span class="hljs-keyword">return</span> intent.replace(<span class="hljs-string">"["</span>, <span class="hljs-string">""</span>).replace(<span class="hljs-string">"]"</span>, <span class="hljs-string">""</span>).strip()
</code></pre>
<p>Once we know the intent, we can easily determine what method/action to execute:</p>
<pre><code class="lang-python">    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span>(<span class="hljs-params">self, prompt</span>):</span>
        intent = self.intent_router(prompt)
        <span class="hljs-keyword">if</span> self.debug:
            print(<span class="hljs-string">f"Intent for: '<span class="hljs-subst">{prompt}</span>' === <span class="hljs-subst">{intent}</span>"</span>)

        <span class="hljs-keyword">return</span> self.invoke_action(intent, prompt=prompt)
</code></pre>
<p>In our "run" method which is the entry point that starts up the agent, we use the intent to dynamically execute a method on the "AIAgent" class.</p>
<p>Taking a peek inside "invoke_action", you will see that it's basically just checking if the class has a method that's named exactly the same as the relevant intent.</p>
<pre><code class="lang-python">    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">invoke_action</span>(<span class="hljs-params">self, action, **kwargs</span>):</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> hasattr(self, action):
            <span class="hljs-keyword">return</span> <span class="hljs-string">"Sorry,...."</span>

        getattr(self, action)(**kwargs)
</code></pre>
<p>If the method exists, we just execute it and we pass any extra arguments in as well via "kwargs".</p>
<p>In my example I am asking the agent to build a landing page, thus the intent is "[landing_page]" and if you look inside the "AIAgent" class you will notice a method with this same name:</p>
<pre><code class="lang-python">    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">landing_page</span>(<span class="hljs-params">self, **kwargs</span>):</span>
        prompt = kwargs[<span class="hljs-string">'prompt'</span>]
        goal = <span class="hljs-string">"build a modern landing page"</span>
        execution_plan = self.build_execution_plan(goal, prompt)
        messages = [
            {<span class="hljs-string">"role"</span>: <span class="hljs-string">"system"</span>, <span class="hljs-string">"content"</span>: <span class="hljs-string">"You must build a landing page for this user based on their requirement."</span>},
            {<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: prompt},
        ]

        <span class="hljs-keyword">for</span> step <span class="hljs-keyword">in</span> execution_plan.steps:
            <span class="hljs-keyword">if</span> self.debug:
                print(<span class="hljs-string">f"Executing step: <span class="hljs-subst">{step}</span>."</span>)
            messages.append({<span class="hljs-string">"role"</span>: <span class="hljs-string">"system"</span>, <span class="hljs-string">"content"</span>: step})
            response = self.ask_model(messages)
            <span class="hljs-keyword">if</span> self.debug:
               print(<span class="hljs-string">f"Done: <span class="hljs-subst">{response}</span>"</span>)
            messages.append({<span class="hljs-string">"role"</span>: <span class="hljs-string">"assistant"</span>, <span class="hljs-string">"content"</span>: response})

        messages.append({<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: <span class="hljs-string">"Please respond with the final design for my landing page requirement. Please return only HTML and no extra commentary."</span>})

        landing_page = self.ask_model(messages)

        <span class="hljs-keyword">with</span> open(<span class="hljs-string">"landing.html"</span>, <span class="hljs-string">"w+"</span>) <span class="hljs-keyword">as</span> f:
            f.write(landing_page)

        <span class="hljs-keyword">return</span> <span class="hljs-string">"Done. Please view landing.html in your browser."</span>
</code></pre>
<p>The method starts off by defining a "goal", this is more descriptive than the single keyword intent and will be used to prompt the model again to get a detailed step-by-step workflow.</p>
<pre><code class="lang-python">execution_plan = self.build_execution_plan(goal, prompt)
</code></pre>
<p>Looking inside the execution plan method, we use structured outputs so that we can tell the LLM to return a list of steps in the format of "ExecutionPlan" which is just a plain pydantic model:</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExecutionPlan</span>(<span class="hljs-params">BaseModel</span>):</span>
    steps: list[str]
...
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">build_execution_plan</span>(<span class="hljs-params">self, goal, prompt</span>):</span>
        system_prompt = <span class="hljs-string">f"""
            Given the current goal:'<span class="hljs-subst">{goal}</span>' and the user prompt, return a step-by-step execution plan
            for an Agent to work through, so that it can adequately and efficiently fulfill the users request.
        """</span>
        messages = [
            {<span class="hljs-string">"role"</span>: <span class="hljs-string">"system"</span>, <span class="hljs-string">"content"</span>: system_prompt},
            {<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: prompt}
        ]

        <span class="hljs-keyword">return</span> self.ask_model(messages, response_format=ExecutionPlan)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run</span>(<span class="hljs-params">self, prompt</span>):</span>
        intent = self.intent_router(prompt)
        <span class="hljs-keyword">if</span> self.debug:
            print(<span class="hljs-string">f"Intent for: '<span class="hljs-subst">{prompt}</span>' === <span class="hljs-subst">{intent}</span>"</span>)

        <span class="hljs-keyword">return</span> self.invoke_action(intent, prompt=prompt)
</code></pre>
<p>Finally, we just loop through all the steps and prompt the model one by one until the final landing page is built.</p>
<h2 id="heading-conclusion-ai-agents-are-here-to-stay">Conclusion: AI Agents are here to stay</h2>
<p>Don't be fooled by these big-name CEOs and tech entrepreneurs who just spread propaganda so that they can drive more investment into their companies. They use AI agents as a means to stir up market interest and get non-technical people all excited.</p>
<p>This is an age-old marketing trick! To these people, they are obsessed with the bottom line. A sad side-effect of capitalism! Greed for growth at any cost is the endgame.</p>
<p>The reality is that the technology while useful, is just over-hyped. It's been a good few years since the first version of ChatGPT was released and yet AI products just fall into two categories: AI Wrappers or value adds.</p>
<p>At the end of the day, Agents, AI, and all these tools are just that, tools. They are here to stay and plenty useful, but do not have reasoning and thinking capabilities on the same level as that of a human being, and probably never will!</p>
]]></content:encoded></item><item><title><![CDATA[Tunnel Syndrome: AI's biggest weakness?]]></title><description><![CDATA[As a developer, are you worried that AI is going to replace you? This is a valid concern, but let me put your mind at ease, chances are this is highly unlikely in 2025, or at any point in the near future.
Why am I so confident?!
AI Tunnel Syndrome is...]]></description><link>https://kevincoder.co.za/tunnel-syndrome-ais-biggest-weakness</link><guid isPermaLink="true">https://kevincoder.co.za/tunnel-syndrome-ais-biggest-weakness</guid><category><![CDATA[AI]]></category><category><![CDATA[LLM's ]]></category><category><![CDATA[discussion]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Mon, 13 Jan 2025 09:56:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736762347352/1a0fe1e7-e7a6-4d6a-ab17-17dd18426379.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As a developer, are you worried that AI is going to replace you? This is a valid concern, but let me put your mind at ease, chances are this is highly unlikely in 2025, or at any point in the near future.</p>
<p>Why am I so confident?!</p>
<p>AI Tunnel Syndrome is one of AI's biggest weaknesses and it's not going to get better any time soon, regardless of "o3" or whatever model comes out next.</p>
<h2 id="heading-preamble-the-consciousness">Preamble: The Consciousness</h2>
<p>Okay, so "AI Tunnel Syndrome" is a term I just coined while thinking about AI stuff, but bear with me for now, we'll get back to what I mean in a minute.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1rthemtbomgo2ozhtyul.jpg" alt="AI Awakening" /></p>
<p>The thing is, to be human, you are more than just a library of knowledge. You are filled with memories, emotions, and experiences. You are a being that can adapt to just about any environment and thrive even in the most impossible circumstances.</p>
<p>You have a worldview and consciousness.</p>
<p>AI, on the other hand, is frozen in time. You see, there is a reason why we call them "models". Each new iteration has a version number. Do you have a version number? No, because your knowledge and experience grow every second of every day.</p>
<p>Models have no consciousness, they are just algorithms that can parse, scan, and find patterns in a large corpus of information.</p>
<p>The version indicates that the model's cutoff knowledge base has just moved further down the line with more data and various improvements on both the dataset and the algorithm.</p>
<p>The LLM stops learning at this version cutoff; every conversation thereafter is just an interaction, and there's no impact on the model's persistent knowledge base (although you can supplement the AI’s knowledge base with RAG and tool calls).</p>
<h2 id="heading-the-world-view">The World View</h2>
<p>As human beings, we understand not only the knowledge we accumulate over time but also how that knowledge fits into the broader context of the world around us.</p>
<p>While we are not soothsayers(well, most of us anyway), we do have the ability to see into the future to a certain extent and make decisions based on all three temporal dimensions: the past, the present, and the future.</p>
<p>To illustrate what I mean, here's an example:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wzc749w4nj3l7smv8mg2.jpg" alt="AI eating ice cream" /></p>
<p>Imagine it's a bright sunny day, incredibly hot, and you are sweating like crazy. At this moment, you have all the knowledge of everything you've ever learned. You have memories and past experiences too. Sure, you might forget stuff, but overall, you remember most things.</p>
<p>Your mind floats to thoughts of a nice cold ice cream, and you remember how refreshing and cooling it feels to have one from past experiences.</p>
<p>This now drives your decision to buy an ice cream! But wait! You bought an ice cream from the guy down the street 3 months ago, and it wasn't all that great.</p>
<p>You also know, it's fast approaching lunchtime, and at 12 PM, the city center gets really busy. On such a hot and sweaty day, the last thing you want to do is get stuck in a crowd of people.</p>
<p>So eventually, you decide to take a quiet side street instead, where you can buy your ice cream away from the city's hustle and bustle.</p>
<p>While enjoying that ice cream, you look to the city center and smile 😊, it's lunchtime and everyone is flocking there! Phew, dodged a bullet, didn't you?</p>
<p>Wait, what does this have to do with AI!?</p>
<p>Patience 🙏 eager-beaver, I'm getting there 🙃</p>
<p>In the above example, your brain is using senses, memories, past experiences, and emotions altogether, all at once to build this complex decision tree in record time, whilst still consuming very little energy.</p>
<p>Furthermore, you are even using that information to predict the future and other possible edge cases and consequences.</p>
<p>Models on the other hand, have no worldview and are narrow-focused; they take your prompt and do some math on it to determine patterns and complex relationships between words.</p>
<p>Thereafter, based on the data they've been trained on, they generate a response that's most likely going to satisfy the prompt. Depending on the quality of your prompt, this will impact the calculation performance and thus the final result.</p>
<p>This is perfectly fine for a simple "lookup" question like: "Explain the theorem of Pythagoras". The question has one clear goal, thus the model can easily determine the meaning and generate an accurate response.</p>
<p>Give it a prompt like "Build me a landing page for an electrician, the colors are red and black. For the content, use placeholder images and text. I need 5 pages: About, services, contact, gallery..."</p>
<p>If you ask a human to do this task, even with this poor specification, they probably will come back with a complete website. AI on the other hand, even powerful models like Sonnet will build you something, but it's never going to be polished and will most likely miss obvious features.</p>
<h2 id="heading-what-is-ai-tunnel-syndrome">What is AI tunnel syndrome?</h2>
<p>With your worldview in the case of the ice cream example, you were able to piece together a complete picture of the environment, the taste, the texture, the horror of overcrowding, and the pleasant memory of eating the ice cream.</p>
<p>Coupled with the fact that you are conscious, every event in your life no matter how small or big it is, contributes to your worldview, your memories, and your learning.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yf752gt6lcznjla6jj5c.png" alt="tunnel vision" /></p>
<p>To drive home the true meaning of "AI Tunnel Syndrome", let's look at another practical example. In the real world just writing code is not enough; you need to sit in meetings with non-technical people; they often don't understand their actual requirements, budget constraints, and various factors that could come into play when building out their application requirements.</p>
<p>Can you imagine a request: "We need something like Twitter, so we can all communicate internally with each other, share files, and even video call. Please, can you build this for us?"</p>
<p>🤖: Sure, has access to the codebase, goes off, generates some Next.js code. Mostly works, but there is no auth integration, so just about anyone can initiate a chat. Also, there's no mime validation on files, so one can upload just about anything, and finally, the UI has a logo of the old Twitter bird icon?!</p>
<p>👨‍💻: I ain't got 3 months to build that, we'll just set up Slack.</p>
<p>The human immediately identifies information beyond the prompt, the context, and the consequences of executing the task concerning time and budget constraints.</p>
<p>The AI, on the other hand, finds the relevant information and starts building but doesn't cater to many edge cases or even think about just using a pre-built solution like Slack.</p>
<p>💡The lack of this complete "big picture" view (which is second nature to human beings) is what I call "AI Tunnel Syndrome".</p>
<h2 id="heading-enter-the-agent">Enter the Agent</h2>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/azbh0yn94lh9qkqg7rfu.png" alt="AI Agents" /></p>
<p>Agents are an interesting next evolution of AI models. Think of an agent as an orchestra conductor. In an orchestra, you have several different instruments: violins, clarinets, trumpets, and so forth. Each instrument produces a different sound.</p>
<p>Can you imagine if each musician played from a different music sheet, the result would be chaotic! Instead of harmony, you'd hear a mix of competing melodies, rhythms, and keys that don't complement each other. Each of these instruments alone sounds great, but as soon as you put them together, you need some sort of structure.</p>
<p>The conductor ensures that they all stay in sync and play harmoniously, producing beautiful music.</p>
<p>Bringing it back to Agents. LLMs simply take input (whether it’s text, voice, images, etc) and respond with some output. They can look at any context data you may supply the prompt, like a PDF or previous chat messages, but beyond that, they cannot access information outside of the model.</p>
<p>To solve this problem, AI companies developed “tools”. With tools, you can connect the LLM to an external service like an API, search engine, emails, calendars, or any other data source. A tool is simply a function in your code that the LLM can trigger, it can pass parameters to the function, and the function should respond with some kind of output that the LLM uses to finish its response back to the user.</p>
<p>This is when we start getting into Agent territory, a tool on its own is still an input/output mechanism. You can most certainly run any kind of code in your function, but tools generally do not know of each other and can’t communicate with each other. So they are essentially like individual musical instruments. On their own, each tool is plenty useful, but together they have no way of sharing information or communicating.</p>
<p>Agents are programs that can orchestrate tools, LLMs, and even elements outside of the LLM’s scope, like creating files in a file system.</p>
<p>When you prompt an agent, it drafts a step-by-step plan, a sort of checklist that it must follow to achieve whatever outcome you asked it to achieve. At each of these checklist steps, the agent will ask the LLM for whatever information it needs, then proceed to call tools or other external functions until it can check that task off its checklist.</p>
<p>The agent will then usually verify each step, and fix any issues that occur along the way until it checks off all items in the checklist.</p>
<p>Agents are therefore a whole new level of automation, and you can use them to perform all kinds of tasks like building landing pages, scaffolding a coding project, web scraping, and building reports, etc.</p>
<p>While agents are powerful, they are still limited by the intelligence of the LLMs; thus, they still cannot think at the same level as a human and will make loads of mistakes.</p>
<p>I covered agents in more detail <a target="_blank" href="https://kevincoder.co.za/demystifying-the-dark-arts-of-ai-agents">here</a> if you want to learn more.</p>
]]></content:encoded></item><item><title><![CDATA[PostgreSQL for a rusty MySQL brain]]></title><description><![CDATA[I come from a PHP background, hence why I prefer MySQL but when you dealing with Django and other Python projects, PostgreSQL seems to be the most popular and preferred DB server.
I have used PostgreSQL now and then throughout my career, it just feel...]]></description><link>https://kevincoder.co.za/postgresql-for-a-rusty-mysql-brain</link><guid isPermaLink="true">https://kevincoder.co.za/postgresql-for-a-rusty-mysql-brain</guid><category><![CDATA[postgresql cheat sheet]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Postgres extensions]]></category><category><![CDATA[pgvector]]></category><category><![CDATA[timescaledb]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Thu, 12 Dec 2024 15:51:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1734018572336/4e3a58a4-61ca-4fbb-aa81-3a20699f56f1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I come from a PHP background, hence why I prefer MySQL but when you dealing with Django and other Python projects, PostgreSQL seems to be the most popular and preferred DB server.</p>
<p>I have used PostgreSQL now and then throughout my career, it just feels weird to me with all the “\” commands. MySQL syntax is more English-like: “show tables; describe table …” so it’s easy to remember.</p>
<p>Nonetheless, PostgreSQL is a powerful open-source DB server that’s more than capable for any project size, this guide is more of a “cheat sheet” for me (and hopefully you too!).</p>
<blockquote>
<p>⚠️ This article assumes you are using Linux, more specifically Ubuntu. Debian might also work, as well as other Linux server types with a few minor tweaks.</p>
</blockquote>
<h2 id="heading-translating-mysql-commands-into-postgresql">Translating MySQL commands into PostgreSQL</h2>
<p><strong>Connecting to a database:</strong></p>
<pre><code class="lang-sql">\c db_name; <span class="hljs-comment"># Postgres</span>
<span class="hljs-keyword">use</span> db_name; <span class="hljs-comment"># MySQL</span>
</code></pre>
<p><strong>List databases:</strong></p>
<pre><code class="lang-sql">\list <span class="hljs-comment"># Postgres</span>
<span class="hljs-keyword">show</span> <span class="hljs-keyword">databases</span>; <span class="hljs-comment"># MySQL</span>
</code></pre>
<p><strong>Show tables:</strong></p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> tablename <span class="hljs-keyword">FROM</span> pg_catalog.pg_tables;  <span class="hljs-comment"># Postgres</span>
<span class="hljs-keyword">SHOW</span> <span class="hljs-keyword">tables</span>; <span class="hljs-comment"># MySQL</span>
</code></pre>
<p>It’s worth noting that Postgres is going to show you all tables on the server (you can use a where clause though), whereas MySQL will just show you only tables in the current database.</p>
<p>Also, not to be a PostgreSQL hater, sorry but PostgreSQL will show you a colon and you need to keep pressing enter, MySQL on the other hand just lists everything.</p>
<p><strong>Describe a table:</strong></p>
<pre><code class="lang-sql">\d tablename; <span class="hljs-comment"># Postgres</span>
<span class="hljs-keyword">describe</span> tablename; <span class="hljs-comment"># MySQL</span>
</code></pre>
<p><strong>Using the CLI:</strong></p>
<pre><code class="lang-sql">mysql -usomeuser -p1234 somedb
psql -U postgres -W somedb
</code></pre>
<p>With PostgreSQL, there is a user called “postgres”, so you can also run the following to access the DB with no password prompt:</p>
<pre><code class="lang-sql">sudo su - postgres
psql somedb

<span class="hljs-comment"># OR</span>
sudo -u postgres psql somedb
</code></pre>
<h2 id="heading-where-art-thou-postgresql">Where art thou PostgreSQL?</h2>
<p>The configs are located in /etc/ on Linux which is very similar to MySQL. Seems there are 2 main config files to pay attention to:</p>
<p><strong>Client authentication settings</strong></p>
<p>This config will control who has access to your server</p>
<p><em>/etc/postgresql/16/main/pg_hba.conf</em></p>
<pre><code class="lang-sql"><span class="hljs-comment"># "local" is for Unix domain socket connections only</span>
local   all             all                                     peer
host    all             all             192.168.0.1/32          scram-sha-256
host    all             all             10.0.0.1/32             scram-sha-256
</code></pre>
<p>PostgreSQL works a tad bit differently than MySQL, in MySQL, you just create a user with an IP address and grant them access to whatever database you want e.g.:</p>
<pre><code class="lang-sql"><span class="hljs-comment"># MySQL grant remote user access</span>
<span class="hljs-keyword">create</span> <span class="hljs-keyword">user</span> me@<span class="hljs-number">192.168</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span> indentified <span class="hljs-keyword">by</span> <span class="hljs-string">'1234'</span>;
</code></pre>
<p>In PostgreSQL on the other hand, you must create a user without the IP and then just grant them access by listing their IP in the config file mentioned above.</p>
<blockquote>
<p>⚠️ For both DB’s you still need to bind the server on 0.0.0.0 or some network IP otherwise they won’t allow external connections.</p>
</blockquote>
<p><strong>Main PostgreSQL config:</strong></p>
<p><em>/etc/postgresql/16/main/postgresql.conf</em></p>
<p>This is the main config file where you can set the bind address, data-directory path, and various other settings such as memory settings, worker configs, row security, logging, etc…</p>
<p>While you can change this in the main config, the default PostgreSQL data directory is located in:</p>
<p><em>/var/lib/postgresql/</em></p>
<h2 id="heading-backups">Backups</h2>
<p>On MySQL, Percona Toolkit is my go-to toolset. You can backup, archive data and do loads more complex management tasks while the DB server is online and serving requests.</p>
<p>Seems like there is a version for PostgreSQL as well, I briefly read through but haven’t used PostgreSQL that comprehensively on a large DB to warrant the use of Percona.</p>
<p>For simple backups, you can simply run (which is similar to mysqldump):</p>
<pre><code class="lang-sql">pg_dump -U postgres -d dbname -f backup.sql
</code></pre>
<p>This will create a SQL dump file. I tend to avoid SQL dump files like the plague, well at least using MySQL anyway because when you restore on a new server, there could be subtle differences in the versions which can cause the restore to fail.</p>
<p>Furthermore, each statement has to be evaluated and run so if you have a 500GB db for example, this will be slow even if you have fast disks.</p>
<p>A better option is to use a filesystem backup, these are much faster and less prone to break when restoring since you copying an entire PostgreSQL instance’s directory:</p>
<pre><code class="lang-sql">pg_basebackup -D <span class="hljs-keyword">backup</span> -Fp
</code></pre>
<ul>
<li><p>-D: the directory to store the backup in.</p>
</li>
<li><p>-F: Format, can be plain or “tar”. Usually “p” for plain is faster but takes up more disk space. Since the “t” for tar option creates a tarball archive, this can be slow depending on how good your disk IO is.</p>
</li>
</ul>
<p>You can then use the following to restore:</p>
<pre><code class="lang-sql">rsync -av <span class="hljs-comment">--progress backup/ /var/lib/postgresql/16/main/</span>
chmod 700 /var/lib/postgresql/16/main
chown -R postgres:postgres /var/lib/postgresql/16/main
</code></pre>
<h2 id="heading-timescale">Timescale</h2>
<p>The great thing about PostgreSQL is it allows you to customize the DB via extensions, this opens up a world of possibilities, allowing the community to extend the core feature set in whatever way needed.</p>
<p>One such impressive extension is called Timescale, as the name suggests, Timescale allows you to store and query large time-series datasets. When querying large tables in an RDBMS (hundreds of millions of rows), you will eventually notice queries becoming incredibly slow, even with indexes and optimizations. You then would need to resort to more expensive hardware, sharding, and replication to get around these issues.</p>
<p>Timescale on the other hand can query and write to such tables incredibly fast, and you probably won’t even need to shard or set up replication (and be able to run on cheaper hardware too).</p>
<p>To install Timescale on Ubuntu for an existing PostgreSQL server, use the following script (or view the full step-by-step instructions on Timescale’s website <a target="_blank" href="https://docs.timescale.com/self-hosted/latest/install/installation-linux/">here</a>):</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"deb https://packagecloud.io/timescale/timescaledb/ubuntu/ <span class="hljs-subst">$(lsb_release -c -s)</span> main"</span> | sudo tee /etc/apt/sources.list.d/timescaledb.list
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg

sudo apt update
sudo apt install timescaledb-2-postgresql-17 postgresql-client-17
sudo timescaledb-tune


sudo systemctl restart postgresql


sudo -u postgres psql
CREATE EXTENSION IF NOT EXISTS timescaledb;
</code></pre>
<p>Once you have Timescale installed, you can setup a “hypertable” as follows:</p>
<pre><code class="lang-bash">CREATE TABLE meter_readings (
   time        TIMESTAMPTZ       NOT NULL,
   warehouse   TEXT              NOT NULL,
   unit_code   TEXT              NOT NULL,
   reading DOUBLE PRECISION  NULL,
);


SELECT create_hypertable(<span class="hljs-string">'meter_readings'</span>, by_range(<span class="hljs-string">'time'</span>))
</code></pre>
<p>So basically, 95% percent is the same except for the “create_hypertable” function which sets up all the Timescale optimizations on your table.</p>
<h2 id="heading-pgvector">PGVector</h2>
<p>Another great extension is “PGVector”; if you are building a RAG-powered chatbot or need to perform similarity searches, you will need to vectorize your data and store those vector embeddings somewhere. Traditional databases like PostgreSQL and MySQL, are not designed for vector embeddings.</p>
<p>You will need to reach for a DB like Qdrant, which is built for that kind of storage.</p>
<p>Except! One problem, your project runs on PostgreSQL and it’s such a pain to introduce a new service into your stack for a dataset that’s not going to exceed maybe 10-20k worth of documents.</p>
<p>In such a case PGVector, while not as fast as some of these vector-optimized storage solutions, is more than sufficient for your use case.</p>
<p>Since it’s just a PostgreSQL extension, installing is a breeze:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> --branch v0.8.0 https://github.com/pgvector/pgvector.git
<span class="hljs-built_in">cd</span> pgvector
make
make install
</code></pre>
<p>Next in the PSQL shell (create extension only needs to be run once):</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> EXTENSION vector;

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> documents (
   <span class="hljs-keyword">id</span> bigserial PRIMARY <span class="hljs-keyword">KEY</span>,
   <span class="hljs-keyword">name</span> <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
   embedding vector(<span class="hljs-number">1536</span>)
);
</code></pre>
<p>I used “1536” because that refers to the dimensions of the vector embeddings and the size of the small text embedding model from OpenAI.</p>
<p>You can then insert and query data as follows:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> documents (embedding) <span class="hljs-keyword">VALUES</span> (<span class="hljs-string">'[1,2,3...]'</span>), (<span class="hljs-string">'[4,5,6....]'</span>);

<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> items <span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> embedding &lt;=&gt; <span class="hljs-string">'[3,1,2...]'</span> <span class="hljs-keyword">LIMIT</span> <span class="hljs-number">5</span>;
</code></pre>
<p>The above “select” will perform a cosine similarity search and return the top 5 results. Bit of a schlep to convert those floating point numbers into the PostgreSQL format, but still far easier than maintaining another vector DB store.</p>
]]></content:encoded></item><item><title><![CDATA[Claude Sonnet is remarkably good]]></title><description><![CDATA[I know there is a lot of hype around AI, but when you look past the hype and just focus on real-life practical use cases, AI is pretty cool! As usual, I am excited and embracing all the change. Being in the industry for over 15 years, I have seen man...]]></description><link>https://kevincoder.co.za/claude-sonnet-is-remarkably-good</link><guid isPermaLink="true">https://kevincoder.co.za/claude-sonnet-is-remarkably-good</guid><category><![CDATA[claude.ai]]></category><category><![CDATA[AI]]></category><category><![CDATA[Web Design]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Sat, 07 Dec 2024 14:20:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733580306953/30cadc11-7359-4999-a25e-22cc24bbabd2.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I know there is a lot of hype around AI, but when you look past the hype and just focus on real-life practical use cases, AI is pretty cool! As usual, I am excited and embracing all the change. Being in the industry for over 15 years, I have seen many hype circles come and go and have always enjoyed riding the wave.</p>
<p>Naturally, I have been test-driving Claude Sonnet for a while, and overall I must say the model is really impressive, especially for coding tasks and even creative tasks like web design.</p>
<p>Let’s see why I think Sonnet is one of the best, if not the best of the bigger models out in the wild right now.</p>
<h2 id="heading-claude-sonnet-is-good-at-long-context-prompts">Claude Sonnet is good at long-context prompts</h2>
<p>With Sonnet, you get a decent 200k context window. This basically means you do not need to fine-tune the model. In the case of HTML templates, if you ask the model to generate you one, it’s going to be a hit-and-miss, sometimes you’ll get great-looking templates, and other times just plain junk!</p>
<p>To get around this you can use RAG, but for this kind of task when generating HTML templates, a full RAG solution might be overkill, instead you can just “shove” HTML as examples in your prompt similar to the below example:</p>
<pre><code class="lang-bash">&lt;example&gt;
...
 &lt;nav class=<span class="hljs-string">"navbar"</span>&gt;
        &lt;ul&gt;
            &lt;li&gt;&lt;a href=<span class="hljs-string">"#home"</span>&gt;Home&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=<span class="hljs-string">"#about"</span>&gt;About&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=<span class="hljs-string">"#services"</span>&gt;Services&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=<span class="hljs-string">"#contact"</span>&gt;Contact&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/nav&gt;
...
&lt;/example&gt;
</code></pre>
<p>Shoving HTML or just about any example like this will help you tame the model and get it to scope its responses to just your context.</p>
<p>Sonnet also supports prompt caching, therefore even though you sending this large chunk of HTML every time you prompt the model, the model does not always need to parse and analyze this every time, instead,d it’ll automatically cache this data which makes the whole process much more efficient and save you money at the same time since cached tokens are generally cheaper than regular tokens.</p>
<p>Furthermore, if you use their batch API instead of the real-time API, you save 50%. That’s remarkably cheap for a powerhouse model like Sonnet.</p>
<h2 id="heading-claude-sonnet-is-really-good-at-backend-code">Claude Sonnet is really good at backend code</h2>
<p>I am a bit of an old-schooler and don’t use too many AI coding assistants, I feel they generate bad code, and I love fine-grade control of the code I write.</p>
<p>Nonetheless, in today’s fast-paced dev world, we have customers to service. Claude Sonnet is proving to be that excellent side-kick junior developer I’ve always needed, not that I am advocating for replacing developers with AI! No Never!</p>
<p>Instead, AI makes developers more productive so that we can actually focus on the hardcore stuff, the engineering and system design stuff. This mundane stuff that took up time before, like fixing some CSS issue, frankly for a backend dev is a waste of time.</p>
<p>Even in the engineering process, there are often times you would “float” (for lack of a better word) between concepts, which means now you have to scaffold 2-3 variations which could take a couple of hours or days.</p>
<p>With Sonnet, you can just prompt the model to scaffold pseudo-code versions and then iterate from there. It’s boilerplates on steroids essentially!</p>
<p>Previously with ChatGPT, you could get the model to do something similar, however, often it was a waste of time.</p>
<p>The code generated was either heavily hallucinated or outdated, you just couldn’t get a consistent generation from those models and wasted too much time fine-tuning the prompt.</p>
<p>Sonnet seems to have a decent level of accuracy, it’s not always perfect but often it gets you there much closer than any of the OpenAI models ever could.</p>
<h2 id="heading-claude-sonnet-is-not-bad-at-web-design">Claude Sonnet is not bad at web design</h2>
<p>I can do UI development just fine, I know my way around pure CSS, Bootstrap, and even Tailwind, yet building frontends is not really my cup of tea, I am backend’er and prefer it that way.</p>
<p>Still, from time to time I have no choice but to throw together a simple landing page or tweak some components here and there.</p>
<p>Previously, well I had two options write the code myself or just buy a template. Now with Sonnet, and a really well-structured prompt, I can get at least 80% there in just a few minutes, and then fine-tune the design manually as and where needed.</p>
<h2 id="heading-a-complete-landing-page-from-a-simple-prompt">A complete landing page from a simple prompt</h2>
<p>It’s amazing how much time you could save with Sonnet; I have done a few experiments with generating layouts and UI components and for the most part, Sonnet does a pretty good job.</p>
<p>When the generated code isn’t up to standard, you can step-by-step ask Sonnet to tweak certain aspects, similar to sitting with a web designer and giving them feedback.</p>
<p>Here’s an example of a full layout designed 100% by Sonnet:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731688381581/eddbd199-b0e3-4d9a-9a15-af9981efbf3a.png" alt class="image--center mx-auto" /></p>
<p>Not bad for a business profile type website right? Sure there are some rough edges, and some styling fine-tuning that one can do to make it a little bit more “pretty” but hey this was generated in less than a minute, and it’ll take me 10 minutes just to go in and tweak versus waiting for a designer or scrolling through marketplaces looking for a paid template.</p>
<p>The prompt (I totally made up the plumbing company name, although Sonnet seems to have stolen real content from a real plumbing website, <a target="_blank" href="https://danvilleplumbers.com/">https://danvilleplumbers.com/</a> in this case, not sponsored but please support them if you are in the area 😊):</p>
<pre><code class="lang-plaintext">Design me a modern looking landing page to 
market my plumbing services. You must have an 
About me section, my services section, 
contact me section including a nice big looking footter. 
My colors are: rgb(220, 38, 38), black and white. 
Use a parallax effect and images from unsplash.com. 
Generate the content for each section as well. 
My company name is Danville Plumbers. 
Make sure you have a lot of white space and use pictures 
that are relevant to plumbing. 
Only return HTML, and no commentary or extra text 
so that it's easy for me to copy and paste.
</code></pre>
<p>And the generated HTML:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Danville Plumbers - Professional Plumbing Services<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
        * {
            <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
            <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
            <span class="hljs-attribute">box-sizing</span>: border-box;
            <span class="hljs-attribute">font-family</span>: <span class="hljs-string">'Arial'</span>, sans-serif;
        }

        <span class="hljs-selector-tag">html</span> {
            <span class="hljs-attribute">scroll-behavior</span>: smooth;
        }

        <span class="hljs-selector-class">.navbar</span> {
            <span class="hljs-attribute">position</span>: fixed;
            <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
            <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.9</span>);
            <span class="hljs-attribute">padding</span>: <span class="hljs-number">20px</span>;
            <span class="hljs-attribute">z-index</span>: <span class="hljs-number">1000</span>;
        }

        <span class="hljs-selector-class">.navbar</span> <span class="hljs-selector-tag">ul</span> {
            <span class="hljs-attribute">list-style</span>: none;
            <span class="hljs-attribute">display</span>: flex;
            <span class="hljs-attribute">justify-content</span>: flex-end;
            <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">50px</span>;
        }

        <span class="hljs-selector-class">.navbar</span> <span class="hljs-selector-tag">li</span> {
            <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> <span class="hljs-number">20px</span>;
        }

        <span class="hljs-selector-class">.navbar</span> <span class="hljs-selector-tag">a</span> {
            <span class="hljs-attribute">color</span>: white;
            <span class="hljs-attribute">text-decoration</span>: none;
            <span class="hljs-attribute">font-weight</span>: bold;
            <span class="hljs-attribute">transition</span>: color <span class="hljs-number">0.3s</span>;
        }

        <span class="hljs-selector-class">.navbar</span> <span class="hljs-selector-tag">a</span><span class="hljs-selector-pseudo">:hover</span> {
            <span class="hljs-attribute">color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">220</span>, <span class="hljs-number">38</span>, <span class="hljs-number">38</span>);
        }

        <span class="hljs-selector-class">.hero</span> {
            <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;
            <span class="hljs-attribute">background</span>: <span class="hljs-built_in">linear-gradient</span>(rgba(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.7</span>), <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.7</span>)),
                        <span class="hljs-built_in">url</span>(<span class="hljs-string">'https://images.unsplash.com/photo-1584622650111-993a426fbf0a?ixlib=rb-4.0.3'</span>);
            <span class="hljs-attribute">background-attachment</span>: fixed;
            <span class="hljs-attribute">background-size</span>: cover;
            <span class="hljs-attribute">display</span>: flex;
            <span class="hljs-attribute">align-items</span>: center;
            <span class="hljs-attribute">justify-content</span>: center;
            <span class="hljs-attribute">color</span>: white;
            <span class="hljs-attribute">text-align</span>: center;
        }

        <span class="hljs-selector-class">.hero-content</span> <span class="hljs-selector-tag">h1</span> {
            <span class="hljs-attribute">font-size</span>: <span class="hljs-number">4em</span>;
            <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">20px</span>;
        }

        <span class="hljs-selector-class">.hero-content</span> <span class="hljs-selector-tag">p</span> {
            <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5em</span>;
            <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">30px</span>;
        }

        <span class="hljs-selector-class">.cta-button</span> {
            <span class="hljs-attribute">padding</span>: <span class="hljs-number">15px</span> <span class="hljs-number">30px</span>;
            <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">220</span>, <span class="hljs-number">38</span>, <span class="hljs-number">38</span>);
            <span class="hljs-attribute">color</span>: white;
            <span class="hljs-attribute">text-decoration</span>: none;
            <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>;
            <span class="hljs-attribute">font-weight</span>: bold;
            <span class="hljs-attribute">transition</span>: background-color <span class="hljs-number">0.3s</span>;
        }

        <span class="hljs-selector-class">.cta-button</span><span class="hljs-selector-pseudo">:hover</span> {
            <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">185</span>, <span class="hljs-number">28</span>, <span class="hljs-number">28</span>);
        }

        <span class="hljs-selector-tag">section</span> {
            <span class="hljs-attribute">padding</span>: <span class="hljs-number">100px</span> <span class="hljs-number">20%</span>;
        }

        <span class="hljs-selector-class">.about</span> {
            <span class="hljs-attribute">background-color</span>: white;
        }

        <span class="hljs-selector-class">.services</span> {
            <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f5f5f5</span>;
        }

        <span class="hljs-selector-class">.service-grid</span> {
            <span class="hljs-attribute">display</span>: grid;
            <span class="hljs-attribute">grid-template-columns</span>: <span class="hljs-built_in">repeat</span>(auto-fit, minmax(<span class="hljs-number">300px</span>, <span class="hljs-number">1</span>fr));
            <span class="hljs-attribute">gap</span>: <span class="hljs-number">30px</span>;
            <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">50px</span>;
        }

        <span class="hljs-selector-class">.service-card</span> {
            <span class="hljs-attribute">background-color</span>: white;
            <span class="hljs-attribute">padding</span>: <span class="hljs-number">30px</span>;
            <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">10px</span>;
            <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">5px</span> <span class="hljs-number">15px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0.1</span>);
            <span class="hljs-attribute">transition</span>: transform <span class="hljs-number">0.3s</span>;
        }

        <span class="hljs-selector-class">.service-card</span><span class="hljs-selector-pseudo">:hover</span> {
            <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translateY</span>(-<span class="hljs-number">10px</span>);
        }

        <span class="hljs-selector-class">.contact</span> {
            <span class="hljs-attribute">background</span>: <span class="hljs-built_in">linear-gradient</span>(rgba(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.8</span>), <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.8</span>)),
                        <span class="hljs-built_in">url</span>(<span class="hljs-string">'https://images.unsplash.com/photo-1599567832218-907816219e67?ixlib=rb-4.0.3'</span>);
            <span class="hljs-attribute">background-attachment</span>: fixed;
            <span class="hljs-attribute">background-size</span>: cover;
            <span class="hljs-attribute">color</span>: white;
        }

        <span class="hljs-selector-class">.contact-form</span> {
            <span class="hljs-attribute">display</span>: grid;
            <span class="hljs-attribute">gap</span>: <span class="hljs-number">20px</span>;
            <span class="hljs-attribute">max-width</span>: <span class="hljs-number">600px</span>;
            <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;
        }

        <span class="hljs-selector-tag">input</span>, <span class="hljs-selector-tag">textarea</span> {
            <span class="hljs-attribute">padding</span>: <span class="hljs-number">15px</span>;
            <span class="hljs-attribute">border</span>: none;
            <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>;
        }

        <span class="hljs-selector-tag">footer</span> {
            <span class="hljs-attribute">background-color</span>: black;
            <span class="hljs-attribute">color</span>: white;
            <span class="hljs-attribute">padding</span>: <span class="hljs-number">50px</span> <span class="hljs-number">20%</span>;
            <span class="hljs-attribute">text-align</span>: center;
        }

        <span class="hljs-selector-class">.footer-content</span> {
            <span class="hljs-attribute">display</span>: grid;
            <span class="hljs-attribute">grid-template-columns</span>: <span class="hljs-built_in">repeat</span>(auto-fit, minmax(<span class="hljs-number">200px</span>, <span class="hljs-number">1</span>fr));
            <span class="hljs-attribute">gap</span>: <span class="hljs-number">30px</span>;
            <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">30px</span>;
        }

        <span class="hljs-selector-tag">h2</span> {
            <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">30px</span>;
            <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2.5em</span>;
        }

        <span class="hljs-selector-tag">p</span> {
            <span class="hljs-attribute">line-height</span>: <span class="hljs-number">1.6</span>;
            <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">20px</span>;
        }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"navbar"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#home"</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#about"</span>&gt;</span>About<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#services"</span>&gt;</span>Services<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#contact"</span>&gt;</span>Contact<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hero"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"home"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hero-content"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Danville Plumbers<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Your Trusted Plumbing Experts Since 1995<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#contact"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"cta-button"</span>&gt;</span>Get Free Quote<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"about"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"about"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>About Us<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>With over 25 years of experience, Danville Plumbers has been the go-to plumbing service provider in the Danville area. We pride ourselves on delivering exceptional quality work with unmatched customer service.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Our team of licensed professionals is available 24/7 for emergency services, ensuring that your plumbing needs are met whenever they arise. We believe in transparent pricing, honest service, and building long-lasting relationships with our clients.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"services"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"services"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Our Services<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"service-grid"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"service-card"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Emergency Repairs<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>24/7 emergency plumbing services for any urgent situations.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"service-card"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Installation<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Professional installation of fixtures, pipes, and plumbing systems.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"service-card"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Maintenance<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Regular maintenance and inspection services to prevent future issues.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"contact"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"contact"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Contact Us<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"contact-form"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Your Name"</span> <span class="hljs-attr">required</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Your Email"</span> <span class="hljs-attr">required</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"tel"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Your Phone"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">textarea</span> <span class="hljs-attr">rows</span>=<span class="hljs-string">"5"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Your Message"</span> <span class="hljs-attr">required</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">textarea</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"cta-button"</span>&gt;</span>Send Message<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">footer</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"footer-content"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Danville Plumbers<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>123 Main Street<span class="hljs-tag">&lt;<span class="hljs-name">br</span>&gt;</span>Somwhere, Someville<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Hours<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Monday-Friday: 8am-6pm<span class="hljs-tag">&lt;<span class="hljs-name">br</span>&gt;</span>
                   Emergency Service: 24/7<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Contact<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Phone: (965) 777-0123<span class="hljs-tag">&lt;<span class="hljs-name">br</span>&gt;</span>
                   Email: info@xyz.com<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-symbol">&amp;copy;</span> 2023 Danville Plumbers. All rights reserved.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="heading-not-just-static-websites">Not just static websites</h2>
<p>With clever prompting, you can even get Sonnet to code in any backend language, including writing the email code for sending forms or building a gallery.</p>
<p>I am not a Figma person (nor a frontend person for that matter), I do not usually draw layouts and concepts first unless it’s a large project, thus usually, I’ll just take an hour or two to “throw together” a POC and then fiddle with that until I get something closer to the finished concept.</p>
<p>Now with Sonnet, within a few minutes, I can scaffold designs, get variations, and play around with different concepts in a fraction of the time. It’s almost like the Ruby-on-Rails scaffold era, where you could spin up a whole blog in minutes.</p>
<p>Don’t worry though; AI is not going to replace designers or developers, it cannot really think or create at the same level as we do, but it can give creatives (or technical) people a powerful paintbrush to scaffold ideas really quickly.</p>
<p>ChatGPT was okayish at one point, but the quality of the code seemed to drop over time and half the time that code wouldn’t work, Sonnet on the other is far better at generating good quality code that actually works!</p>
<h2 id="heading-claude-sonnets-not-perfect-anthropic-has-scaling-issues">Claude Sonnet’s not perfect: Anthropic has scaling issues</h2>
<p>I have always noticed weird scaling issues with Anthropic (model overloaded) and even times when the API returned blank results. I find OpenAI is much more stable and GPT4o-mini is much cheaper as well.</p>
<p>Thus, for more RAG-type applications or high-traffic use cases, I tend to use OpenAI models. For creative and as a coding assistant, Sonnet stability isn’t much of an issue.</p>
<p>Another annoying thing with Anthropic is that you have to follow the conversation sequence: AI-HUMAN-AI-HUMAN, this is not natural in the real world, especially when dealing with Voice AI.</p>
<p>OpenAI just seems more idiot proof 😂.</p>
<p>Hopefully Anthropic can work on the developer experience, I feel that’s really holding them back because their models are really great for creative tasks, usually better than OpenAI models.</p>
]]></content:encoded></item><item><title><![CDATA[How to set up a self-hosted Linux server from scratch?]]></title><description><![CDATA[I often come across people on Reddit and other places complaining about Vercel overcharging them for spikes in traffic and worried about the cost of scaling.
Vercel is a great platform, it works and is convenient. Most developers hate dealing with se...]]></description><link>https://kevincoder.co.za/how-to-set-up-a-self-hosted-linux-server-from-scratch</link><guid isPermaLink="true">https://kevincoder.co.za/how-to-set-up-a-self-hosted-linux-server-from-scratch</guid><category><![CDATA[vercel alternative]]></category><category><![CDATA[Linux]]></category><category><![CDATA[vps server]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Tue, 03 Dec 2024 15:39:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733240283511/48945ace-0a2b-4917-916b-f22fe8417d34.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I often come across people on Reddit and other places complaining about Vercel overcharging them for spikes in traffic and worried about the cost of scaling.</p>
<p>Vercel is a great platform, it works and is convenient. Most developers hate dealing with servers and are sometimes scared of touching Linux, so it makes sense to outsource this aspect to DevOps specialists and just focus on coding.</p>
<p>This approach works well initially, but as you grow, costs can escalate, sometimes exponentially! So why not just roll out your own servers? It might be a few hours of work SSH hardening and setting up, but once it’s running, these servers can go for years without the need for any additional maintenance.</p>
<p>If you want to go down the self-hosted route but don’t know where to start, then this article is perfect for you. I will go through it step-by-step.</p>
<blockquote>
<p>Note: This tutorial assumes you using Ubuntu Server for your instances. Debian might work as well.</p>
</blockquote>
<h2 id="heading-understanding-the-costs">Understanding the costs</h2>
<p>Before we even think about setting up a server, let’s talk about the costs. When you host with PaaS providers like Vercel, they usually charge you for storage and compute separately.</p>
<p>You, therefore, will pay for how much memory your application consumes, and how long it runs for, plus any storage you use. For a low-traffic site, this can be really affordable because when you have little to no requests, your costs are relatively negligible.</p>
<p>A VPS/dedicated server on the other hand tends to come with a fixed cost, I use Hetzner, thus I will base my figures on their current pricing but should be similar to other hosting companies such as Digital Ocean or AWS.</p>
<p>On Hetzner, you can get decently sized VPS servers for (Not sponsored, I use them personally for my sites):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726225698139/34b73210-f74f-4b69-9217-fe9ab04a248b.png" alt class="image--center mx-auto" /></p>
<p>With a VPS instance, you get everything you need including network traffic, disk space, and usually better RAM and CPU allocations. In addition, you have full control of your instance and can run anything you desire.</p>
<p>The only caveat is that VPS providers do limit your traffic allocation per month, so if you exceed 20TB in one month (in the Hetzner case above), you’ll pay per GIG for the extra usage.</p>
<p>Realistically though, if you are getting more than 20 TBs of traffic, these VPS servers are still going to be way cheaper than something like Vercel.</p>
<h2 id="heading-lets-learn-some-basic-nano">Let’s learn some basic Nano</h2>
<p>I am sorry, that this may seem oddly misplaced in the context of this article, but I wanted to get it out the gate as quickly as possible. Linux uses config files for nearly every application, so a basic understanding of terminal-based text editors is essential. If you are already familiar with Nano, Vi, VIM, etc.… you can safely skip this section.</p>
<p>I can’t remember if it comes pre-installed with Ubuntu, but if not simply run:</p>
<pre><code class="lang-bash">apt update
apt install nano
</code></pre>
<p>Nano commands:</p>
<ul>
<li><p>nano file_name - This will open the specified file path in the editor.</p>
</li>
<li><p>Arrow keys to move up or down, or left or right in the editor.</p>
</li>
<li><p>“ctrl+o” to save any changes.</p>
</li>
<li><p>“ctrl+x” to exit.</p>
</li>
<li><p>“ctrl+/” go to any line number e.g. 10</p>
</li>
<li><p>“ctrl+c” cancels an operation.</p>
</li>
<li><p>“ctrl+w” search through the file.</p>
</li>
<li><p>if you use: “nano +5 file_name”, it’ll open the file and move the cursor to the 5th line.</p>
</li>
</ul>
<p>There you go, simple, right? At a bare minimum, you only need to know “ctrl+o” and “ctrl+x” to edit, save, and exit.</p>
<h2 id="heading-initial-setup">Initial setup</h2>
<p>The first thing you want to do is to generate SSH keys, this is a much more secure way of accessing your server compared to regular passwords. On most terminals, you can run:</p>
<blockquote>
<p>ℹ️ SSH Keys are a pair of long encrypted "secret codes" i.e. a private key and public key. These are much more secure and harder to crack compared to regular old passwords. The server will keep a copy of your public key, and match that against the private key you send when trying to SSH into the server, if there are any discrepancies, that connection will be automatically denied.</p>
</blockquote>
<pre><code class="lang-bash">ssh-keygen -t rsa -b 4096
</code></pre>
<p>The above command will generate a private and public key pair. You will need to copy the “.pub” file’s contents and paste it into the server’s “~/.ssh/authorized_keys” file, most providers offer a simple GUI to do this.</p>
<blockquote>
<p>ℹ️ ~/ is just a shortcut for /home/username or the home folder for the currently logged in user on the Linux terminal.</p>
</blockquote>
<p>On Hetzner, you can upload this key under this section when creating a server (I blurbed out my key names for privacy):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726226695756/ce511d74-4218-4f5f-aed1-a54b1e8cb183.png" alt class="image--center mx-auto" /></p>
<p>Finally, once the server has been created, you will get a public IP. To access the machine, just SSH in:</p>
<pre><code class="lang-bash">ssh root@192.x.y.z
</code></pre>
<p>This should automatically pick up the key you created and log you in, if you do encounter issues, try the following:</p>
<pre><code class="lang-bash">ssh -o IdentitiesOnly=yes -i /path/to/your/private/key root@192.x.y.z
</code></pre>
<p>You may also see the error below with a new server, the warning is a bit “dramatic” to say the least. You should verify that you are using the correct IP and the key is correct but usually, this is just a sanity check by the SSH daemon: “I am seeing this server IP for the first time, and I don’t know if I can trust it, so I will refuse and print a scary message!?”</p>
<pre><code class="lang-bash">@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint <span class="hljs-keyword">for</span> the ED25519 key sent by the remote host is
SHA256:g8DZMXxNy8a7fM4/Xyz.
</code></pre>
<p>To fix this, simply run the following to trust this new server:</p>
<pre><code class="lang-bash">ssh-keygen -f <span class="hljs-string">"/home/<span class="hljs-variable">$USER</span>/.ssh/known_hosts"</span> -R <span class="hljs-string">"x.y.z"</span>
</code></pre>
<h2 id="heading-securing-the-server">Securing the server</h2>
<p>When it comes to VPS servers you should use the following security best practices:</p>
<ul>
<li><p>Use both a network and server firewall. Most providers including Hetzner have a concept of a network firewall (also known as a cloud firewall or WAF). You should set one up as soon as you provision your instance. Open ports (TCP): 22, 443, 80 and block everything else.</p>
</li>
<li><p>Change the default SSH port. Hackers can sniff the new port, but most attacks come from automated scripts that target port 22, by simply changing this port, you are protecting yourself from loads of automated tools.</p>
</li>
<li><p>Disable root access and create an SSH-only user.</p>
</li>
<li><p>Use a VPN, this is optional but highly recommended. You can then open ports only to your VPN keeping your server ports invisible to the rest of the internet.</p>
</li>
</ul>
<p>Next, let’s go through each of the mentioned security steps.</p>
<h2 id="heading-setting-up-a-network-firewall">Setting up a network firewall</h2>
<p>This will differ from provider to provider; I will just give you an example using Hetzner. In the Hetzner cloud console, click on “Firewalls” in the left navigation (Cloud firewalls are free to use).</p>
<p>You should see the following screen. Simply just allow TCP 22, 80, and 443. In my case, I also added “9222”. This is because, in the next few steps, we’ll change the default SSH port to “9222”. You can choose any random port you want, just make sure it doesn’t clash with any process running on your server.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726233592534/93e7381d-1365-403c-9e9e-8de12c0ba49b.png" alt class="image--center mx-auto" /></p>
<p>Whenever you create an instance in the future, you should see the “firewalls” option, just click on the one you previously created to attach the instance to that firewall:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726233723928/1a387d4a-72fc-48df-b203-80558e788eec.png" alt class="image--center mx-auto" /></p>
<p>Alternatively, for existing servers. Just click on the “Firewalls” tab in the instance’s details page and select “Apply Firewall” to add the server to one of your existing firewalls:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726233822664/5e3f007b-1b71-4e24-9d85-953917689af4.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-setting-up-a-server-firewall">Setting up a server firewall</h2>
<p>It’s also a good practice to set up a firewall on your VPS instance as well, it’s highly unlikely that network traffic will bypass the main firewall, but just in case, you can never be too sure!</p>
<p>First, you will need to SSH into the server:</p>
<pre><code class="lang-bash">ssh root@your_ip
</code></pre>
<p>UFW is the default firewall on Ubuntu when you login into the remote server, you can confirm that UFW is installed by typing:</p>
<pre><code class="lang-bash">ufw
</code></pre>
<p>You should get the “help” menu, if not just run:</p>
<pre><code class="lang-bash">apt update -y
apt install ufw -y
</code></pre>
<p>Let’s allow the required ports as follows:</p>
<pre><code class="lang-bash">ufw allow http
ufw allow https
ufw allow ssh
ufw allow from any to any port 9222
ufw <span class="hljs-built_in">enable</span>
</code></pre>
<p>You’ll be asked to confirm, just hit “y”. Now if you run:</p>
<pre><code class="lang-bash">ufw status numbered
<span class="hljs-comment">###### You should see #######</span>

Status: active

To                         Action      From
--                         ------      ----
[ 1] 80/tcp                     ALLOW IN    Anywhere                  
[ 2] 22/tcp                     ALLOW IN    Anywhere                  
[ 3] 9222                       ALLOW IN    Anywhere                  
[ 4] 80/tcp (v6)                ALLOW IN    Anywhere (v6)             
[ 5] 22/tcp (v6)                ALLOW IN    Anywhere (v6)             
[ 6] 9222 (v6)                  ALLOW IN    Anywhere (v6)
</code></pre>
<p>Awesome! We now have a decently secured server. If you opted for the VPN option, you can do the following instead:</p>
<pre><code class="lang-bash">ufw allow http
ufw allow https
ufw allow from x.x.x.x to any port 22
ufw allow from x.x.x.x to any port 9222
ufw <span class="hljs-built_in">enable</span>
</code></pre>
<p>Naturally “x.x.x.x” should be replaced by your VPN’s IP address.</p>
<blockquote>
<p>Note: I use “ufw status numbered” because this also prints the “index” of the rule, in case we need to delete that rule. You can totally omit “numbered” and this command will work fine.</p>
</blockquote>
<h2 id="heading-changing-the-default-ssh-port">Changing the default SSH port</h2>
<p>The network firewall can be altered at any time, however, the instance firewall cannot, therefore, it’s advisable to keep one tab open with an active SSH connection and also that you allow both 22 and 9222 [or whatever port you choose] first, then test the new port works, and thereafter drop the old 22 from your firewall.</p>
<p>To change the port do the following (or you can just nano /etc/ssh/sshd_config and manually change it):</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Backup original file just incase</span>
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup

<span class="hljs-comment"># Set the new port</span>
sed -i <span class="hljs-string">'s/\#Port 22/Port 9222/'</span> /etc/ssh/sshd_config

<span class="hljs-comment"># Restart ssh</span>
systemctl restart ssh
</code></pre>
<h2 id="heading-add-another-layer-of-security">Add another layer of security</h2>
<p>Brute force attacks are also another common security issue when you host a publically accessible server, by brute force, I mean a bot that will constantly try hundreds or thousands of different passwords or key combinations to try and break into your server.</p>
<p>A good layer of protection for this kind of attack is to use Fail2Ban. Fail2Ban will basically watch common ports like 22 for abuse traffic and block them automatically.</p>
<p>To setup Fail2Ban:</p>
<pre><code class="lang-bash">sudo apt install fail2ban
<span class="hljs-comment"># So we can customize fail2ban settings</span>
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

sudo nano /etc/fail2ban/jail.local
</code></pre>
<p>Next, paste it into the config file:</p>
<pre><code class="lang-bash">[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
banaction = iptables-multiport

ignoreip = 127.0.0.1/8 ::1
destemail = your@email.com

[sshd]
enabled = <span class="hljs-literal">true</span>
port = 9222
filter = sshd
logpath = /var/<span class="hljs-built_in">log</span>/auth.log
maxretry = 3
bantime = 1h
</code></pre>
<p>And finally, restart:</p>
<pre><code class="lang-bash">sudo systemctl restart fail2ban
</code></pre>
<p>You can do much more advanced stuff like preventing DDOS attacks on port 443, 80, etc…, for no,w though we are basically just protecting port 9222. If abuse is detected, Fail2Ban will block that IP for 1 hour.</p>
<h2 id="heading-setting-up-an-ssh-only-user">Setting up an SSH-only user</h2>
<p>SSH’ing as root is a bad idea, If this user is compromised then the attacker can access your whole system, instead, you should create a password-based user that can elevate their privileges to sudo only when needed.</p>
<pre><code class="lang-bash">adduser yourusername
usermod -aG sudo secureuser
</code></pre>
<p>Next, you should copy your ssh pub key to ~/.ssh/authorized_keys:</p>
<pre><code class="lang-bash">sudo su - secureuser

mkdir ~/.ssh
<span class="hljs-comment"># Paste your pub key with</span>
nano ~/.ssh/authorized_keys

<span class="hljs-comment"># Fix permissions</span>
chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh
</code></pre>
<p>Now you should in a separate terminal, try to ssh as this user:</p>
<pre><code class="lang-bash">ssh -p 9222 secureuser@server_ip
</code></pre>
<p>🤞The above should allow you in, alternatively check that there are no spaces in the authorized file and retry setting the permissions, usually it’s either bad permissions or the public key is invalid.</p>
<p>You can also use “-i” with your SSH command to specify which key you want to use:</p>
<pre><code class="lang-bash">ssh -i ~/.ssh/id_rsa -p 9222 secureuser@server_ip
</code></pre>
<p>Almost there, we want to do two more security optimizations:</p>
<ul>
<li><p>Prevent the root user from logging in.</p>
</li>
<li><p>Disable password logins. Earlier we created the SSH user with a password, this password is not meant for SSH access, instead, it’s just there to add an extra layer of security. Should an attacker gain access to this SSH user, it still makes it a little more difficult to access root, since running “sudo su - “ will prompt for the password.</p>
</li>
</ul>
<p>To action:</p>
<pre><code class="lang-bash">nano /etc/ssh/sshd_config
<span class="hljs-comment"># Uncomment and change these lines to:</span>
PasswordAuthentication no
PermitRootLogin no
</code></pre>
<p>Finally, restart the ssh daemon:</p>
<pre><code class="lang-bash">systemctl restart ssh
</code></pre>
<h2 id="heading-setting-up-docker">Setting up docker</h2>
<p>Great! Now you have a fairly secure server up and running, but to run Next.js or your application code, you probably gonna need to install docker first. Installing Docker is a breeze:</p>
<pre><code class="lang-bash">curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
</code></pre>
<p>Now when you type the following, you should see docker installed:</p>
<pre><code class="lang-bash">docker ps
</code></pre>
<p>It is always a good idea to run docker as a none root user, you can do so as follows:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># You can call the user whatever you want</span>
sudo useradd -m -s /bin/bash dockeruser
sudo usermod -aG docker dockeruser
</code></pre>
<p>Now, you should log in with this user and run your docker containers as “dockeruser”:</p>
<pre><code class="lang-bash">sudo su - dockeruser
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>There you go, a detailed step-by-step guide, copy-and-paste basically of all the steps you need to run your own VPS server, it may seem like a lot of steps but it’s like 99% the same for every server you setup, so it becomes muscle memory after a while.</p>
]]></content:encoded></item><item><title><![CDATA[Python voice AI web phone using WebRTC]]></title><description><![CDATA[Recently, I did a walk-through on how you can use SIP and voice AI to answer the phone with Python (read more here). This got me experimenting a bit more. I have a 6-year-old, and as you can imagine, kids at that age love amusing, funny voices.
What ...]]></description><link>https://kevincoder.co.za/how-i-used-voice-ai-to-bring-imaginary-characters-to-life</link><guid isPermaLink="true">https://kevincoder.co.za/how-i-used-voice-ai-to-bring-imaginary-characters-to-life</guid><category><![CDATA[Python]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[#VoiceAI]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Tue, 29 Oct 2024 15:57:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1730217555599/70f17cd2-33c4-4c1f-9b03-9828ec5b90de.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, I did a walk-through on how you can use SIP and voice AI to answer the phone with Python (read more <a target="_blank" href="https://kevincoder.co.za/answer-the-phone-with-python">here</a>). This got me experimenting a bit more. I have a 6-year-old, and as you can imagine, kids at that age love amusing, funny voices.</p>
<p>What if I could build an AI voice phone just for fun? Something that she could interact with. This would be way cooler than those stuffed animal toys that keep repeating the same sentences over and over. Since this “toy” is powered by a full LLM, it could actually have an entire conversation and role-play as if it were an actual cartoon character!</p>
<blockquote>
<p>💡 Are you new to Python? Learn Python with <a target="_blank" href="https://learnpython.com/?tap_a=131378-305e0d&amp;ref=ngm5zjv">LearnPython.com</a> courses. Fun interactive courses that are based on real-life business scenarios, meaning you’ll be writing Python code and seeing the results instantly. <em>No need to install Python or other tools on your device, everything happens through your favorite web browser</em> (Sponsored content).</p>
</blockquote>
<p>In this article, I will walk you through step-by-step how I achieved this and all the relevant technologies and services needed.</p>
<h2 id="heading-webrtc-voiceai-how-will-this-work">WebRTC VoiceAI: How will this work?</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728657445771/23c85178-b1da-4f2b-907e-5693dd4d6a6c.jpeg" alt class="image--center mx-auto" /></p>
<p>WebRTC is a browser peer-to-peer standard that allows just about any modern browser to stream rich media content such as video or audio in real-time, similar to a voice or video call.</p>
<p>Thus, WebRTC is perfect for our little Voice AI character (Aka ZaZu the Monkey!). For the backend, we will use a WebSocket server built in Python, which orchestrates the AI chain to transcribe the audio, prompt the model, and finally convert the model’s response back to audio and stream it back to the browser using WebRTC.</p>
<p>Now, since this is a fun app, I wasn’t going to spend a lot of time building my own transcription model, or TTS model for that matter. Instead, we’ll just use Assembly AI for transcription and OpenAI for everything else.</p>
<p>I initially tried to build this with VOSK instead of Assembly AI which is a decent open-source speech-to-text library, however, the transcription quality wasn’t that great, and it wasn’t worth the time and effort to train a custom VOSK model. AssemblyAI has really good accuracy and costs just $0.37 per hour, which is insanely cheap!</p>
<p>You can view the full source code here: <a target="_blank" href="https://github.com/kevincoder-co-za/zazu-voiceai">https://github.com/kevincoder-co-za/zazu-voiceai</a></p>
<blockquote>
<p>⚠️ I omitted “try except” blocks throughout to make the code more readable, however, for a real production app, you should catch and handle errors more efficiently.</p>
</blockquote>
<h2 id="heading-setting-up-our-websocket-server">Setting up our WebSocket server</h2>
<p>For the WebSocket server, we’ll go a bit old-school and use Flask. Flask has very good support for web sockets and is fairly easy to spin up a quick prototype. To get started, let’s install a few pip packages:</p>
<pre><code class="lang-bash">pip install -r requirements.txt
</code></pre>
<pre><code class="lang-plaintext">flask
flask-sockets
gevent
gevent-websocket
assemblyai
ffmpeg
</code></pre>
<p>You will in addition need to install “<a target="_blank" href="https://www.ffmpeg.org/">FFMpeg</a>” for your operating system. FFmpeg is a powerful audio manipulation library used by most modern audio tools on the market, and we’ll use it to alter the audio generated by OpenAI to sound more cartoonish.</p>
<p>Next, you’ll want to create a Python file: “server.py” or whatever you prefer:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, render_template
<span class="hljs-keyword">from</span> flask_sockets <span class="hljs-keyword">import</span> Sockets
<span class="hljs-keyword">from</span> gevent <span class="hljs-keyword">import</span> pywsgi
<span class="hljs-keyword">from</span> geventwebsocket.handler <span class="hljs-keyword">import</span> WebSocketHandler
<span class="hljs-keyword">from</span> audio_handlers <span class="hljs-keyword">import</span> AudioHandler
<span class="hljs-keyword">from</span> llm_adapters <span class="hljs-keyword">import</span> OpenAILLM
<span class="hljs-keyword">import</span> settings

app = Flask(__name__)
sockets = Sockets(app)

<span class="hljs-meta">@app.route("/dialer")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">dialer</span>():</span>
    <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">"dialer.html"</span>, 
        sample_rate = settings.SAMPLE_RATE,
        socket_url = settings.SOCKET_URL
    )

<span class="hljs-meta">@sockets.route("/websocket/stream")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">audio_stream</span>(<span class="hljs-params">ws</span>):</span>
    audio_handler = <span class="hljs-literal">None</span>
    llm = <span class="hljs-literal">None</span>

    <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> ws.closed:
        message = ws.receive()

        <span class="hljs-keyword">if</span> message <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            <span class="hljs-keyword">continue</span>
        <span class="hljs-keyword">if</span> isinstance(message, str):
            <span class="hljs-keyword">if</span> <span class="hljs-string">"start call"</span> <span class="hljs-keyword">in</span> message:
                print(<span class="hljs-string">"Call started"</span>, flush=<span class="hljs-literal">True</span>)
                llm = OpenAILLM()
                audio_handler = AudioHandler(llm, ws)
            <span class="hljs-keyword">elif</span> <span class="hljs-string">"end call"</span> <span class="hljs-keyword">in</span> message <span class="hljs-keyword">and</span> audio_handler:
                audio_handler.stop()
                llm = <span class="hljs-literal">None</span>

        <span class="hljs-keyword">elif</span> isinstance(message, bytes) <span class="hljs-keyword">or</span> isinstance(message, bytearray):
            audio_handler.stream(bytes(message))

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    WEBSOCKET_PORT = <span class="hljs-number">5000</span>
    server = pywsgi.WSGIServer((<span class="hljs-string">""</span>, WEBSOCKET_PORT), app, handler_class=WebSocketHandler)
    print(<span class="hljs-string">f"Server listening on ws://0.0.0.0:<span class="hljs-subst">{WEBSOCKET_PORT}</span>"</span>)
    server.serve_forever()
</code></pre>
<p>Let’s go through this step-by-step, first, we set up our socket server and two routes:</p>
<pre><code class="lang-python">app = Flask(__name__)
sockets = Sockets(app)

<span class="hljs-meta">@app.route("/dialer")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">dialer</span>():</span>
    <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">"dialer.html"</span>, 
        sample_rate = settings.SAMPLE_RATE,
        socket_url = settings.SOCKET_URL
    )

<span class="hljs-meta">@sockets.route("/websocket/stream")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">audio_stream</span>(<span class="hljs-params">ws</span>):</span>
</code></pre>
<p>The dialer route just returns an HTML page: “dialer.html”. This contains our JavaScript WebRTC phone implementation. We’ll get to that in a bit.</p>
<p>The “/websocket/stream” is where all the magic happens. When the browser initiates a call, this endpoint is triggered, and a socket is opened. Since the socket will remain open until either party terminates the call, we can therefore constantly stream data back and forth in a consistent manner.</p>
<pre><code class="lang-python"><span class="hljs-meta">@sockets.route("/websocket/stream")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">audio_stream</span>(<span class="hljs-params">ws</span>):</span>
    audio_handler = <span class="hljs-literal">None</span>
    llm = <span class="hljs-literal">None</span>

    <span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> ws.closed:
        message = ws.receive()

        <span class="hljs-keyword">if</span> message <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            <span class="hljs-keyword">continue</span>
        <span class="hljs-keyword">if</span> isinstance(message, str):
            <span class="hljs-keyword">if</span> <span class="hljs-string">"start call"</span> <span class="hljs-keyword">in</span> message:
                print(<span class="hljs-string">"Call started"</span>, flush=<span class="hljs-literal">True</span>)
                llm = OpenAILLM()
                audio_handler = AudioHandler(llm, ws)
            <span class="hljs-keyword">elif</span> <span class="hljs-string">"end call"</span> <span class="hljs-keyword">in</span> message <span class="hljs-keyword">and</span> audio_handler:
                audio_handler.stop()
                llm = <span class="hljs-literal">None</span>

        <span class="hljs-keyword">elif</span> isinstance(message, bytes) <span class="hljs-keyword">or</span> isinstance(message, bytearray):
            audio_handler.stream(bytes(message))
</code></pre>
<p>In our “audio_stream” method, we scan the incoming data to check if it’s raw bytes or a string. When the data is raw bytes, this means that we are receiving audio data and when the data is a string, this means we are receiving an event such as “start call” or “end call”.</p>
<p>When the call starts, we set up an <strong>audio_handler</strong> and <strong>llm</strong>. The “audio_handler” will transcribe audio and send it back to the user.</p>
<p>The “llm” will communicate with GPT4o-mini and generate an appropriate response to the user based on the transcription.</p>
<h2 id="heading-building-the-llm-adapter">Building the LLM Adapter</h2>
<p>The LLM adapter has two tasks it needs to perform:</p>
<ul>
<li><p>Prompt the text model with the transcription and get back a generated response (invoke method).</p>
</li>
<li><p>Take the generated response and convert it to audio so that we can stream it back to the browser (text_to_audio method).</p>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> openai <span class="hljs-keyword">import</span> OpenAI
<span class="hljs-keyword">import</span> settings

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OpenAILLM</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        self.llm = OpenAI()
        self.messages = [
            (<span class="hljs-string">"system"</span>,settings.SYSTEM_AI_PROMPT)
        ]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">invoke</span>(<span class="hljs-params">self, message</span>):</span>
        self.messages.append((<span class="hljs-string">"user"</span>, message))
        messages = self.messages.copy()

        chat_completion = self.llm.chat.completions.create(
            model= settings.OPENAI_GPT_MODEL,
            messages=[{<span class="hljs-string">"role"</span>: m[<span class="hljs-number">0</span>], <span class="hljs-string">"content"</span>: m[<span class="hljs-number">1</span>]} <span class="hljs-keyword">for</span> m <span class="hljs-keyword">in</span> messages]
        )

        response = chat_completion.choices[<span class="hljs-number">0</span>].message.content
        self.messages.append((<span class="hljs-string">"assistant"</span>, response))

        <span class="hljs-keyword">return</span> response

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">text_to_audio</span>(<span class="hljs-params">self, text, audio_file_path</span>):</span>
        response = self.llm.audio.speech.create(
            model=settings.OPENAI_TTS_MODEL,
            voice=settings.OPENAI_TTS_VOICE,
            input=text
        )

        response.stream_to_file(audio_file_path)
</code></pre>
<p><strong>Note:</strong> We also have some memory built in so that the LLM has context for the entire conversation. To maintain memory, we simply just append all messages to a list of “messages” and then use the conversation API to send a batch of messages.</p>
<h2 id="heading-building-the-audio-manager">Building the Audio Manager</h2>
<p>The Audio Manager can be found in “audio_handlers.py”:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> uuid
<span class="hljs-keyword">import</span> settings
<span class="hljs-keyword">import</span> assemblyai <span class="hljs-keyword">as</span> aai
<span class="hljs-keyword">import</span> ffmpeg

aai.settings.api_key = settings.ASSEMBLYAI_API_KEY

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AudioHandler</span>(<span class="hljs-params">aai.RealtimeTranscriber</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, llm, ws</span>):</span>
        super().__init__(
            on_data=self.on_data,
            sample_rate=settings.SAMPLE_RATE,
            on_error=<span class="hljs-keyword">lambda</span> x : print(x, flush=<span class="hljs-literal">True</span>),
            disable_partial_transcripts=<span class="hljs-literal">True</span>
        )
        self.llm = llm
        self.ws = ws
        self.connect()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">stop</span>(<span class="hljs-params">self</span>):</span>
        self.close()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">on_data</span>(<span class="hljs-params">self, transcript: aai.RealtimeTranscript</span>):</span>
       <span class="hljs-keyword">if</span> isinstance(transcript, aai.RealtimeFinalTranscript) <span class="hljs-keyword">and</span> transcript.text:
            response = self.llm.invoke(transcript.text)
            <span class="hljs-keyword">if</span> response != <span class="hljs-string">""</span>:
                self.respond_to_user_prompt(response)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">respond_to_user_prompt</span>(<span class="hljs-params">self, transcript</span>):</span>
        tmp_id = str(uuid.uuid4())
        tmp_file = <span class="hljs-string">f"/tmp/_<span class="hljs-subst">{tmp_id}</span>.mp3"</span>
        cartoonified_version = <span class="hljs-string">f"/tmp/modified_<span class="hljs-subst">{tmp_id}</span>.mp3"</span>

        self.llm.text_to_audio(transcript, tmp_file)        

        ffmpeg.input(tmp_file).filter(<span class="hljs-string">'asetrate'</span>, <span class="hljs-number">33050</span>).output(cartoonified_version).run()

        audio_data = <span class="hljs-literal">None</span>

        <span class="hljs-keyword">with</span> open(cartoonified_version, <span class="hljs-string">"rb"</span>) <span class="hljs-keyword">as</span> f:
            audio_data = f.read()

        <span class="hljs-keyword">if</span> os.path.exists(tmp_file):
            os.remove(tmp_file)
        <span class="hljs-keyword">if</span> os.path.exists(cartoonified_version):
            os.remove(cartoonified_version)

        <span class="hljs-keyword">if</span> audio_data:
            self.ws.send(audio_data)
</code></pre>
<p>The audio manager class extends Assembly AI’s <strong>RealtimeTranscriber</strong>. We override the “on_data” method to grab the finished transcription and then use both OpenAI Whisper and FFMpeg to generate a cartoonish voice (You probably should use a voice cloning API for better performance).</p>
<pre><code class="lang-python">    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, llm, ws</span>):</span>
        super().__init__(
            on_data=self.on_data,
            sample_rate=settings.SAMPLE_RATE,
            on_error=<span class="hljs-keyword">lambda</span> x : print(x, flush=<span class="hljs-literal">True</span>),
            disable_partial_transcripts=<span class="hljs-literal">True</span>
        )
        self.llm = llm
        self.ws = ws
        self.connect()
</code></pre>
<p>In the constructor, we set up a few configs:</p>
<ul>
<li><p><strong>on_data</strong>: This method will allow us to receive the full transcription and perform any other post-processing needed.</p>
</li>
<li><p><strong>sample_rate</strong>: Measured in hertz and basically the number of audio samples carried per second. Assembly AI supports a wide variety of sample rates, but we want to keep a consistent sample rate between the different services (OpenAI Whisper only supports 16 kHz, hence our sample rate is 16000).</p>
</li>
<li><p><strong>disable_partial_transcripts:</strong> for this application, we don’t need phrases or parts of speech; all we care about is the full transcript. Turning this setting on will minimize the network requests between us and Assembly AI, thus improving performance.</p>
</li>
</ul>
<p>Once we get a transcription back from Assembly AI, we then need to:</p>
<ul>
<li><p>Prompt the LLM to generate a response.</p>
</li>
<li><p>Send the response to the OpenAI TTS model.</p>
</li>
<li><p>Receive the audio from OpenAI TTS and then use FFMpeg to alter the audio so that it sounds similar to a cartoonish voice. OpenAI does not support custom voices, so we are stuck with human voices and therefore need to alter the pitch to sound similar to a cartoon character.</p>
</li>
</ul>
<h2 id="heading-silent-pauses-are-a-pain">Silent pauses are a pain!</h2>
<p>One of the major pain points is the “silent pause” which occurs for a few milliseconds (or seconds in some instances) after the user stops speaking, this is the time taken for the audio transcriber to detect the end of a sentence and also all the AI queries back and forth to generate the final response back to the caller.</p>
<p>This can be annoying since it’s not a fluid conversation that you would typically have with a real person.</p>
<p>Nonetheless, it works pretty decently if you can stomach the annoying pauses.</p>
<p>A few approaches I will look at to improve the code when I get some more time:</p>
<ul>
<li><p>Rebuild in a faster language, I actually already done this partially in Golang. It does make a difference, but it could still do with more optimizations.</p>
</li>
<li><p>Run my own model near the WebSocket server. PHI-3 is fairly good and fast, running this model with Ollama and then fine-tuning could really improve both the speed and accuracy of the AI queries.</p>
</li>
<li><p>Train my own voice model, similar to the above. It might be better to just fine-tune a speech model from scratch. I’ve played around with pocketsphinx and Whisper, both are great options and not complicated to train.</p>
</li>
<li><p>Implement using a speech-to-speech model. These improve response times drastically, but can be quite pricy at this stage.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>While this “toy app” is not much at this stage, it’s useful enough to amuse my kid 😀 and a great base for me to build upon a more commercial offering later on. I see Voice AI growing in the next few years and being very useful in environments such as call centers or for building custom business context-aware agents.</p>
<p>The great news is that the technology gets better every few months, and we are spoiled for choice. At the time of writing, I noticed several new speech-to-speech API’s springing up, including OpenAI’s real-time models. The only caveat is that they are just too expensive to run. Elevenlabs is probably the most cost-effective option at this stage at around $99 per 1000 minutes of audio.</p>
<p>I plan to give Elevenlabs a try, and also Deepgram agents will be interesting as well.</p>
]]></content:encoded></item><item><title><![CDATA[Django vs Laravel! Which to choose for your next project?]]></title><description><![CDATA[I have been writing PHP since I first started programming. It's always been a very flexible and easy-to-use language that grows with you, and coupled with Laravel, you’ve got a gem of beauty, a super-charged modern framework that’s flexible, clean, a...]]></description><link>https://kevincoder.co.za/django-vs-laravel-which-to-choose-for-your-next-project</link><guid isPermaLink="true">https://kevincoder.co.za/django-vs-laravel-which-to-choose-for-your-next-project</guid><category><![CDATA[Laravel]]></category><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><category><![CDATA[PHP]]></category><category><![CDATA[Django VS Laravel]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Wed, 11 Sep 2024 15:11:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726067062564/31e45e3b-3028-41c6-bedd-80fe8c80740c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have been writing PHP since I first started programming. It's always been a very flexible and easy-to-use language that grows with you, and coupled with Laravel, you’ve got a gem of beauty, a super-charged modern framework that’s flexible, clean, and powerful!</p>
<p>Python is cool; it's compact, versatile, and has a broad spectrum of use cases, especially in web development and machine learning. Django is well-designed with batteries included and is one of the most mature frameworks around.</p>
<p>Since I have worked extensively with both frameworks and languages, this gives me a unique perspective on things. In this article, I will give you a general overview of both frameworks to help you choose the right one for your needs.</p>
<h2 id="heading-which-is-better">Which is better?</h2>
<p>Neither, there is no better stack. Both frameworks are an excellent choice for most use cases.</p>
<p>Disappointing? Sorry, I know you came here to get a final verdict on which is better, but, my goal is to give you a deep technical review of features and functionality so that you can make an informed decision and not base your decision solely on hype or opinions.</p>
<h2 id="heading-destroy-the-world">Destroy the world!?</h2>
<p>Before we continue with more framework-related stuff, it's important to understand one of the most fundamental differences between PHP and Python.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726067367216/883276fb-138d-45ac-bc41-43929ef4b1a1.jpeg" alt class="image--center mx-auto" /></p>
<p>PHP spawns up a worker process that loads Laravel "from scratch" on every request, thus the lifecycle for a Laravel app begins when a request is made and ends when the worker closes the request.</p>
<p>The PHP engine does use a JIT compiler and some fancy caching optimizations to make this process fast, so it's not exactly "from scratch" each time, however, if you declare a global variable, this variable gets destroyed when the request is finished.</p>
<p>Python on the other hand loads Django into memory once at startup, thus any global variables that exist on startup will persist throughout every request until the application restarts.</p>
<p>This is why when you change a PHP file, it immediately updates on the next request without the need for a restart. In Django, you will most likely need to restart the Gunicorn workers for the new code to take effect.</p>
<p>This is great for performance since Django has everything it needs already loaded, Django can thus serve up requests much faster. Not so great if you want to run your application in a shared environment though, such as Cpanel hosting, since each Django instance is bootstrapped once, the entire application will sit in memory all the time waiting for requests.</p>
<p>The PHP application on the other hand only uses up resources (besides a small percentage for caching) when there are requests.</p>
<blockquote>
<p>PHP does have alternative runtimes like octane and frankenphp, which work similar to Django's load once in memory approach, however, PHP-FPM is the default and most commonly used runtime in the PHP world.</p>
</blockquote>
<h2 id="heading-package-management-and-setting-up">Package management and setting up</h2>
<p>Package management is a vital component in modern development, you almost always will need to pull down at least one external package in your project at some point in time. Both PHP and Python have excellent package managers.</p>
<p>Python’s most popular package manager is PIP. To use PIP you first need a virtual environment:</p>
<pre><code class="lang-bash">python3 -m venv venv
<span class="hljs-built_in">source</span> venv/bin/activate
</code></pre>
<p>The above commands set up a virtual environment that isolates your project dependencies from the system's Python (a very good idea on Linux! Since the OS heavily relies on Python). Furthermore, you can keep a separate virtual environment for each project.</p>
<p>Now you can run the following to install Django and set up a Django project :</p>
<pre><code class="lang-bash">pip instal django
django-admin startproject myproject
</code></pre>
<p>PHP has a similar setup process, except PHP uses Composer as its package manager which you can download and install from <a target="_blank" href="https://getcomposer.org/">here</a>. You will need to have PHP installed prior. If you are on Mac or Windows, you basically can skip all of these steps and just download <a target="_blank" href="https://herd.laravel.com/windows">herd</a>. Herd is a Laravel-provided toolset that installs everything you need to build a Laravel application, including the relevant DB servers.</p>
<p>Once composer is installed on your system, you can install Laravel as follows:</p>
<pre><code class="lang-bash">composer create-project laravel/laravel myprojectname --prefer-dist
</code></pre>
<p>Composer will automatically configure itself within the "myprojectname" project and create a "vendor" folder which is similar to the virtual environment setup in Python.</p>
<p>The only difference is that the "vendor" folder just contains PHP packages, not an entire PHP runtime.</p>
<p>Python copies the whole Python runtime to the virtual environment, thus it's completely isolated (except for C libraries installed via your package manager).</p>
<p>To install a package via composer:</p>
<pre><code class="lang-bash">composer require vendor/package

//e.g.
composer require guzzle/guzzle
</code></pre>
<h2 id="heading-eloquent">Eloquent</h2>
<p>Laravel has a great ORM called eloquent; it's very close to raw SQL and feels natural for someone like me, who prefers writing SQL. I like using native DB functionality and optimizing queries by hand so I enjoy writing queries using Eloquent.</p>
<p>Here is an example model:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Model</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Product</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{

}
</code></pre>
<p>Eloquent will automatically associate this model with a " products" table in our database. Furthermore, you do not have to explicitly declare every table field because when you create an instance of this class: the fields automatically become available on instances of the class e.g.: "$product-&gt;price".</p>
<p>To start querying the model you can then try the following:</p>
<pre><code class="lang-php">Product::where(<span class="hljs-string">"price"</span>, <span class="hljs-string">"&gt;="</span>, <span class="hljs-number">100</span>)
    -&gt;skip(<span class="hljs-number">5</span>)
    -&gt;take(<span class="hljs-number">10</span>)
    -&gt;get()
</code></pre>
<p>This is an elementary example but should give you an idea of how easy it is to use Eloquent.</p>
<p>The great thing about Laravel, in general, is how flexible the framework is, for example, if you do not want to declare a model class, you could actually just use the database facade directly:</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">DB</span>;
DB::table(<span class="hljs-string">"products"</span>)-&gt;where(<span class="hljs-string">"price"</span>, <span class="hljs-string">"&gt;="</span>, <span class="hljs-number">100</span>)
    -&gt;skip(<span class="hljs-number">5</span>)
    -&gt;take(<span class="hljs-number">10</span>)
    -&gt;get()
</code></pre>
<p>You can also just use regular old RAW SQL as follows:</p>
<pre><code class="lang-php">$products = DB::select(<span class="hljs-string">"select statement here"</span>)
</code></pre>
<h2 id="heading-django-orm">Django ORM</h2>
<p>Django has a similar concept of models, however, you have to explicitly declare each field:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Product</span>(<span class="hljs-params">models.Model</span>):</span>
    name = models.CharField(max_length=<span class="hljs-number">255</span>)
    price = DecimalField(max_digits = <span class="hljs-number">5</span>, decimal_places = <span class="hljs-number">2</span>) 
    visible = models.BooleanField(default=<span class="hljs-literal">False</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self.name
</code></pre>
<p>Similar to Eloquent, the Django ORM will automatically look for a table named "products".</p>
<p>I constantly forget the fields, especially on larger tables, thus I kinda like this way of doing things since my editor will now have table field definitions to provide IntelliSense suggestions.</p>
<p>To query the Django ORM is a bit weird compared to regular SQL, but because Python is so easy to read, it's not that bad at all.</p>
<pre><code class="lang-python">Product.objects.filter(price__gte=<span class="hljs-number">100</span>)[<span class="hljs-number">5</span>:<span class="hljs-number">15</span>]
</code></pre>
<p>To perform raw queries you can use the ".raw" instead of "filter":</p>
<pre><code class="lang-python">query = <span class="hljs-string">"""SELECT * FROM myapp_product WHERE price &gt;= %s 
ORDER BY price LIMIT 10 OFFSET 5"""</span>
products = Product.objects.raw(query, [<span class="hljs-number">100</span>])
</code></pre>
<p>You can also use the connection cursor directly for more advanced queries:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> connection

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_obj_using_raw_sql</span>(<span class="hljs-params">sql, binds</span>):</span>
    <span class="hljs-keyword">with</span> connection.cursor() <span class="hljs-keyword">as</span> cursor:
        cursor.execute(<span class="hljs-string">"sql here"</span>, binds)
        row = cursor.fetchone()

    <span class="hljs-keyword">return</span> row
</code></pre>
<h2 id="heading-migrations">Migrations</h2>
<p>Both frameworks provide great tooling for migrations, in Laravel you have to explicitly write out your migration files:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Migrations</span>\<span class="hljs-title">Migration</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Schema</span>\<span class="hljs-title">Blueprint</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Schema</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateUsersTable</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Migration</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span>(<span class="hljs-params"></span>)
    </span>{
        Schema::create(<span class="hljs-string">'users'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Blueprint $table</span>) </span>{
            $table-&gt;id();
            $table-&gt;string(<span class="hljs-string">'name'</span>);
            $table-&gt;string(<span class="hljs-string">'email'</span>)-&gt;unique();
            $table-&gt;timestamp(<span class="hljs-string">'email_verified_at'</span>)-&gt;nullable();
            $table-&gt;string(<span class="hljs-string">'password'</span>);
            $table-&gt;rememberToken();
            $table-&gt;timestamps();
        });
    }
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">down</span>(<span class="hljs-params"></span>)
    </span>{
        Schema::dropIfExists(<span class="hljs-string">'users'</span>);
    }
}
</code></pre>
<p>Next, run the following command to apply these migrations against the database:</p>
<pre><code class="lang-bash">php artisan migrate
</code></pre>
<p>In Django, since you already declared all fields in the "models.py" file, you do not need to explicitly write out a migration. Simply run:</p>
<pre><code class="lang-bash">python manage.py makemigrations
python migrate
</code></pre>
<p>Every time you update a model, simply repeat the above and Django will automatically update your DB to be in sync with the model.</p>
<h2 id="heading-the-verdict-on-models-amp-migrations">The verdict on models &amp; migrations</h2>
<p>I like Django's model and migration structure, it is such a fluid experience just to declare fields and run two commands versus having to manually write out a migrations file every time.</p>
<p>With Django's ORM, as queries get more advanced, you will need to start using "Q", "annotate", "aggregate" and so forth. It becomes a little cumbersome to work with compared to Eloquent which keeps that more natural "SQL" like feel regardless of how complicated the query gets. Thus, for querying, I prefer Eloquent.</p>
<p>While this is not exactly advanced, it's sufficient to give you an idea:</p>
<pre><code class="lang-php">Product::where(<span class="hljs-string">"price"</span>, <span class="hljs-string">"&gt;="</span>, <span class="hljs-number">200</span>)
    -&gt;orWhere(<span class="hljs-string">"discount"</span>, <span class="hljs-string">"&gt;="</span>, <span class="hljs-string">"20"</span>)
    -&gt;sum(<span class="hljs-string">"price"</span>);
</code></pre>
<p>In Django:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> myapp.models <span class="hljs-keyword">import</span> Product
<span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Q
<span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Sum

products = Product.objects.filter(
     Q(price__gte=<span class="hljs-number">200</span>) | Q(discount__gte=<span class="hljs-number">20</span>)
).aggregate(Sum(<span class="hljs-string">'price'</span>))
</code></pre>
<p>At a glance, it's much easier to understand what's happening in the Eloquent code. Either way, both frameworks handle DB queries quite well and provide enough tooling around DB modeling to suit just about any use case.</p>
<h2 id="heading-templating">Templating</h2>
<p>In Laravel the main template engine is called Blade. Blade is fairly easy to understand:</p>
<pre><code class="lang-php-template"><span class="xml">@if(x == y)
// do something
@else
// do someting else
@endif

My {{$variable}} name.

@foreach ($users as $user)
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is user {{ $user-&gt;id }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
@endforeach

@include("somefolder.some_blade_file")</span>
</code></pre>
<p>Templates are stored in "resources/views" and in your controller, you can reference a template using the path relative to the "views" folder:</p>
<pre><code class="lang-php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doSomething</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> view(<span class="hljs-string">"users.index"</span>, [$user =&gt; <span class="hljs-keyword">$this</span>-&gt;user]);
    <span class="hljs-comment">// Looks for the template in: resources/views/users/index.blade.php</span>
}
</code></pre>
<p>Laravel supports both regular PHP files and blade templates, you can use them interchangeably, therefore if you place a file called "index.php" in the user's folder instead, the above code will work just fine as well.</p>
<p>Furthermore, you can also embed PHP code in Blade as well:</p>
<pre><code class="lang-php">@php $x = <span class="hljs-number">1</span>; @endphp
<span class="hljs-comment">// or</span>
<span class="hljs-meta">&lt;?php</span> $x = <span class="hljs-number">1</span>;<span class="hljs-meta">?&gt;</span>
</code></pre>
<p>Blade is quite powerful and flexible, in fact, I think it's probably the best template engine I have ever worked with! You can Learn more <a target="_blank" href="https://laravel.com/docs/11.x/blade">here</a>, the official Laravel docs are always an excellent resource as well.</p>
<p>Besides Blade, Laravel also supports <a target="_blank" href="https://inertiajs.com/">Intertia</a>, which allows you to write your templates in React and other front-end frameworks.</p>
<p>If you don't want to deal with JS/TypeScript but want interactivity in your views, Blade also can be paired with <a target="_blank" href="https://livewire.laravel.com/">Livewire</a>. Livewire allows you to build React-like views using just PHP, absolutely powerful and easy to use as well!</p>
<p>Finally, Blade can also be extended using custom "Directives" as follows:</p>
<pre><code class="lang-php"><span class="hljs-comment">// In class: App\Providers\AppServiceProvider.php</span>

Blade::directive(<span class="hljs-string">'printFriendlyPostDate'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">$pub_date</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-string">"&lt;?php echo date('j F Y', strtotime(<span class="hljs-subst">$pub_date</span>)); ?&gt;"</span>;
});

<span class="hljs-comment">// Use in your template</span>
@printFriendlyPostDate($post-&gt;pub_date)
</code></pre>
<p>Django templating looks very similar to Blade:</p>
<pre><code class="lang-python">{% <span class="hljs-keyword">if</span> user.is_authenticated %}
    Hello, {{ user.username }}.
{% endif %}

Hi, My name <span class="hljs-keyword">is</span> {{user.name}}

&lt;ul&gt;
{% <span class="hljs-keyword">for</span> link <span class="hljs-keyword">in</span> links_list %}
    &lt;li&gt;&lt;a href=<span class="hljs-string">"{{link.url}}"</span>&gt;{{link.name}}&lt;/a&gt;&lt;/i&gt;
{% endfor %}
&lt;/ul&gt;
</code></pre>
<p>The major difference between the two is that there is no default convention as to where you should put templates, usually, Django developers place templates in a directory called "templates/" but this is configurable.</p>
<p>To be fair, you can also change Blade's default templates directory, but nobody ever does.</p>
<p>In Django to render a template:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span>(<span class="hljs-params">request</span>):</span>
    users = Users.objects.order_by(<span class="hljs-string">"-register_date"</span>)[:<span class="hljs-number">5</span>]
    context = {<span class="hljs-string">"users"</span>: users}
    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">"users/index.html"</span>, context)
</code></pre>
<p>Notice, Django is much more strict, you have to stick to the template syntax and cannot just use regular Python, however, there is a concept of "template tags" which allow you to write your own custom template functions.</p>
<p>Template tags are similar to the Blade directives mentioned above:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Place in users/templatetags/custom_tags.py</span>
<span class="hljs-keyword">from</span> django <span class="hljs-keyword">import</span> template
register = template.Library()

<span class="hljs-meta">@register.filter(name='lower_me')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lower_me</span>(<span class="hljs-params">value</span>):</span>
    <span class="hljs-keyword">return</span> value.lower()

<span class="hljs-comment"># In your template</span>
templates/users/index.html

{% load custom_tags %}

Hi {{name | lower_me}}
</code></pre>
<p>Template tags should be placed in a folder called "templatetags" inside your app folder.</p>
<p>While inertia is optimized for Laravel, it's possible to also use <a target="_blank" href="https://github.com/inertiajs/inertia-django">this</a> third-party open-source adapter to get inertia support in Django. I haven't used it myself so can't say if it is any good, seems very basic compared to the Laravel integration.</p>
<p>If you looking for an alternative to Livewire, there is <a target="_blank" href="https://www.django-unicorn.com/">Django Unicorn</a>. I briefly played around with it, but it lacks a lot of features and is very basic, so probably better just to use HTMLX or React.</p>
<p>Either way, Django's support for these kinds of frontend templating is not great, however, Django templates are super powerful and you can always manually add your own integration with React or Vue or any other frontend stack.</p>
<h2 id="heading-django-is-strict">Django is strict!</h2>
<p>Both frameworks are very opinionated, thus there is always the "Laravel way" or "Django way" of doing things.</p>
<p>Django however is a tad bit more strict and rigid. One example is accessing Auth information:</p>
<pre><code class="lang-php">
Auth::user()
</code></pre>
<p>In Laravel, you can access this Facade just about anywhere in your code, not just in controllers. In Django on the other hand, you have to pass the "request" object down the line manually otherwise other modules in your code will not have access to the user information.</p>
<p>Generally, facades work in this fashion, where you can simply just import the facade anywhere and access any information that the facade has access to.</p>
<p>This flexibility is great! but not always a good thing when it comes to clean code and even security, so Django's rigidity is probably a blessing in disguise. I have seen some weird projects butchering Laravel, whereas the average Django project seems much cleaner and well-structured.</p>
<h2 id="heading-background-jobs">Background jobs</h2>
<p>Both frameworks provide a mechanism for handling background jobs, however, Django is a bit more complicated since you have to set up celery and do some configuration in your app to set up queues.</p>
<p>There are too many steps to cover in this article, but you can read further about how to set up Celery <a target="_blank" href="https://docs.celeryq.dev/en/stable/django/first-steps-with-django.html">here</a>.</p>
<p>In Laravel to create a background job:</p>
<pre><code class="lang-php">php artisan make:job SendEmails
</code></pre>
<p>This adds a new class to "app/Jobs/SendEmails.php". Anytime you need to queue a job, simply invoke the class as follows:</p>
<pre><code class="lang-php">SendEmails::dispatch($some_object_or_data);

<span class="hljs-comment">// OR to push on any other queue besides the default one:</span>
SendEmails::dispatch($some_object_or_data)-&gt;onQueue(<span class="hljs-string">"myqueue"</span>);
</code></pre>
<p>This will now store jobs in Redis or your SQL DB and process them in the background (via a "default" queue). You will just need to set up one daemon process:</p>
<pre><code class="lang-php">php artisan queue:work
<span class="hljs-comment">// you can pass --name to specify which queue if you have more than one.</span>

<span class="hljs-comment">// Or you can use Horizon if you have multiple queues</span>
<span class="hljs-comment">// Install horizon first before running: </span>

<span class="hljs-comment">// --&gt; composer require laravel/horizon</span>
<span class="hljs-comment">// --&gt; php artisan horizon:install</span>
php artisan horizon
</code></pre>
<p>Horizon is an optional tool you can install, it basically gives you a metrics dashboard and will automatically run and manage all your queues instead of manually setting up a daemon for each queue.</p>
<h2 id="heading-routing">Routing</h2>
<p>Both frameworks provide a similar routing mechanism, in Django routes are maintained in a "urls.py" file inside the project folder:</p>
<pre><code class="lang-bash">from django.urls import path, include
from accounts import views

urlpatterns = [
    path(<span class="hljs-string">"user/profile/"</span>, views.my_account),
    path(<span class="hljs-string">"user/&lt;int:id&gt;/"</span>, views.view_user),
    path(<span class="hljs-string">"users/"</span>, views.user_list),

    <span class="hljs-comment"># Group all these URLs with the "admin" prefix</span>
    path(<span class="hljs-string">'admin/'</span>, include(<span class="hljs-string">'some_app_level_urls.admin_urls'</span>)),

    <span class="hljs-comment"># Named routes</span>
    path(<span class="hljs-string">'dashboard/'</span>, views.dashboard, name=<span class="hljs-string">"dashboard"</span>)
]
</code></pre>
<p>Learn more about Django URLs <a target="_blank" href="https://docs.djangoproject.com/en/5.1/topics/http/urls/">here</a>.</p>
<p>In Laravel, there are "routes/web.php" and "routes/api.php" :</p>
<pre><code class="lang-php">
Route::get(<span class="hljs-string">'/search'</span>, <span class="hljs-string">'SearchController@index'</span>);

<span class="hljs-comment"># Named routes</span>
Route::post(<span class="hljs-string">'/searchTerm'</span>, <span class="hljs-string">'SearchController@save'</span>)
    -&gt;name(<span class="hljs-string">"save_search_term"</span>);

<span class="hljs-comment"># Group routes with a prefix</span>
Route::prefix(<span class="hljs-string">'stores'</span>)-&gt;group(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    Route::get(<span class="hljs-string">'/{id}/show'</span>, <span class="hljs-string">'StoresController@show'</span>);
    Route::get(<span class="hljs-string">'/{id}/products'</span>, <span class="hljs-string">'StoresController@products'</span>);
});
</code></pre>
<p>Learn more about Laravel routes <a target="_blank" href="https://laravel.com/docs/11.x/routing">here</a>. Both routers are fairly flexible and easy to use, Laravel does have a little edge since the syntax is declarative using HTTP verbs and you can easily build routes by chaining methods "prefix", "group", "middleware" and more.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Django in terms of design, clean code, and using the HMVT pattern is probably the better-structured framework, plus Python is really easy to work with, easy to learn, and has a vast ecosystem of libraries for just about anything.</p>
<p>Laravel is not just a framework, it’s an entire eco-system with loads of free and paid first-party packages such as Reverb, Pulse, Horizon, Jetstream, Herd, etc…</p>
<p>Thus Laravel’s ecosystem is more complete and will get you up and running fast regardless of the task at hand. Django on the other hand does have batteries included but these are limited and move slowly.</p>
<p>An example is the Django admin, it still looks like an admin panel from the early 2000s and is a bit cumbersome to customize, Laravel on the other hand has many options to choose from and they each look modern with dark theme support and tailwind.</p>
<p>Furthermore, PHP is still one of the most dominant web languages around and constantly evolving to keep up with modern standards.</p>
<p>All in all, both frameworks are a solid choice. If you prefer the “C” style syntax, go with Laravel, if you prefer Python then Django is your best bet.</p>
<p>If you need modern TypeScript and JS framework support, Laravel is probably the better option with Intertia JS and much better tooling, including Livewire.</p>
]]></content:encoded></item><item><title><![CDATA[Golang for Python developers]]></title><description><![CDATA[Python is one of my favorite languages; I use it for various tasks including machine learning, web development, and other general scripts. Usually, Python is more than sufficient for most tasks, however, when it comes to high-performance use cases wh...]]></description><link>https://kevincoder.co.za/golang-for-python-developers</link><guid isPermaLink="true">https://kevincoder.co.za/golang-for-python-developers</guid><category><![CDATA[Golang for python developers]]></category><category><![CDATA[Python]]></category><category><![CDATA[golang]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Fri, 06 Sep 2024 18:39:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1725647862354/95447e6e-e5ad-42b6-8a90-1089794d539f.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Python is one of my favorite languages; I use it for various tasks including machine learning, web development, and other general scripts. Usually, Python is more than sufficient for most tasks, however, when it comes to high-performance use cases when you need to maximize your hardware potential, Python can be rather slow.</p>
<p>This is where Golang shines; it’s a compiled language which means it’s much faster for most tasks compared to Python. Although, similar to Python, it’s fairly easy to learn. The language promotes minimalism and does not include unnecessary bloat even though the standard library is really comprehensive and you often don’t need to reach for third-party libraries.</p>
<p>In this guide, I will give you a general introduction to Golang from the perspective of a Python developer and show you why Golang should be the next language you pick up.</p>
<h2 id="heading-golang-is-compiled">Golang is compiled</h2>
<p>This may seem quite obvious, but it makes a huge difference in having code compiled versus being interpreted. Besides just compiler optimizations that can improve the performance of your code, compiled code also ensures types are enforced correctly, thus reducing the number of possible bugs that land up in production (Off-course unit testing is also a good idea).</p>
<p>Python is both strongly typed and dynamically typed. Take this example for instance:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">doMath</span>(<span class="hljs-params">x, y</span>):</span>
    <span class="hljs-keyword">return</span> x+y
</code></pre>
<p>If you had to run this code, Python will not throw any errors, however, try doing this:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">doMath</span>(<span class="hljs-params">x, y</span>):</span>
    <span class="hljs-keyword">return</span> x+y

print(doMath(<span class="hljs-number">1</span>, <span class="hljs-string">"1"</span>))
</code></pre>
<p>You should see:</p>
<pre><code class="lang-python"><span class="hljs-keyword">return</span> x+y
           ~^~
TypeError: unsupported operand type(s) <span class="hljs-keyword">for</span> +: <span class="hljs-string">'int'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'str'</span>
</code></pre>
<p>Without unit tests or static analysis in your IDE, Python code can create many runtime errors if you are not careful. In Golang, it's much harder to create these sorts of bugs because the program won't even compile, even if you are not calling that function or block of code.</p>
<h2 id="heading-golang-variables-arrays-maps">Golang variables, arrays, maps</h2>
<p>In Golang there are two main ways to create variables:</p>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> someInteger <span class="hljs-keyword">int</span>
# OR
someInteger := <span class="hljs-number">10</span>
</code></pre>
<p>You can explicitly set the type using "var" or you can let Go figure out the type by using this operator: ":=". Either way, once you have declared the variable, you can not change its type.</p>
<p>In Python, the following will work fine, but Golang will throw a type error:</p>
<pre><code class="lang-python">x = <span class="hljs-number">1</span>
x = <span class="hljs-string">"Some string"</span>
</code></pre>
<p>Similar to Python, Golang has a couple of container types:</p>
<pre><code class="lang-go"># Python List: items = [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>]

# Golang slice
items := []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>}

#Python dictionary: carData = {<span class="hljs-string">"make"</span>: <span class="hljs-string">"Toyota"</span>, <span class="hljs-string">"model"</span>: <span class="hljs-string">"Corolla"</span>}

# Golang maps
carData := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>)
carData[<span class="hljs-string">"make"</span>]  = <span class="hljs-string">"Toyota"</span>
carData[<span class="hljs-string">"model"</span>] = <span class="hljs-string">"Corolla"</span>
</code></pre>
<p>In Golang there is no "None" type, however you can simply use "nil" instead. Here are some variable types:</p>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> number <span class="hljs-keyword">int</span>
<span class="hljs-keyword">var</span> number <span class="hljs-keyword">int64</span>
<span class="hljs-keyword">var</span> number <span class="hljs-keyword">float64</span>
<span class="hljs-keyword">var</span> text <span class="hljs-keyword">string</span>
... and most of the common types supported by most languages

<span class="hljs-keyword">var</span> anything <span class="hljs-keyword">interface</span>{}
</code></pre>
<p>An "interface{}" type is a special type that allows you to store any other type in that variable. It does not follow the same rules as regular types, it's a dynamic type, thus the compiler will not throw type errors for this type if you do something like:</p>
<pre><code class="lang-go">    <span class="hljs-keyword">var</span> anything <span class="hljs-keyword">interface</span>{}
    anything = <span class="hljs-string">"Some text"</span>
    anything = <span class="hljs-number">101</span>
    fmt.Println(anything)
</code></pre>
<p>This is terrible Go code and you should avoid using the interface type as far as possible. One common use case for the interface type is handling JSON objects, since JSON can be loosely structured where fields could potentially be a number or text or even an object depending on some condition, you can use this type for these dynamic fields so that the JSON is parsed without throwing type errors.</p>
<p>Here's an example of a dynamic JSON object:</p>
<pre><code class="lang-json">{
   <span class="hljs-attr">"product_name"</span>: <span class="hljs-string">"D-T42D15 42 Dled Television"</span>,
   <span class="hljs-attr">"sku"</span>: <span class="hljs-string">"X42310555"</span>,
   <span class="hljs-attr">"price"</span>: <span class="hljs-number">2999.00</span>,
   <span class="hljs-attr">"currency"</span>: <span class="hljs-string">"ZAR"</span>
}
</code></pre>
<pre><code class="lang-json">{
   <span class="hljs-attr">"product_name"</span>: <span class="hljs-string">"D-T42D15 42 Dled Television"</span>,
   <span class="hljs-attr">"sku"</span>: <span class="hljs-number">18745534</span>,
   <span class="hljs-attr">"price"</span>: <span class="hljs-number">2999.00</span>,
   <span class="hljs-attr">"currency"</span>: <span class="hljs-string">"ZAR"</span>
}
</code></pre>
<p>As you can see "sku" is a string sometimes and other times a number. This will cause typing errors in Golang because the type has to be a number or string, and cannot be both.</p>
<p>Therefore you can initially use an interface, and then in your code figure out what the current items data type is at runtime:</p>
<pre><code class="lang-go"><span class="hljs-comment">// For now, lets assume this function returns the</span>
<span class="hljs-comment">// product in the form of a struct (similar to python objects).</span>
<span class="hljs-comment">// We'll learn about structs in a bit...</span>
product := getTheProduct()

<span class="hljs-comment">// check what the type is and then create a new variable with</span>
<span class="hljs-comment">// the correct type.</span>
theSKU := <span class="hljs-string">""</span>
<span class="hljs-keyword">switch</span> product.SKU.(<span class="hljs-keyword">type</span>) {
    <span class="hljs-keyword">case</span> <span class="hljs-keyword">string</span>:
       theSKU = product.SKU.(<span class="hljs-keyword">string</span>)
    <span class="hljs-keyword">case</span> <span class="hljs-keyword">int</span>:
        theSKU = fmt.Sprintf(<span class="hljs-string">"%d"</span>, product.SKU)
}

fmt.Println(unpackedValue)
</code></pre>
<h2 id="heading-golang-loops-if-switch-and-range">Golang loops, if, switch, and range</h2>
<p>Golang is very minimal, thus the language doesn't have as many reserved keywords as Python. For example, the "while" keyword does not exist! Instead, Golang's for loop is very flexible and can be used for a wide variety of loops that other languages generally separate into different reserved keywords.</p>
<p>Here are some examples:</p>
<pre><code class="lang-go">start := <span class="hljs-number">1</span>
end := <span class="hljs-number">10</span>

<span class="hljs-keyword">for</span> start &lt; end {
    start += <span class="hljs-number">1</span>
    fmt.Println(start)
}

<span class="hljs-comment">// this is the most common Loop. Use when you want to iterate</span>
<span class="hljs-comment">// over lists or maps.</span>
items := []<span class="hljs-keyword">int</span>{<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>}
<span class="hljs-keyword">for</span> k, v := <span class="hljs-keyword">range</span> items {
   fmt.Println(k,v)
}

sum := <span class="hljs-number">0</span>
<span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span>; i &lt; <span class="hljs-number">5</span>; i++ {
    sum += i
}
fmt.Println(sum)

<span class="hljs-comment">// GORM db rows: https://gorm.io/docs/sql_builder.html</span>
rows := db.Rows()

<span class="hljs-keyword">for</span> rows.Next() {
   <span class="hljs-keyword">var</span> name <span class="hljs-keyword">string</span>
    rows.Scan(&amp;name)
    fmt.Println(name)
}
</code></pre>
<p>"If" statements are very similar to Python:</p>
<pre><code class="lang-go">age := <span class="hljs-number">22</span>

<span class="hljs-keyword">if</span> age &lt; <span class="hljs-number">18</span> {
   fmt.Println(num, <span class="hljs-string">"You're still a kid."</span>)
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> age &gt; <span class="hljs-number">18</span> {
   fmt.Println(num, <span class="hljs-string">"You can both drink, and drive. 
     Just not at the same time!"</span>)
} <span class="hljs-keyword">else</span> {
   fmt.Println(num, <span class="hljs-string">"Sorry, but your youth is slowly fading :-("</span>)
}
</code></pre>
<p>In early versions of Python (I think before v3.10), there were no switch statements, however, nowadays you can use "match" and "case". Luckily for Go developers, these exist and work similar to most other modern languages:</p>
<pre><code class="lang-go">age := <span class="hljs-number">22</span>

<span class="hljs-keyword">switch</span> age {
    <span class="hljs-keyword">case</span> <span class="hljs-number">18</span>:
        fmt.Println(<span class="hljs-string">"You're on your way to adulthood."</span>)
    <span class="hljs-keyword">case</span> <span class="hljs-number">65</span>:
        fmt.Println(<span class="hljs-string">"#Retirement"</span>)
}
</code></pre>
<p>In Golang the "break" keyword is not required, Go will automatically break out of the switch on the first positive match. The "break" statement is usually used if your code inside the case block has an IF statement or some other logic that can take multiple pathways.</p>
<h2 id="heading-golang-modules">Golang modules</h2>
<p>Modules are essential in any language; In Python creating modules is a breeze, you just use the directory and file name in your import statement to reference whatever code you need:</p>
<pre><code class="lang-python">myapp/
   accounts/models.py

<span class="hljs-comment"># Becomes</span>
<span class="hljs-keyword">from</span> accounts.models <span class="hljs-keyword">import</span> User
</code></pre>
<p>Golang has a similar mechanism, however, it's a tad bit more complicated. The Go file name is usually irrelevant, instead in Go you need to declare a "package" directive at the top of every code file. To start a new Go project, you first have to run the following in your terminal inside the projects root folder:</p>
<pre><code class="lang-bash">go mod init mycompany.com/ecommerce
</code></pre>
<p>The above will create a "go.mod" file that is essentially your project's package information similar to NPM's "package.json" file. This file will also keep track of all the various dependencies you add to your project.</p>
<p>Next, in every folder where you add a ".go" file, you need to add a "package" directive at the top of the file that's the same name as the parent folder:</p>
<pre><code class="lang-bash"> myapp/
   accounts/models.go
main.go <span class="hljs-comment"># the package for main.go will always be "package main"</span>
</code></pre>
<p>Inside models.go:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> accounts

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">PrintHelloWorld</span><span class="hljs-params">()</span></span> {
    fmt.Println(<span class="hljs-string">"Hello World"</span>)
}
</code></pre>
<p>To import this function in our main.go file:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main
<span class="hljs-keyword">import</span> <span class="hljs-string">"mycompany.com/ecommerce/accounts"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
   accounts.PrintHelloWorld()
}
</code></pre>
<p>Your function or variable inside these modules must start with a capital letter to be visible outside of the module, thus if I named the function "printHelloWorld", Go will throw an "undefined" compile error in our main function.</p>
<blockquote>
<p>ℹ️ If you created another file inside the module folder "accounts" e.g. utils.go with the "package accounts" directive at the top, that code file will still have access to "printHelloWorld".</p>
</blockquote>
<p>To compile a go program you can run the following in your terminal:</p>
<pre><code class="lang-bash">go run main.go

<span class="hljs-comment"># Or if you just want to build the binary without running</span>
 go build -o myapp
</code></pre>
<p>Packages can also be imported from GitHub:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main
<span class="hljs-keyword">import</span> <span class="hljs-string">"github.com/labstack/echo/v4"</span>
</code></pre>
<p>If you have multiple imports, you should group them as follows:</p>
<pre><code class="lang-go"><span class="hljs-keyword">import</span> (
  <span class="hljs-string">"github.com/labstack/echo/v4"</span>
  <span class="hljs-string">"github.com/labstack/echo/v4/middleware"</span>
  <span class="hljs-string">"net/http"</span> # These style packages are from the standard library.
)
</code></pre>
<p>You can also provide an alias for a package if the name is too long to type or if you want to prevent collisions between packages with the same name:</p>
<pre><code class="lang-go"><span class="hljs-keyword">import</span> (
  <span class="hljs-string">"github.com/labstack/echo/v4"</span>
  <span class="hljs-string">"github.com/labstack/echo/v4/middleware"</span>
  comMiddleware <span class="hljs-string">"mycompany.com/ecommerce/middleware"</span>
  <span class="hljs-string">"net/http"</span>
)
</code></pre>
<p>When you add a git URL like the above to your import, you need to run the following go mod command before building or running the program:</p>
<pre><code class="lang-bash">go mod tidy
</code></pre>
<p>This is similar to pip, however, you do not need to have a "requirements.txt" file or need to specify each dependency individually. Go's package manager will automatically scan all your code files, look up the URL for that package, and download the relevant code.</p>
<h2 id="heading-dependencies-what-dependencies">Dependencies! What dependencies?</h2>
<p>One common issue with Python is system libraries; when you run "pip install xyz", this just installs the Python library but you may still need to manage the system libraries (and also have Python installed on the target machine) via APT or whatever software installer is used by the target machine.</p>
<p>Golang on the other hand, usually does not need any dependencies installed on the target machine, you don't even need the Go compiler installed. Go will automatically generate a self-contained binary that contains everything you need to run the application.</p>
<p>If you have static assets like images or HTML templates, you would need to copy those as well to the target machine, however, Go also provides an embedder that can package everything including static assets into a single binary file.</p>
<p>When building Go binaries you can also specify what machine architecture you want to target via a simple ENV:</p>
<pre><code class="lang-bash">GOOS=windows go build
</code></pre>
<h2 id="heading-oop-is-not-a-thing-in-go">OOP is not a thing in Go</h2>
<p>Golang does not work like other OOP languages, there are no classes and objects. Instead, Go has a nifty little "container" type called "structs". They are very similar to classes but much more lightweight.</p>
<p>Here's an example of a basic “struct”:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-keyword">type</span> Product <span class="hljs-keyword">struct</span> {
    ID       <span class="hljs-keyword">int64</span>
    Name     <span class="hljs-keyword">string</span>
    Sku      <span class="hljs-keyword">string</span>
    Category <span class="hljs-keyword">string</span>
    Price    <span class="hljs-keyword">float64</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    product := Product{
        ID:       <span class="hljs-number">123</span>,
        Name:     <span class="hljs-string">"iPhone 14 pro"</span>,
        Sku:      <span class="hljs-string">"X41233"</span>,
        Price:    <span class="hljs-number">899</span>,
        Category: <span class="hljs-string">"Smartphones"</span>,
    }

    fmt.Println(product)
}
</code></pre>
<blockquote>
<p>ℹ️ Similar to modules, you need to capitalize the first letter of the struct field name otherwise that field will not be usuable on "instances" of the struct.</p>
</blockquote>
<p>You can also assign values to struct "instance" one at a time:</p>
<pre><code class="lang-go">    product := Product{}
    product.ID = <span class="hljs-number">123</span>
    product.Name = <span class="hljs-string">"iPhone 14 pro"</span>
    product.Sku = <span class="hljs-string">"X41233"</span>
    product.Price = <span class="hljs-number">899</span>
    product.Category = <span class="hljs-string">"Smartphones"</span>
</code></pre>
<blockquote>
<p>I keep using quotes around the word instances to emphasize that these are not objects, structs are just containers or collections, they are meant to be lean and efficient.</p>
</blockquote>
<p>Structs can also have functions attached to them similar to methods in a class:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-keyword">type</span> Product <span class="hljs-keyword">struct</span> {
    ID       <span class="hljs-keyword">int64</span>
    Name     <span class="hljs-keyword">string</span>
    Sku      <span class="hljs-keyword">string</span>
    Category <span class="hljs-keyword">string</span>
    Price    <span class="hljs-keyword">float64</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *Product)</span> <span class="hljs-title">getPrice</span><span class="hljs-params">()</span> <span class="hljs-title">float64</span></span> {
    <span class="hljs-keyword">return</span> p.Price
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *Product)</span> <span class="hljs-title">updatePrice</span><span class="hljs-params">(newPrice <span class="hljs-keyword">float64</span>)</span></span> {
    p.Price = newPrice
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    product := Product{}
    product.ID = <span class="hljs-number">123</span>
    product.Name = <span class="hljs-string">"iPhone 14 pro"</span>
    product.Sku = <span class="hljs-string">"X41233"</span>
    product.Price = <span class="hljs-number">899</span>
    product.Category = <span class="hljs-string">"Smartphones"</span>

    product.updatePrice(<span class="hljs-number">999</span>)
    fmt.Println(product.getPrice())
}
</code></pre>
<p>Note: The same rules apply with function names, if the first letter is not capitalized, the function will not be visible outside of the module it is declared in.</p>
<p>As you build more Go programs, you'll realize that conventional OOP is overrated and Go's minimal approach with structs just makes your code much cleaner and easier to maintain.</p>
<p>You'll notice I skipped past one piece of this code, the "*" character in front of "Product". This essentially is a pointer, instead of copying the product "instance" every time, we pass around a memory location to the original "instance".</p>
<p>Everything in Python is an object, thus the language is already doing this under the hood (passing by reference) so you never need to think about pointers in Python. Golang is just a bit more explicit allowing you to fine-grade control to optimize your code for maximum efficiency.</p>
<p>You can learn more about pointers in Golang and when to use them <a target="_blank" href="https://google.github.io/styleguide/go/decisions#receiver-type">here</a>.</p>
<h2 id="heading-concurrency">Concurrency</h2>
<p>In Python, "asyncio" can be used to achieve concurrency and speed up your program's execution time. It works great but has a little bit of a learning curve, not too complicated but not as natural as Go's goroutines.</p>
<p>In Golang, you can simply do this:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"time"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">doSomeWork</span><span class="hljs-params">()</span></span> {
    time.Sleep(time.Second * <span class="hljs-number">5</span>)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">go</span> doSomeWork()
}
</code></pre>
<p>How simple is that? Just add "go" in front of any function call to make that task run in the background.</p>
<p>Okay, but just one caveat here! If your main program exits before the goroutine finishes, the function will be terminated prematurely. However, usually in Go, your main program would be some kind of background service like an HTTP server, in which case this is not an issue, since the server is always running.</p>
<p>An example HTTP server:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"log"</span>
    <span class="hljs-string">"net/http"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">doSomeWork</span><span class="hljs-params">()</span></span> {
    fmt.Println(<span class="hljs-string">"Hello world from goroutine"</span>)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    http.HandleFunc(<span class="hljs-string">"/api/endpoint"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, 
    r *http.Request)</span></span> {
        <span class="hljs-keyword">go</span> doSomeWork()
        fmt.Fprintf(w, <span class="hljs-string">"Hello, World"</span>)
    })

    log.Fatal(http.ListenAndServe(<span class="hljs-string">":8080"</span>, <span class="hljs-literal">nil</span>))
}
</code></pre>
<p>Since "http.ListenAndServe" basically blocks the program from exiting and keeps it in memory forever (unless of course, some fatal error kills the program), you can safely run the goroutine with minimal risk of it not finishing.</p>
<p>If you are running a program that will exit once it finishes all tasks, like a scraper, for example, you will need to stop and wait for all goroutines to finish. You can do this using wait groups:</p>
<pre><code class="lang-go"><span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"sync"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">doSomeWork</span><span class="hljs-params">(wg *sync.WaitGroup)</span></span> {
    <span class="hljs-keyword">defer</span> wg.Done()
    fmt.Println(<span class="hljs-string">"Hello world from goroutine"</span>)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> wg sync.WaitGroup
    <span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span>; i &lt; <span class="hljs-number">5</span>; i++ {
        wg.Add(<span class="hljs-number">1</span>)
        <span class="hljs-keyword">go</span> doSomeWork(&amp;wg)
    }

    wg.Wait()
}
</code></pre>
<p>In the above code sample, we first create a wait group:</p>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> wg sync.WaitGroup
</code></pre>
<p>Next, in our loop, we "tell" our wait group how many processes we are going to add to the queue, in this case, we just run one goroutine but you can run several more depending on your needs.</p>
<pre><code class="lang-go">wg.Add(<span class="hljs-number">1</span>)
<span class="hljs-keyword">go</span> doSomeWork(&amp;wg)
</code></pre>
<p>Notice that we also pass "&amp;wg" to the "doSomeWork" function. This is essentially passing the "wait group" by reference so that there is only one "instance" of the "wait group" regardless of how many goroutines we run.</p>
<p>See the power of pointers? We are maintaining a singleton, and thus ensuring the counter is always up to date and accurately matches the number of goroutines we spin up.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">doSomeWork</span><span class="hljs-params">(wg *sync.WaitGroup)</span></span> {
    <span class="hljs-keyword">defer</span> wg.Done()
    fmt.Println(<span class="hljs-string">"Hello world from goroutine"</span>)
}
</code></pre>
<p>In the "doSomeWork" function as expected, we accept a pointer to the "wait group". If you look closely we use "defer" to tell the "wait group" that the function is complete and it should update the counter accordingly.</p>
<p>The "defer" keyword in Golang simply ensures that this line of code is always executed last just before the function finishes, regardless of where you call "defer" in your code.</p>
<p>You can have only one "defer" per function, however, "defer" can also run more than one line of code. Simply wrap your code in a nested function:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">doSomeWork</span><span class="hljs-params">(wg *sync.WaitGroup)</span></span> {
    <span class="hljs-keyword">defer</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
      wg.Done()
      fmt.Println(<span class="hljs-string">"I am done"</span>)
    }() <span class="hljs-comment">// &lt;-- We use () to actually execute the function as</span>
       <span class="hljs-comment">//      soon as defer is executed.</span>

    fmt.Println(<span class="hljs-string">"Hello world from goroutine"</span>)
}
</code></pre>
<p>Coming back to our main code, we call "wg.Wait()" at the end of the main function to stop the program from exiting and wait until all goroutines finish.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> wg sync.WaitGroup
    <span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span>; i &lt; <span class="hljs-number">5</span>; i++ {
        wg.Add(<span class="hljs-number">1</span>)
        <span class="hljs-keyword">go</span> doSomeWork(&amp;wg)
    }

    wg.Wait()  <span class="hljs-comment">// &lt;--------------</span>
}
</code></pre>
<h2 id="heading-handling-errors">Handling errors</h2>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"strconv"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> price <span class="hljs-keyword">float64</span>
    price, err := strconv.ParseFloat(<span class="hljs-string">"hello"</span>, <span class="hljs-number">64</span>)
    fmt.Println(price, err)
}
</code></pre>
<p>In most languages, you would expect this program to crash with some kind of "NaN" exception, in Python usually you would wrap this sort of logic in a "try...except" block so that you can safely catch the error and gracefully handle the error without crashing the whole program.</p>
<p>Go, on the other hand, does not have a "try...except" block, instead when an error occurs the function simply returns a struct containing more details about the error.</p>
<p>if "err" is nil, then your code executed fine without any errors and you can continue as normal. This may seem odd, but it's actually quite a nice way of handling errors and it keeps your code clean.</p>
<p>You can even choose to ignore the error if you want (although this is usually a bad idea):</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"strconv"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> price <span class="hljs-keyword">float64</span>
    price, _ := strconv.ParseFloat(<span class="hljs-string">"hello"</span>, <span class="hljs-number">64</span>)
    fmt.Println(price, err)
}
</code></pre>
<p>The "_" character used in this fashion discards the return value and can be used with any function return type not just errors.</p>
<p>Should you do something silly like this, Go will just refuse to compile the program:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    result := <span class="hljs-number">1</span> / <span class="hljs-number">0</span>
}
</code></pre>
<p>Go can throw serious errors known as "panics" which will crash your program, however, these are not common and usually occur when something bad happens outside of your program, e.g. you have an SSD failure and Go cannot write to disk.</p>
<p>Dealing with this kind of error is beyond the scope of this article, but I will cover recovering from serious errors in an upcoming article where we'll take an even deeper dive into the land of Go.</p>
<h2 id="heading-golang-pointers">Golang pointers</h2>
<p>In the Python world, we are used to passing data by reference since everything in Python is an Object. Thus, you don’t really think about pointers a whole lot, but in Golang not everything is an object, you often have to explicitly tell the compiler how to pass data around.</p>
<p><strong>What is a pointer anyway?</strong> Simply put, a pointer is just a variable that contains the address for another complex structure(like a file, object, or something else that holds a value).</p>
<p>To expand on that idea:</p>
<pre><code class="lang-python">brands_list = <span class="hljs-literal">None</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">"brands.txt"</span>, <span class="hljs-string">"r"</span>) <span class="hljs-keyword">as</span> f:
    brands_list = f.read()

<span class="hljs-keyword">for</span> product <span class="hljs-keyword">in</span> products:
    parseProduct(product, brands_list)
</code></pre>
<p>Let’s just say for some bizarre reason you need to load a list of brands from a text file, and that list is needed by a function “parseProduct” which probably parses a raw product object and stores it in the DB. During that process, it needs to access the brands list.</p>
<p>Let’s say we have 1000 products, if we had to read this file at each iteration, that’s going to be inefficient and slow because there’s the disk IO and also the operation of constantly copying 5GB to RAM X 1000. A better approach is to read the file once and then pass the data to “parseProduct”.</p>
<p>Luckily, Python passes by reference, so the memory hit is only 5GB. If Python didn’t pass by reference, you now will have each copy holding 5GB of memory in RAM until garbage collection kicks in.</p>
<p>Golang, on the other hand, will not pass by reference by default:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> Product <span class="hljs-keyword">struct</span> {
    Name <span class="hljs-keyword">string</span>
    <span class="hljs-comment">// other fields</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">parseProduct</span><span class="hljs-params">(product Product, brands <span class="hljs-keyword">string</span>)</span></span> {
    <span class="hljs-comment">// do stuff with product and brands</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    products := getProducts()

    <span class="hljs-comment">// ReadFile is a custom function used to keep this code less verbose.</span>
    brands := ReadFile(<span class="hljs-string">"brands.txt"</span>)


    <span class="hljs-keyword">for</span> _, product := <span class="hljs-keyword">range</span> products {
        parseProduct(product, brands)
    }
}
</code></pre>
<p>At each iteration, Golang is essentially copying the contents of brands and passing it to the “parseProduct” function, so that’s potentially 10GB of RAM being used, more than double what Python used.</p>
<p>To fix this:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">parseProduct</span><span class="hljs-params">(product Product, brands *<span class="hljs-keyword">string</span>)</span></span> {
    <span class="hljs-comment">// do stuff with product and brands</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    products := getProducts()

    <span class="hljs-comment">// ReadFile is a custom function used to keep this code less verbose.</span>
    brands := ReadFile(<span class="hljs-string">"brands.txt"</span>)


    <span class="hljs-keyword">for</span> _, product := <span class="hljs-keyword">range</span> products {
        parseProduct(product, &amp;brands)
    }
}
</code></pre>
<p>Whenever you put a “*” in front of a type in Golang, that makes the variable a pointer. This variable therefore is not actually a string, it’s a memory address of data that is of a “string” type.</p>
<p>Now, we only have one copy of “brands” in memory, and every time “parseProduct” runs, it simply references the initial variable we created(“brands”) instead of repetitively copying the entire file:</p>
<pre><code class="lang-go">    brands := ReadFile(<span class="hljs-string">"brands.txt"</span>)
</code></pre>
<p>The “&amp;” basically gets the memory address of the “brands” variable and passes it to the “parseProduct” function, whereas “*” is just declaring that this variable is a pointer of type “string”.</p>
<blockquote>
<p>⚠️ When using concurrency, mutating data on a pointer can lead to memory leaks and other race conditions. This topic is beyond the scope of the current article, but, if you want to learn more, I did cover this subject in an earlier article <a target="_blank" href="https://kevincoder.co.za/what-is-a-mutex-in-golang">here</a>.</p>
</blockquote>
<h3 id="heading-when-to-use-pointers">When to use pointers?</h3>
<p><em>This is a topic on its own, but here are some of the main use cases for pointers:</em></p>
<ul>
<li><p>To maintain a singleton. For example, in our “parseProduct” function we are probably updating a PostgreSQL database table, it’s going to be really inefficient to open and close a DB connection 1000 times. Instead, we can open the DB connection once before the loop and then pass the pointer to that connection so that it can be reused.</p>
</li>
<li><p>Memory optimization as per the example covered above.</p>
</li>
<li><p>Mutability: Throughout the process, you may mutate data several times. It can become really cumbersome to constantly ensure you return the mutated version, thus using a pointer will make your code a lot cleaner even if there are no major memory optimization advantages.</p>
</li>
</ul>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-keyword">type</span> Product <span class="hljs-keyword">struct</span> {
    Name     <span class="hljs-keyword">string</span>
    Brand    <span class="hljs-keyword">string</span>
    Category <span class="hljs-keyword">string</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *Product)</span> <span class="hljs-title">setBrand</span><span class="hljs-params">(brandName <span class="hljs-keyword">string</span>)</span></span> {
    p.Brand = brandName
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p *Product)</span> <span class="hljs-title">setCategory</span><span class="hljs-params">(CategoryName <span class="hljs-keyword">string</span>)</span></span> {
    p.Category = CategoryName
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">parseProduct</span><span class="hljs-params">(product *Product, brands *<span class="hljs-keyword">string</span>)</span></span> {
    <span class="hljs-comment">// do stuff with product and brands</span>

    product.setBrand(<span class="hljs-string">"Some brand"</span>)
    product.setCategory(<span class="hljs-string">"Some Category"</span>)
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    products := []Product{}
    products = <span class="hljs-built_in">append</span>(products, Product{
        Name: <span class="hljs-string">"Iphone 14"</span>,
    })
    brands := <span class="hljs-string">""</span>

    <span class="hljs-keyword">for</span> _, product := <span class="hljs-keyword">range</span> products {
        parseProduct(&amp;product, &amp;brands)

        fmt.Println(product.Brand, product.Category)
    }
}
</code></pre>
<p>And here’s the none-pointer version, still valid code but a tad bit more annoying to constantly return “product”:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-keyword">type</span> Product <span class="hljs-keyword">struct</span> {
    Name     <span class="hljs-keyword">string</span>
    Brand    <span class="hljs-keyword">string</span>
    Category <span class="hljs-keyword">string</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p Product)</span> <span class="hljs-title">setBrand</span><span class="hljs-params">(brandName <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">Product</span></span> {
    p.Brand = brandName
    <span class="hljs-keyword">return</span> p
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(p Product)</span> <span class="hljs-title">setCategory</span><span class="hljs-params">(categoryName <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">Product</span></span> {
    p.Category = categoryName
    <span class="hljs-keyword">return</span> p
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">parseProduct</span><span class="hljs-params">(product Product, brands *<span class="hljs-keyword">string</span>)</span> <span class="hljs-title">Product</span></span> {
    <span class="hljs-comment">// do stuff with product and brands</span>

    product = product.setBrand(<span class="hljs-string">"Some brand"</span>)
    product = product.setCategory(<span class="hljs-string">"Some Category"</span>)

    <span class="hljs-keyword">return</span> product
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    products := []Product{}
    products = <span class="hljs-built_in">append</span>(products, Product{
        Name: <span class="hljs-string">"Iphone 14"</span>,
    })
    brands := <span class="hljs-string">""</span>

    <span class="hljs-keyword">for</span> _, product := <span class="hljs-keyword">range</span> products {
        updatedProduct := parseProduct(product, &amp;brands)

        fmt.Println(updatedProduct.Brand, updatedProduct.Category)
    }
}
</code></pre>
<h2 id="heading-further-learning-resources">Further learning resources</h2>
<p>If you find Golang interesting and want to expand your knowledge further, here are some excellent resources to help you:</p>
<ul>
<li><p>The <a target="_blank" href="https://go.dev/learn/">official Golang</a> docs are an excellent place to start.</p>
</li>
<li><p>Once you’ve mastered the basics, <a target="_blank" href="https://go.dev/doc/effective_go">Effective Go</a> is another essential guide I highly recommend.</p>
</li>
<li><p>If you are more of a visual learner, I recommend looking at <a target="_blank" href="https://www.youtube.com/@anthonygg_">Anything GG’s YouTube channel</a>. He’s a highly experienced developer and has a wide variety of content for both beginners and experienced devs too.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Whoa! I have so much to share but this article has gotten way too long already. Please consider subscribing to my newsletter and you'll get notified as soon as the next article drops where we go deeper into Golang and learn more advanced concepts.</p>
<p>Golang is a beautiful well-designed language, and as you've seen, it's fairly English-like with very little noise, making it an ideal language to grow into coming from a Python background.</p>
<p>Hopefully, you've learned a thing or two from this article and will keep exploring Go further. Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[🐍 Answer the phone! with Python]]></title><description><![CDATA[Python is a powerful language that can do many things. Most people use it for machine learning or web development, but did you know that it can also interact with hardware and other services like SIP?

💡 Check out my newest article on how to build a...]]></description><link>https://kevincoder.co.za/answer-the-phone-with-python</link><guid isPermaLink="true">https://kevincoder.co.za/answer-the-phone-with-python</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[Python]]></category><category><![CDATA[SIP]]></category><category><![CDATA[voip]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Sun, 07 Jul 2024 14:14:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720361563808/5a5b84c7-f478-43f5-9a56-9348455ba641.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Python is a powerful language that can do many things. Most people use it for machine learning or web development, but did you know that it can also interact with hardware and other services like SIP?</p>
<blockquote>
<p>💡 <strong>Check out my newest article on how to build a</strong> <a target="_blank" href="https://kevincoder.co.za/how-i-used-voice-ai-to-bring-imaginary-characters-to-life"><strong>WebRTC phone in Python here</strong></a><strong>. With WebRTC, you no longer need PyVoip or a SIP provider, accept calls directly from the browser.</strong></p>
</blockquote>
<p>In this article, I am going to walk you through a step-by-step guide on how to build a basic SIP phone and connect that to AI, so that you can have a 2-way conversation with any LLM.</p>
<p><a target="_blank" href="https://learnpython.com/?tap_a=131378-305e0d&amp;ref=ngm5zjv"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1745423056061/172d7634-71ae-45b1-97ed-c1f552df3aef.jpeg" alt class="image--center mx-auto" /></a></p>
<h2 id="heading-what-is-sip-anyway">What is SIP Anyway?</h2>
<p>Similar to how we have HTTP for the web, voice-over-internet systems usually run on a protocol named SIP, which provides guidelines on how to establish, modify, and terminate sessions over the network.</p>
<p>A SIP session can then carry voice, text, or even video. In the case of our application, SIP is just a signaling protocol and, therefore, is responsible for connecting and disconnecting the call to our Python script.</p>
<p>Once the call is answered and established, we then use the "RTP" or Real-time Transport Protocol to handle the audio stream.</p>
<p>Thankfully with PyVoIP, the library takes care of all the SIP and streaming mechanics, thus we don't have to worry too much about how SIP works or RTP for that matter.</p>
<h2 id="heading-lets-build-something-cool">Let's build something cool!?</h2>
<blockquote>
<p>💡 Are you new to Python? Learn Python with <a target="_blank" href="https://learnpython.com/?tap_a=131378-305e0d&amp;ref=ngm5zjv">LearnPython.com</a> courses. Fun interactive courses that are based on real-life business scenarios, meaning you’ll be writing Python code and seeing the results instantly. <em>No need to install Python or other tools on your device, everything happens through your favorite web browser</em> (Sponsored content).</p>
</blockquote>
<p>In this guide, I will show you how to build a simple phone answering service with Python.</p>
<p>The script will do the following:</p>
<ol>
<li><p>Register as a SIP VOIP phone and wait for calls.</p>
</li>
<li><p>Accept incoming calls.</p>
</li>
<li><p>Transcribe the audio using OpenAI's Whisper.</p>
</li>
</ol>
<h2 id="heading-installing-pip-packages">Installing pip packages</h2>
<p>We are going to need a few PIP packages as follows:</p>
<pre><code class="lang-bash">pip install pyVoIP
pip install pywav
pip install openai
</code></pre>
<p>Be sure to also add your OpenAI key to your environment, in bash you can easily do this by doing the following:</p>
<pre><code class="lang-bash">nano ~/.bashrc

<span class="hljs-comment"># Add to the end of the file</span>
<span class="hljs-built_in">export</span> OPENAI_API_KEY=<span class="hljs-string">"sk-xxx"</span>
</code></pre>
<p>You will need to restart your terminal for this to take effect.</p>
<h2 id="heading-setting-up-a-voip-virtual-phone">Setting up a VOIP virtual phone</h2>
<p>PyVoIP is a nifty little library that can easily help you set up a virtual phone with just a few lines of code.</p>
<blockquote>
<p>ℹ️ You probably want to use something like Twilio instead for a real-world application. PyVoIP audio quality isn't the best and needs quite a bit of modification to work correctly.</p>
</blockquote>
<p>To get started, let's set up a basic phone:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> pyVoIP.VoIP <span class="hljs-keyword">import</span> VoIPPhone, CallState

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">answer</span>(<span class="hljs-params">call</span>):</span>
    <span class="hljs-keyword">try</span>:
        call.answer()

    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        print(e)
    <span class="hljs-keyword">finally</span>:
       call.hangup()

vp = VoIPPhone(
    <span class="hljs-string">'sip domain'</span>, <span class="hljs-number">5060</span>, <span class="hljs-string">'sipuser'</span>, 
    <span class="hljs-string">'sippassword'</span>, callCallback=answer
)
vp.start()
print(vp._status)
input(<span class="hljs-string">"Press any key to exit the VOIP phone session."</span>)
vp.stop()
</code></pre>
<p>In this example, we create a virtual phone using the "VoiPPhone" class. This class takes in a few arguments as follows:</p>
<ul>
<li><p><strong>SIP Credentials</strong>: When you purchase a SIP account from a VOIP provider, you should have received a username, password, and an IP or domain name that will be connected to a phone number. (<a target="_blank" href="http://3Cx.com">3Cx.com</a> is an example of a SIP provider).</p>
</li>
<li><p><strong>callCallback</strong>: This is the function that will handle answering the phone call.</p>
</li>
</ul>
<p>The callback function will receive one argument, i.e. the "call" object which will contain all the relevant information relating to the caller and provide various methods for you to accept and receive or send audio back to the caller.</p>
<blockquote>
<p>ℹ️ Did you know that you can build your own VOIP server as well? Asterisk is a powerful open-source VOIP server that you can use to set up your own SIP accounts, phone numbers, extensions, and so forth.</p>
</blockquote>
<h2 id="heading-transcribing-audio">Transcribing audio</h2>
<p>To convert audio into text we can use OpenAI's Whisper service, here's a simple example of how to convert our audio into text:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> openai <span class="hljs-keyword">import</span> OpenAI
<span class="hljs-keyword">import</span> pywav

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">convert_to_wav</span>(<span class="hljs-params">audio, tmpFileName</span>):</span>
    data_bytes = <span class="hljs-string">b""</span>.join(audio)
    wave_write = pywav.WavWrite(tmpFileName, <span class="hljs-number">1</span>, <span class="hljs-number">8000</span>, <span class="hljs-number">8</span>, <span class="hljs-number">7</span>)
    wave_write.write(data_bytes)
    wave_write.close()

    <span class="hljs-keyword">return</span> open(tmpFileName, <span class="hljs-string">"rb"</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transcribe_to_text</span>(<span class="hljs-params">audio_file</span>):</span>
    tmpFileName = <span class="hljs-string">f"/tmp/audio/_audio_buffer_<span class="hljs-subst">{uuid.uuid4()}</span>.wav"</span>
    client = OpenAI()

    transcription = client.audio.transcriptions.create(
        model=<span class="hljs-string">"whisper-1"</span>,
        file=convert_to_wav(audio_file, tmpFileName)
    )

    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">return</span> transcription.text
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> ex:
       print(ex)

    <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>
</code></pre>
<p>The "transcribe_to_text" function takes in a list of raw audio byte samples, we then need to convert those samples into an actual audio file because the OpenAI SDK is expecting a file object, not raw audio bytes.</p>
<p>We therefore use "pywav" in our "convert_to_wav" function to convert the raw audio bytes into a ".wav" audio file.</p>
<blockquote>
<p>⚠️ This logic is simplified so that it's easier to understand, but essentially it can be optimized to remove the need for saving to a temp file since disk IO on a slow drive might cause issues.</p>
</blockquote>
<h2 id="heading-updating-our-answer-method-to-chunk-the-audio">Updating our answer method to chunk the audio</h2>
<p>In our "answer" method we receive the audio as a continuous stream of bytes, therefore each chunk of audio is 20ms. We cannot send a 20ms chunk of audio to Whisper because the minimum length is 100ms.</p>
<p>Thus, we need to append the audio to a buffer and we'll only send the audio to Whisper once we reach 1000ms (or 1 second).</p>
<p>Here is the updated "answer" function:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">answer</span>(<span class="hljs-params">call</span>):</span>
    <span class="hljs-keyword">try</span>:
        call.answer()
        buffer = []
        buff_length = <span class="hljs-number">0</span>

        <span class="hljs-keyword">while</span> call.state == CallState.ANSWERED:
            audio = call.read_audio()
            <span class="hljs-comment"># We divide by 8 because the audio sample rate is 8000 Hz</span>
            buff_length += len(audio) / <span class="hljs-number">8</span> <span class="hljs-comment"># or simply 20</span>

            <span class="hljs-keyword">if</span> buff_length &lt;= <span class="hljs-number">1000</span>:
                buffer.append(audio)
            <span class="hljs-keyword">else</span>:
                print(transcribe_to_text(buffer))
                buffer = []
                buff_length = <span class="hljs-number">0</span>

    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        print(e)
    <span class="hljs-keyword">finally</span>:
       call.hangup()
</code></pre>
<blockquote>
<p>💡 You can also send back audio to the caller by calling "call.write_audio(raw_audio_bytes_here)"</p>
</blockquote>
<h2 id="heading-the-full-code">The full code</h2>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> pyVoIP.VoIP <span class="hljs-keyword">import</span> VoIPPhone, CallState
<span class="hljs-keyword">import</span> uuid
<span class="hljs-keyword">from</span> openai <span class="hljs-keyword">import</span> OpenAI
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> pywav

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">convert_to_wav</span>(<span class="hljs-params">audio, tmpFileName</span>):</span>
    data_bytes = <span class="hljs-string">b""</span>.join(audio)
    wave_write = pywav.WavWrite(tmpFileName, <span class="hljs-number">1</span>, <span class="hljs-number">8000</span>, <span class="hljs-number">8</span>, <span class="hljs-number">7</span>)
    wave_write.write(data_bytes)
    wave_write.close()

    <span class="hljs-keyword">return</span> open(tmpFileName, <span class="hljs-string">"rb"</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transcribe_to_text</span>(<span class="hljs-params">audio_file</span>):</span>
    tmpFileName = <span class="hljs-string">f"/tmp/audio/_audio_buffer_<span class="hljs-subst">{uuid.uuid4()}</span>.wav"</span>
    client = OpenAI()

    transcription = client.audio.transcriptions.create(
        model=<span class="hljs-string">"whisper-1"</span>,
        file=convert_to_wav(audio_file, tmpFileName)
    )

    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">return</span> transcription.text
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> ex:
       print(ex)
    <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">answer</span>(<span class="hljs-params">call</span>):</span>
    <span class="hljs-keyword">try</span>:
        call.answer()
        buffer = []
        buff_length = <span class="hljs-number">0</span>

        <span class="hljs-keyword">while</span> call.state == CallState.ANSWERED:
            audio = call.read_audio()
            buff_length += len(audio) / <span class="hljs-number">8</span>

            <span class="hljs-keyword">if</span> buff_length &lt;= <span class="hljs-number">1000</span>:
                buffer.append(audio)
            <span class="hljs-keyword">else</span>:
                print(transcribe_to_text(buffer))
                buffer = []
                buff_length = <span class="hljs-number">0</span>

    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        print(e)
    <span class="hljs-keyword">finally</span>:
       call.hangup()

vp = VoIPPhone(<span class="hljs-string">'xxx'</span>, <span class="hljs-number">5060</span>, <span class="hljs-string">'xxx'</span>, <span class="hljs-string">'xxx'</span>, callCallback=answer)
vp.start()
print(vp._status)
input(<span class="hljs-string">"Press any key to exist"</span>)
vp.stop()
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>There you have it! A simple phone answering system that can stream live audio and transcribe that audio into text.</p>
<p>As mentioned earlier, PyVoIP is not the best tool for the job since it doesn't handle background noise and static very well. You would need to write some logic to strip out the bad audio samples first before transcribing for an actual real-world application, but hopefully, this is a good start.</p>
]]></content:encoded></item><item><title><![CDATA[Machine learning for web developers]]></title><description><![CDATA[As someone who works 90% of the time on web-related projects; data science, and machine learning were not my core competencies or areas of interest for most of my career.
In the past 2 years, machine learning has become a vital component of my toolbo...]]></description><link>https://kevincoder.co.za/machine-learning-for-web-developers</link><guid isPermaLink="true">https://kevincoder.co.za/machine-learning-for-web-developers</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[webdev]]></category><category><![CDATA[huggingface]]></category><category><![CDATA[vector embeddings]]></category><category><![CDATA[Retrieval-Augmented Generation]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Tue, 07 May 2024 06:42:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715064358055/46ea783a-8bab-440f-a3f5-877ea136c4b1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As someone who works 90% of the time on web-related projects; data science, and machine learning were not my core competencies or areas of interest for most of my career.</p>
<p>In the past 2 years, machine learning has become a vital component of my toolbox as a developer.</p>
<p>Initially, the landscape was wild! We dealt with Keras, PyTorch, Tensorflow, and many other tools. Luckily, since the advent of ChatGPT and Sentence Transformers, it is now easier than ever to build AI-related products.</p>
<p>If you need to sprinkle a little AI magic into your project, you probably can get away with just simple rest calls to OpenAI endpoints, however, if you are building a more complex product and have little to no experience with deep machine learning concepts such as RAG, Langchain and so on, then this article will be an ideal primer for you.</p>
<blockquote>
<p>🤦🏾 We actually abuse the term "AI". Machine learning is just one subset of AI. Nonetheless, to keep things simple, since everyone is on the bandwagon of "AI" these days. I will use these terms interchangeably. Sorry!</p>
</blockquote>
<h2 id="heading-models-give-up-control">Models: Give up control</h2>
<p>There are no or very few "IF", "THEN", "ELSE", or "DO WHILE" controls in machine learning, thus machine learning is a different paradigm compared to conventional programming.</p>
<p>You will often use natural language and configuration-style programming to tune models in the direction you want them to go. Ultimately though, you never have 100% control over what the model will do. You can In most cases predict the outcome with great certainty but it's not the same as programming where we write rules and control flows to precisely guide the user along the path we want to take them.</p>
<p>A model is largely a "black box" of neural networks. You can think of a neural network as a chain of tiny nodes (neurons). Together all the nodes in a neural network build up some pattern recognition ability that the model uses to analyze users' input and generate the appropriate response.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714489319353/a7cdf1ee-fdfc-439b-a7be-3b9484436e02.jpeg" alt class="image--center mx-auto" /></p>
<p>A neural network usually contains 3 types of layers:</p>
<ol>
<li><p><strong>The input layer:</strong> takes in data as a whole. Neurons in this layer would then represent various features of the input data. For example an image of a "Cat", some neurons will represent the fur, others the tail, others the whiskers, and so forth.</p>
</li>
<li><p><strong>Hidden layers:</strong> Features extracted from the first layer are analyzed further by these layers to determine weights and biases. Essentially mathematical algorithms are applied to the data, to determine patterns that develop the model's reasoning and prediction abilities.</p>
</li>
<li><p><strong>Output layer:</strong> This now uses the "learnings" from the previous layer to build an appropriate response back to the user. In the cat image example, we probably want to classify the type of animal. This layer will then return "Cat".</p>
</li>
</ol>
<p>When you train a model, you pass the same data to the model over several iterations. One full data cycle through all layers is known as an "epoch". Each time you cycle through an epoch, the model constantly adjusts and optimizes its pattern recognition ability ( Too many epochs can be bad, read up more on "model loss functions").</p>
<p>Training via epochs is vital to improve the model's accuracy. For example: if the first image is of a "sphynx" cat species, the neural networks are not aware of other cat species yet and therefore will make various assumptions about cats using this species of cat.</p>
<p>This will cause weird predictions since a "sphynx" is significantly different compared to a regular house cat. On the second epoch, the network is now aware of the 99 other cat species and thus has a holistic view of what the different species are and their features. Each subsequent epoch would then re-analyze the data and look for ways to optimize the model's accuracy.</p>
<p>Usually when training a model, you also provide a small sample dataset with accurate examples, so that the model can use this dataset to help fine-tune its accuracy during the training phase.</p>
<h2 id="heading-tooling-and-frameworks-to-build-models">Tooling and frameworks to build models</h2>
<p>In the Python world, PyTorch is the undisputed leader when it comes to building machine learning models. It's open-source and freely available, so just about anyone can implement their own models from scratch, or extend someone else's model.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714489694951/fa9eb92f-cee4-4fa2-8fe4-fcb731eeaecc.jpeg" alt class="image--center mx-auto" /></p>
<p>Before you even build a model, you are going to need some kind of dataset. Usually a CSV or JSON file. You can build your own dataset from scratch using your own data, scrape data from somewhere, or use <a target="_blank" href="https://kaggle.com/">Kaggle</a>.</p>
<blockquote>
<p>💡 Kaggle is a community of "machine learners" where you can find various kinds of freely available datasets and even models to use for both commercial and non-commercial purposes.</p>
</blockquote>
<p>Dealing with large datasets in regular Python lists or dictionaries can become inefficient, thus in addition to Pytorch, you will need to learn 2 libraries that assist with parsing and manipulating data (and Langchain for LLM-specific tasks):</p>
<ul>
<li><p><strong>Pandas:</strong> This will allow you to load and parse your CSV/JSON training data and format it efficiently into whatever format the PyTorch model needs the data in. Furthermore, Pandas has a consistent API, thus you can easily switch between data sources without needing major refactoring in your scripts.</p>
</li>
<li><p><strong>Numpy:</strong> Models usually work with vector embeddings of your text (numerical representations) which are arrays of floating point numbers. Numpy is used throughout the process by models to perform various numerical computations. Pytorch models in general will return a result in the form of a Numpy array, therefore you will need to process or convert that result accordingly.</p>
</li>
<li><p><strong>Langchain:</strong> A powerful LLM utility library to make working with LLM APIs and Models much easier. You most certainly can use the REST API or Python library for your LLM and work with it just fine without Langchain. For larger applications, Langchain simplifies common tasks, like building system prompts, agents, RAG retrievers, and so on.</p>
</li>
</ul>
<h2 id="heading-a-wide-variety-of-pre-trained-models">A wide variety of pre-trained models</h2>
<p>You may be aware of Llama3 and OpenAI models, but in the machine learning world, there are hundreds if not thousands of models available for all kinds of machine learning tasks. Even OpenAI itself has several different models: GPT3.5 (gpt3.5-turbo...) (powers ChatGPT free version), GPT-4 (powers ChatGPT pro), Whisper (for audio to text), DALL·E (image generation) and so forth.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714489806496/52d368fb-ebc9-4304-b87f-811c0eecfdd8.jpeg" alt class="image--center mx-auto" /></p>
<p>Some of the most popular model types are:</p>
<ul>
<li><p>Text embedding: Converts text into vector embeddings. There is a leaderboard to rank models in this class: <a target="_blank" href="https://huggingface.co/spaces/mteb/leaderboard">https://huggingface.co/spaces/mteb/leaderboard</a></p>
</li>
<li><p>Image generation: DALL·E 3, Midjourney, Stable Diffusion, and so forth.</p>
</li>
<li><p>Text classification: Models that can categorize various pieces of data, like spam classification, product categorization, tagging, etc... Some model examples: FastText, BERT, facebook/bart-large-mnli</p>
</li>
<li><p>Image classification: Given an image, these models can determine an appropriate label, tag, or caption. Examples: microsoft/resnet-50, OpenAI CLIP.</p>
</li>
<li><p>Object detection: Given an image, detect various entities in the image. Examples: facebook/detr-resnet-50, keremberke/yolov5m-garbage</p>
</li>
<li><p>Text to Speech (and vice-versa): microsoft/speecht5_tts, OpenAI Whisper, OpenAI TTS.</p>
</li>
<li><p>Large language models: General purpose models that can do some or all of the tasks above. Examples: Chat GPT 3.5, Mixtral, LLama3, Gemini.</p>
</li>
</ul>
<p>Similar to "Kaggle" I mentioned earlier, when it comes to models, one of the best places to find pre-trained models is <a target="_blank" href="https://huggingface.co/">HuggingFace</a>.</p>
<p>In addition to providing models, they also provide an open-source library called "sentence-transformers" allowing you to easily use their models in your code in a consistent way.</p>
<p>Here's a simple example of how you can generate vector embeddings using the Huggingface "sentence-transformers" library:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sentence_transformers <span class="hljs-keyword">import</span> SentenceTransformer
sentences = [<span class="hljs-string">"Some sentence or phrase here"</span>]

<span class="hljs-comment"># Notice we pass in the model name:</span>
<span class="hljs-comment"># 'sentence-transformers/all-mpnet-base-v2'</span>
<span class="hljs-comment"># You can easily swap this model out for any other</span>
<span class="hljs-comment"># - compatible model e.g. "sentence-transformers/all-MiniLM-L6-v2"</span>
model = SentenceTransformer(<span class="hljs-string">'sentence-transformers/all-mpnet-base-v2'</span>)

<span class="hljs-comment"># Will convert the sentences into vector embeddings.</span>
embeddings = model.encode(sentences)

<span class="hljs-comment"># Will print a Numpy array</span>
print(embeddings)
</code></pre>
<h2 id="heading-what-are-vector-embeddings">What are vector embeddings?</h2>
<p>I have mentioned this concept a few times, so just to clarify for those that are unfamiliar.</p>
<p>Machine learning, under the hood, is essentially statistical algorithms at play, they use a ton of mathematical formulas to generate predictions and perform other machine learning tasks.</p>
<p>Now as you can imagine, text and letters are not mathematical in nature. You can't perform calculations on them, therefore, for the math algorithms to work efficiently, we need to convert words/letters into numbers.</p>
<p>Each phrase or word is then converted into a floating point array of numbers (a vector), and multiple vectors are grouped together (e.g. a sentence or paragraph) to form vector embeddings.</p>
<p>Vector embeddings capture contextual information based on how close words are together, how frequently they appear in similar sentences, and so on. This allows for storing enough information for algorithms like COISINE similarity, ANN, and KNN to calculate semantic meanings of the text.</p>
<p>As you can imagine one of the most useful use cases for vector embeddings is to perform semantic searches. With semantic searches, the algorithm can identify sentences and words even if the search term does not appear in the results (this is different compared to SOLR or Elasticsearch where they just look for synonyms or fuzzy-spelled keywords).</p>
<p>You can generate vector embedding as follows:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sentence_transformers <span class="hljs-keyword">import</span> SentenceTransformer
model = SentenceTransformer(<span class="hljs-string">'sentence-transformers/all-mpnet-base-v2'</span>)
embeddings = model.encode([<span class="hljs-string">"Machine learning"</span>])
print(embeddings)
</code></pre>
<p>The result will be a Numpy array (The actual vector embedding is much larger, I have shortened it below to just give you an example) :</p>
<pre><code class="lang-python">[[<span class="hljs-number">-2.14947201e-02</span>  <span class="hljs-number">6.24050908e-02</span> <span class="hljs-number">-6.70606121e-02</span>  <span class="hljs-number">1.17204664e-02</span>
  <span class="hljs-number">-2.23315824e-02</span>  <span class="hljs-number">3.22391130e-02</span> <span class="hljs-number">-1.10149011e-02</span>  <span class="hljs-number">2.08304841e-02</span>
  <span class="hljs-number">-9.21188202e-03</span>  <span class="hljs-number">3.96048091e-02</span>  <span class="hljs-number">1.33278236e-01</span>  <span class="hljs-number">4.91726398e-02</span>
  <span class="hljs-number">-2.97797378e-02</span>  <span class="hljs-number">6.26323521e-02</span>  <span class="hljs-number">2.64899582e-02</span> <span class="hljs-number">-8.43470395e-02</span>
   <span class="hljs-number">8.12786724e-03</span>  <span class="hljs-number">7.97691289e-03</span> <span class="hljs-number">-5.54570891e-02</span>  <span class="hljs-number">6.59016613e-03</span>
   <span class="hljs-number">1.62357709e-03</span>  <span class="hljs-number">7.52743008e-03</span>  <span class="hljs-number">2.48725782e-03</span>  <span class="hljs-number">4.35999036e-03</span>]]
</code></pre>
<p>Depending on the model you use, the dimensions will differ (number of floats). In this case, the model generates "768" floating point numbers. This is not always true, but for the most part, the larger the dimension, the better the accuracy.</p>
<h2 id="heading-what-is-retrieval-augmented-generation-rag">What is Retrieval-Augmented Generation (RAG)?</h2>
<p>This will be one of the most common machine learning systems you will need to build for web applications.</p>
<p>When you ask ChatGPT a question, it refers back to its vast dataset of data and generates data based on that context. Sometimes it can hallucinate and give you incorrect information.</p>
<p>Other times, it may give you the correct information, but in your unique context that information may be irrelevant or too broad to be useful. For example: "What is the price of a Macbook Pro?", the model responds with "From $1,299....".</p>
<p>This may be correct, but it's not very useful because it doesn't give me the price in my local currency, or provide more detail about each model.</p>
<p>RAG systems aim to fix this problem; essentially what happens, is that you give the model a custom dataset and it then scopes its responses to only your dataset, thus allowing for better accuracy and providing better local context for your domain/business.</p>
<p>This is different from finetuning. In finetuning, you are taking a pre-trained model and extending its ability by providing a custom dataset for your use case. You then have to re-run training on the model and the model will generate a new trained state, thus the model's context is frozen at this state and not real-time.</p>
<p>With a RAG system, you are not re-training the model, you are providing context data in real-time, therefore you do not need to train the model every time the data changes.</p>
<p>The LLM uses its original training data as its basis to develop reasoning, but any generation or prediction tasks it performs using RAG will be scoped to your custom data and precedence will be given to your data instead of its original dataset.</p>
<p>If you want to learn how to build a RAG system, you can have a look at an earlier article I have done <a target="_blank" href="https://kevincoder.co.za/how-to-build-a-pdf-chatbot-with-langchain-and-faiss">here</a>.</p>
<h2 id="heading-how-to-host-your-own-llm">How to host your own LLM?</h2>
<p>When you first get into machine learning, I would advise you not to tinker with open-source models like Llama3, rather focus on using OpenAI models first and get familiar with their APIs.</p>
<p>While OpenAI credits are fairly cheap and can get you good mileage, sometimes though, when you are building a large-scale application, you may want to run your own in-house model.</p>
<p>You have a few open-source options:</p>
<ul>
<li><p><strong>Llama3</strong> - Developed by Facebook.</p>
</li>
<li><p><strong>Mixtral</strong> - Developed by an Independent company that's making waves in the ML world.</p>
</li>
<li><p><strong>Phi3</strong> - Developed by Microsoft, very compact and efficient. This model can even run on mobile devices.</p>
</li>
</ul>
<p>I wouldn't touch Google's Gemma model, I had tried it initially and the results were poor, however, to be fair it was within the first week of it being launched.</p>
<p>Depending on your GPU resources, you may want to try Phi3 first, then Mixtral, and finally Llama3. Llama3 should be the best-performing model. It is a tad more resource-intensive and you may not always need that kind of power.</p>
<p>To run your models, you can use <a target="_blank" href="https://ollama.com/">ollama</a>. I also did a tutorial on setting this up <a target="_blank" href="https://kevincoder.co.za/how-to-host-your-own-chatgpt-like-model">here</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Hopefully, I have given you enough insights to get started on expanding your skillset into machine learning, of course, machine learning has gone through many changes in recent years, and libraries/models are still constantly evolving.</p>
<p>I would advise not to get caught up in all the hype, rather pick up Pandas, NumPy and play around with Huggingface models first.</p>
<p>Once you have a solid grasp of these, then move on to Langchain and integrate with OpenAI to build a simple RAG, followed by a simple agent.</p>
<p>Happy building!</p>
]]></content:encoded></item><item><title><![CDATA[Build a production-grade RAG or similarity search using Qdrant and Langchain]]></title><description><![CDATA[In a recent article, I wrote about using Langchain and FAISS to build a RAG system. FAISS, however, is in memory and not scalable when you want to update the training data constantly in real-time.
Qdrant is a great alternative to FAISS. In this artic...]]></description><link>https://kevincoder.co.za/build-a-production-grade-rag-or-similarity-search-using-qdrant-and-langchain</link><guid isPermaLink="true">https://kevincoder.co.za/build-a-production-grade-rag-or-similarity-search-using-qdrant-and-langchain</guid><category><![CDATA[qdrant]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[RAG ]]></category><category><![CDATA[openai]]></category><category><![CDATA[langchain]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Wed, 17 Apr 2024 06:43:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1713334927249/d8d3bf44-d0f5-4ff0-872b-b39a03de72cf.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In a recent <a target="_blank" href="https://kevincoder.co.za/how-to-build-a-pdf-chatbot-with-langchain-and-faiss">article</a>, I wrote about using Langchain and FAISS to build a RAG system. FAISS, however, is in memory and not scalable when you want to update the training data constantly in real-time.</p>
<p>Qdrant is a great alternative to FAISS. In this article, we will look at building a basic similarity search using Langchain and Qdrant.</p>
<h2 id="heading-what-is-qdrant">What is Qdrant?</h2>
<p>In the world of machine learning these days, it's a common practice to vectorize text and store it in a backend DB, unfortunately, most modern databases such as MySQL are not designed to store vector embeddings, although things move fast in the machine learning world and support is getting better, vectors are not yet a first-class citizen for RDBMS type databases.</p>
<p>Qdrant is a modern database that's optimized for storing unstructured data, more especially vector embeddings. Besides being a vector store, you can also use Qdrant as a search engine, it lacks features such as faceting but still is pretty useful on longtail phrase type searches.</p>
<p>Take for example: "Please suggest a high-end gaming machine with lots of RAM and a good graphics card ", a regular keyword search might return no results or poor results.</p>
<p>I tried this on Amazon.com and got weird results for: RAM, mini PCs, and socks 😂:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711451907839/a50ad374-7898-48d7-9f42-3ad474d769ea.png" alt class="image--center mx-auto" /></p>
<p>Naturally, this confuses a keyword-based search engine because it's looking for specific keywords or their synonyms. On Qdrant, you can most probably find "gaming laptops" for the same above search. Since the vector-based search also stores "meaning" and uses algorithms such as "kNN" with cosine similarity to better understand the searcher's intent.</p>
<blockquote>
<p>If you don't know what vector embeddings are: they are basically a numerical representation of text, that machine learning models can use to perform math calculations, in-order to determine meaning and relationships between words and phrases. Qdrant's website provides a more in depth article on this subject as well, you can learn more <a target="_blank" href="https://qdrant.tech/articles/what-are-embeddings/">here</a>.</p>
</blockquote>
<h2 id="heading-setting-up-qdrant">Setting up Qdrant</h2>
<p>Similar to most NoSQL databases, Qdrant uses the concept of "collections". Essentially a collection is a unique grouping of documents, similar to an SQL table.</p>
<p>The easiest way to get started with Qdrant, is to use the docker image:</p>
<pre><code class="lang-bash">docker run -dit -p 6333:6333 qdrant/qdrant
</code></pre>
<p>You should now be able to view the web dashboard by visiting the following link: http://127.0.0.1:6333/dashboard</p>
<p>Before we get to the code, you will need to install a few pip packages:</p>
<pre><code class="lang-bash">pip install langchain
pip install qdrant_client
pip install langchain-community
pip install langchain-openai
</code></pre>
<p>Setting up our schema:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> qdrant_client <span class="hljs-keyword">import</span> QdrantClient
<span class="hljs-keyword">from</span> qdrant_client.models <span class="hljs-keyword">import</span> Distance, VectorParams
<span class="hljs-keyword">from</span> langchain.embeddings <span class="hljs-keyword">import</span> OpenAIEmbeddings
<span class="hljs-keyword">from</span> langchain_community.vectorstores <span class="hljs-keyword">import</span> Qdrant

embeddings = OpenAIEmbeddings()
client = QdrantClient(<span class="hljs-string">'http://127.0.0.1:6333'</span>)

langchain_qdrant_store = Qdrant(
    embeddings=embeddings,
    client=client,
    collection_name=<span class="hljs-string">"products"</span>
)

<span class="hljs-comment"># Should only run this once</span>
client.create_collection(
    collection_name=<span class="hljs-string">"products"</span>,
    vectors_config=VectorParams(size=<span class="hljs-number">1536</span>, distance=Distance.COSINE),
)
</code></pre>
<p>When you run the above, it should create a collection named: "products" with one field that will house our vector embedding. For the purposes of this article and to keep things simple, I am just storing the vector embeddings and no other extra meta information.</p>
<p>You can most certainly, store more information using "payloads" if needed. Learn more about payloads <a target="_blank" href="https://qdrant.tech/documentation/concepts/payload/">here</a>.</p>
<h2 id="heading-storing-documents-in-the-collection">Storing documents in the collection</h2>
<p>Storing documents in the collection is fairly straightforward when using the "OpenAIEmbeddings" and Langchain Documents. Langchain will automatically take care of the complexity of querying OpenAI and generating the vector embeddings.</p>
<p>So basically all you need to do, in order to index a document in Qdrant is as follows:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> langchain.docstore.document <span class="hljs-keyword">import</span> Document

doc = Document(page_content=<span class="hljs-string">"Some text data here"</span>)
langchain_qdrant_store.add_documents([doc])
</code></pre>
<blockquote>
<p>If you want to store documents like PDF’s, here’s an open-source project I built some time ago that provides an example: <a target="_blank" href="https://github.com/kevincoder-co-za/ragable/tree/main">kevincoder-co-za/ragable</a></p>
</blockquote>
<h2 id="heading-finding-similar-documents">Finding similar documents</h2>
<p>To perform a KNN search we simply need to do the following:</p>
<pre><code class="lang-python">documents = langchain_qdrant_store.similarity_search(<span class="hljs-string">"Some search term"</span>)
</code></pre>
<p>The above will return a list of "Langchain documents", which you can pass on to an LLM or process further just like you would any other Python object in your application.</p>
<h2 id="heading-using-the-qdrant-store-as-a-retriever">Using the Qdrant store as a retriever</h2>
<p>Lanchain is awesome and can simplify the process of passing on this information to an LLM for further reasoning, see the example below:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> langchain.chains <span class="hljs-keyword">import</span> RetrievalQA
<span class="hljs-keyword">from</span> langchain.chat_models <span class="hljs-keyword">import</span> ChatOpenAI

llm = ChatOpenAI(model_name=<span class="hljs-string">"gpt-3.5-turbo-0125"</span>, temperature=<span class="hljs-number">0</span>)
chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type=<span class="hljs-string">"stuff"</span>,
    retriever=langchain_qdrant_store.as_retriever()
)
print(chain({<span class="hljs-string">"query"</span>: question}))
</code></pre>
<p>Whenever you pass a question to the LLM, it will automatically query Qdrant for similar documents and then determine its response accordingly based on that context data.</p>
<p>This is quite powerful and is known as "RAG" (retrieval-augmented generation**).** Remember with ChatGPT and other such models, it's often possible for the LLM to hallucinate or provide a response that may be correct but not as in-depth as you may like.</p>
<p>With RAG, you are providing the context data and can scope the LLM to only respond based on the data you provide, this reduces the model's likelihood to hallucinate, and you can enforce an "I don't know" rule or something to that effect when it cannot answer the question effectively.</p>
<h2 id="heading-what-can-i-build-with-this-kind-of-system">What can I build with this kind of system?</h2>
<p>Some common use cases include:</p>
<ul>
<li><p>A summarization tool. You can vectorize hundreds or thousands of documents into Qdrant and then have the LLM summarize and answer questions based on that data. This is useful in various business applications: such as training, onboarding, customer support, and so forth.</p>
</li>
<li><p>Insights tool. Ingest large amounts of statistics and then build an interface with various charts to predict trends and understand your data better. Crime intelligence is a good example.</p>
</li>
<li><p>Suggestion engine. A great use case would be e-commerce, the chatbot can help users with their purchases by providing more information and suggesting other products that may go well together and so forth.</p>
</li>
<li><p>Similarity search. You don't need a full RAG for this, since it'll be slow and use lots of resources if you have a high volume of searches. Qdrant alone is sufficient though using the "similarity_search" function.</p>
</li>
</ul>
<p>These are just a handful of suggestions, I am sure there are loads more interesting applications that can be built using this platform.</p>
<h2 id="heading-what-are-the-disadvantages-of-using-qdrant">What are the disadvantages of using Qdrant?</h2>
<p>One of the major pain points with Qdrant is how the querying engine works, it’s similar to NoSQL but very basic and cumbersome to work with if you need to do some advanced filtering like you would in SQL:</p>
<pre><code class="lang-sql">where city="Cape Town" and price &gt;= 500
</code></pre>
<p>In Qdrant:</p>
<pre><code class="lang-sql">POST /collections/{collection_name}/points/search

{
  "filter": {
    "must": [
      {
        "key": "city",
        "match": {
          "value": "Cape Town"
        }
      },
      {
        "key": "price",
        "range": {
          "gte": 500
        }
      }
    ]
  }
}
</code></pre>
<p>As you can see, it’s so much more verbose and cumbersome to work with versus just plain old SQL, however, this is not a problem usually because 90% of the time you basically just use Qdrant for the similarity search and then can get back a list of IDs or something to identify your data and then use those to query your SQL backend.</p>
<p>Another issue is that while their documentation is fairly okay, it’s still very new thus there are very limited resources and you have to dig into the docs quite a bit to find what you looking for(although these days we just ask Claude right?).</p>
]]></content:encoded></item><item><title><![CDATA[LLM Super Powers with Langchain Agents]]></title><description><![CDATA[Langchain is one of my favorite tools at the moment because of how it simplifies complex machine-learning tasks.
In some cases you may need more than just a well-written prompt, you may want to trigger different data sources or actions based on what ...]]></description><link>https://kevincoder.co.za/llm-super-powers-with-langchain-agents</link><guid isPermaLink="true">https://kevincoder.co.za/llm-super-powers-with-langchain-agents</guid><category><![CDATA[Machine Learning]]></category><category><![CDATA[Python]]></category><category><![CDATA[AI]]></category><category><![CDATA[langchain]]></category><category><![CDATA[LLM-Retrieval ]]></category><dc:creator><![CDATA[Kevin Naidoo]]></dc:creator><pubDate>Thu, 11 Apr 2024 15:51:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1712850340884/eb80adb5-f8d6-40dc-8277-8a7cdc312dce.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Langchain is one of my favorite tools at the moment because of how it simplifies complex machine-learning tasks.</p>
<p>In some cases you may need more than just a well-written prompt, you may want to trigger different data sources or actions based on what the user asks.</p>
<p>For example: in an e-commerce site, if the user asks to view a list of "shoes", you probably would do a keyword/semantic search and return a list of matching shoes.</p>
<p>If they ask about delivery information, you may want to look up that data from an SQL DB or API.</p>
<p>Langchain agents are a powerful mechanism at your disposal that will enable you to build complex custom LLM chatbots. In this article, we will go over what is an agent and how to build one.</p>
<h2 id="heading-what-is-a-langchain-agent">What is a Langchain Agent?</h2>
<p>When you use an LLM like OpenAI's "chatgpt-turbo", you typically will send the model a prompt consisting of one or more messages, and the LLM will respond accordingly.</p>
<p>So essentially text in and text out, this is okay for a chatbot that is performing one particular task like answering support questions, but what if you need to change the data source or do a web search or record something in the DB?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712851656923/df6e7352-849f-4384-935b-10630b4eda16.jpeg" alt class="image--center mx-auto" /></p>
<p>This is where Agents come in handy; an Agent is basically a "task executor". It will allow the LLM to execute functions and other code in your application based on reasoning and user input.</p>
<p>Think in terms of a switch statement, depending on which pathway is true, the switch statement will execute that particular block of code.</p>
<p>Agents are not exclusive to Langchain. Each LLM has a different way of handling agents, however, Langchain just provides a consistent API to work with regardless of which backend LLM you are using.</p>
<h2 id="heading-tools">Tools</h2>
<p>Since agents are task executors, we need some kind of "callback" for the agent to execute such as a function or class.</p>
<p>Tools take in, either the raw prompt or a list of arguments and return some sort of output. Usually, you would return a string, but it's also possible to return more complex data like a LangChain document.</p>
<p>There are multiple ways of declaring a tool. We will cover the decorator approach since it's the most common and easiest solution to understand.</p>
<p>Here is an example:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> langchain.tools <span class="hljs-keyword">import</span> tool

<span class="hljs-meta">@tool</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">search_delivery_information</span>(<span class="hljs-params">prompt :str</span>) -&gt; str:</span>
    <span class="hljs-string">""" When the user requests delivery information """</span>

    <span class="hljs-comment"># We now return some text from an external API</span>
    <span class="hljs-comment"># The LLM will analyze this text and </span>
    <span class="hljs-comment"># - return the appropriate answer to the user.</span>
    <span class="hljs-keyword">return</span> requests.get(<span class="hljs-string">"/somewhere/delivery.json"</span>).text
</code></pre>
<p>Three essential components make up a tool:</p>
<ol>
<li><p>@tool - This decorator will take care of handling the input/output of your function in a way that the LLM can understand.</p>
</li>
<li><p>""" - The docstring; think of this as a prompt system message to the LLM, where you tell the LLM when to execute this function and provide any other useful context data.</p>
</li>
<li><p>The returned data. The LLM will ingest your function's response as context data and scope its response to that context.</p>
</li>
</ol>
<p>To clarify what I mean by "scope", without any context data, if the user asks: "Which is the best shoe brand?", it will respond with some generic response based on the LLMs training data, similar to asking ChatGPT a question, so probably it may respond with "Nike" or "Reebok".</p>
<p>However, if the tool returns "Addidas is our best brand.", then the LLM's response will regard "Addidas" as the best brand and not "Nike" or "Reebok".</p>
<h2 id="heading-putting-it-all-together">Putting it all together</h2>
<p>Okay great! Now you know what an agent is and how to create your custom callback functions to help the LLM better answer the user's question.</p>
<p>Let's now build an Agent and link it to our custom tool:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> langchain_openai <span class="hljs-keyword">import</span> ChatOpenAI
<span class="hljs-keyword">from</span> langchain_core.prompts <span class="hljs-keyword">import</span> ChatPromptTemplate, MessagesPlaceholder
<span class="hljs-keyword">from</span> langchain.agents.format_scratchpad.openai_tools <span class="hljs-keyword">import</span> (
    format_to_openai_tool_messages,
)
<span class="hljs-keyword">from</span> langchain.agents.output_parsers.openai_tools <span class="hljs-keyword">import</span> OpenAIToolsAgentOutputParser

<span class="hljs-comment"># Create a standard Chat LLM</span>
llm = ChatOpenAI(model_name=<span class="hljs-string">"gpt-3.5-turbo-0125"</span>, temperature=<span class="hljs-number">0</span>)

<span class="hljs-comment"># Create a list of all tools you want to enable</span>
tools = [search_delivery_information]

<span class="hljs-comment"># Connect our tools to the LLM</span>
llm_with_tools = llm.bind_tools(tools) 

<span class="hljs-comment"># Build a chat prompt.</span>
<span class="hljs-comment"># Notice we have placeholders for the user's input</span>
<span class="hljs-comment"># - and a second placeholder for the Agent's context data.</span>
prompt = ChatPromptTemplate.from_messages(
        [
            (
                <span class="hljs-string">"system"</span>,
               <span class="hljs-string">f"You are an e-commerce assistant."</span>,
            ),
            (<span class="hljs-string">"user"</span>, <span class="hljs-string">"{input}"</span>),
           MessagesPlaceholder(variable_name=<span class="hljs-string">"agent_ctx"</span>),
        ]
)

<span class="hljs-comment"># Next we build the actual agent.</span>
agent = (
        {
            <span class="hljs-string">"input"</span>: <span class="hljs-keyword">lambda</span> x: x[<span class="hljs-string">"input"</span>],
            <span class="hljs-string">"agent_ctx"</span>: <span class="hljs-keyword">lambda</span> x: format_to_openai_tool_messages(
                x[<span class="hljs-string">"intermediate_steps"</span>]
            )
        }
        | prompt
        | llm_with_tools
        | OpenAIToolsAgentOutputParser()
)
</code></pre>
<pre><code class="lang-python">  {
      <span class="hljs-string">"input"</span>: <span class="hljs-keyword">lambda</span> x: x[<span class="hljs-string">"input"</span>],
      <span class="hljs-string">"agent_ctx"</span>: <span class="hljs-keyword">lambda</span> x: format_to_openai_tool_messages(
           x[<span class="hljs-string">"intermediate_steps"</span>]
      )
  }
</code></pre>
<p>The above block might seem confusing at first glance, but basically, the first argument is the user's input, the prompt template created earlier will replace "{input}" with the actual user's question or message.</p>
<p>Secondarily: "agent_ctx", since our tool callback functions are just Python functions, there needs to be a translation step that converts the output from these functions into something that the model can understand and the agent can transmit via the REST API.</p>
<p>You will also notice we chain one other object at the end "OpenAIToolsAgentOutputParser", this will receive data and convert it into a format that the agent can understand.</p>
<p>Finally, we can instantiate an agent executor and prompt the LLM:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> langchain.agents <span class="hljs-keyword">import</span> AgentExecutor

agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=<span class="hljs-literal">False</span>)
result = agent_executor.invoke({<span class="hljs-string">"input"</span>: question})
print(result)
</code></pre>
<h2 id="heading-wait-what-about-memory">Wait! What about memory?</h2>
<p>Naturally, users will ask follow-up questions and the LLM needs to be aware of these to provide a much more accurate answer.</p>
<p>Luckily, Langchain makes managing memory so much easier. Here is an example:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> myapp.models <span class="hljs-keyword">import</span> Message
<span class="hljs-keyword">from</span> langchain.prompts <span class="hljs-keyword">import</span> MessagesPlaceholder
<span class="hljs-keyword">from</span> langchain_openai <span class="hljs-keyword">import</span> ChatOpenAI
<span class="hljs-keyword">from</span> langchain_core.messages <span class="hljs-keyword">import</span> AIMessage, HumanMessage
<span class="hljs-keyword">from</span> langchain_core.prompts <span class="hljs-keyword">import</span> (
ChatPromptTemplate,
MessagesPlaceholder
)
<span class="hljs-keyword">from</span> langchain.agents.format_scratchpad.openai_tools <span class="hljs-keyword">import</span> (
    format_to_openai_tool_messages,
)
<span class="hljs-keyword">from</span> langchain.agents.output_parsers.openai_tools <span class="hljs-keyword">import</span> (
OpenAIToolsAgentOutputParser
)

<span class="hljs-comment"># ... other agent code as per above snippets</span>
prompt = ChatPromptTemplate.from_messages(
    [
        (
            <span class="hljs-string">"system"</span>,
            <span class="hljs-string">f"You are an e-commerce assistant."</span>,
        ),
        <span class="hljs-comment"># Add new variable to replace with previous chat messages.</span>
        MessagesPlaceholder(variable_name=<span class="hljs-string">"chat_history"</span>), 
        (<span class="hljs-string">"user"</span>, <span class="hljs-string">"{input}"</span>),
        MessagesPlaceholder(variable_name=<span class="hljs-string">"agent_ctx"</span>),
     ]
)

<span class="hljs-comment"># Build a list of messages from the DB.</span>
chat_history = []

<span class="hljs-comment"># Very simple query, you may want to limit to</span>
<span class="hljs-comment"># - the last 10 conversations or something to that effect.</span>
messages = Message.objects.filter(is_deleted=<span class="hljs-literal">False</span>)
<span class="hljs-keyword">for</span> m <span class="hljs-keyword">in</span> messages:
    <span class="hljs-keyword">if</span> m.model_answer <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:
        chat_history.append(
            [
                HumanMessage(content=m.user_question),
                AIMessage(content=m.model_answer),
            ]
         )

agent = (
    {
        <span class="hljs-string">"input"</span>: <span class="hljs-keyword">lambda</span> x: x[<span class="hljs-string">"input"</span>],
        <span class="hljs-string">"agent_ctx"</span>: <span class="hljs-keyword">lambda</span> x: format_to_openai_tool_messages(
            x[<span class="hljs-string">"intermediate_steps"</span>]
        ),
        <span class="hljs-string">"chat_history"</span>: <span class="hljs-keyword">lambda</span> x: x[<span class="hljs-string">"chat_history"</span>],
    }
    | prompt
    | llm
    | printer
    | OpenAIToolsAgentOutputParser()
)
</code></pre>
<p>In the above code, we first add a variable to the original prompt template: "chat_history" and then in our agent pipeline we extract the messages from the list constructed above in "chat_history".</p>
<h2 id="heading-security-concerns">Security concerns</h2>
<p>Since tool functions are essentially serialized and passed to the OpenAI REST API, it's important to ensure you do not expose any sensitive information in these functions.</p>
<p>Furthermore, if you are allowing the LLM access to interact with your SQL RDMS, just be careful of SQL injection. Rather, build an API around your data or use a vector-based database like Qdrant.</p>
]]></content:encoded></item></channel></rss>