<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alejandro Garcia </title>
    <description>The latest articles on DEV Community by Alejandro Garcia  (@gerale30).</description>
    <link>https://dev.to/gerale30</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4072013%2F5c9157e2-c710-41bb-b487-0a35e158d5f5.jpg</url>
      <title>DEV Community: Alejandro Garcia </title>
      <link>https://dev.to/gerale30</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gerale30"/>
    <language>en</language>
    <item>
      <title>The Problem I Saw From the Floor, Not From a Laptop</title>
      <dc:creator>Alejandro Garcia </dc:creator>
      <pubDate>Fri, 28 Aug 2026 18:58:18 +0000</pubDate>
      <link>https://dev.to/gerale30/the-problem-i-saw-from-the-floor-not-from-a-laptop-35hk</link>
      <guid>https://dev.to/gerale30/the-problem-i-saw-from-the-floor-not-from-a-laptop-35hk</guid>
      <description>&lt;p&gt;&lt;em&gt;Why I started building Materia AI before it was anywhere near finished.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There's a specific kind of bad moment that happens on a restaurant floor. A table orders the salmon. You put it in. Ten minutes later the kitchen sends it back — we're out. Now you're the one walking back to that table to explain it, and if you're unlucky, you already told them how good it was.&lt;/p&gt;

&lt;p&gt;That moment isn't rare. It happens most shifts, in some form. Not because anyone's bad at their job — the chefs are stretched thin managing inventory in real time, and the news that something ran out almost always breaks in the middle of service, never before it. By the time it reaches the floor, a waiter has usually already offered the thing that's gone.&lt;/p&gt;

&lt;p&gt;I've been on that floor for years now, alongside writing code. And after watching that same gap play out shift after shift — the kitchen struggling to track what's left, the waiters left out of the loop until it's already a problem in front of a customer — I stopped seeing it as just part of the job and started seeing it as something I actually knew how to fix.&lt;/p&gt;

&lt;p&gt;That's the honest origin of Materia AI. Not a hackathon idea, not a "what if" I dreamed up staring at a blank editor. It's a problem I watched happen in front of me, over and over, from the side of the job most software never gets built by.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it actually stands right now
&lt;/h2&gt;

&lt;p&gt;I want to be straight about this instead of dressing it up: Materia AI isn't finished. The backend — the part that takes a menu item and turns it into a real, bilingual description a customer can understand at a glance — works end to end. I've run it, watched it produce something usable, and trusted the pattern enough to build on top of it. The frontend, the actual page someone would click around on, still isn't there yet.&lt;/p&gt;

&lt;p&gt;So instead of sitting on it until it's "done" — whatever version of done would've made me comfortable enough to show it — I pulled out the backend piece and put it up on Gumroad. Not as a polished product with a bow on it, but as a working slice of the pattern: the part that actually solves the core problem, available now, while the rest keeps getting built in the open.&lt;/p&gt;

&lt;p&gt;The reasoning is simple. If you've watched a version of this same problem happen in your own work — some gap where the people closest to the issue are the last to know about it — you probably don't need to wait for my finished app to go do something about it. You need a working starting point. That's what I put out there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm telling it this way
&lt;/h2&gt;

&lt;p&gt;I could've waited to write about Materia AI until it had a homepage, a demo video, the whole thing polished for a launch post. I decided not to, on purpose. The version of this project that's useful to someone else right now isn't the finished one — it's the honest one. The floor-level problem, the working piece that came out of actually solving it, and the rest still in progress.&lt;/p&gt;

&lt;p&gt;If you're building something and you're waiting to talk about it until it's "ready," I'd push back on that a little. The interesting part isn't the finished screenshot. It's the moment you noticed the problem was real enough to act on.&lt;/p&gt;

&lt;p&gt;I'm continuing to build Materia AI in the open — backend first, frontend next, and I'll keep sharing what that actually looks like as it happens. If you want the working piece of the pattern to start from, it's on &lt;a href="https://saljazz5.gumroad.com/l/ujogq" rel="noopener noreferrer"&gt;Gumroad&lt;/a&gt;. And if you're working on something similar, or want another set of hands on a backend/AI integration problem, I'm open to freelance and full-time work — you can find more of what I've built on &lt;a href="https://github.com/GerAle30" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>career</category>
      <category>buildinpublic</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The AI API Will Fail on You — Here's How I Structure FastAPI to Handle It</title>
      <dc:creator>Alejandro Garcia </dc:creator>
      <pubDate>Fri, 28 Aug 2026 17:25:35 +0000</pubDate>
      <link>https://dev.to/gerale30/the-ai-api-will-fail-on-you-heres-how-i-structure-fastapi-to-handle-it-53ia</link>
      <guid>https://dev.to/gerale30/the-ai-api-will-fail-on-you-heres-how-i-structure-fastapi-to-handle-it-53ia</guid>
      <description>&lt;p&gt;A few weeks ago I was demoing a small FastAPI service that called an LLM to summarize text. Worked great in every test I ran. Then, live, the provider API took almost 9 seconds to respond and my endpoint just... hung. No error, no timeout, just silence while I sat there making small talk to fill the gap.&lt;/p&gt;

