<?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: PAWAN YADAV  (AI Engineer)</title>
    <description>The latest articles on DEV Community by PAWAN YADAV  (AI Engineer) (@2000pawan).</description>
    <link>https://dev.to/2000pawan</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%2F3992255%2Fd2c0c2bf-31f6-46b5-bc3d-5b269fbeb450.jpg</url>
      <title>DEV Community: PAWAN YADAV  (AI Engineer)</title>
      <link>https://dev.to/2000pawan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/2000pawan"/>
    <language>en</language>
    <item>
      <title>Building a Production Multi-Agent System: Supervisor Routing, Tool-Calling, and Taming Hallucinations with a Small 8B LLM</title>
      <dc:creator>PAWAN YADAV  (AI Engineer)</dc:creator>
      <pubDate>Fri, 31 Jul 2026 13:04:56 +0000</pubDate>
      <link>https://dev.to/2000pawan/building-a-production-multi-agent-system-supervisor-routing-tool-calling-and-taming-5dib</link>
      <guid>https://dev.to/2000pawan/building-a-production-multi-agent-system-supervisor-routing-tool-calling-and-taming-5dib</guid>
      <description>&lt;p&gt;How I designed a conversational AI system that lets end users perform structured backend operations in plain English — using a supervisor-agent architecture, tool-calling, and a lightweight 8B open-source model instead of a giant proprietary one.&lt;/p&gt;

&lt;p&gt;The Problem&lt;br&gt;
Most enterprise backend systems expose their functionality through dense, form-heavy portals. Doing something simple usually means:&lt;/p&gt;

&lt;p&gt;Logging into a portal&lt;br&gt;
Hunting through nested dropdowns and categories&lt;br&gt;
Re-typing boilerplate information every time&lt;br&gt;
Coming back later just to check status or add a note&lt;br&gt;
Most users just want to describe what they need in plain language and be done with it.&lt;/p&gt;

&lt;p&gt;So I built a conversational AI layer on top of an existing backend system that lets users perform three categories of operations — submitting new requests, modifying existing ones, and looking things up — entirely through natural language, in chat.&lt;/p&gt;

&lt;p&gt;This post covers three things people keep asking me about:&lt;/p&gt;

&lt;p&gt;How the multi-agent routing actually works&lt;br&gt;
How I got reliable results out of a small 8B model instead of a large frontier model&lt;br&gt;
How I reduced hallucination in a workflow where wrong answers write real records into a real system&lt;br&gt;
Why Multi-Agent Instead of One Big Prompt&lt;br&gt;
My first instinct was “just write one giant system prompt that handles everything.” That fell apart fast:&lt;/p&gt;

&lt;p&gt;The prompt became enormous and slow to reason over&lt;br&gt;
The model would confuse one type of operation with another&lt;br&gt;
Tool-calling accuracy dropped as the number of available tools grew&lt;br&gt;
Debugging why something went wrong was nearly impossible — one flow’s logic bled into another’s&lt;br&gt;
The fix was to split the system into a Supervisor Agent and several specialized Worker Agents, each with a narrow, well-defined job and its own small toolset. This is the same pattern used in most production multi-agent systems: a router model decides what the user wants, and a specialist model decides how to do it.&lt;/p&gt;

&lt;p&gt;Architecture &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3jah82gusxim89gehucv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3jah82gusxim89gehucv.png" alt=" " width="604" height="789"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want a rendered version instead of ASCII, here’s the same flow as Mermaid (renders natively on Dev.to, GitHub, and most markdown viewers — for Medium you’d paste this into a Mermaid live editor and drop in the exported image):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffw3dd8tc87q4oae9ucs7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffw3dd8tc87q4oae9ucs7.png" alt=" " width="572" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How the Supervisor Routes Requests&lt;br&gt;
The supervisor doesn’t call any backend tools itself — its only job is intent classification + delegation + context tracking. That single-responsibility design is what makes it reliable even on a small model: it’s not trying to reason about categories, records, and filters all at once, it’s just answering “which of 3 buckets does this go in?”&lt;/p&gt;

&lt;p&gt;Rough logic:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc852rd5wf16zkz99gf6j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc852rd5wf16zkz99gf6j.png" alt=" " width="569" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Signal in user messageRoutes toWants to submit something newAgent AlphaWants to change or add to something that already existsAgent BetaWants to look up or review existing recordsAgent GammaUnclear / multiple intents in one messageSupervisor asks a clarifying question first&lt;/p&gt;

&lt;p&gt;Each worker agent then owns its own multi-step flow. For example, Agent Alpha walks the user through: pick a category → pick a sub-category → describe what’s needed → get an AI-suggested resolution → confirm → record submitted → confirmation shown. Agent Beta and Agent Gamma follow the same “narrow tool, narrow job” philosophy, just with different backend calls (fetch existing items, apply a change, query by a filter or time window, etc.).&lt;/p&gt;

&lt;p&gt;Doing This on a Small 8B Model (Not GPT-4-class)&lt;br&gt;
The part people are most surprised by: this whole system runs on an open-source 8B parameter model, not a large frontier model. A few techniques made that viable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Narrow the job per agent. An 8B model struggles to hold a large catalog of categories, dozens of tools, and multiple conversation flows in its head at once. Split across several agents, each model call only needs to reason about a handful of tools and a tightly scoped instruction set — well within an 8B model’s comfort zone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Constrain tool schemas aggressively. Every tool has a strict JSON schema with enums where possible, instead of free-text parameters. Small models are much better at picking from a constrained set than generating an unconstrained one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Force structured output, not free prose, for anything that feeds a tool call. The model never “writes” an identifier or category name from memory — it’s only ever allowed to select values that came from a prior tool response (e.g., the actual list returned by a catalog lookup). This alone eliminated most of the invented-value problem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multi-turn confirmation instead of one-shot execution. Nothing destructive (submitting a new record, applying a change) happens on the first pass. The flow always shows the user what it’s about to do and waits for confirmation. This isn’t just good UX — it’s a hallucination safety net: if the model got a field wrong, the user catches it before it’s written to the backend.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Overcoming Hallucination&lt;br&gt;
This was the hardest part, because a hallucinated output isn’t just a wrong answer in a chat window — it’s a bad record in a real backend system. Here’s what actually moved the needle:&lt;/p&gt;

