<?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>DEV Community: Naveen Vandanapu</title>
    <description>The latest articles on DEV Community by Naveen Vandanapu (@naveen_v_5562871ba6455b7e).</description>
    <link>https://dev.to/naveen_v_5562871ba6455b7e</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%2F3634991%2F1577c530-d0be-4c32-920b-d29cd93aa096.png</url>
      <title>DEV Community: Naveen Vandanapu</title>
      <link>https://dev.to/naveen_v_5562871ba6455b7e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/naveen_v_5562871ba6455b7e"/>
    <language>en</language>
    <item>
      <title>Rethinking Expense Splitting: A Graph-Based Approach with LLM Integration</title>
      <dc:creator>Naveen Vandanapu</dc:creator>
      <pubDate>Fri, 26 Dec 2025 06:28:39 +0000</pubDate>
      <link>https://dev.to/naveen_v_5562871ba6455b7e/rethinking-expense-splitting-a-graph-based-approach-with-llm-integration-mbo</link>
      <guid>https://dev.to/naveen_v_5562871ba6455b7e/rethinking-expense-splitting-a-graph-based-approach-with-llm-integration-mbo</guid>
      <description>&lt;h2&gt;
  
  
  The Problem with Traditional Expense Splitters
&lt;/h2&gt;

&lt;p&gt;If you've ever used group expense splitting tools, you know the drill: great concept, but many implementations leave something to be desired. A common pain point is when timelines mix expenses from different groups, making it difficult to focus on a specific trip or event. Want to quickly review just your weekend getaway expenses? You might find yourself scrolling through unrelated bills and dinner splits from other groups.&lt;/p&gt;

&lt;p&gt;This counter-intuitive design pattern got me thinking: there has to be a better way.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Graph-Based Mental Model
&lt;/h2&gt;

&lt;p&gt;The breakthrough came when I realized expense splitting is fundamentally a &lt;strong&gt;graph problem&lt;/strong&gt;. Here's the elegant simplicity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nodes&lt;/strong&gt; represent people in your group&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directed edges&lt;/strong&gt; represent money owed (A → B means A owes B)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge weights&lt;/strong&gt; are the amounts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you add an expense, you're essentially creating edges in this graph. If Alice pays $120 for lunch shared equally among three people, she's owed $40 by Bob and $40 by Charlie. That's two edges: Bob → Alice ($40) and Charlie → Alice ($40).&lt;br&gt;
This mental model is intuitive because it mirrors how we naturally think about debts: "I owe Alice $40" is a directed relationship with a value attached.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building expense-splitter
&lt;/h2&gt;

&lt;p&gt;Armed with this insight, I built expense-splitter as a graph-based tool. Key design decisions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Group isolation: Each group is its own separate graph. Your Napa trip expenses never mix with your apartment utilities.&lt;/li&gt;
&lt;li&gt;Flexible splitting: Support for equal splits, weighted splits, and percentage-based splits—all translating cleanly to edge calculations.&lt;/li&gt;
&lt;li&gt;Clean settlements: The graph makes it trivial to calculate who owes whom. No more confusing chains of "Alice pays Bob who pays Charlie"—just direct settlements.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  The MCP Integration: Where It Gets Interesting
&lt;/h2&gt;

&lt;p&gt;Here's where modern AI tooling transforms the experience. I packaged expense-splitter as an MCP (Model Context Protocol) tool and built an agent using Google's Agent Development Kit (ADK) in Go.&lt;br&gt;
The result? A conversational interface that's actually intuitive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: let's do expense splitting

Agent: I can help with that! First, let's create a group.
What would you like to name it?

User: create a group called napa-trip

Agent: Great! I've created a group called 'napa-trip'.
Now, who are the members of this group?

User: add alice, bob, charlie to the group

Agent: I've added Alice, Bob, and Charlie to the napa-trip group.
Now, let's add some expenses...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No clicking through forms. No dropdown menus. Just natural conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Expenses Naturally
&lt;/h2&gt;

&lt;p&gt;The real magic shows when adding expenses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: add an expense by alice for lunch. 120$

Agent: I've added a $120 expense for lunch, paid by Alice.
What's the next expense?

User: it's for snacks and drinks by charlie. 60$

Agent: Got it. $60 for snacks and drinks, paid by Charlie, has been added.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even complex splits feel natural:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: add an expense by bob for show tickets with unequal weights

