<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Suman Das on Medium]]></title>
        <description><![CDATA[Stories by Suman Das on Medium]]></description>
        <link>https://medium.com/@dassum?source=rss-54106b197f0a------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*d3CoFf8bJpFnBeJXUp-gKg.jpeg</url>
            <title>Stories by Suman Das on Medium</title>
            <link>https://medium.com/@dassum?source=rss-54106b197f0a------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 03:00:39 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@dassum/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Loop Engineering with Agents]]></title>
            <link>https://dassum.medium.com/loop-engineering-with-agents-5e9b984e8d8a?source=rss-54106b197f0a------2</link>
            <guid isPermaLink="false">https://medium.com/p/5e9b984e8d8a</guid>
            <category><![CDATA[ai-engineering]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[ai-agent]]></category>
            <dc:creator><![CDATA[Suman Das]]></dc:creator>
            <pubDate>Tue, 30 Jun 2026 05:13:58 GMT</pubDate>
            <atom:updated>2026-06-30T05:13:58.943Z</atom:updated>
            <content:encoded><![CDATA[<p>The system that decides your agent’s next move: when to use it, when to skip it, and how to build one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*58FkNRZLUZUfWDuYWSdMPQ.png" /><figcaption>Loop Engineering with Agents</figcaption></figure><p>Every AI agent already runs in a loop. Almost nobody designs that loop on purpose — and that’s the difference between an agent that quietly gets the job done and one that burns through your budget overnight, convinced it’s making progress. That gap has a name now: <strong>loop engineering</strong>.</p><p>The loop itself is simple. On a single turn an agent reasons about what to do, takes an action (calls a tool, runs a search), observes the result, and reasons again — the <strong>reason → act → observe</strong> cycle that powers tools like Claude Code and Cursor. For a while the skill that mattered most was <em>prompt engineering</em>: wording that one request as well as possible. But once models could act, search, and read their own results, the bigger question became what decides the agent’s next move — and when it should stop. <strong>Loop engineering</strong> is the choice to design that on purpose instead of leaving it to chance: deciding the goal, the checks, and the stopping point, then building a small system that defines the goal once and lets the agent iterate until the work is done. The shift is from telling the agent what to do at each step to building the system that does the telling.</p><p>That’s powerful, but it’s where the real work starts. A poorly designed loop wastes tokens, runs forever, or fools itself into thinking it’s making progress; a well-designed one is efficient, stops at the right moment, and produces reliable output. The difference isn’t a cleverer prompt — it’s a better loop. This article is about when that’s worth doing, when it isn’t, and how to build one.</p><blockquote><strong><em>A quick note on tools.</em></strong><em> The examples below use </em><a href="https://www.langchain.com/langgraph"><em>LangGraph</em></a><em>, because it makes the structure of a loop easy to see. But loop engineering isn’t tied to any one framework — the same ideas apply whether we build with LangGraph, a different agent framework, or plain code we write ourselves. The framework changes; the concepts stay the same.</em></blockquote><p>Let’s dive in.</p><h3>Table of Contents</h3><ol><li><strong>The Anatomy of an Agentic Loop</strong> — the five parts every loop is made of</li><li><strong>When to Use Loop Engineering</strong> — the problem shapes that genuinely need a loop</li><li><strong>Common Types of Loops</strong> — the handful of shapes most loops take</li><li><strong>How to Build a Loop</strong> — a step-by-step LangGraph walkthrough</li><li><strong>Failure Modes &amp; Guardrails</strong> — what breaks, and how to prevent each</li><li><strong>When NOT to Use Loop Engineering</strong> — where a loop adds cost and risk for no benefit</li><li><strong>Conclusion</strong> — a quick decision checklist</li><li><strong>References</strong></li></ol><h4>1. The Anatomy of an Agentic Loop</h4><p>Before deciding <em>when</em> to use a loop, it helps to know what one is made of. Put simply, almost every agentic loop has five parts.</p><ul><li><strong>A trigger.</strong> Something kicks the loop off — a human saying “go,” a scheduled job, a new support ticket, a pull request opening. Until there’s a trigger, nothing happens.</li><li><strong>A goal we can check.</strong> The loop needs to know what “done” looks like, ideally in a way a machine can verify. “Make the app better” is a recipe for an infinite loop; “make all the tests pass” or “produce an answer where every claim is backed by a cited source” gives the loop a real finish line.</li><li><strong>Tools.</strong> The loop only works if the agent can actually <em>do</em> things and <em>see</em> the results — run code and read the error, search the web and read what came back. Without real feedback from the environment, the agent is just guessing in a vacuum.</li><li><strong>State (memory).</strong> The current picture of the work: what’s been tried, what’s been found, how many times the loop has run. Managing this well is the hardest part of loop design — let the state balloon and the model loses the signal in the noise.</li><li><strong>Stop rules.</strong> The conditions that end the loop — and there are three ways out, not two: success (“goal met”), surrender (“tried five times, giving up”), and <em>hand-off</em>, where the loop hits something risky or ambiguous and escalates to a human instead of guessing.</li></ul><p>Keep these five in mind. Most of the “when,” “when not,” and “how” below comes straight back to them.</p><h4>2. When to Use Loop Engineering</h4><p>A loop earns its keep when a single pass through the model isn’t enough — when the task genuinely needs the agent to try, check, and try again.</p><p><strong>When to use:</strong></p><ul><li><strong>The work can be verified.</strong> This is the big one. If we can check the result automatically — tests pass or fail, an answer either cites real sources or doesn’t, a JSON output is valid or malformed — a loop can use that check to drive itself toward “correct.” Verification is the steering wheel; without it, looping just spends money.</li><li><strong>The first attempt is often wrong but fixable.</strong> Writing code, drafting and refining research, cleaning messy data — these improve with iteration. The agent produces something, sees what’s wrong, and patches it.</li><li><strong>The number of steps isn’t known in advance.</strong> Some problems take two steps; the same kind of problem might take seven the next time. A fixed pipeline can’t handle that variability, but a loop that runs until the goal is met can.</li><li><strong>The task runs unattended.</strong> If we want an agent to handle tickets overnight, or keep retrying a flaky integration until it succeeds, we need a system that decides its own next move rather than waiting for us.</li></ul><p><strong>Sample Use Case:</strong> A <strong>research agent that verifies its own findings</strong>. It drafts an answer, checks whether each claim is actually supported by sources, and if not, goes back and researches the weak spots — looping until the answer holds up or it hits a limit. This fits all four signals above, and it’s the example we’ll build in Section 4.</p><h4>3. Common Types of Loops</h4><p>Most agentic loops are variations on a few shapes. Knowing them helps you spot which one a problem wants before wiring anything together.</p><p><strong>Generate-and-verify (reflection).</strong> One step produces something, a second step checks it, and the loop repeats until it passes. Steering is pass/fail — the check says “good enough” or “here’s what’s wrong.” Best when correctness is checkable: code that must pass tests, research whose claims must hold up. <em>This is the loop we’ll build below.</em></p><pre>   +-----------+      +-----------+<br>--&gt;| Generate  |-----&gt;|  Verify   |----- pass -----&gt; [ done ]<br>   +-----------+      +-----+-----+<br>        ^                   |<br>        +------- fail ------+</pre><p><strong>Pick it when</strong> you have an objective check for “correct” and want the agent to keep fixing until it passes. <em>Example:</em> an agent that writes a SQL query, runs it, and loops until the query executes and returns sensible rows.</p><p><strong>Evaluator-optimizer (generate-and-score).</strong> Instead of a yes/no check, a scorer rates each attempt and the loop pushes toward the highest score rather than merely “correct.” Use it when there’s no clean pass/fail but you can still measure <em>better</em> — tuning a prompt, refining ad copy, ranking results.</p><pre>   +-----------+      +-----------+<br>--&gt;| Generate  |-----&gt;|  Score    |--- good enough --&gt; [ done ]<br>   +-----------+      +-----+-----+<br>        ^                   |<br>        +-- improve --------+<br>            (use the score)</pre><p><strong>Pick it when</strong> quality is a spectrum rather than pass/fail, but you can put a number on it. <em>Example:</em> drafting a product tagline where a scorer rates clarity and punch, and the loop keeps refining the strongest candidate.</p><p><strong>Plan-execute-replan.</strong> A planner lays out the steps, executors carry them out, and the loop circles back to revise the plan when a step turns up something it didn’t expect. Here the loop wraps the <em>plan</em>, not a single output — a fit for open-ended, multi-step work like research or a coding project.</p><pre>   +-----------+      +-----------+<br>--&gt;|   Plan    |-----&gt;|  Execute  |-- all steps done --&gt; [ done ]<br>   +-----------+      +-----+-----+<br>        ^                   |<br>        +-- replan ---------+<br>            (hit a surprise)</pre><p><strong>Pick it when</strong> the task is multi-step and you can’t know every step up front. <em>Example:</em> “migrate this service to a new framework” — plan the steps, start executing, and replan the moment a step uncovers an unexpected dependency.</p><p><strong>Scheduled / monitoring loop.</strong> Rather than iterating to a goal in one run, it wakes on a cadence or a trigger to watch and react — triage incoming tickets, sweep for failing builds, retry a flaky job. The loop runs <em>across time</em> instead of within a single task.</p><pre>   +-----------+     +-----------+     +-----------+<br>--&gt;|   Wait    |----&gt;|   Check   |----&gt;|    Act    |---+<br>   | (timer /  |     +-----------+     +-----------+   |<br>   |  trigger) |                                       |<br>   +-----------+ &lt;-------------------------------------+<br>        (no fixed end — keeps watching over time)</pre><p><strong>Pick it when</strong> there’s no single finish line — you want ongoing watching and reacting. <em>Example:</em> a job that checks an inbox every few minutes and triages new tickets, or a nightly sweep that flags stale pull requests.</p><p>These aren’t mutually exclusive — real systems often nest them (a scheduled loop that, on each wake, runs a generate-and-verify pass inside it). We’ll take the most common shape, generate-and-verify, and build it end to end next.</p><h4>4. How to Build a Loop</h4><p><strong>What we’re building.</strong> A <em>research agent</em> that answers a question and refuses to trust its own first draft. Its job: take a question, gather information and write an answer, then fact-check every claim in that answer against real sources. Any claim that isn’t supported gets sent back for another round of research. The agent keeps doing this until the whole answer holds up — or until it hits a retry limit and hands back the best version it has, flagging what’s still shaky. In short, its role is not just to <em>answer</em>, but to answer and then <em>prove the answer to itself</em> before returning it.</p><p>That self-checking behavior is exactly the generate-and-verify loop from Section 3, so it’s a clean first build.</p><p><strong>LangGraph</strong> is a good fit for loop engineering because it models an agent as a <em>graph</em> — boxes (nodes) that do work, connected by arrows (edges) that decide what runs next. Crucially, those arrows can point backward, which is what makes a loop possible. (A plain linear chain can’t loop; a graph can.)</p><p>Our research agent has three jobs, so it has three nodes. Notice that the first two split the work into a <em>maker</em> and a <em>checker</em> — a doer and a critic kept separate, one of the most common loop patterns:</p><pre>                    +------------------+<br>  START ----------&gt; |     Research     | &lt;-------------+<br>                    | (draft / revise) |               |<br>                    +--------+---------+               |<br>                             |                         |<br>                             v                         | needs work<br>                    +------------------+               | (+ feedback)<br>                    |      Verify      |               |<br>                    |  (check claims)  |               |<br>                    +--------+---------+               |<br>                             |                         |<br>                             v                         |<br>                       +-----------+   needs work      |<br>                       |  Decide   |-------------------+<br>                       | (router)  |<br>                       +-----+-----+<br>                             | verified / out of tries<br>                             v<br>                          [ END ]</pre><ol><li><strong>Research</strong> — gather information and draft an answer.</li><li><strong>Verify</strong> — critique the draft: check whether each claim in it is actually supported.</li><li><strong>Decide</strong> — a small router that reads the verdict and chooses what happens next: finish, or loop back and research the weak spots.</li></ol><p><strong>Setup.</strong> This example runs on <strong>Python 3.12</strong>. Install the three packages it needs:</p><pre>pip install langgraph==1.2.6 langchain-openai==1.3.3 pydantic==2.13.4</pre><ul><li><strong>langgraph</strong> — the graph/loop engine (StateGraph, START, END).</li><li><strong>langchain-openai</strong> — the OpenAI chat model wrapper (ChatOpenAI).</li><li><strong>pydantic</strong> — defines the structured verdict the verifier returns.</li></ul><p>We’ll also need an OpenAI API key in your environment before running it:</p><pre>export OPENAI_API_KEY=&quot;your-key-here&quot;</pre><p>python</p><pre>from typing import TypedDict, Literal<br>from langgraph.graph import StateGraph, START, END<br>from langchain_openai import ChatOpenAI<br>from pydantic import BaseModel, Field<br><br># A model client on OpenAI&#39;s Responses API, with the built-in web-search tool<br># bound on — so the model can search the web itself, no extra package needed.<br># &quot;gpt-5&quot; is a reasoning model; swap it for any current model you have access to.<br>llm = ChatOpenAI(model=&quot;gpt-5&quot;, use_responses_api=True)<br>searcher = llm.bind_tools([{&quot;type&quot;: &quot;web_search&quot;}])<br><br>MAX_ITERATIONS = 3         # a stop rule, decided up front<br><br># STATE — the shared memory passed between every node<br>class ResearchState(TypedDict):<br>    question: str          # what we&#39;re answering<br>    draft: str             # the current best answer<br>    verdict: str           # &quot;verified&quot; or &quot;needs_work&quot;<br>    feedback: str          # what the verifier said to fix<br>    iterations: int        # how many times we&#39;ve looped<br><br># The verifier returns a typed result, so the router gets a clean<br># &quot;verified&quot; / &quot;needs_work&quot; to branch on instead of parsing prose.<br>class Verdict(BaseModel):<br>    status: str = Field(description=&#39;&quot;verified&quot; or &quot;needs_work&quot;&#39;)<br>    feedback: str = Field(description=&quot;claims lacking support, if any&quot;)<br><br># NODE 1 — the maker: search the web, then draft (or revise) the answer.<br>def research(state: ResearchState) -&gt; dict:<br>    prompt = &quot;Search the web, then answer the question. Back every claim with a source.\n&quot;<br>    if state.get(&quot;feedback&quot;):<br>        prompt += f&quot;A reviewer flagged these gaps — fix them:\n{state[&#39;feedback&#39;]}\n&quot;<br>    prompt += f&quot;\nQuestion: {state[&#39;question&#39;]}&quot;<br>    draft = searcher.invoke(prompt).text<br>    return {&quot;draft&quot;: draft, &quot;iterations&quot;: state[&quot;iterations&quot;] + 1}<br><br># NODE 2 — the checker: search for evidence, then critique the draft.<br>def verify(state: ResearchState) -&gt; dict:<br>    evidence = searcher.invoke(<br>        f&quot;Search the web for evidence to fact-check claims about: {state[&#39;question&#39;]}&quot;<br>    ).text<br>    checker = llm.with_structured_output(Verdict)<br>    result = checker.invoke(<br>        &quot;You are a fact-checker. Using the evidence below, reply &#39;verified&#39; &quot;<br>        &quot;only if every claim in the answer is supported; otherwise &#39;needs_work&#39; &quot;<br>        &quot;and list the unsupported claims as feedback.\n\n&quot;<br>        f&quot;Evidence:\n{evidence}\n\nAnswer:\n{state[&#39;draft&#39;]}&quot;<br>    )<br>    return {&quot;verdict&quot;: result.status, &quot;feedback&quot;: result.feedback}<br><br># ROUTER — the heart of the loop. Reads the verdict, picks the next step.<br>def decide(state: ResearchState) -&gt; Literal[&quot;research&quot;, &quot;__end__&quot;]:<br>    if state[&quot;verdict&quot;] == &quot;verified&quot;:<br>        return &quot;__end__&quot;                      # success: goal met<br>    if state[&quot;iterations&quot;] &gt;= MAX_ITERATIONS:<br>        return &quot;__end__&quot;                      # surrender: out of tries<br>    return &quot;research&quot;                         # loop back and fix the gaps<br><br># WIRE IT UP<br>graph = StateGraph(ResearchState)<br>graph.add_node(&quot;research&quot;, research)<br>graph.add_node(&quot;verify&quot;, verify)<br><br>graph.add_edge(START, &quot;research&quot;)            # trigger<br>graph.add_edge(&quot;research&quot;, &quot;verify&quot;)         # always verify a fresh draft<br>graph.add_conditional_edges(&quot;verify&quot;, decide, {<br>    &quot;research&quot;: &quot;research&quot;,                   # the backward arrow = the loop<br>    &quot;__end__&quot;: END,<br>})<br><br>agent = graph.compile()<br><br>result = agent.invoke({<br>    &quot;question&quot;: &quot;What were the main causes of the 2008 financial crisis?&quot;,<br>    &quot;draft&quot;: &quot;&quot;, &quot;verdict&quot;: &quot;&quot;, &quot;feedback&quot;: &quot;&quot;, &quot;iterations&quot;: 0,<br>})<br>print(result[&quot;draft&quot;])</pre><p>Two things worth noting. First, both nodes reach the live web through OpenAI’s built-in web_search tool (bound on with bind_tools) — the research node searches before drafting so its claims rest on real results, and the verify node searches again to fact-check the draft against fresh evidence rather than the model&#39;s memory. That second search is what gives the loop something objective to steer by. Second, the verify node uses with_structured_output, which forces the model to return a typed verdict and feedback rather than prose — that&#39;s what lets the router branch reliably instead of parsing free text.</p><p>Notice how cleanly the five parts of a loop map onto the code:</p><ul><li><strong>Trigger:</strong> invoke(...) starts it with a question.</li><li><strong>Goal we can check:</strong> the verify node decides &quot;verified&quot; vs &quot;needs_work&quot;.</li><li><strong>Tools:</strong> both nodes use OpenAI’s built-in web search for real evidence; research drafts from it, verify fact-checks against it.</li><li><strong>State:</strong> the ResearchState dictionary carries the draft, the feedback, and the iteration count from node to node.</li><li><strong>Stop rules:</strong> the decide router ends the loop on <em>either</em> success <em>or</em> hitting MAX_ITERATIONS.</li></ul><p>That last point is the whole game. The router is where loop engineering actually lives. A single line — “stop if verified, stop if we’ve tried three times, otherwise go back” — is the difference between an agent that reliably improves its answer and one that runs up our bill forever.</p><p>Our example has two exits — verified, or out of tries — but production loops usually add a third: <strong>hand off to a human.</strong> When the agent hits something risky or ambiguous, the smartest move isn’t to guess or to quit silently; it’s to stop and escalate with everything it has gathered. In the router that’s just one more branch: if confidence is low or the action is sensitive, return an &quot;escalate&quot; route that ends the loop and surfaces the draft, the open questions, and what&#39;s been tried. Knowing when <em>not</em> to decide is part of good loop design.</p><h4>5. Failure Modes &amp; Guardrails</h4><p>Once a loop can run on its own, a handful of predictable failures show up. Each has a standard defense.</p><ul><li><strong>The infinite loop.</strong> The agent never decides it’s done and runs until something crashes or our budget does. <em>Guard:</em> always include a hard cap — a maximum number of iterations, like MAX_ITERATIONS above. Every loop needs an escape hatch that doesn&#39;t depend on the agent&#39;s own judgment.</li><li><strong>Token burn.</strong> Each pass re-sends a growing pile of context, so cost creeps up quietly. <em>Guard:</em> keep state lean. Pass forward only what the next step needs — the current draft and the specific feedback — not the entire history of everything that’s been tried.</li><li><strong>Hallucinated progress.</strong> The agent reports success without achieving it (“I’ve fixed the issue!” — it hasn’t). <em>Guard:</em> never let the agent grade its own homework on the thing that matters. Use a separate verification step with an objective check — tests that really run, sources that really exist — so “done” means done, not “the model said so.”</li><li><strong>Spinning on the same error.</strong> The loop retries the identical failing action and gets the identical failure. <em>Guard:</em> feed the previous failure into the next attempt so the agent changes its approach instead of repeating it — exactly why our research node receives the verifier&#39;s feedback.</li><li><strong>Silent, unaccountable actions.</strong> As loops act over longer stretches of time, we lose track of what happened and why. <em>Guard:</em> log every step — what the agent saw, what it decided, what it did. Observability stops being optional the moment a loop touches anything that matters, and tools like LangSmith exist specifically to trace these runs.</li><li><strong>Going fully autonomous on day one.</strong> A loop that takes real actions unsupervised from the start is one we haven’t earned the right to trust yet. <em>Guard:</em> roll it out in phases — start in report-only mode (it tells us what it <em>would</em> do), then let it make assisted changes we approve, then let it run unattended. Each phase has to earn the next.</li></ul><h4>6. When NOT to Use Loop Engineering</h4><p>Loops are powerful, which makes them easy to over-apply. Reach for something simpler in these cases.</p><p><strong>When NOT to use:</strong></p><ul><li><strong>One pass is enough.</strong> Summarizing a document, translating text, answering a factual question, classifying an email — these are single-shot tasks. Wrapping them in a loop adds cost, latency, and new failure modes for zero benefit. If a straight line of steps does the job, use a straight line.</li><li><strong>We can’t tell when it’s done.</strong> If there’s no way to check the result — the goal is subjective, fuzzy, or only a human can judge it — a loop has nothing to steer by. It will either stop arbitrarily or spin forever. Without a verifiable goal, we don’t have a loop; we have a slot machine.</li><li><strong>Mistakes are expensive or irreversible.</strong> A loop that sends emails, moves money, or deletes data on each iteration can do real damage fast. Keep a human in the approval path, or don’t loop on the consequential action at all.</li><li><strong>Cost and latency matter more than autonomy.</strong> Every iteration is more model calls, more tokens, more time. For a high-volume, low-margin, or latency-sensitive feature, a predictable single call often beats a smart-but-expensive loop.</li></ul><p><strong>Rule of thumb:</strong> if we can’t write down how the loop knows it succeeded and when it must give up, we’re not ready to build one yet.</p><h4>Conclusion</h4><p>Loop engineering is a genuine shift in where the leverage lives. The most valuable skill is no longer writing the perfect prompt; it’s designing the system that tells the agent what to do — the trigger, the goal, the tools, the state, and the stop rules. But a loop is a tool, not a default.</p><p>Before building one, run through five quick questions:</p><ol><li><strong>Can we verify the result automatically?</strong> If not, don’t loop — we have nothing to steer with.</li><li><strong>Does the task actually improve with iteration?</strong> If one pass is enough, keep it simple.</li><li><strong>What exactly does “done” mean?</strong> Write the success condition down before writing any code.</li><li><strong>When must it give up?</strong> Set a hard limit so the loop can’t run forever.</li><li><strong>Are the actions safe to repeat?</strong> If each iteration could cause real harm, add a human checkpoint or don’t loop on that step.</li></ol><p>If we can answer all five, we’re ready to build a loop that’s reliable rather than just impressive. Start small — a single research-and-verify cycle like the one above — get the stop rules right, and grow from there.</p><p>One thought to close on: a loop doesn’t just speed the work up, it multiplies whatever judgment goes into it — repeating good calls and bad ones alike, at machine speed. So the goal isn’t to wire one up and walk away. The discipline is to stay close enough to keep understanding what the loop does, even after it runs on its own — to remain its engineer rather than just the person who starts it.</p><h4>References</h4><ul><li><strong>Loop Engineering (Cobus Greyling, Substack)</strong> — <a href="https://cobusgreyling.substack.com/p/loop-engineering">https://cobusgreyling.substack.com/p/loop-engineering</a></li><li><strong>Loop Engineering (Addy Osmani)</strong> — <a href="https://addyosmani.com/blog/loop-engineering/">https://addyosmani.com/blog/loop-engineering/</a></li><li><strong>loop-engineering — patterns &amp; reference (GitHub)</strong> — <a href="https://github.com/cobusgreyling/loop-engineering">https://github.com/cobusgreyling/loop-engineering</a></li><li><strong>LangGraph documentation</strong> — <a href="https://langchain-ai.github.io/langgraph/">https://langchain-ai.github.io/langgraph/</a></li><li><strong>LangGraph (overview)</strong> — <a href="https://www.langchain.com/langgraph">https://www.langchain.com/langgraph</a></li><li><strong>Building Effective Agents (Anthropic)</strong> — <a href="https://www.anthropic.com/research/building-effective-agents">https://www.anthropic.com/research/building-effective-agents</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5e9b984e8d8a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Multi-Agent AI Patterns for Developers: Pick the Right Pattern for the Right Problem]]></title>
            <link>https://dassum.medium.com/multi-agent-ai-patterns-for-developers-pick-the-right-pattern-for-the-right-problem-8f03ef476b45?source=rss-54106b197f0a------2</link>
            <guid isPermaLink="false">https://medium.com/p/8f03ef476b45</guid>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[agents]]></category>
            <category><![CDATA[agentic-ai]]></category>
            <category><![CDATA[genai]]></category>
            <dc:creator><![CDATA[Suman Das]]></dc:creator>
            <pubDate>Wed, 01 Apr 2026 15:41:19 GMT</pubDate>
            <atom:updated>2026-04-01T15:41:19.578Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YN0OUfpgIDz6z7ixj46dmg.png" /><figcaption>Multi-Agent AI Patterns</figcaption></figure><p>In this <strong>agentic world</strong> we’re living in, it’s tempting to believe that one powerful AI agent can do it all — research, write, code, review, and deploy. And honestly, for simple tasks, it can.</p><p>But before we go further, let’s get on the same page about what we actually mean by an <strong>“agent.”</strong></p><p><strong>An agent</strong> isn’t just an LLM responding to a prompt. It’s an LLM with <strong>autonomy</strong> — it can <strong>reason</strong> about a goal, <strong>decide</strong> what steps to take, <strong>use tools</strong> (APIs, databases, search engines, code interpreters), <strong>observe</strong> the results, and <strong>adjust</strong> its approach. Think of it as the difference between asking someone a question and hiring someone to get a job done. A chatbot answers. An agent <strong>acts</strong>.</p><p>Now here’s where it gets interesting.</p><p>The moment we ask a <strong>single agent</strong> to juggle ten things at once — pull data from five sources, cross-check facts, draft a report, get it reviewed, and fix what’s wrong — <strong>the cracks show</strong>. It loses context. It hallucinates under pressure. It forgets what it said three steps ago.</p><p>Sound familiar? It should. We solved this exact problem in software engineering decades ago. We stopped building <strong>monoliths</strong>. We broke systems into <strong>microservices</strong> — small, focused, independently deployable. Each service did one thing well and talked to others through clean interfaces.</p><p><strong>Multi-agent AI is that same evolution</strong>, and we’re right in the middle of it.</p><p>Instead of asking one LLM to be the architect, builder, tester, and project manager all at once, we give each role to a <strong>specialized agent</strong>. One plans. One executes. One critiques. One decides. They <strong>collaborate</strong>, <strong>challenge each other</strong>, and together deliver what no single agent could.</p><p>The real question for us isn’t <strong>whether</strong> to use multiple agents — it’s <strong>which pattern</strong> fits our problem and <strong>when</strong>.</p><p>That’s exactly what we’ll cover in this article. We’ll walk through the most widely adopted <strong>multi-agent design patterns</strong> — each with a visual diagram, a real-world use case, clear guidance on when to use it, and which frameworks support it out of the box.</p><p>Whether we’re building with <strong>LangGraph, CrewAI, AutoGen, Google ADK, or the Anthropic Agent SDK</strong> — by the end, we’ll know exactly which pattern to reach for and why.</p><p><strong>Let’s dive in.</strong></p><h3>What We’ll Cover</h3><p><strong>Core Patterns</strong></p><ol><li><strong>Sequential / Pipeline </strong>— Agents in a straight line, one after another</li><li><strong>Orchestrator-Worker</strong> — A manager delegates to specialists</li><li><strong>Parallel / Fan-Out → Fan-In </strong>— Independent tasks run concurrently</li><li><strong>Reflection / Self-Critique </strong>— Generate, review, improve, repeat</li><li><strong>Router / Dispatch</strong> — Classify and route to the right agent</li><li><strong>Planning + Execution </strong>— Separate thinking from doing</li><li><strong>Handoff </strong>— Transfer control when out of scope</li><li><strong>Evaluator-Optimizer Loop</strong> — Generate, score, improve</li></ol><p><strong>Bonus</strong></p><ul><li><strong>Emerging Patterns </strong>— Debate, Group Chat, Mixture of Agents</li></ul><p><strong>Putting It All Together</strong></p><ul><li><strong>Quick Reference:</strong> Pick Your Pattern</li><li><strong>Combining Patterns</strong></li><li><strong>Final Thoughts</strong></li></ul><h4>1. Sequential / Pipeline</h4><pre>+-----------+     +-----------+     +-----------+     +-----------+<br>|  Agent A  | --&gt; |  Agent B  | --&gt; |  Agent C  | --&gt; |  Agent D  |<br>| (Extract) |     |(Transform)|     | (Validate)|     |(Summarize)|<br>+-----------+     +-----------+     +-----------+     +-----------+</pre><p>The simplest pattern we can start with. We line up our agents like an assembly line — each one does its job and passes the result to the next.</p><p><strong>When to use:</strong></p><ul><li>Our task has clear, ordered stages</li><li>Each stage has a distinct responsibility</li><li>We need auditability and traceability at every step</li></ul><p><strong>When NOT to use:</strong></p><ul><li>Stages are independent and don’t need sequential ordering</li><li>We need speed — this pattern is only as fast as the slowest agent</li></ul><p><strong>Sample Use Case:</strong> A content publishing pipeline — Agent A extracts key points from raw research, Agent B transforms them into a blog draft, Agent C validates facts and grammar, Agent D generates the final summary and SEO metadata.</p><p><strong>Framework Support:</strong></p><ul><li><strong>LangGraph</strong> — Native linear graph edges</li><li><strong>CrewAI</strong> — Process.sequential</li><li><strong>AutoGen</strong> — initiate_chats with sequential carryover</li><li><strong>Google ADK</strong> — SequentialAgent (native workflow agent)</li><li><strong>Anthropic Agent SDK</strong> — No dedicated sequential API; Claude naturally chains steps through its reasoning loop</li></ul><h4>2. Orchestrator-Worker (Hierarchical)</h4><pre>                          +----------------+<br>                          |  Orchestrator  |<br>                          |   (Manager)    |<br>                          +-------+--------+<br>                                  |<br>                     +------------+------------+<br>                     |            |            |<br>                     v            v            v<br>               +----------+  +----------+  +----------+<br>               | Worker A |  | Worker B |  | Worker C |<br>               |(Frontend)|  | (Backend)|  |(Database)|<br>               +----+-----+  +----+-----+  +----+-----+<br>                    |              |              |<br>                    +--------------+--------------+<br>                                   |<br>                                   v<br>                          +----------------+<br>                          |  Orchestrator  |<br>                          |  (Aggregate)   |<br>                          +----------------+</pre><p>This is probably the most widely used pattern in production. We have a <strong>smart manager agent</strong> that understands the big picture, <strong>dynamically decides</strong> what sub-tasks to create, delegates to specialists, monitors progress, and stitches the results together. The key word here is <strong>dynamic</strong> — the orchestrator reasons about the task and may change its plan mid-execution based on what workers return.</p><p><strong>How is this different from Parallel Fan-Out?</strong> In Fan-Out, we know all the sub-tasks upfront and just run them simultaneously. Here, the orchestrator <strong>figures out</strong> the sub-tasks at runtime, may run some in sequence and others in parallel, and can reassign or retry if something fails. Think of it as a project manager vs a mail room — the project manager thinks and adapts, the mail room just distributes.</p><p><strong>When to use:</strong></p><ul><li>Sub-tasks aren’t known upfront and need <strong>dynamic decomposition</strong></li><li>Workers may have <strong>dependencies</strong> on each other’s output</li><li>We need <strong>centralized coordination</strong>, error handling, and adaptive replanning</li></ul><p><strong>When NOT to use:</strong></p><ul><li>The task is simple enough for a single agent</li><li>All sub-tasks are independent and known upfront — just use Parallel Fan-Out instead</li></ul><p><strong>Sample Use Case:</strong> An e-commerce order processing system — a customer places an order, and the orchestrator agent takes over. It delegates to an Inventory agent (check stock availability), a Payment agent (process the payment), and a Shipping agent (calculate delivery options and schedule dispatch). But here’s the key: if the Inventory agent reports “out of stock,” the orchestrator <strong>adapts</strong> — it asks a Recommendation agent to suggest alternatives, updates the customer, and only proceeds to Payment once the customer confirms. A simple fan-out can’t handle this kind of dynamic decision-making.</p><p><strong>Framework Support:</strong></p><ul><li><strong>LangGraph</strong> — Supervisor pattern with sub-graphs</li><li><strong>CrewAI</strong> — Process.hierarchical with manager_llm</li><li><strong>AutoGen</strong> — GroupChat with GroupChatManager</li><li><strong>Google ADK</strong> — LlmAgent with sub_agents and transfer_to_agent</li><li><strong>Anthropic Agent SDK</strong> — Parent agent spawns subagents via Agent tool (core pattern)</li></ul><h4>3. Parallel / Fan-Out → Fan-In</h4><pre>                          +-----------+<br>                          | Splitter  |<br>                          +-----+-----+<br>                                |<br>                  +------+------+------+---------+<br>                  |      |             |         |<br>                  v      v             v         v<br>              +-------+  +-------+  +-------+  +-------+<br>              |Agent 1|  |Agent 2|  |Agent 3|  |Agent 4|<br>              | (Web) |  | (DB)  |  | (API) |  |(Docs) |<br>              +---+---+  +---+---+  +---+---+  +---+---+<br>                  |          |           |          |<br>                  +----------+-----------+----------+<br>                                 |<br>                                 v<br>                          +------------+<br>                          | Aggregator |<br>                          +------------+</pre><p>This is the <strong>speed pattern</strong>. When we have independent sub-tasks that don’t depend on each other, why run them one by one? We fan out to multiple agents running concurrently, then fan in to merge the results. Unlike the Orchestrator-Worker, there’s <strong>no smart manager here</strong> — we know all the sub-tasks upfront, fire them all at once, and a simple aggregator combines the results.</p><p><strong>How is this different from Orchestrator-Worker?</strong> The splitter doesn’t think — it just distributes pre-defined tasks. There’s no dynamic decision-making, no replanning, no dependency management between workers. It’s pure parallelism for speed. If any worker’s output should change what other workers do, we need the Orchestrator pattern instead.</p><p><strong>When to use:</strong></p><ul><li>All sub-tasks are <strong>known upfront</strong> and <strong>independent</strong> of each other</li><li><strong>Latency is critical</strong> — we want results as fast as the slowest agent</li><li>We’re gathering data from multiple sources that don’t affect each other</li></ul><p><strong>When NOT to use:</strong></p><ul><li>Tasks have dependencies on each other — use Orchestrator-Worker instead</li><li>We need dynamic task creation based on intermediate results</li></ul><p><strong>Sample Use Case:</strong> A competitive analysis tool — we need data from four independent sources. Agent 1 scrapes competitor websites, Agent 2 pulls financial data from APIs, Agent 3 gathers social media sentiment, Agent 4 searches news articles. None of them need each other’s output. They all run at the same time, and the aggregator combines everything into a single report. Total time = slowest agent, not the sum of all four.</p><p><strong>Framework Support:</strong></p><ul><li><strong>LangGraph</strong> — Parallel branches with fan-in node, Send API for map-reduce</li><li><strong>CrewAI</strong> — async_execution=True on tasks</li><li><strong>AutoGen</strong> — a_initiate_chats with concurrent execution</li><li><strong>Google ADK</strong> — ParallelAgent (native workflow agent, runs sub-agents concurrently)</li><li><strong>Anthropic Agent SDK</strong> — Multiple Agent tool calls in a single message execute concurrently</li></ul><h4>4. Reflection / Self-Critique</h4><pre>                      +-----------+<br>                      | Generator |&lt;-----------+<br>                      +-----+-----+            |<br>                            |                  |<br>                            |  Output          | Feedback<br>                            v                  | (if not good enough)<br>                      +-----------+            |<br>                      | Reviewer  |------------+<br>                      +-----+-----+<br>                            |<br>                            |  Approved (meets quality threshold)<br>                            v<br>                      [Final Output]</pre><p>We all write better with a good editor. This pattern gives our AI a built-in editor — one agent generates, another reviews, and we iterate until the output meets our quality bar.</p><p><strong>When to use:</strong></p><ul><li>Code generation (write → test → fix cycles)</li><li>Content creation where first drafts aren’t good enough</li><li>Any task where we can define clear quality criteria</li></ul><p><strong>When NOT to use:</strong></p><ul><li>We need real-time responses — iteration adds latency</li><li>The task is simple enough that the first pass is usually correct</li><li>We don’t have clear criteria for “good enough”</li></ul><p><strong>Sample Use Case:</strong> An automated code review system — the Generator agent writes a function, the Reviewer agent runs tests, checks for edge cases, security issues, and style. If anything fails, it sends feedback. The Generator rewrites. We repeat until all tests pass and the review score exceeds our threshold.</p><p><strong>Framework Support:</strong></p><ul><li><strong>LangGraph</strong> — Cycles with conditional stop (well-documented pattern)</li><li><strong>CrewAI</strong> — guardrail for LLM-based validation, max_iter for retries</li><li><strong>AutoGen</strong> — register_nested_chats for inner critic conversations</li><li><strong>Google ADK</strong> — LoopAgent with max_iterations and escalate=True to exit</li><li><strong>Anthropic Agent SDK</strong> — No dedicated reflection API; Claude naturally self-evaluates and retries through its reasoning loop</li></ul><h4>5. Router / Dispatch</h4><pre>                              +--------------+<br>                User -------&gt; |    Router    |<br>                Query         | (Classifier) |<br>                              +------+-------+<br>                                     |<br>                        +------------+------------+<br>                        |            |            |<br>                        v            v            v<br>                  +----------+  +----------+  +----------+<br>                  | Billing  |  |Technical |  |  Sales   |<br>                  |  Agent   |  |  Agent   |  |  Agent   |<br>                  +----------+  +----------+  +----------+</pre><p>Not every query needs a specialist. But every query needs the <em>right</em> specialist. We use a lightweight router agent to <strong>classify the input once</strong> and send it to the best-fit agent. That’s it — the router’s job is done.</p><p><strong>How is this different from Orchestrator-Worker?</strong> The router is a <strong>traffic cop</strong>, not a project manager. It makes <strong>one decision</strong> (which agent should handle this?) and steps aside. It doesn’t break the task into sub-tasks, doesn’t wait for results, doesn’t coordinate between agents, and doesn’t aggregate anything. The Orchestrator does all of that. Use a Router when the task goes to <strong>one</strong> specialist. Use an Orchestrator when the task needs to be <strong>split across multiple</strong> specialists.</p><p><strong>When to use:</strong></p><ul><li>We receive diverse input types that each need a <strong>single</strong> specialist</li><li>We want to optimize cost (route simple queries to cheaper/smaller models)</li><li>Customer support, helpdesks, and multi-domain assistants</li></ul><p><strong>When NOT to use:</strong></p><ul><li>The task needs to be split across multiple agents — use Orchestrator-Worker instead</li><li>All queries need the same type of processing</li><li>We only have one specialist — just call it directly</li></ul><p><strong>Sample Use Case:</strong> A SaaS customer support system — the Router classifies incoming tickets as billing, technical, account, or feature-request. A billing query goes <strong>entirely</strong> to the Billing agent. A technical issue goes <strong>entirely</strong> to the Technical agent. The router doesn’t coordinate between them — each specialist handles the full request on its own.</p><p><strong>Framework Support:</strong></p><ul><li><strong>LangGraph</strong> — Conditional edges with routing functions (native)</li><li><strong>CrewAI</strong> — @router decorator in Flows, ConditionalTask</li><li><strong>AutoGen</strong> — Custom speaker_selection_method in GroupChat</li><li><strong>Google ADK</strong> — LlmAgent with sub_agents and routing instructions (no dedicated RouterAgent)</li><li><strong>Anthropic Agent SDK</strong> — LLM-driven dispatch via Agent tool descriptions</li></ul><h4>6. Planning + Execution</h4><pre>                     +-----------+<br>                +---&gt;|  Planner  |<br>                |    +-----+-----+<br>                |          |  Step 1, Step 2, Step 3...<br>                |          v<br>                |    +-----------+     +-----------+     +-----------+<br>                |    | Executor  | --&gt; | Executor  | --&gt; | Executor  |<br>                |    | (Step 1)  |     | (Step 2)  |     | (Step 3)  |<br>                |    +-----------+     +-----------+     +-----+-----+<br>                |                                              |<br>                |    Replan if needed                          |<br>                +----------------------------------------------+</pre><p>We separate the <strong>thinking</strong> from the <strong>doing</strong>. A planner agent creates the full roadmap upfront, then executor agents carry out each step. The planner steps back and only re-engages if something fails and we need to replan.</p><p><strong>How is this different from Orchestrator-Worker?</strong> The Orchestrator is a <strong>micromanager</strong> — it stays involved at every step, makes decisions between tasks, and coordinates in real-time. Plan + Execute is more like an <strong>architect and builders</strong> — the architect draws the full blueprint upfront, hands it to builders, and only comes back if the foundation cracks. The planning and execution are <strong>clearly separated phases</strong>, not interleaved.</p><p><strong>When to use:</strong></p><ul><li>Complex, multi-step goals where we benefit from <strong>thinking before acting</strong></li><li>We need the ability to <strong>replan</strong> when intermediate results change the approach</li><li>Research tasks, coding projects, data analysis workflows</li></ul><p><strong>When NOT to use:</strong></p><ul><li>The workflow is fixed and well-known — just use a Pipeline</li><li>We need real-time decision-making at every step — use Orchestrator-Worker instead</li><li>The task is too simple to justify a separate planning step</li></ul><p><strong>Sample Use Case:</strong> An automated research assistant — the Planner agent breaks down “write a market analysis report on EV batteries” into: (1) identify top 5 companies, (2) gather financial data, (3) analyze patents, (4) compare market share, (5) draft report. The planner hands this off and steps back. Executor agents handle each step sequentially. If Step 2 reveals a major company we missed, the Planner re-engages, revises the plan, and executors continue with the updated steps.</p><p><strong>Framework Support:</strong></p><ul><li><strong>LangGraph</strong> — Plan-and-execute pattern (well-documented with tutorials)</li><li><strong>CrewAI</strong> — Task context dependencies + Flows for planning</li><li><strong>AutoGen</strong> — Planner + executor via two-agent or GroupChat (manual setup)</li><li><strong>Google ADK</strong> — Compose with SequentialAgent + LoopAgent for replanning</li><li><strong>Anthropic Agent SDK</strong> — No dedicated planning API; Claude naturally plans and executes through its reasoning loop</li></ul><h4>7. Handoff</h4><pre>    +----------+  &quot;I can&#39;t handle this&quot;  +------------+<br>    |  Agent A |------------------------&gt;|  Agent B   |<br>    | (General)|   + context transfer    |(Specialist)|<br>    +----------+                         +------+-----+<br>                                                |<br>                           &quot;Needs escalation&quot;   |<br>                                                v<br>                                           +----------+<br>                                           |  Agent C |<br>                                           |  (Human) |<br>                                           +----------+</pre><p>Sometimes an agent knows it’s out of its depth. Instead of hallucinating an answer, it hands off the conversation — with full context — to a more capable agent or a human.</p><p><strong>When to use:</strong></p><ul><li>Customer service with escalation tiers</li><li>Multi-domain assistants where scope changes mid-conversation</li><li>Human-in-the-loop workflows where some cases need manual review</li></ul><p><strong>When NOT to use:</strong></p><ul><li>We can handle everything with a single agent</li><li>We prefer routing upfront rather than mid-conversation handoffs</li></ul><p><strong>Sample Use Case:</strong> A healthcare triage bot — Agent A handles general wellness questions. When it detects symptoms that need medical advice, it hands off to Agent B (a medically-trained agent with access to clinical guidelines). If Agent B determines the case is urgent, it escalates to Agent C — a human doctor — with the full conversation history attached.</p><p><strong>Framework Support:</strong></p><ul><li><strong>LangGraph</strong> — Conditional edges with full state transfer between nodes</li><li><strong>CrewAI</strong> — allow_delegation=True for autonomous agent delegation</li><li><strong>AutoGen</strong> — register_hand_off in Swarm pattern (v0.4+)</li><li><strong>Google ADK</strong> — transfer_to_agent with session state context</li><li><strong>Anthropic Agent SDK</strong> — Subagents return to parent (no direct peer handoff)</li></ul><h4>8. Evaluator-Optimizer Loop</h4><pre>            +-------------+     Candidates     +-------------+<br>            |  Generator  |-------------------&gt;|  Evaluator  |<br>            | (N outputs) |                    |  (Score)    |<br>            +------+------+                    +------+------+<br>                   ^                                  |<br>                   |        Feedback + Best           |<br>                   +----------------------------------+<br>                          Iterate until optimal</pre><p>We generate multiple candidates, score them, and use the feedback to generate better ones. It’s evolution in action — each iteration gets us closer to optimal.</p><p><strong>When to use:</strong></p><ul><li>We can define a clear scoring function or evaluation criteria</li><li>Prompt optimization, query refinement, search ranking</li><li>Code optimization where we can measure performance</li></ul><p><strong>When NOT to use:</strong></p><ul><li>We don’t have clear evaluation criteria</li><li>We need a single quick answer, not an optimized one</li><li>The generation cost is too high to produce multiple candidates</li></ul><p><strong>Sample Use Case:</strong> An ad copy optimizer — the Generator creates 10 variations of ad copy. The Evaluator agent scores each on readability, brand alignment, emotional appeal, and CTA strength. The top 3 go back to the Generator with feedback. We repeat for 3 rounds and pick the winner.</p><p><strong>Framework Support:</strong></p><ul><li><strong>LangGraph</strong> — Cycles with scoring nodes and conditional exit</li><li><strong>CrewAI</strong> — Task output validation functions + guardrail + max_iter</li><li><strong>AutoGen</strong> — register_nested_chats for evaluation before reply</li><li><strong>Google ADK</strong> — LoopAgent with evaluator LlmAgent (custom composition)</li><li><strong>Anthropic Agent SDK</strong> — No dedicated scoring API; implement scoring as a custom tool and Claude iterates through its reasoning loop</li></ul><h4>Bonus: Emerging Patterns</h4><p>These patterns are powerful in specific scenarios but less commonly used in production today. They’re worth knowing as our multi-agent systems mature.</p><p><strong>Debate / Adversarial:</strong></p><pre>                  +------------+           +------------+<br>                  |  Proposer  | &lt;-------&gt; |   Critic   |<br>                  |   (For)    |  Debate   |  (Against) |<br>                  +------+-----+  Rounds   +------+-----+<br>                         |                        |<br>                         +------------+-----------+<br>                                      |<br>                                      v<br>                               +------------+<br>                               |   Judge    |<br>                               | (Decision) |<br>                               +------------+</pre><p>Sometimes the best way to find the truth is to argue about it. We set up agents on opposing sides and let them challenge each other. A judge agent listens to both and makes the final call. Best for <strong>high-stakes decisions</strong>, fact verification, and red-teaming.</p><p><strong>Sample Use Case:</strong> An investment analysis system — a Bull agent argues for, a Bear agent argues against. A Judge agent weighs both sides and generates a balanced recommendation.</p><p><strong>Framework Support:</strong></p><ul><li><strong>AutoGen</strong> — Two-agent initiate_chat (built-in, natural fit)</li><li><strong>LangGraph</strong> — Cyclic graphs with conditional edges</li><li><strong>Google ADK</strong> — LoopAgent with two LlmAgent sub-agents</li></ul><p><strong>Multi-Agent Group Chat:</strong></p><pre>              +----------+    +----------+    +----------+<br>              | Product  |    | Engineer |    | Designer |<br>              | Manager  |    |          |    |          |<br>              +----+-----+    +----+-----+    +----+-----+<br>                   |               |               |<br>                   +---------------+---------------+<br>                                   |<br>                                   v<br>                        +--------------------+<br>                        |  Shared Context    |<br>                        |  (Conversation)    |<br>                        +--------------------+</pre><p>Multiple agents sit in a shared conversation, each contributing from their expertise. Best for <strong>brainstorming</strong> and simulating cross-functional team discussions. Expensive — every agent reads every message.</p><p><strong>Sample Use Case:</strong> Designing a new API — PM, Engineer, Security, and QA agents iterate in shared conversation until we have a complete spec.</p><p><strong>Framework Support:</strong></p><ul><li><strong>AutoGen</strong> — GroupChat + GroupChatManager (native, best support)</li><li><strong>CrewAI</strong> — Crew shares task outputs between agents (not true group chat)</li><li><strong>Anthropic Agent SDK</strong> — Not supported (subagents run in isolated contexts)</li></ul><p><strong>Mixture of Agents (MoA):</strong></p><pre>      Layer 1 (Generate)      Layer 2 (Refine)      Layer 3 (Final)<br><br>          +--------+<br>          | LLM A  |--+<br>          +--------+  |          +--------+<br>          +--------+  +---------&gt;| LLM D  |--+<br>          | LLM B  |--+          +--------+  |       +------------+<br>          +--------+  |          +--------+  +------&gt;| Aggregator |<br>          +--------+  +---------&gt;| LLM E  |--+       +------------+<br>          | LLM C  |--+          +--------+<br>          +--------+</pre><p>We use multiple models to generate diverse responses, then refine and aggregate. Different models have different strengths — together, they’re better than any one alone. Best for <strong>maximum accuracy</strong> when cost and latency aren’t constraints.</p><p><strong>Sample Use Case:</strong> A legal contract review — Claude analyzes clause risks, GPT-4 checks regulatory compliance, Gemini cross-references case law. An aggregator synthesizes findings into a unified risk report.</p><p><strong>Framework Support:</strong></p><ul><li><strong>LangGraph</strong> — Parallel nodes with different model configs per node</li><li><strong>CrewAI</strong> — Each agent configurable with different llm parameter</li><li><strong>AutoGen</strong> — Each agent in GroupChat can use different llm_config</li><li><strong>Google ADK</strong> — Model-agnostic; each LlmAgent can use Gemini, Claude, GPT via LiteLLM</li><li><strong>Anthropic Agent SDK</strong> — Claude-only (no multi-provider support)</li></ul><h4>Quick Reference: Pick Your Pattern</h4><p><strong>Core Patterns:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F-gtZLaAS1F97r06pZLD0A.png" /><figcaption>core pattern comparison</figcaption></figure><p><strong>Emerging Patterns:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rhyDXmNPH5qvhv9bKaXB2Q.png" /><figcaption>Emerging patterns comparison</figcaption></figure><h4>Combining Patterns: How Production Systems Actually Work</h4><p>In the real world, we rarely use a single pattern in isolation. Most production systems combine two or three patterns together. Here are some common combinations we see:</p><p><strong>Router + Reflection</strong> We route incoming queries to specialists, and each specialist uses a reflection loop to ensure quality before responding.</p><p><strong>Orchestrator + Parallel + Reflection</strong> The orchestrator decomposes a task, fans out to workers in parallel, each worker reflects on its output, and the orchestrator aggregates the results.</p><p><strong>Plan + Execute + Debate</strong> The planner creates a strategy, executors carry it out, and at critical decision points, a debate between agents validates the approach before we proceed.</p><p><strong>Router + Handoff + Human-in-the-Loop</strong> The router classifies the query, specialist agents handle it, and if confidence is low, we hand off to a human — with full context preserved.</p><h4>Final Thoughts</h4><p>Multi-agent design patterns aren’t just academic concepts — they’re practical tools we can reach for when a single agent isn’t enough. The key is to <strong>start simple</strong> and <strong>add complexity only when we need it</strong>.</p><p>Here’s a simple decision framework we can follow:</p><ol><li><strong>Can one agent handle it?</strong> → Don’t use multi-agent. Keep it simple.</li><li><strong>Do we need specialization?</strong> → Orchestrator-Worker or Router.</li><li><strong>Do we need speed?</strong> → Parallel Fan-Out.</li><li><strong>Do we need accuracy?</strong> → Reflection, Debate, or Mixture of Agents.</li><li><strong>Do we need adaptability?</strong> → Plan + Execute.</li><li><strong>Do we need graceful escalation?</strong> → Handoff.</li></ol><p>The agentic era is just getting started, and these patterns will evolve as we build more sophisticated systems. But the fundamentals — decomposition, specialization, coordination, and feedback — these are timeless engineering principles wearing a new hat.</p><p>Now let’s go build something.</p><h4>References</h4><p><strong>Framework Documentation</strong></p><ul><li>LangGraph — <a href="https://langchain-ai.github.io/langgraph/">https://langchain-ai.github.io/langgraph/</a></li><li>CrewAI — <a href="https://docs.crewai.com/">https://docs.crewai.com/</a></li><li>AutoGen — <a href="https://microsoft.github.io/autogen/">https://microsoft.github.io/autogen/</a></li><li>Google ADK — <a href="https://google.github.io/adk-docs/">https://google.github.io/adk-docs/</a></li><li>Anthropic Agent SDK — <a href="https://platform.claude.com/docs/en/agent-sdk/overview">https://platform.claude.com/docs/en/agent-sdk/overview</a></li></ul><p><strong>Pattern Deep Dives</strong></p><ul><li>LangGraph Multi-Agent Patterns — <a href="https://langchain-ai.github.io/langgraph/concepts/multi_agent/">https://langchain-ai.github.io/langgraph/concepts/multi_agent/</a></li><li>Google ADK Multi-Agent Systems — <a href="https://google.github.io/adk-docs/agents/multi-agents/">https://google.github.io/adk-docs/agents/multi-agents/</a></li><li>Google Developers Blog: Multi-Agent Patterns in ADK — <a href="https://developers.googleblog.com/en/developers-guide-to-multi-agent-patterns-in-adk/">https://developers.googleblog.com/en/developers-guide-to-multi-agent-patterns-in-adk/</a></li><li>AutoGen Conversation Patterns — <a href="https://microsoft.github.io/autogen/0.2/docs/tutorial/conversation-patterns/">https://microsoft.github.io/autogen/0.2/docs/tutorial/conversation-patterns/</a></li><li>CrewAI Processes (Sequential &amp; Hierarchical) — <a href="https://docs.crewai.com/concepts/processes">https://docs.crewai.com/concepts/processes</a></li><li>Anthropic: Building Effective Agents — <a href="https://www.anthropic.com/research/building-effective-agents">https://www.anthropic.com/research/building-effective-agents</a></li></ul><p><strong>Research Papers &amp; Articles</strong></p><ul><li>“Mixture-of-Agents Enhances Large Language Model Capabilities” (2024) — <a href="https://arxiv.org/abs/2406.04692">https://arxiv.org/abs/2406.04692</a></li><li>“Plan-and-Solve Prompting” — <a href="https://arxiv.org/abs/2305.04091">https://arxiv.org/abs/2305.04091</a></li><li>“Reflexion: Language Agents with Verbal Reinforcement Learning” — <a href="https://arxiv.org/abs/2303.11366">https://arxiv.org/abs/2303.11366</a></li><li>“CAMEL: Communicative Agents for Mind Exploration” — <a href="https://arxiv.org/abs/2303.17760">https://arxiv.org/abs/2303.17760</a></li></ul><p><strong>Workflow Agent Types (Google ADK)</strong></p><ul><li>SequentialAgent — <a href="https://google.github.io/adk-docs/agents/workflow-agents/sequential-agents/">https://google.github.io/adk-docs/agents/workflow-agents/sequential-agents/</a></li><li>ParallelAgent — <a href="https://google.github.io/adk-docs/agents/workflow-agents/parallel-agents/">https://google.github.io/adk-docs/agents/workflow-agents/parallel-agents/</a></li><li>LoopAgent — <a href="https://google.github.io/adk-docs/agents/workflow-agents/loop-agents/">https://google.github.io/adk-docs/agents/workflow-agents/loop-agents/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8f03ef476b45" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Create Your Own Ghibli-Style Podcast!]]></title>
            <link>https://dassum.medium.com/how-to-create-your-own-ghibli-style-podcast-7076f1f82777?source=rss-54106b197f0a------2</link>
            <guid isPermaLink="false">https://medium.com/p/7076f1f82777</guid>
            <category><![CDATA[ghibli]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[chatgpt]]></category>
            <dc:creator><![CDATA[Suman Das]]></dc:creator>
            <pubDate>Mon, 31 Mar 2025 15:21:05 GMT</pubDate>
            <atom:updated>2025-04-06T03:52:39.244Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_iYYhPcpkRSTqc2QblCGTQ.png" /></figure><p>Drawing inspiration from NotebookLM and OpenAI’s Ghibli-style art, this guide explores how to create a captivating podcast on any topic, infused with the charm and aesthetics of the Ghibli style.</p><p>Let us dive into creating engaging and interactive content for a solo podcast using Generative AI. Once we select an interesting topic for which we would like to create a podcast, we can leverage various AI tools to generate compelling podcast content for our topic.</p><p>The following steps will demonstrate how to do this:</p><ol><li>Choose a Podcast Topic</li><li>Set Up the Notebook</li><li>Gather Content</li><li>Data Cleaning and Chunking for LLMs</li><li>Generate the Podcast Script</li><li>Add Voice to the Podcast Script</li><li>Generate an Image for the podcast</li><li>Bring the Podcast Together</li></ol><h4><strong>1. Choose a Podcast Topic</strong></h4><p>The first step in creating a podcast is selecting a topic and gathering content to serve as its foundation. In this case, we’ll use the <strong>Wikipedia</strong> data to generate the text corpus. However, there are various other ways to source content.</p><p>For this tutorial, as a sports enthusiast, I have chosen the <strong>IPL </strong>(Indian Premier League) as a topic for my podcast, as it is currently ongoing and a major event in my country. However, you can choose any topic that interests you.</p><h4><strong>2. Set Up the Notebook</strong></h4><p>In this tutorial:</p><ul><li><strong>Code: <br></strong>To code, we’ll be using a Google Colab notebook, but you can use any Jupyter notebook environment.</li><li><strong>Content:<br></strong>To generate the podcast content, we will be using the OpenAI <a href="https://platform.openai.com/docs/models">GPT-4o mini</a> model. To use this model, we will need an API key. If you’ve already signed up with OpenAI, you can generate a new key from the profile section or use any existing key.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2k7pvB5OUjIrx6xc4LHpNg.png" /><figcaption>view API-Keys</figcaption></figure><p>If you haven&#39;t signed in yet, you can use the URL below to sign in to OpenAI and then get the key from the profile section.</p><p><a href="https://platform.openai.com/docs/overview">https://platform.openai.com/docs/overview</a></p><ul><li><strong>Voiceover: <br></strong>To generate a voiceover for the podcast script, we’ll use <a href="https://elevenlabs.io/">ElevenLabs</a> with the <a href="https://elevenlabs.io/docs/models">Multilingual V2</a> model. Like OpenAI, ElevenLabs requires an API key to access its models. If you’re already signed up, you can generate a new key from the profile section or use an existing one.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BRDcd6QvlhNA54FhmB3ZNA.png" /><figcaption>elevenlabs api-keys</figcaption></figure><p>To set up the notebook, perform the following steps:</p><p><strong>Step 2.1:</strong> Open a new notebook and write a brief description of the activity or the topic of your podcast.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yONbDRAvWKqJ3Of1AUkwIg.png" /><figcaption>notebook-setup</figcaption></figure><p><strong>Step 2.2:</strong> Once done, connect to the runtime.<br><strong><em>Note:</em></strong><em> In </em><strong><em>Google Colab</em></strong><em>, a “runtime” is an environment where your code runs.</em></p><p><strong>Step 2.3:</strong> Run the following code blocks in the notebook to store the API keys of Open API and Eleven Labs, which we will use later.</p><pre>from getpass import getpass<br><br>api_key = getpass(&#39;Enter your OPENAI_API_KEY&#39;)<br><br>eleven_labs_api_key = getpass(&#39;Enter your ELEVEN_LABS_API_KEY&#39;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Qg-SFbg5ziAen1PSUzvTTw.png" /><figcaption>store api keys</figcaption></figure><h4><strong>3. Gather Content</strong></h4><p><strong>Wikipedia</strong> is a good starting point for our text corpus as it is a free online encyclopedia and covers an extensive range of subjects such as history, science, current events, celebrities, and much more. Using the Wikipedia library, we can get all the details of a particular Wiki page based on the title or the Wiki ID.</p><p><strong><em>Note:</em></strong><em> Wikipedia is a useful tool for general information, but it should not be relied upon as the sole or primary source for in-depth, high-stakes research.</em></p><p>To proceed with this tutorial, we need to install several libraries in the Colab notebook.</p><p><strong>Step 3.1: Install LangChain and required libraries</strong></p><p>Install all the required libraries using the following command.</p><pre>!pip install wikipedia tiktoken langchain_community langchain_experimental langchain_openai langgraph elevenlabs pydub --quiet</pre><p><strong>Step 3.2: Load Wikipedia Document Using LangChain</strong></p><p>Once the libraries are successfully installed, to gather content about the IPL (Indian Premier League) from Wikipedia, we will use the LangChain WikipediaLoader. LangChain provides a streamlined way to load, process, and query data from various sources, including Wikipedia.</p><p>To load Wikipedia documents using <strong>LangChain</strong>, you can use the WikipediaLoader from the LangChain library in the following way.</p><pre>from langchain_community.document_loaders import WikipediaLoader<br><br>docs = WikipediaLoader(query=&quot;Indian_Premier_League&quot;, load_max_docs=3).load()</pre><p>To read more about the Langchain WikipediaLoader, check the following link.</p><p><a href="https://python.langchain.com/docs/integrations/document_loaders/wikipedia/">Wikipedia | 🦜️🔗 LangChain</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gpuhj2cihHijkfdoRxpN8A.png" /><figcaption>Download data from Wikipedia</figcaption></figure><p>We can’t provide all the downloaded data to the LLM at the same time due to context length limitations. Therefore, we need to split the data into smaller, logical chunks that can be processed within these constraints. Each LLM model has its own context length, which determines how much text it can handle at a time. So, how do we address this challenge? Let’s explore the solution in the next step.</p><h4><strong>4. Data Cleaning and Chunking for LLMs</strong></h4><p>Before training any machine learning model, data cleaning is essential. Similarly, before providing data as input to an LLM, we must perform both <strong>data cleaning</strong> and <strong>chunking</strong>.</p><p><strong>What is Data Chunking?</strong></p><p>Data chunking is a strategy to work around the context window limitation of Large Language Models (LLMs), ensuring that longer texts can be effectively processed. It involves:</p><ul><li>Splitting text into sections that fit within the model’s context window.</li><li>Maintaining inter-chunk dependencies to preserve narrative flow.</li></ul><p><strong>Using LangChain’s SemanticChunker</strong></p><p>To efficiently split the Wikipedia-derived text corpus, we will use <strong>LangChain’s Semantic Text Splitter</strong>. This tool ensures that the text is broken down in a way that preserves meaning and coherence. You may need a language model or embeddings to help with semantic splitting. In this case, we will use OpenAI embeddings, but you can use other embeddings as well.</p><p>Below is the reference code used to split the documents using SemanticChunker.</p><pre>from langchain_experimental.text_splitter import SemanticChunker<br>from langchain_openai.embeddings import OpenAIEmbeddings<br><br>text_splitter = SemanticChunker(<br>    OpenAIEmbeddings(api_key=api_key), breakpoint_threshold_type=&quot;gradient&quot;<br>)<br>split_docs = text_splitter.split_documents(docs)<br>len(split_docs)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*by_j8IV8ujhaPBPLL_p1rw.png" /><figcaption>Data Chunking</figcaption></figure><h4><strong>5. Generate the Podcast Script</strong></h4><p>To create a compelling podcast script, we need to identify key points from the content that would be engaging for the audience. Once we’ve gathered these interesting discussion points, we should structure them in a format suitable for voiceover narration.</p><p>Here, we’ll use <strong>LangGraph’s MapReduce pattern</strong> to summarize the content in a structured format that can be effectively used for our podcast.</p><p>The following code creates the map and reduce chains separately using ChatPromptTemplate, which will later be used in LangGraph.</p><pre>from langchain.chat_models import init_chat_model<br>from langchain_core.prompts import ChatPromptTemplate<br>from langchain_core.output_parsers import StrOutputParser<br>import os<br>if not os.environ.get(&quot;OPENAI_API_KEY&quot;):<br>  os.environ[&quot;OPENAI_API_KEY&quot;] = api_key<br><br>llm = init_chat_model(&quot;gpt-4o-mini&quot;, model_provider=&quot;openai&quot;,temperature = 0.7)<br><br># Map<br>map_template = &quot;&quot;&quot;<br>You are a Sports enthusiast who is doing a research for a podcast. Your task is to extract relevant information from the Result delimited by triple quotes.<br>Please identify 3 interesting questions and answers which can be used for a podcast discussion.<br>The identified discussions should be returned in the following format.<br>- Highlight 1 from the text<br>- Highlight 2 from the text<br>- Highlight 3 from the text<br>Result: {docs}&quot;&quot;&quot;<br><br># Reduce<br>reduce_template =&quot;&quot;&quot;<br>You are a writer creating the script for the another episode of a podcast &quot;Sport 101&quot; hosted by \&quot;Tom\&quot;.<br>Make the podcast casual, engaging and informative<br>Extract relevant information for the solo podcast from the Result delimited by triple quotes.<br>Use \&quot;Tom\&quot; as the person speaking and sharing insights about the topic with the audience.<br>Use the below format for the solo podcast.<br>1. Introduction about the topic and welcome everyone for another episode of the podcast &quot;Sport 101&quot;.<br>2. Introduce the speaker in brief.<br>3. Then start the podcast.<br>4. Start the podcast with some casual discussion like what he is doing right now at this moment.<br>5. End the podcast with thank you speech to everyone.<br>6. Do not use the word \&quot;conversation\&quot; in the response.<br>7. Do not use the word \&quot;Introduction\&quot; in the response.<br>8. Do not use the word \&quot;Podcast Script\&quot; in response.<br>Result: &quot;{doc_summaries}&quot;<br>&quot;&quot;&quot;<br><br>map_prompt = ChatPromptTemplate([(&quot;human&quot;, map_template)])<br>reduce_prompt = ChatPromptTemplate([(&quot;human&quot;, reduce_template)])<br><br>map_chain = map_prompt | llm | StrOutputParser()<br>reduce_chain = reduce_prompt | llm | StrOutputParser()</pre><p>Here, the map chain is used to extract interesting facts from the content, while the reduce chain helps generate the podcast script in the desired format for our IPL podcast.</p><p>Below is a sample LangGraph implementation using the map and reduce chains created above. The graph includes a node for generating summaries, which is applied to a list of input documents. This output then flows into a second node that generates the final summary.</p><pre>import operator<br>from typing import Annotated, List, TypedDict<br>from langgraph.constants import Send<br>from langgraph.graph import END, START, StateGraph<br><br># This will be the overall state of the main graph.<br># It will contain the input document contents, corresponding<br># summaries, and a final summary.<br>class OverallState(TypedDict):<br>    # Notice here we use the operator.add<br>    # This is because we want combine all the summaries we generate<br>    # from individual nodes back into one list - this is essentially<br>    # the &quot;reduce&quot; part<br>    contents: List[str]<br>    summaries: Annotated[list, operator.add]<br>    final_summary: str<br><br><br># This will be the state of the node that we will &quot;map&quot; all<br># documents to in order to generate summaries<br>class SummaryState(TypedDict):<br>    content: str<br><br><br># Here we generate a summary, given a document<br>async def generate_summary(state: SummaryState):<br>    response = await map_chain.ainvoke(state[&quot;content&quot;])<br>    return {&quot;summaries&quot;: [response]}<br><br><br># Here we define the logic to map out over the documents<br># We will use this an edge in the graph<br>def map_summaries(state: OverallState):<br>    # We will return a list of `Send` objects<br>    # Each `Send` object consists of the name of a node in the graph<br>    # as well as the state to send to that node<br>    return [<br>        Send(&quot;generate_summary&quot;, {&quot;content&quot;: content}) for content in state[&quot;contents&quot;]<br>    ]<br><br># Here we will generate the final summary<br>async def generate_final_summary(state: OverallState):<br>    response = await reduce_chain.ainvoke(state[&quot;summaries&quot;])<br>    return {&quot;final_summary&quot;: response}<br><br><br># Construct the graph: here we put everything together to construct our graph<br>graph = StateGraph(OverallState)<br>graph.add_node(&quot;generate_summary&quot;, generate_summary)<br>graph.add_node(&quot;generate_final_summary&quot;, generate_final_summary)<br>graph.add_conditional_edges(START, map_summaries, [&quot;generate_summary&quot;])<br>graph.add_edge(&quot;generate_summary&quot;, &quot;generate_final_summary&quot;)<br>graph.add_edge(&quot;generate_final_summary&quot;, END)<br>app = graph.compile()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gEsbz6ynhm7dDrIO3EUwEQ.png" /><figcaption>LangGraph MapReduce Document chain</figcaption></figure><p>Let’s now call the Graph to get the desired output.</p><pre># Call the graph:<br>async for step in app.astream({&quot;contents&quot;: [doc.page_content for doc in split_docs]}):<br>    print(step)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wtInXa0T2iNR-knq_S1hyQ.png" /><figcaption>Generate summary using LangGraph</figcaption></figure><p>Our podcast script is now ready! Next, we need to add a voiceover. Let’s see how we can do that using ElevenLabs.</p><h4><strong>6. Add Voice to the Podcast Script</strong></h4><p>First, let’s select a voice for our podcast. We can find a list of available voices in the voice section on the ElevenLabs website. Here, we’ll use the ‘Sports Guy’ voice, but feel free to choose any voice that suits your preference.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5v76kUFP64k9oj4i86qimQ.png" /><figcaption>voice selection</figcaption></figure><p><strong><em>Note:</em></strong><em> While ElevenLabs offers some free credits, they are only sufficient for short scripts. For longer scripts, additional credits must be purchased to generate the voiceover.</em></p><p>We will use the helper function below to convert the text to voice using ElevenLabs.</p><pre>from elevenlabs.client import ElevenLabs<br><br>client = ElevenLabs(<br>  api_key=eleven_labs_api_key,<br>)<br><br>def createPodcast(podcastScript, speakerChoice):<br>  genPodcast = []<br>  podcastLines = podcastScript.split(&#39;\n\n&#39;)<br>  for line in podcastLines:<br>    print(line)<br>    genVoice = client.generate(text=line, voice=speakerChoice, model=&quot;eleven_multilingual_v2&quot;)<br>    # Change: Iterate through the generator and write bytes to a bytearray<br>    audio_bytes = bytearray()<br>    for chunk in genVoice:<br>        audio_bytes.extend(chunk)<br>    genPodcast.append(audio_bytes)  # Append bytearray to genPodcast<br>  return genPodcast</pre><pre>speakerName1 = &quot;Tom&quot;<br>speakerChoice1 = &quot;Sports Guy - Excited and fast, giving you that play by play.&quot;<br>genPodcast = createPodcast(podcastScript, speakerChoice1)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tS1WjYev0ZN_xu31Mdk2yg.png" /><figcaption>Convert text-to-voice</figcaption></figure><p>Once we execute the script above, an audio file will be generated. We can then manually download it for later use.</p><p>Now our podcast is ready with a voiceover. Next, we need to generate an image in Ghibli style for our podcast.</p><h4><strong>7. Generate an Image for the podcast</strong></h4><p>This step is simple — choose any podcast image as a reference and use ChatGPT to transform it into a Ghibli-style illustration.</p><p>Here is my Ghibli-style podcast image generated using ChatGPT.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_iYYhPcpkRSTqc2QblCGTQ.png" /><figcaption>Ghibli-style image</figcaption></figure><p>Sharing the prompt that we used for the above image transformation.</p><pre>Convert the image into and anime style using immersive realism similar at 99.99% to Studio Ghiblis style but not the same but similar so it’s not infrigment</pre><h4><strong>8. Bring the Podcast Together</strong></h4><p>This is the most exciting part of our podcast creation! We have our podcast audio script ready, along with a Ghibli-style podcast image. Now, let’s use <a href="https://www.hedra.com/"><strong>Hedra</strong></a> to generate the podcast video.</p><p>Hedra Studio is a content creation platform that allows users to generate high-quality videos, images, and audio. It enables video creation using an image and an audio script.</p><p><strong><em>Note</em></strong><em>: Although Hedra provides some free credits, they might not be sufficient to generate a full podcast video. If we need to create the entire video, we may have to purchase additional credits from Hedra.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Tj3zngQzYuNxc41CkXrTPQ.png" /><figcaption>Hedra Studio</figcaption></figure><p>The prompt that we used in Hedra: A sports enthusiast speaking on a solo podcast.</p><p>Hedra takes some time to generate the video. Once done, we can download and share our podcast video.</p><p>Below is the video which got generated using the above prompt.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FmZz5KntKGBA%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fshorts%2FmZz5KntKGBA&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FmZz5KntKGBA%2Fhq2.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/de0be452ecf544aef3faafec8eb03ed4/href">https://medium.com/media/de0be452ecf544aef3faafec8eb03ed4/href</a></iframe><p>If you’d like to access the complete notebook, please refer to the repository provided below.</p><p><a href="https://github.com/sumanentc/Machine-Learning-with-Python/blob/main/GenerativeAI/Ghibli_style_podcast.ipynb">Machine-Learning-with-Python/GenerativeAI/Ghibli_style_podcast.ipynb at main · sumanentc/Machine-Learning-with-Python</a></p><p>That’s all, folks! Looking forward to seeing the creative tasks you accomplish with these LLM advancements.</p><h4>References</h4><ul><li><a href="https://elevenlabs.io/text-to-speech">Free Text To Speech Online with Lifelike AI Voices | ElevenLabs</a></li><li><a href="https://elevenlabs.io/docs/cookbooks/text-to-speech/streaming">Streaming text to speech | ElevenLabs Documentation</a></li><li><a href="https://www.langchain.com/langgraph">LangGraph: Agent Orchestration Framework for Reliable AI Agents</a></li><li><a href="https://python.langchain.com/docs/how_to/semantic-chunker/">How to split text based on semantic similarity | 🦜️🔗 LangChain</a></li><li><a href="https://python.langchain.com/docs/versions/migrating_chains/map_reduce_chain/">Migrating from MapReduceDocumentsChain | 🦜️🔗 LangChain</a></li><li><a href="https://www.hedra.com/app/video">Hedra</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7076f1f82777" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fine Tune Large Language Model (LLM) on a Custom Dataset with QLoRA]]></title>
            <link>https://medium.com/swlh/fine-tune-large-language-model-llm-on-a-custom-dataset-with-qlora-fb60abdeba07?source=rss-54106b197f0a------2</link>
            <guid isPermaLink="false">https://medium.com/p/fb60abdeba07</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <dc:creator><![CDATA[Suman Das]]></dc:creator>
            <pubDate>Thu, 25 Jan 2024 03:56:01 GMT</pubDate>
            <atom:updated>2024-01-25T03:56:01.578Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3H2V5GJOfaj3SGt1lcibfA.png" /><figcaption>Fine-Tuning LLM</figcaption></figure><p>The field of natural language processing has been revolutionized by large language models (LLMs), which showcase advanced capabilities and sophisticated solutions. Trained on extensive text datasets, these models excel in tasks like text generation, translation, summarization, and question-answering. Despite their power, LLMs may not always align with specific tasks or domains.</p><p>In this tutorial, we will explore how fine-tuning LLMs can significantly improve model performance, reduce training costs, and enable more accurate and context-specific results.</p><h4>What is LLM Fine-tuning?</h4><p>Fine-tuning LLM involves the additional training of a pre-existing model, which has previously acquired patterns and features from an extensive dataset, using a smaller, domain-specific dataset. In the context of “LLM Fine-Tuning,” LLM denotes a “Large Language Model,” such as the GPT series by OpenAI. This approach holds significance as training a large language model from the ground up is highly resource-intensive in terms of both computational power and time. Utilizing the existing knowledge embedded in the pre-trained model allows for achieving high performance on specific tasks with substantially reduced data and computational requirements.</p><p>Below are some of the key steps involved in LLM Fine-tuning:</p><ol><li><strong>Select a pre-trained model</strong>: For LLM Fine-tuning first step is to carefully select a base pre-trained model that aligns with our desired architecture and functionalities. Pre-trained models are generic purpose models that have been trained on a large corpus of unlabeled data.</li><li><strong>Gather relevant Dataset</strong>: Then we need to gather a dataset that is relevant to our task. The dataset should be labeled or structured in a way that the model can learn from it.</li><li><strong>Preprocess Dataset</strong>: Once the dataset is ready, we need to do some preprocessing for fine-tuning by cleaning it, splitting it into training, validation, and test sets, and ensuring it’s compatible with the model on which we want to fine-tune.</li><li><strong>Fine-tuning</strong>: After selecting a pre-trained model we need to fine tune it on our preprocessed relevant dataset which is more specific to the task at hand. The dataset which we will select might be related to a particular domain or application, allowing the model to adapt and specialize for that context.</li><li><strong>Task-specific adaptation</strong>: During fine-tuning, the model’s parameters are adjusted based on the new dataset, helping it better understand and generate content relevant to the specific task. This process retains the general language knowledge gained during pre-training while tailoring the model to the nuances of the target domain.</li></ol><p>Fine-tuning LLMs is commonly used in natural language processing tasks such as sentiment analysis, named entity recognition, summarization, translation, or any other application where understanding context and generating coherent language is crucial. It helps leverage the knowledge encoded in pre-trained models for more specialized and domain-specific tasks.</p><h4>Fine-tuning methods</h4><p>Fine-tuning a Large Language Model (LLM) involves a supervised learning process. In this method, a dataset comprising labeled examples is utilized to adjust the model’s weights, enhancing its proficiency in specific tasks. Now, let’s delve into some noteworthy techniques employed in the fine-tuning process.</p><ol><li><strong>Full Fine Tuning (Instruction fine-tuning)</strong>: Instruction fine-tuning is a strategy to enhance a model’s performance across various tasks by training it on examples that guide its responses to queries. The choice of the dataset is crucial and tailored to the specific task, such as summarization or translation. This approach, known as full fine-tuning, updates all model weights, creating a new version with improved capabilities. However, it demands sufficient memory and computational resources, similar to pre-training, to handle the storage and processing of gradients, optimizers, and other components during training.</li><li><strong>Parameter Efficient Fine-Tuning (PEFT)</strong> is a form of instruction fine-tuning that is much more efficient than full fine-tuning. Training a language model, especially for full LLM fine-tuning, demands significant computational resources. Memory allocation is not only required for storing the model but also for essential parameters during training, presenting a challenge for simple hardware. PEFT addresses this by updating only a subset of parameters, effectively “freezing” the rest. This reduces the number of trainable parameters, making memory requirements more manageable and preventing catastrophic forgetting. Unlike full fine-tuning, PEFT maintains the original LLM weights, avoiding the loss of previously learned information. This approach proves beneficial for handling storage issues when fine-tuning for multiple tasks. There are various ways of achieving Parameter efficient fine-tuning. Low-Rank Adaptation <strong>LoRA</strong> &amp; <strong>QLoRA</strong> are the most widely used and effective.</li></ol><h4>What is LoRa?</h4><p><a href="https://arxiv.org/abs/2106.09685">LoRA</a> is an improved finetuning method where instead of finetuning all the weights that constitute the weight matrix of the pre-trained large language model, two smaller matrices that approximate this larger matrix are fine-tuned. These matrices constitute the LoRA adapter. This fine-tuned adapter is then loaded into the pre-trained model and used for inference.</p><p>After LoRA fine-tuning for a specific task or use case, the outcome is an unchanged original LLM and the emergence of a considerably smaller “LoRA adapter,” often representing a single-digit percentage of the original LLM size (in MBs rather than GBs).</p><p>During inference, the LoRA adapter must be combined with its original LLM. The advantage lies in the ability of many LoRA adapters to reuse the original LLM, thereby reducing overall memory requirements when handling multiple tasks and use cases.</p><h4>What is Quantized LoRA (QLoRA)?</h4><p><a href="https://arxiv.org/abs/2305.14314">QLoRA</a> represents a more memory-efficient iteration of LoRA. <strong>QLoRA</strong> takes <strong>LoRA</strong> a step further by also quantizing the weights of the LoRA adapters (smaller matrices) to lower precision (e.g., 4-bit instead of 8-bit). This further reduces the memory footprint and storage requirements. In QLoRA, the pre-trained model is loaded into GPU memory with quantized 4-bit weights, in contrast to the 8-bit used in LoRA. Despite this reduction in bit precision, QLoRA maintains a comparable level of effectiveness to LoRA.</p><p>In this tutorial, we will use <strong>Parameter-efficient fine-tuning with QLoRA</strong>.</p><p>Now let’s explore how we can fine-tune LLM on a custom dataset using QLoRA on a single GPU.</p><ol><li>Setting up the NoteBook</li><li>Install required libraries</li><li>Loading dataset</li><li>Create Bitsandbytes configuration</li><li>Loading the Pre-Trained model</li><li>Tokenization</li><li>Test the Model with Zero Shot Inferencing</li><li>Pre-processing dataset</li><li>Preparing the model for QLoRA</li><li>Setup PEFT for Fine-Tuning</li><li>Train PEFT Adapter</li><li>Evaluate the Model Qualitatively (Human Evaluation)</li><li>Evaluate the Model Quantitatively (with ROUGE Metric)</li></ol><h4>1. Setting up the NoteBook.</h4><p>While we will utilize a Kaggle notebook for this demonstration, feel free to use any Jupyter notebook environment. Kaggle offers a generous allowance of 30 hours of free GPU usage per week, which is ample for our experimentation. To begin, let’s open a new notebook, establish some headings, and then proceed to connect to the runtime.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P9NHjF2_TU7fGYIwFuH-pQ.png" /><figcaption>notebook-with-headings</figcaption></figure><p>Here, we will select the <strong>GPU P100</strong> as the <strong>ACCELERATOR. </strong>Feel free to try other GPU options available in Kaggle or any other environment.</p><p>In this tutorial, we will be using <strong>HuggingFace</strong> libraries to download and train the model. To download models from <strong>HuggingFace</strong>, we will need an <strong>Access Token</strong>. If you’ve already signed up with HuggingFace, you can generate a new Access Token from the settings section or use any existing Access Token.</p><h4>2. Install required libraries</h4><p>Now, let’s install the necessary libraries for this experiment.</p><pre>!pip install -q -U bitsandbytes transformers peft accelerate datasets scipy einops evaluate trl rouge_score</pre><p>Let’s understand the importance of some of these libraries.</p><ul><li><strong>Bitsandbytes</strong>: An excellent package that provides a lightweight wrapper around custom CUDA functions that make LLMs go faster — optimizers, matrix multiplication, and quantization. In this tutorial, we’ll be using this library to load our model as efficiently as possible.</li><li><strong>transformers</strong>: A library by <a href="https://huggingface.co/">Hugging Face</a> (<strong>🤗</strong>) that provides pre-trained models and training utilities for various natural language processing tasks.</li><li><strong>peft</strong>: A library by <a href="https://huggingface.co/">Hugging Face</a> (<strong>🤗</strong>) that enables parameter-efficient fine-tuning.</li><li><strong>accelerate:</strong> Accelerate abstracts exactly and only the boilerplate code related to multi-GPUs/TPU/fp16 and leave the rest of your code unchanged.</li><li><strong>datasets</strong>: Another library by <a href="https://huggingface.co/">Hugging Face</a> (<strong>🤗</strong>) that provides easy access to a wide range of datasets.</li><li><strong>einops</strong>: A library that simplifies tensor operations.</li></ul><p><strong>Loading the required libraries</strong></p><pre>from datasets import load_dataset<br>from transformers import (<br>    AutoModelForCausalLM,<br>    AutoTokenizer,<br>    BitsAndBytesConfig,<br>    HfArgumentParser,<br>    AutoTokenizer,<br>    TrainingArguments,<br>    Trainer,<br>    GenerationConfig<br>)<br>from tqdm import tqdm<br>from trl import SFTTrainer<br>import torch<br>import time<br>import pandas as pd<br>import numpy as np<br>from huggingface_hub import interpreter_login<br><br>interpreter_login()</pre><p>For this tutorial we are not going to track our training metrics, so let’s disable <strong>Weights and Biases. </strong>The <a href="https://docs.wandb.ai/guides"><strong>W&amp;B</strong></a> Platform constitutes a fundamental collection of robust components for monitoring, visualizing data and models, and conveying the results. To deactivate <strong>Weights and Biases</strong> during the fine-tuning process, set the below environment property.</p><pre>import os<br># disable Weights and Biases<br>os.environ[&#39;WANDB_DISABLED&#39;]=&quot;true&quot;</pre><p>If you have an account with <strong>Weights and Biases</strong>, feel free to enable it and experiment with it.</p><h4>3. Loading dataset</h4><p>Numerous datasets are available for fine-tuning the model. In this instance, we will utilize the <a href="https://huggingface.co/datasets/neil-code/dialogsum-test">DialogSum</a> DataSet from HuggingFace for the fine-tuning process. DialogSum is an extensive dialogue summarization dataset, featuring 13,460 dialogues along with manually labeled summaries and topics.</p><p>There is no specific reason for selecting this dataset. Feel free to try this experiment with any custom dataset.</p><p>Let’s execute the below code to load the above dataset from HuggingFace.</p><pre>huggingface_dataset_name = &quot;neil-code/dialogsum-test&quot;<br>dataset = load_dataset(huggingface_dataset_name)</pre><p>Once the dataset is loaded, we can take a look at it to understand what it contains:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Wm7o0lPbZ0ThEyrKSeSGEQ.png" /><figcaption>a sample row of the dataset</figcaption></figure><p>It contains the below fields.</p><ul><li><strong>dialogue</strong>: text of the dialogue.</li><li><strong>summary</strong>: human-written summary of the dialogue.</li><li><strong>topic</strong>: human written topic/one-liner of the dialogue.</li><li><strong>id</strong>: unique file id of an example.</li></ul><h4>4. Create Bitsandbytes configuration</h4><p>To load the model, we need a configuration class that specifies how we want the quantization to be performed. We’ll be using BitsAndBytesConfig to load our model in 4-bit format. This will reduce memory consumption considerably, at a cost of some accuracy.</p><pre>compute_dtype = getattr(torch, &quot;float16&quot;)<br>bnb_config = BitsAndBytesConfig(<br>        load_in_4bit=True,<br>        bnb_4bit_quant_type=&#39;nf4&#39;,<br>        bnb_4bit_compute_dtype=compute_dtype,<br>        bnb_4bit_use_double_quant=False,<br>    )</pre><h4>5. Loading the Pre-Trained model</h4><p>Microsoft recently open-sourced the <strong>Phi-2</strong>, a Small Language Model(<strong>SLM</strong>) with <strong>2.7 billion</strong> parameters. Here, we will use <strong>Phi-2 </strong>for the fine-tuning process. This language model exhibits remarkable reasoning and language understanding capabilities, achieving state-of-the-art performance among base language models.</p><p>Let’s now load <strong>Phi-2</strong> using 4-bit quantization from HuggingFace.</p><pre>model_name=&#39;microsoft/phi-2&#39;<br>device_map = {&quot;&quot;: 0}<br>original_model = AutoModelForCausalLM.from_pretrained(model_name, <br>                                                      device_map=device_map,<br>                                                      quantization_config=bnb_config,<br>                                                      trust_remote_code=True,<br>                                                      use_auth_token=True)</pre><p>The model is loaded in 4-bit using the `<strong>BitsAndBytesConfig</strong>` from the bitsandbytes library. This is a part of the <strong>QLoRA</strong> process, which involves quantizing the pre-trained weights of the model to 4-bit and keeping them fixed during fine-tuning.</p><h4>6. Tokenization</h4><p>Now, let’s configure the tokenizer, incorporating <a href="https://ai.stackexchange.com/questions/41485/while-fine-tuning-a-decoder-only-llm-like-llama-on-chat-dataset-what-kind-of-pa">left-padding</a> to optimize memory usage during training.</p><pre>tokenizer = AutoTokenizer.from_pretrained(model_name,trust_remote_code=True,padding_side=&quot;left&quot;,add_eos_token=True,add_bos_token=True,use_fast=False)<br>tokenizer.pad_token = tokenizer.eos_token</pre><h4>7. Test the Model with Zero Shot Inferencing</h4><p>We will evaluate the base model that we loaded above using a few sample inputs.</p><pre>%%time<br>from transformers import set_seed<br>seed = 42<br>set_seed(seed)<br><br>index = 10<br><br>prompt = dataset[&#39;test&#39;][index][&#39;dialogue&#39;]<br>summary = dataset[&#39;test&#39;][index][&#39;summary&#39;]<br><br>formatted_prompt = f&quot;Instruct: Summarize the following conversation.\n{prompt}\nOutput:\n&quot;<br>res = gen(original_model,formatted_prompt,100,)<br>#print(res[0])<br>output = res[0].split(&#39;Output:\n&#39;)[1]<br><br>dash_line = &#39;-&#39;.join(&#39;&#39; for x in range(100))<br>print(dash_line)<br>print(f&#39;INPUT PROMPT:\n{formatted_prompt}&#39;)<br>print(dash_line)<br>print(f&#39;BASELINE HUMAN SUMMARY:\n{summary}\n&#39;)<br>print(dash_line)<br>print(f&#39;MODEL GENERATION - ZERO SHOT:\n{output}&#39;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dq-AenXd7A3YAPvoonshBw.png" /><figcaption>base model output</figcaption></figure><p>From the observation above, it’s evident that the model faces challenges in summarizing the dialogue compared to the baseline summary. However, it manages to extract essential information from the text, suggesting the potential for fine-tuning the model for the specific task at hand.</p><h4>8. Pre-processing dataset</h4><p>The dataset cannot be directly employed for fine-tuning. It is essential to format the prompt in a way that the model can comprehend. Referring to the HuggingFace model documentation, it is evident that a prompt needs to be generated using dialogue and summary in the specified format below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JhyiAmJcXtnZa6dWwAUW_Q.png" /><figcaption>Prompt Format</figcaption></figure><p>We’ll create some helper functions to format our input dataset, ensuring its suitability for the fine-tuning process. Here, we need to convert the dialog-summary (prompt-response) pairs into explicit instructions for the LLM.</p><pre>def create_prompt_formats(sample):<br>    &quot;&quot;&quot;<br>    Format various fields of the sample (&#39;instruction&#39;,&#39;output&#39;)<br>    Then concatenate them using two newline characters <br>    :param sample: Sample dictionnary<br>    &quot;&quot;&quot;<br>    INTRO_BLURB = &quot;Below is an instruction that describes a task. Write a response that appropriately completes the request.&quot;<br>    INSTRUCTION_KEY = &quot;### Instruct: Summarize the below conversation.&quot;<br>    RESPONSE_KEY = &quot;### Output:&quot;<br>    END_KEY = &quot;### End&quot;<br>    <br>    blurb = f&quot;\n{INTRO_BLURB}&quot;<br>    instruction = f&quot;{INSTRUCTION_KEY}&quot;<br>    input_context = f&quot;{sample[&#39;dialogue&#39;]}&quot; if sample[&quot;dialogue&quot;] else None<br>    response = f&quot;{RESPONSE_KEY}\n{sample[&#39;summary&#39;]}&quot;<br>    end = f&quot;{END_KEY}&quot;<br>    <br>    parts = [part for part in [blurb, instruction, input_context, response, end] if part]<br><br>    formatted_prompt = &quot;\n\n&quot;.join(parts)<br>    sample[&quot;text&quot;] = formatted_prompt<br><br>    return sample</pre><p>The above function can be used to convert our input into prompt format.</p><p>Now, we will use our model tokenizer to process these prompts into tokenized ones.</p><p>Our aim here is to generate input sequences with consistent lengths, which is beneficial for fine-tuning the language model by optimizing efficiency and minimizing computational overhead. It is essential to ensure that these sequences do not surpass the model’s maximum token limit.</p><pre>from functools import partial<br><br># SOURCE https://github.com/databrickslabs/dolly/blob/master/training/trainer.py<br>def get_max_length(model):<br>    conf = model.config<br>    max_length = None<br>    for length_setting in [&quot;n_positions&quot;, &quot;max_position_embeddings&quot;, &quot;seq_length&quot;]:<br>        max_length = getattr(model.config, length_setting, None)<br>        if max_length:<br>            print(f&quot;Found max lenth: {max_length}&quot;)<br>            break<br>    if not max_length:<br>        max_length = 1024<br>        print(f&quot;Using default max length: {max_length}&quot;)<br>    return max_length<br><br><br>def preprocess_batch(batch, tokenizer, max_length):<br>    &quot;&quot;&quot;<br>    Tokenizing a batch<br>    &quot;&quot;&quot;<br>    return tokenizer(<br>        batch[&quot;text&quot;],<br>        max_length=max_length,<br>        truncation=True,<br>    )<br><br># SOURCE https://github.com/databrickslabs/dolly/blob/master/training/trainer.py<br>def preprocess_dataset(tokenizer: AutoTokenizer, max_length: int,seed, dataset):<br>    &quot;&quot;&quot;Format &amp; tokenize it so it is ready for training<br>    :param tokenizer (AutoTokenizer): Model Tokenizer<br>    :param max_length (int): Maximum number of tokens to emit from tokenizer<br>    &quot;&quot;&quot;<br>    <br>    # Add prompt to each sample<br>    print(&quot;Preprocessing dataset...&quot;)<br>    dataset = dataset.map(create_prompt_formats)#, batched=True)<br>    <br>    # Apply preprocessing to each batch of the dataset &amp; and remove &#39;instruction&#39;, &#39;context&#39;, &#39;response&#39;, &#39;category&#39; fields<br>    _preprocessing_function = partial(preprocess_batch, max_length=max_length, tokenizer=tokenizer)<br>    dataset = dataset.map(<br>        _preprocessing_function,<br>        batched=True,<br>        remove_columns=[&#39;id&#39;, &#39;topic&#39;, &#39;dialogue&#39;, &#39;summary&#39;],<br>    )<br><br>    # Filter out samples that have input_ids exceeding max_length<br>    dataset = dataset.filter(lambda sample: len(sample[&quot;input_ids&quot;]) &lt; max_length)<br>    <br>    # Shuffle dataset<br>    dataset = dataset.shuffle(seed=seed)<br><br>    return dataset</pre><p>By utilizing these functions, our dataset will be prepared for the fine-tuning process!</p><pre>## Pre-process dataset<br>max_length = get_max_length(original_model)<br>print(max_length)<br><br>train_dataset = preprocess_dataset(tokenizer, max_length,seed, dataset[&#39;train&#39;])<br>eval_dataset = preprocess_dataset(tokenizer, max_length,seed, dataset[&#39;validation&#39;])</pre><h4>9. Preparing the model for QLoRA</h4><pre># 2 - Using the prepare_model_for_kbit_training method from PEFT<br># Preparing the Model for QLoRA<br>original_model = prepare_model_for_kbit_training(original_model)</pre><p>Here, the model is prepared for QLoRA training using the `<strong>prepare_model_for_kbit_training()</strong>` function. This function initializes the model for QLoRA by setting up the necessary configurations.</p><h4>10. Setup PEFT for Fine-Tuning</h4><p>Let us now define the LoRA config for Fine-tuning the base model.</p><pre>from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training<br><br>config = LoraConfig(<br>    r=32, #Rank<br>    lora_alpha=32,<br>    target_modules=[<br>        &#39;q_proj&#39;,<br>        &#39;k_proj&#39;,<br>        &#39;v_proj&#39;,<br>        &#39;dense&#39;<br>    ],<br>    bias=&quot;none&quot;,<br>    lora_dropout=0.05,  # Conventional<br>    task_type=&quot;CAUSAL_LM&quot;,<br>)<br><br># 1 - Enabling gradient checkpointing to reduce memory usage during fine-tuning<br>original_model.gradient_checkpointing_enable()<br><br>peft_model = get_peft_model(original_model, config)</pre><p>Note the rank (r) hyper-parameter, which defines the rank/dimension of the adapter to be trained. r is the rank of the low-rank matrix used in the adapters, which thus controls the number of parameters trained. A higher rank will allow for more expressivity, but there is a compute tradeoff.</p><p>alpha here is the scaling factor for the learned weights. The weight matrix is scaled by alpha/r, and thus a higher value for alpha assigns more weight to the LoRA activations.</p><p>Once everything is set up and the PEFT is prepared, we can use the print_trainable_parameters() helper function to see how many trainable parameters are in the model.</p><pre>print(print_number_of_trainable_model_parameters(peft_model))</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/816/1*xjmlX-nm_6aJYCnpMWqjfQ.png" /><figcaption>trainable parameters</figcaption></figure><h4>11. Train PEFT Adapter</h4><p>Define training arguments and create Trainer instance.</p><pre>output_dir = f&#39;./peft-dialogue-summary-training-{str(int(time.time()))}&#39;<br>import transformers<br><br>peft_training_args = TrainingArguments(<br>    output_dir = output_dir,<br>    warmup_steps=1,<br>    per_device_train_batch_size=1,<br>    gradient_accumulation_steps=4,<br>    max_steps=1000,<br>    learning_rate=2e-4,<br>    optim=&quot;paged_adamw_8bit&quot;,<br>    logging_steps=25,<br>    logging_dir=&quot;./logs&quot;,<br>    save_strategy=&quot;steps&quot;,<br>    save_steps=25,<br>    evaluation_strategy=&quot;steps&quot;,<br>    eval_steps=25,<br>    do_eval=True,<br>    gradient_checkpointing=True,<br>    report_to=&quot;none&quot;,<br>    overwrite_output_dir = &#39;True&#39;,<br>    group_by_length=True,<br>)<br><br>peft_model.config.use_cache = False<br><br>peft_trainer = transformers.Trainer(<br>    model=peft_model,<br>    train_dataset=train_dataset,<br>    eval_dataset=eval_dataset,<br>    args=peft_training_args,<br>    data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm=False),<br>)</pre><p>Here, we have used 1000 training steps. It seems to be good enough for our custom dataset. We need to try out different numbers before finalizing with training steps. Also, the hyperparameters used above might vary depending on the dataset/model we are trying to fine-tune. This is just to show the capability of fine-tuning.</p><p>Let’s start the training now. Training the model will take some time depending upon the hyperparameters used in TrainingArguments.</p><pre>peft_trainer.train()</pre><p>Once the model is trained successfully, we can use it for inference. Let’s now prepare the inference model by adding an adapter to the original Phi-2 model. Here, we are setting is_trainable=False because the plan is only to perform inference with this PEFT model.</p><pre>import torch<br>from transformers import AutoTokenizer, AutoModelForCausalLM<br><br>base_model_id = &quot;microsoft/phi-2&quot;<br>base_model = AutoModelForCausalLM.from_pretrained(base_model_id, <br>                                                      device_map=&#39;auto&#39;,<br>                                                      quantization_config=bnb_config,<br>                                                      trust_remote_code=True,<br>                                                      use_auth_token=True)</pre><pre>eval_tokenizer = AutoTokenizer.from_pretrained(base_model_id, add_bos_token=True, trust_remote_code=True, use_fast=False)<br>eval_tokenizer.pad_token = eval_tokenizer.eos_token</pre><pre>from peft import PeftModel<br><br>ft_model = PeftModel.from_pretrained(base_model, &quot;/kaggle/working/peft-dialogue-summary-training-1705417060/checkpoint-1000&quot;,torch_dtype=torch.float16,is_trainable=False)</pre><p>Fine-tuning is often an iterative process. Based on the validation and test sets results, we may need to make further adjustments to the model’s architecture, hyperparameters, or training data to improve its performance. Let’s now see how to evaluate the results of Fine-tuned LLM.</p><h4>12. Evaluate the Model Qualitatively (Human Evaluation)</h4><p>Now, let’s perform inference using the same input but with the PEFT model, as we did previously in step 7 with the original model.</p><pre>%%time<br>from transformers import set_seed<br>set_seed(seed)<br><br>index = 5<br>dialogue = dataset[&#39;test&#39;][index][&#39;dialogue&#39;]<br>summary = dataset[&#39;test&#39;][index][&#39;summary&#39;]<br><br>prompt = f&quot;Instruct: Summarize the following conversation.\n{dialogue}\nOutput:\n&quot;<br><br>peft_model_res = gen(ft_model,prompt,100,)<br>peft_model_output = peft_model_res[0].split(&#39;Output:\n&#39;)[1]<br>#print(peft_model_output)<br>prefix, success, result = peft_model_output.partition(&#39;###&#39;)<br><br>dash_line = &#39;-&#39;.join(&#39;&#39; for x in range(100))<br>print(dash_line)<br>print(f&#39;INPUT PROMPT:\n{prompt}&#39;)<br>print(dash_line)<br>print(f&#39;BASELINE HUMAN SUMMARY:\n{summary}\n&#39;)<br>print(dash_line)<br>print(f&#39;PEFT MODEL:\n{prefix}&#39;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6F8Q6YMpDFh2B53DAae7uQ.png" /><figcaption>PEFT model output</figcaption></figure><h4>13. Evaluate the Model Quantitatively (with ROUGE Metric)</h4><p><strong>ROUGE</strong>, or Recall-Oriented Understudy for Gisting Evaluation, is a set of metrics and a software package used for evaluating automatic summarization and machine translation software in natural language processing. The metrics compare an automatically produced summary or translation against a reference or a set of references (human-produced) summary or translation.</p><p>Let’s now use the <a href="https://en.wikipedia.org/wiki/ROUGE_(metric)"><strong>ROUGE</strong> metric</a> to quantify the validity of summarizations produced by models. It compares summarizations to a “baseline” summary which is usually created by a human. While it’s not a perfect metric, it does indicate the overall increase in summarization effectiveness that we have accomplished by fine-tuning.</p><p>To demonstrate the capability of ROUGE Metric Evaluation we will use some sample inputs to evaluate.</p><pre>original_model = AutoModelForCausalLM.from_pretrained(base_model_id, <br>                                                      device_map=&#39;auto&#39;,<br>                                                      quantization_config=bnb_config,<br>                                                      trust_remote_code=True,<br>                                                      use_auth_token=True)</pre><pre>import pandas as pd<br><br>dialogues = dataset[&#39;test&#39;][0:10][&#39;dialogue&#39;]<br>human_baseline_summaries = dataset[&#39;test&#39;][0:10][&#39;summary&#39;]<br><br>original_model_summaries = []<br>instruct_model_summaries = []<br>peft_model_summaries = []<br><br>for idx, dialogue in enumerate(dialogues):<br>    human_baseline_text_output = human_baseline_summaries[idx]<br>    prompt = f&quot;Instruct: Summarize the following conversation.\n{dialogue}\nOutput:\n&quot;<br>    <br>    original_model_res = gen(original_model,prompt,100,)<br>    original_model_text_output = original_model_res[0].split(&#39;Output:\n&#39;)[1]<br>    <br>    peft_model_res = gen(ft_model,prompt,100,)<br>    peft_model_output = peft_model_res[0].split(&#39;Output:\n&#39;)[1]<br>    print(peft_model_output)<br>    peft_model_text_output, success, result = peft_model_output.partition(&#39;###&#39;)<br><br>    original_model_summaries.append(original_model_text_output)<br>    peft_model_summaries.append(peft_model_text_output)<br><br>zipped_summaries = list(zip(human_baseline_summaries, original_model_summaries, peft_model_summaries))<br> <br>df = pd.DataFrame(zipped_summaries, columns = [&#39;human_baseline_summaries&#39;, &#39;original_model_summaries&#39;, &#39;peft_model_summaries&#39;])<br>df</pre><pre>import evaluate<br><br>rouge = evaluate.load(&#39;rouge&#39;)<br><br>original_model_results = rouge.compute(<br>    predictions=original_model_summaries,<br>    references=human_baseline_summaries[0:len(original_model_summaries)],<br>    use_aggregator=True,<br>    use_stemmer=True,<br>)<br><br>peft_model_results = rouge.compute(<br>    predictions=peft_model_summaries,<br>    references=human_baseline_summaries[0:len(peft_model_summaries)],<br>    use_aggregator=True,<br>    use_stemmer=True,<br>)<br><br>print(&#39;ORIGINAL MODEL:&#39;)<br>print(original_model_results)<br>print(&#39;PEFT MODEL:&#39;)<br>print(peft_model_results)<br><br>print(&quot;Absolute percentage improvement of PEFT MODEL over ORIGINAL MODEL&quot;)<br><br>improvement = (np.array(list(peft_model_results.values())) - np.array(list(original_model_results.values())))<br>for key, value in zip(peft_model_results.keys(), improvement):<br>    print(f&#39;{key}: {value*100:.2f}%&#39;)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wJYClrqJIhbdFDXGpXfnkg.png" /><figcaption>Rouge metric evaluation</figcaption></figure><p>As we can see in the above results, there is a significant improvement in the PEFT model as compared to the original model denoted in terms of percentage.</p><p>If you’d like to access the complete notebook, please refer to the repository below.</p><p><a href="https://www.kaggle.com/code/dassum/finetune-phi-2-on-custom-dataset">FineTune Phi-2 on Custom DataSet</a></p><h4>Conclusion</h4><p>Fine-tuning Large Language Models (LLMs) has become essential for enterprises seeking to optimize their operational processes. While the initial training of LLMs imparts a broad language understanding, the fine-tuning process refines these models into specialized tools capable of handling specific topics and providing more accurate results. Tailoring LLMs for distinct tasks, industries, or datasets extends the capabilities of these models, ensuring their relevance and value in a dynamic digital landscape. Looking ahead, ongoing exploration and innovation in LLMs, coupled with refined fine-tuning methodologies, are poised to advance the development of smarter, more efficient, and contextually aware AI systems.</p><h4>References</h4><ul><li><a href="https://huggingface.co/microsoft/phi-2">microsoft/phi-2 · Hugging Face</a></li><li><a href="https://www.superannotate.com/blog/llm-fine-tuning">Fine-tuning large language models (LLMs) in 2024 | SuperAnnotate</a></li><li><a href="https://huggingface.co/microsoft/phi-2/discussions/19">microsoft/phi-2 · How to fine-tune this? + Training code</a></li><li><a href="https://www.microsoft.com/en-us/research/blog/phi-2-the-surprising-power-of-small-language-models/">Phi-2: The surprising power of small language models</a></li><li><a href="https://ai.stackexchange.com/questions/41485/while-fine-tuning-a-decoder-only-llm-like-llama-on-chat-dataset-what-kind-of-pa">While fine-tuning a decoder only LLM like LLaMA on chat dataset, what kind of padding should one use?</a></li><li><a href="https://huggingface.co/docs/peft/conceptual_guides/lora"></a></li><li><a href="https://huggingface.co/spaces/evaluate-metric/rouge">ROUGE - a Hugging Face Space by evaluate-metric</a></li><li><a href="https://github.com/TimDettmers/bitsandbytes">GitHub - TimDettmers/bitsandbytes: Accessible large language models via k-bit quantization for PyTorch.</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fb60abdeba07" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swlh/fine-tune-large-language-model-llm-on-a-custom-dataset-with-qlora-fb60abdeba07">Fine Tune Large Language Model (LLM) on a Custom Dataset with QLoRA</a> was originally published in <a href="https://medium.com/swlh">The Startup</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Harnessing GenAI to Craft Podcast Episodes]]></title>
            <link>https://medium.com/swlh/harnessing-genai-to-craft-podcast-episodes-62849d689248?source=rss-54106b197f0a------2</link>
            <guid isPermaLink="false">https://medium.com/p/62849d689248</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[Suman Das]]></dc:creator>
            <pubDate>Sat, 26 Aug 2023 08:52:45 GMT</pubDate>
            <atom:updated>2023-08-26T08:52:45.946Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aACn2lw70Lcn4jdtUtSGVA.png" /><figcaption>AI-Generated Podcast</figcaption></figure><p>You must have heard about podcasts. They’re like really cool radio shows that people can listen to on their phones or computers. Lots of people love listening to them because they tell interesting stories, share facts, and make people laugh or learn new things.</p><p>In this article, we will see how to use Gen AI to create our own podcast content in a really amazing way. It’s like adding magical powers to the stories and shows we make!</p><h4>Gen AI (Generative AI):</h4><p>“Gen AI” is a broader term that refers to a class of AI systems designed to generate content autonomously. This content can include text, images, music, videos, and more. These systems leverage generative algorithms and models to create content that often simulates human creativity and style. Gen AI encompasses a wide range of AI models, including language models like LLMs, as well as models specifically designed for generating other types of content like images or music.</p><h4>LLM (Large Language Model):</h4><p>LLMs are a specific subset of Generative AI models that are focused on generating human-like text. These models are trained on massive amounts of text data to understand the patterns, structure, and context of language. LLMs like GPT-3 (Generative Pre-trained Transformer 3) are capable of generating coherent and contextually relevant text based on the input they receive. They can perform tasks such as answering questions, generating stories, providing explanations, and more by predicting the next words or phrases in a given text sequence.</p><p>In summary, “<strong>Gen AI</strong>” is a broader category that includes various AI systems for generating different types of content, while “<strong>LLM</strong>” specifically refers to large language models that generate text-based content.</p><p>Now, let’s explore how we can create the content for an interactive and engaging podcast using AI. You can choose any interesting topic from the internet for your podcast. The upcoming steps will demonstrate how to utilize various AI tools to generate the podcast’s content:</p><ol><li>Setting Up the Notebook</li><li>Choose a Podcast Topic</li><li>Gather Content</li><li>Preprocessing and Data Chunking</li><li>Data Summarization</li><li>Generate the Podcast Script</li><li>Add Voice to the Podcast Script</li><li>Generate Podcast Intro Music</li><li>Bring the Podcast Together</li></ol><h4>1. Setting Up the Notebook</h4><p>We’ll be using a Google Colab notebook for this, but you can use any Jupyter notebook environment.</p><p>First, let’s open a new notebook and create some headings. After that, we’ll connect to the runtime.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ULxnIdyJUqkEs9xXeT-JEw.png" /><figcaption>notebook-with-headings</figcaption></figure><p>For this tutorial, we will be using the OpenAI <a href="https://platform.openai.com/docs/models/gpt-3-5">GPT-3.5</a> model. To use this model, we will need an API key. If you’ve already signed up with OpenAI, you can generate a new key from the profile section or use any existing key.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8-JX2N3PsEr0sbQURmnsqg.png" /><figcaption>view API keys</figcaption></figure><p>If you have not signed in yet then you can use the below URL to sign-in to OpenAI and then get the key from the profile section.</p><p><a href="https://auth0.openai.com/u/signup/identifier?state=hKFo2SBvd3UwOUhOOU54bjMyTTVWRUplaW0xS2FJTUNEUTlRMaFur3VuaXZlcnNhbC1sb2dpbqN0aWTZIFRNeUFyX2FiUVZCWG92aG9GZFlZMmpERjZZVHJZakFfo2NpZNkgRFJpdnNubTJNdTQyVDNLT3BxZHR3QjNOWXZpSFl6d0Q">OpenAI Platform</a></p><p>Copy and save the API key in a secure location. We’ll need to use this key later on.</p><h4>2. Choose a Podcast Topic</h4><p>The initial step in creating a podcast is to choose a topic and gather content that will serve as the backbone of our podcast. In this instance, we’ll utilize <strong>Wikipedia</strong> data to generate the text corpus. However, there are other methods available for generating content for our podcast as well. In our case, we’ll be creating a podcast centered around <strong>Lionel Messi</strong>, the legendary footballer. Given my fondness for Messi, I’m inclined toward this topic, but you’re free to select any other topic of your preference.</p><h4>3. Gather Content</h4><p><strong>Wikipedia</strong> is a great source for gathering information about the history, current events, celebrities and so much more! It’s a good starting point for our text corpus. Using the Wikipedia library, we can get all the details of a certain Wiki page based on the title or the Wiki ID.</p><p>To proceed with our tutorial, we’ll need to install several libraries in the Colab notebook. Let’s begin by installing the Wikipedia library using the command below.</p><pre>!pip install wikipedia</pre><p>Once the library is installed let’s verify if we can extract the content of any page from Wikipedia.</p><pre>import wikipedia<br>print (wikipedia.summary(&quot;Wikipedia&quot;))</pre><p>As we can see below the library is successfully installed and we are able to extract content from Wikipedia.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YDK2VxoIMyFxXLN6eNXzcg.png" /><figcaption>wikipedia-setup</figcaption></figure><p>Moving forward, let’s collect some information about <strong>Lionel Messi</strong> from <strong>Wikipedia</strong>. As previously mentioned, feel free to choose a different topic if you prefer. When we access Lionel Messi’s Wikipedia page, you’ll notice that the Wiki ID appears at the end of the URI. We’ll utilize this ID to fetch the necessary data.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SkHX7sVMOw3BJA1AvZ6AdQ.png" /></figure><pre>input = wikipedia.page(&quot;Lionel_Messi&quot;, auto_suggest=False)<br>wiki_input = input.content<br>print (len(wiki_input))</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fMY2vbmNq9d7LFsXyKusoQ.png" /><figcaption>getting text corpus from Wikipidea</figcaption></figure><p>As shown above, the text corpus is substantial, encompassing 131,764 characters. However, we can’t utilize this data as is. The limitation arises from the context length of Large Language Models (LLMs), which determines the extent of text they can comprehend at once. Each LLM model has its own context length, and they process text within this constraint. So, how can we address this challenge? Let’s explore the solution in the next step.</p><h4>4. Preprocessing and Data Chunking</h4><p>Before training any machine learning model, it’s important to perform data cleaning. Likewise, before providing data as input to an LLM, we should perform both data cleaning and chunking.</p><p>Data chunking is a strategy to work around the Large Language Models (LLMs) context window limitation and ensure that longer texts can be effectively processed by the model. It involves dividing the text into sections that are within the model’s context window size, processing these sections sequentially, and handling any inter-chunk dependencies to maintain the flow of the narrative.</p><p>What are tokens in the context of LLM?</p><p>In the context of Large Language Models (LLMs) like GPT-3, tokens refer to the individual units that text is broken down into. A token can be as short as a single character or as long as a word. For example:</p><ul><li>The word “apple” is one token.</li><li>The phrase “New York” is two tokens, as it consists of two words.</li><li>The sentence “I love ice cream” is four tokens: [“I”, “love”, “ice”, “cream”].</li></ul><p>Large Language Models have limitations on the maximum number of tokens they can process in a single input due to computational constraints. This limitation requires users to be mindful of input length, especially when dealing with lengthy texts.</p><p>Here, we will use tiktoken library from <strong>OpenAI</strong> to overcome the token limitation.</p><p>We will use the below command to install this library.</p><pre>!pip install tiktoken</pre><p>Let’s check the total number of tokens present in our text corpus.</p><pre>import tiktoken<br>enc = tiktoken.encoding_for_model(&quot;gpt-3.5-turbo&quot;)<br>print (&quot;Number of tokens in corpus &quot;, len(enc.encode(wiki_input)))</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uKk5yoo-Shg0jbQ58AwNYw.png" /><figcaption>token count</figcaption></figure><p>As we can see above, the number of tokens is large for our text corpus. So, we will need to do some processing to divide the corpus into batches before sending it to OpenAI. In this tutorial, we’ll be using the <strong>GPT-3.5-turbo</strong> model, which has a maximum token length of 4k. This limit is much lower than the token length of our text corpus.</p><p>Merely splitting the text corpus based on token length isn’t sufficient, as doing so might lead to the loss of sentence meanings when we send the chunk to ChatGPT. Therefore, our approach involves initially segmenting the text corpus into coherent sections and subsequently evaluating the token length by combining these subsections.</p><p>To split our text corpus we will use the library called <a href="https://www.nltk.org/">NLTK</a>. The <strong>sent_tokenize </strong>function from the NLTK library can help us to tokenize the text into logical sentences.</p><p>We’ll create some helper functions to divide our text corpus into smaller chunks making it suitable for processing by the GPT-3.5 model within a single request. These chunks will be grouped into batches, with each chunk limited to a maximum of 2,500 tokens.</p><pre>import nltk<br>nltk.download(&#39;punkt&#39;)<br>from nltk.tokenize import sent_tokenize<br># Return a sentence-tokenized copy of text, using NLTK’s recommended sentence tokenizer (currently PunktSentenceTokenizer for the specified language).<br>def split_text (input_text):<br>  split_texts = sent_tokenize(input_text)<br>  return split_texts<br><br>def create_chunks(split_sents, max_token_len=2500):<br>  current_token_len = 0<br>  input_chunks = []<br>  current_chunk = &quot;&quot;<br>  for sents in split_sents:<br>    sent_token_len = len(enc.encode(sents))<br>    if (current_token_len + sent_token_len) &gt; max_token_len:<br>      input_chunks.append(current_chunk)<br>      current_chunk = &quot;&quot;<br>      current_token_len = 0<br>    current_chunk = current_chunk + sents<br>    current_token_len = current_token_len + sent_token_len<br>  if current_chunk != &quot;&quot;:<br>    input_chunks.append(current_chunk)<br>  return input_chunks</pre><p>Let’s now use the above function to split our text corpus.</p><pre>split_sents = split_text(wiki_input)<br>input_chunks = create_chunks(split_sents, max_token_len=2500)<br>print(f&#39;Batch size: {len(input_chunks)}&#39;)<br># Batch size: 13</pre><p>Now, our total batch size is 13, with approximately 2500 tokens in each batch.</p><h4>5. Data Summarization</h4><p>We’ll now construct a prompt for the LLM, using the input text corpus, to generate a summary. This helps us pinpoint the crucial themes and standout points of the selected topic. This summary will serve as a foundation for creating our podcast script. Remember, a simple summary might be too brief and not provide adequate context for an in-depth podcast discussion. Hence, we need to ensure that the summary contains the most important highlights of the topic that we want to be a part of the podcast conversation.</p><p>Moving forward, let’s install the Openai package and ensure that it works seamlessly with our API key.</p><pre>!pip install openai</pre><pre>import openai<br>from getpass import getpass<br><br>openai.api_key = getpass(&#39;Enter your OPENAI_API_KEY&#39;)</pre><pre># we can confirm that the API key works by listing all the OpenAI models<br># we will be using the gpt-3.5-turbo version for this project<br>models = openai.Model.list()<br>for model in models[&quot;data&quot;]:<br>  print (model[&quot;root&quot;])</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dO-3CJeVPYkk1hs4FlYolw.png" /><figcaption>Test OpenAI connection</figcaption></figure><p>Let’s proceed to instruct ChatGPT to craft a summary from our text corpus. We’ve reached the stage where we’ve segmented our source text into chunks, and now it’s time to collaborate with ChatGPT to generate the podcast’s summary.</p><p>Remember, we’re not seeking a typical summary here — our goal is to create a summary that holds enough content for a captivating podcast discussion. Additionally, it’s important that the information is presented in a structured manner, facilitating easy parsing and reuse for the next step.</p><p>Let’s generate a sample prompt for the topic we’ve chosen. We’ll attempt to create a general prompt that can be adapted for various topics. Don’t hesitate to experiment with different prompts. The prompt provided below is meant to offer some guidance in summarizing the text corpus for our podcast creation.</p><pre>topic = &quot;Sports&quot;<br><br>instructPrompt = f&quot;&quot;&quot;<br>You are a {topic} enthusiast who is doing a research for a podcast. Your task is to extract relevant information from the Result delimited by triple quotes. <br>Please identify 2 interesting questions and answers which can be used for a podcast discussion.<br>The identified discussions should be returned in the following format.<br>- Highlight 1 from the text<br>- Highlight 2 from the text<br>&quot;&quot;&quot;<br><br>requestMessages = []<br>for text in input_chunks:<br>  requestMessage = instructPrompt + f&quot;Result: ```{text}```&quot;<br>  requestMessages.append(requestMessage)</pre><p>Next, we’ll proceed to execute these prompts to summarize our text corpus and gather the results.</p><pre>chatOutputs = []<br>for request in requestMessages:<br>  chatOutput = openai.ChatCompletion.create(model=&quot;gpt-3.5-turbo&quot;,<br>                                            messages=[{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;You are a helpful assistant.&quot;},<br>                                                      {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: request}<br>                                                      ]<br>                                            )<br>  chatOutputs.append(chatOutput)</pre><pre>podcastFacts = &quot;&quot;<br>for chats in chatOutputs:<br>  podcastFacts = podcastFacts + chats.choices[0].message.content</pre><p>We got the below summary.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1T5SYx_cZjd_LCt_oKwTFQ.png" /><figcaption>podcast summary</figcaption></figure><p>The output above should present a list of significant facts, themes, or highlights from our chosen topic. These are the aspects we’d like to address in the podcast conversation.</p><h4>6. Generate the Podcast Script</h4><p>We now possess all the essential facts and highlights that we intend to incorporate into our podcast episode. With this foundation, let’s proceed to create a podcast script using these highlighted points.</p><p>For this purpose, let’s formulate a new prompt that will guide ChatGPT in generating the podcast script using the aforementioned highlights.</p><p>While large-language models exhibit impressive capabilities even with zero-shot prompting, they can encounter challenges with more complex tasks. In such cases, we need to provide thinking time to LLM models. It might be helpful to provide clear instructions with defined steps. It’s a similar way to how we offer stepwise instructions to a child.</p><p>Below, you’ll find the prompt utilized to generate the podcast script. In this prompt, we’ve designated ‘<strong>Tom</strong>’ as the main host of the podcast and ‘<strong>Jerry</strong>’ as the co-host.</p><pre>podcast_name = &quot;Sport 101&quot;<br>podcastPrompt = f&quot;&quot;&quot;<br>You are a writer creating the script for the another episode of a podcast {podcast_name} hosted by \&quot;Tom\&quot; and \&quot;Jerry\&quot;.<br>Use \&quot;Tom\&quot; as the person asking questions and \&quot;Jerry\&quot; as the person providing interesting insights to those questions.<br>Always specify speaker name as  \&quot;Tom\&quot; or \&quot;Jerry\&quot; to identify who is speaking.<br>Make the convesation casual and interesting.<br>Extract relevant information for the podcast conversation from the Result delimited by triple quotes.<br>Use the below format for the podcast conversation.<br>1. Introduction about the topic and welcome everyone for another episode of the podcast {podcast_name}.<br>2. Tom is the main host.<br>2. Introduce both the speakers in brief.<br>3. Then start the conversation.<br>4. Start the conversation with some casual discussion like what they are doing right now at this moment.<br>5. End the conversation with thank you speech to everyone.<br>6. Do not use the word \&quot;conversation\&quot; response.<br><br>&quot;&quot;&quot;<br><br>requestMessage = podcastPrompt + f&quot;Result: ```{podcastFacts}```&quot;</pre><p>Let’s now use this prompt to generate the podcast script using the below command.</p><pre>finalOutput = openai.ChatCompletion.create(model=&quot;gpt-3.5-turbo&quot;,<br>                                            messages=[{&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;You are a helpful assistant.&quot;},<br>                                                      {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: requestMessage}<br>                                                      ],<br>                                           temperature = 0.7<br>                                            )</pre><p>Below is the sample podcast script that we received as a response from ChatGPT.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vTm6cI6m92S9I-ATdrYo4A.png" /><figcaption>generated podcast script</figcaption></figure><h4>7. Add Voice to the Podcast Script</h4><p>Our podcast script is now prepared, and all that’s left is to have human voices read this script. To accomplish this, we will employ <a href="https://elevenlabs.io/speech-synthesis">ElevenLabs</a> for voice generation.</p><p>ElevenLabs leverages advanced AI technology to produce top-notch spoken audio in various voices, styles, and languages.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BHDMAQtzF8385mWfWUeZWA.png" /></figure><p>Initially, we’ll need to sign up for ElevenLabs and generate the API key. Upon registration, we’re granted complimentary credits, which are more than sufficient for generating the audio required for our podcast.</p><p>To sign up, we can use the URL provided below.</p><p><a href="https://elevenlabs.io/">Free AI Voice Generator &amp; Voice Agents Platform | ElevenLabs</a></p><p>Once we have completed the sign-up process, we need to navigate to the profile settings to access the API Key. Make sure to keep this API key secure, as it will be required for future steps.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LHgWgxpNmjVV6wxtBeYzYA.png" /><figcaption>Eleven Labs profile settings</figcaption></figure><p>We can use the below command to install the dependency first.</p><pre>!pip install elevenlabs</pre><p>Let’s set the API Key for ElevenLabs next.</p><pre>from elevenlabs import set_api_key<br><br>set_api_key(getpass(&#39;Enter your ELEVEN_LABS_API_KEY  &#39;))</pre><p>In ElevenLabs, we will find a variety of speakers listed in their demo dropdown. They offer a playground feature where we can select different speakers to listen to their voices. Simply choose a speaker and click the play button to hear their voice. For our interactive podcast, we’ll need to select two speakers.</p><p>After finalizing our choice of speakers, make a note of their names. We’ll need this information for the next steps.</p><p>To facilitate voice generation, we’ll create a helper method. This method assumes that each line of the input text is spoken by a distinct person, and the person’s name is included within the text. It’s important to ensure that the output of our podcast script adheres to this style. If needed, feel free to modify the method accordingly.</p><pre>from elevenlabs import generate<br><br>def createPodcast(podcastScript, speakerName1, speakerChoice1, speakerName2, speakerChoice2):<br>  genPodcast = []<br>  podcastLines = podcastScript.split(&#39;\n\n&#39;)<br>  podcastLineNumber = 0<br>  for line in podcastLines:<br>    if podcastLineNumber % 2 == 0:<br>      speakerChoice = speakerChoice1<br>      line = line.replace(speakerName1+&quot;:&quot;, &#39;&#39;)<br>    else:<br>      speakerChoice = speakerChoice2<br>      line = line.replace(speakerName2+&quot;:&quot;, &#39;&#39;)<br>    genVoice = generate(text=line, voice=speakerChoice, model=&quot;eleven_monolingual_v1&quot;)<br>    genPodcast.append(genVoice)<br>    podcastLineNumber += 1<br>  return genPodcast</pre><pre>speakerName1 = &quot;Tom&quot;<br>speakerChoice1 = &quot;Adam&quot;<br>speakerName2 = &quot;Jerry&quot;<br>speakerChoice2 = &quot;Domi&quot;<br>genPodcast = createPodcast(podcastScript, speakerName1, speakerChoice1, speakerName2, speakerChoice2)</pre><p>When creating the podcast script, we designated ‘Tom’ as the main host and ‘Jerry’ as the cohost. During the voice generation process, we’ll be using the ‘Adam’ voice for ‘Tom’ and the ‘Domi’ voice for ‘Jerry’.</p><p>We can use the below method to save our podcast script.</p><pre>with open(&quot;/content/sample_data/genPodcast.mpeg&quot;, &quot;wb&quot;) as f:<br>  for pod in genPodcast:<br>    f.write(pod)</pre><p>Finally, our podcast audio file is generated and saved as ‘genPodcast.mpeg’.</p><h4>8. Generate Podcast Intro Music</h4><p>To give our podcast a professional touch, we can incorporate an introductory music segment. To generate this introduction music, we’ll utilize the <a href="https://huggingface.co/spaces/facebook/MusicGen">MusicGen</a> model, available on HuggingFace.</p><p>MusicGen is a text-to-music model with the ability to create high-quality music samples based on text descriptions or audio prompts.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D-HdniLbolie_My9nJ4wLQ.png" /><figcaption>MusicGen from HuggingFace</figcaption></figure><p>Here’s a sample prompt that we can use to generate the introduction music. Don’t hesitate to experiment with different prompts.</p><pre>Create a musical composition that evokes a sense of tranquility and relaxation. Incorporate gentle melodies, soft harmonies, and gradual transitions to craft a soothing and smoothening music piece.</pre><p>Once the audio is generated, download it and import it to the colab notebook.</p><h4>9. Bring the Podcast Together</h4><p>At this point, we possess both the podcast audio file and the introduction music file. The next step involves merging these two files to create the complete, final podcast.</p><p>To combine these audio files we will use <a href="https://github.com/jiaaro/pydub"><strong>pydub</strong></a> package. It is versatile and allows us to load audio from various formats — MP3 and WAV files in our case and combine them in ways that we like.</p><p>We can use the below command to install this dependency.</p><pre>!pip install pydub</pre><p>With the dependency now installed, let’s proceed to merge the audio files using the <strong>pydub</strong> library. You can utilize the following code to combine the audio files and create the final podcast.</p><pre>from pydub import AudioSegment<br><br># Import your respective saved Audio files<br>music_intro = AudioSegment.from_file(&quot;/content/sample_data/intro_music.mp4&quot;,&quot;mp4&quot;)<br>podcast_content = AudioSegment.from_file(&quot;/content/sample_data/genPodcast.mpeg&quot;)<br><br>final_version = music_intro.append(podcast_content, crossfade=1500)<br>#Save your final audio file<br>final_version.export(&quot;/content/finalPodcast.mp3&quot;, format=&quot;mp3&quot;)</pre><p>Our podcast is now ready. An optional step we can take is to add an image to our final podcast audio script.</p><p>Here is our podcast episode which we generated using Gen AI.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FpnGz6mbjOPo%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DpnGz6mbjOPo&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FpnGz6mbjOPo%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/58334b49ab3a51198270d8a12f6e8f66/href">https://medium.com/media/58334b49ab3a51198270d8a12f6e8f66/href</a></iframe><p>If you’d like to access the complete notebook, please refer to the repository provided below.</p><p><a href="https://github.com/sumanentc/Machine-Learning-with-Python/tree/main/GenerativeAI/Generate-Podcast">Machine-Learning-with-Python/GenerativeAI/Generate-Podcast at main · sumanentc/Machine-Learning-with-Python</a></p><h4>References</h4><ul><li><a href="https://podcast.ai/">podcast.ai</a></li><li><a href="https://en.wikipedia.org/wiki/Lionel_Messi">Lionel Messi - Wikipedia</a></li><li><a href="https://github.com/openai/tiktoken">GitHub - openai/tiktoken: tiktoken is a fast BPE tokeniser for use with OpenAI&#39;s models.</a></li><li><a href="https://platform.openai.com/docs/models/gpt-3-5">OpenAI Platform</a></li><li><a href="https://www.promptingguide.ai/techniques/fewshot">Few-Shot Prompting | Prompt Engineering Guide</a></li><li><a href="https://docs.elevenlabs.io/api-reference/quick-start/introduction">Introduction - ElevenLabs</a></li><li><a href="https://api.elevenlabs.io/docs">ElevenLabs API Documentation</a></li><li><a href="https://huggingface.co/spaces/facebook/MusicGen">MusicGen - a Hugging Face Space by facebook</a></li><li><a href="https://github.com/jiaaro/pydub">GitHub - jiaaro/pydub: Manipulate audio with a simple and easy high level interface</a></li><li><a href="https://www.nltk.org/">NLTK</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=62849d689248" width="1" height="1" alt=""><hr><p><a href="https://medium.com/swlh/harnessing-genai-to-craft-podcast-episodes-62849d689248">Harnessing GenAI to Craft Podcast Episodes</a> was originally published in <a href="https://medium.com/swlh">The Startup</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Auto Scaling Microservices with Kubernetes Event-Driven Autoscaler (KEDA)]]></title>
            <link>https://medium.com/cuddle-ai/auto-scaling-microservices-with-kubernetes-event-driven-autoscaler-keda-8db6c301b18?source=rss-54106b197f0a------2</link>
            <guid isPermaLink="false">https://medium.com/p/8db6c301b18</guid>
            <category><![CDATA[autoscaling]]></category>
            <category><![CDATA[cuddlepe]]></category>
            <category><![CDATA[cuddleengineering]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[cloud-computing]]></category>
            <dc:creator><![CDATA[Suman Das]]></dc:creator>
            <pubDate>Sun, 26 Feb 2023 04:43:30 GMT</pubDate>
            <atom:updated>2023-02-27T05:29:13.157Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jVHESsjaA45xZf8IJKITVA.png" /></figure><p><strong>Autoscaling</strong> (sometimes spelled as auto scaling or auto-scaling) is the process of automatically increasing or decreasing the usage of computational resources required for a cloud workload based on the application&#39;s need. Scalability is one of the most important aspects to consider for modern container based application deployments. Autoscaling has become an integral component in almost all the cloud platforms, microservices <em>aka</em> containers/pods are not an exemption to this. Microservices in fact are known for flexible and decoupled design, which can best fit in for auto-scaling as they are much easier to create on-demand than virtual machines.</p><p>The primary benefit of autoscaling, when configured and managed properly, is that the workload gets exactly the cloud computational resources it requires at any given point in time. We pay only for the server resources we need at that point in time.</p><p>Since Kubernetes has become the de facto standard for container orchestration and has been adopted very well across all industries, we need to think about how we can scale these applications deployed on Kubernetes on demand and also to scale it down when not in use. Since most of the cloud providers support <a href="https://learn.microsoft.com/en-us/azure/aks/cluster-autoscaler"><strong>Cluster Autoscaler</strong></a>, cluster autoscaler along with container autoscaling enabled becomes a powerful combination to scale any applications with minimal cloud cost.</p><h4>Cluster Autoscaler</h4><p>To adjust to changing application demands, such as between the workday and evening or on a weekend, clusters often need a way to automatically scale. Most of the Cloud providers like Azure, AWS, and GCP support the given Kubernetes Autoscalers. Cluster AutoScaler is nothing but the capability provided by Cloud providers to add/remove computing resources (machines -&gt; VM) at runtime. In general Kubernetes clusters scale in and scale out in one of two ways:</p><ul><li>The Cluster Autoscaler watches for pods that can’t be scheduled on nodes because of resource constraints. The cluster then automatically increases the number of nodes. In case there are no pods running on any nodes then it will remove that node automatically.</li><li>The Horizontal Pod Autoscaler uses the Metrics Server in a Kubernetes cluster to monitor the resource demand of pods. If an application needs more resources, the number of pods is automatically increased to meet the demand.</li></ul><p>Here, we will not demonstrate the use of the Cluster Autoscaler feature for Node AutoScaling. But in real-world scenarios, we can enable Cluster AutoScaler along with Pod AutoScaling to save on cloud costs. Using <strong>KEDA</strong>, we can remove the pods when not required, and then the Cluster Autoscaler will remove the node on which no pods are scheduled. This will help us to reduce cloud costs.</p><h4>What is KEDA?</h4><p><strong>KEDA</strong> is a <a href="https://kubernetes.io/">Kubernetes</a>-based Event Driven Autoscaler. With KEDA, we can drive the scaling of any container in Kubernetes based on the number of events needing to be processed. KEDA handles the triggers to respond to events that occur in other services and scales workloads as needed.</p><p><strong>KEDA</strong> is a single-purpose and lightweight component that can be added to any Kubernetes cluster. KEDA works alongside standard Kubernetes components like the <a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/">Horizontal Pod Autoscaler</a> and can extend functionality without overwriting or duplication. With KEDA we can explicitly map the apps we want to scale, with other apps continuing to function. This makes KEDA a flexible and safe option to run alongside any Kubernetes applications or frameworks.</p><h4>Scaler</h4><p><strong>KEDA</strong> has a wide range of <a href="https://keda.sh/scalers"><strong>scalers</strong></a> that can detect if a deployment should be activated or deactivated, and feed custom metrics for a specific event source.</p><h4>ScaledObject</h4><p>ScaledObject is deployed as a Kubernetes CRD (Custom Resource Definition) which is used to define how <strong>KEDA</strong> should scale our application and what triggers to use.</p><p>In this tutorial, we will see how we can leverage <strong>KEDA</strong> to autoscale Microservices deployed as containers or pods in the Kubernetes cluster, based on the below events.</p><ol><li>External event or trigger.</li><li>HTTP event</li></ol><p>Although the example that we will use here is very trivial, it is just for pure demonstration purposes only. Here, we will explore some of the <a href="https://keda.sh/docs/2.9/scalers/"><strong>scalers</strong></a> supported by KEDA.</p><p>We will follow the below steps to illustrate, how we can leverage KEDA for auto-scaling pods deployed in the Kubernetes cluster.</p><ol><li>Installing KEDA</li><li>Deploying sample application</li><li>Deploying KEDA Event Scaler</li><li>Testing Auto-Scaling with Event Scaler</li><li>Deploying HTTP Scaler</li><li>Testing Auto-Scaling with HTTP Scaler</li></ol><p><strong>Prerequisites</strong></p><ul><li><strong>Kubernetes cluster:</strong> We will need a running Kubernetes cluster. In case Kubernetes Cluster is not available then we can follow the instructions to <a href="https://kubernetes.io/docs/setup/"><em>Set Up a Kubernetes Cluster</em></a><em>.</em></li><li><strong>Docker Hub Account:</strong> An account at Docker Hub for storing Docker images that we will create during this tutorial. Refer to the <a href="https://hub.docker.com/">Docker Hub</a> page for details about creating a new account.</li><li><strong>kubectl:</strong> Refer to <a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/">Install and Set Up kubectl</a> page for details about installing kubectl.</li><li><strong>Helm</strong>: Here we will use Helm (<a href="https://helm.sh/docs/intro/install/">Helm installation guide</a>) to deploy KEDA.</li></ul><p>Let’s begin.</p><h4>1. Installing KEDA</h4><p>Below are the various options which can be used to install KEDA on Kubernetes Cluster.</p><ul><li><a href="https://keda.sh/docs/2.9/deploy/#helm">Helm charts</a></li><li><a href="https://keda.sh/docs/2.9/deploy/#operatorhub">Operator Hub</a></li><li><a href="https://keda.sh/docs/2.9/deploy/#yaml">YAML declarations</a></li></ul><p>Here, we will use Helm Chart to deploy KEDA.</p><p><strong>1.1 Add Keda Core Repo to Helm:</strong> First, we will add the kedacore repository to our Helm instance by executing the below command.</p><pre>helm repo add kedacore https://kedacore.github.io/charts</pre><p><strong>1.2 Update Helm repo:</strong> Then, let’s fetch the information on the new kedacore repo that we added above:</p><pre>helm repo update</pre><p><strong>1.3 Install </strong><strong>keda Helm chart: </strong>Finally, we need to create a namespace for KEDA and install kedacore using Helm chart:</p><pre>kubectl create namespace keda<br>helm install keda kedacore/keda --namespace keda</pre><p>Once the installation is successful, let’s verify whether the <strong>keda</strong> pods are up and running by executing the below command.</p><pre>kubectl get deploy,crd -n keda</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dFaXEEYfre5XVfpJkjE0Cg.png" /><figcaption>keda pods status</figcaption></figure><p>As we can see above in the image the keda pods are up and running.</p><h4>2. Deploying Sample Application</h4><p>It’s time to deploy a demo application. Here, we will use the official latest nginx image at Kubernetes. To deploy the sample application, we will execute the below commands. It creates a ReplicaSet to bring up nginx Pods.</p><p><strong>2.1 Creating a Deployment</strong></p><p>Deployment provides declarative updates for <a href="https://kubernetes.io/docs/concepts/workloads/pods/">Pods</a> and <a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/">ReplicaSets</a>. In Kubernetes, a deployment is a method of launching a pod with containerized applications and ensuring that the necessary number of replicas is always running on the cluster. Let’s create a file nginx-deployment.yaml with the below content for our sample application. The file contains the deployment details for our sample application.</p><pre>apiVersion: apps/v1<br>kind: Deployment<br>metadata:<br>  name: nginx<br>  labels:<br>    app: nginx<br>spec:<br>  replicas: 1<br>  selector:<br>    matchLabels:<br>      app: nginx<br>  template:<br>    metadata:<br>      labels:<br>        app: nginx<br>    spec:<br>      containers:<br>      - name: nginx<br>        image: nginx:1.14.2<br>        ports:<br>        - containerPort: 80 </pre><p>Now we will create the Deployment using the above file.</p><pre>kubectl apply -f nginx-deployment.yaml</pre><p>Let’s check if the deployment was created successfully by executing the below command.</p><pre>kubectl get deployments</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/950/1*qHb8eO5U3OW6iUKIGtbk-A.png" /><figcaption>deployment status</figcaption></figure><p>As we can see above in the image our deployment is deployed successfully with one ReplicaSet.</p><p>Let’s also check the status of the pod using the below command. Its status should be Running.</p><pre>kubectl get pods</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PqcHT8N9VhOkNIi5kRBHJA.png" /><figcaption>pod status</figcaption></figure><p>We can see that 1 instance of our pod is running that we have specified in our deployment configuration.</p><p><strong>2.4 Exposing the pods</strong></p><p>Now we will be exposing our pods so that they can be accessed on the browser. A NodePort service is the most primitive way to get external traffic directly to our service. NodePort, as the name implies, opens a specific port on all the Nodes (the VMs), and any traffic that is sent to this port is forwarded to the service. We will use the below command to create the NodePort service.</p><pre>kubectl expose deployment nginx --port=80 --type=NodePort</pre><p>Once the above command runs successfully, we should be able to list out the services.</p><pre>kubectl get svc</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W2m-yZlBEJtCYYVCvtrYrQ.png" /><figcaption>node port service</figcaption></figure><p>Now we should be able to access the sample application page using the below command:</p><pre>curl http://localhost:32511</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GkifUy9wEB1Wypa5o7GF4Q.png" /><figcaption>sample application output</figcaption></figure><p>As we can see in the above image we are getting a valid response from our deployed application.</p><p>We now have our Kubernetes Cluster up and running with our sample application deployed. We have also deployed <strong>KEDA</strong> related configurations. Let’s now see how we can scale our application using KEDA.</p><h4>3. Deploying KEDA Event Scaler</h4><p>KEDA integrates with multiple <a href="https://keda.sh/docs/2.9/scalers/"><strong>Scalers</strong></a> (event sources) and uses Custom Resources (CRDs) to define the required/desired scaling behavior and parameters. Deployments and StatefulSets are the most common way to scale workloads with KEDA. It allows us to define the Kubernetes Deployment or StatefulSet that we want KEDA to scale based on a scale trigger. KEDA will monitor that service and based on the events that occur it will automatically scale our resource out/in accordingly. Behind the scenes, KEDA monitors the event source and feeds that data to Kubernetes and the HPA (Horizontal Pod Autoscaler) to drive the rapid scale of a resource.</p><p><strong>3.1 Scaler</strong></p><p>KEDA uses a <strong>Scaler</strong> to detect if a deployment should be activated or deactivated (scaling) which in turn is fed into a specific event source. Here we will use <a href="https://keda.sh/docs/2.9/scalers/cron/"><strong>Cron</strong></a> event scaler to demonstrate Auto Scaling. We will scale out our application for some duration and then later scale it back to normal count. Cron scaler is especially useful when there is a known pattern in the application usage like it’s used during weekdays and not used during weekends.</p><p><strong>3.2 ScaledObject</strong></p><p>ScaledObject is deployed as a Kubernetes CRD (Custom Resource Definition) which defines the relationship between an event source to a specific workload (i.e. Deployment, StatefulSet) for scaling.</p><p>Let’s create a file with the name scaledobjects.yaml with the below content. The file contains the event details that KEDA will monitor to scale our sample application.</p><pre>apiVersion: keda.sh/v1alpha1<br>kind: ScaledObject<br>metadata:<br>  name: nginx-deployment<br>  namespace: default<br>spec:<br>  scaleTargetRef:<br>    apiVersion:    apps/v1        # Optional. Default: apps/v1<br>    kind:          Deployment     # Optional. Default: Deployment<br>    name:          nginx      # Mandatory. Must be in the same namespace as the ScaledObject<br>  pollingInterval:  5                    # Optional. Default: 5 seconds<br>  cooldownPeriod:   300                  # Optional. Default: 300 seconds<br>  minReplicaCount:  0                   # Optional. Default: 0<br>  maxReplicaCount:  5                    # Optional. Default: 100<br>  fallback:                                        # Optional. Section to specify fallback options<br>    failureThreshold: 3                            # Mandatory if fallback section is included<br>    replicas: 1                                    # Mandatory if fallback section is included<br>  advanced:                                        # Optional. Section to specify advanced options<br>    restoreToOriginalReplicaCount: true     # Optional. Default: false<br>    horizontalPodAutoscalerConfig:          # Optional. Section to specify HPA related options<br>      name: keda-hpa-nginx       # Optional. Default: keda-hpa-{scaled-object-name}<br>      behavior:                             # Optional. Use to modify HPA&#39;s scaling behavior<br>        scaleDown:<br>          stabilizationWindowSeconds: 600<br>          policies:<br>            - type: Percent<br>              value: 100<br>              periodSeconds: 15<br>  triggers:<br>  - type: cron<br>    metadata:<br>      # Required<br>      timezone: Asia/Kolkata  # The acceptable values would be a value from the IANA Time Zone Database.<br>      start: 30 * * * *       # Every hour on the 30th minute<br>      end: 45 * * * *         # Every hour on the 45th minute<br>      desiredReplicas: &quot;5&quot;</pre><p>Here we have used Cron scaler as a trigger event. The above ScaledObject Custom Resource is used to define how KEDA will scale our application and what the triggers are. The minimum replica count is set to 0 and the maximum replica count is 5.</p><p>Let’s create the ScaledObject using the below command.</p><pre>kubectl apply -f scaledobjects.yaml</pre><p>As we can see, it’s all pretty straightforward. The most important thing is the triggers definition, which will configure what kind of event our application will be scaled in response to.</p><p>Cron trigger contains the below parameters.</p><ul><li>timezone - One of the acceptable values from the IANA Time Zone Database. The list of timezones can be found <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">here</a>.</li><li>start - Cron expression indicating the start of the Cron schedule.</li><li>end - Cron expression indicating the end of the Cron schedule.</li><li>desiredReplicas - Number of replicas to which the resource has to be scaled <strong>between the start and end</strong> of the Cron schedule.</li></ul><p>Once the ScaledObject is created, the KEDA controller automatically syncs the configuration and starts watching the deployment nginx created above. KEDA seamlessly creates a HPA (Horizontal Pod Autoscaler) object with the required configuration and scales out the replicas based on the trigger-rule provided through ScaledObject (in this case it is Cron expression).</p><h4>4. Testing Auto-Scaling with Event Scaler</h4><p>Once the ScaledObject is created for an application with a relevant trigger, KEDA will start monitoring our application. We can check the status of ScaledObjects using the below command.</p><pre>kubectl get scaledobjects</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VZdz8AAZ84YH_WuJHFxUbw.png" /><figcaption>scaledObject</figcaption></figure><p>As we can see above in the image our ScaledObject is deployed successfully with a minimum replica count of 0 and a maximum replica count of 5 which we have specified in our ScaledObject definition. Once the event is triggered then KEDA will scale out our application automatically. Let’s check the KEDA logs to monitor the events.</p><p>We can use the below command to list the KEDA pods.</p><pre>kubectl get pods -n keda</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lDgVX_iOUMPUtYYe0dCOuQ.png" /><figcaption>keda pods</figcaption></figure><p>We can then monitor the logs of keda-operator pod for events.</p><pre>kubectl logs -f keda-operator-6c99649b58-r5qtl -n keda --tail=100</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oaUmh6TjGsWnQE7GjXGlAQ.png" /><figcaption>keda-operator logs</figcaption></figure><p>The above image shows the scale-out event was triggered. Once the scale-out event is triggered, our application will be scaled to run 5 instances and once the scale-in event is triggered it will be scaled back to 0.</p><p>Let’s check the count of our application instances after the scale-out event is triggered:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l26Qw4Tx9ClsilfpppidDg.png" /><figcaption>after scale-out is triggered</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/982/1*mp8mtMdVRVknec2F5crhEg.png" /><figcaption>after scale-out is triggered</figcaption></figure><p>The below image displays our application deployment status after the scale-in event is triggered:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1014/1*OxCAVrNS4MqLSEZRs798IQ.png" /><figcaption>after scale-in is triggered</figcaption></figure><h4>5. Deploying HTTP Scaler</h4><p>We just saw above, how we can scale our application based on events. Now let’s say we have some application where we cannot generate events, it supports only HTTP traffic. Then how do we scale those applications?</p><p>Let’s see that now.</p><p>Currently, there are 2 ways by which we can scale our application based on HTTP Traffic using KEDA.</p><ol><li>Using <a href="https://keda.sh/scalers/prometheus/">Prometheus scaler</a> to create scale rules based on metrics around HTTP events.</li><li>Using KEDA HTTP Add-on.</li></ol><p>Here we will explore <strong>KEDA HTTP Add-on.</strong></p><p><strong>5.1 Install HTTP add-on</strong></p><p>The KEDA HTTP Add-on allows Kubernetes users to automatically scale their application up and down (including to/from zero) based on incoming HTTP traffic.</p><p>KEDA doesn’t come with an HTTP scaler by default, so we will have to install it separately. We need to execute the below command to install HTTP Add-on on our Kubernetes Cluster.</p><pre>helm install http-add-on kedacore/keda-add-ons-http --namespace keda</pre><p><strong>5.2 Defining an Autoscaling Strategy for HTTP Traffic</strong></p><p>Now we have KEDA installed along with HTTP Add-on. To keep things simple we will delete the above deployed ScaledObject. We don’t have to do this in production since we will be using different scaling strategies for different applications. Since we are using the same application for demonstration purposes, so we are deleting the above deployed ScaledObject.</p><p>To delete the ScaledObject deployed earlier :</p><p>Get the deployed ScaledObject name using the below command.</p><pre>kubectl get scaledobject</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*J5GERdG-DvJlLrEurRJ9Jg.png" /><figcaption>scaledObject list</figcaption></figure><p>Then delete the ScaledObject using the below command.</p><pre>kubectl delete scaledobject nginx-deployment</pre><p>Let’s now start defining the new ScaledObject for HTTP Trigger. The KEDA HTTP add-on exposes a CRD where we can describe how our application should be scaled. Let’s create a new file with the name “scaledobjects-http.yaml” with the below contents.</p><pre>kind: HTTPScaledObject<br>apiVersion: http.keda.sh/v1alpha1<br>metadata:<br>    name: nginx-http-scaledobject<br>spec:<br>    host: myhost.com<br>    targetPendingRequests: 1<br>    scaleTargetRef:<br>        deployment: nginx<br>        service: nginx<br>        port: 80<br>    replicas:<br>        min: 0<br>        max: 3</pre><p>The above code defines a ScaledObject of type HTTPScaledObject. It will listen to the requests which come from <strong>myhost.com</strong>. The minimum replica count is set to 0 and the maximum replica count is 3.</p><p>Let’s now deploy the above ScaledObject using the below command.</p><pre>kubectl apply -f scaledobjects-http.yaml</pre><p>When we deploy the above ScaledObject, our application will be scaled down to 0. This is because we have set the minimum replica count to 0.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/976/1*qc55_Nmdx2DBpTHN2-uW-w.png" /><figcaption>deployment status</figcaption></figure><p>So now, our application is not running. This is to simulate the scenario when our application is not in use. So what happens when someone makes a request to our application? Let’s see that next.</p><h4>6. Testing Auto-Scaling with HTTP Scaler</h4><p>A Kubernetes Service called keda-add-ons-http-interceptor-proxy was created automatically when we installed the Helm chart for HTTP-add-on.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gPKBEKKJjgpvMOPACbH0iQ.png" /><figcaption>keda services</figcaption></figure><p>For autoscaling to work appropriately, the HTTP traffic must route through the above service first. We can use kubectl port-forward to test this quickly in our local setup.</p><pre>kubectl port-forward svc/keda-add-ons-http-interceptor-proxy -n keda 32511:8080</pre><p>Here, connections made to local port 32511 are forwarded to port 8080 of the keda-add-ons-http-interceptor-proxy service that is running in Kubernetes Cluster.</p><p>Let’s now make some requests to our application pretending the request comes from <strong>myhost.com</strong>.</p><pre>curl localhost:32511 -H &#39;Host: myhost.com&#39;</pre><p>If we inspect the pods, we will notice that the deployment was scaled to a single replica. So when we routed the traffic to the KEDA’s service, the interceptor keeps track of the number of pending HTTP requests that haven’t had a reply yet.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XnTergctVQCoaJLdTunyTw.png" /><figcaption>pod status</figcaption></figure><p>The KEDA scaler periodically checks the size of the queue of the interceptor and stores the metrics. The KEDA controller monitors the metrics and increases or decreases the number of replicas as needed. In this case, a single request was pending so the KEDA controller scaled the deployment to a single replica.</p><p>As mentioned above, the Service that the add-on creates will be inaccessible over the network from outside of the Kubernetes cluster.</p><p>While we can access it via the kubectl port-forward command above, it is not the recommended way of using it in a production setting. Instead, we should use an <a href="https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/"><strong>ingress controller</strong></a> to route traffic to the Keda interceptor service.</p><h4>Conclusion</h4><p>In this article, we learned about KEDA and its concepts. We also saw it in action through scaling a sample NGINX application in our Kubernetes cluster. As we can see if we combine Kubernetes pod auto-scaling along with Cluster Autoscaler, then not only can we scale our application on demand but also with minimal cloud cost.</p><h4>References</h4><p><a href="https://keda.sh/">KEDA</a></p><p><a href="https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-autoscaler">About cluster autoscaling | Google Kubernetes Engine (GKE) | Google Cloud</a></p><ul><li><a href="https://kubernetes.io/docs/concepts/overview/">Overview</a></li><li><a href="https://learn.microsoft.com/en-us/azure/aks/cluster-autoscaler">Use the cluster autoscaler in Azure Kubernetes Service (AKS) - Azure Kubernetes Service</a></li><li><a href="https://keda.sh/docs/2.9/concepts/#architecture">KEDA | KEDA Concepts</a></li><li><a href="https://keda.sh/docs/2.9/deploy/">KEDA | Deploying KEDA</a></li><li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/">Deployments</a></li><li><a href="https://keda.sh/blog/2021-06-24-announcing-http-add-on/">KEDA | Announcing KEDA HTTP Add-on v0.1.0</a></li><li><a href="https://keda.sh/docs/2.9/scalers/prometheus/">KEDA | Prometheus</a></li><li><a href="https://github.com/kedacore/http-add-on/blob/main/docs/walkthrough.md">http-add-on/walkthrough.md at main · kedacore/http-add-on</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8db6c301b18" width="1" height="1" alt=""><hr><p><a href="https://medium.com/cuddle-ai/auto-scaling-microservices-with-kubernetes-event-driven-autoscaler-keda-8db6c301b18">Auto Scaling Microservices with Kubernetes Event-Driven Autoscaler (KEDA)</a> was originally published in <a href="https://medium.com/cuddle-ai">Crux Intelligence</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Testing Spring Boot Application using WireMock and JUnit 5]]></title>
            <link>https://medium.com/cuddle-ai/testing-spring-boot-application-using-wiremock-and-junit-5-d514a47ab931?source=rss-54106b197f0a------2</link>
            <guid isPermaLink="false">https://medium.com/p/d514a47ab931</guid>
            <category><![CDATA[spring]]></category>
            <category><![CDATA[cuddleengineering]]></category>
            <category><![CDATA[cuddlepe]]></category>
            <category><![CDATA[integration-testing]]></category>
            <category><![CDATA[spring-boot]]></category>
            <dc:creator><![CDATA[Suman Das]]></dc:creator>
            <pubDate>Tue, 26 Jul 2022 15:20:55 GMT</pubDate>
            <atom:updated>2022-07-28T13:18:01.354Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8AvtHu8XsoRHv9a7-3j1tA.png" /><figcaption>Spring Boot with WireMock</figcaption></figure><p>In this world of Microservices, we want to build and ship things faster. When we ship our code, we want to make sure we ship well tested quality code. Hence, testing our code becomes very important. Since testing our code is so important, today we have many tools to help us write efficient tests.</p><p>In this article, we will discuss one such tool named <a href="https://wiremock.org/docs/"><strong>Wiremock</strong></a><strong> </strong>along with<strong> </strong><a href="https://junit.org/junit5/docs/current/user-guide/"><strong>Junit 5</strong></a>,<strong> </strong>that we can use to simulate other services on which our app depends during integration testing of the <strong>Spring Boot</strong> application.</p><p>Let us start with WireMock.</p><h4>What is WireMock?</h4><p><strong>WireMock</strong> is a library for stubbing and mocking web services. It constructs an HTTP server that acts as an actual web service. In simple terms, it is a mock server that is highly configurable and has the ability to return recorded HTTP responses for requests matching criteria.</p><p><strong>WireMock</strong> has a feature called Record and Playback. It can create stub mappings automatically from requests it has received. Combined with its proxying feature this allows us to “record” stub mappings from interaction with existing APIs. When in action, we have the option to either call the external API and capture the response or use one of the earlier recorded stubs to reply to the caller without calling the external API.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/920/1*HnWxZqBy1Dvi-_LYZvBdMA.png" /><figcaption>WireMock Flow</figcaption></figure><h4><strong>Why do we need WireMock?</strong></h4><p>Suppose our SpringBoot Application calls external applications and this call to external services bears some cost. However, we can’t test the application without calling the external services. In this case, we can use WireMock to mock the external applications while testing the REST API that we are developing in SpringBoot.</p><p><strong>WireMock</strong> can be used as a library or as a standalone process. In this tutorial, we will use it as a library. Later, we will see how we can use <strong>WireMock</strong> to mock the external applications while testing the REST API that we are developing using SpringBoot.</p><h4>What is JUnit 5?</h4><p><strong>JUnit</strong> is a unit testing framework for the Java programming language. JUnit has been important in the development of test-driven development and is one of a family of unit testing frameworks, which is collectively known as xUnit that originated with SUnit.</p><p>Unlike previous versions of JUnit, JUnit 5 is composed of several different modules from three different sub-projects.</p><p><strong>JUnit 5 = <em>JUnit Platform</em> + <em>JUnit Jupiter</em> + <em>JUnit Vintage</em></strong></p><p>The <strong>JUnit Platform</strong> serves as a foundation for <a href="https://junit.org/junit5/docs/current/user-guide/#launcher-api">launching testing frameworks</a> on the JVM. It also defines the <a href="https://junit.org/junit5/docs/current/api/org.junit.platform.engine/org/junit/platform/engine/TestEngine.html">TestEngine</a> API for developing a testing framework that runs on the platform. Furthermore, the platform provides a <a href="https://junit.org/junit5/docs/current/user-guide/#running-tests-console-launcher">Console Launcher</a> to launch the platform from the command line and the <a href="https://junit.org/junit5/docs/current/user-guide/#junit-platform-suite-engine">JUnit Platform Suite Engine</a> for running a custom test suite using one or more test engines on the platform.</p><p><strong>JUnit Jupiter</strong> is the combination of the new <a href="https://junit.org/junit5/docs/current/user-guide/#writing-tests">programming model</a> and <a href="https://junit.org/junit5/docs/current/user-guide/#extensions">extension model</a> for writing tests and extensions in JUnit 5. The Jupiter sub-project provides a TestEngine for running Jupiter based tests on the platform.</p><p><strong>JUnit Vintage</strong> provides a TestEngine for running JUnit 3 and JUnit 4 based tests on the platform. It requires JUnit 4.12 or later to be present on the classpath or module path.</p><p>Let’s create a simple REST API using Spring Boot and JAVA Config. Then we will perform an integration test of our application using the <strong>JUnit 5</strong> and <strong>WireMock</strong>.</p><p><strong>Prerequisites</strong></p><p>Before we begin, we must ensure that the following pre-requisites are installed on the local machine:</p><ul><li><a href="https://maven.apache.org/install.html">Maven</a></li><li>Java 17</li></ul><h3>Steps to Build</h3><ol><li>Set up the Spring Boot Project</li><li>Include Additional Maven Dependencies</li><li>Furnish the Configurations</li><li>Define a DTO Class</li><li>Define the Rest Controller Endpoints</li><li>Construct the Service Layer</li><li>Start the Application</li><li>Test the Application with Swagger-UI</li><li>Add Integration Test</li><li>Conclusion</li></ol><h4>1. Set up the Spring Boot Project</h4><p>For this tutorial, we are using JDK 17 and Spring Boot 2.7.1 project<em>. </em>Use <a href="https://start.spring.io/">start.spring.io</a> and create a sample project using the below artifacts:</p><ul><li><strong>Spring Web: </strong>Build web, including RESTful, applications using Spring MVC. Uses Apache Tomcat as the default embedded container.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qim0E5ic4vfe6SQXoFrmEw.png" /><figcaption>Creating Sample Project</figcaption></figure><h4>2. Include Additional Maven Dependencies</h4><p>Add the additional dependencies, shown below, to our project to enable Swagger, Lombok, WireMock, and JUnit for our application.</p><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;org.springdoc&lt;/groupId&gt;<br>    &lt;artifactId&gt;springdoc-openapi-ui&lt;/artifactId&gt;<br>    &lt;version&gt;1.6.9&lt;/version&gt;<br>&lt;/dependency&gt;<br>&lt;dependency&gt;<br>    &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;<br>    &lt;artifactId&gt;lombok&lt;/artifactId&gt;<br>    &lt;version&gt;1.18.24&lt;/version&gt;<br>    &lt;scope&gt;provided&lt;/scope&gt;<br>&lt;/dependency&gt;<br>&lt;dependency&gt;<br>    &lt;groupId&gt;com.github.tomakehurst&lt;/groupId&gt;<br>    &lt;artifactId&gt;wiremock-standalone&lt;/artifactId&gt;<br>    &lt;version&gt;2.27.2&lt;/version&gt;<br>    &lt;scope&gt;test&lt;/scope&gt;<br>&lt;/dependency&gt;<br>&lt;dependency&gt;<br>    &lt;groupId&gt;org.junit.jupiter&lt;/groupId&gt;<br>    &lt;artifactId&gt;junit-jupiter-engine&lt;/artifactId&gt;<br>    &lt;version&gt;5.8.2&lt;/version&gt;<br>    &lt;scope&gt;test&lt;/scope&gt;<br>&lt;/dependency&gt;</pre><p><strong>Swagger</strong>: Swagger is an open-source tool to develop APIs, interact with APIs, and mainly generate documentation for APIs.</p><p><strong>Lombok: </strong>Java annotation library which helps to reduce boilerplate code.</p><p><strong>WireMock</strong> and <strong>JUnit: </strong>for Integration testing.</p><h4>3. Furnish the Configurations</h4><p><strong>3.1 Configure RestTemplate</strong></p><p>To call an external API we need to create a RestTemplate instance. Let’s start with that. <strong><em>Spring Boot &gt;= 1.4 </em></strong>no longer automatically defines a RestTemplate but instead defines a RestTemplateBuilder allowing us more control over the RestTemplate that gets created. We can inject the RestTemplateBuilder as an argument in our @Bean method to create a RestTemplate:</p><pre>@Configuration<br>public class ApplicationConfig {<br>    @Bean<br>    public RestTemplate restTemplate(RestTemplateBuilder builder) {<br>        // Do any additional configuration here<br>        return builder.build();<br>    }<br>}</pre><p><strong>3.2 Configure application.yml</strong></p><p>application.yml file contains all the configuration required by our application during runtime. It also contains externalized configurations. Here, we will just specify our application name and externalize the URL for the dependent service. We will use this URL later to connect to the external service.</p><pre>spring:<br>  application:<br>    name: &quot;Sample Spring Boot Application&quot;<br><br>universitiesBaseURL: http://universities.hipolabs.com</pre><p><strong>3.3 Configuration to read external properties</strong></p><p>We can bind the properties defined in the application.yml file to a Java class using @ConfigurationProperties. This annotation allows to map the entire properties and yml files into an object easily. The @ConfigurationProperties annotation takes a prefix parameter, and binds all the properties with the specified prefix to the Java class.</p><pre>@Configuration<br>@ConfigurationProperties(prefix = &quot;wiremock-config&quot;)<br>@Data<br>@Profile(&quot;integration&quot;)<br>public class WireMockConfig {<br>    private List&lt;WireMockProxy&gt; proxies;<br>}</pre><p>In this article, we will use the WireMockConfig class to read all the configurations related to WireMock defined in application-integration.yml file. We will be defining this application-integration.yml file when we write the integration tests in Step 9. We will need these configurations during the Integration test.</p><h4>4. Define a DTO Class</h4><p>In this application, we will call some external API and then map the JSON response of the API to this DTO UniversityDTO.</p><pre>@Data<br>@JsonIgnoreProperties(ignoreUnknown = true)<br>@JsonInclude(JsonInclude.Include.<em>NON_NULL</em>)<br>public class UniversityDTO {<br>    private List&lt;String&gt; domains;<br>    private List&lt;String&gt; web_pages;<br>    private String name;<br>    private String country;<br>    private String alpha_two_code;<br>}</pre><h4>5. Define the Rest Controller Endpoints</h4><p>Finally, let us write some APIs that can be exposed to the clients. This API will call some external APIs and return the results to the client. Here, we are using one of the free API listed in <a href="https://apipheny.io/free-api/">Apipheny</a> for demonstration purposes. We will use the <a href="https://github.com/Hipo/university-domains-list">Universities List API</a> to get a list of universities in a specified country. We will not be validating the response, as we are just simulating the scenario where in our application we may need to call some APIs, do certain processing on top of that and then return the results.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fdd9f7b8711f2ea5d59cf68ce6e9aaa9/href">https://medium.com/media/fdd9f7b8711f2ea5d59cf68ce6e9aaa9/href</a></iframe><p>In the above code from line 22to 23 we are calling the method getUniversitiesForCountry of the service class, for fetching the list of all universities for the given input. Then we return the result to the caller.</p><p>Next, let’s see how we are getting the data from the external API.</p><h4>6. Construct the Service Layer</h4><p>Let us construct the Service Layer for our application. The service layer is required for encapsulating the business logic and also for centralizing the data access.</p><p>We will add a Service class named UniversityService inside the sub-package service of the root package. This class will contain the method getUniversitiesForCountry that will be used to fetch the universities for the given country.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8df2fb39d1bb091f72b32cd37a20f453/href">https://medium.com/media/8df2fb39d1bb091f72b32cd37a20f453/href</a></iframe><p>As we can see above, UniversityService does not have much business logic other than calling some external service, and map the response to UniversityDTO and then return the response to the caller.</p><h4>7. Start the Application</h4><p>Till now we have written all the code required for our application to run. We will execute the below maven command from the root directory of the project to compile and build the jar.</p><pre>mvn clean package</pre><p>Once the build is complete then we can start the application. Here, we will use IntelliJ to run the application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zqZJqQpQ-G5UX2jPRGnHug.png" /><figcaption>Starting the application</figcaption></figure><p>As we can see above, the application started successfully on port 8080.</p><h4>8. Test the Application with Swagger-UI</h4><p>Let’s test our universities endpoint using Swagger-UI. Once the application started successfully, we can see all the endpoints for our application using the Swagger-UI. Open the swagger UI and check the endpoint. The Swagger will open at the below URL.</p><pre><a href="http://localhost:8080/swagger-ui/index.html">http://localhost:8080/swagger-ui/index.html</a></pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b2gN-7WGrW7VupzTd5p95g.png" /><figcaption>Swagger-UI</figcaption></figure><p>Let’s now test the endpoint for some sample countries.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f3yUIMYir6rqZqQyx5vdKQ.png" /><figcaption>Sample Test using Swagger</figcaption></figure><p>As we can see above, we got the response for the country India. So our application is up and running. Similarly, it can also be tested for other countries.</p><h4>9. Add Integration Test</h4><p>Let’s start writing some Integration tests for this application. During Integration tests, we don’t want to call the actual HTTP endpoint. This is less critical for this example, but imagine a scenario where we manipulate the data within the remote application using HTTP POST or PUT. We don’t want this to happen during our tests and instead talk to a stub.</p><p>Also, if we connect to the actual remote system for our integration test, the test outcome depends on the availability of the remote service and produces additional dependency. At the same time, we may also want to test different responses (e.g., for different scenarios and status codes).</p><p>We can resolve the above challenges of connecting to the external API during integration testing using <strong>WireMock</strong>. With WireMock, we can capture the actual response for a given URL and set the body and status code to whatever we need. Internally, WireMock spawns a local Jetty server that handles the remote calls during our integration tests.</p><p><strong>9.1 Add the Configuration File</strong></p><p>First, we need to add a configuration file name : application-integration.yml inside the “src/main/resources” folder. This file will contain all the configurations, which we need during the Integration test or we want to override during the Integration tests. Here, the term integration in the file name refers to the <strong>profile</strong> name which we will use during the tests. We have already defined the Java class WireMockConfig in step 3.3 above to read these configurations during runtime.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/714/1*uQICH2wAJ7VxwauSySdl6g.png" /><figcaption>application-integration.yml</figcaption></figure><p>The file will contain the below configurations:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Lz8HiqUA9eZB2WeRmc6-fA.png" /><figcaption>Integration configurations</figcaption></figure><p>As we have seen above in step 5 we are calling the <a href="https://github.com/Hipo/university-domains-list">Universities List API</a> to get a list of universities for a given country. So during our Integration tests, we want WireMock to listen to this URL and return responses from the stubs if present. Here, we are specifying the Mock Server to run at port : 9081 for the base URL : http://universities.hipolabs.com. Any request made to this URL will be intercepted by this Mock Server and will return a response from the source or stubs depending upon the value of the property : recording .</p><p>If recording is true then it will call the source and capture the response and return the response to the caller. If the recording is false then it will look into the mappings folder for the captured stubs. If there is a stub matching the required criteria, then it will return a response from the stub to the caller.</p><p><strong>Note</strong>: We can see above that we have overridden the universitiesBaseURL to the proxy URL.</p><p>Here, we have taken proxies in a list format and not as a single entry, although we are calling only one API in this case. This is because if we have to call multiple external APIs, then we can just append the configuration below the current one and it will work.</p><p><strong>9.2 Add Test Directories</strong></p><p>We need to create some directories inside the “src/test/resources” folder for WireMock to record files.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/652/1*lOZvTY3zHVMhE134DTAmFA.png" /><figcaption>test directories</figcaption></figure><p>As we can see above, we need to add the following directories for the integration test:</p><ul><li><strong>__file </strong>(contains response errors and messages JSON)</li><li><strong>mappings </strong>(contains request and response JSON) directories that are needed by WireMock for recording purposes</li><li><strong>recorded</strong> directory is used to capture the actual response of the REST API which we will use to compare against our Integration test results</li></ul><p>Let’s now start writing the Integration test for the REST API which we exposed earlier.</p><p><strong>9.3 Add the Test Case</strong></p><p>Let’s start with adding a new Integration Test class with the file name : SpringBootWiremockApplicationTests inside “src/test/java” folder.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/812/1*p4j3PdN3fTrTF855z2IF8A.png" /><figcaption>Integration Test file</figcaption></figure><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/084c3de9052269b6092f38f910c1fb50/href">https://medium.com/media/084c3de9052269b6092f38f910c1fb50/href</a></iframe><p>In the above code:</p><ul><li><strong>Set the active profile: </strong>In line 1 we are directing our tests to use the profile: integration by annotating the test class with the @ActiveProfiles annotation. By setting integration profile as the active profile, the tests will read the configurations from the file application-integration.yml which we defined in step 9.1.</li><li><strong>Look up main configuration class:</strong> In line 2 @SpringBootTest annotation tells Spring Boot to look for the main configuration class (one with @SpringBootApplication, for instance) and use that to start a Spring application context.</li><li><strong>Configure auto-configuration of MockMVC:</strong> We also have added the annotation @AutoConfigureMockMvc in line 4 because we’ll use <strong>MockMvc</strong> to call the “/api/university “ endpoint for the test. We could have called the service method directly depending on what kind of test we wanted to implement. In this article, we are trying to demonstrate an example of an integration test by calling the REST endpoints directly with MockMvc.</li><li><strong>Start WireMock server and record:</strong> From line 24 to 36 we defined a method called startRecording, which will be executed before every test case. It reads the configuration file defined in step 9.1. If recording flag is turned on then it will start the <strong>WireMock</strong> server in recording mode, else it will start in regular mode.</li><li><strong>Shut down WireMock server and stop recording:</strong> From line 39 to 48 we defined a method called stopRecording which will be executed after every test case. If recording flag is turned on then it will stop the recording before shutting down the <strong>WireMock</strong> server in recording mode or else it will just stop the server in regular mode.</li><li><strong>Provide WireMock server configuration:</strong> From line 124 to 133 we have provided some configuration for the WireMock server. It will start intercepting all the requests specified at the wiremock-config.proxies.url. In this tutorial we have specified only one URL : <a href="http://universities.hipolabs.com/">http://universities.hipolabs.com</a>. If the recording is enabled then it will persist the stubs in the mappings folder. While matching against the stubs it will ignore the arrayOrder and extraElements in the response JSON.</li><li><strong>Define integration test case:</strong> From line 51 to 63 we defined the integration test case for the REST API “/api/university”. Here we have used the parameterized test case of JUnit 5. <strong>MockMvc</strong> is used to call the “/api/university “ endpoint. It then saves the response temporarily in JSON format to compare against the expected response. Here, we are using <a href="https://github.com/skyscreamer/JSONassert">JsonAssert</a> to compare the expected and actual JSON responses. Once the test case is successful then the current file is cleaned up automatically.</li></ul><h4>9.4 Record the Test Case</h4><p>Let’s now see how easily we can record the response of external API and use it during the Integration test. To turn on the recording mode we just need to set recording : true in the configuration defined in step 9.1 above.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hhP8OEFug4fwQWIyeT5cSQ.png" /><figcaption>recording enabled</figcaption></figure><p>Now run the test case. When we run the test case, the response of the external API will be captured inside the mappings folder. The expected response of our API will be captured inside the recorded folder.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IkpC_2wGwk_9cQ3pN4OZIw.png" /><figcaption>recording the test case</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4B6vMnQx4ikOj774JQ5chA.png" /><figcaption>running test case in recording mode</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fCaGUkuK5Up1NsrHY-mdeA.png" /><figcaption>recorded files captured</figcaption></figure><h4>9.5 Replay the Test Case</h4><p>Now everything is in place. We have the Integration Test case ready to be executed. We have all the recorded files in place. Let’s now run the Test case with recording: false .</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Lz8HiqUA9eZB2WeRmc6-fA.png" /><figcaption>recording disabled</figcaption></figure><p>With recording off, <strong>WireMock</strong> will try to search the mappings folder for one of the matched responses for the given request. In case it is able to find the response then will return that, else, it will throw an exception saying it’s not able to get any stubbed response.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8jrOjjzhdKbECsR37M67Ug.png" /><figcaption>replay the test case</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*t_90eyD_HXLTFhyoCi0_IA.png" /><figcaption>Running the test case with recording off</figcaption></figure><p>Let’s modify the recorded response manually to see what happens when WireMock is not able to find the matched response.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jczsA2HUm2fWuVZwUh8CRw.png" /><figcaption>update the captured response</figcaption></figure><p>Let’s say we update the country name in the response to india1 from india . to simulate the scenario when we don’t have the recorded file for a given request.</p><p>Now let’s run the test case again with recording: false</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S28vVfSrOh3TLTHjS8uEiQ.png" /><figcaption>no recording found</figcaption></figure><p>As we can see above, when we run our test case after modifying the response, we got the error <strong>Request was not matched</strong>. This time WireMock is not able to find any recordings. It also displayed the closest match it can find.</p><h4>Conclusion</h4><p>In this article, we saw how we can leverage WireMock in Spring Boot applications to record the requests and responses for external API for various scenarios, and then we can use that recorded files during Integration Test.</p><p>If you would like to refer to the full code, do check:</p><p><a href="https://github.com/sumanentc/spring-boot-with-wiremock">GitHub - sumanentc/spring-boot-with-wiremock: The purpose of this project is to demonstrate how we can use Spring Boot along with WireMock and JUnit 5 to perform Integration Testing with mocking external REST API calls.</a></p><h4>References &amp; Useful Readings</h4><ul><li><a href="https://junit.org/junit5/docs/current/user-guide/">JUnit 5 User Guide</a></li><li><a href="https://wiremock.org/docs/">WireMock User Documentation</a></li><li><a href="https://wiremock.org/docs/record-playback/">Record and Playback</a></li><li><a href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests">JUnit 5 User Guide</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d514a47ab931" width="1" height="1" alt=""><hr><p><a href="https://medium.com/cuddle-ai/testing-spring-boot-application-using-wiremock-and-junit-5-d514a47ab931">Testing Spring Boot Application using WireMock and JUnit 5</a> was originally published in <a href="https://medium.com/cuddle-ai">Crux Intelligence</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Async Architecture with FastAPI, Celery, and RabbitMQ]]></title>
            <link>https://medium.com/cuddle-ai/async-architecture-with-fastapi-celery-and-rabbitmq-c7d029030377?source=rss-54106b197f0a------2</link>
            <guid isPermaLink="false">https://medium.com/p/c7d029030377</guid>
            <category><![CDATA[fastapi]]></category>
            <category><![CDATA[python-programming]]></category>
            <category><![CDATA[cuddleengineering]]></category>
            <category><![CDATA[cuddlepe]]></category>
            <category><![CDATA[python-web-developer]]></category>
            <dc:creator><![CDATA[Suman Das]]></dc:creator>
            <pubDate>Tue, 10 May 2022 18:26:55 GMT</pubDate>
            <atom:updated>2022-05-14T16:21:38.648Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zK1KQN6k_fy4xvSB6mo9cA.png" /><figcaption>FastAPI With RabbitMQ and Celery</figcaption></figure><p>In one of my earlier tutorials, we have seen how we can optimize the performance of a <a href="https://fastapi.tiangolo.com/"><strong>FastAPI</strong></a><strong> </strong>application using<strong> Async IO</strong>. To know more you can refer to <a href="https://medium.com/cuddle-ai/concurrency-with-fastapi-1bd809916130">Concurrency with FastAPI</a>.</p><p>Async IO is suitable when we want to perform small BackgroundTasks or to perform tasks using concurrency and parallelism. However, when it comes to performing heavy background computations or complex tasks, we would ideally not like to run them as a part of the same process. As such, to perform these complex tasks as a separate process, we need specialized tools such as <a href="https://docs.celeryq.dev/en/latest/getting-started/introduction.html">Celery</a>.</p><p><a href="https://docs.celeryq.dev/en/latest/getting-started/introduction.html"><strong>Celery</strong></a> is a distributed task queue that simplifies the management of task distribution and processing. Task queues are used as a mechanism to distribute work across threads or machines. A task queue’s input is a unit of work called a task. Dedicated worker processes constantly monitor task queues for new work to perform.</p><p>As a task-queueing system, Celery works well with long running processes or small repeatable tasks working in batches. The types of problems Celery handles are common asynchronous tasks.</p><p><strong>Celery</strong> communicates via messages, usually using a broker to mediate between clients and workers. To initiate a task the client adds a message to the queue, and the broker then delivers that message to a worker. <strong>RabbitMQ</strong> and <strong>Redis</strong> are the brokers transports completely supported by Celery. In our example, we will use RabbitMQ as broker transport.</p><p><a href="https://www.rabbitmq.com/"><strong>RabbitMQ</strong></a> is the most widely deployed open-source message broker. RabbitMQ is lightweight and easy to deploy on-premises and in the cloud. It supports multiple messaging protocols. RabbitMQ can be deployed in distributed and federated configurations to meet high-scale, high-availability requirements. RabbitMQ is the default broker for Celery so it doesn’t require any additional dependencies or initial configuration, other than the URL location of the broker instance we want to use.</p><p>There are several tools available to monitor and inspect Celery clusters. <a href="https://flower.readthedocs.io/"><strong>Flower</strong></a> is a real-time web application monitoring and administration tool for Celery.</p><p>The following figure shows a simplified diagram of how various components interact. Here, we are using FastAPI as our Celery Client and RabbitMQ as a message broker and Result Backend.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GzqPxgh43BMtJZnhOmrjnw.png" /><figcaption>FastAPI with Celery Flow</figcaption></figure><ol><li>The client sends a request to our FastAPI application.</li><li>FastAPI app sends the task message to the message broker.</li><li>Celery workers consume the messages from the message broker. After the task finishes, it saves the result to the Result Backend and updates the task status.</li><li>After sending the task to the message broker, the FastAPI app can also monitor the status of the task from the Result Backend.</li><li>Flower can also monitor the celery app tasks by processing messages on the message broker.</li></ol><p>In this tutorial, we will see how we can integrate <strong>Celery</strong> into <a href="https://fastapi.tiangolo.com/"><strong>FastAPI</strong></a> application to perform asynchronous tasks without blocking user’s requests. Although the example which we will use here is very trivial, it is just for pure demonstration purposes only.</p><p>Some of the use cases where the application of Celery with FastAPI can be useful:</p><ol><li>Sending out Emails as Background tasks in an app.</li><li>Processing the uploaded images in the background.</li><li>Offline training of ML models.</li><li>Periodic tasks like report generation or web scrapping.</li></ol><p>The following steps illustrate, how we can use Celery along with FastAPI to perform asynchronous tasks:</p><ol><li>Setup and Installation</li><li>Set up Message Broker</li><li>Add Celery</li><li>Add Celery Tasks</li><li>Add APIRouter</li><li>Start the Application and Celery Worker Server</li><li>Test the Application</li><li>Monitor the Tasks</li></ol><p><strong>Prerequisites</strong></p><p>We require Python 3.9.2 with <strong>Pipenv</strong> and <a href="https://git-scm.com/">Git</a> installed. <a href="https://pipenv.readthedocs.io/en/latest/"><strong>Pipenv</strong></a> is a package and a virtual environment manager, which uses PIP under the hood. It provides more advanced features like version locking and dependency isolation between projects.</p><h3>1. Setup and Installation</h3><p>Once the prerequisites are in place we can begin creating our application.</p><p>To begin with our application, create a folder called fastapi-celery-rabbitmq-application in any directory on the disk for our project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PWQKUAJOiACpIeKgDNpHNA.png" /><figcaption>create project folder</figcaption></figure><p>Navigate to the project folder.</p><p><strong>Activate Virtual Environment</strong></p><p>Once we are inside the project folder, execute the following commands to activate the VirtualEnv.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YXdqOnIEDleJ6nTMlLKYaw.png" /><figcaption>Start pipenv shell</figcaption></figure><p>The virtual environment will now be activated, which will provide the required project isolation and version locking.</p><p><strong>Install Dependencies</strong></p><p>Next, we will install all the required dependencies using Pipenv present in requirements.txt.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*u1f-KdYYRZ8Axw9-gP2gyg.png" /><figcaption>all the dependencies used in our application</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QCr26kt49lkVd29_XwWn3w.png" /><figcaption>install all the dependencies</figcaption></figure><p>After we execute the above commands, the required dependencies will be installed.</p><p>We can see now two files, which have been created inside our project folder, namely, Pipfile and Pipfile.lock.</p><ul><li>Pipfile contains all the names of the dependencies we just installed.</li><li>Pipfile.lock is intended to specify, based on the dependencies present in Pipfile, which specific version of those should be used, avoiding the risks of automatically upgrading dependencies that depend upon each other and breaking your project dependency tree.</li></ul><p><strong><em>Note</em></strong>: Here, we have installed all the dependencies with specific versions, which worked on my machine while writing this tutorial. If we don’t specify any version then the latest version of that dependency will be installed, which might not be compatible with other dependencies.</p><p>The next step is to set up a Message Broker.</p><h4>2. Set up Message Broker</h4><p>Celery requires a solution to send and receive messages; usually, this comes in the form of a separate service called a <em>message broker</em>. There are several choices available, <strong>RabbitMQ</strong> is one of them.</p><p>One of the easiest ways to run <strong>RabbitMQ</strong> on our local machine is by using <a href="https://brew.sh/">Homebrew</a>. <a href="https://brew.sh/">Homebrew</a> is a popular package manager for <strong>MacOS</strong>. <a href="https://github.com/Homebrew/homebrew-core/blob/master/Formula/rabbitmq.rb">RabbitMQ formula</a> is available from <a href="https://brew.sh/">Homebrew</a>’s core tap (out of the box).</p><ol><li>To install the RabbitMQ server using Homebrew just execute the below command:</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/924/1*FHUzBaXmKPg7eUjGmx1jeg.png" /><figcaption>Brew install RabbitMQ</figcaption></figure><p>2. The server can then be started with <strong>rabbitmq-server</strong> in the foreground or with brew services start rabbitmq to have it run under launched in the background.</p><p>3. Once we start the server then we can log in to the server with default credentials(Username: guest, Password: guest) at http://localhost:15672.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/964/1*fCnp4arc1mq1hSFjG95RIA.png" /><figcaption>RabbitMQ Server</figcaption></figure><p>For other installation options for RabbitMQ, check the below link:</p><p><a href="https://www.rabbitmq.com/documentation.html">RabbitMQ Documentation | RabbitMQ</a></p><p>Our message broker is up and running. Let’s configure Celery now.</p><h4>3. Add Celery</h4><p>We have already installed all the dependencies required for Celery to work. Let’s now configure it. In the root directory of the project, let’s create a file named main.py .</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7758a2744d039968bb5445c753ec977e/href">https://medium.com/media/7758a2744d039968bb5445c753ec977e/href</a></iframe><ul><li>In the above code from line 8 to 16 we have created a factory function called create_app(), which can be called any time, and returns a FastAPI app instance for us to use. In this function, we are calling a function called create_celery()to create a Celery app instance. Also, we have integrated the universities routing module, which will be explained further in this tutorial as we go ahead.</li><li>The code from line 23 to 24 is required to run the FastAPI application using <strong>Uvicorn</strong>. FastAPI is the framework that we have used to build our API, and <strong>Uvicorn</strong> is the server that we will use to serve the requests.</li></ul><p><strong>Celery Configuration</strong></p><p>Now start with the celery configuration by adding a file config/celery_config.py.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7ed82baa3418caeede251bca4b3f3117/href">https://medium.com/media/7ed82baa3418caeede251bca4b3f3117/href</a></iframe><ul><li>The above file contains all the configurations required by Celery to run. From line 14 to 15 we have defined the Broker URL and Result backend. Here, we are using RabbitMQ, which we installed in step 2, as a broker as well as Result Backend.</li><li>From line 17 to 23 we have defined all the Queues, which we will use in our application along with the default celery Queue. Instead of manually configuring routing rules at each task level, here we will use CELERY_TASK_ROUTES to configure the routing rules dynamically.</li><li>We have added a helper method from line 6 to 10 fetch the routes per task at runtime.</li><li>From line 32 to 39 we defined a function to load the celery configuration during startup.</li></ul><p><strong>Celery App Instance</strong></p><p>Let’s now add one more file config/celery_utils.py that will contain the below code to create the celery app instance.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a309ace9a24dc1efd3a57f557569c83a/href">https://medium.com/media/a309ace9a24dc1efd3a57f557569c83a/href</a></iframe><p>The above file defines a create_celery factory function that configures and then returns a Celery app instance. It will read all the celery related configurations defined earlier. We also updated some of the default celery configurations. This function is called from main.py while creating the FastAPI app instance.</p><p>From line 22 to 32 we defined one more helper function get_task_info to get the status and result of the asynchronous tasks submitted via Celery.</p><h4>4. Add Celery Tasks</h4><p>We have now all the Celery-related configurations in place. Let us now add some tasks which we want to execute asynchronously. We have to add a file celery_tasks/tasks.py which will contain all the tasks which we want to execute asynchronously.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/88bf7bdc1e862ce7812fc2ed9f667238/href">https://medium.com/media/88bf7bdc1e862ce7812fc2ed9f667238/href</a></iframe><p>In the above file we have defined two tasks get_all_universities_task and get_university_task . get_all_universities_task is used to get the all the universities for the list of countries provided as input. get_university_task is used to get the universities for a given country.</p><p>Here, we have used shared_task to make our code reusable, which requires current_app instance inside create_celery method instead of creating a new Celery instance. The @shared_task decorator returns a proxy that always uses the task instance in the current_app. It lets us create tasks that can be used by any app(s). Since Celery has built-in support for retrying the failed tasks, we have enabled it for our tasks. With retry option, we can now let the exception bubble up and let the decorator handle it.</p><h4>5. Add APIRouter</h4><p>Now, let’s add some entry points to test our application. We will start with a simple Sync entrypoint. This entry point will call some external APIs and return the results to the client. Here, we are using the same free API which I have used in one of my earlier tutorial. It’s listed in <a href="https://apipheny.io/free-api/">Apipheny</a>. We will use the <a href="https://github.com/Hipo/university-domains-list">Universities List API</a> to get a list of universities for a specified country. For this tutorial, we are fetching the list of universities provided as input to the API. We will not be validating the response, as we are just simulating the scenario where in our application we are performing some long running tasks to complete the request like we need to call multiple APIs, do certain processing on top of that and then return the results.</p><p>Let’s add a dedicated file for handling just universities requests as a module at /routers/universities.py.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4d7b5cdf1a304118d3812d137a66939c/href">https://medium.com/media/4d7b5cdf1a304118d3812d137a66939c/href</a></iframe><p>We wanted to have the <em>path operations</em> related to universities separated from the rest of the code, to keep the code organized. But it’s still part of the same <strong>FastAPI</strong> application. We can create the <em>path operations</em> for any module using APIRouter. We have already included this router in our application in step 3 . Here we have added 4 different endpoints to test our application.</p><ul><li>get_universites : From line 14 to 22 we have defined a simple API which takes a list of countries as an input, and then it calls some external API to get the result. The result of each country is combined and then returned to the caller. This API is added just to check the API integration. It has nothing to do with Celery.</li><li>get_universities_async: This API is used to demonstrate how we can use Celery to perform the long running tasks asynchronously. When we call this API, it submits a task message to the broker and returns the taskID for the same. Consumers then can use the other API to check the status of the submitted task or to get the task result. As we can see here, we just need to call the get_all_universities_task method defined earlier in step 4 . Celery task is called just by adding the .apply_async() after the function name. This will tell Celery to add new task to the queue. It internally submits the task to universities Queue. There will be a worker process which will listen to this Queue . The worker process will pickup the message, process the request and save the results in Result Backend.</li><li>get_universities_parallel: This API is used to demonstrate how we can use Celery to split a large task into smaller subtasks and execute in parallel. When we call this API, we create a one task for each country provided as input. Then we group these tasks using Celery <a href="https://docs.celeryq.dev/en/stable/reference/celery.html#celery.group"><strong>Group</strong></a><strong>. </strong>Celery<strong> </strong>group is lazy so we must call it to take action and evaluate the group. Then we execute the group and wait for it to complete. Once all the tasks are completed then the group will be completed and we will get the response. After that we collect the response of the group and return it to the caller. This is a synchronous flow which uses Celery for parallel execution of subtasks.</li><li>get_task_status: This API is used to get the status and result of the asynchronous tasks submitted using one of the API which we described earlier.</li></ul><p>Let us have a look at the code which fetches the universities:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/43597d4e1c113b5c234dadfb322d78c3/href">https://medium.com/media/43597d4e1c113b5c234dadfb322d78c3/href</a></iframe><p>In line 5 we are using the HTTP client named <a href="https://www.python-httpx.org/"><strong>httpx</strong></a><strong> </strong>to send an HTTP request. HTTPX is a fully-featured HTTP client for Python 3, which provides sync and async APIs, and support for both HTTP/1.1 and HTTP/2.</p><p>Here, we are using the sync API to get the universities data. In the above code in line 6 we are calling the universities API to get the results. In line 7 we convert the response to JSON format. Then from line 9 to 11 , we map the JSON object to the <strong>Pydantic</strong> model University. And finally, we return the dictionary, which contains the country name as key and the list of universities as value.</p><p>Now our code is complete to run the FastAPI with Celery.</p><h4>6. Start the Application and Celery Worker Server</h4><p>We have already added the code in step 3 required to start the application.</p><p>FastAPI is the framework that we have used to build our API, and <strong>Uvicorn</strong> is the server that we will use to serve the requests. We have already installed <a href="https://www.uvicorn.org/">Uvicorn</a>. That will be our server. We can start the application by executing the below command:</p><pre><strong>python main.py</strong></pre><p>Once the application starts successfully, we can navigate to http://localhost:9000/docs. The system will bring up a page that looks something like this. We can see the endpoint in the Swagger-UI which was defined earlier.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qZuebxYa0Yz9K-adcOTfYw.png" /><figcaption>Swagger-ui</figcaption></figure><p>Let’s now start the Celery worker server. We need to open a new terminal and go the project root directory. Then we need to install all the dependencies using the below command:</p><pre><strong>Pipenv install -r requirements.txt</strong></pre><p>Once all the dependencies are installed then we can execute the below command:</p><pre><strong>celery -A main.celery worker --loglevel=info -Q universities,university</strong></pre><p>The Celery worker will listen to the custom Queue : universities, university used in our application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*91bnwwNT4tNTHcNdBLesew.png" /><figcaption>Celery-worker</figcaption></figure><p>Everything is up and running now! Let’s test our application.</p><h4>7. Test the Application</h4><p>Let us test our universities endpoint with some sample countries to validate the external API integration. Here, we are getting all the universities for “Turkey” and “India”. We are not validating the response or data.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AU94msS5ICztmZBAFdSwVg.png" /><figcaption>get-universities-request</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5uc60h4EtRcIJ0oSgbo2FA.png" /><figcaption>get-universities-response</figcaption></figure><p>As we can see above, we got the universities list for the requested countries. Here, we are using the <a href="https://github.com/Hipo/university-domains-list">Universities List API</a> to get a list of universities for the specified country.</p><p>Once we have tested that the external API interaction is working fine, let’s test the asynchronous task execution using Celery. Here, we will try to get the list of universities for the same set of countries provided as input.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f0BHYXAzdYZMq1nQI4v-ig.png" /><figcaption>async-request</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g2A1wWF8ybvLa0a5AZoaxQ.png" /><figcaption>async-response</figcaption></figure><p>Once we submit the request, we just get the task_id as an acknowledgment response. The request is getting processed asynchronously using Celery as a separate process. We can check the status of the task using the other API. With this architecture, we can handle a large number of requests. We can Enqueue the user requests and process them as per resource availability for our application.</p><p>Let’s check the status of the asynchronous task submitted above. We can use the below API with task_id as input.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pQiMZ-pOWqB9ESoR5YJsJA.png" /><figcaption>get task status request</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*k-8MCi8U6on_khX55zLE0Q.png" /><figcaption>get task status response</figcaption></figure><p>Let’s now test the scenario where we want to split the user request into multiple subtasks, process them in parallel using Celery and then return the results.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ljSbNV0y3i3U77WfOJd8eQ.png" /><figcaption>parallel-request</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r4vWrnJhT59vfKIdAwkRsA.png" /><figcaption>parallel-response</figcaption></figure><p>This has the same request and response as the first API which we tested earlier. However, this will execute faster because we are getting the universities of each country in parallel using Celery. Here, we got the response in 0.58 sec as compared to 1.67 sec as per the first API.</p><h4>8. Monitor the tasks</h4><p>Till now we can see that using Celery we are submitting some tasks to be executed in the background. It would be good if we can see the progress of these using GUI. The easiest way to monitor the Celery app is via Flower. Using it, we can easily check the progress of all the tasks. We already have all the dependencies installed. We just need to start flower. To run the flower server on our local machine execute the below command from the project root directory:</p><pre><strong>celery -A main.celery flower --port=5555</strong></pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YtGNf-OsJKXElqMoQrE98w.png" /><figcaption>flower</figcaption></figure><p>We can now see our flower monitoring tool running on our local server at <a href="http://localhost:5555/">http://localhost:5555/</a> .</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aTpBVoBbvGbOxmzz4foSBA.png" /><figcaption>Flower-dashboard</figcaption></figure><p>In the Dashboard, we can see the summary of all the tasks executed via Celery.</p><p>Once the tasks are submitted, we can also see the progress of each task by clicking on the tasks tab on the Flower navigation bar.</p><p>For example, we can see the state of each task as it goes from In Progress to Success or Failed. We can also see when the task started, its runtime and its response, and so on.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lJpIs0nfRlpWrI6OSmCd5w.png" /><figcaption>Celery-tasks</figcaption></figure><h4>Conclusion</h4><p>In this tutorial, we saw how we can integrate Celery with FastAPI application. Using Celery along with FastAPI application will not only improve the overall performance and efficiency but will help in better handling of background jobs for long-running tasks. We also saw if we want to monitor the tasks submitted via Celery then how we can do the same using Flower.</p><p>If you would like to refer to the full code, do check:</p><p><a href="https://github.com/sumanentc/fastapi-celery-rabbitmq-application">GitHub - sumanentc/fastapi-celery-rabbitmq-application: Sample FastAPI Application to demonstrate Async architecture with Celery, RabbitMQ and Flower</a></p><h4>References &amp; Useful Readings</h4><ul><li><a href="https://docs.celeryq.dev/en/stable/index.html">Celery - Distributed Task Queue - Celery 5.2.6 documentation</a></li><li><a href="http://ask.github.io/celery/userguide/groups.html#groups">Groups, Chords, Chains and Callbacks - Celery 2.6.0rc4 documentation</a></li><li><a href="https://flower.readthedocs.io/en/latest/">Flower - Celery monitoring tool - Flower 1.0.1 documentation</a></li><li><a href="https://stackoverflow.com/questions/21233089/how-to-use-the-shared-task-decorator-for-class-based-tasks">How to use the @shared_task decorator for class based tasks</a></li><li><a href="https://www.rabbitmq.com/documentation.html">RabbitMQ Documentation | RabbitMQ</a></li><li><a href="https://www.rabbitmq.com/documentation.html">RabbitMQ Documentation | RabbitMQ</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c7d029030377" width="1" height="1" alt=""><hr><p><a href="https://medium.com/cuddle-ai/async-architecture-with-fastapi-celery-and-rabbitmq-c7d029030377">Async Architecture with FastAPI, Celery, and RabbitMQ</a> was originally published in <a href="https://medium.com/cuddle-ai">Crux Intelligence</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Concurrency with FastAPI]]></title>
            <link>https://medium.com/cuddle-ai/concurrency-with-fastapi-1bd809916130?source=rss-54106b197f0a------2</link>
            <guid isPermaLink="false">https://medium.com/p/1bd809916130</guid>
            <category><![CDATA[fastapi]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[cuddleengineering]]></category>
            <category><![CDATA[python-programming]]></category>
            <category><![CDATA[cuddlepe]]></category>
            <dc:creator><![CDATA[Suman Das]]></dc:creator>
            <pubDate>Wed, 19 Jan 2022 17:42:19 GMT</pubDate>
            <atom:updated>2022-01-20T05:50:48.321Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/727/1*N-jySKpYBpiJPAaWW8PWfg.png" /><figcaption>Concurrency with FastAPI</figcaption></figure><p>In one of my earlier tutorials, we have seen how we can create a Python application using <a href="https://fastapi.tiangolo.com/"><strong>FastAPI</strong></a>. To know more you can refer to <a href="https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1">Building REST APIs using FastAPI</a>. One of the main reasons to choose FastAPI over other frameworks is due to its <a href="https://fastapi.tiangolo.com/#performance">very high performance</a>, which is on par with <strong>NodeJS</strong> and <strong>Go.</strong></p><p>In this tutorial, we will see how we can optimize the performance of our FastAPI application using concurrency and parallelism.</p><h3>Understanding Concurrency and Parallelism</h3><h4>Concurrency</h4><p>Concurrency is when two or more tasks can start, run, and complete in overlapping time periods. It doesn’t necessarily mean they’ll be running at the same instant, such as multitasking on a single-core machine.</p><p><em>Threading</em> is a concurrent execution model whereby multiple threads take turns executing tasks. One process can contain multiple threads. It uses pre-emptive multitasking.</p><p><em>Async IO</em> is a style of concurrent programming that is single-threaded, single-process design. It uses co-operative multitasking. We will be covering Async IO in Python in detail as we go ahead.</p><h4>Parallelism</h4><p>Parallelism is when tasks literally run at the same time, wherein we can split tasks up into smaller subtasks, which can be processed in parallel using multiple CPUs at the exact same time, such as, on a multicore processor.</p><p><em>Multiprocessing</em> is a means to effect parallelism, and it entails spreading tasks over a computer’s central processing units (CPUs, or cores). The processes all run at the same time, essentially multiprocessing on different processors.</p><p><strong>Concurrency</strong> and <strong>parallelism</strong> both relate to “different things happening more or less at the same time”.</p><h3>Introduction to Async IO</h3><p><strong>Async IO</strong> is a style of concurrent programming introduced in <strong>Python 3.4</strong>. Asynchronous I/O is a form of input/output processing. While Synchronous I/O waits for an operation to complete before moving on to the next one, Asynchronous I/O permits other processing to continue before the transmission of one operation has finished.</p><p>Synchronous I/O is easier to code as it follows all the steps in a program to complete a task before moving on to the next. To simplify asynchronous code and make it as simple as synchronous code, Async IO uses coroutines and futures as there are no callbacks. It uses <strong>async </strong>and<strong> await</strong> keywords to manage and execute asynchronous code.</p><p><a href="https://docs.python.org/3.5/library/asyncio-task.html#coroutines"><strong>Coroutines</strong></a> are special functions that work similar to Python generators. It is a function that can suspend its execution before reaching return, and it can indirectly pass control to another coroutine for some time. On <strong><em>await,</em></strong> they release the flow of control back to the event loop. A coroutine needs to be scheduled to run on the event loop, once scheduled coroutines are wrapped in <em>Tasks </em>which is a type of <em>Future</em>.</p><p><a href="https://docs.python.org/3.5/library/asyncio-task.html#future"><strong>Futures</strong></a> represent the result of a task that may or may not have been executed. This result may be an exception.</p><p>With <strong>FastAPI</strong> we can take the advantage of concurrency that is very commonly used for web development. We don’t have to worry about the Event loop or <strong>async/await </strong>management. What we need is to just do the following :</p><ul><li>Declare the first path functions as coroutines via async def wherever appropriate. If we do this wrong, FastAPI will still be able to handle it, we just won’t get the performance benefits.</li><li>Declare particular points as awaitable via the await keyword within the coroutines.</li></ul><p>We will see this in practice below when we come to the coding part.</p><p>In most of the applications, we perform some Input-output (I/O) operations which are slow in nature. To demonstrate the slow nature of Input-output (I/O) operations, in this tutorial we will create a FastAPI application that will internally call some external APIs to get some data. When we call external API we are performing some network (I/O). Then we will see how to utilize Async IO to improve the performance of our application.</p><p>The following steps illustrate, how we can use Async IO in FastAPI to improve performance:</p><ol><li>Setup and Installation</li><li>Add a Sync EntryPoint</li><li>Add Middleware</li><li>Start the Application</li><li>Test the Application</li><li>Add an Async EntryPoint</li><li>Compare the Performance</li></ol><p><strong>Prerequisites</strong></p><p>We require Python 3.9 with <strong>Pipenv</strong> and <a href="https://git-scm.com/">Git</a> installed. <a href="https://pipenv.readthedocs.io/en/latest/"><strong>Pipenv</strong></a> is a package and a virtual environment manager, which uses PIP under the hood. It provides more advanced features like version locking and dependency isolation between projects.</p><h4>1. Setup and Installation</h4><p>Once the prerequisites are in place we can begin creating our application.</p><p>To begin with, our application, create a folder called python-sample-fastapi-application in any directory on the disk for our project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w-SJeMzI2p6gfeb6j0mLdQ.png" /><figcaption>create-project-folder</figcaption></figure><p>Navigate to the project folder.</p><p><strong>b) Activate Virtual Environment</strong></p><p>Once we are inside the project folder, execute the following commands to activate the VirtualEnv.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kKaQPhVqTdu_omZ57CCJ8g.png" /><figcaption>start pipenv shell</figcaption></figure><p>The virtual environment will now be activated, which will provide the required project isolation and version locking.</p><p><strong>c) Install Dependencies</strong></p><p>Next, install all the required dependencies using Pipenv as shown.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O75Byb4TkcYTixU_2fRIOQ.png" /><figcaption>install-dependencies</figcaption></figure><p>After we execute the above commands, the required dependencies will be installed.</p><p>We can see now two files, which have been created inside our project folder, namely, Pipfile and Pipfile.lock.</p><ul><li>Pipfile contains all the names of the dependencies we just installed.</li><li>Pipfile.lock is intended to specify, based on the dependencies present in Pipfile, which specific version of those should be used, avoiding the risks of automatically upgrading dependencies that depend upon each other and breaking your project dependency tree.</li></ul><p><strong><em>Note</em></strong>: Here, we have installed all the dependencies with specific versions, which worked on my machine while writing this tutorial. If we don’t specify any version then the latest version of that dependency will be installed, which might not be compatible with other dependencies.</p><p>The next step is to add the Entrypoint for our application.</p><h4>2. Add a Sync EntryPoint</h4><p>In the root directory of the project, let’s create a file named main.py . Now, let’s add an entry point. The entry point will call some external APIs and return the results to the client. Here, we are using one of the free API listed in <a href="https://apipheny.io/free-api/">Apipheny</a> for demonstration purposes. We will use the <a href="https://github.com/Hipo/university-domains-list">Universities List API</a> to get a list of universities in a specified country. For this tutorial, we are fetching the list of universities in turkey, india and australia. We will not be validating the response, as we are just simulating the scenario where in our application we may need to call multiple APIs, do certain processing on top of that and then return the results.</p><p>Let us have a look at the code for adding the non-async endpoint:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/37e273b36358bf4149d8898919e8cf60/href">https://medium.com/media/37e273b36358bf4149d8898919e8cf60/href</a></iframe><p>In the above code from line 7 to 9 we are calling the method get_all_universities_for_country for fetching the list of all universities from three different countries and storing the result in the dictionary. After collecting the results for all the countries we are returning the results to the caller.</p><p>Next, let’s see how we are getting the data. We will add a file named universities.py in the root directory. This file will contain the function get_all_universities_for_country that will be used to fetch the universities for the given country.</p><p>Let us have a look at the code which fetches the universities:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/43597d4e1c113b5c234dadfb322d78c3/href">https://medium.com/media/43597d4e1c113b5c234dadfb322d78c3/href</a></iframe><p>In line 5 we are using the HTTP client named <a href="https://www.python-httpx.org/"><strong>httpx</strong></a><strong> </strong>to send an HTTP request. HTTPX is a fully-featured HTTP client for Python 3, which provides sync and async APIs, and support for both HTTP/1.1 and HTTP/2.</p><p>Here, we are using the sync API to get the universities data. Later on, we will use the async API.</p><p>In the above code in line 6 we are calling the universities API to get the results. In line 7 we convert the response to JSON format. Then from line 9 to 11 , we map the JSON object to the <strong>Pydantic</strong> model University.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4xnfdGXwlr5Gkg10ynxVjw.png" /><figcaption>University Model</figcaption></figure><p>And finally, we return the dictionary, which contains the country name as key and the list of universities as value.</p><p>Now our sync endpoint is ready. To check the response time of our API let’s add a middleware.</p><h4>3. Add middleware</h4><p>A <a href="https://fastapi.tiangolo.com/tutorial/middleware/"><strong>middleware</strong></a> is a function that works with every <strong>request</strong> before it is processed by any specific <em>path operation </em>and also with every <strong>response</strong> before returning it.</p><p>Add the below middleware code in main.py</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0acce06ba8d4eab9ba5b6ca33dfb871b/href">https://medium.com/media/0acce06ba8d4eab9ba5b6ca33dfb871b/href</a></iframe><p>As we can see in the above code, the middleware function uses the decorator @app.middleware(&quot;http&quot;) at the top. It will intercept our entrypoint request and then forward the request to the corresponding path operation. We will use this middleware to calculate the time taken by our entrypoint and return the response time to the caller. Here, we are adding a response header X-Process-Time in line 6 which will contain the response time.</p><h4>4. Start the Application</h4><p>Till now we have written all the code required for our application to run. We just need to add the below code to main.py file.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m7inf4rrH1zNHqet2xSolQ.png" /><figcaption>main.py</figcaption></figure><p>FastAPI is the framework that we have used to build our API, and <strong>Uvicorn</strong> is the server that we will use to serve the requests. We have already installed <a href="https://www.uvicorn.org/">Uvicorn</a>. That will be our server. Our application is ready now. We can start the application by executing the below command:</p><pre>python main.py</pre><p>Once the application starts successfully, we can navigate to http://localhost:9000/docs. The system will bring up a page that looks something like this. We can see the endpoint in the Swagger-UI which was defined earlier.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*khVBDr4h_mEHeeaFKSpH6w.png" /><figcaption>swagger-with-sync-API</figcaption></figure><h4>5. Test the application</h4><p>Let us test our universities endpoint and see, how much time does it take to complete the request.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IQb26Jz8RvanKv7qyPvLoA.png" /><figcaption>sync-response</figcaption></figure><p>As we can see in the response that the sync endpoint took around 2.0514 sec to get the list of universities for three countries.</p><h4>6. Add an Async EntryPoint</h4><p>Let’s now see how we can improve the performance of the REST API using Async IO.</p><p>We will add an async equivalent of the above endpoint. There is no functional difference between these two endpoints. It is just a different way of handling the request.</p><p>Let us have a look at the code for the async endpoint:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/09320d7d3d2500c52181396f4aff1320/href">https://medium.com/media/09320d7d3d2500c52181396f4aff1320/href</a></iframe><p>When we define a function with the async keyword, it is a special function called a <a href="https://docs.python.org/3/glossary.html#term-coroutine">coroutine</a>. The reason why coroutines are special is that they can be paused internally, allowing the program to execute them in increments via multiple entry points for suspending and resuming execution. This is in contrast to normal functions which only have one entry point for execution.</p><p>When we use the await keyword, inside async function, it tells the program that this is a “suspendable point” in the coroutine. It will suspend execution at this point and can perform some other tasks. It will come back later once the results are available.</p><p>In the above code in line 7 we can see that, we are using <a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.gather">asyncio.gather</a> to run a sequence of awaitable objects (i.e. our coroutines) concurrently. Once we gather the response of all the coroutines, we are returning the response to the caller. The main difference here is that we are not waiting for the response of the first API to finish before calling the next API. It’s like we are calling all the APIs concurrently and waiting for the results.</p><p>Next, we will add one more function in universities.py called get_all_universities_for_country_async to get the universities in async mode.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/a97de5f7580c2d4ad0a2cf4c3d1311bc/href">https://medium.com/media/a97de5f7580c2d4ad0a2cf4c3d1311bc/href</a></iframe><p>The above function is declared with the async keyword, defining it as a coroutine. In the above code in line 3 we can see that here we are using the async API of httpx . async with httpx.AsyncClient() is the httpx context managed client for making async HTTP calls. Here, GET request is made with the await keyword, telling Python that this is a point where it can suspend execution and do something else. Once we receive the response then the code remains the same as sync call.</p><h4>7. Compare the Performance</h4><p>Now our async API is ready to be tested. Let us re-run the application. We can now see two endpoints for our application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SEAt8xVUM0OZwuPXGNM5Ug.png" /><figcaption>sample-FastAPI-application</figcaption></figure><p>Let us run the async endpoint and compare the results.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_SHmnTVSa5SEm5PR5RgPlA.png" /><figcaption>async-API</figcaption></figure><p>As we can see that using Async IO we are able to get the response in 0.58 sec which earlier took around 2 sec</p><h4><strong>Conclusion</strong></h4><p>In this tutorial, we saw how we can take advantage of the Async IO style of programming in FastAPI to improve the performance of our REST APIs. If we have a scope in our application to use Async IO, then we should give it a try.</p><p>If you would like to refer to the full code, do check:</p><p><a href="https://github.com/sumanentc/python-sample-FastAPI-application">GitHub - sumanentc/python-sample-FastAPI-application: Sample Python application using FastAPI , Uvicorn and SQLAlchemy</a></p><h4>References &amp; Useful Readings</h4><ul><li><a href="https://www.python-httpx.org/">HTTPX</a></li><li><a href="https://fastapi.tiangolo.com/async/#asynchronous-code">Concurrency and async / await - FastAPI</a></li><li><a href="https://stackoverflow.com/questions/1050222/what-is-the-difference-between-concurrency-and-parallelism">What is the difference between concurrency and parallelism?</a></li><li><a href="https://docs.python.org/3/library/asyncio.html">asyncio - Asynchronous I/O</a></li><li><a href="https://www.python.org/dev/peps/pep-0492/">PEP 492 -- Coroutines with async and await syntax</a></li><li><a href="https://github.com/Hipo/university-domains-list">GitHub - Hipo/university-domains-list: University Domains and Names Data List &amp; API</a></li><li><a href="https://apipheny.io/free-api/">Free API - Huge List of Public APIs For Testing [No Key] - Apipheny</a></li><li><a href="https://realpython.com/python-concurrency/">Speed Up Your Python Program With Concurrency - Real Python</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1bd809916130" width="1" height="1" alt=""><hr><p><a href="https://medium.com/cuddle-ai/concurrency-with-fastapi-1bd809916130">Concurrency with FastAPI</a> was originally published in <a href="https://medium.com/cuddle-ai">Crux Intelligence</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Querying Cache in Apache Ignite]]></title>
            <link>https://medium.com/cuddle-ai/querying-cache-in-apache-ignite-ecd6d350c52a?source=rss-54106b197f0a------2</link>
            <guid isPermaLink="false">https://medium.com/p/ecd6d350c52a</guid>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[cuddleengineering]]></category>
            <category><![CDATA[apache-ignite]]></category>
            <category><![CDATA[search-engines]]></category>
            <category><![CDATA[cuddlepe]]></category>
            <dc:creator><![CDATA[Suman Das]]></dc:creator>
            <pubDate>Mon, 13 Dec 2021 13:12:25 GMT</pubDate>
            <atom:updated>2021-12-13T13:12:25.121Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/752/1*mfXP4xJ5150c7SCrR7nAIA.jpeg" /><figcaption>Cache Queries</figcaption></figure><p><strong>Apache Ignite </strong>is a memory-centric distributed database, caching, and processing platform for transactional, analytical, and streaming workloads delivering in-memory speeds at the petabyte scale.</p><p><strong>Ignite</strong> provides an in-memory data store where each node in an <strong>Ignite</strong> cluster by default stores data in RAM. The data is kept in off-heap storage to ensure low latency and consistent access times. The system is multi-model, with the ability to support structured, semi-structured, and unstructured data.</p><p>Apache Ignite offers an elegant query API with the following components:</p><ul><li><strong>Predicate-based Scan Query<br></strong>A scan query is a simple search query used to retrieve data from a cache in a distributed manner. When executed without parameters, a scan query returns all entries from the cache. Scan queries return entries that match a predicate, if specified. The predicate is applied to the remote nodes.</li><li><strong>ANSI 99-compliant SQL Query<br></strong>Apache Ignite SQL Grid is a distributed data grid where you can execute ANSI SQL-99-compliant SQLs (SELECT, UPDATE, INSERT, MERGE, and DELETE queries) to manipulate a cache.</li><li><strong>Lucene Index-based Text Query<br></strong>Lucene is a full-text search library in Java which makes it easy to add search functionality to an application or website. Ignite supports full-text queries based on the Apache Lucene engine.</li><li><strong>Continuous query <br></strong>Continuous queries allow registering a remote filter and a local listener for cache updates. Once the cache is updated and passes the filter criteria, then the cache update events will be sent to the node that executed the query, and the local listener will be notified. This helps us to take action in case the cache is updated on Ignite Cluster.</li></ul><p>For SQL queries, Ignite supports in-memory indexing, so all the data lookups are extremely fast. If we are caching our data in off-heap memory, then query indexes will also be cached in off-heap memory as well.</p><p>In this tutorial, we will learn how to query the data stored in the <strong>Cache</strong> of <a href="https://ignite.apache.org/docs/latest/"><strong>Apache Ignite</strong></a><strong> </strong>using query API. We will not cover Continuous Query in this tutorial.</p><p>We will use the following steps to explore Query-based search in Apache Ignite Cache:</p><ol><li>Integrating Apache Ignite Server Node With Spring Boot</li><li>Enabling Caching</li><li>Query and QueryCursor</li><li>Scan Queries</li><li>SQL Queries</li><li>Text Queries</li><li>Testing the Application</li></ol><h4>Prerequisites</h4><p>For this tutorial, we are using JDK 1.8 and Spring Boot 2.5.6 project with the following dependencies:</p><pre>spring-boot-starter<br>spring-boot-starter-web<br>spring-boot-starter-test</pre><p>Here, we will use <a href="https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html"><strong>Maven</strong></a> for dependency management. Along with the above dependencies, we will also use <a href="https://github.com/springdoc/springdoc-openapi"><strong>springdoc-openapi</strong></a> to automatically generate the OpenAPI 3 specification docs for our API. For that, we simply need to add the springdoc-openapi-ui dependency to our <a href="https://maven.apache.org/guides/introduction/introduction-to-the-pom.html"><strong>pom.xml</strong></a><strong> :</strong></p><pre>&lt;dependency&gt;     <br>  &lt;groupId&gt;org.springdoc&lt;/groupId&gt;<br>  &lt;artifactId&gt;springdoc-openapi-ui&lt;/artifactId&gt;<br>   &lt;version&gt;1.5.2&lt;/version&gt;<br>&lt;/dependency&gt;</pre><h4>1. Integrating Apache Ignite Server Node With Spring Boot</h4><p>The Apache Ignite server nodes act as containers for data and computations. Once interconnected, the server nodes will represent a distributed database (or data grid) that stores the data, participates in queries processing, compute execution, stream processing, and so on.</p><p><strong>Ignite</strong> provides an implementation of JCache (JSR 107) specification. <strong>JCache</strong> provides a very simple and powerful API for data access. However, the specification omits any details about data distribution and consistency to allow vendors enough freedom for their own implementations. Here, we will use <strong>org.apache.ignite.cache.CacheManager</strong> to interact with the Cache.</p><p>We will use Spring XML-based configuration for Ignite.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/140f476a569ed5d231cf5e834d0c8141/href">https://medium.com/media/140f476a569ed5d231cf5e834d0c8141/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9a904719984d15ab1157b99c7beaf49a/href">https://medium.com/media/9a904719984d15ab1157b99c7beaf49a/href</a></iframe><p>The above codes create a CacheManager instance using the Ignite configuration XML file path specified in igniteConfigPath variable.</p><p>We also need to add the following Maven dependencies in our Spring Boot app to enable Ignite-based caching.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d610ff3abd0772251ed6b1e4596f655a/href">https://medium.com/media/d610ff3abd0772251ed6b1e4596f655a/href</a></iframe><p>Here, we are using GridGain community edition version: <strong>8.8.10. </strong>Once we are done with the above configuration, then we can start the Ignite node in embedded mode, that is, in the same JVM where the application is running.</p><h4>2. Enabling Caching</h4><p>After we have the Spring Boot app with embedded Ignite node configured, then we need to define our Cache. Here, we will define a sample <strong>Player</strong> cache, which we will use for querying purposes. We are using XML-based configuration to define our cache.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e574343d13541d0d0dee00370619f331/href">https://medium.com/media/e574343d13541d0d0dee00370619f331/href</a></iframe><p>Once we declare our Player cache, then we have to define it.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5118001e1e677a8557903df8d9971c03/href">https://medium.com/media/5118001e1e677a8557903df8d9971c03/href</a></iframe><p>The above code does the following:</p><p>We started off by defining the Player class with some attributes, which we have declared in our cache configuration. We have used the annotation <a href="https://ignite.apache.org/releases/latest/javadoc/org/apache/ignite/cache/query/annotations/QuerySqlField.html"><strong>@QuerySqlField</strong></a><strong> </strong>on fields, which we will use for SQL queries. All fields which need to be involved in SQL clauses must have this annotation. We have also used the annotation <a href="https://ignite.apache.org/releases/latest/javadoc/org/apache/ignite/cache/query/annotations/QueryTextField.html"><strong>@QueryTextField </strong></a>on fields that need to be indexed for full-text search using Lucene.</p><h4>3. Query and QueryCursor</h4><p>IgniteCache has several query methods, all of which require some subclass of the Query class. When we query IgniteCache it returns the QueryCursor.Query abstract class that represents an abstract paginated query to be executed on the distributed cache. We can also set the page size for the returned cursor via Query.setPageSize(...) method (default is 1024).</p><p>QueryCursor class represents the query result set and allows for transparent page-by-page iteration. Whenever we start iterating over the last page, it will automatically request the next page in the background. For cases when pagination is not needed, we can use the QueryCursor.getAll() method, which will fetch the whole query result and store it in a collection. If we use QueryCursor.getAll() method, it will also automatically close the cursor in the background.</p><p>To explore the search queries we will create a class called PlayerService. This will contain various methods required for the search operation.</p><h4>4. Scan Queries</h4><p>Apache Ignite’s key-value pair API is used to store objects in a cache and retrieve values using keys. It is a query API that lets us query objects using expressions. ScanQuery provides one of the implementations of Query API, which allows us to query the cache in distributed form using some user-defined predicate.</p><p>Let us explore the ScanQuery API now.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e4871efd30fb1b90041726c15f877a02/href">https://medium.com/media/e4871efd30fb1b90041726c15f877a02/href</a></iframe><p>The above code does the following:</p><ul><li>We started off by creating a public method called scanQuerySearch in line 1 with search text input, which we will use to search in Player cache.</li><li>In line 3, we retrieved an instance of the Player cache from Ignite node.</li><li>Then we created an instance of ScanQuery in line 4, which takes IgniteBiPredicate as an argument. IgniteBiPredicate is a functional interface that accepts two parameters and returns a boolean. It is usually used to filter a collection of objects and can be used in lambda expression also. Here, we are going to use a Java 8 lambda expression to represent IgniteBiPredicate. The k represents the key of the cache and v is the value of Player. Our IgniteBiPredicate returns True only if any player stored in the cache qualifies the expression player.getTeam() EQ input text. The result is returned as a QueryCursor. It stores all qualified entries (key-value pairs).</li><li>From line 7 to 14 we iterated through the results to locate if such an entry exists in the cache and then populated a local List of filtered Players.</li><li>In line 15 we are returning the filtered list of Players to the caller.</li></ul><p>The ScanQuery goes over each cache entry and applies the predicate, which may not always be the most efficient way to query objects, especially if the data size is large.</p><h4>5. SQL Queries</h4><p>The SQLQuery API supports ANSI-99 SQL queries against caches. This API allows SQL joins against collocated entries on the same node as well as non-collocated distributed nodes.</p><p>To tell Apache Ignite which fields are accessible for SQL queries, we need to define the metadata. Apache Ignite’s <strong>@</strong><a href="https://ignite.apache.org/releases/latest/javadoc/org/apache/ignite/cache/query/annotations/QuerySqlField.html"><strong>QuerySqlField</strong></a> annotation does the trick. We can even index the fields for a faster query by setting the annotation value: <strong>@</strong><a href="https://ignite.apache.org/releases/latest/javadoc/org/apache/ignite/cache/query/annotations/QuerySqlField.html"><strong>QuerySqlField</strong></a><strong>(index=true)</strong>. We have used this annotation in Step 2 for our Player cache to enable SQL queries.</p><p>Let us explore the SQLQuery API now.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cb3f98e270dff8f5470848fc6481a539/href">https://medium.com/media/cb3f98e270dff8f5470848fc6481a539/href</a></iframe><p>The above code does the following:</p><ul><li>We started off by creating a public method called sqlQuerySearch in line 1 with search text input, which we will use to search in Player cache.</li><li>In line 3, we retrieved an instance of the Player cache from Ignite node.</li><li>Next, we extracted the table name from the cache in line 4 to 5. Once we have the table name, then we can form a SQL Query using the table name to search the cache.</li><li>We created an instance of SqlFieldsQuery in line 6.<br>The SqlFieldsQuery class provides another implementation of Query API. It is used for executing SQL statements and navigating through the results. It accepts a standard SQL query as its constructor​ parameter and executes it. SqlFieldsQuery is executed through the IgniteCache.query(SqlFieldsQuery) method, which returns a QueryCursor.</li><li>From line 8 to 9 we iterated and extracted the results using getAll() method. If an entry exists in the cache, then we collect the results and return the response to the caller.</li></ul><p>In replicated mode, SQL joins execute fast as all entries are replicated in all cluster nodes. However, in partitioned mode, a few nodes may not contain the primary backup, hence, distributed joins become complex and expensive. If we are planning to use distributed joins on partitioned tables then we should use <a href="https://ignite.apache.org/docs/latest/SQL/distributed-joins">Colocated joins</a> or <a href="https://ignite.apache.org/docs/latest/SQL/distributed-joins#hash-joins">Hash joins</a>.</p><h4>6. Text Queries</h4><p>The TextQuery API allows us to run a full-text search on stored objects in the cache. The TextQuery works on Lucene indexes. Elasticsearch and Apache Solr also use Lucene for indexing text.</p><p>Here too, we need to define the metadata to tell Apache Ignite which fields are to be enabled for Text search. The <a href="http://twitter.com/QueryTextField"><strong>@QueryTextField</strong></a> annotation enables indexing along with Text Search. Also, our cache configuration needs to enable indexing by setting the <strong>setIndexedTypes</strong>. We have already set this in Step 2 during our cache configuration.</p><p>Let us explore the TextQuery API now.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/43dc5b3d5760033fb656b294cc27ffec/href">https://medium.com/media/43dc5b3d5760033fb656b294cc27ffec/href</a></iframe><p>The above code does the following:</p><ul><li>In line 1 we created a method called textSearch, which takes a text as an input and then searches that text in Player cache. It searches the fields in Player cache annotated with <strong>QueryTextField</strong>.</li><li>Then in line 3, we retrieved an instance of the Player cache from Ignite node.</li><li>We created an instance of TextQuery in line 4. Once we have an instance of TextQuery and IgniteCache we called the method searchPlayers to search the text in the cache.</li><li>From line 25 to 29 we iterated through the results to locate if such an entry exists in the cache and then populated a local List of filtered Players.</li><li>In line 30 we are returning the filtered list of Players to the caller.</li><li>Similar to textSearch we also created a method called fuzzySearch from line 8 to 13 to perform Fuzzy search on Player cache. Fuzzy search determines whether there is any similarity between elements of the data. Let’s say we want to fetch players where the team name is ‘Barcelona’ or ‘Barcenola’. Then we just need to add a ‘~’ at the end of our search string and pass it to the TextQuery constructor. We are going to find the players where the text ‘Barcenola’ is present, and the fuzzy search will find a match between <strong>nola</strong> and <strong>lona</strong> and return all ‘Barcelona’ players.</li><li>We can also perform Fuzzy search on specific fields. For this reason we added another method called fuzzySearchOnSpecificField from line 15 to 20 which takes fieldName and text as an input.</li><li>In the previous methods <strong>textSearch</strong> and <strong>fuzzySearch</strong>, during search operation <strong>Ignite</strong> looked at all the indexes, name and team, to find data. However, we can also ask Ignite to look into specific index. We can see that in line 18 . In this method we ask <strong>Ignite</strong> to look at a specific index. It will search the input text only in the field provided by user. For example, if we are interested in name = ‘Neymar’, then the TextQuery can be configured as name:”Neymar”.</li></ul><p>It is advisable to use indexes for querying entries. However, one drawback is that an index itself takes up space and may slow down the data modification as every time we modify an entry, the index needs to be rebuilt.</p><h4>7. Testing the Application</h4><p>Till now we have written most of the code required to perform various kinds of searches in Ignite Cache. Now let us write some code to populate the cache at startup . The PlayerService class contains various methods required to explore Query API in Ignite. Let us also add the below method to populate the cache.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/04f9cb7633e0a6438c356e40b30d0bac/href">https://medium.com/media/04f9cb7633e0a6438c356e40b30d0bac/href</a></iframe><p>The above code populates the Player Cache with Player name and Team. We can use these values for testing purposes.</p><p>After we have populated our cache with some sample values, then we added a Controller to enable users to perform various search operations.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8a130de30adc6513d97c8631103802a9/href">https://medium.com/media/8a130de30adc6513d97c8631103802a9/href</a></iframe><p>The above code does the following:</p><p>It exposes API which can be used to test the various features of Ignite Query API that we discussed so far. It uses the PlayerService class to perform the search operations.</p><p>Once we build the project using mvn clean package then we can start the application using java -jar target/ignite-poc*.jar command.</p><p>After the application is started successfully, then we can use the Swagger-UI to test the application. The Swagger-UI comes up at <strong>http://localhost:9051/swagger-ui.html</strong> in local.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cSgOvjKx_UbTKkmCk07zQg.png" /><figcaption>Swagger-UI</figcaption></figure><p><strong>7.1 Search Player Cache with team name as input using SQLQuery API</strong></p><p>Here, we are using SQLQuery to search all the Players whose team contains the given text : “Barcelona”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w1bMc3ODOh8WQdR2UzjVvA.png" /><figcaption>SQLQuery</figcaption></figure><p><strong>7.2 Search Player Cache with team name or player name that matches the text input using TextQuery API</strong></p><p>Here, we are using TextQuery to search all the Players where either name or team contains the given text: “United”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mtz7gKjB9uRMzVknWLzVng.png" /><figcaption>TextQuery</figcaption></figure><p><strong>7.3 Search Player Cache with team name that matches the text input using ScanQuery API</strong></p><p>Here, we are using ScanQuery to search all the Players whose team contains the given text: “Barcelona”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*75gAoIwbliEsvA0Z2kFR6A.png" /><figcaption>SsanQuery</figcaption></figure><p><strong>7.4 Search Player Cache with team name or player name that matches the text input using TextQuery with Fuzzy search enabled</strong></p><p>Here, we are using using Fuzzy TextQuery to search all the Players where either name or team contains the given text: ”Barcenona”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X1JzBW_eOFRGYjytZ6B2Vg.png" /><figcaption>Fuzzy-Search</figcaption></figure><p><strong>7.5 Search Player Cache with input field name and text using TextQuery API</strong></p><p>Here, we are using Fuzzy TextQuery to search all Players with fieldName where fieldName contains the given text: “Messi”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8la48Y3LP5mu1u8_nThemQ.png" /><figcaption>TextQuery-with-field</figcaption></figure><p>If you would like to refer to the full code, do check:</p><p><a href="https://github.com/sumanentc/Ignite-Querying-Cache">GitHub - sumanentc/Ignite-Querying-Cache: Sample Spring Boot application to query the data stored in the Cache of Apache Ignite using query API.</a></p><h4>Conclusion</h4><p>To perform search operation on Key-Value pair, Cache is a great feature provided by Ignite. If Query API is used properly then the performance is also at par with key-value pair search. If there is a need for key-value search along with SQL Query support then Apache Ignite is one of the best choices currently available.</p><h4>References &amp; Useful Readings</h4><ul><li><a href="https://ignite.apache.org/use-cases/caching/jcache-provider.html">JCache Provider - Apache Ignite</a></li><li><a href="https://ignite.apache.org/docs/latest/extensions-and-integrations/spring/spring-boot">Apache Ignite With Spring Boot</a></li><li><a href="https://www.gridgain.com/docs/latest/getting-started/what-is-gridgain">What Is GridGain?</a></li><li><a href="https://ignite.apache.org/docs/latest/index">Apache Ignite Documentation</a></li><li><a href="https://apacheignite.readme.io/docs/cache-queries">Cache Queries</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ecd6d350c52a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/cuddle-ai/querying-cache-in-apache-ignite-ecd6d350c52a">Querying Cache in Apache Ignite</a> was originally published in <a href="https://medium.com/cuddle-ai">Crux Intelligence</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>