<?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: ChunTing Wu</title>
    <description>The latest articles on DEV Community by ChunTing Wu (@lazypro).</description>
    <link>https://dev.to/lazypro</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%2F778575%2F460340c1-34d2-4312-890f-aef75f695c9d.jpeg</url>
      <title>DEV Community: ChunTing Wu</title>
      <link>https://dev.to/lazypro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lazypro"/>
    <language>en</language>
    <item>
      <title>Kiro Workflow for Copilot, Claude &amp; More</title>
      <dc:creator>ChunTing Wu</dc:creator>
      <pubDate>Mon, 08 Sep 2025 01:26:22 +0000</pubDate>
      <link>https://dev.to/lazypro/kiro-workflow-for-copilot-claude-more-4ihp</link>
      <guid>https://dev.to/lazypro/kiro-workflow-for-copilot-claude-more-4ihp</guid>
      <description>&lt;p&gt;Last week we introduced how an agent &lt;a href="https://medium.com/stackademic/ai-agents-for-complex-codebase-analysis-eef748fe6dfd" rel="noopener noreferrer"&gt;understands a codebase&lt;/a&gt; and built an actual &lt;a href="https://github.com/wirelessr/codebase-analyzer-agent" rel="noopener noreferrer"&gt;codebase agent&lt;/a&gt; to demonstrate the underlying mechanism.&lt;/p&gt;

&lt;p&gt;During this process, we highlighted some current limitations of Kiro. For instance, even with a detailed &lt;code&gt;design.md&lt;/code&gt; file, Kiro frequently deviates from the plan during execution, producing results that diverge significantly from the intended design.&lt;/p&gt;

&lt;p&gt;The root cause lies in Kiro's execution process, where it uses &lt;code&gt;design.md&lt;/code&gt; as context but fails to fully read the entire file. This issue becomes particularly serious when steering content is extensive, as the agent tends to protect its context window. Consequently, it may only read the first few lines of a file and consider it fully processed.&lt;/p&gt;

&lt;p&gt;This means when Kiro executes plans, we still need to constantly remind it to deviate from the design. Not only does this waste a lot of vibe request quotas, but it also lowers overall development efficiency. After all, we need to review every detail more carefully.&lt;/p&gt;

&lt;p&gt;The problem is that Kiro is a closed-source product, so we can't fine-tune its prompts. We're left watching this cycle repeat over and over. So I wondered if there's a way to use prompts to make tools like Copilot or Claude Code perform Kiro-like workflows while still allowing adjustment of each execution detail.  &lt;/p&gt;

&lt;p&gt;After actually trying it out, it turns out to work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/wirelessr/kiro-workflow-prompts" rel="noopener noreferrer"&gt;https://github.com/wirelessr/kiro-workflow-prompts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although this project is written in Copilot's format, it can be applied to any agent, including Claude Code. However, since I'm using Copilot, I'll demonstrate using its actual workflow.  &lt;/p&gt;

&lt;p&gt;Installation is straightforward, simply place these prompt files in the project directory or a global prompts folder to use them directly in Copilot's chat window.&lt;/p&gt;

&lt;p&gt;All prompt files are thoroughly explained in the &lt;code&gt;README.md&lt;/code&gt;. This article will focus on how to use them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copilot Spec-driven Development
&lt;/h2&gt;

&lt;p&gt;At first, just like Kiro's spec mode, we need to ask Copilot to create a spec directory and convert our requirements into an EARS specification document.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;/createSpec&lt;/code&gt;&lt;br&gt;
I want to build a web service with OAuth authentication, integrating Google OAuth and requiring a database to store user information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By running the &lt;code&gt;/createSpec&lt;/code&gt; command with these requirements, the Agent begins preparing the &lt;code&gt;requirements.md&lt;/code&gt; file. If there are materials under &lt;code&gt;.kiro/steering&lt;/code&gt; that provide behavioral guidelines for the Agent, it will also reference those contents.&lt;/p&gt;

&lt;p&gt;Throughout the requirements creation process, we can continuously interact with the Agent to refine the details of the entire requirements document. &lt;/p&gt;

&lt;p&gt;Here's an interesting observation, I've noticed Kiro tends to over-engineer things, adding some obsessive details. So I used system instructions to make our Agent more pragmatic like Linus Torvalds, adhering to the KISS (Keep It Simple, Stupid) principle.&lt;/p&gt;

&lt;p&gt;Once we have the &lt;code&gt;requirements.md&lt;/code&gt; file, we move on to the design phase.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;/design&lt;/code&gt;&lt;br&gt;
I approve the requirements document. Let's begin the design phase.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The design phase requires no prompting, as demonstrated by Kiro. We only need to approve &lt;code&gt;requirements.md&lt;/code&gt;, after which the Agent will autonomously initiate the design process. Of course, the Agent will still reference steering documents for design guidance, and we can continue to fine-tune the process throughout.&lt;/p&gt;

&lt;p&gt;Upon completing the design, the Agent will generate the corresponding &lt;code&gt;design.md&lt;/code&gt; file, enabling us to proceed to the next step, the planning phase.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;/createTask&lt;/code&gt; &lt;br&gt;
I approve the design document. Let's begin planning.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here, explicit approval is still required; otherwise, the Agent will not proceed.  &lt;/p&gt;

&lt;p&gt;After this phase is complete, we will have the three most critical specifications: &lt;code&gt;requirements.md&lt;/code&gt;, &lt;code&gt;design.md&lt;/code&gt;, and &lt;code&gt;tasks.md&lt;/code&gt;. Interestingly, these outputs follow Kiro's rules exactly. Therefore, switching back to Kiro to execute tasks from this stage is no problem at all.&lt;/p&gt;

&lt;p&gt;After approving &lt;code&gt;tasks.md&lt;/code&gt;, the final step is to enter the execution phase.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;/executeTask&lt;/code&gt; task1&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can explicitly specify which task to start with, or omit the task number altogether. In that case, the Agent will attempt to begin with the first uncompleted task.&lt;/p&gt;

&lt;p&gt;This workflow finishes once the entire &lt;code&gt;tasks.md&lt;/code&gt; file is marked as complete. We can see this is basically identical to Kiro's process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;In these prompts, I explicitly state that all documents must be thoroughly reviewed before proceeding. However, the preceding steps don't need to be so meticulous, as humans will collaborate with the Agent to co-create those specification documents.&lt;/p&gt;

&lt;p&gt;However, &lt;code&gt;/executeTask&lt;/code&gt; operates in headless execution mode, so we absolutely must ensure the Agent understands its purpose. Therefore, within the &lt;code&gt;executeTask&lt;/code&gt; prompt, I not only require it to thoroughly review all files but also demand it summarize each one. Only by asking for summaries can we enforce sufficient rigor to ensure the Agent genuinely reads everything carefully.&lt;/p&gt;

&lt;p&gt;This insight emerged during my implementation of the codebase agent.  The greatest advantage of this workflow is that we can achieve results comparable to Kiro, or even better. Using tools we're familiar with (and have paid for), we can customize prompts to meet specific needs. Just as I required the agent to thoroughly read files, there's lots of room for adjustment, allowing us to fully unleash our creativity.&lt;/p&gt;

&lt;p&gt;If you have any good ideas, feel free to contribute to my project. Currently, I've tailored the prompts based on my own habits and needs, but you might have even more insights to share.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>How to Learn AI from Scratch</title>
      <dc:creator>ChunTing Wu</dc:creator>
      <pubDate>Tue, 17 Jun 2025 01:44:33 +0000</pubDate>
      <link>https://dev.to/lazypro/how-to-learn-ai-from-scratch-3368</link>
      <guid>https://dev.to/lazypro/how-to-learn-ai-from-scratch-3368</guid>
      <description>&lt;p&gt;In today's era of generative AI, there are countless ways to get started with AI. However, for engineers without a background in AI or machine learning, the overwhelming number of buzzwords can make it hard to know where to begin. That said, there's no denying the productivity gap between those who know how to use AI and those who don’t.&lt;/p&gt;

&lt;p&gt;This article aims to give software engineers a fast-track introduction—a practical guide to navigating and thriving in this new landscape.&lt;/p&gt;

&lt;p&gt;The mind map below outlines the flow of the article. We’ll start with how to use AI effectively, then move into how to build things with it, touching on key concepts along the way. Let’s dive in.&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%2Fvhnvcj33xdu9ctlegpnw.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%2Fvhnvcj33xdu9ctlegpnw.png" alt="image" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use
&lt;/h2&gt;

&lt;p&gt;When people talk about using AI, it's impossible not to bring up how it all started—&lt;strong&gt;ChatGPT&lt;/strong&gt; was the moment generative AI really hit the mainstream. After that, every major company started launching their own chat models.&lt;/p&gt;

&lt;p&gt;I’ve listed four models I personally use almost every day. You might wonder why I switch between them. Simple reason: free plans come with limits, so rotating helps me stay productive without paying.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ChatGPT – This is the one I use the most. Whether I’m writing, editing, brainstorming, or just trying to get a fresh idea out, ChatGPT usually gets the first draft going.&lt;/li&gt;
&lt;li&gt;Claude – When it comes to quick scripts or anything related to the command line, Claude feels the easiest to work with. For instance, if I need a curl command to upload a JSON file with auth, I’ll ask Claude.&lt;/li&gt;
&lt;li&gt;Gemini – I use this mainly for more in-depth research. It gives off a more grounded vibe, which helps when I need something solid to work with.&lt;/li&gt;
&lt;li&gt;Grok – Once I hit Gemini’s limit for the day, Grok usually takes over.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools make it really easy to fold AI into everyday tasks. For most situations, this setup covers everything I need. But when I’m working on something more specific—like building a presentation—I’ll bring in different tools.&lt;/p&gt;

&lt;p&gt;One I rely on a lot is &lt;a href="https://gamma.app/" rel="noopener noreferrer"&gt;Gamma.app&lt;/a&gt;. It’s changed how I make slides.&lt;/p&gt;

&lt;p&gt;I already had a pretty good rhythm from doing talks regularly, so I can usually outline things quickly. But Gamma takes it even further. I just give it a prompt, let it build a rough version, and then tweak the parts I want to improve. Something that used to take half a day now takes me about an hour.&lt;/p&gt;

&lt;p&gt;Another one I keep coming back to is &lt;a href="https://www.perplexity.ai" rel="noopener noreferrer"&gt;Perplexity&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since generative AI is basically predicting what words come next, it’s not always accurate. That’s where Perplexity helps—it’s the tool I use to cross-check facts or dig up references. Sure, other AI tools have similar features, but I’ve set Perplexity as my browser’s default search engine, so it’s the quickest for me.&lt;/p&gt;

&lt;p&gt;I use a few other tools depending on the project. For example, if you work with Confluence Cloud, Rovo Chat is a solid option that fits nicely into that workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vibe Coding
&lt;/h3&gt;

&lt;p&gt;For software engineers, vibe coding has become a key part of using AI effectively to boost productivity. But getting good at it takes a lot of hands-on practice—and even just picking the right IDE and agent can take serious trial and error.&lt;/p&gt;

&lt;p&gt;Beyond that, no matter which agent you go with, you’ll still need to plug it into the right ecosystem to unlock its full potential. Some rely on MCP-style control flows, others on rule templates. Here are a few specific needs I personally care a lot about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/eyaltoledano/claude-task-master" rel="noopener noreferrer"&gt;Task master&lt;/a&gt;: If your instructions to the AI aren’t clear enough, it’s easy for the model to get stuck—wasting tokens without producing anything useful. This was highlighted in &lt;a href="https://ml-site.cdn-apple.com/papers/the-illusion-of-thinking.pdf" rel="noopener noreferrer"&gt;Apple’s paper&lt;/a&gt;, which shows that as task complexity increases, model performance can collapse entirely. That’s why task decomposition is essential. Task master is a solid open-source option for that.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/vanzan01/cursor-memory-bank" rel="noopener noreferrer"&gt;Memory bank&lt;/a&gt;: Since LLMs have limited context windows, they tend to forget past mistakes or important task details. A persistent memory mechanism helps address that gap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are plenty of other tools that can be added, depending on your development habits and how far you've gone with vibe coding. It's really about building the setup that fits your workflow.&lt;/p&gt;

