<?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"><channel><title><![CDATA[Subhojeet's Engineering Blog]]></title><description><![CDATA[Subhojeet's Engineering Blog]]></description><link>https://subhojeetchanda.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 28 Oct 2026 02:35:39 GMT</lastBuildDate><atom:link href="https://subhojeetchanda.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why Supervised Fine-Tuning Fails Against Prompt Injection: Co-Evolving LLM Defenses with Multi-Agent RL (MAPPO)]]></title><description><![CDATA[Most teams defend Large Language Models (LLMs) against prompt-injection and jailbreak attacks using Supervised Fine-Tuning (SFT) on a static dataset of…]]></description><link>https://subhojeetchanda.hashnode.dev/why-supervised-fine-tuning-fails-against-prompt-injection-co-evolving-llm-defenses-with-multi-agent-rl-mappo</link><guid isPermaLink="true">https://subhojeetchanda.hashnode.dev/why-supervised-fine-tuning-fails-against-prompt-injection-co-evolving-llm-defenses-with-multi-agent-rl-mappo</guid><category><![CDATA[llm]]></category><category><![CDATA[Security]]></category><category><![CDATA[pytorch]]></category><category><![CDATA[ #ReinforcementLearning ]]></category><category><![CDATA[MachineLearning]]></category><dc:creator><![CDATA[Subhojeet Chanda]]></dc:creator><pubDate>Fri, 23 Oct 2026 18:38:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6adb8e625da8c4afbfc0326d/790c706a-13cf-4f31-bed8-ed578cfa5ab2.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>The Problem: Static Defenses Lose to Adaptive Attackers</h3>
<p>Most teams defend Large Language Models (LLMs) against prompt-injection and jailbreak attacks using <strong>Supervised Fine-Tuning (SFT)</strong> on a static dataset of known attacks. The problem? As soon as an attacker crafts an Out-Of-Distribution (OOD) prompt that doesn't look like the training data, SFT models crumble.</p>
<p>To solve this, I built <strong>MAPPO-Sec</strong>, a two-agent adversarial reinforcement learning framework using PyTorch, Hugging Face Transformers, LoRA, and Multi-Agent Proximal Policy Optimization (MAPPO) where an Attacker LLM and a Defender LLM continuously co-evolve.</p>
<h3>Architecture: Centralized Training with Decentralized Execution (CTDE)</h3>
<p>Instead of training a defender in isolation, MAPPO-Sec pits two LoRA-adapted models against each other in a zero-sum game using <strong>Centralized Training with Decentralized Execution (CTDE)</strong>:</p>
<pre><code class="language-plaintext">                 ┌───────────────────────────────────────────┐
                 │         Centralized Critic V(s)           │
                 │  (Evaluates Joint State &amp; Computes GAE)   │
                 └─────────────┬───────────────┬─────────────┘
                               │               │
             ┌─────────────────▼──┐         ┌──▼─────────────────┐
             │   Attacker Agent   │         │   Defender Agent   │
             │   (LoRA Policy)    ├────────&gt;│   (LoRA Policy)    │
             └────────────────────┘ Adversarial └────────┬───────────┘
                                     Prompt          │ Safe / Compromised
                                                     ▼
                                           [ Trajectory Buffer ]
</code></pre>
<ul>
<li><strong>The Attacker Agent:</strong> Generates adversarial prompt-injection payloads attempting to hijack the system instruction.</li>
<li><strong>The Defender Agent:</strong> Processes the untrusted input and responds while maintaining policy compliance and helpfulness.</li>
<li><strong>Centralized Critic &amp; GAE:</strong> During training, a centralized critic observes the joint state of both agents, stores rollouts in a trajectory buffer, and computes variance-reduced advantage estimates using <strong>Generalized Advantage Estimation (GAE)</strong>. At inference time (decentralized execution), only the hardened Defender policy is deployed.</li>
</ul>
<h3>Core Implementation: GAE &amp; Clipped MAPPO Objective in PyTorch</h3>
<p>To keep training stable while both policies shift simultaneously, we use Low-Rank Adaptation (<strong>LoRA</strong>) to fine-tune attention weights efficiently and compute GAE over the trajectory buffer:</p>
<pre><code class="language-python">import torch
import torch.nn as nn

