<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Abhi</title>
    <description>The latest articles on Forem by Abhi (@better-boy).</description>
    <link>https://forem.com/better-boy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3297862%2F11b4f74e-b65c-4b0a-9b13-a47c09840d28.jpg</url>
      <title>Forem: Abhi</title>
      <link>https://forem.com/better-boy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/better-boy"/>
    <language>en</language>
    <item>
      <title>Real-Time Data Streaming Made Simple: Server-Sent Events in Ballerina</title>
      <dc:creator>Abhi</dc:creator>
      <pubDate>Thu, 30 Oct 2025 22:20:45 +0000</pubDate>
      <link>https://forem.com/better-boy/real-time-data-streaming-made-simple-server-sent-events-in-ballerina-dd2</link>
      <guid>https://forem.com/better-boy/real-time-data-streaming-made-simple-server-sent-events-in-ballerina-dd2</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In today's world of real-time applications—from live sports scores to stock tickers, chat notifications to IoT dashboards—the ability to push data from server to client is no longer a luxury, it's a necessity. While WebSockets often steal the spotlight, there's an elegant, simpler alternative that's perfect for many use cases: &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this post, we'll explore how Ballerina, with its cloud-native design and powerful HTTP module, makes implementing SSE incredibly straightforward. We'll build real working examples, understand the patterns, and see why SSE might be exactly what your next project needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Server-Sent Events?
&lt;/h2&gt;

&lt;p&gt;Server-Sent Events (SSE) is a web technology that enables servers to push data to web clients over a single, long-lived HTTP connection. Think of it as a one-way communication channel from your server to the browser—perfect for scenarios where the server needs to continuously update the client.&lt;/p&gt;

&lt;h3&gt;
  
  
  The SSE Advantage
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why choose SSE over WebSockets?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Simpler to implement&lt;/strong&gt; - Built on standard HTTP, no special protocols&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Automatic reconnection&lt;/strong&gt; - Browsers handle reconnection automatically&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Event IDs &amp;amp; resume&lt;/strong&gt; - Can resume from the last received event&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Firewall friendly&lt;/strong&gt; - Works over standard HTTP/HTTPS ports&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Perfect for one-way updates&lt;/strong&gt; - Server → Client communication  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use SSE:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time dashboards and monitoring&lt;/li&gt;
&lt;li&gt;Live sports scores or stock tickers&lt;/li&gt;
&lt;li&gt;News feeds and notifications&lt;/li&gt;
&lt;li&gt;Progress indicators for long-running tasks&lt;/li&gt;
&lt;li&gt;Social media updates&lt;/li&gt;
&lt;li&gt;Server logs streaming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When NOT to use SSE:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Need bidirectional communication (use WebSockets)&lt;/li&gt;
&lt;li&gt;Require binary data streaming&lt;/li&gt;
&lt;li&gt;Need sub-millisecond latency&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Why Ballerina for SSE?
&lt;/h2&gt;

&lt;p&gt;Ballerina is a cloud-native programming language designed for network interactions and integration. Its HTTP module provides first-class support for SSE with a clean, type-safe API. Here's what makes Ballerina special for SSE:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Native stream support&lt;/strong&gt; - Ballerina's stream type maps perfectly to SSE&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type safety&lt;/strong&gt; - Compile-time checking for SSE events&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple syntax&lt;/strong&gt; - Minimal boilerplate code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in error handling&lt;/strong&gt; - Graceful error management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-ready&lt;/strong&gt; - Built for enterprise applications&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's see it in action!&lt;/p&gt;
&lt;h2&gt;
  
  
  Your First SSE Application in 5 Minutes
&lt;/h2&gt;

&lt;p&gt;Let's build a simple event counter that streams events to clients. This example demonstrates the core SSE pattern in Ballerina.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Server Code
&lt;/h3&gt;

&lt;p&gt;Here's a complete working SSE server in Ballerina:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import ballerina/http;
import ballerina/lang.runtime;

listener http:Listener counterListener = new(8080);

@http:ServiceConfig {
    cors: {
        allowOrigins: ["*"],
        allowCredentials: false,
        allowHeaders: ["*"],
        allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
    }
}
service /counter on counterListener {

    resource function get events() returns stream&amp;lt;http:SseEvent, error?&amp;gt;|error {
        CounterStream counterStream = new CounterStream();
        stream&amp;lt;http:SseEvent, error?&amp;gt; eventStream = new(counterStream);
        return eventStream;
    }
}

class CounterStream {
    *object:Iterable;
    private int count = 0;
    private final int maxCount = 10;

    public isolated function iterator() returns object {
        public isolated function next() returns record {|http:SseEvent value;|}|error?;
    } {
        return self;
    }