&lt;p&gt;Underneath all of this is prompt engineering—everything starts with how you communicate your intent to the model. Getting that part right matters a lot. Fortunately, Google has published a pretty thorough &lt;a href="https://www.kaggle.com/whitepaper-prompt-engineering" rel="noopener noreferrer"&gt;whitepaper&lt;/a&gt; that’s worth reading if you’re looking to understand the fundamentals.&lt;/p&gt;

&lt;p&gt;If you want to go deeper, there’s a growing body of research too.&lt;/p&gt;

&lt;p&gt;For example, &lt;a href="https://arxiv.org/pdf/2312.16171v1" rel="noopener noreferrer"&gt;this paper&lt;/a&gt; summarizes 26 different techniques for improving prompts—worth checking out if you’re serious about refining your workflow.&lt;/p&gt;

&lt;p&gt;Vibe coding is half tools, half mindset—and prompt engineering is the bridge between the two.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Develop
&lt;/h2&gt;

&lt;p&gt;Once you’ve got the hang of using AI, the natural next step—especially for engineers—is to start experimenting with what AI can actually do for you. Personally, I’ve built a handful of small tools that I now use regularly at work, like a code review agent and a text-to-command-line agent.&lt;/p&gt;

&lt;p&gt;These tools each focus on solving very specific problems with AI. So how do you even start building something like that?&lt;/p&gt;

&lt;h3&gt;
  
  
  Model selection
&lt;/h3&gt;

&lt;p&gt;The first step is understanding what resources are available. By “resources,” I mean which AI services expose endpoints that you can actually call.&lt;/p&gt;

&lt;p&gt;If you’re willing to pay, then the big-name providers are all viable. But if you’re trying to keep costs low, what are your options? Turns out—plenty. Here are a few I actively use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://openrouter.ai/" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt;: This platform gives you access to a wide range of models, including quite a few with free quotas. In fact, even models like Google’s Gemma 3:27B have a free tier here.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;: If you’re worried about running through OpenRouter’s quotas, you can always fall back to local setups. Ollama is plug-and-play and runs locally. The only trade-off? Heavy models are tough to handle on a local machine—but smaller models are often too limited.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/snailyp/gemini-balance" rel="noopener noreferrer"&gt;gemini-balance&lt;/a&gt;: This one’s kind of clever. Gemini offers a free tier that’s actually free—it doesn’t sneak in charges once you pass a usage limit. The catch is, the quota’s small. But if you can cycle through enough free-tier tokens, you can effectively run things at zero cost. That’s exactly what gemini-balance helps with.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s how I’ve set things up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For important tasks, I rely on gemini-balance. The Gemini 2.0 models are just that good—I trust them with higher-stakes stuff.&lt;/li&gt;
&lt;li&gt;For lighter tasks, I go with OpenRouter, especially when I want to use Gemma 3:27B. It’s a strong model, but OpenRouter doesn’t support function calling for Gemma, so I keep it for simpler jobs.&lt;/li&gt;
&lt;li&gt;For embedding, I use Ollama locally. Embedding isn’t very compute-intensive, but I run it at scale, so I’d rather not worry about hitting quotas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you can probably tell, picking a model isn’t just about performance—it’s also about constraints, access, and cost. Each model comes with its own trade-offs, so understanding those is key to building something reliable.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Application
&lt;/h3&gt;

&lt;p&gt;Once you're familiar with the tools, the next step is figuring out what kind of applications you want to build.&lt;/p&gt;

&lt;p&gt;This is where imagination comes in—but regardless of what you're building, you'll eventually run into the concept of RAG.&lt;/p&gt;

&lt;p&gt;RAG (Retrieval-Augmented Generation) is one of the most effective ways to unlock real-world utility from LLMs.&lt;/p&gt;

&lt;p&gt;Why? &lt;/p&gt;

&lt;p&gt;Because language models can’t access huge documents directly—their input limits are real. But many tasks (like customer support) require deep background context. That’s exactly what RAG helps with.&lt;/p&gt;

&lt;p&gt;I won’t go deep into RAG architecture here. If you're curious, I wrote a more detailed piece: &lt;a href="https://medium.com/@lazypro/evolution-of-rag-baseline-rag-graphrag-and-kag-93b6f221bde6" rel="noopener noreferrer"&gt;Evolution of RAG: Baseline RAG, GraphRAG, and KAG&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;No matter what kind of app you're building, sooner or later, you'll hit the need for fine-tuning. Most base models are trained for general tasks, but real applications require customization—your data, your workflows. That’s where fine-tuning comes in. Of course, fine-tuning isn’t trivial—it assumes some background in ML. I’ll cover that in another post.&lt;/p&gt;

&lt;p&gt;And then there’s evaluation and observability—two things you must consider when your AI app is live. You need to know if the model is doing its job, and why it failed when it didn’t. Tools like &lt;a href="https://www.langchain.com/langsmith" rel="noopener noreferrer"&gt;LangSmith&lt;/a&gt; and &lt;a href="https://langfuse.com/" rel="noopener noreferrer"&gt;LangFuse&lt;/a&gt; can help with this, but you’ll need to spend time experimenting with what works best for your stack.&lt;/p&gt;

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

&lt;p&gt;So yeah—diving into AI development is a big journey.&lt;/p&gt;

&lt;p&gt;From picking tools, to writing prompts, to deploying apps, there’s a huge surface area of decisions.&lt;/p&gt;

&lt;p&gt;But don’t let that intimidate you. AI is still new, and most of us are learning as we go.&lt;/p&gt;

&lt;p&gt;My advice: start with problems from your day-to-day life. That’s the best way to learn. You’ll naturally uncover more tools, more patterns, and more techniques as you build.&lt;br&gt;
Eventually, you’ll develop your own set of “survival skills” for the AI era.&lt;/p&gt;

&lt;p&gt;This article includes a pretty massive mind map—it touches on nearly every major area in the Gen AI space.&lt;/p&gt;

&lt;p&gt;You don’t need to master everything—not even close. Just start with what you care about, build one small thing, and the rest will follow.&lt;/p&gt;

&lt;p&gt;In this new AI era, curiosity and momentum matter way more than credentials.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>career</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Master Vibe Coding: AI-Powered Workflow</title>
      <dc:creator>ChunTing Wu</dc:creator>
      <pubDate>Mon, 12 May 2025 06:32:12 +0000</pubDate>
      <link>https://dev.to/lazypro/master-vibe-coding-ai-powered-workflow-1jmf</link>
      <guid>https://dev.to/lazypro/master-vibe-coding-ai-powered-workflow-1jmf</guid>
      <description>&lt;p&gt;Recently, I've taken a lot of time to actually experience vibe coding, and I've come up with a few tips.&lt;/p&gt;

&lt;p&gt;TL;DR&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The used agent must support differential mode modification instead of overwriting the entire file.&lt;/li&gt;
&lt;li&gt;Commit continuously.&lt;/li&gt;
&lt;li&gt;The commands should be clear and simple, and context-sensitive.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before describing the experience I'd like to briefly introduce the toolset I use.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IDE is VS Code.&lt;/li&gt;
&lt;li&gt;Agent is a plugin from &lt;a href="https://marketplace.visualstudio.com/items/?itemName=RooVeterinaryInc.roo-cline" rel="noopener noreferrer"&gt;RooCode&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Model uses several free models provided by &lt;a href="https://openrouter.ai/" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt;.

&lt;ul&gt;
&lt;li&gt;The main one is &lt;a href="https://openrouter.ai/deepseek/deepseek-chat-v3-0324:free" rel="noopener noreferrer"&gt;deepseek-v3-0324&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Occasionally &lt;a href="https://openrouter.ai/meta-llama/llama-4-scout:free" rel="noopener noreferrer"&gt;llama-4-scout&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;As you can see I use free tools, which I believe is the best setup for the common developer. But it also creates a lot of headaches, which I'll talk about in a bit more detail.&lt;/p&gt;

&lt;p&gt;Next, I'll provide you with the complete project I made using vibe coding.&lt;br&gt;
&lt;a href="https://github.com/wirelessr/alice" rel="noopener noreferrer"&gt;https://github.com/wirelessr/alice&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a command line tool that converts the natural language into the actual terminal command to be executed and gets the result.&lt;/p&gt;

&lt;p&gt;In fact, many tools have similar functions, as follows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Warp (AI-powered terminal tool)&lt;/li&gt;
&lt;li&gt;iTerm2 AI&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/TheR1D/shell_gpt" rel="noopener noreferrer"&gt;ShellGPT&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/BuilderIO/ai-shell" rel="noopener noreferrer"&gt;ai-shell&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are many more, but they all have their own problems.&lt;/p&gt;

&lt;p&gt;The main one is they don't support customized models like OpenRouter or Anthropic, and the second one is that the results are not very good, either the results are not on point or they aren't easy to use.&lt;/p&gt;

&lt;p&gt;So I wrote a tool for my own needs, and I won't dive into the details of the tool.&lt;/p&gt;

&lt;p&gt;The whole development process is I wrote a basic working script first, which is &lt;code&gt;main.py&lt;/code&gt; and then I rely on vibe coding to start refactoring and adding new features step by step.&lt;/p&gt;

&lt;p&gt;This development process should fit the scenario of most developers practicing vibe coding. Instead of using vibe coding to create a project from scratch, we use vibe coding to iterate on an existing project.&lt;/p&gt;

&lt;p&gt;Let's start by explaining the three points mentioned at the beginning of the article.&lt;/p&gt;
&lt;h2&gt;
  
  
  Diff mode patching
&lt;/h2&gt;

&lt;p&gt;I think this is one of the most important things, why?&lt;/p&gt;

&lt;p&gt;When the model is not smart enough, it is easy to overwrite the whole file and change something that is not a problem. Here is an example of deepseek-v3-0324 muddying the water.&lt;/p&gt;

&lt;p&gt;The project I developed uses &lt;a href="https://github.com/microsoft/autogen" rel="noopener noreferrer"&gt;AutoGen&lt;/a&gt; to implement the communication between agents, here is the import I used.&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;autogen_ext.models.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIChatCompletionClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;autogen_core.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ModelInfo&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;autogen_agentchat.agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AssistantAgent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But when RooCode wanted to make some changes to the code, he would often overwrite my entire file. Then the code breaks. This is the most common “unauthorized” change he does.&lt;/p&gt;

&lt;p&gt;He changes &lt;code&gt;models&lt;/code&gt; to &lt;code&gt;model&lt;/code&gt; and &lt;code&gt;agents&lt;/code&gt; to &lt;code&gt;agent&lt;/code&gt; for some reason.&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;autogen_ext.model.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIChatCompletionClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;autogen_core.model&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ModelInfo&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;autogen_agentchat.agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AssistantAgent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, I've added a couple of absolute rules to my RooCode.&lt;/p&gt;

&lt;p&gt;.roo/rules/rules.md&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Do not overwrite the whole file easily.
2. Just modify parts that is asked.
3. Do not change any no bug part.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Commit continuously
&lt;/h2&gt;

&lt;p&gt;In vibe coding, agent behavior is largely unpredictable, especially with dummy agents like the example above.&lt;/p&gt;