def compute_gae(rewards, values, next_values, dones, gamma=0.99, lam=0.95):
    """
    Computes Generalized Advantage Estimation (GAE) for CTDE rollouts.
    """
    advantages = torch.zeros_like(rewards)
    last_gae_lam = 0

    for t in reversed(range(len(rewards))):
        non_terminal = 1.0 - dones[t]
        delta = rewards[t] + gamma * next_values[t] * non_terminal - values[t]
        advantages[t] = last_gae_lam = delta + gamma * lam * non_terminal * last_gae_lam

    returns = advantages + values
    return advantages, returns

def mappo_policy_loss(log_probs, old_log_probs, advantages, clip_eps=0.2):
    """
    Clipped surrogate objective for updating Attacker and Defender LoRA policies.
    """
    ratio = torch.exp(log_probs - old_log_probs)
    surr1 = ratio * advantages
    surr2 = torch.clamp(ratio, 1.0 - clip_eps, 1.0 + clip_eps) * advantages
    return -torch.min(surr1, surr2).mean()
</code></pre>
<h3>Why Co-Evolution Works</h3>
<p>In static SFT, the defender memorizes surface-level refusal patterns. In <strong>MAPPO-Sec</strong>, whenever the Defender patches a vulnerability, the Attacker is rewarded for discovering a new semantic bypass. This forces the Defender to learn robust internal representations of instruction boundaries rather than keyword filtering.</p>
<h3>Benchmark Results: MAPPO-Sec vs. SFT Baseline</h3>
<p>When we evaluated both approaches against unseen, <strong>Out-Of-Distribution (OOD)</strong> prompt-injection attacks, the difference was stark:</p>
<table style="min-width:100px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><td><p><strong>Defense Method</strong></p></td><td><p><strong>Training Paradigm</strong></p></td><td><p><strong>OOD Attack-Defense Accuracy</strong></p></td><td><p></p></td></tr><tr><td><p><strong>Baseline Model</strong></p></td><td><p>Supervised Fine-Tuning (SFT)</p></td><td><p><strong>82.8%</strong></p></td><td><p></p></td></tr><tr><td><p><strong>MAPPO-Sec (Ours)</strong></p></td><td><p>Multi-Agent RL (CTDE + GAE + LoRA)</p></td><td><p><strong>97.6%</strong> (+14.8% improvement)</p></td><td><p></p></td></tr></tbody></table>