&lt;p&gt;That was the moment it clicked for me: when you put an AI API behind your own API, you've inherited &lt;em&gt;two&lt;/em&gt; sets of failure modes — yours and theirs. Rate limits, timeouts, malformed JSON in the response, the model deciding to add a chatty preamble before the JSON you asked for. None of that is hypothetical. It's Tuesday.&lt;/p&gt;

&lt;p&gt;Here's how I've learned to structure a FastAPI backend so that when (not if) the AI call misbehaves, it fails in a way you control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't let the router touch the AI call directly
&lt;/h2&gt;

&lt;p&gt;The most common pattern I see (and the one I used to write) is catching exceptions right inside the endpoint function. It works until you have more than one endpoint calling the model, and suddenly your error handling is copy-pasted five times with five slightly different bugs.&lt;/p&gt;

&lt;p&gt;Instead, the router's only job is to receive the request, validate it, and hand it off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/summarize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;summarize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SummarizeRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SummaryService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the "what could go wrong" logic lives one layer down, in the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the AI provider's failures their own exception types
&lt;/h2&gt;

&lt;p&gt;Generic &lt;code&gt;except Exception&lt;/code&gt; blocks tell you nothing about &lt;em&gt;why&lt;/em&gt; something failed, which makes writing a sane HTTP response back to your client impossible. I define a small hierarchy instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AIProviderError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Base error for anything the model provider throws at us.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AIProviderTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AIProviderError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AIProviderRateLimited&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AIProviderError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AIResponseMalformed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AIProviderError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;The provider responded, but not with what we asked for.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service layer catches the provider SDK's raw exceptions and re-raises them as one of these. A dedicated exception handler at the app level then maps each one to the right HTTP status: 504 for a timeout, 429 for rate limits, 502 when the model's output doesn't parse. Your client gets something actionable instead of a raw 500 and a stack trace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Never trust the model's output — validate it like a client request
&lt;/h2&gt;

&lt;p&gt;This is the one people skip. We're all trained to validate incoming requests with Pydantic, but the response coming &lt;em&gt;back&lt;/em&gt; from the AI is just as untrusted. Models occasionally return almost-JSON, or valid JSON with a field renamed, or an extra sentence wrapped around it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SummaryResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;key_points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_model_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SummaryResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SummaryResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;AIResponseMalformed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it doesn't fit the schema, it doesn't get past this line — full stop. That one function has saved me from shipping garbage data downstream more times than I'd like to admit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern, not the code
&lt;/h2&gt;

&lt;p&gt;None of this is complicated once you see it laid out, but I didn't see it laid out anywhere when I needed it — I pieced it together across a few projects, mostly the hard way, after enough live demos went sideways. Routers that stay thin, a service layer that owns the provider calls, a typed exception hierarchy, and Pydantic validating both directions instead of just one.&lt;/p&gt;

&lt;p&gt;I ended up packaging exactly this structure — routers, services, schemas, and the error-handling layer between your FastAPI app and an AI model — into a small starter template so I'd stop rebuilding it from scratch every time. If you want the full working reference instead of piecing it together yourself, you can grab it &lt;a href="https://saljazz5.gumroad.com/l/ujogq" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Worth noting: it's a structural pattern, not a full production app — no JWT auth or database included, on purpose, so it stays easy to read and adapt to whatever you're building.&lt;/p&gt;

