<?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: Pramod Kodag</title>
    <description>The latest articles on DEV Community by Pramod Kodag (@thesilentknight).</description>
    <link>https://dev.to/thesilentknight</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%2F1135958%2F78c4a455-9959-4376-8001-ecf496950e48.png</url>
      <title>DEV Community: Pramod Kodag</title>
      <link>https://dev.to/thesilentknight</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thesilentknight"/>
    <language>en</language>
    <item>
      <title>I built an API that roasts you, and every response is AI-generated</title>
      <dc:creator>Pramod Kodag</dc:creator>
      <pubDate>Mon, 22 Jun 2026 08:32:23 +0000</pubDate>
      <link>https://dev.to/thesilentknight/i-built-an-api-that-roasts-you-and-every-response-is-ai-generated-2b6f</link>
      <guid>https://dev.to/thesilentknight/i-built-an-api-that-roasts-you-and-every-response-is-ai-generated-2b6f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; I built &lt;a href="https://github.com/PramodTKodag/snark" rel="noopener noreferrer"&gt;Snark&lt;/a&gt;, an open-source REST API that serves AI-generated humor. Roasts, brutally honest commit messages, ELI5, corporate jargon, and around 25 other endpoints. Every response is a live LLM call, so you almost never get the same line twice. Here's how it works under the hood.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPramodTKodag%2Fsnark%2Fmain%2Fassets%2Fsnark-demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FPramodTKodag%2Fsnark%2Fmain%2Fassets%2Fsnark-demo.gif" alt="Snark returning live AI-generated responses from real curl calls" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Most joke APIs hand you a random line from a static list. That's fine for about ten requests, and then you've seen everything it has.&lt;/p&gt;

&lt;p&gt;I wanted the opposite. An API where every response is generated fresh by a language model, in the voice of a specific persona, and where the same endpoint almost never repeats a joke on you. Honestly it started as an excuse to learn provider fallback and caching properly, but I ended up using it more than I expected.&lt;/p&gt;

&lt;p&gt;Here are a few real responses from the running service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; localhost:8100/v1/wit/commit-message/ | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .response
fix: finally found the typo

&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; localhost:8100/v1/wit/bug-blame/ | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .response
The culprit behind the burnt toast is a rogue toaster wire,
sparked to life by a freak solar flare. Case closed.

&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"localhost:8100/v1/wit/explain-like-im-5/?q=quantum+physics"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .response
Quantum physics is like coloring with crayons, but the colors
can be &lt;span class="k"&gt;in &lt;/span&gt;many places at the same time.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Nothing exotic here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Django and Django REST Framework for the API&lt;/li&gt;
&lt;li&gt;PostgreSQL for personas and a response log&lt;/li&gt;
&lt;li&gt;Redis for caching and per-IP rate limiting&lt;/li&gt;
&lt;li&gt;Groq as the default model provider on its free tier, with Gemini and Claude as optional fallbacks&lt;/li&gt;
&lt;li&gt;Docker Compose, so a single &lt;code&gt;docker compose up&lt;/code&gt; brings the whole thing up with nothing external to provision&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Provider fallback is the part I care about
&lt;/h2&gt;

&lt;p&gt;If you lean on a single model provider, it will eventually let you down. Rate limits, content filters, the occasional 500. So Snark doesn't trust any one provider on its own.&lt;/p&gt;