<h3>Conclusion</h3>
<p>As LLM applications move into production, static guardrails are no longer sufficient. By combining parameter-efficient LoRA adapters with Multi-Agent RL (MAPPO), we can proactively red-team and harden language models against novel prompt injections before attackers find them in the wild. Explore the implementation on my <a href="https://github.com/subhojeetchanda/RL-for-Self-Improving-Cyber-LLMs">GitHub</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Architecting CareLink: Combining DenseNet121, FAISS RAG, and WebSockets for Real-Time Medical AI]]></title><description><![CDATA[Medical AI often fails in real-world clinical settings for two reasons: doctors don't trust "black-box" image predictions without visual proof, and…]]></description><link>https://subhojeetchanda.hashnode.dev/architecting-carelink-combining-densenet121-faiss-rag-and-websockets-for-real-time-medical-ai</link><guid isPermaLink="true">https://subhojeetchanda.hashnode.dev/architecting-carelink-combining-densenet121-faiss-rag-and-websockets-for-real-time-medical-ai</guid><category><![CDATA[AI]]></category><category><![CDATA[RAG ]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[Python]]></category><category><![CDATA[websockets]]></category><dc:creator><![CDATA[Subhojeet Chanda]]></dc:creator><pubDate>Fri, 23 Oct 2026 17:17:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6adb8e625da8c4afbfc0326d/c2850c25-f361-43a7-8525-956b66659ff5.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>The Problem: Accuracy Without Explainability Fails Patients</h3>
<p>Medical AI often fails in real-world clinical settings for two reasons: doctors don't trust "black-box" image predictions without visual proof, and patients can't understand dense radiology reports filled with clinical jargon.</p>
<p>When building <strong>CareLink</strong> (a finalist project at the VIT Bhopal Health Hackathon in collaboration with Johns Hopkins University), I designed a dual-interface AI diagnostic platform using Next.js, Node.js, TensorFlow, Firebase, and <a href="http://Socket.io">Socket.io</a> that bridges the gap between radiologists and patients. Here is how the three core pipelines work under the hood.</p>
<h3>System Architecture</h3>
<pre><code class="language-plaintext">                       ┌──&gt; [DenseNet121 + Grad-CAM] ──&gt; Doctor View (91% Acc Heatmap)
[X-Ray / Report Input]─┤
                       └──&gt; [Tesseract OCR -&gt; FAISS RAG (MedlinePlus)] ──&gt; Patient Plain-Language View
                                                                                 │
[Browser Sensor APIs] ────&gt; [Socket.io Safety Layer] ────────────────────────────┴──&gt; &lt;3s GPS SOS Alert
</code></pre>
<h3>1. Explainable Radiology with DenseNet121 &amp; Grad-CAM</h3>
<p>For chest X-ray classification, raw probability scores aren't enough for a clinician. We trained a <strong>DenseNet121</strong> Convolutional Neural Network in TensorFlow (achieving <strong>91% classification accuracy</strong>) and integrated <strong>Grad-CAM (Gradient-weighted Class Activation Mapping)</strong>.</p>
<p>Grad-CAM extracts the gradients of the target pathology flowing into the final convolutional layer (<code>conv5_block16_concat</code> in DenseNet121) to generate a localized heatmap highlighting the exact lung regions that triggered the prediction:</p>
<pre><code class="language-python">import tensorflow as tf
import numpy as np
import cv2

def generate_gradcam_heatmap(img_array, model, last_conv_layer_name="conv5_block16_concat"):
    grad_model = tf.keras.models.Model(
        [model.inputs], 
        [model.get_layer(last_conv_layer_name).output, model.output]
    )

    with tf.GradientTape() as tape:
        last_conv_layer_output, preds = grad_model(img_array)
        class_channel = preds[:, tf.argmax(preds[0])]

    grads = tape.gradient(class_channel, last_conv_layer_output)
    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))

    heatmap = last_conv_layer_output[0] @ pooled_grads[..., tf.newaxis]
    heatmap = tf.squeeze(heatmap)
    heatmap = tf.maximum(heatmap, 0) / tf.math.reduce_max(heatmap)
    return heatmap.numpy()
</code></pre>
<h3>2. Translating Medical Jargon with Tesseract OCR + FAISS RAG</h3>
<p>When a patient uploads a physical lab report, feeding it directly to a generic LLM risks dangerous hallucinations. Instead, we built a grounded pipeline:</p>
<ol>
<li><strong>Text Extraction:</strong> <strong>Tesseract OCR</strong> parses raw text from scanned diagnostic reports.</li>
<li><strong>Vector Retrieval:</strong> We indexed verified medical definitions from <strong>MedlinePlus</strong> into a <strong>FAISS</strong> vector database.</li>
<li><strong>Grounded Generation:</strong> Retrieved clinical context is injected into the prompt so the patient gets an accurate, plain-language explanation.</li>
</ol>
<pre><code class="language-python">import pytesseract
from PIL import Image
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
vector_db = FAISS.load_local("medlineplus_faiss_index", embeddings, allow_dangerous_deserialization=True)

def explain_medical_report(image_path: str):
    raw_report_text = pytesseract.image_to_string(Image.open(image_path))
    
    retrieved_docs = vector_db.similarity_search(raw_report_text, k=3)
    context = "\n".join([doc.page_content for doc in retrieved_docs])
    
    prompt = f"""
    Using ONLY the verified MedlinePlus context below, explain this medical report 
    in simple, empathetic plain language for a patient.
    
    Verified Context:
    {context}
    
    Patient Report:
    {raw_report_text}
    """
    return prompt