&lt;p&gt;I'm also currently open to backend/full-stack roles (Python, FastAPI, Flask, AI integrations) and freelance work — feel free to check out more of my code on &lt;a href="https://github.com/GerAle30" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Kept Burning API Credits Every Time I Ran My Test Suite — Here's How I Fixed It</title>
      <dc:creator>Alejandro Garcia </dc:creator>
      <pubDate>Sat, 22 Aug 2026 21:42:55 +0000</pubDate>
      <link>https://dev.to/gerale30/i-kept-burning-api-credits-every-time-i-ran-my-test-suite-heres-how-i-fixed-it-5gh8</link>
      <guid>https://dev.to/gerale30/i-kept-burning-api-credits-every-time-i-ran-my-test-suite-heres-how-i-fixed-it-5gh8</guid>
      <description>&lt;p&gt;I ran my test suite four times in a row one afternoon while debugging a flaky endpoint, and by the time I checked my OpenAI usage dashboard I'd burned through more credits than I meant to spend that whole week. Nothing had even broken — the tests were just... running, and every single one of them was making a real call to a real model. That's when it clicked that I'd built something that worked, but that I hadn't actually built something I could &lt;em&gt;test cheaply&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you're building anything backend-heavy on top of an LLM — a FastAPI service, a Flask app, whatever — this is a problem you'll hit eventually. Your tests either hit the real API (slow, costs money, occasionally flaky because the model's response isn't 100% deterministic) or you don't really test the AI-calling parts at all and just cross your fingers in production. Neither is great.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens
&lt;/h2&gt;

&lt;p&gt;The root cause is usually structural, not a testing problem. If your route handler does something like "receive request → build prompt → call OpenAI → parse response → return," then the only way to test any of that logic is to actually call OpenAI, because the AI call is tangled up with everything else.&lt;/p&gt;

&lt;p&gt;The fix is the same one you'd use for any external dependency: pull it out behind its own boundary. In the backend template I put together for my own projects, I split things into three layers — routers, services, and schemas — specifically so the "call the model" part lives in exactly one place and can be swapped out in tests.&lt;/p&gt;

&lt;p&gt;A simplified version of that service layer looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# services/ai_service.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarize the following text in one sentence.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the router just calls it — it doesn't know or care that there's an API call happening underneath:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# routers/summary.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;schemas.summary&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SummaryRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SummaryResponse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;services.ai_service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;generate_summary&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/summarize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SummaryResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;summarize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SummaryRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SummaryResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing it without spending a cent
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;generate_summary&lt;/code&gt; is its own importable function, I can patch it directly in tests instead of hitting the network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# test_summary.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.testclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TestClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;routers.summary.generate_summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_summarize_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock_generate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;mock_generate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This is a mocked summary.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/summarize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Some long text here...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This is a mocked summary.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That test runs in milliseconds, costs nothing, and is fully deterministic — no more "wait, did that test fail because of a bug or because the model phrased it differently this time?"&lt;/p&gt;

&lt;p&gt;One gotcha that trips people up here: notice the patch target is &lt;code&gt;routers.summary.generate_summary&lt;/code&gt;, not &lt;code&gt;services.ai_service.generate_summary&lt;/code&gt;. Since the router does &lt;code&gt;from services.ai_service import generate_summary&lt;/code&gt;, it holds its own local reference to that function — patching the original module doesn't touch it. The rule of thumb with &lt;code&gt;unittest.mock&lt;/code&gt; is to always patch where the name is &lt;em&gt;used&lt;/em&gt;, not where it's &lt;em&gt;defined&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I also like adding one or two tests that check what happens when the AI call &lt;em&gt;fails&lt;/em&gt; — timeout, rate limit, malformed response — since that's the stuff that actually breaks in production and almost never gets tested:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;routers.summary.generate_summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_summarize_handles_upstream_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mock_generate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;mock_generate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;side_effect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upstream timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/summarize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of this is exotic — it's the same mocking pattern you'd use for any third-party API. The only real "trick" is disciplined separation: keep the model call in its own service function, keep validation in Pydantic schemas, and keep the router thin. Once that boundary exists, testing (and later, swapping providers, adding caching, adding retries) gets a lot less painful.&lt;/p&gt;