Agent: Okay, Bob paid for show tickets with unequal weights.
What was the total amount? And how would you like to split
the weights among Alice, Bob, and Charlie?

User: 100

Agent: And what are the unequal weights for Alice, Bob, and Charlie?

User: alice: 1, bob:2, charlie:1

Agent: I've added the $100 expense for show tickets, paid by Bob,
with the following weights: Alice 1, Bob 2, Charlie 1.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bob paid $100 but his share (weight 2 out of 4 total) was $50, so he's actually only owed $50.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Payoff: Clear Settlements
&lt;/h2&gt;

&lt;p&gt;When you're ready to settle up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: get group details now

Agent: Here are the details for the 'napa-trip' group:

**Members:** Alice, Bob, Charlie

**Settlement Details:**

- Bob needs to pay Alice $15
- Charlie needs to pay Alice $20
- Charlie needs to pay Bob $5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Visualizing the Graph: DOT Notation
&lt;/h2&gt;

&lt;p&gt;One of the advantages of modeling expenses as a graph is that you can actually see the structure. The tool exports the graph in DOT notation, which can be visualized using Graphviz:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="k"&gt;digraph&lt;/span&gt; &lt;span class="s2"&gt;"napa-trip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"alice"&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"alice"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="s2"&gt;"bob"&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"bob"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="s2"&gt;"charlie"&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"charlie"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="s2"&gt;"alice"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"bob"&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"$25.00"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="s2"&gt;"alice"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"charlie"&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"$20.00"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="s2"&gt;"bob"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"alice"&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"$40.00"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="s2"&gt;"bob"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"charlie"&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"$20.00"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="s2"&gt;"charlie"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"alice"&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"$40.00"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="s2"&gt;"charlie"&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"bob"&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"$25.00"&lt;/span&gt;&lt;span class="o"&gt;]&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;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%2Ff0dmr2kkd3tbw0dadtth.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%2Ff0dmr2kkd3tbw0dadtth.png" alt=" " width="800" height="703"&gt;&lt;/a&gt;&lt;br&gt;
This represents the complete debt graph showing every individual obligation. Notice that this is the raw graph showing all pairwise debts from each expense. The settlement algorithm then simplifies this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alice owes Bob $25 but Bob owes Alice $40 → Net: Bob owes Alice $15&lt;/li&gt;
&lt;li&gt;Charlie owes Alice $40 but Alice owes Charlie $20 → Net: Charlie owes Alice $20&lt;/li&gt;
&lt;li&gt;Charlie owes Bob $25 but Bob owes Charlie $20 → Net: Charlie owes Bob $5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The beauty of the graph representation is its transparency. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visualize it&lt;/strong&gt;: Paste into any Graphviz tool to see the network of obligations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debug it&lt;/strong&gt;: See exactly how each expense contributed to the final settlements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit it&lt;/strong&gt;: Verify the math by tracing through the edges&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand it&lt;/strong&gt;: The visual representation makes the settlement logic obvious&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This level of transparency is rare in expense splitting tools. Most are black boxes where you input expenses and get settlements without seeing the underlying logic. By exposing the graph, users who want to understand why they owe a certain amount can trace it back through the structure. Clean, minimal, and derived directly from the graph structure. No intermediate transfers, no confusion.&lt;/p&gt;

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

&lt;p&gt;The combination of a sound data model (graphs) and an intuitive interface (conversational AI) creates something genuinely better than existing solutions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cognitive alignment: The graph model matches how we think about debts&lt;/li&gt;
&lt;li&gt;Context preservation: Groups stay separate, making it easy to review specific events&lt;/li&gt;
&lt;li&gt;Conversational ease: No UI to learn, just describe what you want&lt;/li&gt;
&lt;li&gt;Extensibility: MCP tools can be integrated with various AI platforms (Claude Code, custom agents, etc.)&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;For those interested, the stack is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core logic&lt;/strong&gt;: Graph-based settlement calculation implemented in &lt;strong&gt;Go&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;MCP layer&lt;/strong&gt;: Exposed as Model Context Protocol tool implemented in &lt;strong&gt;Go&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Agent&lt;/strong&gt;: Built with Google's Agent Development Kit - ADK in Go&lt;br&gt;
&lt;strong&gt;Interface&lt;/strong&gt;: Natural language via LLM&lt;/p&gt;