&lt;p&gt;So when you make a command, and you get an acceptable result, you have to make a &lt;code&gt;git commit&lt;/code&gt; right away. Even if the command is only partially complete, at least the result is acceptable, so save it.&lt;/p&gt;

&lt;p&gt;In the beginning, I often let the agent do what he wants and I ask him to do what I want, and then I have no idea what he's changed, or even what he's changed that's broken.&lt;/p&gt;

&lt;p&gt;Therefore, please remember, as long as you see the result is acceptable, then just commit.&lt;/p&gt;

&lt;p&gt;With commit, you will be able to compare what you have changed in subsequent commands, and you will also be able to know what needs to be fixed in this prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt should be clear and contextualized
&lt;/h2&gt;

&lt;p&gt;Don't expect the agent to know what you want to change.&lt;/p&gt;

&lt;p&gt;If you want to change something, tell the agent exactly what you want, which file, which function or even which lines you want to change. The clearer you are, the more you can get the expected result, not the free-for-all of a creative person.&lt;/p&gt;

&lt;p&gt;When I first came across vibe coding, I always thought that agent can see all the code and structure, so agent should know what to change. But I was wrong, and very wrong.&lt;/p&gt;

&lt;p&gt;There is a limit to the context that Agent can see. Of course, you can make that context huge, but there are a couple of problems.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Huge contexts cost money.&lt;/li&gt;
&lt;li&gt;Huge contexts are easier to hallucinate.&lt;/li&gt;
&lt;li&gt;Huge contexts are not always acceptable in terms of agent capability.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So you have to tell the agent clearly what to look at and where to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Will developers be eliminated?
&lt;/h2&gt;

&lt;p&gt;There is often a dream that agent can replace the developer, I can only say, this is really a dream.&lt;/p&gt;

&lt;p&gt;The project structure this time is quite simple, and the functions are not complicated.&lt;/p&gt;

&lt;p&gt;To put it frankly, it is actually three agents communicate with each other to accomplish one thing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Planning agent converts the natural language into a command line to be executed.&lt;/li&gt;
&lt;li&gt;Execution agent executes the command line and reports the result.&lt;/li&gt;
&lt;li&gt;Verification agent checks whether the result is correct or not, if not, it will start again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Along the way, I added some new features.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use PyInstaller to generate the executable.&lt;/li&gt;
&lt;li&gt;Automatically release with Github Action.&lt;/li&gt;
&lt;li&gt;Support both environment variables and configuration files.&lt;/li&gt;
&lt;li&gt;Model setting.&lt;/li&gt;
&lt;li&gt;Choose the language of the response.&lt;/li&gt;
&lt;li&gt;Hide agent interactions and only get the final result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I've also done some refactoring, such as splitting &lt;code&gt;main.py&lt;/code&gt; into corresponding modules or turning the config module into a singleton pattern.&lt;/p&gt;

&lt;p&gt;But with vibe coding, I feel a bit exhausted, agent's development efficiency is not as good as my own code, sometimes, I am really angry to see him playing idiot, but after all, I am practicing vibe coding, so I can only continue to see him playing idiot.&lt;/p&gt;

&lt;p&gt;This kind of playing idiots with the stronger the model will be less and less, that is to say, it can be easier to write down the prompt.&lt;/p&gt;

&lt;p&gt;Back to the above conclusion, vibe coding can not replace the developer, but for the skilled developers can improve development efficiency.&lt;/p&gt;

&lt;p&gt;For example, if I tell the agent to turn my &lt;code&gt;load_config()&lt;/code&gt; into a singleton pattern, he will make a standard singleton class as he imagines, but his context is not enough to change all the callers to &lt;code&gt;load_config&lt;/code&gt; and then break the program.&lt;/p&gt;

&lt;p&gt;But if I tell him to turn &lt;code&gt;load_config&lt;/code&gt; into a singleton while keeping the &lt;code&gt;load_config&lt;/code&gt; interface the same, he'll be able to make something similar to what I imagined.&lt;/p&gt;

&lt;p&gt;So the role of the developer has become more important.&lt;/p&gt;

&lt;p&gt;Nowadays, more and more agents have orchestractor (dispatcher) mode, which means he will break down complex tasks and assign them to other agents. Honestly, what he does is really not good, it's better to break down the requirements myself and then use prompt to tell the agent what to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;Vibe coding is already a trend among developers, but just like we practice writing programs, vibe coding requires continuous practice.&lt;/p&gt;

&lt;p&gt;The more we use an agent, the easier to know where the agent's limits are, and the easier to make the agent meet the expectations.&lt;/p&gt;

&lt;p&gt;Vibe coding is just a tool, a tool to make things easier for the user, not to replace the user. I hope everyone can become a sophisticated user of vibe coding, I am practicing too.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>promptengineering</category>
    </item>
    <item>
      <title>Hands-On WrenAI Review: Text-to-SQL Powered by RAG</title>
      <dc:creator>ChunTing Wu</dc:creator>
      <pubDate>Mon, 21 Apr 2025 01:44:48 +0000</pubDate>
      <link>https://dev.to/lazypro/hands-on-wrenai-review-text-to-sql-powered-by-rag-40fb</link>
      <guid>https://dev.to/lazypro/hands-on-wrenai-review-text-to-sql-powered-by-rag-40fb</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/Canner/WrenAI" rel="noopener noreferrer"&gt;WrenAI&lt;/a&gt; is a text to sql solution that I've been following for a while. Recently, I have some time to try it out, so let me share my experience.&lt;/p&gt;

&lt;p&gt;First of all, according to the &lt;a href="https://docs.getwren.ai/oss/installation" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt; provided by the official document, we can generally deploy WrenAI to the local machine. As for the integration with local Ollama, there are some &lt;a href="https://docs.getwren.ai/oss/ai_service/guide/custom_llm#update-your-configurations" rel="noopener noreferrer"&gt;sample configuration files&lt;/a&gt; that can be used.&lt;/p&gt;

&lt;p&gt;However, there are still some details to be adjusted that are not mentioned in the document. For example, if we use the &lt;code&gt;ollama/nomic-embed-text&lt;/code&gt; mentioned in the document as the embedder, then we need to change the &lt;code&gt;embedding_model_dim&lt;/code&gt; of the configuration file from 3072 to 768, which is a detail that can be easily overlooked.&lt;/p&gt;

&lt;p&gt;Just provide the appropriate settings and WrenAI will work fine.&lt;/p&gt;

&lt;p&gt;By the way, I am using MySQL and the official MySQL &lt;a href="https://github.com/datacharmer/test_db" rel="noopener noreferrer"&gt;test dataset&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  WrenAI Advantage
&lt;/h2&gt;

&lt;p&gt;In addition to schema-based chat Q&amp;amp;A, WrenAI has another awesome feature.&lt;/p&gt;

&lt;p&gt;It provides the flexibility to customize prompts, and under WrenAI's Knowledge page, it is possible to enter pre-designed questions and corresponding SQL answers. In addition, we can also enter additional commands to provide the AI with additional “parameters”.&lt;/p&gt;

&lt;p&gt;After I disassembled WrenAI's prompt, I realized that these Knowledge play a very important role in determining the final SQL's appearance. This customization provides a reliable fine tuning opportunity when integrating a usage-based data source like BigQuery.&lt;/p&gt;

&lt;h2&gt;
  
  
  WrenAI Disadvantage
&lt;/h2&gt;

&lt;p&gt;This is a fairly new project (0.19.2 at the moment), so there are bound to be some bugs.&lt;/p&gt;

&lt;p&gt;I've encountered two problems that I find quite annoying.&lt;/p&gt;

&lt;p&gt;First, when I first logged in the homepage, WrenAI will provide some recommended questions based on the data model, so that users can get into the situation quickly. However, this kind of full model scanning consumes a lot of computing power, and if the model is not strong enough, basically we cannot get the result.&lt;/p&gt;

&lt;p&gt;It doesn't matter if we can't get the recommended questions, but WrenAI's error handle is not well designed, it will provide a lot of unimportant built-in questions, and there is no align project setting for the language. I've mentioned a &lt;a href="https://github.com/Canner/WrenAI/issues/1550" rel="noopener noreferrer"&gt;GitHub issue&lt;/a&gt; about this.&lt;/p&gt;

&lt;p&gt;Secondly, even though there are not many data models in the source and not many columns, the &lt;code&gt;llama3.1:8b&lt;/code&gt; model still has a certain percentage of AI hallucination. &lt;/p&gt;

&lt;p&gt;Since I'm a data engineer, it's easy for me to read SQL, so it's easy for me to find the problem, and WrenAI also provides a good correction mechanism to correct the original answer, so I haven't encountered too many obstacles in using it.&lt;/p&gt;

&lt;p&gt;However, I am worried that people who are not familiar with datasets and SQL may have unexpected surprises if they use it directly.&lt;/p&gt;

&lt;p&gt;Lastly, and this is both a strength and a weakness of WrenAI, WrenAI is based on a RAG implementation to generate SQL, which requires a strong model to support. Take my &lt;code&gt;llama3.1:8b&lt;/code&gt; running on MacBook Pro M2, a simple problem (joining a few tables) would take more than 5 minutes, not to mention the complicated problems, and it's common to run into internal server error.&lt;/p&gt;

&lt;p&gt;I won't go into the details of some minor Web UI issues and design flaws in the interaction with the backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;WrenAI uses RAG as the foundation to implement a pretty good text to sql solution, and because it is based on RAG, there is a lot of flexibility to customize the prompt.&lt;/p&gt;

&lt;p&gt;However, because it is a RAG, the computing power and model capacity are high, and the effect on small model scenes needs to be strengthened.&lt;/p&gt;

&lt;p&gt;I will do more experiments with more powerful models and expect to get better results. Overall, WrenAI is a product worth trying.&lt;/p&gt;

</description>
      <category>datascience</category>
      <category>sql</category>
      <category>ai</category>
      <category>rag</category>
    </item>
    <item>
      <title>Benchmarking Lakehouse Query Engines with TPC-DS</title>
      <dc:creator>ChunTing Wu</dc:creator>
      <pubDate>Thu, 20 Feb 2025 02:51:34 +0000</pubDate>
      <link>https://dev.to/lazypro/benchmarking-lakehouse-query-engines-with-tpc-ds-2ac0</link>
      <guid>https://dev.to/lazypro/benchmarking-lakehouse-query-engines-with-tpc-ds-2ac0</guid>
      <description>&lt;p&gt;Apache Iceberg is already a popular lakehouse format that is supported by many query engines. What should we do if we want to make a technical selection among many query engines?&lt;/p&gt;

&lt;p&gt;In the data warehouse domain, the most commonly used standard is &lt;a href="https://www.tpc.org/tpcds/" rel="noopener noreferrer"&gt;TPC-DS&lt;/a&gt;, which defines several common scenarios and provides a set of standardized queries. Generally speaking, TPC-DS is the gold standard for benchmarking performance.&lt;/p&gt;

&lt;p&gt;Although TPC-DS is quite popular and there are many common connectors for dumping test data into various databases, and even Trino, a pure computing engine, provides a dedicated &lt;a href="https://trino.io/docs/current/connector/tpcds.html" rel="noopener noreferrer"&gt;catalog&lt;/a&gt; for TPC-DS, there is no such thing as a TPC-DS for lakehouse at the moment.&lt;/p&gt;

&lt;p&gt;Lakehouse does not have a good connector for this purpose. Therefore, in this article we will try to describe how to dump the test data of TPC-DS into Iceberg's lakehouse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experiment environment setup
&lt;/h2&gt;