    public isolated function next() returns record {|http:SseEvent value;|}|error? {

        if self.count &amp;gt;= self.maxCount {
            return ();
        }

        self.count += 1;

        http:SseEvent event = {
            data: string `Count: ${self.count}`,
            event: "counter",
            id: self.count.toString()
        };

        runtime:sleep(1);

        return {value: event};
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That's it!&lt;/strong&gt; Save this as &lt;code&gt;simple_sse.bal&lt;/code&gt; and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bal run simple_sse.bal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing with curl
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-N&lt;/span&gt; http://localhost:8080/simple/events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event: counter
id: 1
data: Count: 1

event: counter
id: 2
data: Count: 2

event: counter
id: 3
data: Count: 3
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Client Code
&lt;/h3&gt;

&lt;p&gt;Here's a simple HTML client to visualize the events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;SSE Counter Demo&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;#events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f5f5f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.event&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
                 &lt;span class="nl"&gt;border-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#2196F3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;SSE Counter Demo&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Disconnected&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"events"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:8080/simple/events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventsDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;statusDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;statusDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Connected ✅&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;statusDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;eventDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;eventDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
                &amp;lt;strong&amp;gt;Event ID:&amp;lt;/strong&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastEventId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;br&amp;gt;
                &amp;lt;strong&amp;gt;Data:&amp;lt;/strong&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;br&amp;gt;
                &amp;lt;strong&amp;gt;Time:&amp;lt;/strong&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLocaleTimeString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;
            `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;eventsDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventDiv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;eventsDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstChild&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;statusDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Disconnected ❌&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;statusDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open this HTML file in your browser, and watch the events stream in real-time!&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the SSE Pattern in Ballerina
&lt;/h2&gt;

&lt;p&gt;Let's break down the key components:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The http:SseEvent Record
&lt;/h3&gt;

&lt;p&gt;Every SSE event in Ballerina is represented by the &lt;code&gt;http:SseEvent&lt;/code&gt; record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type SseEvent record {|
    string data;       // The event payload (REQUIRED)
    string event?;     // Event type name (optional)
    string id?;        // Event identifier (optional)
    int retry?;        // Reconnection time in ms (optional)
    string comment?;   // Comment line (optional)
|};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http:SseEvent event = {
    data: "Hello from server!",
    event: "greeting",
    id: "1",
    retry: 3000  // Client will retry after 3 seconds
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. The Stream Class Pattern
&lt;/h3&gt;

&lt;p&gt;Every SSE stream in Ballerina follows this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MyEventStream {
    *object:Iterable;  // Step 1: Implement Iterable

    // Step 2: Implement iterator() method
    public isolated function iterator() returns object {
        public isolated function next() returns record {|http:SseEvent value;|}|error?;
    } {
        return self;
    }

    // Step 3: Implement next() method
    public isolated function next() returns record {|http:SseEvent value;|}|error? {
        // Return nil to end the stream
        if shouldEnd {
            return ();
        }

        // Create and return event
        http:SseEvent event = { /* ... */ };
        return {value: event};
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;*object:Iterable&lt;/code&gt; makes your class iterable&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;iterator()&lt;/code&gt; returns &lt;code&gt;self&lt;/code&gt;, allowing the class to iterate over itself&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;next()&lt;/code&gt; generates each event; return &lt;code&gt;nil&lt;/code&gt; to end the stream&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;isolated&lt;/code&gt; qualifier ensures thread safety&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. The Resource Function
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource function get events() returns stream&amp;lt;http:SseEvent, error?&amp;gt;|error {
    MyEventStream myStream = new MyEventStream();
    stream&amp;lt;http:SseEvent, error?&amp;gt; eventStream = new(myStream);
    return eventStream;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resource function creates the stream and returns it. Ballerina handles all the SSE protocol details automatically!&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Example: Live Stock Ticker
&lt;/h2&gt;

&lt;p&gt;Let's build something more practical—a real-time stock price ticker. This example demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic data generation&lt;/li&gt;
&lt;li&gt;Multiple symbols&lt;/li&gt;
&lt;li&gt;URL path parameters&lt;/li&gt;
&lt;li&gt;Realistic timing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Stock Ticker Server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import ballerina/http;
import ballerina/lang.runtime;
import ballerina/random;

type Stock record {|
    string symbol;
    decimal price;
    decimal change;
    string timestamp;
|};

listener http:Listener stockListener = new(9090);

service /stocks on stockListener {

    // Stream stock updates for specified symbols
    // Example: /stocks/stream/AAPL,GOOGL,MSFT
    resource function get stream/[string symbols]() returns stream&amp;lt;http:SseEvent, error?&amp;gt;|error {
        string[] symbolList = re `,`.split(symbols);
        StockPriceStream stockStream = new StockPriceStream(symbolList);
        return new(stockStream);
    }
}

class StockPriceStream {
    *object:Iterable;
    private string[] symbols;
    private int eventCount = 0;
    private final int maxEvents = 50;

    function init(string[] symbols) {
        self.symbols = symbols;
    }

    public isolated function iterator() returns object {
        public isolated function next() returns record {|http:SseEvent value;|}|error?;
    } {
        return self;
    }

    public isolated function next() returns record {|http:SseEvent value;|}|error? {
        if self.eventCount &amp;gt;= self.maxEvents {
            return ();
        }

        // Select random stock
        int randomIndex = check random:createIntInRange(0, self.symbols.length());
        string symbol = self.symbols[randomIndex];

        // Generate realistic price data
        decimal basePrice = &amp;lt;decimal&amp;gt;(50.0 + &amp;lt;float&amp;gt;(check random:createIntInRange(0, 950)));
        decimal change = &amp;lt;decimal&amp;gt;(&amp;lt;float&amp;gt;(check random:createIntInRange(-500, 500)) / 100.0);
        decimal currentPrice = basePrice + change;

        Stock stock = {
            symbol: symbol.toUpperAscii(),
            price: currentPrice,
            change: change,
            timestamp: getCurrentTimestamp()
        };

        http:SseEvent event = {
            data: stock.toJsonString(),
            event: "stock-update",
            id: self.eventCount.toString()
        };

        self.eventCount += 1;
        runtime:sleep(1);

        return {value: event};
    }
}

function getCurrentTimestamp() returns string {
    // Simplified - in production, use time:utcNow()
    return "2025-10-30T19:00:00Z";
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Stock Ticker Client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Live Stock Ticker&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;135deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#1e3c72&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#2a5298&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1200px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.stock-grid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="py"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto-fill&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;250px&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.stock-card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#e0e0e0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.stock-card.updated&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulse&lt;/span&gt; &lt;span class="m"&gt;0.5s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2196F3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;pulse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="err"&gt;50&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1.05&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.symbol&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#333&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;36px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.change&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.positive&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#d4edda&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#155724&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.negative&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f8d7da&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#721c24&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;📈 Live Stock Ticker&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"symbols"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"AAPL,GOOGL,MSFT,TSLA"&lt;/span&gt; 
                   &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"AAPL,GOOGL,MSFT,TSLA"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"connect()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Connect&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"disconnect()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Disconnect&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Disconnected&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"stock-grid"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"stockGrid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;eventSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stockData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;symbols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;symbols&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="nx"&gt;eventSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`http://localhost:9090/stocks/stream/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;✅ Connected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;green&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stock-update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nf"&gt;updateStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;❌ Disconnected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="nx"&gt;eventSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;stockData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;renderStocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderStocks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stockGrid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="nx"&gt;stockData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stock-card updated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;changeClass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;positive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;negative&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;changeSign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
                    &amp;lt;div class="symbol"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;
                    &amp;lt;div class="price"&amp;gt;$&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/div&amp;gt;
                    &amp;lt;div class="change &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;changeClass&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;
                        &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;changeSign&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;
                    &amp;lt;/div&amp;gt;
                `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="nx"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practices for SSE in Ballerina
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Always Handle Stream Termination
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public isolated function next() returns record {|http:SseEvent value;|}|error? {
    // Always provide a way to end the stream
    if self.count &amp;gt;= self.maxCount {
        return (); // End stream gracefully
    }
    // ... generate event
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Use Meaningful Event Types
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Good - descriptive event types
event: "stock-update"
event: "news-article"
event: "user-notification"

// Bad - generic names
event: "data"
event: "msg"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Always Send JSON for Complex Data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Convert records to JSON strings
type StockData record {| string symbol; decimal price; |};
StockData stock = {symbol: "AAPL", price: 150.00};

http:SseEvent event = {
    data: stock.toJsonString(),  // ← Use toJsonString()
    event: "stock-update"
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Implement Proper Error Handling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public isolated function next() returns record {|http:SseEvent value;|}|error? {
    // Use check for operations that can fail
    int randomValue = check random:createIntInRange(0, 100);

    // Or use try-catch for more control
    do {
        int value = check riskyOperation();
        return {value: createEvent(value)};
    } on fail error e {
        // Log error and return safe event
        log:printError("Error generating event", e);
        return {value: createErrorEvent()};
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Control Event Frequency
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import ballerina/lang.runtime;

public isolated function next() returns record {|http:SseEvent value;|}|error? {
    // Add delay between events to prevent overwhelming clients
    runtime:sleep(1);  // Wait 1 second

    // Generate and return event
    return {value: event};
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Use Event IDs for Resumption
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http:SseEvent event = {
    data: "...",
    id: self.eventCount.toString()  // Client can resume from this ID
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clients can use the &lt;code&gt;Last-Event-ID&lt;/code&gt; header to resume from where they left off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastEventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lastEventId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/events&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Last-Event-ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lastEventId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lastEventId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastEventId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Set Appropriate Retry Times
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http:SseEvent event = {
    data: "...",
    retry: 5000  // Client waits 5 seconds before reconnecting
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. Provide Stream Status Events
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Send periodic heartbeat
if self.eventCount % 10 == 0 {
    return {value: {event: "heartbeat", data: "alive"}};
}

// Send stream completion event
if self.count == self.maxCount {
    return {value: {event: "stream-end", data: "completed"}};
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Memory Management
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class EfficientStream {
    *object:Iterable;
    private final int maxEvents = 100;  // Limit total events

    public isolated function next() returns record {|http:SseEvent value;|}|error? {
        // Clean up resources periodically
        if self.eventCount &amp;gt;= self.maxEvents {
            self.cleanup();
            return ();
        }
        // ... generate event
    }

    function cleanup() {
        // Release resources
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connection Limits
&lt;/h3&gt;

&lt;p&gt;Monitor active SSE connections and implement limits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// In a real application, track active connections
isolated int activeConnections = 0;
final int MAX_CONNECTIONS = 1000;

resource function get events() returns stream&amp;lt;http:SseEvent, error?&amp;gt;|error {
    lock {
        if activeConnections &amp;gt;= MAX_CONNECTIONS {
            return error("Too many active connections");
        }
        activeConnections += 1;
    }

    // Return stream...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Comparison: SSE vs WebSockets vs HTTP Polling
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;SSE&lt;/th&gt;
&lt;th&gt;WebSockets&lt;/th&gt;
&lt;th&gt;HTTP Polling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Connection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One-way (Server → Client)&lt;/td&gt;
&lt;td&gt;Two-way&lt;/td&gt;
&lt;td&gt;Request-Response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Protocol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;WebSocket (ws://)&lt;/td&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auto-Reconnect&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (built-in)&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Event IDs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Browser Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Universal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Firewall Friendly&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Sometimes blocked&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use Case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Real-time updates&lt;/td&gt;
&lt;td&gt;Chat, gaming&lt;/td&gt;
&lt;td&gt;Simple updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Efficiency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Highest&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Resources:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ballerina HTTP Docs:&lt;/strong&gt; &lt;a href="https://lib.ballerina.io/ballerina/http/latest" rel="noopener noreferrer"&gt;https://lib.ballerina.io/ballerina/http/latest&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSE Specification:&lt;/strong&gt; &lt;a href="https://html.spec.whatwg.org/multipage/server-sent-events.html" rel="noopener noreferrer"&gt;https://html.spec.whatwg.org/multipage/server-sent-events.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MDN SSE Guide:&lt;/strong&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events" rel="noopener noreferrer"&gt;https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Happy Streaming!&lt;/strong&gt; 🚀&lt;/p&gt;

&lt;p&gt;If you found this helpful, share it with your team and let us know what you build with SSE in Ballerina!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions or feedback?&lt;/strong&gt; Drop a comment below or reach out to the Ballerina community.&lt;/p&gt;

</description>
      <category>ballerina</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
    <item>
      <title>Advanced Prompt Engineering for goose: A Comprehensive Guide</title>
      <dc:creator>Abhi</dc:creator>
      <pubDate>Thu, 30 Oct 2025 08:17:31 +0000</pubDate>
      <link>https://forem.com/better-boy/advanced-prompt-engineering-for-goose-a-comprehensive-guide-3j6n</link>
      <guid>https://forem.com/better-boy/advanced-prompt-engineering-for-goose-a-comprehensive-guide-3j6n</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;While basic prompting can get you started with &lt;a href="https://block.github.io/goose/" rel="noopener noreferrer"&gt;goose&lt;/a&gt;, advanced prompt engineering techniques can dramatically improve the quality, accuracy, and efficiency of your AI-powered development sessions. &lt;a href="https://block.github.io/goose/" rel="noopener noreferrer"&gt;goose&lt;/a&gt; is an open-source AI agent framework developed by Block that automates development tasks, from debugging to deployment, and mastering sophisticated prompting strategies will help you unlock its full potential.&lt;/p&gt;

&lt;p&gt;This guide explores expert-level techniques for maximizing goose capabilities through strategic prompt design, context management, and workflow optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding goose architecture
&lt;/h2&gt;

&lt;p&gt;Before diving into advanced techniques, it's crucial to understand how goose works. goose operates through an interactive loop where it receives user input, passes it to an LLM for processing, executes tool calls based on the model's response, and manages context revision to optimize token usage.&lt;/p&gt;

&lt;p&gt;Key architectural components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interface&lt;/strong&gt;: Desktop app or CLI where you interact with goose&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent&lt;/strong&gt;: Core logic that manages the interactive loop&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extensions&lt;/strong&gt;: Tools that enable specific capabilities (file management, shell commands, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context Management&lt;/strong&gt;: Automatic revision to keep relevant information and manage token costs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Refer &lt;a href="https://block.github.io/goose/docs/category/architecture-overview" rel="noopener noreferrer"&gt;goose architecture&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Principles of Advanced Prompting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Agentic Thinking: Task-Oriented vs. Chat-Oriented
&lt;/h3&gt;

&lt;p&gt;goose is agentic, not conversational. Understanding this distinction is fundamental:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;❌ Conversational approach:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Can you help me with my tests?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;✅ Agentic approach:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goose, add comprehensive test coverage for src/services/paymentService.js

**Current coverage:** 45% (unacceptable)
**Target coverage:** 90%+

**Testing requirements:**
1. Unit tests for all public methods
2. Integration tests for payment gateway interactions
3. Edge cases and error scenarios
4. Mock external API calls (Stripe)
5. Test success and failure paths
6. Validate input handling
7. Check error message clarity

**Test structure to follow:**
- Use Jest as testing framework
- Follow AAA pattern (Arrange, Act, Assert)
- Group tests by method using describe blocks
- Use descriptive test names (it should...)
- Mock external dependencies properly
- Test one thing per test case

**Specific scenarios to cover:**
- Valid payment processing
- Invalid payment details
- Network failures
- Timeout scenarios
- Insufficient funds
- Currency validation
- Amount validation (positive, not zero, within limits)
- Idempotency (duplicate payment handling)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key differences:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State the task clearly upfront&lt;/li&gt;
&lt;li&gt;Provide measurable success criteria&lt;/li&gt;
&lt;li&gt;Include structural requirements&lt;/li&gt;
&lt;li&gt;Specify testing/validation approaches&lt;/li&gt;
&lt;li&gt;List edge cases explicitly&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Context Architecture: The .goosehints Strategy
&lt;/h3&gt;

&lt;p&gt;.goosehints is a text file that provides additional context about your project to improve communication with Goose. Advanced users leverage hierarchical hints files strategically.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hierarchical Context Strategy
&lt;/h4&gt;

&lt;p&gt;All .goosehints files from your current directory up to the root directory are automatically loaded and combined. Structure them by scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-project/
├── .git/
├── .goosehints                 # Project-wide standards
├── frontend/
│   ├── .goosehints            # Frontend-specific hints
│   └── components/
│       ├── .goosehints        # Component-specific hints
│       └── Button.tsx
└── backend/
    ├── .goosehints            # Backend-specific hints
    └── api/
        └── routes.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Root-level .goosehints example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Project Standards&lt;/span&gt;

&lt;span class="gu"&gt;## Code Style&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Use TypeScript strict mode
&lt;span class="p"&gt;-&lt;/span&gt; Follow Airbnb style guide
&lt;span class="p"&gt;-&lt;/span&gt; Maximum function length: 50 lines
&lt;span class="p"&gt;-&lt;/span&gt; Prefer functional components

&lt;span class="gu"&gt;## Testing Philosophy&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Minimum 80% coverage for new code
&lt;span class="p"&gt;-&lt;/span&gt; Integration tests for all API endpoints
&lt;span class="p"&gt;-&lt;/span&gt; Unit tests for business logic
&lt;span class="p"&gt;-&lt;/span&gt; E2E tests for critical user flows

&lt;span class="gu"&gt;## Documentation Requirements&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; JSDoc comments for all public functions
&lt;span class="p"&gt;-&lt;/span&gt; README updates for new features
&lt;span class="p"&gt;-&lt;/span&gt; API documentation in OpenAPI format

&lt;span class="gu"&gt;## Architecture Patterns&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Use repository pattern for data access
&lt;span class="p"&gt;-&lt;/span&gt; Implement CQRS for complex domains
&lt;span class="p"&gt;-&lt;/span&gt; Event-driven architecture for async operations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Component-level .goosehints example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Component Guidelines&lt;/span&gt;

&lt;span class="gu"&gt;## Button Component Patterns&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; All buttons must support loading state
&lt;span class="p"&gt;-&lt;/span&gt; Use semantic HTML (&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt; not &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;)
&lt;span class="p"&gt;-&lt;/span&gt; Include aria-label for icon-only buttons
&lt;span class="p"&gt;-&lt;/span&gt; Follow design system color tokens

&lt;span class="gu"&gt;## Testing Requirements&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Snapshot tests for visual regression
&lt;span class="p"&gt;-&lt;/span&gt; Interaction tests with Testing Library
&lt;span class="p"&gt;-&lt;/span&gt; Accessibility tests with jest-axe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Advanced .goosehints Techniques
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;1. @-mentions for Dynamic Context Loading&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For frequently-needed documentation, use @filename.md or &lt;a class="mentioned-user" href="https://dev.to/relative"&gt;@relative&lt;/a&gt;/path/testing.md to automatically include file content in context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# API Development&lt;/span&gt;

When working with API endpoints, reference:
@docs/api-standards.md
@docs/authentication-flow.md

For database operations, see:
@backend/database/schema.sql
@backend/database/migration-guide.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Environment-Specific Hints&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Development Environment&lt;/span&gt;

Current environment: ${ENVIRONMENT}

{% if ENVIRONMENT == "production" %}
⚠️ PRODUCTION ENVIRONMENT
&lt;span class="p"&gt;-&lt;/span&gt; All changes require review
&lt;span class="p"&gt;-&lt;/span&gt; Database migrations must be reversible
&lt;span class="p"&gt;-&lt;/span&gt; Feature flags required for new features
{% else %}
Development mode active
&lt;span class="p"&gt;-&lt;/span&gt; Auto-formatting enabled
&lt;span class="p"&gt;-&lt;/span&gt; Verbose logging enabled
{% endif %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Custom Command Definitions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can define useful tasks in .goosehints that benefit from language model-based agent execution flow, like idea generation, summarization, or structured data extraction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Custom Commands&lt;/span&gt;

&lt;span class="gu"&gt;## /review&lt;/span&gt;
Perform comprehensive code review:
&lt;span class="p"&gt;1.&lt;/span&gt; Check for security vulnerabilities
&lt;span class="p"&gt;2.&lt;/span&gt; Verify test coverage
&lt;span class="p"&gt;3.&lt;/span&gt; Validate documentation
&lt;span class="p"&gt;4.&lt;/span&gt; Assess performance implications
&lt;span class="p"&gt;5.&lt;/span&gt; Check accessibility compliance

&lt;span class="gu"&gt;## /optimize&lt;/span&gt;
Analyze and optimize:
&lt;span class="p"&gt;1.&lt;/span&gt; Identify performance bottlenecks
&lt;span class="p"&gt;2.&lt;/span&gt; Suggest caching strategies
&lt;span class="p"&gt;3.&lt;/span&gt; Recommend database query improvements
&lt;span class="p"&gt;4.&lt;/span&gt; Propose code splitting opportunities

&lt;span class="gu"&gt;## /summarize [timeframe]&lt;/span&gt;
Generate summary of changes:
&lt;span class="p"&gt;-&lt;/span&gt; Scan git history for [timeframe]
&lt;span class="p"&gt;-&lt;/span&gt; Group changes by category
&lt;span class="p"&gt;-&lt;/span&gt; Highlight breaking changes
&lt;span class="p"&gt;-&lt;/span&gt; Create changelog format
&lt;span class="p"&gt;-&lt;/span&gt; Save to docs/changelog/YYYY-MM.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Context vs. Memory: Strategic Information Management
&lt;/h3&gt;

&lt;p&gt;Every line in your .goosehints file gets sent with every request to goose, consuming input tokens. This is a critical consideration for advanced users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use .goosehints for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coding standards and conventions&lt;/li&gt;
&lt;li&gt;Project architecture patterns&lt;/li&gt;
&lt;li&gt;Testing requirements&lt;/li&gt;
&lt;li&gt;Documentation standards&lt;/li&gt;
&lt;li&gt;Technology stack information&lt;/li&gt;
&lt;li&gt;File structure guidelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use Memory Extension for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User-specific preferences&lt;/li&gt;
&lt;li&gt;Dynamic project state&lt;/li&gt;
&lt;li&gt;Learned patterns from past interactions&lt;/li&gt;
&lt;li&gt;Frequently accessed but changing information&lt;/li&gt;
&lt;li&gt;Personal coding habits&lt;/li&gt;
&lt;li&gt;Context that needs tags/keywords for retrieval&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Token Optimization Strategy:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Minimal .goosehints (Optimized)&lt;/span&gt;

Stack: React 18, TypeScript 5, Node 20
Style: Airbnb, Prettier
Tests: Jest + RTL, 80% coverage
Docs: JSDoc + OpenAPI

[Store detailed standards in Memory Extension]
Use tags: #testing-patterns, #api-conventions, #component-structure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of storing 200+ lines of detailed conventions in .goosehints (processed every request), store them in Memory Extension and retrieve on-demand with tags.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Prompting Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pattern 1: Structured Decomposition
&lt;/h3&gt;

&lt;p&gt;Break complex tasks into explicit phases with validation gates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Refactor the authentication system with the following phased approach:

PHASE 1 - ANALYSIS (Do not proceed to Phase 2 until this is complete)
1. Map current authentication flow
2. Identify all dependencies
3. List potential breaking changes
4. Create rollback strategy
5. Present findings for review

PHASE 2 - TEST COVERAGE (Requires Phase 1 approval)
1. Add tests for existing behavior
2. Achieve 95%+ coverage
3. Document test scenarios
4. Run full test suite
5. Present coverage report

PHASE 3 - IMPLEMENTATION (Requires Phase 2 passing tests)
1. Create feature branch
2. Implement new authentication layer
3. Maintain backward compatibility
4. Update integration tests
5. Run performance benchmarks

PHASE 4 - VALIDATION (Requires Phase 3 success)
1. Execute full test suite
2. Perform security audit
3. Check performance metrics
4. Validate error handling
5. Review documentation

PHASE 5 - DEPLOYMENT (Requires Phase 4 approval)
1. Create deployment plan
2. Set up monitoring
3. Configure feature flags
4. Document rollback procedure
5. Prepare incident response

**Validation Gate**: Pause after each phase and wait for explicit approval before proceeding.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 2: Perspective-Based Review
&lt;/h3&gt;

&lt;p&gt;Review from multiple perspectives using different expert personas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Review src/api/payment-processor.ts from multiple expert perspectives:

PERSPECTIVE 1 - SECURITY ANALYST
You are a security expert specializing in payment systems.
Focus on:
- Authentication and authorization
- Data encryption in transit and at rest
- PCI DSS compliance
- Input validation and sanitization
- SQL injection prevention
- XSS vulnerabilities
- Rate limiting
- Audit logging

PERSPECTIVE 2 - PERFORMANCE ENGINEER
You are a performance optimization specialist.
Focus on:
- Database query efficiency
- Caching opportunities
- Memory leaks
- Connection pooling
- Async operation handling
- Load testing considerations
- Scalability bottlenecks

PERSPECTIVE 3 - RELIABILITY ENGINEER
You are an SRE focused on system reliability.
Focus on:
- Error handling and recovery
- Retry logic and backoff strategies
- Circuit breaker patterns
- Timeout configurations
- Monitoring and alerting
- Graceful degradation
- Disaster recovery

PERSPECTIVE 4 - ACCESSIBILITY EXPERT
You are an accessibility specialist.
Focus on:
- Error message clarity
- User feedback mechanisms
- Progressive enhancement
- Screen reader compatibility
- Keyboard navigation
- WCAG compliance

For each perspective:
1. List specific findings (with line numbers)
2. Assess severity (Critical/High/Medium/Low)
3. Provide actionable recommendations
4. Suggest specific code changes

Save complete findings to docs/reviews/payment-processor-review.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 3: Constraint-Driven Development
&lt;/h3&gt;

&lt;p&gt;Define explicit constraints that guide implementation decisions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implement real-time notification system with these non-negotiable constraints:

PERFORMANCE CONSTRAINTS:
- Latency: &amp;lt; 100ms for notification delivery
- Throughput: Handle 10,000 concurrent connections
- Memory: &amp;lt; 512MB per instance
- CPU: &amp;lt; 40% average utilization

RELIABILITY CONSTRAINTS:
- Uptime: 99.95% SLA
- Message delivery: At-least-once guarantee
- Failover: &amp;lt; 30 seconds recovery time
- Data loss: Zero tolerance

SCALABILITY CONSTRAINTS:
- Horizontal scaling: Auto-scale from 2-20 instances
- Database connections: Pool size max 20
- WebSocket connections: Support 500 per instance
- Queue depth: Max 10,000 messages

SECURITY CONSTRAINTS:
- Authentication: JWT with 15-minute expiry
- Authorization: Role-based access control
- Encryption: TLS 1.3 for all connections
- Rate limiting: 100 requests/minute per user

OPERATIONAL CONSTRAINTS:
- Monitoring: Prometheus metrics exposed
- Logging: Structured JSON logs
- Tracing: OpenTelemetry integration
- Deployment: Zero-downtime deployments

Implementation approach:
1. Choose technology stack that meets ALL constraints
2. Create proof-of-concept demonstrating constraint adherence
3. Implement comprehensive benchmarking
4. Document constraint validation approach
5. Set up automated constraint testing

**Validation**: After implementation, provide evidence that each constraint is met, including benchmark results and test output.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 4: Error-Driven Refinement
&lt;/h3&gt;

&lt;p&gt;Anticipate and specify error handling comprehensively.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implement data import pipeline with exhaustive error handling:

ERROR CATEGORIES TO HANDLE:

1. INPUT VALIDATION ERRORS
   - Empty file
   - Wrong file format
   - Corrupted data
   - Encoding issues
   - Schema mismatches
   - Missing required fields
   - Invalid data types
   - Constraint violations

2. PROCESSING ERRORS
   - Timeout during processing
   - Memory overflow
   - Circular dependencies
   - Duplicate detection
   - Data transformation failures
   - Business rule violations

3. EXTERNAL SERVICE ERRORS
   - API rate limit exceeded
   - Service unavailable (503)
   - Authentication failures (401)
   - Authorization failures (403)
   - Network timeouts
   - SSL certificate errors

4. STORAGE ERRORS
   - Database connection lost
   - Transaction rollback needed
   - Constraint violations
   - Deadlock detection
   - Disk space exhausted
   - Write permission denied

For EACH error category:
- Define specific exception types
- Implement retry logic where appropriate
- Set up proper logging with context
- Create user-friendly error messages
- Design recovery strategies
- Add monitoring/alerting
- Write comprehensive tests

Error Response Format:
{
  "status": "error",
  "code": "ERR_VALIDATION_001",
  "message": "User-friendly message",
  "details": "Technical details",
  "timestamp": "ISO-8601",
  "trace_id": "uuid",
  "suggestions": ["Actionable", "recovery", "steps"]
}

Test coverage requirement: 100% for all error paths
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 5: Documentation-First Development
&lt;/h3&gt;

&lt;p&gt;Generate comprehensive documentation before implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Design and document a feature before implementation:

FEATURE: Advanced search with filters

Step 1: Create comprehensive design document at docs/features/advanced-search.md

REQUIRED SECTIONS:

1. OVERVIEW
   - Business objective
   - User story
   - Success metrics
   - Timeline and milestones

2. TECHNICAL DESIGN
   - Architecture diagram
   - Data flow
   - API specifications (OpenAPI)
   - Database schema changes
   - Cache strategy
   - Security considerations

3. USER EXPERIENCE
   - Wireframes (describe in detail)
   - User flows
   - Error states
   - Loading states
   - Edge cases
   - Accessibility requirements

4. IMPLEMENTATION PLAN
   - Task breakdown
   - Dependencies
   - Testing strategy
   - Deployment approach
   - Rollback plan
   - Feature flag configuration

5. ACCEPTANCE CRITERIA
   - Functional requirements (Given/When/Then)
   - Non-functional requirements
   - Browser compatibility
   - Performance benchmarks
   - Security validation

6. RISKS AND MITIGATIONS
   - Technical risks
   - Business risks
   - Mitigation strategies
   - Contingency plans

After documentation is complete and approved:
- Generate test cases from acceptance criteria
- Implement features matching documentation exactly
- Keep documentation updated with any changes
- Create user-facing documentation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Workflow Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pattern 6: Iterative Refinement Loop
&lt;/h3&gt;

&lt;p&gt;Build in explicit review cycles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implement OAuth2 authentication with iterative refinement:

ITERATION 1 - BASIC IMPLEMENTATION
Implement minimal OAuth2 flow:
- Authorization endpoint
- Token endpoint
- Basic token validation

After implementation, STOP and:
- Run security scan (npm audit, Snyk)
- Execute test suite
- Check performance
- Review code with OWASP guidelines
Present findings before continuing.

ITERATION 2 - ERROR HANDLING
Based on iteration 1 review:
- Add comprehensive error handling
- Implement retry logic
- Add request logging
- Set up monitoring

After implementation, STOP and:
- Test failure scenarios
- Verify error messages
- Check logging output
- Validate monitoring alerts
Present findings before continuing.

ITERATION 3 - SECURITY HARDENING
Based on iteration 2 review:
- Implement rate limiting
- Add CSRF protection
- Set up token rotation
- Configure secure headers

After implementation, STOP and:
- Run penetration tests
- Verify security headers
- Test rate limiting
- Review audit logs
Present findings before continuing.

ITERATION 4 - OPTIMIZATION
Based on iteration 3 review:
- Add caching layer
- Optimize database queries
- Implement connection pooling
- Configure CDN

After implementation:
- Run load tests
- Compare performance metrics
- Document optimizations
Final review and approval.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 7: Test-Driven Validation
&lt;/h3&gt;

&lt;p&gt;Generate tests that validate requirements explicitly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create comprehensive test suite for user registration:

REQUIREMENTS-BASED TEST GENERATION:

For each requirement in docs/requirements/user-registration.md:
1. Generate test case name following pattern: "should [requirement] when [condition]"
2. Write test implementation
3. Add edge case variations
4. Include performance assertions where relevant

REQUIRED TEST CATEGORIES:

1. HAPPY PATH TESTS
   - Valid registration completes successfully
   - User receives confirmation email
   - User can log in with new credentials
   - Profile data is correctly stored

2. VALIDATION TESTS
   For each field (email, password, name, etc.):
   - Empty value rejection
   - Invalid format rejection
   - Boundary value testing
   - SQL injection attempt rejection
   - XSS attempt rejection
   - Unicode character handling

3. BUSINESS LOGIC TESTS
   - Duplicate email rejection
   - Password strength validation
   - Email verification requirement
   - Terms acceptance validation
   - Age verification (if applicable)

4. SECURITY TESTS
   - Password hashing validation
   - Token generation security
   - Session management
   - Rate limiting enforcement
   - CAPTCHA validation

5. INTEGRATION TESTS
   - Email service integration
   - Database transaction handling
   - External API interactions
   - Event publishing verification

6. PERFORMANCE TESTS
   - Registration completes in &amp;lt; 2 seconds
   - Database queries &amp;lt; 100ms
   - Email queuing &amp;lt; 50ms

TEST QUALITY REQUIREMENTS:
- Each test must be independent
- Use factories for test data
- Clean up after each test
- Mock external dependencies
- Include descriptive failure messages
- Assert all observable outcomes
- Achieve 100% branch coverage

Generate tests BEFORE implementation to serve as specification.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 8: Contextual Problem-Solving
&lt;/h3&gt;

&lt;p&gt;Provide rich context for better solutions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Debug production issue with full context:

PROBLEM STATEMENT:
Users report intermittent 500 errors during checkout process.

ENVIRONMENTAL CONTEXT:
- Environment: Production
- Region: US-East-1
- Instance count: 12
- Load: ~5,000 requests/minute
- Error rate: 0.5% (started 2 hours ago)
- No recent deployments

TECHNICAL CONTEXT:
- Stack: Node.js 20, Express 4.18, PostgreSQL 14
- Architecture: Microservices with API Gateway
- Cache: Redis 7.0
- Message Queue: RabbitMQ
- Monitoring: DataDog

ERROR DETAILS:
Error: Connection pool exhausted
  at Pool.connect (pg-pool:123)
  at PaymentService.processPayment (payment-service.js:45)
  at OrderController.checkout (order-controller.js:89)

AVAILABLE LOGS:
- Application logs: /var/log/app/*.log
- Database logs: CloudWatch PostgreSQL logs
- API Gateway logs: /var/log/nginx/access.log
- Queue logs: RabbitMQ management console

MONITORING DATA:
- CPU: 45% average (normal)
- Memory: 78% (slightly elevated)
- Database connections: 95/100 (suspicious)
- Cache hit rate: 85% (normal)
- Response time: 250ms avg (normal, except errors)

RECENT CHANGES:
- Marketing campaign launched 3 hours ago
- Traffic increased 30%
- New product category added yesterday

INVESTIGATION APPROACH:
1. Analyze connection pool configuration
2. Check for connection leaks
3. Review recent query patterns
4. Examine transaction handling
5. Check for long-running queries
6. Verify connection timeout settings
7. Review error correlation with traffic spikes

Provide:
- Root cause analysis
- Immediate mitigation steps
- Long-term solution
- Prevention strategy
- Monitoring improvements
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Optimization Techniques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Technique 1: Token-Aware Prompting
&lt;/h3&gt;

&lt;p&gt;.goosehints files consume input tokens with every request, which impacts costs for users paying for LLM access. Optimize for token efficiency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (Token-Heavy):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Detailed Style Guide (1,500+ tokens)&lt;/span&gt;

&lt;span class="gu"&gt;## Function Naming Conventions&lt;/span&gt;
Functions should be named using camelCase...
[200 lines of detailed conventions]

&lt;span class="gu"&gt;## Variable Naming Conventions&lt;/span&gt;
Variables should follow these patterns...
[150 lines of detailed patterns]

&lt;span class="gu"&gt;## Code Organization&lt;/span&gt;
Files should be organized in the following manner...
[300 lines of structure details]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (Token-Optimized):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Style Guide (Core - 200 tokens)&lt;/span&gt;

Style: Airbnb + Prettier
Naming: camelCase functions, PascalCase classes
Structure: feature-based modules
Tests: co-located &lt;span class="err"&gt;*&lt;/span&gt;.test.ts
Docs: JSDoc public APIs only

[Detailed conventions in Memory Extension]
Tags: #naming, #structure, #testing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Technique 2: Progressive Context Loading
&lt;/h3&gt;

&lt;p&gt;Use @-mentions for frequently-needed documentation to include file content in context, but reference without @ for optional or very large files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Project Context (Smart Loading)&lt;/span&gt;

&lt;span class="gu"&gt;## Always Available&lt;/span&gt;
@docs/api-core.md              (Essential - auto-load)
@docs/security-requirements.md (Critical - auto-load)

&lt;span class="gu"&gt;## Load On Demand&lt;/span&gt;
Reference docs/api-extended.md      (Use when working on API)
Reference docs/deployment-guide.md  (Use when deploying)
Reference docs/architecture.md      (Use for system design)

&lt;span class="gu"&gt;## Tags for Memory&lt;/span&gt;
Common patterns: #auth-patterns, #db-patterns, #error-handling
Debugging guides: #debug-auth, #debug-database, #debug-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Technique 3: Session Management
&lt;/h3&gt;

&lt;p&gt;LLMs have context windows, which are limits on how much conversation history they can retain; monitor token usage and start new sessions as needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session Planning Strategy:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Session 1: Architecture Design
- Design system architecture
- Create API specifications
- Document data models
Total estimated tokens: ~20,000
[Start new session after this]

# Session 2: Implementation (Backend)
- Implement API endpoints
- Write tests
- Set up database
Total estimated tokens: ~30,000
[Start new session after this]

# Session 3: Implementation (Frontend)
- Build UI components
- Integrate with API
- Write E2E tests
Total estimated tokens: ~25,000

# Session 4: Review &amp;amp; Refinement
- Code review
- Performance optimization
- Documentation updates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Advanced Extension Management
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Selective Extension Enablement
&lt;/h3&gt;

&lt;p&gt;Turning on too many extensions can degrade performance; enable only essential extensions and tools to improve tool selection accuracy and save context window space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategic Extension Configuration:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Task-Specific Extension Profiles

## Profile: Backend Development
Extensions:
- Developer (core)
- Database MCP
- Git MCP
- Testing tools
Disabled: Browser, Design tools

## Profile: Frontend Development
Extensions:
- Developer (core)
- Browser Controller
- Component library MCP
- Design system MCP
Disabled: Database tools, Infrastructure

## Profile: DevOps
Extensions:
- Developer (core)
- Docker MCP
- Kubernetes MCP
- Cloud provider MCP
Disabled: UI tools, Testing frameworks

Switch profiles based on task:
`goose configure` → Enable relevant extensions only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Custom MCP Integration
&lt;/h3&gt;

&lt;p&gt;Build custom MCP servers for domain-specific tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create custom MCP server for internal tools:

PURPOSE: Integrate proprietary deployment system

TOOLS TO EXPOSE:
1. deploy_to_staging(service, version, config)
2. promote_to_production(service, version, approvals)
3. rollback_deployment(service, target_version)
4. get_deployment_status(service, environment)
5. trigger_health_check(service, environment)

IMPLEMENTATION REQUIREMENTS:
- OAuth authentication with internal IdP
- Rate limiting (10 requests/minute)
- Comprehensive error handling
- Audit logging for all operations
- Dry-run mode for testing
- Approval workflow integration

INTEGRATION WITH goose:
- Add to .goose/config.yaml
- Document in .goosehints
- Create usage examples
- Set up monitoring

Enable goose to handle deployment workflows end-to-end:
"Deploy user-service v2.3.4 to staging, run tests, and promote to production if tests pass"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pattern 9: .gooseignore for Protection
&lt;/h3&gt;

&lt;p&gt;goose can be eager to make changes; you can stop it from changing specific files by creating a .gooseignore file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .gooseignore - Critical File Protection

# Secrets and credentials
.env
.env.*
**/secrets/**
**/credentials/**
**/*_key.pem
**/*_secret.*

# Configuration
**/production.config.*
**/prod.*.yaml
kubernetes/prod/**

# Database
**/migrations/*.sql
**/seeds/production/**

# CI/CD
.github/workflows/production.yml
.circleci/config.yml

# Documentation
docs/architecture/decisions/**
CHANGELOG.md
LICENSE

# Generated files
**/dist/**
**/build/**
**/coverage/**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 10: Permission-Based Workflows
&lt;/h3&gt;

&lt;p&gt;You can customize supervision levels - Auto Mode for full autonomy, or require approval before actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk-Based Permission Strategy:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;High-Risk Operations (Require Approval):
- Database migrations
- Production deployments
- Dependency updates
- Security-related changes
- API contract modifications

Medium-Risk Operations (Notify):
- Test modifications
- Documentation updates
- Configuration changes
- Refactoring

Low-Risk Operations (Auto-Approve):
- Code formatting
- Comment additions
- Variable renaming
- Import organization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Measuring Success
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prompt Quality Metrics
&lt;/h3&gt;

&lt;p&gt;Track improvement over time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Evaluate prompt effectiveness:

METRIC 1: First-Try Success Rate
- Track: % of tasks completed without iteration
- Target: &amp;gt; 80% for routine tasks
- Measure: Weekly review of session logs

METRIC 2: Token Efficiency
- Track: Average tokens per completed task
- Target: Reduce by 20% over 3 months
- Measure: Monitor LLM API costs

METRIC 3: Context Relevance
- Track: % of .goosehints content used per session
- Target: &amp;gt; 60% utilization
- Measure: Review context window usage

METRIC 4: Error Recovery
- Track: Steps required to recover from errors
- Target: &amp;lt; 3 steps average
- Measure: Count correction cycles

METRIC 5: Task Complexity Handling
- Track: Successfully completed complex tasks
- Target: Increase by 50% over 6 months
- Measure: Define complexity rubric
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Advanced prompt engineering for goose requires understanding its agentic nature, strategically managing context, and designing prompts that guide rather than restrict. By implementing these techniques:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Think in tasks, not conversations&lt;/strong&gt; - Structure prompts as clear, actionable tasks with success criteria&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architect your context&lt;/strong&gt; - Use hierarchical .goosehints, Memory Extension, and @-mentions strategically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize for tokens&lt;/strong&gt; - Balance comprehensive context with cost efficiency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build in validation&lt;/strong&gt; - Include explicit review gates and multi-perspective analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterate systematically&lt;/strong&gt; - Use structured refinement loops for complex implementations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure and improve&lt;/strong&gt; - Track metrics to continuously enhance your prompting strategy&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The most effective goose users treat prompting as a systematic engineering discipline, applying the same rigor to AI interaction as they do to software development. Start with these patterns, experiment with variations, and develop your own sophisticated techniques based on your specific workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://block.github.io/goose/" rel="noopener noreferrer"&gt;Official goose Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/block/goose" rel="noopener noreferrer"&gt;goose GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Community: &lt;a href="https://discord.gg/goose-oss" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>goose</category>
      <category>promptengineering</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building Semantica: An AI-Powered Academic Search Platform with MindsDB</title>
      <dc:creator>Abhi</dc:creator>
      <pubDate>Wed, 29 Oct 2025 10:09:28 +0000</pubDate>
      <link>https://forem.com/better-boy/building-semantica-an-ai-powered-academic-search-platform-with-mindsdb-596n</link>
      <guid>https://forem.com/better-boy/building-semantica-an-ai-powered-academic-search-platform-with-mindsdb-596n</guid>
      <description>&lt;p&gt;&lt;em&gt;Connecting ideas across the frontiers of knowledge&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 The Problem: Information Overload in Academic Research
&lt;/h2&gt;

&lt;p&gt;Picture this: You're a researcher trying to explore cutting-edge developments in quantum computing. You fire up your favorite search engine and type "quantum error correction." What do you get? Thousands of results. Papers from arXiv, patents, preprints from bioRxiv, medRxiv, chemRxiv—all scattered across different platforms, each with its own search interface.&lt;/p&gt;

&lt;p&gt;But here's the real challenge:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keyword-only search fails to capture semantic meaning&lt;/strong&gt;: Traditional search engines look for exact keyword matches. If you search for "machine learning in healthcare," you might miss groundbreaking papers that use terms like "artificial intelligence in medical diagnostics" or "neural networks for disease prediction."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fragmented knowledge sources&lt;/strong&gt;: Academic papers live in silos—arXiv for physics and CS, bioRxiv for biology, medRxiv for medicine, chemRxiv for chemistry, and patents in yet another database. Researchers waste hours searching across multiple platforms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No contextual understanding&lt;/strong&gt;: Found an interesting paper? Great! Now you need to read all 50 pages to understand if it's relevant to your research. What if you could just &lt;em&gt;ask&lt;/em&gt; the paper questions?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Comparing multiple papers is tedious&lt;/strong&gt;: Want to understand how three different approaches to the same problem compare? You'll need to read all three papers, take notes, and synthesize the information yourself.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The research community needed something better.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 The Solution: Semantica
&lt;/h2&gt;

&lt;p&gt;Enter &lt;strong&gt;Semantica&lt;/strong&gt;—an intelligent academic search and chat platform that revolutionizes how researchers discover and interact with scientific literature.&lt;/p&gt;

&lt;p&gt;Semantica solves these problems by:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Semantic Search&lt;/strong&gt;: Find papers by &lt;em&gt;meaning&lt;/em&gt;, not just keywords&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Multi-Source Integration&lt;/strong&gt;: Search across arXiv, bioRxiv, medRxiv, chemRxiv, and patents simultaneously&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;AI-Powered Chat&lt;/strong&gt;: Ask questions and get answers directly from papers&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Multi-Document Analysis&lt;/strong&gt;: Compare and analyze up to 4 papers in a single conversation&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Hybrid Search&lt;/strong&gt;: Combine traditional keyword search with semantic understanding&lt;/p&gt;



&lt;p&gt;Github Repo - &lt;a href="https://github.com/Better-Boy/Semantica" rel="noopener noreferrer"&gt;Semantica&lt;/a&gt;&lt;br&gt;
Video Demo - &lt;a href="https://youtu.be/8wIhcXm_7nk" rel="noopener noreferrer"&gt;Semantica&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  🏗️ How I Built It: The MindsDB Magic
&lt;/h2&gt;

&lt;p&gt;The secret sauce behind Semantica is &lt;strong&gt;MindsDB&lt;/strong&gt;—an AI platform that transforms databases into AI-powered systems. Here's how we leveraged MindsDB's powerful features:&lt;/p&gt;
&lt;h3&gt;
  
  
  1. &lt;strong&gt;Knowledge Bases with pgvector Integration&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;At the heart of Semantica lies MindsDB's knowledge base functionality, which seamlessly integrates with PostgreSQL and the pgvector extension.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I did:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created a PostgreSQL database with the pgvector extension for efficient vector storage&lt;/li&gt;
&lt;li&gt;Connected it to MindsDB using their database integration&lt;/li&gt;
&lt;li&gt;Created a knowledge base that automatically generates embeddings using OpenAI's &lt;code&gt;text-embedding-3-small&lt;/code&gt; model
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Sample startup code
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup_knowledge_base&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Create knowledge base in MindsDB with automatic embeddings&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;create_kb_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    CREATE KNOWLEDGE_BASE &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;kb_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
    USING
        model = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;embedding_model&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,
        storage = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;db_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;storage_table&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;;
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_kb_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Why this is powerful:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic embeddings&lt;/strong&gt;: MindsDB handles the complexity of generating and storing vector embeddings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata filtering&lt;/strong&gt;: We can filter by publication year, category, source, and more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid search&lt;/strong&gt;: MindsDB supports combining semantic similarity with traditional SQL WHERE clauses&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. &lt;strong&gt;Semantic Search with Hybrid Capabilities&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;One of MindsDB's standout features is its hybrid search capability, which we extensively use in Semantica.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The search query looks like this:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relevance&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;my_knowledge_base&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'quantum error correction'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;hybrid_search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; 
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;hybrid_search_alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'arxiv'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;published_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2024'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Breaking it down:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;content = 'query'&lt;/code&gt;: Performs semantic similarity search&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hybrid_search = true&lt;/code&gt;: Enables hybrid mode (semantic + keyword)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hybrid_search_alpha&lt;/code&gt;: Controls the balance (0.0 = pure keyword, 1.0 = pure semantic)&lt;/li&gt;
&lt;li&gt;Additional WHERE clauses: Traditional SQL filtering on metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The result?&lt;/strong&gt; Users can fine-tune their search from pure semantic understanding to traditional keyword matching, getting the best of both worlds!&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Dynamic AI Agents for Multi-Document Chat&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Here's where things get really exciting. When a user selects papers to chat with, Semantica:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Creates individual knowledge bases for each paper&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;KNOWLEDGE_BASE&lt;/span&gt; &lt;span class="n"&gt;paper_123_kb&lt;/span&gt;
   &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;small&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="k"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_pgvector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pgvector_storage_table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;paper_123_kb&lt;/span&gt;
   &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstract&lt;/span&gt;
   &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;my_pgvector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;paper_raw&lt;/span&gt;
   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;article_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'123'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'arxiv'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generates a custom AI agent with access to all selected papers&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;AGENT&lt;/span&gt; &lt;span class="n"&gt;research_assistant&lt;/span&gt;
   &lt;span class="k"&gt;USING&lt;/span&gt; 
       &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'gpt-4o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;skills&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
       &lt;span class="n"&gt;knowledge_bases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'paper_123_kb'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'paper_456_kb'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'paper_789_kb'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
       &lt;span class="n"&gt;prompt_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'You are a research assistant...'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enables natural language queries across all papers&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; 
   &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;research_assistant&lt;/span&gt; 
   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Compare the methodologies used in these papers'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this approach is brilliant:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each chat session gets a dedicated AI agent&lt;/li&gt;
&lt;li&gt;The agent has deep understanding of all selected papers through their knowledge bases&lt;/li&gt;
&lt;li&gt;MindsDB handles the RAG (Retrieval-Augmented Generation) pipeline automatically&lt;/li&gt;
&lt;li&gt;Responses are grounded in the actual paper content, reducing hallucinations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Reranking for Improved Relevance&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;MindsDB's knowledge base supports reranking, which we leverage to improve search quality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;knowledge_base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;reranking_model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai"&lt;/span&gt;
    &lt;span class="na"&gt;model_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reranking takes the initial search results and uses a more powerful model (GPT-4o) to reorder them based on true relevance to the query. This two-stage approach provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast initial retrieval using vector similarity&lt;/li&gt;
&lt;li&gt;High-quality ranking using advanced language understanding&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;Background jobs to keep up&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;MindsDB's knowledge base supports jobs, which we leverage to improve keep the knowledge base updated with latest papers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE JOB kb_sync (
   INSERT INTO kv_kb (
      SELECT * FROM my_pgvector.paper_raw
   )
)
EVERY 1 day;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every paper with the text and metadata is inserted to postgres table &lt;code&gt;my_pgvector.paper_raw&lt;/code&gt; from which it's inserted to the knowledge base.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Technical Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frijk36qlf2er2kzxn2oi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frijk36qlf2er2kzxn2oi.png" alt=" " width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Stack
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────┐
│   Frontend (React + TypeScript)    │
│   - Search interface                │
│   - Chat UI with PDF viewer         │
│   - Filter controls                 │
└─────────────┬───────────────────────┘
              │ REST API
┌─────────────▼───────────────────────┐
│   Backend (FastAPI + Python)        │
│   - /search endpoint                │
│   - /chat/initiate endpoint         │
│   - /chat/completion endpoint       │
└─────────────┬───────────────────────┘
              │ MindsDB SDK
┌─────────────▼───────────────────────┐
│   MindsDB Platform                  │
│   ┌───────────────────────────┐     │
│   │   Knowledge Bases         │     │
│   │   - Main KB (all papers)  │     │
│   │   - Per-paper KBs (chat)  │     │
│   └───────────────────────────┘     │
│   ┌───────────────────────────┐     │
│   │   AI Agents (GPT-4o)      │     │
│   │   - Dynamic generation    │     │
│   │   - Multi-KB access       │     │
│   └───────────────────────────┘     │
└─────────────┬───────────────────────┘
              │ PostgreSQL Protocol
┌─────────────▼───────────────────────┐
│   PostgreSQL + pgvector             │
│   - Vector embeddings               │
│   - Metadata storage                │
│   - Fast similarity search          │
└─────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Data Flow for Search
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User enters query&lt;/strong&gt;: "machine learning for optics"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend sends request&lt;/strong&gt; to &lt;code&gt;/api/v1/search&lt;/code&gt; with query and filters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend constructs MindsDB query&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relevance&lt;/span&gt;
   &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;my_knowledge_base&lt;/span&gt;
   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'machine learning for optics'&lt;/span&gt;
     &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;hybrid_search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
     &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;hybrid_search_alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;MindsDB processes query&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Generates query embedding&lt;/li&gt;
&lt;li&gt;Performs vector similarity search in pgvector&lt;/li&gt;
&lt;li&gt;Applies filters&lt;/li&gt;
&lt;li&gt;Reranks results&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Results transformed and returned&lt;/strong&gt; to frontend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User sees ranked papers&lt;/strong&gt; with relevance scores&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Data Flow for Chat
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User selects papers&lt;/strong&gt; (e.g., 3 papers about quantum computing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend initiates chat&lt;/strong&gt; via &lt;code&gt;/api/v1/chat/initiate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend creates infrastructure&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Creates 3 individual knowledge bases (one per paper)&lt;/li&gt;
&lt;li&gt;Populates each KB with paper content&lt;/li&gt;
&lt;li&gt;Creates an AI agent with access to all 3 KBs&lt;/li&gt;
&lt;li&gt;Returns agent ID to frontend&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User asks question&lt;/strong&gt;: "What are the key challenges?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend sends message&lt;/strong&gt; via &lt;code&gt;/api/v1/chat/completion&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend queries agent&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; 
   &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;agent_abc123&lt;/span&gt; 
   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'What are the key challenges?'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;MindsDB agent&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Retrieves relevant context from all 3 knowledge bases&lt;/li&gt;
&lt;li&gt;Constructs answer using GPT-4o&lt;/li&gt;
&lt;li&gt;Returns grounded response&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answer displayed&lt;/strong&gt; in chat interface with markdown formatting&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🚀 Key Features in Action
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Feature 1: Multi-Source Semantic Search
&lt;/h3&gt;

&lt;p&gt;Users can search across five different academic sources simultaneously:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Query&lt;/strong&gt;: "CRISPR applications in gene therapy"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens behind the scenes:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Query is embedded using OpenAI's embedding model&lt;/li&gt;
&lt;li&gt;MindsDB performs vector similarity search across all sources&lt;/li&gt;
&lt;li&gt;Results are filtered by user-selected corpora&lt;/li&gt;
&lt;li&gt;Reranking improves result quality&lt;/li&gt;
&lt;li&gt;Papers are returned ranked by relevance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The UX:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search Results:
📄 "CRISPR-Cas9 Applications in Gene Therapy" (bioRxiv, 2023)
📄 "Therapeutic Genome Editing Methods" (patent, 2024)
📄 "Gene Therapy Advances Using CRISPR" (medRxiv, 2023)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Feature 2: Hybrid Search with Alpha Control
&lt;/h3&gt;

&lt;p&gt;Users can slide between semantic and keyword search:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alpha = 0.0&lt;/strong&gt;: Pure keyword matching (fast, specific)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alpha = 0.5&lt;/strong&gt;: Balanced hybrid search&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alpha = 1.0&lt;/strong&gt;: Pure semantic search (finds conceptually similar papers)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real-world example:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query: "neural networks"&lt;/li&gt;
&lt;li&gt;Alpha 0.0: Returns papers with exact phrase "neural networks"&lt;/li&gt;
&lt;li&gt;Alpha 1.0: Returns papers about "deep learning," "artificial neural systems," "connectionist models"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Feature 3: AI-Powered Multi-Document Chat
&lt;/h3&gt;

&lt;p&gt;The killer feature! Select up to 4 papers and have a conversation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example conversation:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;User&lt;/strong&gt;: What are the main differences in methodology between these papers?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Agent&lt;/strong&gt;: Based on the three papers you selected:&lt;/p&gt;

&lt;p&gt;Paper 1 uses a supervised learning approach with labeled datasets...&lt;br&gt;
Paper 2 employs reinforcement learning with reward shaping...&lt;br&gt;
Paper 3 introduces an unsupervised method using contrastive learning...&lt;/p&gt;

&lt;p&gt;The key distinction is in their learning paradigms, with Paper 1 requiring...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What makes this powerful:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The AI has read and understood all papers&lt;/li&gt;
&lt;li&gt;Answers are grounded in actual paper content&lt;/li&gt;
&lt;li&gt;Can compare, contrast, and synthesize information&lt;/li&gt;
&lt;li&gt;Cites specific findings from the papers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Feature 4: Live PDF Viewing
&lt;/h3&gt;

&lt;p&gt;While chatting, users can view the actual PDFs in a split-pane interface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Left: PDF viewer with Google Docs integration&lt;/li&gt;
&lt;li&gt;Right: Chat interface&lt;/li&gt;
&lt;li&gt;Switch between papers with one click&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 The Impact of MindsDB Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before MindsDB: The Traditional Approach
&lt;/h3&gt;

&lt;p&gt;Building this without MindsDB would require:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Manually generate embeddings
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-embedding-3-small&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;

&lt;span class="c1"&gt;# Manually implement vector search
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pgvector&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_papers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;query_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_embedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        SELECT id, metadata, 
               1 - (embedding &amp;lt;=&amp;gt; %s::vector) as similarity
        FROM papers
        ORDER BY embedding &amp;lt;=&amp;gt; %s::vector
        LIMIT %s
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Manually implement RAG
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;answer_question&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;paper_ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Retrieve relevant chunks from papers
&lt;/span&gt;    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;retrieve_relevant_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;paper_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Construct prompt with context
&lt;/span&gt;    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Context: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Question: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Answer:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Call GPT-4
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;

&lt;span class="c1"&gt;# Manually implement hybrid search
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hybrid_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;semantic_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;vector_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;keyword_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;full_text_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;combine_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;semantic_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keyword_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problems with this approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;😰 200+ lines of complex code&lt;/li&gt;
&lt;li&gt;🐛 Bug-prone embedding management&lt;/li&gt;
&lt;li&gt;🔧 Manual vector database optimization&lt;/li&gt;
&lt;li&gt;📈 Difficult to scale&lt;/li&gt;
&lt;li&gt;⚡ No built-in reranking&lt;/li&gt;
&lt;li&gt;🤯 Complex RAG pipeline implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After MindsDB: The Modern Approach
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create knowledge base (handles embeddings, storage, indexing)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;KNOWLEDGE_BASE&lt;/span&gt; &lt;span class="n"&gt;papers_kb&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'text-embedding-3-small'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="k"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;postgres_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;papers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Search with hybrid capabilities&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;papers_kb&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'quantum computing'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;hybrid_search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;hybrid_search_alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Create AI agent for chat&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;AGENT&lt;/span&gt; &lt;span class="n"&gt;research_agent&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'gpt-4o'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;knowledge_bases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'paper1_kb'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'paper2_kb'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;-- Get answers&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;research_agent&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'What are the key findings?'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Simple, declarative SQL syntax&lt;/li&gt;
&lt;li&gt;✅ Automatic embedding generation and management&lt;/li&gt;
&lt;li&gt;✅ Built-in hybrid search&lt;/li&gt;
&lt;li&gt;✅ Managed RAG pipeline&lt;/li&gt;
&lt;li&gt;✅ Optimized vector search&lt;/li&gt;
&lt;li&gt;✅ Automatic reranking&lt;/li&gt;
&lt;li&gt;✅ Scalable infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Development time saved:&lt;/strong&gt; Approximately 2-3 weeks of implementation and testing!&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. MindsDB Simplifies AI Pipelines
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaway&lt;/strong&gt;: What took weeks to build manually takes hours with MindsDB.&lt;/p&gt;

&lt;p&gt;The knowledge base abstraction is incredibly powerful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need to manage embedding generation&lt;/li&gt;
&lt;li&gt;No need to implement vector search algorithms&lt;/li&gt;
&lt;li&gt;No need to build RAG pipelines from scratch&lt;/li&gt;
&lt;li&gt;Focus on user experience, not infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. SQL as an AI Interface is Powerful
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaway&lt;/strong&gt;: Developers already know SQL. Why not use it for AI?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- This is AI magic disguised as familiar SQL:&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ai_agent&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Summarize this paper'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The learning curve is minimal, but the possibilities are vast.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Hybrid Search is Essential
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaway&lt;/strong&gt;: Don't force users to choose between semantic and keyword search.&lt;/p&gt;

&lt;p&gt;Users want both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Semantic for exploratory research&lt;/li&gt;
&lt;li&gt;Keyword for specific citations/terms&lt;/li&gt;
&lt;li&gt;Hybrid for balanced results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MindsDB makes this trivial with the &lt;code&gt;hybrid_search_alpha&lt;/code&gt; parameter.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Dynamic Resource Creation Unlocks Flexibility
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaway&lt;/strong&gt;: Creating KBs and agents on-the-fly enables powerful features.&lt;/p&gt;

&lt;p&gt;Rather than one monolithic agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create focused, specialized agents per session&lt;/li&gt;
&lt;li&gt;Tailor knowledge bases to user selection&lt;/li&gt;
&lt;li&gt;Scale horizontally (more sessions = more agents)&lt;/li&gt;
&lt;li&gt;Clean architecture with clear boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Developer Experience Matters
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaway&lt;/strong&gt;: Fast setup = more adoption.&lt;/p&gt;

&lt;p&gt;Our automated startup script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Eliminates manual configuration&lt;/li&gt;
&lt;li&gt;Reduces errors&lt;/li&gt;
&lt;li&gt;Gets new contributors productive immediately&lt;/li&gt;
&lt;li&gt;Serves as living documentation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🌟 Real-World Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use Case 1: Literature Review
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: PhD student researching quantum error correction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search: "quantum error correction near-term devices"&lt;/li&gt;
&lt;li&gt;Enable hybrid search (alpha = 0.7) for balanced results&lt;/li&gt;
&lt;li&gt;Filter to 2023-2024, arXiv + patents&lt;/li&gt;
&lt;li&gt;Select 4 most relevant papers&lt;/li&gt;
&lt;li&gt;Initiate chat&lt;/li&gt;
&lt;li&gt;Ask: "What are the different approaches to reducing qubit overhead?"&lt;/li&gt;
&lt;li&gt;Ask: "Which paper reports the best error rates?"&lt;/li&gt;
&lt;li&gt;Ask: "What are the experimental challenges mentioned?"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Outcome&lt;/strong&gt;: 30-minute conversation replaces hours of reading and note-taking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case 2: Cross-Domain Research
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: Biomedical engineer exploring AI applications in medicine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search: "machine learning medical diagnostics"&lt;/li&gt;
&lt;li&gt;Select sources: arXiv, bioRxiv, medRxiv&lt;/li&gt;
&lt;li&gt;Hybrid search to catch both ML papers and medical papers&lt;/li&gt;
&lt;li&gt;Find papers bridging CS and medicine&lt;/li&gt;
&lt;li&gt;Chat with selected papers to understand interdisciplinary approaches&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Outcome&lt;/strong&gt;: Discovers connections between computer science and medical research that single-domain search would miss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case 3: Patent Analysis
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: R&amp;amp;D team checking novelty of invention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search: "graph neural networks semiconductor design"&lt;/li&gt;
&lt;li&gt;Enable patents corpus&lt;/li&gt;
&lt;li&gt;Filter to recent years (2022-2024)&lt;/li&gt;
&lt;li&gt;Review patents in relevant space&lt;/li&gt;
&lt;li&gt;Chat: "What techniques are patented in this domain?"&lt;/li&gt;
&lt;li&gt;Chat: "Are there any patents specifically covering [our approach]?"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Outcome&lt;/strong&gt;: Efficient prior art search with AI-assisted analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case 4: Teaching &amp;amp; Learning
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: Professor preparing lecture on CRISPR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Search: "CRISPR gene therapy clinical trials"&lt;/li&gt;
&lt;li&gt;Select foundational papers + recent advances&lt;/li&gt;
&lt;li&gt;Chat: "Explain the evolution from bench to bedside"&lt;/li&gt;
&lt;li&gt;Chat: "What are the key safety concerns?"&lt;/li&gt;
&lt;li&gt;Chat: "Suggest examples for undergraduate vs graduate courses"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Outcome&lt;/strong&gt;: AI becomes a teaching assistant, helping structure educational content.&lt;/p&gt;




&lt;h2&gt;
  
  
  🙏 Acknowledgments
&lt;/h2&gt;

&lt;p&gt;This project wouldn't exist without:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MindsDB Team&lt;/strong&gt;: For building an incredible AI platform and hosting Hacktoberfest&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI&lt;/strong&gt;: For GPT-4o and embedding models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL &amp;amp; pgvector&lt;/strong&gt;: For robust vector storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI &amp;amp; React&lt;/strong&gt;: For excellent developer experiences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Open Source Community&lt;/strong&gt;: For countless libraries and tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Special thanks to MindsDB for making AI accessible to developers worldwide!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;#MindsDB #Hacktoberfest #AI #SemanticSearch #AcademicResearch #OpenSource #RAG #VectorDatabase #GPT4 #Python #React&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Made with ❤️ for MindsDB Hacktoberfest 2025&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mindsdb</category>
      <category>rag</category>
      <category>ai</category>
    </item>
    <item>
      <title>RAG Pipelines Made Simple: Ballerina for AI-Powered Search</title>
      <dc:creator>Abhi</dc:creator>
      <pubDate>Tue, 28 Oct 2025 21:53:26 +0000</pubDate>
      <link>https://forem.com/better-boy/rag-pipelines-made-simple-ballerina-for-ai-powered-search-3lpi</link>
      <guid>https://forem.com/better-boy/rag-pipelines-made-simple-ballerina-for-ai-powered-search-3lpi</guid>
      <description>&lt;p&gt;&lt;em&gt;Why building production-ready RAG systems shouldn't feel like assembling IKEA furniture in the dark&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The RAG Reality Check
&lt;/h2&gt;

&lt;p&gt;Picture this: You've just convinced your team to build an AI-powered documentation search. The plan is simple—take your company's docs, throw them into a vector database, add some LLM magic, and boom: instant answers for your users.&lt;/p&gt;

&lt;p&gt;Three weeks later, you're drowning in boilerplate. Your codebase looks like a Frankenstein monster stitched together from five different libraries. There's error handling scattered everywhere (or worse, nowhere). You're manually chunking documents, wrestling with embedding APIs, debugging vector similarity algorithms, and somehow your "simple" RAG pipeline has morphed into 2,000 lines of glue code.&lt;/p&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;

&lt;p&gt;This is where Ballerina walks in, takes a look at your mess, and says: &lt;em&gt;"Why are you working so hard?"&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes RAG Systems Complex?
&lt;/h2&gt;

&lt;p&gt;Before we dive into the solution, let's break down why RAG pipelines are deceptively complex:&lt;/p&gt;

&lt;h3&gt;
  
  
  The RAG Dance (and Why It's Tricky)
&lt;/h3&gt;

&lt;p&gt;A typical RAG workflow involves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Document Loading&lt;/strong&gt;: Read files from various sources (PDFs, markdown, databases)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunking Strategy&lt;/strong&gt;: Split documents intelligently without losing context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embedding Generation&lt;/strong&gt;: Convert text chunks into vectors using an embedding model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector Storage&lt;/strong&gt;: Store embeddings efficiently with metadata&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query Processing&lt;/strong&gt;: When a user asks a question, embed their query&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Similarity Search&lt;/strong&gt;: Find the most relevant document chunks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context Augmentation&lt;/strong&gt;: Combine retrieved chunks with the user's query&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM Generation&lt;/strong&gt;: Send the enriched prompt to an LLM for a final answer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In traditional frameworks, each of these steps requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choosing and configuring multiple libraries&lt;/li&gt;
&lt;li&gt;Writing integration code between incompatible APIs&lt;/li&gt;
&lt;li&gt;Manual error handling at every boundary&lt;/li&gt;
&lt;li&gt;Custom observability instrumentation&lt;/li&gt;
&lt;li&gt;Performance optimization from scratch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most developers spend 70% of their time on plumbing and only 30% on actual AI logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enter Ballerina: Network-Native Meets AI-Native
&lt;/h2&gt;

&lt;p&gt;Ballerina was designed with a radical idea: &lt;em&gt;What if distributed systems programming was actually... easy?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, with Ballerina's AI module, this philosophy extends to RAG pipelines. Instead of stitching together separate libraries for embeddings, vector stores, chunking, and LLMs, you get a unified, type-safe API that handles the complexity for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Aha!" Moment
&lt;/h3&gt;

&lt;p&gt;Let's compare building a RAG system in two approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional Approach (Python + Multiple Libraries):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.document_loaders&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TextLoader&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.text_splitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.embeddings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbeddings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.vectorstores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.llms&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="c1"&gt;# Configure everything separately
&lt;/span&gt;&lt;span class="n"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextLoader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./leave_policy.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;text_splitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;chunk_overlap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text_splitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;embeddings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAIEmbeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPENAI_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;vectorstore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Chroma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPENAI_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Now try to make them work together...
&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How many leave days can I carry forward?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vectorstore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;similarity_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page_content&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Context: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Question: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Ballerina Approach:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import ballerina/ai;
import ballerina/io;

// Initialize the RAG system
final ai:VectorStore vectorStore = check new ai:InMemoryVectorStore();
final ai:EmbeddingProvider embeddingProvider = 
    check ai:getDefaultEmbeddingProvider();
final ai:KnowledgeBase knowledgeBase = 
    new ai:VectorKnowledgeBase(vectorStore, embeddingProvider);
final ai:ModelProvider modelProvider = check ai:getDefaultModelProvider();

public function main() returns error? {
    // Load and ingest documents
    ai:DataLoader loader = check new ai:TextDataLoader("./leave_policy.md");
    ai:Document|ai:Document[] documents = check loader.load();
    check knowledgeBase.ingest(documents);

    // Query and get answer
    string query = "How many leave days can I carry forward?";
    ai:QueryMatch[] matches = check knowledgeBase.retrieve(query, 10);
    ai:Chunk[] context = from ai:QueryMatch match in matches 
                         select match.chunk;

    ai:ChatUserMessage augmentedQuery = ai:augmentUserQuery(context, query);
    ai:ChatAssistantMessage answer = check modelProvider-&amp;gt;chat(augmentedQuery);

    io:println("Answer: ", answer.content);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what's &lt;strong&gt;different&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No manual chunking configuration (handled intelligently by default)&lt;/li&gt;
&lt;li&gt;No explicit embedding API calls (abstracted away)&lt;/li&gt;
&lt;li&gt;No prompt engineering boilerplate (built-in augmentation)&lt;/li&gt;
&lt;li&gt;No manual context concatenation (query expressions handle it)&lt;/li&gt;
&lt;li&gt;Clean error handling with &lt;code&gt;check&lt;/code&gt; keyword&lt;/li&gt;
&lt;li&gt;Everything is type-safe&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Building a Real RAG System: Employee Handbook Assistant
&lt;/h2&gt;

&lt;p&gt;Let's build something practical: an AI assistant that answers questions about your company's employee handbook. We'll cover the complete journey from document ingestion to production deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Setup: Configuration Made Easy
&lt;/h3&gt;

&lt;p&gt;First, let's understand Ballerina's provider pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import ballerina/ai;
import ballerina/io;

// The VectorStore holds your embeddings
final ai:VectorStore vectorStore = check new ai:InMemoryVectorStore();

// EmbeddingProvider converts text to vectors
// Uses your configured provider (OpenAI, Cohere, etc.) via Ballerina VS Code command
final ai:EmbeddingProvider embeddingProvider = 
    check ai:getDefaultEmbeddingProvider();

// KnowledgeBase combines storage + embeddings
final ai:KnowledgeBase knowledgeBase = 
    new ai:VectorKnowledgeBase(vectorStore, embeddingProvider);

// ModelProvider handles LLM chat completions
final ai:ModelProvider modelProvider = check ai:getDefaultModelProvider();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's happening here?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ballerina uses a configuration-based approach. Instead of hardcoding API keys and model names, you configure providers through Ballerina's VS Code extension or configuration files. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No secrets in code&lt;/li&gt;
&lt;li&gt;Easy switching between providers (OpenAI, Azure, local models)&lt;/li&gt;
&lt;li&gt;Environment-specific configurations&lt;/li&gt;
&lt;li&gt;Type-safe provider interfaces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: This example uses the default embedding provider and model provider implementations. To generate the necessary configuration, open up the VS Code command palette (Ctrl + Shift + P or command + shift + P), and run the Configure default WSO2 Model Provider command to add your configuration to the Config.toml file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Document Ingestion - Simpler Than You Think
&lt;/h3&gt;

&lt;p&gt;The hardest part of building a RAG system is usually getting data in. Ballerina's &lt;code&gt;DataLoader&lt;/code&gt; abstractions make this trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public function ingestEmployeeHandbook() returns error? {
    // Load a single document
    ai:DataLoader policyLoader = check new ai:TextDataLoader("./leave_policy.md");
    ai:Document|ai:Document[] policyDocs = check policyLoader.load();
    check knowledgeBase.ingest(policyDocs);
    io:println("✅ Leave policy ingested");

    // Load multiple documents from a directory
    ai:DataLoader benefitsLoader = check new ai:TextDataLoader("./benefits/");
    ai:Document|ai:Document[] benefitsDocs = check benefitsLoader.load();
    check knowledgeBase.ingest(benefitsDocs);
    io:println("✅ Benefits documentation ingested");

    // You can also load PDFs, Word docs, etc.
    ai:DataLoader handbookLoader = check new ai:PDFDataLoader("./handbook.pdf");
    ai:Document|ai:Document[] handbookDocs = check handbookLoader.load();
    check knowledgeBase.ingest(handbookDocs);
    io:println("✅ Employee handbook ingested");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Magic Behind the Scenes:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you call &lt;code&gt;knowledgeBase.ingest()&lt;/code&gt;, Ballerina automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Chunks your documents&lt;/strong&gt; using intelligent text splitting (respects sentence boundaries, maintains context)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generates embeddings&lt;/strong&gt; for each chunk using your configured embedding provider&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stores vectors&lt;/strong&gt; with metadata in the vector store&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles errors&lt;/strong&gt; gracefully with proper propagation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No manual chunking. No explicit embedding calls. Just load and ingest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: The Query Pipeline - Where RAG Shines
&lt;/h3&gt;

&lt;p&gt;Now comes the fun part: answering questions with context from your documents.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type RAGResponse record {|
    string answer;
    Source[] sources;
    float averageRelevance;
|};

type Source record {|
    string content;
    float relevance;
|};

function answerQuestion(string query, int topK = 5) returns RAGResponse|error {
    io:println("\n🔍 Query: ", query);

    // Step 1: Retrieve relevant chunks from knowledge base
    ai:QueryMatch[] queryMatches = check knowledgeBase.retrieve(query, topK);

    // Step 2: Extract chunks for context
    ai:Chunk[] context = from ai:QueryMatch queryMatch in queryMatches
                         select queryMatch.chunk;

    // Step 3: Augment user query with retrieved context
    ai:ChatUserMessage augmentedQuery = ai:augmentUserQuery(context, query);

    // Step 4: Get answer from LLM
    ai:ChatAssistantMessage assistantMessage = 
        check modelProvider-&amp;gt;chat(augmentedQuery);

    // Step 5: Calculate average relevance for confidence scoring
    float totalRelevance = 0.0;
    foreach ai:QueryMatch match in queryMatches {
        totalRelevance += match.score;
    }
    float avgRelevance = queryMatches.length() &amp;gt; 0 ? 
        totalRelevance / queryMatches.length() : 0.0;

    // Step 6: Build response with sources
    Source[] sources = from ai:QueryMatch match in queryMatches
                       select {
                           content: match.chunk.content,
                           relevance: match.score
                       };

    return {
        answer: assistantMessage.content,
        sources: sources,
        averageRelevance: avgRelevance
    };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Breaking Down the Query Expression:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of Ballerina's superpowers is query expressions. Look at this beauty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ai:Chunk[] context = from ai:QueryMatch queryMatch in queryMatches
                     select queryMatch.chunk;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not just syntactic sugar. It's a type-safe, functional way to transform data that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads like SQL&lt;/li&gt;
&lt;li&gt;Compiles to efficient code&lt;/li&gt;
&lt;li&gt;Maintains type safety throughout the pipeline&lt;/li&gt;
&lt;li&gt;Makes data transformations explicit and readable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare this to Python's list comprehension or JavaScript's map—it's clearer and more maintainable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Understanding Query Augmentation
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;ai:augmentUserQuery()&lt;/code&gt; function is doing heavy lifting behind the scenes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ai:ChatUserMessage augmentedQuery = ai:augmentUserQuery(context, query);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function takes your retrieved chunks and combines them with the user's query using a well-designed prompt template. It's essentially doing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a helpful assistant. Answer the question based on the following context.

Context:
[Chunk 1 content]
[Chunk 2 content]
[Chunk 3 content]

Question: {user's query}

Answer:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But you don't have to worry about prompt engineering—Ballerina handles it with best practices baked in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Making it Production-Ready with HTTP Service
&lt;/h3&gt;

&lt;p&gt;Let's expose our RAG system as a REST API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import ballerina/http;
import ballerina/ai;
import ballerina/io;

// Initialize RAG components (same as before)
final ai:VectorStore vectorStore = check new ai:InMemoryVectorStore();
final ai:EmbeddingProvider embeddingProvider = 
    check ai:getDefaultEmbeddingProvider();
final ai:KnowledgeBase knowledgeBase = 
    new ai:VectorKnowledgeBase(vectorStore, embeddingProvider);
final ai:ModelProvider modelProvider = check ai:getDefaultModelProvider();

type QueryRequest record {|
    string question;
    int topK = 5;
|};

type QueryResponse record {|
    string answer;
    Source[] sources;
    float confidence;
|};

type Source record {|
    string content;
    float relevance;
|};

service /api on new http:Listener(8080) {

    // Initialize knowledge base on service startup
    function init() returns error? {
        io:println("🚀 Starting Employee Handbook Assistant...");

        // Ingest documents
        ai:DataLoader loader = check new ai:TextDataLoader("./documents/");
        ai:Document|ai:Document[] documents = check loader.load();
        check knowledgeBase.ingest(documents);

        io:println("✅ Knowledge base initialized");
    }

    // Query endpoint
    resource function post query(@http:Payload QueryRequest request) 
            returns QueryResponse|http:InternalServerError {

        // Retrieve relevant chunks
        ai:QueryMatch[]|error queryMatches = 
            knowledgeBase.retrieve(request.question, request.topK);

        if queryMatches is error {
            return {
                body: {
                    message: "Failed to retrieve context",
                    error: queryMatches.message()
                }
            };
        }

        // Extract context chunks
        ai:Chunk[] context = from ai:QueryMatch match in queryMatches
                             select match.chunk;

        // Augment query and get answer
        ai:ChatUserMessage augmentedQuery = 
            ai:augmentUserQuery(context, request.question);
        ai:ChatAssistantMessage|error assistantMessage = 
            modelProvider-&amp;gt;chat(augmentedQuery);

        if assistantMessage is error {
            return {
                body: {
                    message: "Failed to generate answer",
                    error: assistantMessage.message()
                }
            };
        }

        // Calculate confidence
        float totalRelevance = 0.0;
        foreach ai:QueryMatch match in queryMatches {
            totalRelevance += match.score;
        }
        float confidence = queryMatches.length() &amp;gt; 0 ? 
            totalRelevance / queryMatches.length() : 0.0;

        // Build sources
        Source[] sources = from ai:QueryMatch match in queryMatches
                           select {
                               content: match.chunk.content,
                               relevance: match.score
                           };

        return {
            answer: assistantMessage.content,
            sources: sources,
            confidence: confidence
        };
    }

    // Health check endpoint
    resource function get health() returns json {
        return {
            status: "healthy",
            service: "Employee Handbook Assistant",
            timestamp: time:utcNow()
        };
    }

    // Add new documents endpoint
    resource function post documents(http:Request req) 
            returns http:Created|http:BadRequest|http:InternalServerError {

        string|http:ClientError filePath = req.getTextPayload();

        if filePath is http:ClientError {
            return {body: "Invalid file path"};
        }

        ai:DataLoader|error loader = new ai:TextDataLoader(filePath);
        if loader is error {
            return {body: "Failed to create data loader"};
        }

        ai:Document|ai:Document[]|error documents = loader.load();
        if documents is error {
            return {body: "Failed to load documents"};
        }

        error? result = knowledgeBase.ingest(documents);
        if result is error {
            return {body: "Failed to ingest documents"};
        }

        return {
            body: {message: "Documents ingested successfully"}
        };
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What Makes This Special:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Automatic JSON Binding&lt;/strong&gt;: The &lt;code&gt;@http:Payload&lt;/code&gt; annotation automatically deserializes JSON to Ballerina records&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type-Safe Responses&lt;/strong&gt;: Return types are checked at compile time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in Error Handling&lt;/strong&gt;: Union types (&lt;code&gt;|error&lt;/code&gt;) force you to handle failures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean Service Definition&lt;/strong&gt;: RESTful endpoints with clear resource functions&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Learn More
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ballerina.io/" rel="noopener noreferrer"&gt;Ballerina Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ballerina.io/use-cases/ai/" rel="noopener noreferrer"&gt;Ballerina AI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ballerina</category>
      <category>rag</category>
      <category>ai</category>
    </item>
    <item>
      <title>Simplifying Data Persistence in Ballerina: A Deep Dive into `bal persist`</title>
      <dc:creator>Abhi</dc:creator>
      <pubDate>Tue, 28 Oct 2025 21:26:11 +0000</pubDate>
      <link>https://forem.com/better-boy/simplifying-data-persistence-in-ballerina-a-deep-dive-into-bal-persist-4f9f</link>
      <guid>https://forem.com/better-boy/simplifying-data-persistence-in-ballerina-a-deep-dive-into-bal-persist-4f9f</guid>
      <description>&lt;p&gt;Building modern cloud-native applications often requires seamless integration with various data stores. Whether you're working with MySQL, PostgreSQL, Redis, or even Google Sheets, managing data persistence can become complex and time-consuming. Enter &lt;strong&gt;&lt;code&gt;bal persist&lt;/code&gt;&lt;/strong&gt; – a powerful feature in the Ballerina programming language that streamlines data persistence across multiple data stores with a unified, type-safe approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is &lt;code&gt;bal persist&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bal persist&lt;/code&gt; is a comprehensive data persistence feature in Ballerina that allows developers to store and retrieve data across different data stores using a consistent syntax. Think of it as your universal translator for databases – write your code once, and it works seamlessly whether you're using MySQL, MSSQL, PostgreSQL, Redis, Google Sheets, or even in-memory tables.&lt;/p&gt;

&lt;p&gt;The beauty of &lt;code&gt;bal persist&lt;/code&gt; lies in its simplicity: you don't need to learn different syntaxes for different databases. Define your data model once, and &lt;code&gt;bal persist&lt;/code&gt; handles the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Pillars of &lt;code&gt;bal persist&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;bal persist&lt;/code&gt; feature is built on three core components that work together to provide a seamless data persistence experience:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Data Model&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The data model is the single source of truth for your application's data structure. It's defined using Ballerina record types in a dedicated file within the &lt;code&gt;persist&lt;/code&gt; directory of your project.&lt;/p&gt;

&lt;p&gt;Here's a simple example of defining an &lt;code&gt;Employee&lt;/code&gt; entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Employee record {
    readonly int id;
    string name;
    int age;
    float salary;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;readonly&lt;/code&gt; keyword designates primary key fields&lt;/li&gt;
&lt;li&gt;At least one primary key field is required per entity&lt;/li&gt;
&lt;li&gt;Multiple primary keys are supported for composite keys&lt;/li&gt;
&lt;li&gt;The Ballerina VS Code extension provides validation, code actions, and even Entity Relationship diagram visualization&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;CLI Tool&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;bal persist&lt;/code&gt; CLI tool is your code generation powerhouse. It automatically generates type-safe client APIs from your data model, along with all necessary configuration files and setup scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key commands:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;persist init&lt;/code&gt;&lt;/strong&gt;: Creates the persist directory and an initial data model file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;persist add&lt;/code&gt;&lt;/strong&gt;: Sets up the model file and integrates with &lt;code&gt;bal build&lt;/code&gt; for automatic code generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;persist generate&lt;/code&gt;&lt;/strong&gt;: Provides one-time code generation without build integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;persist pull&lt;/code&gt;&lt;/strong&gt;: Introspects existing databases (MySQL, MSSQL, PostgreSQL) to generate data models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;persist migrate&lt;/code&gt;&lt;/strong&gt; (experimental): Generates SQL scripts to update database schemas when your data model changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part? With &lt;code&gt;bal build&lt;/code&gt; integration, your client APIs are automatically regenerated whenever you build your project!&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Type-Safe Client API&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The generated client API provides a strongly-typed, resource-oriented interface for interacting with your data store. It's generated in the &lt;code&gt;generated&lt;/code&gt; directory and includes a Ballerina client object with resources for each entity in your model.&lt;/p&gt;

&lt;p&gt;Here's how simple CRUD operations look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Create a new employee record
EmployeeInsert employee = {id: 1, name: "John", age: 30, salary: 3000.0};
int[]|error employeeId = sClient-&amp;gt;/employees.post([employee]);

// Get an employee record by ID
Employee|error employee = sClient-&amp;gt;/employees/1;

// Update an employee record
Employee|error updated = sClient-&amp;gt;/employees/1.put({salary: 4000.0});

// Delete an employee record
Employee|error deleted = sClient-&amp;gt;/employees/1.delete();

// Get all employees as a stream
stream&amp;lt;Employee, error?&amp;gt; employees = sClient-&amp;gt;/employees;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how clean and intuitive the API is – it uses Ballerina's resource access syntax with type safety built in.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It All Works Together
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;bal persist&lt;/code&gt; workflow is elegantly simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Define Your Data Model&lt;/strong&gt;: Create entity records in the &lt;code&gt;persist&lt;/code&gt; directory using Ballerina record types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure Your Data Store&lt;/strong&gt;: Specify which data store you're using (MySQL, PostgreSQL, Redis, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate Client API&lt;/strong&gt;: Run &lt;code&gt;bal build&lt;/code&gt; or use CLI commands to generate the type-safe client&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use in Your Application&lt;/strong&gt;: Import and use the generated client in your business logic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fballerina.io%2Flearn%2Fimages%2Fbal-persist-diagram.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fballerina.io%2Flearn%2Fimages%2Fbal-persist-diagram.png" alt="The bal persist architecture" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Supported Data Stores
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bal persist&lt;/code&gt; currently supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Relational Databases&lt;/strong&gt;: MySQL, MSSQL, PostgreSQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-Memory&lt;/strong&gt;: In-memory tables for testing and caching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NoSQL&lt;/strong&gt;: Redis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Services&lt;/strong&gt;: Google Sheets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The unified API means switching between data stores is as simple as updating your configuration – no code changes required!&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Benefits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Type Safety&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Every operation is type-checked at compile time, reducing runtime errors and improving developer productivity.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Database Agnostic&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Write your code once and switch between data stores without rewriting your data access layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Developer Experience&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;With VS Code integration, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time validation of data models&lt;/li&gt;
&lt;li&gt;Code actions for quick fixes&lt;/li&gt;
&lt;li&gt;Visual ER diagrams&lt;/li&gt;
&lt;li&gt;IntelliSense for generated APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Automatic Code Generation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;No manual boilerplate – the CLI generates everything you need, from client classes to database setup scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;Migration Support&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The experimental &lt;code&gt;persist migrate&lt;/code&gt; command helps you evolve your database schema as your application grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Microservices Architecture
&lt;/h3&gt;

&lt;p&gt;Build microservices with different data persistence needs. One service uses PostgreSQL for transactional data, another uses Redis for caching, and a third uses Google Sheets for simple data – all using the same &lt;code&gt;bal persist&lt;/code&gt; patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Tenant Applications
&lt;/h3&gt;

&lt;p&gt;Easily support different data stores for different tenants without code duplication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Migration
&lt;/h3&gt;

&lt;p&gt;Start with an in-memory table for prototyping, then seamlessly migrate to a production database when ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  Change Data Capture (CDC)
&lt;/h3&gt;

&lt;p&gt;Build CDC services that react to data changes across different data stores with consistent APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Ready to try &lt;code&gt;bal persist&lt;/code&gt;? Here's a quick start:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Define your data model&lt;/strong&gt; in &lt;code&gt;persist/model.bal&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   type User record {
       readonly int id;
       string username;
       string email;
   }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initialize persist in your Ballerina project:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   bal persist add &lt;span class="nt"&gt;--datastore&lt;/span&gt; mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build your project&lt;/strong&gt; (this auto-generates the client):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   bal build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use the generated client&lt;/strong&gt; in your application:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   import ballerina/io;

   public function main() returns error? {
       Client dbClient = check new();

       UserInsert newUser = {id: 1, username: "alice", email: "alice@example.com"};
       int[] userId = check dbClient-&amp;gt;/users.post([newUser]);

       io:println("Created user with ID: ", userId);
   }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use the VS Code Extension&lt;/strong&gt;: Leverage the built-in validation and visualization tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start Simple&lt;/strong&gt;: Begin with basic entities and add relationships as needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage Introspection&lt;/strong&gt;: Use &lt;code&gt;persist pull&lt;/code&gt; to reverse-engineer existing databases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan for Migration&lt;/strong&gt;: Consider using &lt;code&gt;persist migrate&lt;/code&gt; (experimental) to manage schema evolution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test with In-Memory&lt;/strong&gt;: Use in-memory tables for unit testing before deploying to production databases&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Future of Data Persistence in Ballerina
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;bal persist&lt;/code&gt; represents a significant step forward in how we handle data persistence in cloud-native applications. By providing a unified, type-safe interface across multiple data stores, it eliminates much of the complexity and boilerplate traditionally associated with data access layers.&lt;/p&gt;

&lt;p&gt;Whether you're building microservices, REST APIs, or data-intensive applications, &lt;code&gt;bal persist&lt;/code&gt; offers a pragmatic approach to data persistence that scales with your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn More
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ballerina.io/learn/bal-persist-overview/" rel="noopener noreferrer"&gt;Official bal persist documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ballerina.io/learn/persist-model/" rel="noopener noreferrer"&gt;Data Model Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ballerina.io/learn/persist-cli-tool/" rel="noopener noreferrer"&gt;CLI Tool Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ballerina.io/learn/persist-client-api/" rel="noopener noreferrer"&gt;Client API Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ballerina.io/learn/supported-data-stores/" rel="noopener noreferrer"&gt;Supported Data Stores&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Have you tried &lt;code&gt;bal persist&lt;/code&gt; in your projects? Share your experiences and use cases in the comments below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ballerina</category>
      <category>database</category>
    </item>
    <item>
      <title>RankGap: Multi-Agent Amazon SEO &amp; Product Visibility Analyzer</title>
      <dc:creator>Abhi</dc:creator>
      <pubDate>Sun, 31 Aug 2025 06:49:59 +0000</pubDate>
      <link>https://forem.com/better-boy/rankgap-multi-agent-amazon-seo-product-visibility-analyzer-2h81</link>
      <guid>https://forem.com/better-boy/rankgap-multi-agent-amazon-seo-product-visibility-analyzer-2h81</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/brightdata-n8n-2025-08-13"&gt;AI Agents Challenge powered by n8n and Bright Data&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Selling on Amazon is one thing; being &lt;strong&gt;discovered&lt;/strong&gt; is another. RankGap is a &lt;strong&gt;multi-agent AI system&lt;/strong&gt; designed to uncover &lt;strong&gt;Amazon ranking blind spots&lt;/strong&gt; for your products using real-time data. It identifies where your product ranks, which search queries you’re missing, and how to optimize for better visibility.&lt;/p&gt;

&lt;p&gt;Unlike generic reports, RankGap delivers actionable &lt;strong&gt;Amazon SEO insights&lt;/strong&gt; by combining intelligent &lt;strong&gt;search keyword/query generation, ranking analysis, and gap detection&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Being discovered on Amazon isn’t just luck — it’s about &lt;strong&gt;strategic keyword targeting&lt;/strong&gt; and &lt;strong&gt;SEO-aware visibility tracking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Key challenges sellers face:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keyword uncertainty&lt;/strong&gt;: Many sellers don’t know which queries actually drive traffic.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual rank tracking is slow&lt;/strong&gt;: Monitoring dozens of queries across multiple products is tedious.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visibility gaps cost revenue&lt;/strong&gt;: Missing even one relevant search query can drastically affect sales.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time complexity&lt;/strong&gt;: Tracking and analyzing search data dynamically is challenging without automation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Around &lt;strong&gt;60% of product discoveries on Amazon happen via keyword or query searches&lt;/strong&gt;. &lt;/p&gt;




&lt;h2&gt;
  
  
  How it works?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;User enters an Amazon product URL (the target product) and selects how many search queries to generate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The app scrapes the target product details (title, brand, description, features, etc.) using Bright Data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An LLM generates n search queries based on the product information.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Each query is run on Amazon; the top 20 results are scraped via Bright Data Web Unlocker.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Full product details for those top 20 results are collected using Bright Data’s Amazon scraper.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The target product and its competing results are then analyzed by three specialized LLMs, each receiving only the data it needs.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllhh1bqc0mawwgp5d2zc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllhh1bqc0mawwgp5d2zc.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Try out the Live App - &lt;a href="https://rank-gap.vercel.app/" rel="noopener noreferrer"&gt;RankGap&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ohMNU2IG2ok"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h3&gt;
  
  
  n8n Workflow
&lt;/h3&gt;

&lt;p&gt;There are 3 workflows involved: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Main Workflow&lt;/li&gt;
&lt;li&gt;Download Product Data from Amazon&lt;/li&gt;
&lt;li&gt;Analysis of the scraped product data&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Main Workflow:
&lt;/h4&gt;

&lt;p&gt;The main workflow is the entry point of the entire pipeline. It executes the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The pipeline is triggered via a webhook (a POST request) with the following payload:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.amazon.com/dp/B07QTVRF3J"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;amazon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;product&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;url&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"queryCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;search&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;queries&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;generate&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"execution_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;uuid&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;identifying&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;workflow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;supabase&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The given Amazon product is scraped using the Download Product Data from Amazon workflow, returning the full attribute list from Bright Data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Search Query Generator agent produces multiple relevant search queries (see AI agents table below for prompts and details).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Search results for each query are scraped using Bright Data Unlocker. The Amazon search URL pattern is:&lt;br&gt;
&lt;a href="https://www.amazon.com/s?k=" rel="noopener noreferrer"&gt;https://www.amazon.com/s?k=&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The HTML returned is parsed, and the top 20 product ASINs are extracted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Download Product Data from Amazon workflow scrapes detailed info for these top 20 products.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data transformations are applied to retain only relevant attributes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Both the target product and competitors are sent to the Analysis of the Scraped Product Data workflow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The final analysis is stored in a Supabase database, linked via the execution_id passed at trigger time.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfcvkvn65dgrdf643m1l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfcvkvn65dgrdf643m1l.png" alt="Main Workflow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Total number of n8n nodes in this workflow - 19&lt;/p&gt;

&lt;p&gt;Workflow json can be found &lt;a href="https://github.com/Better-Boy/RankGap/blob/main/workflows/RankGap.json" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Download Product Data from Amazon Workflow:
&lt;/h4&gt;

&lt;p&gt;This workflow’s sole job is to scrape Amazon product data using the Bright Data web scraper. Steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Input is a list of product URLs in the following format:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.amazon.com/dp/B07DSVRF2J"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.amazon.com/dp/B07ABCDF2J"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Bright Data Verified Node triggers a collection using the Trigger Collection By URL operation, returning a snapshot ID.&lt;/li&gt;
&lt;li&gt;Using Monitor Snapshot and an n8n Loop node, the workflow waits until the snapshot is ready.&lt;/li&gt;
&lt;li&gt;Once complete, the Download Snapshot operation retrieves product data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqc878wvwgfyjq4qc9t5n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqc878wvwgfyjq4qc9t5n.png" alt="Download Product Data from Amazon"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Total number of n8n nodes - 8&lt;/p&gt;

&lt;p&gt;Workflow json can be found &lt;a href="https://github.com/Better-Boy/RankGap/blob/main/workflows/Download%20Amazon%20Products.json" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Analysis of the scraped product data Workflow:
&lt;/h4&gt;

&lt;p&gt;This workflow performs structured analysis on the target product and its competitors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Input: Target product JSON + search results JSON.&lt;/li&gt;
&lt;li&gt;Sent to the &lt;strong&gt;Visibility &amp;amp; Presence Analysis Agent&lt;/strong&gt; for ranking visibility.&lt;/li&gt;
&lt;li&gt;Sent to the &lt;strong&gt;Competitor &amp;amp; Attribute Gap Analysis Agent&lt;/strong&gt; for feature/attribute gaps.&lt;/li&gt;
&lt;li&gt;Sent to the &lt;strong&gt;Final Recommendations &amp;amp; Action Plan Agent&lt;/strong&gt; for actionable next steps.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwdbqsdhhxd48ih0yimw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiwdbqsdhhxd48ih0yimw.png" alt="Analysis"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Workflow json can be found &lt;a href="https://github.com/Better-Boy/RankGap/blob/main/workflows/RankGap%20Analysis.json" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Combined total nodes - 19 + 18 + 8 = 45&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Implementation
&lt;/h2&gt;

&lt;p&gt;There are 4 AI agents in the complete workflow. Refer the following table for detailed info:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent Name&lt;/th&gt;
&lt;th&gt;Functionality&lt;/th&gt;
&lt;th&gt;LLM Model&lt;/th&gt;
&lt;th&gt;BackUp Model&lt;/th&gt;
&lt;th&gt;System Prompt Instructions&lt;/th&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Search Query Generator&lt;/td&gt;
&lt;td&gt;Generate natural language keyword and search queries based on input product data&lt;/td&gt;
&lt;td&gt;GPT-4o&lt;/td&gt;
&lt;td&gt;Gemini 2.5 Pro&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Better-Boy/RankGap/blob/main/prompts.txt#L1" rel="noopener noreferrer"&gt;https://github.com/Better-Boy/RankGap/blob/main/prompts.txt#L1&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;NA&lt;/td&gt;
&lt;td&gt;Structured Output Parser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visibility &amp;amp; Presence Analysis Agent&lt;/td&gt;
&lt;td&gt;Analyze visibility and presence of the target product in the search results&lt;/td&gt;
&lt;td&gt;GPT-4.1-mini&lt;/td&gt;
&lt;td&gt;Gemini 2.5 Pro&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Better-Boy/RankGap/blob/main/prompts.txt#L20" rel="noopener noreferrer"&gt;https://github.com/Better-Boy/RankGap/blob/main/prompts.txt#L20&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;NA&lt;/td&gt;
&lt;td&gt;NA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Competitor &amp;amp; Attribute Gap Analysis&lt;/td&gt;
&lt;td&gt;Analyze attribute gap between the target and competitor products&lt;/td&gt;
&lt;td&gt;GPT-4.1-mini&lt;/td&gt;
&lt;td&gt;Gemini 2.5 Pro&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Better-Boy/RankGap/blob/main/prompts.txt#L71" rel="noopener noreferrer"&gt;https://github.com/Better-Boy/RankGap/blob/main/prompts.txt#L71&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;NA&lt;/td&gt;
&lt;td&gt;NA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final Recommendations &amp;amp; Action Plan Agent&lt;/td&gt;
&lt;td&gt;Final Recommendation on what can be done better&lt;/td&gt;
&lt;td&gt;GPT-4.1-mini&lt;/td&gt;
&lt;td&gt;Gemini 2.5 Pro&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/Better-Boy/RankGap/blob/main/prompts.txt#L122" rel="noopener noreferrer"&gt;https://github.com/Better-Boy/RankGap/blob/main/prompts.txt#L122&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;NA&lt;/td&gt;
&lt;td&gt;NA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;GPT-4.1-mini&lt;/code&gt; was chosen as it has large context windows.&lt;/p&gt;




&lt;h3&gt;
  
  
  Bright Data Verified Node
&lt;/h3&gt;

&lt;p&gt;The following operations from bright data verified node are used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scraping Amazon search page HTML via Web Unlocker&lt;/li&gt;
&lt;li&gt;Submitting multiple URLs for product scraping via Trigger Collection by URL&lt;/li&gt;
&lt;li&gt;Monitoring snapshot status&lt;/li&gt;
&lt;li&gt;Downloading product data snapshots&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Application
&lt;/h2&gt;

&lt;p&gt;Tech stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend - n8n cloud webhook&lt;/li&gt;
&lt;li&gt;Database - supabase to store results&lt;/li&gt;
&lt;li&gt;Frontend - React + vite + tailwind&lt;/li&gt;
&lt;li&gt;Deployment - Vercel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Frontend codebase - &lt;a href="https://github.com/Better-Boy/RankGap" rel="noopener noreferrer"&gt;https://github.com/Better-Boy/RankGap&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Screenshots:&lt;/p&gt;

&lt;p&gt;Landing Page &amp;amp; Input:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm54v4euuz7iizl909kwh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm54v4euuz7iizl909kwh.png" alt="input"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0l1j7hjt2md76ijjg5w8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0l1j7hjt2md76ijjg5w8.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6ke9hhkglwwwnrdhjfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6ke9hhkglwwwnrdhjfs.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3tlfpgmwy94q4a85rnk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3tlfpgmwy94q4a85rnk.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foib5exdqhmtaqts37bz4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foib5exdqhmtaqts37bz4.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bkho1pnqhvh2jk77ztr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bkho1pnqhvh2jk77ztr.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;This was my first time working with both n8n and Bright Data. While there was a learning curve, the community support on discord and the documentation made the process smooth. &lt;/p&gt;

&lt;p&gt;Key Learnings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Bright Data’s strength in web data collection became clear when my manual searches kept triggering captchas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;n8n streamlined the entire backend process, saving countless hours of work. It's exhaustive set of nodes is powerful enough to build even the most complex workflows.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Challenges Encountered &amp;amp; How I Overcame Them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Even when a Bright Data snapshot shows as “ready,” downloading sometimes fails with &lt;code&gt;status: building&lt;/code&gt;. Increasing the wait time from 5s → 10s solved this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In production, cloudflare times out requests after 100 seconds, while n8n workflows can run up to 3 minutes. My workflow often needed 3–5 minutes, so the webhook returned immediately triggering the pipeline as a background process. The pipeline would store the results in supabase with UUID &lt;code&gt;execution_id&lt;/code&gt; given by the frontend. By long-polling Supabase from the frontend, I ensured results were fetched once available.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building RankGap required handling the complexities of Amazon search, real-time scraping, and nested n8n loops — but the result is a powerful system that makes Amazon SEO more transparent and actionable.&lt;/p&gt;

&lt;p&gt;If you found this useful, press ❤️ Like or drop a comment.&lt;br&gt;
Thanks for reading! 🚀&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>n8nbrightdatachallenge</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>PickPerfect - An AI powered recommendation engine</title>
      <dc:creator>Abhi</dc:creator>
      <pubDate>Mon, 11 Aug 2025 04:29:22 +0000</pubDate>
      <link>https://forem.com/better-boy/pickperfect-an-ai-powered-recommendation-engine-1o43</link>
      <guid>https://forem.com/better-boy/pickperfect-an-ai-powered-recommendation-engine-1o43</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/redis-2025-07-23"&gt;Redis AI Challenge&lt;/a&gt;: Real-Time AI Innovators&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Challenge: Milliseconds Matter in E-commerce
&lt;/h3&gt;

&lt;p&gt;Picture this: A user lands on your e-commerce site, browses through a few products, adds something to cart, then hesitates. In those crucial seconds, your recommendation engine needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Process their real-time behavior&lt;/li&gt;
&lt;li&gt;Analyze their browsing patterns&lt;/li&gt;
&lt;li&gt;Serve personalized recommendations&lt;/li&gt;
&lt;li&gt;Do all of this in under 100ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional recommendation systems using batch processing and SQL databases? They're great for Netflix (where you can wait a few seconds), but in e-commerce, every millisecond of delay costs conversions.&lt;/p&gt;

&lt;p&gt;PickPerfect as the name suggests picks the perfect product to recommend to the user based the user's activity like viewing the product page, adding product to cart etc...&lt;/p&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Frontend - vite, react, typescript, tailwind css&lt;/li&gt;
&lt;li&gt;Backend - fastapi&lt;/li&gt;
&lt;li&gt;Database - Redis&lt;/li&gt;
&lt;li&gt;AI engine - RedisAI&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhr7v039xb9l0wt0cy09h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhr7v039xb9l0wt0cy09h.png" alt=" " width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above image shows geolocation based search for &lt;code&gt;Featured Deals&lt;/code&gt; and vector search for &lt;code&gt;Recommended For You&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5bafqg6k7zdpwtnwucc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5bafqg6k7zdpwtnwucc.png" alt=" " width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above image shows product info page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdb6jdk30o2hl7441gef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjdb6jdk30o2hl7441gef.png" alt=" " width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above shows vector search on the input query.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlp5xd9f454jtvyhohdl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlp5xd9f454jtvyhohdl.png" alt=" " width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above image shows range based search for numeric fields, exact match for tag fields with &lt;code&gt;OR&lt;/code&gt; operator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffumc9lydslecy8cpm38s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffumc9lydslecy8cpm38s.png" alt=" " width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above image shows cart page. Whenever a product is added to the cart, it is tracked.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Redis 8?
&lt;/h2&gt;

&lt;p&gt;Redis is used in the application almost everywhere. As a real time data layer and as well as permanent storage database. The individual steps where it's is as follows:&lt;/p&gt;

&lt;h4&gt;
  
  
  Products Indexing
&lt;/h4&gt;

&lt;p&gt;The textual part of the part i.e title, description etc. are concatenated together to form a string. This new string is sent to openAI for embedding which forms the feature vector of the product. This feature vector is indexed in redis full text search document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
            FT.CREATE &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;SEARCH_INDEX_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ON JSON PREFIX 1 product: SCHEMA
            $.name AS name TEXT
            $.description AS description TEXT
            $.brand AS brand TAG
            $.price AS price NUMERIC
            $.rating AS rating NUMERIC
            $.reviews AS reviews NUMERIC
            $.category AS category TAG
            $.inStock AS inStock TAG
            $.features[*] AS features TEXT
            $.warehouse_geolocation AS warehouse_location GEO
            $.image AS image TEXT NOINDEX
            $.embedding AS embedding VECTOR FLAT 6 TYPE FLOAT32 DIM &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;EMBED_DIM&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; DISTANCE_METRIC &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;VECTOR_ALGORITHM&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
            &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Full Text Search
&lt;/h4&gt;

&lt;p&gt;Products are searched via search filters based on the attributes of the products.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;multi_parameter_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;text_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;price_min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;price_max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;in_stock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;geo_location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;geo_radius_km&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Search products with multiple parameters.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;query_parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="n"&gt;query_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;query_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(@name|@description|@brand|@features:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text_query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price_min&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;price_max&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;min_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price_min&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price_min&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-inf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="n"&gt;max_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price_max&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price_max&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+inf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="n"&gt;query_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@price:[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;min_val&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;max_val&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;query_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@category:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;query_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@brand:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;brand&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rating&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;query_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@rating:[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rating&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; 5]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;in_stock&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;query_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@inStock:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_stock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;geo_location&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;geo_radius_km&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_location&lt;/span&gt;
            &lt;span class="n"&gt;query_parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@warehouse_location:[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;geo_radius_km&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; km]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;base_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;query_parts&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_query&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;return_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;dialect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search_index_name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query_params&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;filtered_products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;filtered_products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;filtered_products&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Multi-parameter search error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Vector search
&lt;/h4&gt;

&lt;p&gt;Search queries are converted to embedding using openAI and top K nearest neighbors are searched using redis full text search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;vector_search_embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Search products using vector similarity.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;vector_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tobytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;base_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*=&amp;gt;[KNN &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; @embedding $vec AS vector_score]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;return_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;params_dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vector_bytes&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search_index_name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query_params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params_dict&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;filtered_products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;filtered_products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;filtered_products&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Semantic Cache
&lt;/h4&gt;

&lt;p&gt;Redis Semantic cache module of langchain uses RedisVL internally for semantically matching keys. The code for langchain semantic cache can be found in langchain docs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Geolocation based search
&lt;/h4&gt;

&lt;p&gt;Products in warehouses near to the location of the user can be delivered faster and hence giving extra discount or newer deals becomes a crucial part. This is performed using full text search geo datatype. Reusing the code of full text search.&lt;/p&gt;

&lt;h4&gt;
  
  
  Personalized recommendation
&lt;/h4&gt;

&lt;p&gt;Whenever an user visits the product page or adds a product to the cart, an event is stored at redis with userId, productId, timestamp. During product recommendation process a sliding window of latest 10 events are retrieved and the embedding vectors of the products are averaged out and again search a vector search is performed on the full text data structure. For personalized recommendations, the user has to be logged in. &lt;/p&gt;

&lt;p&gt;As user interests keep changing, time decay method is used to give more importance to the latest events than the older ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_personalized_recommendations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get personalized recommendations for a user.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;user_events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_events:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;user_events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;product_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_trending_products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;product_scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event_str&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;user_events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;event_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event_str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;event_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;product_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

            &lt;span class="n"&gt;time_decay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calculate_time_decay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;base_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_weights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;final_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_score&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time_decay&lt;/span&gt;

            &lt;span class="n"&gt;product_scores&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;product_scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;final_score&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;product_scores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;product_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_trending_products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Create user preference vector
&lt;/span&gt;        &lt;span class="n"&gt;weighted_vectors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;product_scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$.embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;weighted_vectors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;weighted_vectors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;user_preference_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weighted_vectors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;initial_limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;product_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vector_search_embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;user_preference_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initial_limit&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;recommendations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;product_scores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="n"&gt;recommendations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;recommendations&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Trending products and categories
&lt;/h4&gt;

&lt;p&gt;Trending products and categories are found out as a result of user event tracking. Redis sorted sets are used here to store the products views.&lt;/p&gt;

&lt;p&gt;Code - &lt;a href="https://github.com/Better-Boy/PickPerfect" rel="noopener noreferrer"&gt;https://github.com/Better-Boy/PickPerfect&lt;/a&gt;&lt;/p&gt;

</description>
      <category>redischallenge</category>
      <category>devchallenge</category>
      <category>database</category>
      <category>ai</category>
    </item>
    <item>
      <title>PaperSense: Semantic arXiv Search &amp; Chat Built with MindsDB</title>
      <dc:creator>Abhi</dc:creator>
      <pubDate>Sat, 28 Jun 2025 21:38:01 +0000</pubDate>
      <link>https://forem.com/better-boy/papersense-semantic-arxiv-search-chat-built-with-mindsdb-1kfi</link>
      <guid>https://forem.com/better-boy/papersense-semantic-arxiv-search-chat-built-with-mindsdb-1kfi</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;As a developer working with AI, I constantly found myself drowning in arXiv papers. The existing search was purely keyword-based, which meant I'd miss relevant papers that used different terminology. Even worse, after finding papers, I’d lose hours parsing dense academic prose just to understand core ideas.&lt;/p&gt;

&lt;p&gt;I needed a system that could: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understand the semantic meaning behind my search queries&lt;/li&gt;
&lt;li&gt;Let me "talk" to research papers directly&lt;/li&gt;
&lt;li&gt;Actually understand context, not just keywords&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Solution: A Semantic Search + Chatbot System
&lt;/h3&gt;

&lt;p&gt;I decided to build a comprehensive system using MindsDB's knowledge base capabilities. The final product allows researchers to search semantically through research papers and then have natural language conversations with individual papers or groups of papers.&lt;/p&gt;

&lt;h3&gt;
  
  
  The tech stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MindsDB&lt;/strong&gt;: Handles semantic indexing and chat interface using LLMs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ChromaDB &amp;amp; PGVector&lt;/strong&gt;: Stores embeddings for fast similarity search.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt;: Powers a RESTful backend to interact with the front end and MindsDB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Javascript &amp;amp; CSS&lt;/strong&gt;: Powers the frontend.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  High-Level Architecture
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxs3tqx2riln73aosh3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxs3tqx2riln73aosh3u.png" alt="architecture" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Paperspace Features
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Search for papers using natural language&lt;/li&gt;
&lt;li&gt;Summarize the paper using AI&lt;/li&gt;
&lt;li&gt;Generate new innovative future research directions based on a paper&lt;/li&gt;
&lt;li&gt;Chat with the paper (using arxiv id or click on one of the semantic search results)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;p&gt;What worked well:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;MindsDB&lt;/strong&gt;: Using AI services as SQL queries saves a lot of development time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Search&lt;/strong&gt;: I found relevant papers I would never have discovered with keyword search&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata Enrichment&lt;/strong&gt;: Including authors and categories in search dramatically improved relevance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What Was Harder Than Expected:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Escaping characters&lt;/strong&gt;: Academic papers are full of mathematical notation that breaks text processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context Window Management&lt;/strong&gt;: Long papers required careful chunking strategies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limiting&lt;/strong&gt;: Both arXiv API and OpenAI had strict limits that required careful orchestration. I lost enough openAI credits while playing with long research papers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Want to try it out or contribute?&lt;/p&gt;

&lt;p&gt;🚀 &lt;a href="https://github.com/Better-Boy/PaperSense" rel="noopener noreferrer"&gt;View the GitHub project&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎥 &lt;a href="https://youtu.be/_Aw-lg2cKbA" rel="noopener noreferrer"&gt;Watch the YouTube demo&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>mindsdb</category>
      <category>python</category>
    </item>
  </channel>
</rss>