&lt;p&gt;The MCP abstraction means the same tool can be used from CLI tools like Claude Code, web interfaces, or custom agents—wherever you can connect an LLM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Source
&lt;/h2&gt;

&lt;p&gt;I’ve open-sourced the implementation at&lt;br&gt;
&lt;a href="https://github.com/vnaveen-mh/expense-splitter" rel="noopener noreferrer"&gt;https://github.com/vnaveen-mh/expense-splitter&lt;/a&gt;&lt;br&gt;
If you’re experimenting with MCP, agent elicitation, or graph-based modeling, feel free to fork it and build on top of it.&lt;/p&gt;

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

&lt;p&gt;Sometimes the best solutions come from stepping back and asking: "What's the fundamental structure of this problem?" For expense splitting, it was recognizing the graph nature of debts and obligations.&lt;br&gt;
Combined with modern AI tooling, we can finally build interfaces that feel as natural as asking a friend, "Hey, what do I owe you?"&lt;br&gt;
The code is a testament to how good abstractions—both in data modeling and tool interfaces—can transform user experience from frustrating to delightful.&lt;/p&gt;

&lt;p&gt;Have you experienced similar frustrations with existing tools? I'd love to hear about problems you've solved by rethinking the fundamental data model.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>llm</category>
      <category>go</category>
    </item>
    <item>
      <title>LLMs + Tool Calls: Clever But Cursed</title>
      <dc:creator>Naveen Vandanapu</dc:creator>
      <pubDate>Thu, 11 Dec 2025 15:51:44 +0000</pubDate>
      <link>https://dev.to/naveen_v_5562871ba6455b7e/llms-tool-calls-clever-but-cursed-1kcn</link>
      <guid>https://dev.to/naveen_v_5562871ba6455b7e/llms-tool-calls-clever-but-cursed-1kcn</guid>
      <description>&lt;p&gt;A real example of how LLMs creatively use tools — and why sandbox safety matters more than most people realize.&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%2F1b3im3s4c622fx00739h.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%2F1b3im3s4c622fx00739h.png" alt="LLMs+Tool Calls" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LLMs are great for generating code, but they can also get a bit too creative sometimes.&lt;/p&gt;

&lt;p&gt;Today I ran into one of those clever but cursed AI moments that’s too interesting not to share — especially for anyone building LLM + tool calling systems.&lt;/p&gt;

&lt;p&gt;I added a simple &lt;strong&gt;LuaExecutor&lt;/strong&gt; tool to my Genkit app. I wanted to start simple: a single Genkit Flow, a single tool (LuaExecutor) and a Lua VM to run Lua code from within a Go application such as gopher-lua - that's it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Intended Purpose:&lt;/strong&gt; To ask LLM something like - "generate a Lua script to do something and run it"&lt;/p&gt;

&lt;p&gt;Then I casually asked the model:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Generate a Go program that demonstrates context cancellation — and run it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Note that I asked to generate a Go program, not Lua code.&lt;/p&gt;

&lt;p&gt;Simple so far. An innocent request... nothing suspicious. But, what happened next surprised me.&lt;/p&gt;

&lt;p&gt;Instead of saying “I can’t run Go,” the model improvised:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generated the Go code&lt;/li&gt;
&lt;li&gt;Embedded it inside a multiline Lua string&lt;/li&gt;
&lt;li&gt;Used &lt;code&gt;os.execute("go run main.go")&lt;/code&gt; from generated Lua code&lt;/li&gt;
&lt;li&gt;Captured and returned the Go program’s output as if this was totally normal 🤯&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This moment hit me from three different mindsets:&lt;/p&gt;

&lt;h2&gt;
  
  
  🧪 As a Research Engineer
&lt;/h2&gt;

&lt;p&gt;This was incredibly creative — almost agent-like reasoning across languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛡️ As a Product Builder
&lt;/h2&gt;

&lt;p&gt;This was a red flag.&lt;/p&gt;

&lt;p&gt;A narrowly scoped Lua tool had quietly become a general-purpose shell executor and ended up overwriting my source code. Fortunately, I recovered it using git history.&lt;/p&gt;

&lt;p&gt;A great reminder that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sandboxing is extremely critical for tool call execution&lt;/li&gt;
&lt;li&gt;Tools must have clear boundaries&lt;/li&gt;
&lt;li&gt;System prompt design is critical - it is your first safety layer. It should not only tell (or guide) the model what to do, but also explicitly declare what not to do.&lt;/li&gt;
&lt;li&gt;LLMs will exploit any capability you expose (often in surprising ways)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Safety &amp;gt; Cleverness&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  🙂 As a user
&lt;/h2&gt;