&lt;p&gt;Regarding how to build the TPC-DS tools is not the focus of this article, so I'll start by assuming that &lt;code&gt;dsdgen&lt;/code&gt; is already installed.&lt;/p&gt;

&lt;p&gt;First, let's generate a test data package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dsdgen &lt;span class="nt"&gt;-SCALE&lt;/span&gt; 1 &lt;span class="nt"&gt;-DIR&lt;/span&gt; /home/ec2-user/sample
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have the test data, we need to build the Iceberg environment and import the data.&lt;/p&gt;

&lt;p&gt;Although I've provided some Iceberg playgrounds before, this time I'd like to use tabular's &lt;a href="https://www.tabular.io/blog/docker-spark-and-iceberg-the-fastest-way-to-try-iceberg/" rel="noopener noreferrer"&gt;experiment environment&lt;/a&gt;. The main reason is that the tabular environment also includes a spark notebook, which helps a lot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/wirelessr/37b19323664cff6f9af42bd814f05a5d#file-docker-compose-yaml" rel="noopener noreferrer"&gt;https://gist.github.com/wirelessr/37b19323664cff6f9af42bd814f05a5d#file-docker-compose-yaml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One small modification we made was to mount the test folder &lt;code&gt;sample&lt;/code&gt; into &lt;code&gt;spark-iceberg&lt;/code&gt;.&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;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./warehouse:/home/iceberg/warehouse&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./notebooks:/home/iceberg/notebooks/notebooks&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./sample:/home/iceberg/sample&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Import test data
&lt;/h2&gt;

&lt;p&gt;After we have created the environment, we need to write the data into Iceberg.&lt;/p&gt;

&lt;p&gt;First, we need to create table schema.&lt;/p&gt;

&lt;p&gt;In general, the &lt;code&gt;tpcds.sql&lt;/code&gt; of TPC-DS can be used directly, but there are a few things that need to be modified in the Iceberg experiment.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add catalog and database to the original table name, i.e. &lt;code&gt;&amp;lt;catalog&amp;gt;.&amp;lt;db&amp;gt;.&amp;lt;table&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;primary key&lt;/code&gt; line should be pulled out and declared &lt;code&gt;PARTITIONED BY&lt;/code&gt; instead. It's better to have it in buckets, so I've split it into 16 buckets, but it can be tweaked.&lt;/li&gt;
&lt;li&gt;Now that the &lt;code&gt;primary key&lt;/code&gt; has been removed, remember to remove the comma from the previous line as well.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the table &lt;code&gt;income_band&lt;/code&gt; for illustration.&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;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;income_band&lt;/span&gt;      &lt;span class="c1"&gt;-- rename table&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ib_income_band_sk&lt;/span&gt;  &lt;span class="nb"&gt;integer&lt;/span&gt;  &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ib_lower_bound&lt;/span&gt;     &lt;span class="nb"&gt;integer&lt;/span&gt;          &lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ib_upper_bound&lt;/span&gt;     &lt;span class="nb"&gt;integer&lt;/span&gt;          &lt;span class="c1"&gt;-- remove comma: ,&lt;/span&gt;
    &lt;span class="c1"&gt;-- remove line: primary key (ib_income_band_sk)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;PARTITIONED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ib_income_band_sk&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;-- add line&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have written a Python script to handle this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/wirelessr/37b19323664cff6f9af42bd814f05a5d#file-proc_ddl-py" rel="noopener noreferrer"&gt;https://gist.github.com/wirelessr/37b19323664cff6f9af42bd814f05a5d#file-proc_ddl-py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we are done with all the DDLs, then we just need to open the &lt;code&gt;Spark SQL&lt;/code&gt; built into the experiment environment to copy and paste those DDLs directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; spark-iceberg spark-sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the tables are defined, it's time to start importing the data into Iceberg, which we typically do using the shortcuts provided by Spark SQL.&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;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;income_band&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;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;`file:///home/iceberg/sample/income_band.dat`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there is a problem, the column type will not match and cause an error (SQL will treat &lt;code&gt;INT&lt;/code&gt; in csv as &lt;code&gt;STRING&lt;/code&gt;). So we can do it with PySpark to save some time.&lt;/p&gt;

&lt;p&gt;There is also a ready-made notebook available in the experiment environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; spark-iceberg pyspark-notebook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, we just need to open the notebook and start the task of importing the csv.&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;pyspark.sql&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;

&lt;span class="n"&gt;dat&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;call_center.dat&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;catalog_page.dat&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;catalog_returns.dat&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;catalog_sales.dat&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;customer.dat&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;customer_address.dat&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;customer_demographics.dat&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;date_dim.dat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# 'dbgen_version.dat',
&lt;/span&gt;    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;household_demographics.dat&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;income_band.dat&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;inventory.dat&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;item.dat&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;promotion.dat&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;reason.dat&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;ship_mode.dat&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;store.dat&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;store_returns.dat&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;store_sales.dat&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;time_dim.dat&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;warehouse.dat&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;web_page.dat&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;web_returns.dat&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;web_sales.dat&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;web_site.dat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;spark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Import CSV&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getOrCreate&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;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;csv&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;file:///home/iceberg/sample/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.dat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inferSchema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sep&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="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;# drop the last empty column
&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;append&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;insertInto&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;demo.test.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's pretty easy to fill out each table with PySpark's &lt;code&gt;inferSchema&lt;/code&gt; feature. One thing to note is that I intentionally cut off the last column of the csv.&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;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason is that the csv generated by TPC-DS has a separator at the end of each line, which will be wrongly recognized as one more column.&lt;/p&gt;

&lt;p&gt;At this point, we have written all the data into lakehouse, and then we can use various query engines to benchmark the performance of the query predefined by TPC-DS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;TPC-DS is still a standard on lakehouse, but there are fewer resources on how to test it on lakehouse.&lt;/p&gt;

&lt;p&gt;This article provides a quick overview of how to import data on lakehouse, and I believe that following the steps should be feasible.&lt;/p&gt;

&lt;p&gt;The only thing left to do is to actually query on it, but that's a less difficult task, so I won't dive into it.&lt;/p&gt;

</description>
      <category>data</category>
      <category>architecture</category>
      <category>tutorial</category>
      <category>performance</category>
    </item>
    <item>
      <title>Apache Paimon Playground ft. Flink and Trino</title>
      <dc:creator>ChunTing Wu</dc:creator>
      <pubDate>Mon, 25 Nov 2024 01:38:03 +0000</pubDate>
      <link>https://dev.to/lazypro/apache-paimon-playground-ft-flink-and-trino-10fo</link>
      <guid>https://dev.to/lazypro/apache-paimon-playground-ft-flink-and-trino-10fo</guid>
      <description>&lt;p&gt;&lt;a href="https://paimon.apache.org/" rel="noopener noreferrer"&gt;Apache Paimon&lt;/a&gt; is a new data lakehouse format that focuses on solving the challenges of streaming scenarios, but also supports batch processing. Overall, Paimon has the potential to replace the existing Iceberg as the new standard for data lakehousing.&lt;/p&gt;

&lt;p&gt;Why Iceberg and not the other two (Hudi and Delta Lake)? &lt;/p&gt;

&lt;p&gt;Iceberg is the most widely supported by various open-source engines, including pure query engines (e.g., Trino), New SQL databases (e.g., StarRocks, Doris), and streaming frameworks (e.g., Flink, Spark), all of which support Iceberg.&lt;/p&gt;

&lt;p&gt;However, Iceberg faces several problems in streaming scenarios, the most serious one is the fragmentation of small files. Queries in data lakehouses rely heavily on file reads, and if a query has to scan many files at once, it will of course perform poorly. &lt;/p&gt;

&lt;p&gt;To avoid this problem, we need to rely on an external orchestrator to merge files on a regular basis. Paimon is designed with a built-in merge mechanism, and many other optimizations for mass writes, making it more adaptable to streaming scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experiment environment
&lt;/h2&gt;

&lt;p&gt;In order to learn more about Iceberg, I have set up two experimental environments.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wirelessr/flink-iceberg-playground" rel="noopener noreferrer"&gt;Iceberg + Flink&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wirelessr/trino-iceberg-playground" rel="noopener noreferrer"&gt;Iceberg + Trino&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This time I also built a playground for Paimon, which also includes Trino and Flink.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/wirelessr/paimon-trino-flink-playground" rel="noopener noreferrer"&gt;https://github.com/wirelessr/paimon-trino-flink-playground&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, StarRocks was also put in as a representative of New SQL.&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%2Fxgmycbggdruk2nt25o9s.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%2Fxgmycbggdruk2nt25o9s.png" alt="Image description" width="594" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because neither Trino nor StarRocks support streaming writes at this stage, Paimon's writes come from Flink.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: Since some of the links to the official Paimon files are not working, I've put the files into this repo. However, some of the files are huge, so I put them in via LFS, so be sure to install &lt;code&gt;git-lfs&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Trino driver is in &lt;code&gt;paimon-trino-427-0.8-20241112.000605-197-plugin.tar.gz&lt;/code&gt;.&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="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-zxvf&lt;/span&gt; paimon-trino-427-0.8-20241112.000605-197-plugin.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it will run normally with &lt;code&gt;docker compose up -d&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Flink
&lt;/h3&gt;

&lt;p&gt;Let's start by connecting to Flink SQL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;flink-jobmanager ./bin/sql-client.sh
./bin/sql-client.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To write data using Flink we first need to create the correct catalog.&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;CATALOG&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paimon'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'warehouse'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'s3://warehouse/flink'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'s3.endpoint'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://storage:9000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'s3.access-key'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'s3.secret-key'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'s3.path.style.access'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As shown in the above commands, we're using the &lt;code&gt;MinIO&lt;/code&gt; as an &lt;code&gt;S3&lt;/code&gt; to store the Paimon.&lt;/p&gt;

&lt;p&gt;The next step in creating the table and writing the data is quite simple, just run the commands according to the &lt;a href="https://paimon.apache.org/docs/0.9/flink/quick-start/" rel="noopener noreferrer"&gt;official documentat&lt;/a&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="n"&gt;USE&lt;/span&gt; &lt;span class="k"&gt;CATALOG&lt;/span&gt; &lt;span class="n"&gt;my_catalog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- create a word count table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;word_count&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;ENFORCED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cnt&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- create a word data generator table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMPORARY&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;word_table&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="n"&gt;STRING&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'connector'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'datagen'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'fields.word.length'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- paimon requires checkpoint interval in streaming mode&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="s1"&gt;'execution.checkpointing.interval'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'10 s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- write streaming data to dynamic table&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;word_count&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;word_table&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we actually read it and see the result of what we've written.&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;-- use tableau result mode&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="s1"&gt;'sql-client.execution.result-mode'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'tableau'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- switch to batch mode&lt;/span&gt;
&lt;span class="k"&gt;RESET&lt;/span&gt; &lt;span class="s1"&gt;'execution.checkpointing.interval'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="s1"&gt;'execution.runtime-mode'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'batch'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- olap query the table&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;word_count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trino
&lt;/h3&gt;

&lt;p&gt;Let's go to Trino's cli first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;trino trino
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trino's paimon catalog is already set up, but I didn't add a new schema but just used the default one.&lt;/p&gt;

&lt;p&gt;So we can query the Flink write result directly.&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;paimon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;word_count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should see something similar to the Flink query.&lt;/p&gt;

&lt;h3&gt;
  
  
  StarRocks
&lt;/h3&gt;

&lt;p&gt;This is an extra, just to show how much attention Paimon is getting now that many New SQL databases are starting to support it.&lt;/p&gt;