&lt;p&gt;Every endpoint runs through one orchestrator. It tries the default provider, and if that goes wrong it walks down a chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_generate_with_fallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;primary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ProviderRegistry&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="k"&gt;try&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;primary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ContentFilterError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# The model refused. Soften the prompt and retry on the SAME provider
&lt;/span&gt;        &lt;span class="c1"&gt;# before giving up on it.
&lt;/span&gt;        &lt;span class="n"&gt;softened&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;system_prompt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;IMPORTANT: Keep it light and safe...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;try&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;primary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;softened&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temperature&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;),&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;ContentFilterError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ProviderError&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;except&lt;/span&gt; &lt;span class="n"&gt;ProviderError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="c1"&gt;# Primary is out. Walk the rest of the chain.
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fallback&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ProviderRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_fallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exclude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;primary&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="k"&gt;try&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;fallback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&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;ContentFilterError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ProviderError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ProviderError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All AI providers failed to generate a response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bit I'm happiest with is the content-filter branch. When a model refuses, the first instinct is to bail and hit the next provider, but a lot of the time the model just needs a calmer prompt. So before switching anything, it lowers the temperature, appends a "keep it safe" line, and asks the same provider again. That alone rescues a surprising number of requests, and it's cheaper than paying for a second provider's round trip.&lt;/p&gt;

&lt;p&gt;The other nice side effect is that a provider is just a class with a &lt;code&gt;generate()&lt;/code&gt; method. When I added Claude, it was one new file. The registry handles the ordering and the "don't retry the one that already failed" logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stopping it from repeating itself
&lt;/h2&gt;

&lt;p&gt;A generator that keeps repeating itself feels broken, even if every response is technically a fresh API call. I deal with this in the prompt rather than in code.&lt;/p&gt;

&lt;p&gt;Right before each call, Snark grabs the last 10 responses for that persona from the database and drops them into the system prompt as a "don't do these again" list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ResponseLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;persona&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;persona&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-created_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="n"&gt;ANTI_REPETITION_COUNT&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;anti_rep&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="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;IMPORTANT: Do NOT repeat or closely paraphrase any of these &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recent responses. Be completely original:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's cheap, the model doesn't have to remember anything between calls, and in practice it does a good job of keeping things varied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching without making everything identical
&lt;/h2&gt;

&lt;p&gt;This one's a bit of a contradiction. Caching saves money and latency, but the entire point of the service is that responses are unique. Cache too hard and you've built the static joke list I was trying to avoid.&lt;/p&gt;

&lt;p&gt;The middle ground I landed on is to cache by the exact shape of the request, but only for a few minutes. The key is a SHA-256 of &lt;code&gt;slug : user_input : mood&lt;/code&gt;, and it expires after five:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_response_cache_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mood&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;raw&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mood&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&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="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&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;wit:resp:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if two people hit &lt;code&gt;/roast/dave/?mood=spicy&lt;/code&gt; in the same minute, they share one result and I only pay for one call. Across different inputs, or the same input a few minutes later, you still get something new. Each response tells you which one you got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"persona"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The Honest Committer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"cached"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Personas instead of hardcoded prompts
&lt;/h2&gt;

&lt;p&gt;Every endpoint maps to a persona that lives in the database. A name, a system prompt, a tone, some rules, and its own &lt;code&gt;temperature&lt;/code&gt; and &lt;code&gt;max_tokens&lt;/code&gt;. "The Honest Committer" writes the commit messages, "The Feedback Villain" handles code review comments. Adding an endpoint is usually just adding a row, not writing code.&lt;/p&gt;

&lt;p&gt;On top of that there's an optional &lt;code&gt;?mood=&lt;/code&gt; parameter (sarcastic, deadpan, unhinged, wholesome, and so on) that overrides the tone, so one persona can say the same thing fifteen different ways.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few things I learned
&lt;/h2&gt;

&lt;p&gt;The one that bit me: my tests mock the model SDKs, which is the right call for fast, deterministic tests, but it also means a green test suite tells you nothing about whether a provider's real API still matches your code. When I started bumping dependencies I had to check the SDKs structurally instead of trusting the checkmark.&lt;/p&gt;

&lt;p&gt;Bundling Postgres and Redis into the compose file early was worth it too. Once &lt;code&gt;docker compose up&lt;/code&gt; brought up everything, the "how do I even run this" questions disappeared.&lt;/p&gt;