&lt;p&gt;I'd expect predictable behavior.&lt;/p&gt;

&lt;p&gt;If I ask for Go, just show me Go — don’t wrap it in Lua and spawn processes behind the scenes. Users trust depends on clear, bounded behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This wasn’t a bug. It was the model being too smart, and it perfectly demonstrates why building safe, resilient LLM apps/tools requires real engineering discipline.&lt;/p&gt;

&lt;p&gt;All of this reinforces a timeless reminder:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With more power comes more responsibility&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>go</category>
      <category>safety</category>
    </item>
    <item>
      <title>Building an AI-Powered App Entirely in Go: From Simple Prompt to Smart Pipeline</title>
      <dc:creator>Naveen Vandanapu</dc:creator>
      <pubDate>Fri, 28 Nov 2025 16:49:32 +0000</pubDate>
      <link>https://dev.to/naveen_v_5562871ba6455b7e/building-an-ai-powered-app-entirely-in-go-from-simple-prompt-to-smart-pipeline-237b</link>
      <guid>https://dev.to/naveen_v_5562871ba6455b7e/building-an-ai-powered-app-entirely-in-go-from-simple-prompt-to-smart-pipeline-237b</guid>
      <description>&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;I've shipped AI features in many stacks, but over a weekend, I wanted to answer one question: &lt;strong&gt;"Can I build a complete, production-quality AI app using only Go?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not just a proof of concept. A real application with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structured AI flows&lt;/li&gt;
&lt;li&gt;Content moderation&lt;/li&gt;
&lt;li&gt;Smart interpretation&lt;/li&gt;
&lt;li&gt;Reactive UI&lt;/li&gt;
&lt;li&gt;Type safety end-to-end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An &lt;strong&gt;AI Welcome Note Generator&lt;/strong&gt; that evolved from a 10-line prompt to a multi-stage pipeline with safety filters and natural language understanding—all without leaving Go.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article walks through how everything fits together — from the simplest flow to a smart, multi-stage LLM pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  See It In Action
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://youtu.be/AULVFSzUHrU" rel="noopener noreferrer"&gt;Welcome Note Generator Demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch the application in action: from simple prompts to smart, moderated AI flows&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Genkit&lt;/strong&gt; — AI flow orchestration (the star of the show)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gin&lt;/strong&gt; — HTTP routing and middleware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini 2.0 Flash&lt;/strong&gt; — Fast, powerful LLM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; — Local model support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend (yes, in Go):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Templ&lt;/strong&gt; — Type-safe HTML templates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Datastar&lt;/strong&gt; — Reactive UI via Server-Sent Events (zero JavaScript!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; — Clean, responsive styling&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Rate limiting (configurable, per-IP)&lt;/li&gt;
&lt;li&gt;CSRF protection (with a clever workaround)&lt;/li&gt;
&lt;li&gt;Docker deployment&lt;/li&gt;
&lt;li&gt;Structured logging&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Google Cloud Run&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Before we dive into flows, here’s the full system at a glance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
          ┌──────────────────────────────┐
          │          Browser UI          │
          │  (Templ + Datastar + HTMX)   │
          └──────────────┬───────────────┘
                         │
                HTTP Form / SSE Streams
                         │
              ┌──────────▼───────────┐
              │         Gin          │
              │   (Handlers &amp;amp; API)   │
              └──────────┬───────────┘
                         │
                  Call Genkit Flow
                         │
            ┌────────────▼────────────┐
            │         Genkit          │
            │  Flows / Prompts / AI   │
            └────────────┬────────────┘
                         │
            ┌────────────▼─────────────┐
            │      Model Provider      │
            │   (Gemini / Ollama)      │
            └──────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything downstream of Gin is strongly typed, observable, traceable, and testable thanks to &lt;strong&gt;Genkit flows&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Journey: Five Versions, Five Lessons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Version 1: Keep It Simple
&lt;/h3&gt;