&lt;p&gt;Prepare a &lt;code&gt;mysql&lt;/code&gt; client locally to connect to StarRocks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-P&lt;/span&gt; 9030 &lt;span class="nt"&gt;-h&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;--prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"StarRocks &amp;gt; "&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We still need to create a catalog.&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;EXTERNAL&lt;/span&gt; &lt;span class="k"&gt;CATALOG&lt;/span&gt; &lt;span class="n"&gt;paimon_catalog_flink&lt;/span&gt;
&lt;span class="n"&gt;PROPERTIES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;"type"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"paimon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;"paimon.catalog.type"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"filesystem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;"paimon.catalog.warehouse"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"s3://warehouse/flink"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;"aws.s3.enable_path_style_access"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;"aws.s3.endpoint"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"http://storage:9000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;"aws.s3.access_key"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;"aws.s3.secret_key"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"password"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mysql client should not support Trino's table locator format: &lt;code&gt;&amp;lt;catalog&amp;gt;. &amp;lt;schema&amp;gt;. &amp;lt;table&amp;gt;&lt;/code&gt;, so we have to switch to the db before we can query.&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="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;paimon_catalog_flink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&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;word_count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The results here will be similar to the above.&lt;/p&gt;

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

&lt;p&gt;Although Paimon supports many kinds of metastore as follows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;filesystem&lt;/li&gt;
&lt;li&gt;hive metastore&lt;/li&gt;
&lt;li&gt;jdbc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But for the sake of simplicity, I didn't use extra components, so I only use S3 aka filesystem as metastore. Although the function is fine, according to the &lt;a href="https://paimon.apache.org/docs/0.9/concepts/concurrency-control/" rel="noopener noreferrer"&gt;official document&lt;/a&gt;, using S3 as warehouse needs to be paired with hive metastore or jdbc metastore to ensure consistency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But for object storage such as OSS and S3, their 'RENAME' does not have atomic semantic. We need to configure Hive or jdbc metastore and enable 'lock.enabled' option for the catalog. Otherwise, there may be a chance of losing the snapshot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Understanding what kind of scenarios require this kind of consistency will be the goal of my future experiments.&lt;/p&gt;

</description>
      <category>data</category>
      <category>tutorial</category>
      <category>architecture</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Is there an Alternative to Debezium + Kafka?</title>
      <dc:creator>ChunTing Wu</dc:creator>
      <pubDate>Mon, 04 Nov 2024 01:28:50 +0000</pubDate>
      <link>https://dev.to/lazypro/is-there-an-alternative-to-debezium-kafka-1abk</link>
      <guid>https://dev.to/lazypro/is-there-an-alternative-to-debezium-kafka-1abk</guid>
      <description>&lt;p&gt;I asked this question on &lt;a href="https://www.reddit.com/r/dataengineering/comments/1fv186f/is_there_an_alternative_to_debezium_kafka/" rel="noopener noreferrer"&gt;Reddit&lt;/a&gt; a while back and received lots of valuable answers.&lt;/p&gt;

&lt;p&gt;Therefore, I've looked into each answer and documented the results in this article.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TL;DR&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No, Debezium dominates the market at the moment, despite some drawbacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background Explanation
&lt;/h2&gt;

&lt;p&gt;Why would we want to find an alternative to Debezium? The main reason is we encountered a challenging scenario.&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%2Fwgb0ccm0vey4f1rl7jh2.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%2Fwgb0ccm0vey4f1rl7jh2.png" alt="Image description" width="800" height="123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a typical scenario for Debezium, where any modifications to the data source are captured and fed into Kafka for downstream processing.&lt;/p&gt;

&lt;p&gt;The advantage of this architecture is simple and efficient, ensuring all downstream processes are as real-time as possible.&lt;/p&gt;

&lt;p&gt;If the source has a large number of updates, Debezium can scale horizontally until a large number of updates are concentrated in a single table. This is where Debezium hits its limits.&lt;/p&gt;

&lt;p&gt;Even though Debezium can scale horizontally, it means the updates originally handled by one process can be distributed to multiple processes. If each table already has a dedicated process, horizontal scaling is no longer feasible.&lt;/p&gt;

&lt;p&gt;We are in such a situation, in our environment, even if the machine specification is stretched, the CDC throughput of a single table is capped at 25 MB/s.&lt;/p&gt;

&lt;p&gt;This is certainly not a regular case, after all, 25 MB/s change for a single table is quite significant. However, if we encounter a data source that is doing large-scale data migration, this limit can be easily breached.&lt;/p&gt;

&lt;p&gt;In order to ensure the real-time performance of our data pipeline downstream, we can only ask the upstream to be merciful when encountering this level of data migration, and try to do a good job of rate limiting.&lt;/p&gt;

&lt;p&gt;However, this limitation will greatly reduce the productivity of the upstream developers. On the one hand, they have to add auditing process to their regular maintenance, and on the other hand, they need to develop an additional rate limit for each maintenance.&lt;/p&gt;

&lt;p&gt;So let's find a solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution Overview
&lt;/h2&gt;

&lt;p&gt;The following solutions were gathered from that Reddit article.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://estuary.dev/vs-debezium/" rel="noopener noreferrer"&gt;Estuary Flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.striim.com/" rel="noopener noreferrer"&gt;Striim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.fivetran.com/data-movement" rel="noopener noreferrer"&gt;Fivetran HVR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Proprietary CDC&lt;/li&gt;
&lt;li&gt;&lt;a href="https://conduit.io/" rel="noopener noreferrer"&gt;Conduit&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first three solutions are enterprise services without open source, so they're not going to work for us. After all, we're trying to solve a partial use case, not a complete do-over.&lt;/p&gt;

&lt;p&gt;Although Estuary Flow says they have a local deployment approach, I couldn't find any information about it.&lt;/p&gt;

&lt;p&gt;The fourth solution was to develop a new tool ourselves, which I believe would be a fundamental solution to the problem. After all, Debezium is developed in JAVA, and we should be able to achieve better performance with Golang, Rust, or even C/C++. However, the development cost was too high for us, and it was difficult to start from scratch.&lt;/p&gt;

&lt;p&gt;The first four options didn’t meet our needs, but the fifth option caught my attention as a promising solution.&lt;/p&gt;

&lt;p&gt;Conduit is an open source data migration platform developed in Golang, and provides a variety of connectors to integrate many data stores. In addition, we can also develop our own converter to do data format preprocessing.&lt;/p&gt;

&lt;p&gt;Therefore, I started to test the performance of Conduit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expirement Environment
&lt;/h2&gt;

&lt;p&gt;To keep things simple, I used Kafka Connect in place of Debezium. The two are essentially the same but with different dispatchers, and behind the scenes they all use the same &lt;a href="https://github.com/mongodb/mongo-kafka" rel="noopener noreferrer"&gt;library&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%2Fqna2xi1sj6chpup8qmjq.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%2Fqna2xi1sj6chpup8qmjq.png" alt="Image description" width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Locust is responsible for generating MongoDB changes, then Conduit and Kafka Connect will write to different Kafka topics.&lt;/p&gt;

&lt;p&gt;We can observe the writing speed of Kafka topics to determine who has better performance.&lt;/p&gt;

&lt;p&gt;The whole experiment environment is as follows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/wirelessr/82a642685d40d78a49a4cdb1ff1cfa9f" rel="noopener noreferrer"&gt;https://gist.github.com/wirelessr/82a642685d40d78a49a4cdb1ff1cfa9f&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I use two of my own packaged images, Conduit and Kafka Connect, which have MongoDB connectors.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wirelessr/conduit-mongo-connect" rel="noopener noreferrer"&gt;Conduit + Mongo connector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wirelessr/mongodb-kafka-connect" rel="noopener noreferrer"&gt;Kafka Connect + Mongo connector&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's easy to generate a large amount of change by stuffing MongoDB with a bunch of fat documents and then just changing the value of a field in all the documents.&lt;/p&gt;

&lt;p&gt;The locust script used is as follows.&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;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;locust&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;between&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;faker&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Faker&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pymongo&lt;/span&gt;

&lt;span class="n"&gt;fake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Faker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MongoDBUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;between&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="mi"&gt;5&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;__init__&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;environment&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environment&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;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_start&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pymongo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;MongoClient&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;host&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;db&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;client&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test&lt;/span&gt;&lt;span class="sh"&gt;"&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;collection&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;db&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test_new&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nd"&gt;@task&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;incr_seq&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;start_perf_counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;response_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;response&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;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_many&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$inc&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seq&lt;/span&gt;&lt;span class="sh"&gt;"&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="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response_length&lt;/span&gt; &lt;span class="o"&gt;=&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;matched_count&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="n"&gt;exception&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;request_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mongo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;incr seq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;response_time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_perf_counter&lt;/span&gt;&lt;span class="p"&gt;)&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;response_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response_length&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;response&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="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;exception&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;h2&gt;
  
  
  Load Test Result
&lt;/h2&gt;

&lt;p&gt;For this test, I used a local machine without fully stressing its CPU or memory, leaving some resources available to avoid errors from performance bottlenecks.&lt;/p&gt;

&lt;p&gt;In other words, this test shows the regular ability of a single process to handle a single table load.&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%2Fqunqoq09bj4ir6msdp4l.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%2Fqunqoq09bj4ir6msdp4l.png" alt="image" width="528" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Conduit&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%2Fdiaht1u2xwovksj95b70.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%2Fdiaht1u2xwovksj95b70.png" alt="image" width="528" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kafka Connect&lt;/p&gt;

&lt;p&gt;As the results show, Kafka Connect's throughput significantly outperforms Conduit’s when system resources are sufficient.&lt;/p&gt;

&lt;p&gt;I was a bit confused about this result, so I repeated the test a few times, but I got similar numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;Back to the question in the title.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is there an alternative to Debezium + Kafka?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not at the moment—at least, not among open-source tools.&lt;/p&gt;

&lt;p&gt;I've asked on Reddit, but maybe Dev.to will have a different answer, so feel free to offer your solutions.&lt;/p&gt;

</description>
      <category>data</category>
      <category>architecture</category>
      <category>eventdriven</category>
      <category>kafka</category>
    </item>
    <item>
      <title>Dockerize Local RAG with Models</title>
      <dc:creator>ChunTing Wu</dc:creator>
      <pubDate>Wed, 18 Sep 2024 03:35:29 +0000</pubDate>
      <link>https://dev.to/lazypro/dockerize-local-rag-with-models-41al</link>
      <guid>https://dev.to/lazypro/dockerize-local-rag-with-models-41al</guid>
      <description>&lt;p&gt;Previously, I introduced &lt;a href="https://medium.com/@lazypro/%E5%81%9A%E4%B8%80%E5%80%8B%E5%8B%99%E5%AF%A6%E7%9A%84rag-pragmatic-rag-65fc63647c51" rel="noopener noreferrer"&gt;a generic RAG tamplate&lt;/a&gt;, in which I mentioned that there are three cores needed to make a high-quality RAG.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;embedding with semantic understanding&lt;/li&gt;
&lt;li&gt;LLM with contextualized knowledge.&lt;/li&gt;
&lt;li&gt;compression result by rerank.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When all of these are in place, a high quality RAG will be created, regardless of whether there is fine-tuning or not.&lt;/p&gt;

&lt;p&gt;Add high quality sources and accurate prompts, and you've got a complete RAG.&lt;/p&gt;

&lt;p&gt;Simple, right?&lt;/p&gt;

&lt;p&gt;Is it possible to containerize such a simple yet useful implementation and run it completely locally? Yes, of course.&lt;/p&gt;

&lt;p&gt;Let's take the three models mentioned in the previous template as an example.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Ollama&lt;/code&gt; plus &lt;code&gt;TAIDE&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BGE-M3&lt;/code&gt; for embedding.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ms-marco-MultiBERT-L-12&lt;/code&gt; as reranker&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Ollama with Models
&lt;/h2&gt;