</code></pre>
<h3>3. Sub-3-Second Fall Detection &amp; SOS Dispatch via <a href="http://Socket.io">Socket.io</a></h3>
<p>To protect high-risk patients at home, we built a real-time safety layer on the frontend using browser <code>DeviceMotionEvent</code> sensor APIs paired with a low-latency <a href="http://Socket.io"><strong>Socket.io</strong></a> WebSocket server in Node.js.</p>
<p>When sudden acceleration spikes followed by stillness are detected on the client device, the browser immediately grabs GPS coordinates and emits a high-priority WebSocket event, delivering emergency SOS alerts to caregivers in <strong>under 3 seconds</strong>:</p>
<pre><code class="language-javascript">window.addEventListener('devicemotion', (event) =&gt; {
  const { x, y, z } = event.accelerationIncludingGravity;
  const magnitude = Math.sqrt(x * x + y * y + z * z);

  if (magnitude &gt; 28.5) {
    navigator.geolocation.getCurrentPosition((pos) =&gt; {
      socket.emit('emergency_sos', {
        patientId: currentUser.id,
        timestamp: Date.now(),
        coordinates: {
          lat: pos.coords.latitude,
          lng: pos.coords.longitude,
        },
        impactG: (magnitude / 9.8).toFixed(2),
      });
    });
  }
});
</code></pre>
<h3>Wrapping Up</h3>
<p>By separating the clinician workflow (DenseNet121 + Grad-CAM) from the patient workflow (OCR + FAISS RAG + WebSocket SOS), <strong>CareLink</strong> demonstrates how combining computer vision, retrieval-augmented generation, and real-time event streaming can create safer, more transparent healthcare tools. Check out the full source code on my <a href="https://github.com/subhojeetchanda/CareLink">GitHub</a></p>
<p>.</p>
]]></content:encoded></item><item><title><![CDATA[Stop Duplicating Pages: How I Built a JSON-Driven Content Pipeline in Next.js and Cut Load Times by 20%]]></title><description><![CDATA[When scaling content-heavy developer platforms, hardcoding individual routes or duplicating layout logic across dozens of chapters quickly becomes a…]]></description><link>https://subhojeetchanda.hashnode.dev/stop-duplicating-pages-how-i-built-a-json-driven-content-pipeline-in-next-js-and-cut-load-times-by-20</link><guid isPermaLink="true">https://subhojeetchanda.hashnode.dev/stop-duplicating-pages-how-i-built-a-json-driven-content-pipeline-in-next-js-and-cut-load-times-by-20</guid><category><![CDATA[Next.js]]></category><category><![CDATA[React]]></category><category><![CDATA[webdev]]></category><category><![CDATA[performance]]></category><category><![CDATA[SEO for Developers]]></category><category><![CDATA[SEO]]></category><dc:creator><![CDATA[Subhojeet Chanda]]></dc:creator><pubDate>Fri, 23 Oct 2026 17:02:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6adb8e625da8c4afbfc0326d/1a9a2f6e-49c1-4a3d-909c-4b1c1ef30e62.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3>The Problem: Static Page Sprawl</h3>
<p>When scaling content-heavy developer platforms, hardcoding individual routes or duplicating layout logic across dozens of chapters quickly becomes a maintenance nightmare. During my time as an SDE Intern at SecPen Labs, we needed a way to scale multi-chapter technical content without creating per-page duplication, while simultaneously improving Core Web Vitals and search visibility.</p>
<p>Here is how we re-architected the frontend using a single-schema JSON pipeline in Next.js, reduced page-load time by <strong>20%</strong>, and lifted organic traffic by <strong>15%</strong>.</p>
<h3>Architecture Overview</h3>
<p>Instead of maintaining separate React templates for every chapter, we centralized all content into a strict JSON schema and used Next.js dynamic routing to hydrate pages on demand:</p>
<pre><code class="language-plaintext">[ JSON Content Schema ] 
         │
         ▼
[ Dynamic Route: /chapters/[slug]/page.tsx ]
         ├──&gt; Route-Level Code Splitting (Dynamic Imports)
         ├──&gt; Schema-to-Component Mapper (Zero Duplication)
         └──&gt; Automated Metadata &amp; Sitemap Generator (SEO)
</code></pre>
<h3>Step 1: Designing the Single-Schema JSON Pipeline</h3>
<p>To eliminate per-page duplication, every chapter adheres to a unified JSON structure containing metadata, hierarchy, and modular content blocks:</p>
<pre><code class="language-json">{
  "slug": "network-pentesting-fundamentals",
  "title": "Network Penetration Testing Fundamentals",
  "SEO": {
    "description": "Learn the core methodology of network reconnaissance and exploitation.",
    "keywords": ["pentesting", "network security", "secpen"]
  },
  "sections": [
    {
      "type": "markdown",
      "content": "## Reconnaissance\nActive and passive scanning techniques..."
    },
    {
      "type": "code",
      "language": "bash",
      "snippet": "nmap -sC -sV -oA initial_scan 10.10.11.24"
    },
    {
      "type": "callout",
      "variant": "warning",
      "text": "Always ensure you have explicit written authorization before scanning targets."
    }
  ]
}
</code></pre>
<h3>Step 2: Dynamic Chapter Routing &amp; Route-Level Code Splitting</h3>
<p>Using Next.js dynamic routes (<code>app/chapters/[slug]/page.tsx</code>), we mapped the JSON schema blocks to lazy-loaded React components. By applying route-level code-splitting with <code>next/dynamic</code>, heavy components (like syntax highlighters or interactive diagrams) are only downloaded when a chapter actually uses them:</p>
<pre><code class="language-typescript">import dynamic from 'next/dynamic';
import { notFound } from 'next/navigation';
import { getChapterBySlug } from '@/lib/contentPipeline';