&lt;p&gt;Ground every list in a live tool call, never in the model’s memory. Category names, sub-category names, and record identifiers are always fetched fresh from the backend right before they’re shown to the user. The model is never asked to recall or generate them.&lt;br&gt;
Retrieval-augmented suggestions before action, not instead of grounding. Before a new record is submitted (or a change applied), a retrieval-augmented step generates a suggested resolution based on the user’s actual described need and a knowledge base — not the model’s raw training data. This both reduces hallucinated “answers” and often resolves the need without any record being written at all.&lt;br&gt;
One category per submission, explicit selection only. The model can’t infer or guess a category — the user must explicitly pick from a list that was just returned by a tool. This removes an entire class of “close enough” hallucinations.&lt;br&gt;
Validation layer between model output and tool execution. Every tool call the model proposes is validated against the tool’s schema and the last known-good state (e.g., “is this identifier actually one of the records we just fetched for this user?”) before it’s executed. If validation fails, the flow re-prompts instead of silently proceeding.&lt;br&gt;
Confirm-before-commit on all write operations. As mentioned above — submissions and changes are always shown to the user in a preview state first.&lt;br&gt;
Together, these turned “occasionally confidently wrong” into “reliably grounded,” even on a much smaller model than you’d normally reach for.&lt;/p&gt;

&lt;p&gt;Managing the Server &amp;amp; Auto-Switching Between LLMs&lt;br&gt;
In production, a single model endpoint will occasionally be busy, rate-limited, or slow to respond — especially when you’re self-hosting an 8B model instead of paying for elastic cloud capacity. Rather than let a busy model stall the whole chat, I built a small LLM router with automatic failover: if the primary model is unavailable or times out, the next request transparently goes to a backup model.&lt;/p&gt;

&lt;p&gt;Write on Medium&lt;br&gt;
Here’s a simplified, copy-paste version of that router (Python):&lt;/p&gt;

&lt;p&gt;python&lt;/p&gt;

&lt;p&gt;import time&lt;br&gt;
import random&lt;br&gt;
import requests&lt;br&gt;
from dataclasses import dataclass, field&lt;br&gt;
from typing import Optional&lt;br&gt;
@dataclass&lt;br&gt;
class LLMEndpoint:&lt;br&gt;
    name: str&lt;br&gt;
    url: str&lt;br&gt;
    healthy: bool = True&lt;br&gt;
    last_failure_ts: float = 0&lt;br&gt;
    cooldown_seconds: int = 30  # how long to skip this endpoint after a failure&lt;br&gt;
class LLMRouter:&lt;br&gt;
    """&lt;br&gt;
    Routes a chat/completion request across multiple LLM endpoints.&lt;br&gt;
    If the current endpoint is busy/slow/erroring, it automatically&lt;br&gt;
    fails over to the next healthy endpoint in the pool.&lt;br&gt;
    """&lt;br&gt;
    def &lt;strong&gt;init&lt;/strong&gt;(self, endpoints: list[LLMEndpoint], timeout: int = 8, max_retries: int = 3):&lt;br&gt;
        self.endpoints = endpoints&lt;br&gt;
        self.timeout = timeout&lt;br&gt;
        self.max_retries = max_retries&lt;br&gt;
        self._rr_index = 0  # round-robin pointer&lt;br&gt;
    def _next_endpoint(self) -&amp;gt; Optional[LLMEndpoint]:&lt;br&gt;
        """Pick the next healthy endpoint using round-robin, respecting cooldowns."""&lt;br&gt;
        now = time.time()&lt;br&gt;
        # Re-enable endpoints whose cooldown has expired&lt;br&gt;
        for ep in self.endpoints:&lt;br&gt;
            if not ep.healthy and (now - ep.last_failure_ts) &amp;gt; ep.cooldown_seconds:&lt;br&gt;
                ep.healthy = True&lt;br&gt;
        healthy = [ep for ep in self.endpoints if ep.healthy]&lt;br&gt;
        if not healthy:&lt;br&gt;
            return None&lt;br&gt;
        self._rr_index = (self._rr_index + 1) % len(healthy)&lt;br&gt;
        return healthy[self._rr_index]&lt;br&gt;
    def _mark_failed(self, endpoint: LLMEndpoint):&lt;br&gt;
        endpoint.healthy = False&lt;br&gt;
        endpoint.last_failure_ts = time.time()&lt;br&gt;
    def call(self, payload: dict) -&amp;gt; dict:&lt;br&gt;
        """&lt;br&gt;
        Attempts the request against healthy endpoints, retrying with&lt;br&gt;
        exponential backoff and failing over to a different model each attempt.&lt;br&gt;
        """&lt;br&gt;
        last_error = None&lt;br&gt;
        for attempt in range(self.max_retries):&lt;br&gt;
            endpoint = self._next_endpoint()&lt;br&gt;
            if endpoint is None:&lt;br&gt;
                raise RuntimeError("No healthy LLM endpoints available")&lt;br&gt;
            try:&lt;br&gt;
                response = requests.post(&lt;br&gt;
                    endpoint.url,&lt;br&gt;
                    json=payload,&lt;br&gt;
                    timeout=self.timeout,&lt;br&gt;
                )&lt;br&gt;
                if response.status_code == 429 or response.status_code &amp;gt;= 500:&lt;br&gt;
                    # Model is busy / overloaded / erroring -&amp;gt; fail over&lt;br&gt;
                    raise requests.HTTPError(f"{endpoint.name} returned {response.status_code}")&lt;br&gt;
                return {"endpoint": endpoint.name, "data": response.json()}&lt;br&gt;
            except (requests.Timeout, requests.ConnectionError, requests.HTTPError) as e:&lt;br&gt;
                last_error = e&lt;br&gt;
                self._mark_failed(endpoint)&lt;br&gt;
                backoff = (2 ** attempt) + random.uniform(0, 0.5)&lt;br&gt;
                time.sleep(backoff)&lt;br&gt;
                continue&lt;br&gt;
        raise RuntimeError(f"All retries exhausted. Last error: {last_error}")&lt;/p&gt;