&lt;p&gt;I start every AI project with the bare minimum:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a flow that takes a string&lt;/li&gt;
&lt;li&gt;generates text&lt;/li&gt;
&lt;li&gt;returns it directly
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="n"&gt;genkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefineFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"welcomeV1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;occasion&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;system&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"You write simple, warm welcome notes."&lt;/span&gt;

        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;genkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithSystem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`Generate a welcome note for "%s".`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;occasion&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&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;resp&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="no"&gt;nil&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;Input:&lt;/strong&gt; &lt;code&gt;"birthday party"&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Output:&lt;/strong&gt; A friendly welcome message&lt;/p&gt;

&lt;p&gt;This version teaches the foundation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;flows are typed Go functions&lt;/li&gt;
&lt;li&gt;prompt → model → response is explicit&lt;/li&gt;
&lt;li&gt;Genkit generates schemas, observability, and HTTP endpoints automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI for this version is a single input box and a result area — powered purely by Templ + Datastar, no JS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Start with &lt;code&gt;string → string&lt;/code&gt;. Get the basics working before adding complexity.&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%2F2pwx4kgej7xpkodo301b.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%2F2pwx4kgej7xpkodo301b.png" alt="V1 Flow Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Version 2: Add Structure
&lt;/h3&gt;

&lt;p&gt;Real apps need more than a text box. Users want control. Let's move from raw text to structured fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;occasion or context&lt;/li&gt;
&lt;li&gt;language of choice&lt;/li&gt;
&lt;li&gt;length of the generated note&lt;/li&gt;
&lt;li&gt;tone or style of the note to be generated
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;WelcomeInput&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Occasion&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"occasion"`&lt;/span&gt; &lt;span class="c"&gt;// Occasion or Context&lt;/span&gt;
    &lt;span class="n"&gt;Language&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"language"`&lt;/span&gt; &lt;span class="c"&gt;// English, Spanish, etc.&lt;/span&gt;
    &lt;span class="n"&gt;Length&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"length"`&lt;/span&gt;   &lt;span class="c"&gt;// Short, Medium, Long&lt;/span&gt;
    &lt;span class="n"&gt;Tone&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"tone"`&lt;/span&gt;     &lt;span class="c"&gt;// Formal, Casual, Friendly&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The flow signature changes to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WelcomeInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step lets users customize the note through clean dropdowns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Structured input = better UX. Genkit handles validation automatically.&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%2Fbmou4lvtjgqkvmy03euo.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%2Fbmou4lvtjgqkvmy03euo.png" alt="V2 Flow Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Version 3: Structured Output + Metadata
&lt;/h3&gt;

&lt;p&gt;This is the moment Genkit's flows become truly powerful. Instead of parsing text, we tell the LLM to return &lt;strong&gt;typed JSON&lt;/strong&gt;:&lt;br&gt;
The model returns typed metadata, not just a string and Genkit parses it into typed data and validates it against the schema for us.&lt;/p&gt;
&lt;h4&gt;
  
  
  V3 Output
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;WelcomeOutput&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Note&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;            &lt;span class="s"&gt;`json:"note"`&lt;/span&gt;
    &lt;span class="n"&gt;Occasion&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;            &lt;span class="s"&gt;`json:"occasion"`&lt;/span&gt;
    &lt;span class="n"&gt;Language&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;            &lt;span class="s"&gt;`json:"language"`&lt;/span&gt;
    &lt;span class="n"&gt;Length&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt;            &lt;span class="s"&gt;`json:"length"`&lt;/span&gt;
    &lt;span class="n"&gt;Tone&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;            &lt;span class="s"&gt;`json:"tone"`&lt;/span&gt;
    &lt;span class="n"&gt;Metadata&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"metadata"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Flow V3 (structured input → structured output)
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;genkit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GenerateData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WelcomeOutput&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithSystem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPrompt&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;p&gt;Now we get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The welcome note&lt;/li&gt;
&lt;li&gt;Extracted occasion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata&lt;/strong&gt; (sentiment, safety score, comments)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I update UI to showcase metadata panels and structured JSON output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; &lt;code&gt;GenerateData[T]&lt;/code&gt; gives you type-safe AI responses. The LLM becomes a structured API.&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%2Facft3f89tghj8eq2hlks.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%2Facft3f89tghj8eq2hlks.png" alt="V3 Flow Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Safe Flow: Add Content Moderation
&lt;/h3&gt;