&lt;p&gt;Ollama is a completely local LLM framework, you can pull down the LLM model you want to use by &lt;code&gt;ollama pull&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Ollama itself provides a basic container.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker pull ollama/ollama&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nevertheless, there is no simple way to get this container to mount the model. So here's a little bit hack, let me demonstrate with a &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ollama/ollama&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;taide_base&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;nohup &lt;/span&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"ollama serve &amp;amp;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;5 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ollama pull cwchang/llama3-taide-lx-8b-chat-alpha1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use Ollama's containers directly and wake up the ollama service during the &lt;code&gt;docker build&lt;/code&gt; process and download the model directly.&lt;/p&gt;

&lt;p&gt;This way we have an LLM framework with models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packaging &lt;code&gt;BGE-M3&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;BGE-M3&lt;/code&gt; here is a HuggingFace supplied model, so all we need to do is find the HuggingFace model catalog and copy it into the container.&lt;/p&gt;

&lt;p&gt;In my environment (without modifying any settings), the model directory is at&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;~/.cache/huggingface/hub/models--BAAI-bge-m3&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Therefore, we only need to &lt;code&gt;COPY&lt;/code&gt; the contents of this directory into the container.&lt;/p&gt;

&lt;p&gt;However, it is important to note that HuggingFace requires &lt;code&gt;config.json&lt;/code&gt; when loading models, and this file is very deep.&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;init_embeddings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain_huggingface&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HuggingFaceEmbeddings&lt;/span&gt;
    &lt;span class="n"&gt;HF_EMBEDDING_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;./models--BAAI--bge-m3/snapshots/5617a9f61b028005a4858fdac845db406aefb181&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HuggingFaceEmbeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HF_EMBEDDING_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;model_kwargs&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;device&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;cpu&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;encode_kwargs&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;normalize_embeddings&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&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;As we can see from this code, we actually need to specify the snapshot that is used at the moment when using the model.&lt;/p&gt;

&lt;p&gt;Well, we are left with the last one, reranker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packaging &lt;code&gt;ms-marco-MultiBERT-L-12&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ms-marco-MultiBERT-L-12&lt;/code&gt; used here is integrated by langchain. With the default behavior, langchain's &lt;code&gt;document_compressors&lt;/code&gt; will place the model in &lt;code&gt;/tmp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In other words, when we run the following code, it downloads the model into &lt;code&gt;/tmp&lt;/code&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.retrievers.document_compressors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FlashrankRerank&lt;/span&gt;
&lt;span class="n"&gt;compressor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FlashrankRerank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ms-marco-MultiBERT-L-12&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what we need to do is copy &lt;code&gt;/tmp/ms-marco-MultiBERT-L-12&lt;/code&gt; into the container.&lt;/p&gt;

&lt;p&gt;But that's not enough, we need to explicitly specify on the client side that the model's directory has been changed to the container's current directory. This is a bit complicated to explain, so let's just look at an example.&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;flashrank&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Ranker&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;langchain.retrievers.document_compressors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FlashrankRerank&lt;/span&gt;

&lt;span class="n"&gt;ranker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Ranker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ms-marco-MultiBERT-L-12&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cache_dir&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="n"&gt;compressor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FlashrankRerank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ranker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All right, we've got the three models we need in the container.&lt;/p&gt;

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

&lt;p&gt;Although this article provides a containerized RAG solution, I have to say that the container image is 18 GB.&lt;/p&gt;

&lt;p&gt;If we were to package it with the embedded vectors from the source, it would easily exceed 20 GB.&lt;/p&gt;

&lt;p&gt;Therefore, this container can only be used for simple testing, and is not really capable of scaling, so you need to be more careful when using it.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>docker</category>
      <category>rag</category>
      <category>ai</category>
    </item>
    <item>
      <title>Turn Based Multiplayer Beer Game</title>
      <dc:creator>ChunTing Wu</dc:creator>
      <pubDate>Wed, 11 Sep 2024 06:00:20 +0000</pubDate>
      <link>https://dev.to/lazypro/turn-based-multiplayer-beer-game-3n5k</link>
      <guid>https://dev.to/lazypro/turn-based-multiplayer-beer-game-3n5k</guid>
      <description>&lt;p&gt;Since I need to organize a systems thinking workshop in the near future, I need a beer game to start it off.&lt;/p&gt;

&lt;p&gt;The beer game itself consists of four characters: Retailer, Wholesaler, Distributor and Factory. Through the time-delay nature of the logistics to understand the system perspective, and can have a better understanding of the system boundaries.&lt;/p&gt;

&lt;p&gt;As this is a few hours workshop, I want this beer game to fulfill the following features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's a multiplayer game&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The beer game itself will have many participants playing various roles in the supply chain, but I'd like to be able to have multiple supply chains competing at the same time to see who scores higher. Thus, we can learn about their system strategies at the same time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The game host should be able to see everyone's status&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Since there are multiple teams competing at the same time, as a host I need to be able to see how each team is progressing and scoring at the moment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The game flow has to be simple and easy to control the pace&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As I said at the beginning, this is a short workshop, so I need to get everyone up to speed quickly and I need to be able to control the details of each round.&lt;/p&gt;

&lt;p&gt;Moreover, a timer appears in the player's UI at the beginning of each round, advancing the game pace by counting down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be able to customize the characters&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A classic beer game consists of four characters, but the more characters there are, the longer the game will be. So I'd like to adjust the game pace so that it's better to have three characters.&lt;/p&gt;

&lt;p&gt;After searching around, I found that neither open source projects nor projects that are already online can satisfy these requirement perfectly. So, I'd better make one myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beer Game Project
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/wirelessr/beer_game" rel="noopener noreferrer"&gt;https://github.com/wirelessr/beer_game&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02szseho8xv998svyvtp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02szseho8xv998svyvtp.png" alt="Host UI" width="800" height="369"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Host UI&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahjbvnrw2sygb7uhbck7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fahjbvnrw2sygb7uhbck7.png" alt="Player UI" width="800" height="477"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Player UI&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The entire project is business driven developed and tested with over 90% coverage, so please feel free to use it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Preparedness
&lt;/h2&gt;

&lt;p&gt;Create a file for secrets in the project folder. You should see me copy it in the &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.streamlit/secrets.toml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mongo]&lt;/span&gt;
&lt;span class="py"&gt;uri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;your mongo connection&amp;gt;"&lt;/span&gt;

&lt;span class="nn"&gt;[admin]&lt;/span&gt;
&lt;span class="py"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;your admin key&amp;gt;"&lt;/span&gt;

&lt;span class="nn"&gt;[player]&lt;/span&gt;
&lt;span class="py"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;your player key&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since this project is using MongoDB, you have to fill in the link with your account password. In addition, &lt;code&gt;admin.key&lt;/code&gt; and &lt;code&gt;player.key&lt;/code&gt; correspond to the key fields on the UI.&lt;/p&gt;

&lt;p&gt;After all, I'm uploading the app to the public cloud, so I still need a basic authentication mechanism. If you're running locally only and find authentication troublesome, you can remove the corresponding source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation and Use
&lt;/h2&gt;

&lt;p&gt;This project has a &lt;code&gt;Dockerfile&lt;/code&gt; attached, so it can be run directly with &lt;code&gt;docker&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; beer_game &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; beer &lt;span class="nt"&gt;-p&lt;/span&gt; 8501:8501 beer_game
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For development, in addition to &lt;code&gt;requiremnts.txt&lt;/code&gt;, &lt;code&gt;requirements-test.txt&lt;/code&gt;, which runs the unit tests, should also be installed. Then you can run all the unit tests through the &lt;code&gt;Makefile&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requiremnts.txt
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements-test.txt
make &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Game Flow
&lt;/h2&gt;

&lt;p&gt;The whole game is divided into a host mode and a participant mode, which correspond to the options in the top corner of the UI.&lt;/p&gt;

&lt;p&gt;The host first assigns a &lt;code&gt;game_id&lt;/code&gt; to create the game, and all participants have to fill in the &lt;code&gt;player_game&lt;/code&gt; with this id.&lt;/p&gt;

&lt;p&gt;All players on the same supply chain need to use the same &lt;code&gt;player_id&lt;/code&gt;, so this id is also known as the supply chain ID, and participants with the same &lt;code&gt;player_id&lt;/code&gt; are separated into roles by &lt;code&gt;player_role&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can see the status on the host's screen when a participant joins.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vhn2c873oeokcd2l317.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vhn2c873oeokcd2l317.png" alt="image" width="308" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's look at what a full iteration would look like from the host's point of view.&lt;/p&gt;

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

&lt;p&gt;All the components that need to be manipulated are in this picture, and each turn starts by pressing the &lt;code&gt;Refresh&lt;/code&gt; button and ends by pressing &lt;code&gt;Next Week&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As for how many orders to send to all the supply chains in this round, they will be triggered by &lt;code&gt;Place Order&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's worth mentioning that the &lt;code&gt;Place Order&lt;/code&gt; itself is idempotent, so it's fine to change the number and press it again, the last number will be used. The &lt;code&gt;Place Order&lt;/code&gt; of each participant's interface will be idempotent as well.&lt;/p&gt;

&lt;p&gt;Once the host has placed the order, the shop player can take the order.&lt;/p&gt;

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

&lt;p&gt;Similarly, each role in the supply chain starts with &lt;code&gt;Refresh&lt;/code&gt; and ends with &lt;code&gt;Place Order&lt;/code&gt;, with the shop player taking the action followed by the retailer player, and so on.&lt;/p&gt;

&lt;p&gt;Finally, back to the host, who can press &lt;code&gt;Refresh&lt;/code&gt; again to see all the statuses for the round, and &lt;code&gt;Next Week&lt;/code&gt; to end the round.&lt;/p&gt;

&lt;h2&gt;
  
  
  Game Detail
&lt;/h2&gt;

&lt;p&gt;There are a couple of things actually done during &lt;code&gt;Refresh&lt;/code&gt;. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;it refills inventory from downstream based on orders placed four weeks ago. &lt;/li&gt;
&lt;li&gt;it receives orders from upstream.&lt;/li&gt;
&lt;li&gt;decides how much to sell based on what inventory it can sell.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since &lt;code&gt;Place Order&lt;/code&gt; is idempotent, &lt;code&gt;Refresh&lt;/code&gt; itself is idempotent too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future work
&lt;/h2&gt;

&lt;p&gt;It basically meets all of my needs now, but there are some enhancements that could be made.&lt;/p&gt;

&lt;p&gt;For example, although the host can see the status of all the participants, it would be helpful to have a graph to show the change of inventory and cost information over time, which would be useful for reviewing the game after it is over.&lt;/p&gt;

&lt;p&gt;There's also a more basic problem: the current UI has no test coverage at all, mainly because the current game flow is quite simple. Just a few clicks on the UI will cover all the UI flow, so I don't rely so much on auto-testing. However, if there is a UI modification, it will still be a bit tedious, so it would be better to have a UI unit test.&lt;/p&gt;

&lt;p&gt;Overall, these requirements are optimizations, but their lack does not affect the functionality.&lt;/p&gt;

&lt;p&gt;If you have additional ideas, you can also just submit a pull request, contributions are welcome.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>When to Consider Design Patterns</title>
      <dc:creator>ChunTing Wu</dc:creator>
      <pubDate>Wed, 07 Aug 2024 01:59:37 +0000</pubDate>
      <link>https://dev.to/lazypro/when-to-consider-design-patterns-45np</link>
      <guid>https://dev.to/lazypro/when-to-consider-design-patterns-45np</guid>
      <description>&lt;p&gt;This week, at the study group, our engineer asked a question: Should we follow the design pattern to design and implement the project from the beginning?&lt;/p&gt;