&lt;p&gt;And the thing that took longest to accept is that the jokes were the easy part. The fallback, the retries, the anti-repetition, the caching, that's what actually makes it feel like a real service rather than a demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;It's open source under AGPL-3.0 and free. The endpoints don't need any auth or keys. You only need a free Groq key to run your own instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/PramodTKodag/snark.git
&lt;span class="nb"&gt;cd &lt;/span&gt;snark &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env   &lt;span class="c"&gt;# add a free Groq key&lt;/span&gt;
docker compose &lt;span class="nt"&gt;--profile&lt;/span&gt; dev up
curl http://localhost:8100/v1/wit/roast/your-pr/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo is here: &lt;strong&gt;&lt;a href="https://github.com/PramodTKodag/snark" rel="noopener noreferrer"&gt;https://github.com/PramodTKodag/snark&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If it got a laugh out of you, a star on the repo genuinely helps. And if you'd have built the fallback differently, I'd like to hear it.&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>ai</category>
      <category>showdev</category>
    </item>
    <item>
      <title>🔒 The Future of Authentication Is Coming</title>
      <dc:creator>Pramod Kodag</dc:creator>
      <pubDate>Fri, 15 Aug 2025 15:07:04 +0000</pubDate>
      <link>https://dev.to/thesilentknight/the-future-of-authentication-is-coming-7k0</link>
      <guid>https://dev.to/thesilentknight/the-future-of-authentication-is-coming-7k0</guid>
      <description>&lt;p&gt;For years, authentication has been stuck in the same cycle, passwords, OTPs, wallets, and endless sign-in hoops.&lt;/p&gt;

&lt;p&gt;I’m building something that changes that&lt;/p&gt;

&lt;p&gt;It’s &lt;strong&gt;secure&lt;/strong&gt; enough to trust with your most valuable data, yet &lt;strong&gt;simple&lt;/strong&gt; enough for anyone to use without a manual.&lt;/p&gt;

&lt;p&gt;It bridges the gap between the &lt;strong&gt;familiar world of today’s logins&lt;/strong&gt; and the &lt;strong&gt;decentralized possibilities of tomorrow&lt;/strong&gt;, all without the friction you’ve come to expect.&lt;/p&gt;

&lt;p&gt;No, I’m not ready to spill the technical magic just yet.&lt;/p&gt;

&lt;p&gt;But I will say this: once you’ve experienced it, going back to the old way of logging in will feel like using dial-up internet in 2025.&lt;/p&gt;

&lt;p&gt;The countdown has begun.&lt;/p&gt;

&lt;p&gt;Stay tuned.&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>security</category>
      <category>developers</category>
      <category>programming</category>
    </item>
    <item>
      <title>HR Management system using FLASK framework</title>
      <dc:creator>Pramod Kodag</dc:creator>
      <pubDate>Wed, 09 Aug 2023 10:47:59 +0000</pubDate>
      <link>https://dev.to/thesilentknight/hr-management-system-using-flask-framework-42fo</link>
      <guid>https://dev.to/thesilentknight/hr-management-system-using-flask-framework-42fo</guid>
      <description>&lt;p&gt;I am thrilled to share one of my significant projects developed for Trakit Solutions—an Attendance and HR Management System. This web-based application, built on Flask, has revolutionized the company's workforce operations by providing an integrated solution for attendance tracking and streamlining HR processes.&lt;/p&gt;

&lt;p&gt;Simplifying Attendance Tracking: The primary focus of this project was to simplify and automate the attendance tracking process. Leveraging the versatility of Flask, I designed a user-friendly interface that allows employees to conveniently log their attendance using various methods such as biometric devices, QR codes, or manual entry. The system captures and securely stores attendance data, ensuring accurate and reliable records.&lt;/p&gt;

&lt;p&gt;Efficient HR Management: In addition to attendance tracking, the HR Management System offers an array of features to streamline HR processes. I developed modules to manage employee profiles, including personal details, employment history, and performance evaluations. This centralizes employee data, simplifying HR tasks such as onboarding, leave management, and generating comprehensive reports.&lt;/p&gt;