&lt;h1&gt;
  
  
  --- Example usage ---
&lt;/h1&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    pool = [&lt;br&gt;
        LLMEndpoint(name="llm-primary", url="&lt;a href="http://localhost:8001/v1/chat/completions%22" rel="noopener noreferrer"&gt;http://localhost:8001/v1/chat/completions"&lt;/a&gt;),&lt;br&gt;
        LLMEndpoint(name="llm-secondary", url="&lt;a href="http://localhost:8002/v1/chat/completions%22" rel="noopener noreferrer"&gt;http://localhost:8002/v1/chat/completions"&lt;/a&gt;),&lt;br&gt;
        LLMEndpoint(name="llm-tertiary", url="&lt;a href="http://localhost:8003/v1/chat/completions%22" rel="noopener noreferrer"&gt;http://localhost:8003/v1/chat/completions"&lt;/a&gt;),&lt;br&gt;
    ]&lt;br&gt;
    router = LLMRouter(endpoints=pool, timeout=8, max_retries=3)&lt;br&gt;
    payload = {&lt;br&gt;
        "model": "your-8b-model",&lt;br&gt;
        "messages": [{"role": "user", "content": "Summarize the latest status."}],&lt;br&gt;
    }&lt;br&gt;
    result = router.call(payload)&lt;br&gt;
    print(f"Served by: {result['endpoint']}")&lt;br&gt;
    print(result["data"])&lt;br&gt;
How this behaves in practice:&lt;/p&gt;

&lt;p&gt;Each model instance is treated as an interchangeable endpoint in a pool.&lt;br&gt;
If an endpoint returns a 429 (busy/rate-limited), a 5xx, times out, or drops the connection, it's marked unhealthy and skipped for a cooldown window (default 30s) instead of being hammered again immediately.&lt;br&gt;
The next query automatically round-robins to a different, healthy endpoint — the user never sees the failure.&lt;br&gt;
Exponential backoff with jitter prevents a thundering-herd retry storm if multiple endpoints are struggling at once.&lt;br&gt;
A few production notes if you adapt this:&lt;/p&gt;

&lt;p&gt;Put a lightweight health-check ping on a background thread (e.g., every 15s) so endpoints get marked healthy again proactively, not just lazily on the next request.&lt;br&gt;
If you’re running multiple different model sizes (e.g., an 8B and a 13B) as your pool, keep prompts model-agnostic — avoid model-specific prompt tricks that only one of them understands.&lt;br&gt;
Log which endpoint served each request. This alone will tell you if one node is consistently overloaded and needs more replicas.&lt;br&gt;
What I’d Do Differently&lt;br&gt;
Add per-agent evaluation sets earlier — I only started rigorously testing routing accuracy (Supervisor → correct agent) after I’d already built all three flows, and it would’ve caught misroutes sooner.&lt;br&gt;
Cache catalog/category lookups with a short TTL — they don’t change often, and re-fetching on every single message added latency for no benefit.&lt;br&gt;
Instrument hallucination specifically (not just “did the tool call succeed”) — track how often the model tried to propose a value that wasn’t in the last tool response, even if validation caught it. That signal is gold for prompt tuning.&lt;br&gt;
Closing Thoughts&lt;br&gt;
The headline lesson: you don’t need a giant model to build a reliable agentic system — you need tight scoping. A supervisor that only routes, workers that only own one job each, tools with strict schemas, and a hard rule that nothing gets written to a backend without being grounded in a fresh tool call. That combination got an 8B open-source model to perform a task reliably that I originally assumed would require a much larger, much more expensive model.&lt;/p&gt;

&lt;p&gt;If you’re building something similar, happy to go deeper on any piece of this — routing logic, retrieval-grounding, or the failover setup — in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>multiagent</category>
      <category>agenticai</category>
    </item>
    <item>
      <title>Building a Centralized Vector Database Pipeline for Multi-Project RAG</title>
      <dc:creator>PAWAN YADAV  (AI Engineer)</dc:creator>
      <pubDate>Fri, 10 Jul 2026 09:57:10 +0000</pubDate>
      <link>https://dev.to/2000pawan/building-a-centralized-vector-database-pipeline-for-multi-project-rag-4ldf</link>
      <guid>https://dev.to/2000pawan/building-a-centralized-vector-database-pipeline-for-multi-project-rag-4ldf</guid>
      <description>&lt;p&gt;If you've worked on more than one Retrieval-Augmented Generation (RAG) project, you've probably run into the same annoying pattern: every project ends up with its own little vector store, its own embedding logic, its own duplicate code for chunking files, and its own inconsistent way of tracking what's actually been indexed.&lt;br&gt;
I got tired of that, so I built a centralized vector database service that every project in my organization can plug into. One backend, one embedding model, one clean set of endpoints - and every project gets its own isolated, admin-controlled storage space.&lt;br&gt;
Here's how it works.&lt;/p&gt;

&lt;p&gt;The Problem&lt;/p&gt;

&lt;p&gt;When you're running multiple RAG-powered projects at once, a few pain points show up fast:&lt;br&gt;
Every team reinvents chunking and embedding logic&lt;br&gt;
There's no single place to see what data lives where&lt;br&gt;
Deleting or updating stored documents becomes a manual, error-prone process&lt;br&gt;
Nobody has a clear picture of which files are indexed in which project&lt;/p&gt;

&lt;p&gt;I wanted one system that solves this once, for everyone.&lt;br&gt;
The Idea: One Service, Many Isolated Databases&lt;br&gt;
Instead of giving each project its own standalone vector database, I built a shared service where:&lt;br&gt;
Each project gets its own named storage directory&lt;br&gt;
All directories live under one base storage path&lt;br&gt;
An admin layer controls the full lifecycle - create, insert, update, delete, list - across every project's storage&lt;br&gt;
Every project only ever talks to a small set of simple endpoints to embed and retrieve its own data&lt;/p&gt;

&lt;p&gt;This means projects don't need to know anything about vector databases, embedding models, or similarity search internals. They just send text in and get relevant results back.&lt;br&gt;
Architecture Overview&lt;/p&gt;

&lt;p&gt;Here's the high-level flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frq534xzajeq4bp5c3bvp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frq534xzajeq4bp5c3bvp.jpg" alt=" " width="720" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In plain terms:&lt;br&gt;
A project sends a file or text to the service&lt;br&gt;
The service breaks it into clean text chunks&lt;br&gt;
Those chunks get embedded and stored (upserted) into that project's own FAISS index&lt;br&gt;
Later, the project sends a query, and the service returns the most similar stored chunks&lt;br&gt;
An admin layer sits on top of all of this, with full control to create, update, or delete data across any project's storage&lt;/p&gt;

&lt;p&gt;What's Under the&amp;nbsp;Hood&lt;br&gt;
Embedding model: a lightweight sentence-transformer model, chosen for speed and low resource usage&lt;br&gt;
Vector engine: FAISS, for fast similarity search at scale&lt;br&gt;
Backend framework: a modern Python web framework built for speed and simplicity&lt;br&gt;
Storage layout: every project's database lives in its own folder, containing:&lt;br&gt;
the FAISS index itself&lt;br&gt;
a metadata/pickle file for the index&lt;br&gt;
a tracked-files registry (so you always know what's indexed)&lt;br&gt;
a source map (so you always know how many chunks came from each file)&lt;/p&gt;

&lt;p&gt;Each project is fully sandboxed. Nothing bleeds across storage directories.&lt;br&gt;
The Core&amp;nbsp;Workflow&lt;br&gt;
Every project follows the same simple lifecycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a storage directory
A new, isolated space is created for the project. Invalid or unsafe directory names are automatically rejected.&lt;/li&gt;
&lt;li&gt;Process a file into&amp;nbsp;chunks
Upload a document (PDF, TXT, DOCX, or Markdown) and it comes back as clean, ready-to-embed text chunks. This step doesn't store anything yet - it just prepares the data.&lt;/li&gt;
&lt;li&gt;Upsert the&amp;nbsp;chunks
The chunks get embedded and stored. If the project's index doesn't exist yet, it's created automatically. If it does exist, new vectors are simply appended. Duplicate sources are automatically prevented.&lt;/li&gt;
&lt;li&gt;Search
A query comes in, gets embedded the same way, and the service returns the top-k most similar chunks - along with which source file they came from. A similarity score threshold filters out weak matches.&lt;/li&gt;
&lt;li&gt;Manage the&amp;nbsp;data
At any point, an admin can:
List every database in the system, with file counts and tracked sources
Delete a specific file's vectors from a project's index
Have all of this triggered externally through a single webhook event, so other systems can kick off any step of this pipeline programmatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why This&amp;nbsp;Matters&lt;br&gt;
The real win here isn't the vector search itself - FAISS and sentence-transformers are well-understood tools. The win is centralization with isolation:&lt;br&gt;
One codebase to maintain instead of N duplicated ones&lt;br&gt;
One admin view into every project's stored data&lt;br&gt;
Every project gets embedding and retrieval "for free," without owning any of the infrastructure&lt;br&gt;
Adding a brand-new project to RAG-powered search takes minutes: create a directory, start upserting&lt;/p&gt;

&lt;p&gt;It turns "set up a vector database" from an infrastructure task into a two-endpoint integration for whoever's building the next project.&lt;br&gt;
What's Next&lt;br&gt;
A few directions I'm considering for this pipeline:&lt;br&gt;
Authentication and per-project access scoping&lt;br&gt;
Async batch processing for very large files&lt;br&gt;
Pluggable embedding models per project (not just one global model)&lt;br&gt;
A lightweight admin dashboard on top of the existing list/delete endpoints&lt;/p&gt;

&lt;p&gt;If you're building multiple RAG projects and keep rebuilding the same vector storage logic every time, this pattern - one shared service, isolated storage per project, centralized admin control - is worth stealing.&lt;/p&gt;




&lt;p&gt;Have you built something similar, or run into different tradeoffs with multi-project RAG infrastructure? I'd love to hear how you approached it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Self-Hosted RAG Chatbot with a Dual-Agent LLM Pipeline (and Automatic LLM Failover)</title>
      <dc:creator>PAWAN YADAV  (AI Engineer)</dc:creator>
      <pubDate>Fri, 19 Jun 2026 12:50:59 +0000</pubDate>
      <link>https://dev.to/2000pawan/building-a-self-hosted-rag-chatbot-with-a-dual-agent-llm-pipeline-and-automatic-llm-failover-137c</link>
      <guid>https://dev.to/2000pawan/building-a-self-hosted-rag-chatbot-with-a-dual-agent-llm-pipeline-and-automatic-llm-failover-137c</guid>
      <description>&lt;p&gt;Over the past few weeks I built a Retrieval-Augmented Generation (RAG) chatbot from the ground up — one that answers strictly from a knowledge base I control, supports role-based access for regular users vs. admins, and never makes things up. In this post I want to walk through the architecture, the design decisions, and one piece that took real engineering effort: automatically switching to a backup LLM when the primary one is busy or rate-limited, so a query never just fails.&lt;/p&gt;

&lt;p&gt;This isn’t a toy demo — it’s a full pipeline with authentication, a vector database, a two-stage reasoning process, and an admin console for managing the knowledge base. Here’s how it all fits together.&lt;/p&gt;

&lt;p&gt;The Problem I Was Solving&lt;br&gt;
Most “just call an LLM API” chatbots have two issues:&lt;/p&gt;

&lt;p&gt;They hallucinate. Ask a generic LLM a domain-specific question and it will confidently make something up if it doesn’t actually know the answer.&lt;br&gt;
They have a single point of failure. If your one LLM provider is rate-limited, down, or just slow at that moment, your whole app stalls.&lt;br&gt;
I wanted a system where:&lt;/p&gt;

&lt;p&gt;Every answer is grounded in real, embedded documents — no hallucination.&lt;br&gt;
Two different roles (regular users and admins) get different capabilities.&lt;br&gt;
If the LLM serving a request is unavailable, the system transparently retries on a different model instead of erroring out.&lt;br&gt;
High-Level Architecture&lt;br&gt;
The system has three user-facing surfaces and one processing core:&lt;/p&gt;

&lt;p&gt;Signup / Identification Page — the user enters an email; the backend checks it against a list of approved admin addresses and routes accordingly.&lt;br&gt;
User View — query-only. Regular users can ask questions but cannot touch the knowledge base.&lt;br&gt;
Admin View — everything a user can do, plus the ability to upload documents/URLs, view what’s been embedded, and trigger re-indexing.&lt;br&gt;
Here’s the high-level flow:&lt;/p&gt;

&lt;p&gt;flowchart TD&lt;br&gt;
    A[Signup Page - Enter Email] --&amp;gt; B{Role Check}&lt;br&gt;
    B --&amp;gt;|Regular Email| C[User View - Query Only]&lt;br&gt;
    B --&amp;gt;|Admin Email| D[Admin View - Manage + Query]&lt;br&gt;
    C --&amp;gt; E[Query Pipeline]&lt;br&gt;
    D --&amp;gt; E&lt;br&gt;
    E --&amp;gt; F[Agent 1: Tool &amp;amp; Argument Selection]&lt;br&gt;
    F --&amp;gt; G[Execute Retrieval Tool]&lt;br&gt;
    G --&amp;gt; H[Raw Retrieved Chunks]&lt;br&gt;
    H --&amp;gt; I[Agent 2: Refinement]&lt;br&gt;
    I --&amp;gt; J[Final Answer Returned]&lt;br&gt;
If your blogging platform doesn’t render Mermaid diagrams (Medium, for instance, won’t render this natively), here’s the same flow as plain text:&lt;/p&gt;

&lt;p&gt;Signup Page&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
Role Check (User or Admin?)&lt;br&gt;
   |---------------------|&lt;br&gt;
   v                      v&lt;br&gt;
User View             Admin View&lt;br&gt;
(Query Only)        (Manage + Query)&lt;br&gt;
   |---------------------|&lt;br&gt;
              v&lt;br&gt;
        Query Pipeline&lt;br&gt;
              v&lt;br&gt;
   Agent 1: Tool &amp;amp; Argument Selection&lt;br&gt;
              v&lt;br&gt;
      Execute Retrieval Tool&lt;br&gt;
              v&lt;br&gt;
       Raw Retrieved Chunks&lt;br&gt;
              v&lt;br&gt;
       Agent 2: Refinement&lt;br&gt;
              v&lt;br&gt;
        Final Answer Returned&lt;/p&gt;

&lt;p&gt;Why Two Agents Instead of One?&lt;br&gt;
A single LLM call trying to both decide what to retrieve and how to phrase the final answer tends to produce messier output. So I split the reasoning into two sequential agents, orchestrated as a small graph of steps rather than one big prompt:&lt;/p&gt;

&lt;p&gt;Agent 1 — Tool Decision Takes the raw user query and decides which retrieval tool to call and with what arguments. It returns a small, strict structure like:&lt;/p&gt;

&lt;p&gt;[{"name": "retrieval_tool", "arguments": {"query": "your parsed query here"}}]&lt;br&gt;
A lightweight Python handler parses this output, extracts the tool name and arguments, and manually executes the retrieval step — no need to trust the LLM to “just run the function,” which keeps things deterministic and debuggable.&lt;/p&gt;

&lt;p&gt;Agent 2 — Refinement Takes whatever raw chunks came back from retrieval and turns them into a clear, well-formatted, professional answer. This is also the layer that keeps responses grounded — it’s instructed to work only with what was retrieved, not to add outside knowledge.&lt;/p&gt;

&lt;p&gt;sequence Diagram&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;participant U as User/Admin
participant A1 as Agent 1 (Tool Decision)
participant T as Retrieval Tool
participant V as Vector Store
participant A2 as Agent 2 (Refinement)

U-&amp;gt;&amp;gt;A1: Submits query
A1-&amp;gt;&amp;gt;A1: Decide tool + arguments
A1-&amp;gt;&amp;gt;T: Call retrieval tool
T-&amp;gt;&amp;gt;V: Similarity search
V--&amp;gt;&amp;gt;T: Top-K relevant chunks
T--&amp;gt;&amp;gt;A2: Raw chunks + sources
A2-&amp;gt;&amp;gt;A2: Refine into polished answer
A2--&amp;gt;&amp;gt;U: Final answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The Retrieval Layer&lt;br&gt;
Documents and URLs go through a standard but carefully tuned pipeline:&lt;/p&gt;

&lt;p&gt;Loading — PDFs, Word docs, plain text, and Markdown files are parsed with format-specific loaders; web pages are scraped and cleaned.&lt;br&gt;
Chunking — content is split into ~400-character chunks using a recursive character splitter, with each chunk tagged with its source (filename or URL) for traceability.&lt;br&gt;
Embedding — each chunk is converted into a vector using an embedding model.&lt;br&gt;
Storage — vectors go into a local vector index that supports fast similarity search and incremental updates (you don’t need to rebuild the whole index every time you add a new file).&lt;br&gt;
Retrieval — when a query comes in, the index returns the top-K most similar chunks, filtered by a minimum similarity score threshold so irrelevant matches get dropped before they ever reach the LLM.&lt;/p&gt;

&lt;p&gt;Two tunable parameters matter a lot here:&lt;/p&gt;

&lt;p&gt;score_threshold — the minimum similarity score a chunk needs to be considered relevant. Raise it for stricter, more precise retrieval; lower it if the bot is being too conservative and saying "I don't know" too often.&lt;br&gt;
k — how many top chunks get passed into the refinement agent. Too low and you risk missing context; too high and you risk diluting the answer with noise (and burning more tokens).&lt;br&gt;
Admin Controls&lt;br&gt;
Admins get a dedicated panel to manage the knowledge base directly:&lt;/p&gt;

&lt;p&gt;Upload new files (PDF, Word, text, Markdown) for embedding.&lt;br&gt;
Submit URLs to scrape and embed.&lt;br&gt;
View every currently embedded source and a running count.&lt;br&gt;
Run test queries against the live index.&lt;br&gt;
Trigger a manual reload of the retriever so newly embedded content becomes searchable immediately, without restarting the whole service.&lt;br&gt;
Regular users never see any of this — they only get a query box. Role detection is based on a simple, server-side check against a list of approved admin emails at signup time, and every admin-only route re-checks that role before doing anything destructive or data-modifying.&lt;/p&gt;

&lt;p&gt;Now, the Interesting Part: Automatic LLM Failover&lt;br&gt;
Here’s the piece I want to focus on, since it’s the part most tutorials skip. If you’ve ever called a single LLM provider in production, you’ve hit this: rate limits, timeouts, momentary outages, or a model that’s just slow to respond under load. If your whole app depends on one model, one bad moment takes everything down with it.&lt;/p&gt;

&lt;p&gt;Become a Medium member&lt;br&gt;
The fix is a failover-aware LLM router sitting in front of both agents. Instead of hardcoding “always call Model A,” every LLM call goes through a small dispatcher that:&lt;/p&gt;

&lt;p&gt;Tries the primary model first.&lt;br&gt;
If that call fails, times out, or comes back with a rate-limit / “server busy” type error, it automatically retries the same request on the next model in a prioritized list — no user-facing error, no manual restart.&lt;br&gt;
Logs which model actually served the request (useful for debugging and for tracking cost/usage per provider).&lt;br&gt;
Optionally applies a short backoff before retrying the original model on the next query, so it isn’t hammered immediately after a failure.&lt;br&gt;
Press enter or click to view image in full size&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F24qfgwh3khn7cuwczn8c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F24qfgwh3khn7cuwczn8c.png" alt=" " width="640" height="716"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How to actually manage this on the server&lt;br&gt;
Practically, here’s the setup that works well:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define a prioritized list of models, not a single model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;LLM_PRIORITY = [&lt;br&gt;
    {"name": "primary_llm", "provider": "provider_a", "model": "model-a-large"},&lt;br&gt;
    {"name": "secondary_llm", "provider": "provider_b", "model": "model-b-large"},&lt;br&gt;
    {"name": "fallback_llm", "provider": "provider_c", "model": "model-c-small"},&lt;br&gt;
]&lt;br&gt;
Order them by quality/cost first, reliability second. The first entry is your “ideal” answer quality; the rest exist purely so a request never just dies.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wrap every call in a router function that catches specific failure types.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;import time&lt;br&gt;
def call_llm_with_failover(prompt, models=LLM_PRIORITY, max_retries_per_model=1):&lt;br&gt;
    last_error = None&lt;br&gt;
    for model_config in models:&lt;br&gt;
        for attempt in range(max_retries_per_model):&lt;br&gt;
            try:&lt;br&gt;
                response = call_provider(&lt;br&gt;
                    provider=model_config["provider"],&lt;br&gt;
                    model=model_config["model"],&lt;br&gt;
                    prompt=prompt,&lt;br&gt;
                    timeout=15  # seconds — don't let one model hang the whole pipeline&lt;br&gt;
                )&lt;br&gt;
                # Success — record which model actually answered&lt;br&gt;
                log_model_usage(model_config["name"])&lt;br&gt;
                return response&lt;br&gt;
            except RateLimitError:&lt;br&gt;
                last_error = "rate_limited"&lt;br&gt;
                break  # don't retry the same rate-limited model, move to next one&lt;br&gt;
            except TimeoutError:&lt;br&gt;
                last_error = "timeout"&lt;br&gt;
                continue  # maybe worth one retry on the same model&lt;br&gt;
            except ServerBusyError:&lt;br&gt;
                last_error = "busy"&lt;br&gt;
                break&lt;br&gt;
    raise AllModelsUnavailableError(f"All models exhausted. Last error: {last_error}")&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Use this wrapper for both agents, not just one. Agent 1 (tool decision) and Agent 2 (refinement) should each go through the same failover router independently — it’s entirely possible for the model serving Agent 1 to be busy while the model serving Agent 2 is fine, or vice versa.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a circuit breaker so you don’t keep hammering a model that’s clearly down. A simple in-memory counter works for small-to-medium traffic:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;from collections import defaultdict&lt;br&gt;
from time import time&lt;br&gt;
failure_counts = defaultdict(list)&lt;br&gt;
COOLDOWN_SECONDS = 60&lt;br&gt;
FAILURE_THRESHOLD = 3&lt;br&gt;
def is_model_in_cooldown(model_name):&lt;br&gt;
    now = time()&lt;br&gt;
    recent_failures = [t for t in failure_counts[model_name] if now - t &amp;lt; COOLDOWN_SECONDS]&lt;br&gt;
    failure_counts[model_name] = recent_failures&lt;br&gt;
    return len(recent_failures) &amp;gt;= FAILURE_THRESHOLD&lt;br&gt;
def record_failure(model_name):&lt;br&gt;
    failure_counts[model_name].append(time())&lt;br&gt;
Check is_model_in_cooldown() before attempting a model in the priority list — if it's in cooldown, skip straight to the next one instead of wasting a request and a timeout window on a model you already know is struggling.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make the model list configurable, not hardcoded. Keep it in an environment variable or a small config file so you can reorder priority, swap providers, or add a new model without touching code:&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  .env
&lt;/h1&gt;

&lt;p&gt;LLM_PRIORITY_ORDER=primary_llm,secondary_llm,fallback_llm&lt;br&gt;
PRIMARY_LLM_PROVIDER=provider_a&lt;br&gt;
SECONDARY_LLM_PROVIDER=provider_b&lt;br&gt;
FALLBACK_LLM_PROVIDER=provider_c&lt;br&gt;
REQUEST_TIMEOUT_SECONDS=15&lt;br&gt;
COOLDOWN_SECONDS=60&lt;br&gt;
This is the difference between “I have to redeploy to change providers” and “I edit one line in .env and restart the service."&lt;/p&gt;

&lt;p&gt;Why this matters in production&lt;br&gt;
With this in place, a query never just hangs or errors out because one provider happened to be under load at that exact second. The user experience stays consistent — they get an answer, possibly from a slightly different underlying model, but they’re never staring at a spinner that times out. And because every fallback event is logged, you get visibility into how often your primary model is actually struggling, which is useful data for deciding whether to renegotiate rate limits, add a fourth fallback, or just accept the current setup.&lt;/p&gt;

&lt;p&gt;flowchart TD&lt;br&gt;
    Q[Incoming Query] --&amp;gt; P[Try Primary LLM]&lt;br&gt;
    P --&amp;gt;|Success| R[Return Answer]&lt;br&gt;
    P --&amp;gt;|Rate Limited / Busy / Timeout| S[Try Secondary LLM]&lt;br&gt;
    S --&amp;gt;|Success| R&lt;br&gt;
    S --&amp;gt;|Failure| T[Try Fallback LLM]&lt;br&gt;
    T --&amp;gt;|Success| R&lt;br&gt;
    T --&amp;gt;|Failure| U[Raise Controlled Error]&lt;br&gt;
Technology Choices&lt;br&gt;
For anyone curious about the stack, at a glance:&lt;/p&gt;

&lt;p&gt;Backend: Python web framework with async support, SQL-backed database for user/role data, secure session handling, password hashing for any stored credentials.&lt;br&gt;
Agent orchestration: a graph-based orchestration library to define the two-agent sequence cleanly, with a tool/document-processing framework underneath.&lt;br&gt;
Embeddings &amp;amp; retrieval: open embedding models for vectorization, a local vector index for similarity search, recursive text chunking, and a set of format-specific document loaders (PDF, Word, text, Markdown, web pages).&lt;br&gt;
Frontend: plain HTML/CSS/JS with a utility-first CSS framework, server-rendered templates.&lt;br&gt;
Backups: periodic backup of the embedding store to cloud object storage.&lt;br&gt;
Concurrency: thread pools and async I/O so embedding jobs and queries don’t block each other.&lt;br&gt;
What’s Next&lt;br&gt;
A few things on the roadmap:&lt;/p&gt;

&lt;p&gt;Letting admins delete individual files or specific vectors from the index, instead of only adding to it.&lt;br&gt;
Tightening retrieval accuracy by experimenting with chunk size and embedding model choice.&lt;br&gt;
Possibly collapsing the two-agent pipeline into a single, faster agent once latency becomes a bigger priority than the clean separation of concerns.&lt;br&gt;
Expanding the failover router to support weighted load balancing across models even when all of them are healthy, not just failover when one is down.&lt;br&gt;
Closing Thoughts&lt;br&gt;
The biggest lesson from this project: a RAG chatbot is “easy” to build to a demo-quality bar, and genuinely hard to build to a doesn’t-fall-over-in-production bar. The retrieval and refinement pipeline gets you accurate, grounded answers. The failover router is what keeps the lights on when your LLM provider has a bad five minutes. Both matter — and most tutorials only show you the first one.&lt;/p&gt;

&lt;p&gt;If you’re building something similar, start with the dual-agent retrieval pipeline to get accuracy right, then treat your LLM calls as a resource pool rather than a single dependency from day one. Retrofitting failover later is a lot more painful than designing for it up front.&lt;/p&gt;

</description>
      <category>rag</category>
      <category>agents</category>
      <category>agenticrag</category>
      <category>llm</category>
    </item>
    <item>
      <title>Prompt-Driven Tool-Calling for Lightweight Open Source LLMs</title>
      <dc:creator>PAWAN YADAV  (AI Engineer)</dc:creator>
      <pubDate>Fri, 19 Jun 2026 09:36:01 +0000</pubDate>
      <link>https://dev.to/2000pawan/prompt-driven-tool-calling-for-lightweight-open-source-llms-1hfh</link>
      <guid>https://dev.to/2000pawan/prompt-driven-tool-calling-for-lightweight-open-source-llms-1hfh</guid>
      <description>&lt;p&gt;🚀 How Lightweight LLMs Can Use Tools Without Large Compute: A Prompt-Driven Tool-Calling Approach&lt;/p&gt;

&lt;h1&gt;
  
  
  AI #LLM #MachineLearning #AIAgents #PromptEngineering #OpenSourceAI
&lt;/h1&gt;

&lt;p&gt;🚀 Introduction&lt;/p&gt;

&lt;p&gt;Large Language Models (LLMs) like GPT-4 or Claude are extremely powerful, but they come with a major limitation:&lt;br&gt;
they require huge computational resources.&lt;/p&gt;

&lt;p&gt;But what if smaller, open-source models could also perform complex reasoning tasks—without needing massive GPUs?&lt;/p&gt;

&lt;p&gt;This question led to my research:&lt;/p&gt;

&lt;p&gt;“Prompt-Driven Tool-Calling for Lightweight Open Source LLMs”&lt;/p&gt;

&lt;p&gt;🧠 The Problem&lt;/p&gt;

&lt;p&gt;Today’s AI systems face three key challenges:&lt;/p&gt;

&lt;p&gt;Small models lack strong reasoning ability&lt;br&gt;
Tool usage (calculators, APIs, search engines) is not native&lt;br&gt;
Large models are expensive and difficult to deploy everywhere&lt;/p&gt;

&lt;p&gt;So the gap is clear:&lt;/p&gt;

&lt;p&gt;👉 We need efficient AI agents that don’t rely on large models&lt;/p&gt;

&lt;p&gt;⚙️ The Idea: Prompt-Driven Tool Calling&lt;/p&gt;

&lt;p&gt;Instead of forcing a model to “learn everything,” we guide it using structured prompts that allow it to:&lt;/p&gt;

&lt;p&gt;Decide when to use a tool&lt;br&gt;
Select the correct tool&lt;br&gt;
Combine outputs from multiple steps&lt;br&gt;
In simple terms:&lt;/p&gt;

&lt;p&gt;The model becomes a controller, not a knowledge storage system.&lt;/p&gt;

&lt;p&gt;🔧 How It Works&lt;/p&gt;

&lt;p&gt;This system enables lightweight LLMs to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understand user intent&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The prompt helps the model break the problem into steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decide tool usage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Instead of answering directly, the model selects tools such as:&lt;/p&gt;

&lt;p&gt;Calculator&lt;br&gt;
Search engine&lt;br&gt;
API call&lt;br&gt;
External functions&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Execute multi-step reasoning&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Flow:&lt;/p&gt;

&lt;p&gt;User Question → LLM → Tool Selection → Tool Execution → Final Answer&lt;/p&gt;

&lt;p&gt;💡 Key Benefits&lt;/p&gt;

&lt;p&gt;This approach enables:&lt;/p&gt;

&lt;p&gt;✅ Smaller models to behave like intelligent agents&lt;br&gt;
✅ Reduced dependency on large proprietary LLMs&lt;br&gt;
✅ Lower compute cost&lt;br&gt;
✅ Deployment on CPUs and edge devices&lt;br&gt;
✅ More practical real-world AI systems&lt;br&gt;
🌍 Why This Matters&lt;/p&gt;

&lt;p&gt;We are moving toward a future where:&lt;/p&gt;

&lt;p&gt;Intelligence is not about model size, but about system design.&lt;/p&gt;

&lt;p&gt;Instead of scaling parameters, we scale:&lt;/p&gt;

&lt;p&gt;Tool integration&lt;br&gt;
Reasoning workflows&lt;br&gt;
System-level intelligence&lt;/p&gt;

&lt;p&gt;This makes AI:&lt;/p&gt;

&lt;p&gt;More accessible&lt;br&gt;
More affordable&lt;br&gt;
More deployable in real-world environments&lt;br&gt;
📊 My Research Contribution&lt;/p&gt;

&lt;p&gt;This work proposes a prompt-driven framework that:&lt;/p&gt;

&lt;p&gt;Enables tool-calling in lightweight open-source LLMs&lt;br&gt;
Improves multi-step reasoning capability&lt;br&gt;
Reduces dependency on large models&lt;br&gt;
Moves toward practical AI agent systems&lt;br&gt;
📄 Publication Details&lt;/p&gt;

&lt;p&gt;📌 Published in: AIS2C2 2025&lt;br&gt;
📚 Pages: 493–497&lt;/p&gt;

&lt;p&gt;🔗 Paper Link:&lt;br&gt;
&lt;a href="https://www.aiscindia.co.in/wp-content/uploads/2026/06/ilovepdf_merged-4.pdf" rel="noopener noreferrer"&gt;https://www.aiscindia.co.in/wp-content/uploads/2026/06/ilovepdf_merged-4.pdf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🚀 Final Thoughts&lt;/p&gt;

&lt;p&gt;The future of AI is not just about building bigger models.&lt;/p&gt;

&lt;p&gt;It is about building smarter systems around smaller models.&lt;/p&gt;

&lt;p&gt;Prompt-driven tool-calling is one step toward that direction.&lt;/p&gt;

&lt;p&gt;🤝 Let’s Connect&lt;/p&gt;

&lt;p&gt;I’m always open to discussions around:&lt;/p&gt;

&lt;p&gt;LLMs and AI agents&lt;br&gt;
Tool-calling systems&lt;br&gt;
Open-source AI development&lt;br&gt;
Practical AI engineering&lt;br&gt;
🔖 Tags&lt;/p&gt;

&lt;h1&gt;
  
  
  ArtificialIntelligence #LLM #AIAgents #PromptEngineering #MachineLearning #DeepLearning #OpenSourceAI
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvt3ac69qurcflbod7k6a.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvt3ac69qurcflbod7k6a.jpeg" alt=" " width="610" height="787"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>machinelearning</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