&lt;p&gt;Production AI needs safety and guardrails. I built a &lt;strong&gt;two-stage pipeline&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generate&lt;/strong&gt; the welcome note&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Moderate&lt;/strong&gt; it with a second LLM call&lt;/li&gt;
&lt;li&gt;If flagged, &lt;strong&gt;sanitize&lt;/strong&gt; and return both versions&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Safety Sequence Diagram
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
      ┌────────┐      ┌───────────────┐      ┌───────────────┐
Input │  User  │ ---&amp;gt; │ Generate Note │ ---&amp;gt; │ Moderate Note │
      └────────┘      └───────────────┘      └───────┬───────┘
                                                     │
                                          Block?  Sanitize?
                                                     │
                                      ┌──────────────▼──────────────┐
                                      │   SafeWelcomeNoteOutput     │
                                      └─────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Moderation Prompt (small but effective)
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a content safety filter. Remove or rewrite:

- toxicity
- insults
- hate speech
- threats
- explicit content
- sensitive details

Return JSON:
{
"note": "... sanitized note ...",
"blocked": false,
"moderationNote": "reason"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Safe Flow Logic
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Stage 1: Generate&lt;/span&gt;
&lt;span class="n"&gt;note&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;generateWelcomeNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Stage 2: Moderate&lt;/span&gt;
&lt;span class="n"&gt;moderation&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;moderateContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;note&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;moderation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Blocked&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;SafeOutput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Note&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;           &lt;span class="n"&gt;moderation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SanitizedNote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;OriginalNote&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Blocked&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ModerationNote&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;moderation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reason&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;UI is updated with amber sanitization banner — with "View original (flagged)" collapsible detail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Don't trust raw LLM output. Use a second model to validate safety.&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%2Fgo0y9vf3fou5qimk82p1.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%2Fgo0y9vf3fou5qimk82p1.png" alt="Content Moderation Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Smart Flow: Interpret Natural Language
&lt;/h3&gt;

&lt;p&gt;The final evolution: let users describe what they want in plain English.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User input:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I need a short, friendly welcome note for my hotel guests arriving this weekend&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Interpret&lt;/strong&gt; the description → extract structured parameters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate&lt;/strong&gt; the note using those parameters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Moderate&lt;/strong&gt; the output for safety&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Smart Flow Pipeline
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Raw Description
      │
      ▼
Interpretation Flow (LLM → structured input)
      │
      ▼
V3 Generator (structured → JSON note)
      │
      ▼
Safe Flow (moderation + sanitization)
      │
      ▼
SmartWelcomeFlowOutput
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Smart Flow Output
&lt;/h4&gt;

&lt;p&gt;This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;note (possibly sanitized)&lt;/li&gt;
&lt;li&gt;structured fields&lt;/li&gt;
&lt;li&gt;metadata&lt;/li&gt;
&lt;li&gt;original note (if sanitized)&lt;/li&gt;
&lt;li&gt;moderation reason&lt;/li&gt;
&lt;li&gt;raw user description&lt;/li&gt;
&lt;li&gt;parsed interpretation
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;SmartFlowOutput&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;SafeWelcomeNoteOutput&lt;/span&gt;              &lt;span class="c"&gt;// everything from safe welcome note output&lt;/span&gt;
    &lt;span class="n"&gt;RawDescription&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;               &lt;span class="c"&gt;// what user typed&lt;/span&gt;
    &lt;span class="n"&gt;ParsedInput&lt;/span&gt;    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WelcomeNoteInput&lt;/span&gt;    &lt;span class="c"&gt;// result of Interpretation Flow LLM output&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;UI is updated with extra sections such as - "Here's how the AI interpreted your request".&lt;br&gt;
This feels magical for user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Chain flows together. Each step is clean, testable, and observable.&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%2Fzotajkzs4wki5r371w54.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%2Fzotajkzs4wki5r371w54.png" alt="Smart Flow Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The Interesting Technical Bits
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. CSRF Challenges: Combining Gin and Gorilla
&lt;/h3&gt;

&lt;p&gt;Gin is great for routing and middleware. Gorilla has battle-tested CSRF protection. But &lt;strong&gt;they don't play nicely out of the box&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Gorilla CSRF expects &lt;code&gt;http.Handler&lt;/code&gt;, while Gin uses its own handler chain. I needed to combine the best of both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution:&lt;/strong&gt; Wrap the Gin router with CSRF middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// In cmd/web/main.go&lt;/span&gt;
&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c"&gt;// use router as needed to define routes&lt;/span&gt;

&lt;span class="c"&gt;// Wrap Gin with Gorilla CSRF&lt;/span&gt;
&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;csrf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Protect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CSRF&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;csrf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SameSite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csrf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SameSiteStrictMode&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c"&gt;// ... other options&lt;/span&gt;
&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Helper middleware to add CSRF token to Gin context&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"csrf_token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;csrf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c"&gt;// handler is of type http.Handler and ready to be used&lt;/span&gt;
&lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServeMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. Configurable Rate Limiting
&lt;/h3&gt;

&lt;p&gt;I built per-IP rate limiting with &lt;strong&gt;zero rebuilds&lt;/strong&gt; for config changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Environment variables&lt;/span&gt;
&lt;span class="nv"&gt;RATE_LIMIT_REQUESTS_PER_MINUTE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;30
&lt;span class="nv"&gt;RATE_LIMIT_BURST_SIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The middleware uses Go's &lt;code&gt;golang.org/x/time/rate&lt;/code&gt; for token bucket limiting. Clean, efficient, and Docker-friendly.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Reactive UI Without JavaScript
&lt;/h3&gt;

&lt;p&gt;Datastar + Templ = reactive UI via &lt;strong&gt;Server-Sent Events&lt;/strong&gt;:&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="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;data-on-submit=&lt;/span&gt;&lt;span class="s"&gt;"@post('/api/smart/generate')"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"description"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Generate&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-signal-loading&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Loading...&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;data-signal-result&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{result.note}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server streams updates, the browser reacts—all type-safe in Go.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Architecture Works (and scales)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Go is the perfect LLM backend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Fast&lt;/li&gt;
&lt;li&gt;Typed&lt;/li&gt;
&lt;li&gt;Memory-efficient&lt;/li&gt;
&lt;li&gt;Simple concurrency model&lt;/li&gt;
&lt;li&gt;Deployable anywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Genkit flows &amp;gt; hand-rolled AI wrappers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Observability&lt;/li&gt;
&lt;li&gt;Schemas&lt;/li&gt;
&lt;li&gt;Generation tracing&lt;/li&gt;
&lt;li&gt;Safe replayability&lt;/li&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Type inference&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Templ + Datastar feel like React without React
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Full reactivity&lt;/li&gt;
&lt;li&gt;No client bundles&lt;/li&gt;
&lt;li&gt;No JS toolchain&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Multi-stage flows model real AI workloads
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Generation&lt;/li&gt;
&lt;li&gt;Interpretation&lt;/li&gt;
&lt;li&gt;Moderation&lt;/li&gt;
&lt;li&gt;Structured output&lt;/li&gt;
&lt;li&gt;Pipeline composition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This mirrors real systems: customer support bots, agents, RAG pipelines, content tools, internal automation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The full code is open source. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run it locally with Ollama (no API keys needed)&lt;/li&gt;
&lt;li&gt;Deploy with Docker (one command)&lt;/li&gt;
&lt;li&gt;Swap Gemini for other models&lt;/li&gt;
&lt;li&gt;Extend the flows with your own logic&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;internal/flows/&lt;/code&gt; — All 5 flow versions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;web/middleware/&lt;/code&gt; — Rate limiting, CSRF, logging&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;web/templates/&lt;/code&gt; — Templ components&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Dockerfile&lt;/code&gt; — Production-ready Alpine build&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This project started as a small experiment:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What if I built an AI product entirely in Go?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It ended up demonstrating something bigger. Go's simplicity, Genkit's type safety, and a few well-chosen tools can give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast iteration&lt;/li&gt;
&lt;li&gt;Clean architecture&lt;/li&gt;
&lt;li&gt;Production-ready features&lt;/li&gt;
&lt;li&gt;Type safety from frontend to LLM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with V1. Add structure when you need it. Layer in safety and smart features as you go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The code is simple and the results are powerful.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Go 1.25, Genkit 1.2, and a love for clean code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/vnaveen-mh/welcome-note-generator" rel="noopener noreferrer"&gt;View the code&lt;/a&gt;&lt;br&gt;
🚀 &lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://welcome-note-generator-20546638839.us-central1.run.app" rel="noopener noreferrer"&gt;Try it live&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on Medium: &lt;a href="https://medium.com/p/0d9be75d3d00" rel="noopener noreferrer"&gt;https://medium.com/p/0d9be75d3d00&lt;/a&gt; — posting here for the dev.to community as well.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>go</category>
      <category>genkit</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