&lt;p&gt;The design pattern mentioned here refers to GoF's &lt;code&gt;Design Patterns&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87zybek8ap6oahixjrr8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F87zybek8ap6oahixjrr8.png" alt="Image description" width="502" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Meanwhile, there is a related context which suggests that our coding should follow &lt;code&gt;SOLID principle&lt;/code&gt; as much as possible, especially the first S and the second O, anyway.&lt;/p&gt;

&lt;p&gt;Since SOLID principle should be followed as much as possible, then the design pattern should also be taken into consideration from the very beginning, right?&lt;/p&gt;

&lt;p&gt;No, not really.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are design patterns?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The design patterns we often talk about actually refer to those solutions defined by GoF, which are designed to solve the problems often faced in object-oriented programming.&lt;/p&gt;

&lt;p&gt;When we want the code to be easy to maintain and easy to modify, i.e., the open and closed principle, we abstract the problems through various encapsulation techniques, and eventually become those interesting design patterns.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Did you notice two key points in this paragraph?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OOP-specific problems&lt;/li&gt;
&lt;li&gt;Ease of maintenance and modification&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your project has just started and there is still a question mark over whether it will be successful or not, why do you need to think about the flexibility in the long term?&lt;/p&gt;

&lt;p&gt;If your project requirements are fixed, why do you need to worry about maintenance and modification?&lt;/p&gt;

&lt;p&gt;If you're one of my regular readers, you'll remember we've talked about a similar question before.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://lazypro.medium.com/when-to-consider-clean-architecture-a-practical-guide-6a21fccb5e6b" rel="noopener noreferrer"&gt;When to consider clean architecture?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even earlier on, we have talked about another similar issue.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://interviewnoodle.com/do-you-really-need-a-microservice-91a48cbea8c1" rel="noopener noreferrer"&gt;Do you really need a microservice?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most of time, I'm on the side of "don't over-engineering".&lt;/p&gt;

&lt;p&gt;Whether it's design patterns, clean architecture, or even microservice architecture, they are all means to solve problems, but first, you have to encounter the problem. If you have a clear problem, find the right solution, and don't do it just for the sake of doing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What exactly is a Pattern?
&lt;/h2&gt;

&lt;p&gt;By the way, what is pattern?&lt;/p&gt;

&lt;p&gt;As I mentioned at the beginning, the design patterns we often talk about actually refer to the solutions listed in the GoF book.&lt;/p&gt;

&lt;p&gt;Pattern is like a "symptom", when you have a cold over and over again, you will know that you need to drink more water and take more rest, the same is true when we program. When we see a certain problem over and over again, we will naturally have a corresponding solution, but will this solution be the most effective one?&lt;/p&gt;

&lt;p&gt;The design pattern is the medicine for that symptom. When we come across a symptom, we will find out the corresponding medicine to solve the problem, and therefore, you should not usually take medicine without thinking about it, right? It's unhealthy to take medicine when you don't have symptoms.&lt;/p&gt;

&lt;p&gt;In fact, patterns are everywhere.&lt;/p&gt;

&lt;p&gt;Object-oriented programming has its patterns, software architecture has its patterns, and even system architecture has its patterns.&lt;/p&gt;

&lt;p&gt;For example, the following book should not be unfamiliar to you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foam6sbspndaxh7qmjqpp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foam6sbspndaxh7qmjqpp.png" alt="Image description" width="500" height="627"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This book may feel a little unfamiliar.&lt;/p&gt;

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

&lt;p&gt;Here is a &lt;a href="https://en.wikipedia.org/wiki/Pattern-Oriented_Software_Architecture" rel="noopener noreferrer"&gt;series of books&lt;/a&gt; from Volume 1 to Volume 5.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ny3oxg7empkbvws1ac5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ny3oxg7empkbvws1ac5.png" alt="Image description" width="770" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These books are all about the patterns encountered in various software development contexts, in short, a list of medicines. &lt;/p&gt;

&lt;p&gt;If we're sick, we can get something out of it, but if we've never been sick, we don't even know what they're talking about, not to mention when to consider the patterns.&lt;/p&gt;

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

&lt;p&gt;Software development is a pragmatic process.&lt;/p&gt;

&lt;p&gt;What we are doing is always encountering problems, thinking about them, and finally solving them.&lt;/p&gt;

&lt;p&gt;"This may be needed in the future, so I'm ready for it." I've heard this phrase a lot, but in reality, most of these pre-prepared things don't work. Because requirements are always changing.&lt;/p&gt;

&lt;p&gt;But if we find requirements have changed and it's hard for us to implement them, then it's not too late to consider what medicine we need to take. This is refactoring.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>tutorial</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Paradigm Shift in Software Development, Part 2</title>
      <dc:creator>ChunTing Wu</dc:creator>
      <pubDate>Wed, 31 Jul 2024 07:08:33 +0000</pubDate>
      <link>https://dev.to/lazypro/paradigm-shift-in-software-development-part-2-1594</link>
      <guid>https://dev.to/lazypro/paradigm-shift-in-software-development-part-2-1594</guid>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@lazypro/a-paradigm-shift-in-software-development-leveraging-genai-for-direct-business-logic-processing-9929a724cc1c" rel="noopener noreferrer"&gt;Paradigm Shift in Software Development, Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Paradigm Shift in Software Development, Part 2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last time, we mentioned that GenAI can be used to implement business logic and dramatically increase development productivity and reduce the effort of debugging.&lt;/p&gt;

&lt;p&gt;However, I have to say that GenAI is not suitable for applications that require accurate computation. In other words, the demonstration in the previous article was just a demo and not a recommendation to use GenAI for calculating promotions.&lt;/p&gt;

&lt;p&gt;But that doesn't mean using GenAI as a business logic is a bad idea. On the contrary, GenAI is really suitable for replacing "some" business logic.&lt;/p&gt;

&lt;p&gt;Maybe you will ask, most of the business logic needs to be calculated and requires accuracy, so what exactly can GenAI help? Well, I'll tell you, it helps call the APIs that have already been coded.&lt;/p&gt;

&lt;p&gt;The correct way to use GenAI to handle business logic is to encapsulate that business logic directly into a Remote Procedure Call (RPC) and let GenAI prepare the parameters needed for the RPC and call the corresponding RPC correctly.&lt;/p&gt;

&lt;p&gt;Currently, the RPC that is easiest for GenAI to handle is the REST API for the following reasons. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Plain text style, no matter the URI, HTTP header, HTTP method, query parameter and request payload are all plain text.&lt;/li&gt;
&lt;li&gt;Full specification support. Nowadays, the most straightforward way to describe a REST API is to use a swagger, and the description file of the swagger is also plain text.&lt;/li&gt;
&lt;li&gt;REST is a mature and relatively less dependency choice, on the contrary, such as gRPC and other protocols need to have to install additional drivers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Therefore it's pretty clear what we're going to do, so let's follow the steps and explain them. We will still use Gemini as a demonstration as in the previous article, but once again, you can use what you are familiar with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experimental environment introduction
&lt;/h2&gt;

&lt;p&gt;I have prepared a web service with basic CRUD.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/wirelessr/genai_api_calling" rel="noopener noreferrer"&gt;https://github.com/wirelessr/genai_api_calling&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just &lt;code&gt;docker compose up&lt;/code&gt; to get the service up.&lt;/p&gt;

&lt;p&gt;This service is a product microservice that handles CRUD for a single product and saves the results in a database. For ease of use, I'm using Redis as the database for this example.&lt;/p&gt;

&lt;p&gt;This service has two entry points.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="http://localhost:50000" rel="noopener noreferrer"&gt;http://localhost:50000&lt;/a&gt; is the home page of the microservice, with a simple list page and blocks for adding and modifying products.&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://localhost:50000/apidocs" rel="noopener noreferrer"&gt;http://localhost:50000/apidocs&lt;/a&gt; is the swagger home page, which lists all the API descriptions and specifications.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then prepare some basic test data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"http://localhost:50000/api/product"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"accept: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/x-www-form-urlencoded"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"id=123&amp;amp;name=Apple&amp;amp;description=Fruit"&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"http://localhost:50000/api/product"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"accept: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/x-www-form-urlencoded"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"id=234&amp;amp;name=Bird&amp;amp;description=Animal"&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"http://localhost:50000/api/product"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"accept: application/json"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/x-www-form-urlencoded"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"id=345&amp;amp;name=Cat&amp;amp;description=Animal"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is worth noting that creating a new product and modifying a product are actually the same API: &lt;code&gt;POST /api/product&lt;/code&gt;. When the product ID doesn't exist, the API will create a new product; on the other hand, if the product ID exists, then it will modify the product.&lt;/p&gt;

&lt;p&gt;Remember this, because it's relevant to the business logic we're trying to implement.&lt;/p&gt;

&lt;p&gt;The following examples are actually listed in &lt;a href="https://github.com/wirelessr/genai_api_calling/blob/main/rest_api_calling.ipynb" rel="noopener noreferrer"&gt;rest_api_calling.ipynb&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  GenAI calls RPC
&lt;/h2&gt;

&lt;p&gt;First, we need to enable GenAI to call RPCs, which is called function calling, and both OpenAI's ChatGPT and Google's Gemini have similar capabilities.&lt;/p&gt;

&lt;p&gt;Reference links are listed here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://platform.openai.com/docs/guides/function-calling" rel="noopener noreferrer"&gt;ChatGPT function calling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/function-calling" rel="noopener noreferrer"&gt;Gemini function calling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following is an example.&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_website_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&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="nb"&gt;str&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 the content from a specific URL similar to curl -X GET.

  Args.
      url (str, required): The target URL is either remote or local.

  Returns.
      str: The raw content from a specific URL.
  &lt;/span&gt;&lt;span class="sh"&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;requests&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;url&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;text&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&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="n"&gt;data&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="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
  Send a request similar to curl -X POST.

  Args.
      url (str, required): Target URL.
      data (str, required): The data of the form in &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id=value1&amp;amp;name=value2&amp;amp;description=value3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; format.

  Returns.
      dict: Dictionary containing the status code and response content.
  &lt;/span&gt;&lt;span class="sh"&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;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&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;Content-Type&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;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="sh"&gt;"&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;data&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;response_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;response_content&lt;/span&gt; &lt;span class="o"&gt;=&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;text&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status_code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&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;status_code&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;response_content&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_instruction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;You are a professional web crawler and familiar with swagger usage.
You can get the content you want by yourself through web api, 
and when you utilize web api, you will actively list which api is used.
If you don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t know the answer to a question, just answer no, don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t make up an answer.
&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gemini-1.5-pro&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;get_website_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post_request&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                              &lt;span class="n"&gt;system_instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;system_instruction&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have designed two tools for Gemini, &lt;code&gt;get_website_content&lt;/code&gt; and &lt;code&gt;post_request&lt;/code&gt;. The code comments and annotations must be written in detail so that GenAI can realize the use of these tools.&lt;/p&gt;

&lt;p&gt;Now GenAI can use &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  GenAI understands RPC specifications
&lt;/h2&gt;

&lt;p&gt;Once GenAI is able to invoke RPC, we then need to enable GenAI to learn "all" RPC specifications.&lt;/p&gt;

&lt;p&gt;In the case of REST APIs, the simplest way is to use a swagger to describe all the APIs, including their inputs and outputs, as well as their functionality. The more detailed the information, the more GenAI can operate correctly without having to spend a lot of effort tuning prompts.&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;chat_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;enable_automatic_function_calling&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;history&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;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;parts&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I have a website and this website provides full swagger: http://localhost:50000/apispec_1.json Please tell me the features of this website.&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="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;model&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;parts&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;This website provides four API endpoints:

- Create or update a product: You can send a POST request to `/api/product` with product ID, name, and description to create or update a product.
- Delete a product: You can send a POST request to `/api/product/delete/{product_id}` to delete a product by its ID.
- Get a product: You can send a GET request to `/api/product/{product_id}` to retrieve the details of a product by its ID.
- Get all products: You can send a GET request to `/api/products` to get a list of all products. &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="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;This is the record I left after talking to Gemini beforehand, it can be used as the history of the prompt.&lt;/p&gt;

&lt;p&gt;You can also do some additional conversations to verify that Gemini has really learned it.&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chat_session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_message&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 a new product by your own thought&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;response&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical Business Logic
&lt;/h2&gt;

&lt;p&gt;Now we've made sure that GenAI knows the basics, we can make GenAI implement business logic based on those basics.&lt;/p&gt;

&lt;p&gt;For example, the current &lt;code&gt;POST /api/product&lt;/code&gt; is a combination of creation and modification, with the &lt;code&gt;id&lt;/code&gt; determining whether to create or modify. The &lt;code&gt;id&lt;/code&gt; is filled in by the client itself, so it is very likely to be wrong.&lt;/p&gt;

&lt;p&gt;Then we can ask GenAI to find out the unused &lt;code&gt;id&lt;/code&gt; to add based on the result of &lt;code&gt;list&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Avoid the existing ids and create a new product.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this way, the client does not need to fill in the &lt;code&gt;id&lt;/code&gt; itself but GenAI is responsible for generating the &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Or another use case, right now the &lt;code&gt;description&lt;/code&gt; is free format, so it can be written any way we want. But we can use GenAI to provide a template so that all creations and modifications are in a fixed format, such as the following template.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When creating and modifying products, the description must conform to the following format.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Category: str, the category of the product.&lt;/li&gt;
&lt;li&gt;Price: int, the price of the product.&lt;/li&gt;
&lt;li&gt;Notes: str, additional information.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The implementation of this business logic doesn't involve computation, it's just a matter of letting GenAI call on a known API to accomplish a specific goal.&lt;/p&gt;

&lt;p&gt;You can use your imagination to make more variations.&lt;/p&gt;

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

&lt;p&gt;In the previous article we mentioned that GenAI can be used to implement business logic, and this is true. But in practice, we still need to do some development work to make GenAI able to implement business logic accurately.&lt;/p&gt;

&lt;p&gt;Because GenAI may be good at business logic, but it's not accurate, so in order to maximize GenAI's strengths, we need to make GenAI do as little computation as possible. By encapsulating the business logic that needs to be computed and enabling GenAI to execute it exactly according to the instructions we provide, we can maximize productivity.&lt;/p&gt;

&lt;p&gt;Why do we use GenAI to implement business logic?&lt;/p&gt;

&lt;p&gt;Let's go back and answer this fundamental question. Because GenAI has the ability to understand natural language and execute our predefined scripts or steps, implementing business logic with GenAI becomes software development in natural language.&lt;/p&gt;

&lt;p&gt;Of course, GenAI is not perfect, there are a few core elements in the development process.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Integration must be done properly. Although the examples I have provided are all working scripts, in fact, to make GenAI actually put into production environment requires a lot of infrastructure. For example, LLM caching, vector databases, and model repository. Each of these components is a new tech stack for organizations that have never introduced GenAI before.&lt;/li&gt;
&lt;li&gt;Prompts must be good. Although we are developing in natural language, GenAI can easily "learn the wrong way" if we are not precise enough. Moreover, GenAI may perform normally but crash when it encounters a specific pattern, which will be very difficult to debug.&lt;/li&gt;
&lt;li&gt;Testing must be done right. We have already encapsulated business logic into RPC for GenAI, but we still need to make sure that GenAI works properly in all kinds of scenarios, so we must have a high coverage of test cases.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As we can see, although software development with GenAI may seem attractive, there are many challenges that must be overcome. If we want to become a master of prompting, we still have to be a developer first.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>programming</category>
      <category>ai</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>A Paradigm Shift in Software Development: Leveraging GenAI for Direct Business Logic Processing</title>
      <dc:creator>ChunTing Wu</dc:creator>
      <pubDate>Wed, 17 Jul 2024 01:49:15 +0000</pubDate>
      <link>https://dev.to/lazypro/a-paradigm-shift-in-software-development-leveraging-genai-for-direct-business-logic-processing-16h0</link>
      <guid>https://dev.to/lazypro/a-paradigm-shift-in-software-development-leveraging-genai-for-direct-business-logic-processing-16h0</guid>
      <description>&lt;h1&gt;
  
  
  A Paradigm Shift in Software Development: Leveraging GenAI for Direct Business Logic Processing
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Generative AI transforms the way we handle business logic&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you read the title and thought I am going to introduce Copilot, you are wrong.&lt;/p&gt;

&lt;p&gt;Before we start the topic, let's start with a case study of an e-commerce platform.&lt;/p&gt;

&lt;p&gt;Suppose the shopping cart looks like the following.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&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;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;456&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;29.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&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;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;789&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"category_id"&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="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;p&gt;Each field should be simple enough to contain the item purchased, the quantity purchased, the price of the single item, and the category it belongs to.&lt;/p&gt;

&lt;p&gt;I have 3 promotions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;$5 off a $20 purchase, which continues to accrue after qualifying.&lt;/li&gt;
&lt;li&gt;buy 2 get 1 free on category_id 1 items.&lt;/li&gt;
&lt;li&gt;30% off the total price of category_id 3 items.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;What is the total price after calculation?&lt;/li&gt;
&lt;li&gt;How much of the discount is allocated to each item?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To implement such promotions, please answer the following questions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How long would it take you to write this logic?&lt;/li&gt;
&lt;li&gt;Can you make the logic better than O(n^2)?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first question is easy to understand, but what does the second question mean?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Flte76lwkkou95fgoe80x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Flte76lwkkou95fgoe80x.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have three promotions, and to be able to determine the impact of every promotion we need to scan the entire shopping cart for every item. So, in the example above, that's &lt;code&gt;3 * 3 = 9&lt;/code&gt;, i.e. O(n^2).&lt;/p&gt;

&lt;h3&gt;
  
  
  Then
&lt;/h3&gt;

&lt;p&gt;What if I told you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I could do it in just a few minutes.&lt;/li&gt;
&lt;li&gt;And it's O(1).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would you believe me?&lt;/p&gt;

&lt;p&gt;Guess how I did it.&lt;/p&gt;

&lt;h3&gt;
  
  
  GenAI can help
&lt;/h3&gt;

&lt;p&gt;Although GenAI was mentioned, if you thought I am going to introduce Copilot or similar tools you are very wrong.&lt;/p&gt;

&lt;p&gt;It's true that those code generation tools can produce business logic in a matter of minutes, but the business logic they produce will still work the way we think, which means it will still be O(n^2).&lt;/p&gt;

&lt;p&gt;So what do we do with GenAI? The answer is simple: let GenAI learn business logic and then answer the results directly.&lt;/p&gt;

&lt;p&gt;Sounds unbelievable, right? Let's see how I did it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aistudio.google.com/app/prompts?state=%7B%22ids%22:%5B%221l57m9Y-lzOoiVLVSej8RSPlquod_QnFl%22%5D,%22action%22:%22open%22,%22userId%22:%22112447852159053274838%22,%22resourceKeys%22:%7B%7D%7D&amp;amp;usp=sharing" rel="noopener noreferrer"&gt;GEMINI DEMO LINK&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even though I'm using Gemini as an example, actually, you can use any model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1
&lt;/h3&gt;

&lt;p&gt;First, I'll tell Gemini his role using &lt;code&gt;System Instructions&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are an e-commerce expert who is well versed in all kinds of promotions and understands how shopping cart profits are calculated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 2
&lt;/h3&gt;

&lt;p&gt;Next, ask Gemini to explain the structure of a shopping cart that I dropped in. It's important to ask him to explain this. Instead of telling him what it is, it's better to let him understand it for himself so that he can get a more accurate mental model.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Let's describe a shopping cart in JSON, here's an example.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 3
&lt;/h3&gt;

&lt;p&gt;Tell Gemini what he needs to know about the promotion, and explain in detail what we need. This echoes the question at the beginning of this article. The point of this step, by the way, is not just to explain the promotion, but also to tell him what results to send back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4
&lt;/h3&gt;

&lt;p&gt;Based on Gemini's thought process, we have to keep correcting it until his understanding and calculations are correct. Fortunately, GenAI doesn't hide anything. He tells us step by step what he's thinking, so it's easy to find mistakes in the middle. I have to say, it's much easier to debug a natural language than a programmed language.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5
&lt;/h3&gt;

&lt;p&gt;Ask Gemini to generate a response structure that corresponds to the requirements in step 3, which is why I said we should tell him what we want as early as possible. If we don't tell him as early as possible, we may need to go back and adjust his thought process at this step, which would be very ineffective.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final step
&lt;/h3&gt;

&lt;p&gt;Because GenAI will still keep "describing" his answer, we have to tell him, "I don't want to see the process, I just want to see the result, and I don't want any description".&lt;/p&gt;

&lt;p&gt;Finally, the business logic is complete.&lt;/p&gt;

&lt;p&gt;Wait, that's a little weird. We don't deal with business logic this way by interacting with GenAI.&lt;/p&gt;

&lt;p&gt;Yes, that's right! The first step to the last step are all pre-defined "prompts", and we can get the result by wrapping all these prompts and business logic inputs in the same question and asking GenAI.&lt;/p&gt;

&lt;p&gt;In fact, it looks like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/wirelessr/85ed1e1616513a4fcd4bdc3ad5f7874b" rel="noopener noreferrer"&gt;https://gist.github.com/wirelessr/85ed1e1616513a4fcd4bdc3ad5f7874b&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;INSERT_INPUT_HERE&lt;/code&gt; is actually the original structure of our shopping cart promotion calculation.&lt;/p&gt;

&lt;p&gt;This process is exactly the same as the popular prompt engineering nowadays.&lt;/p&gt;

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

&lt;p&gt;In this article we have shown a case study of using GenAI to accomplish business logic.&lt;/p&gt;

&lt;p&gt;Let's organize the whole process again.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inform about the role of GenAI&lt;/li&gt;
&lt;li&gt;Explain the input of the business logic.&lt;/li&gt;
&lt;li&gt;Describe the requirements of the business logic.&lt;/li&gt;
&lt;li&gt;Correct GenAI's errors.&lt;/li&gt;
&lt;li&gt;Generate the output of the business logic.&lt;/li&gt;
&lt;li&gt;Prune all descriptive statements.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These steps are all centered around prompt engineering, and the more you are familiar with prompt engineering, the quicker the process will be.&lt;/p&gt;

&lt;p&gt;The benefits of this process are not only that we can make O(n^2) business logic processing become O(1) as mentioned at the beginning, but also that we can make business logic easier to debug. As I said, it's much easier to catch human speech defects than it is to find bugs in a program.&lt;/p&gt;

&lt;p&gt;Nevertheless, there is one important thing to realize about this development process. We have to realize that GenAI is actually a Large Language Model, or LLM, which is not good at computation. So when we use GenAI to write business logic, we still need to have full unit testing to make sure the results are what we expect.&lt;/p&gt;

&lt;p&gt;In other words, the importance of unit testing increases rather than decreases with this development process.&lt;/p&gt;

&lt;p&gt;When we think of GenAI for software development, we always think of Copilot, but it's much simpler to let GenAI implement business logic directly without generating code.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