&lt;p&gt;This is basically the skeleton I now reuse every time I start a new FastAPI + AI project, so I packaged it as a small starter template — routers/services/schemas separated like above, Pydantic validation, and consistent error handling baked in from the start. It intentionally does &lt;em&gt;not&lt;/em&gt; include auth or a database, since those tend to be project-specific and I'd rather hand you a clean pattern than force my opinions on you there. If that'd save you some setup time, you can grab it &lt;a href="https://saljazz5.gumroad.com/l/ujogq" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm also currently open to freelance and full-time backend/fullstack work (Python, FastAPI, Flask, AI integrations) — if you're hiring or know someone who is, my code is at &lt;a href="https://github.com/GerAle30" rel="noopener noreferrer"&gt;github.com/GerAle30&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>testing</category>
      <category>ai</category>
    </item>
    <item>
      <title>What I Learned Turning a Side Project Into a Sellable Dev Tool</title>
      <dc:creator>Alejandro Garcia </dc:creator>
      <pubDate>Mon, 17 Aug 2026 17:00:35 +0000</pubDate>
      <link>https://dev.to/gerale30/what-i-learned-turning-a-side-project-into-a-sellable-dev-tool-35ie</link>
      <guid>https://dev.to/gerale30/what-i-learned-turning-a-side-project-into-a-sellable-dev-tool-35ie</guid>
      <description>&lt;p&gt;I built Materia AI to solve a real problem I saw firsthand — messy menu and inventory management from my years working as a waiter. It's still growing. But somewhere along the way, I realized the &lt;em&gt;backend pattern&lt;/em&gt; I kept reusing — the FastAPI structure, the AI service layer — was useful on its own, separate from the specific product.&lt;/p&gt;

&lt;p&gt;So I pulled it out and packaged it. Here's what that process actually taught me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cutting scope until it's honest
&lt;/h2&gt;

&lt;p&gt;My first instinct was to make the package look impressive — throw in auth, a database layer, the works. Then I stopped and asked: does the code actually have that? It didn't. So I cut the description down to exactly what exists, nothing more. It felt like a downgrade in the moment. It's actually the only version I'd feel fine standing behind if someone asked for a refund.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the README for a stranger, not for future-me
&lt;/h2&gt;

&lt;p&gt;My own project notes assume I remember context. A README for someone who's never seen the code needs to explain why, not just what — why routers stay thin, why the AI call lives in its own service, why the response gets parsed defensively instead of trusted blindly. Writing that down clarified my own thinking more than I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code isn't the product — the packaging is
&lt;/h2&gt;

&lt;p&gt;Anyone could write similar FastAPI code in an afternoon. What they're actually paying for is not having to make the fifty small decisions I already made and documented. That reframing changed how I wrote everything, from the file structure down to the comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting cheap on purpose
&lt;/h2&gt;

&lt;p&gt;I priced it low to start. Not because the pattern isn't worth more, but because the real goal right now is learning what resonates before optimizing for revenue. Feedback first, pricing later.&lt;/p&gt;

&lt;p&gt;None of this is groundbreaking business advice. It's just what actually happened, in order, the first time I tried it.&lt;/p&gt;

&lt;p&gt;If you want to see what came out of it, it's &lt;a href="https://saljazz5.gumroad.com/l/ujogq" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm a backend developer working in Python, FastAPI, and AI integrations, currently open to freelance work — find me on &lt;a href="https://github.com/GerAle30" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I Actually Use Claude as a Backend Engineer 🤖</title>
      <dc:creator>Alejandro Garcia </dc:creator>
      <pubDate>Thu, 13 Aug 2026 22:47:44 +0000</pubDate>
      <link>https://dev.to/gerale30/how-i-actually-use-claude-as-a-backend-engineer-24fe</link>
      <guid>https://dev.to/gerale30/how-i-actually-use-claude-as-a-backend-engineer-24fe</guid>
      <description>&lt;p&gt;For a while, I treated Claude like a fancy autocomplete — ask a question, paste the answer, move on. That approach worked, but it wasn't until I slowed down and treated it more like a thinking partner that things actually clicked for my day-to-day backend work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mistake Most People Make 🙈
&lt;/h2&gt;

&lt;p&gt;The biggest mistake I see (and made myself) is asking for a finished answer instead of walking through the problem. When I just say "write me a FastAPI endpoint," I get generic code. When I explain the actual constraint — the schema, the edge case, why the last approach broke — I get something I can actually ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Small Habits That Made a Big Difference ✨
&lt;/h2&gt;

&lt;p&gt;💬 Give context before you give instructions. A couple sentences about your stack and goal changes the quality of the output completely.&lt;/p&gt;

&lt;p&gt;🧠 Ask it to explain its reasoning, not just produce code. If I don't understand why a fix works, I ask before I merge it.&lt;/p&gt;

&lt;p&gt;🔁 Treat the first answer as a draft. The second or third pass, after pushing back with more detail, is usually the one that ends up in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude vs. Gemini: My Honest, No-Hype Take ⚖️
&lt;/h2&gt;

&lt;p&gt;I've used both, and I'll skip the hype. For backend work — API design, debugging tricky logic, reasoning through a schema — Claude has been more consistent for me, especially on longer, multi-step problems where it keeps track of what we already decided a few messages back. Gemini has its strengths too, particularly when I need something wired up quickly, but for the kind of careful, back-and-forth problem solving backend work usually needs, Claude is what I reach for first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters Beyond Just Being Productive 🌱
&lt;/h2&gt;