const CodeBlock = dynamic(() =&gt; import('@/components/CodeBlock'), {
  loading: () =&gt; &lt;div className="h-24 animate-pulse bg-slate-800 rounded-md" /&gt;,
});
const CalloutBox = dynamic(() =&gt; import('@/components/CalloutBox'));

export default async function ChapterPage({ params }: { params: { slug: string } }) {
  const chapter = await getChapterBySlug(params.slug);
  if (!chapter) return notFound();

  return (
    &lt;article className="max-w-3xl mx-auto py-10 px-4"&gt;
      &lt;h1 className="text-3xl font-bold tracking-tight mb-6"&gt;{chapter.title}&lt;/h1&gt;
      &lt;div className="space-y-6"&gt;
        {chapter.sections.map((section, idx) =&gt; {
          switch (section.type) {
            case 'code':
              return &lt;CodeBlock key={idx} language={section.language} code={section.snippet} /&gt;;
            case 'callout':
              return &lt;CalloutBox key={idx} variant={section.variant} text={section.text} /&gt;;
            default:
              return &lt;p key={idx} className="leading-relaxed text-slate-300"&gt;{section.content}&lt;/p&gt;;
          }
        })}
      &lt;/div&gt;
    &lt;/article&gt;
  );
}
</code></pre>
<h3>Step 3: Tailwind CSS Optimization &amp; Technical SEO</h3>
<p>To push performance further, we paired our routing architecture with two key optimizations:</p>
<ol>
<li><strong>Asset &amp; Tailwind CSS Optimization:</strong> We compressed static media assets, purged unused utility classes, and flattened nested DOM structures to reduce CSS bundle overhead.</li>
<li><strong>Programmatic SEO &amp; Sitemap Generation:</strong> Using the same JSON pipeline, we dynamically injected OpenGraph and structured metadata via Next.js <code>generateMetadata</code> and automated XML sitemap generation:</li>
</ol>
<pre><code class="language-typescript">export async function generateMetadata({ params }: { params: { slug: string } }) {
  const chapter = await getChapterBySlug(params.slug);
  if (!chapter) return {};

  return {
    title: `${chapter.title} | SecPen Labs`,
    description: chapter.SEO.description,
    keywords: chapter.SEO.keywords,
    openGraph: {
      title: chapter.title,
      description: chapter.SEO.description,
      type: 'article',
    },
  };
}
</code></pre>
<h3>Production Impact</h3>
<p>Moving from static templates to a schema-driven Next.js pipeline delivered measurable results across the platform:</p>
<table style="min-width:100px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><td><p></p></td><td><p><strong>Metric</strong></p></td><td><p><strong>Optimization Applied</strong></p></td><td><p><strong>Result</strong></p></td></tr><tr><td><p><strong>Page-Load Time</strong></p></td><td><p>Route-level code-splitting, asset compression &amp; Tailwind optimization</p></td><td><p><strong>20% reduction</strong></p></td><td><p></p></td></tr><tr><td><p><strong>Code Maintainability</strong></p></td><td><p>Single-schema JSON pipeline with dynamic chapter routing</p></td><td><p><strong>0% per-page duplication</strong></p></td><td><p></p></td></tr><tr><td><p><strong>Organic Search Traffic</strong></p></td><td><p>Structured metadata &amp; automated sitemap optimization</p></td><td><p><strong>+15% organic lift</strong></p></td><td><p></p></td></tr></tbody></table>

<h3>Key Takeaway</h3>
<p>Treating your UI as a function of a clean JSON schema doesn't just make your codebase DRY—it makes performance optimization, code-splitting, and technical SEO repeatable across thousands of pages.</p>
]]></content:encoded></item></channel></rss>