&lt;p&gt;Enhanced Data Analysis and Reporting: To enable data-driven decision-making, I integrated powerful analytics and reporting capabilities into the system. Using Flask's extensibility, I incorporated data visualization libraries such as Matplotlib and Plotly. This allows HR administrators to gain valuable insights from attendance trends, employee performance, and leave patterns, facilitating resource planning and performance evaluation.&lt;/p&gt;

&lt;p&gt;Seamless Integration and User-Friendly Interface: The Attendance and HR Management System seamlessly integrates with Trakit Solutions' existing infrastructure, ensuring a smooth implementation process. The user interface features a clean design and intuitive workflow, making it easy for employees and HR personnel to navigate and interact with the system. Continuous feedback and iterative development ensure ongoing enhancements for an optimal user experience.&lt;/p&gt;

&lt;p&gt;This project exemplifies my expertise in web application development, specifically in leveraging Flask to deliver practical solutions for real-world business challenges. By simplifying attendance tracking, optimizing HR processes, and facilitating data analysis and reporting, this system has significantly improved Trakit Solutions' workforce management.&lt;/p&gt;

&lt;p&gt;Through this project, I have demonstrated my dedication to leveraging technology to enhance operational efficiency and productivity within organizations. The Attendance and HR Management System stands as a testament to my ability to deliver effective solutions tailored to the unique needs of businesses in streamlining their HR processes.&lt;/p&gt;

&lt;h1&gt;
  
  
  HRManagementSystem #AttendanceTracking #FlaskDevelopment #WorkforceOptimization #DataAnalysis #EfficientHRProcesses
&lt;/h1&gt;

</description>
    </item>
    <item>
      <title>Selenium project with Python to scrap data</title>
      <dc:creator>Pramod Kodag</dc:creator>
      <pubDate>Wed, 09 Aug 2023 10:46:16 +0000</pubDate>
      <link>https://dev.to/thesilentknight/selenium-project-with-python-to-scrap-data-104o</link>
      <guid>https://dev.to/thesilentknight/selenium-project-with-python-to-scrap-data-104o</guid>
      <description>&lt;p&gt;I am excited to share one of my initial projects, which holds a special place in my portfolio. This project involved scraping data from the ICEGATE website while overcoming challenging security measures, including captchas. Through perseverance and expertise in Python, I developed a bot that successfully bypassed captchas using Tesseract, extracted and parsed the data, and stored it in a MySQL database.&lt;/p&gt;

&lt;p&gt;Overcoming the Captcha Barrier: In the early stages of this project, I encountered captchas implemented on the ICEGATE website, designed to prevent automated scraping. Overcoming this obstacle required innovative thinking and technical skills. To address this challenge, I leveraged Tesseract, an OCR engine. By training Tesseract on a diverse set of captcha images, I enabled my bot to accurately recognize and decode captchas, effectively bypassing this security measure.&lt;/p&gt;

&lt;p&gt;Scraping and Parsing Data: Once the captcha barrier was overcome, I focused on scraping the desired data from the ICEGATE website. Python, with its rich ecosystem of libraries, proved to be an excellent choice for this task. I utilized powerful web scraping libraries such as BeautifulSoup and Selenium to navigate the website, extract the required data, and prepare it for further processing.&lt;/p&gt;

&lt;p&gt;Parsing the obtained data was a crucial step in transforming the raw information into a structured format. With Python's string manipulation capabilities and the use of regular expressions, I developed a robust parsing algorithm. This algorithm efficiently extracted the relevant information from the data dump, ensuring its cleanliness and compatibility for subsequent analysis.&lt;/p&gt;

&lt;p&gt;Storing Data in MySQL Database: To ensure proper management and accessibility of the scraped data, I integrated a MySQL database into the project. Leveraging the MySQL Connector library in Python, I established a seamless connection between my bot and the database. This allowed me to store the parsed data in well-structured tables and columns, facilitating efficient retrieval and future use.&lt;/p&gt;

</description>
      <category>python</category>
      <category>scraping</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