&lt;p&gt;This isn't about writing less code — it's about spending less time stuck and more time building. As a freelancer, that difference shows up directly in what I can deliver to clients and how fast I can do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next 🚀
&lt;/h2&gt;

&lt;p&gt;I'm continuing to build with Claude in the loop on real client projects, and I'll keep sharing what actually works (and what doesn't) as I go.&lt;/p&gt;

&lt;p&gt;👉 Check out my work and get in touch here: &lt;a href="https://www.upwork.com/freelancers/~01cb9249fa00d01405" rel="noopener noreferrer"&gt;Upwork profile&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>Building My First AI Agent with Python and Flask: What I Learned</title>
      <dc:creator>Alejandro Garcia </dc:creator>
      <pubDate>Tue, 11 Aug 2026 02:10:59 +0000</pubDate>
      <link>https://dev.to/gerale30/building-my-first-ai-agent-with-python-and-flask-what-i-learned-1l5m</link>
      <guid>https://dev.to/gerale30/building-my-first-ai-agent-with-python-and-flask-what-i-learned-1l5m</guid>
      <description>&lt;p&gt;Before I became a developer, I worked as a waiter for over 6 years. And if there's one thing I saw repeat itself over and over during that time, it was how poorly managed menus and inventory are in most restaurants: dishes kept being sold without available ingredients, food waste from lack of stock visibility, and menu decisions made on gut feeling instead of real data.&lt;/p&gt;

&lt;p&gt;When I started coding, that experience stuck with me. I knew I wanted to build something that tackled this exact problem, and AI seemed like the perfect tool to help restaurants make better decisions about their menu and inventory in real time. That's how Materia AI was born, and in this post I'll walk you through how I built the first version of the agent using Python and Flask.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack I chose
&lt;/h2&gt;

&lt;p&gt;For the backend I used Flask, mainly because I wanted something lightweight and fast to iterate on while I was still figuring out the business logic. I didn't need the full structure of Django, and I wanted direct control over each endpoint while experimenting with the AI integration.&lt;/p&gt;

&lt;p&gt;For the AI piece, I connected the backend to a language model that analyzes sales and inventory patterns, and generates recommendations (for example, which dishes to adjust or pull from the menu based on available stock).&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic architecture
&lt;/h2&gt;

&lt;p&gt;The flow is simple but effective:&lt;/p&gt;

&lt;p&gt;Frontend (React) -&amp;gt; Flask Endpoint (/api/analyze) -&amp;gt; AI API call -&amp;gt; Response processing -&amp;gt; JSON with recommendations -&amp;gt; Frontend&lt;/p&gt;

&lt;h2&gt;
  
  
  The core code
&lt;/h2&gt;

&lt;p&gt;Here's a simplified example of the endpoint that receives inventory and sales data, and returns AI-generated recommendations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/analyze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyze_inventory&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
    &lt;span class="n"&gt;menu_items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;menu_items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inventory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Analyze the following menu and available inventory.
    Menu: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;menu_items&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
    Inventory: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
    Suggest which dishes should be paused due to missing ingredients
    and which products are at risk of being wasted.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recommendation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The challenges I ran into
&lt;/h2&gt;

&lt;p&gt;Not everything was smooth. I had to solve a few real problems along the way: how to structure the prompt so the AI returned consistent, useful responses instead of generic text, how to handle API rate limits without the app crashing, and how to store the API key securely using environment variables instead of hardcoding it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting it to the fullstack side
&lt;/h2&gt;

&lt;p&gt;As a fullstack developer, I didn't want this to stay only on the backend. On the React side, a simple example of how this endpoint gets consumed would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getRecommendation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;menuItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/analyze&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;menu_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;menuItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inventory&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommendation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Building this taught me three key things. First, the best motivation for a technical project almost always comes from a real problem you lived close to. Second, integrating AI into an app isn't just about calling an API, it's about designing the prompt carefully and handling errors with care. And third, coming from a waiter role gave me an advantage I didn't expect: I understood the business problem better than many developers who've never been on the other side of the counter.&lt;/p&gt;

&lt;p&gt;If you work in the restaurant industry, or something similar happened to you in another field, I'd love to hear about it in the comments. And if you want to check out more of my projects, you can find me on GitHub: &lt;a href="https://github.com/GerAle30" rel="noopener noreferrer"&gt;https://github.com/GerAle30&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
