{"slug": "building-an-mvp-agentic-tool-use-bot-with-node-js-and-openrouter", "title": "Building an MVP Agentic Tool-Use Bot with Node.js and OpenRouter 🌤️🤖", "summary": "Developer Ankit has released Mausam AI, an MVP chatbot built with Node.js and OpenRouter that demonstrates an agentic tool-use architecture. The bot uses a structured loop (START, PLAN, TOOL, OUTPUT) to call an external weather API (wttr.in) and inject real-time data into the LLM's context, overcoming the limitation of static training data. The project is available on GitHub and is intended as a conceptual demonstration, not a production-ready application.", "body_md": "Have you ever wanted to build your own ChatGPT-like interface that can actually *do* things? Today, we're going to break down **Mausam AI**, a chatbot built with Node.js that can check real-time weather, temperature, and humidity for any city.\n\n**⚠️ Disclaimer:** This project is an MVP (Minimum Viable Product). It is **not a production-ready application**. Instead, it serves as a conceptual demonstration of an Agentic Tool-Use architecture.\n\n🔗 **GitHub Repository:** [[https://github.com/OriginalAnkit/ai-rag-mausam-ai](https://github.com/OriginalAnkit/ai-rag-mausam-ai)]\n\nLet's dive into how the code works!\n\nTo understand why tool-use is so important, we first need to understand a massive limitation of Large Language Models (LLMs):\n\n**Trained models don't have real-time data.** \n\nAn LLM's knowledge is frozen in time based on when it was trained. If you ask a standard, isolated model, \"What is the weather in Mumbai right now?\", it will either hallucinate a random answer or apologize, stating that it cannot browse the live internet. \n\n**This is where Agentic workflows come in.**\n\nInstead of relying solely on the LLM's static internal memory, we give the LLM a tool (`get_mausam`) that it can call to fetch the live, real-time weather report from an external API (`wttr.in`). We then inject that live data straight back into the conversation context so the LLM can generate an accurate, up-to-the-minute response!\n\nThe magic of this bot lives in `helper.js`. Instead of just asking the LLM to write text, we force the LLM to think in a structured loop: `START` ➡️ `PLAN` ➡️ `TOOL` ➡️ `OUTPUT`. \n\nWe achieve this using a strict system prompt and forcing the response format to JSON.\n\n``` js\nconst MAIN_SYSTEM_PROMPT = `\nYou are an AI agent that reply only to queries related to weather, temperate and humidity.\n\nSTRICT RULES:\n- output must a single valid json without any extra space, text. NO markdown , No Text, No output tags.\n- MUST run one step at a time. Do not run multiple steps in parallel. Stop after each step\n- Strictly follow the Sequence of steps must be START then PLAN then TOOL then OUTPUT\n- don't run a step more than once for a single query.\n\nOUTPUT FORMAT:\n{\n    \"step\": START|PLAN|TOOL|OUTPUT,\n    \"context\": \"string\",\n    \"input\": \"string\",\n    \"usefull\": \"boolean\",\n    \"toolname\": \"string\"\n}\n\nAVAILABLE TOOL:\n- get_mausam -> return temperate, weather and humidity for a given location\n`;\n```\n\nBy enforcing this structure, our backend can read the JSON step by step. If the AI decides it needs to use a tool, it outputs `{\"step\": \"TOOL\", \"toolname\": \"get_mausam\", \"input\": \"Mumbai\"}`.\n\nOur backend intercepts this and executes the tool on behalf of the AI. To prevent infinite loops or hallucinations, we wrap it in a strict `MAX_ITERATIONS` check with proper error boundaries:\n\n``` js\nconst getConversation = async function (messages, context = []) { \n    let iterations = 0;\n    const MAX_ITERATIONS = 5;\n\n    while (iterations < MAX_ITERATIONS) {\n        iterations++;\n        try {\n            const completion = await callOpenRouterModel(messages);\n            let outputContent = completion?.choices[0]?.message?.content;\n\n            // ... Parse JSON Output Safely ...\n\n            if (output.step === \"OUTPUT\") {\n                // The AI has the final answer\n                context.push({ sender: \"SYSTEM\", message: output.context });\n                return;\n            } else if (output.step === \"TOOL\" && output.toolname === \"get_mausam\") {\n                 // The AI requested a tool. We fetch the data and feed it back!\n                 let tool_resp = await getWeather(output.input);\n                 messages.push({ role: \"system\", content: \\`RESPONSE FROM get_mausam: \\${tool_resp}\\` });\n            } else { // Intermediate thinking steps (START, PLAN)\n\n                 context.push({ sender: \"BOT\", message: (output.context || 'Thinking') + '...' });\n                 messages.push({ role: \"system\", content: outputContent });\n            }\n            await sleep(1000); // 1-second safety delay to prevent spamming\n        } catch (error) {\n            console.error(\"Agent loop error:\", error.message);\n            context.push({ sender: \"SYSTEM\", message: \"An error occurred while processing your request.\" });\n            return;\n        }\n    }\n}\n```\n\nWhen the AI calls `get_mausam`, it triggers a simple JavaScript `fetch` to `wttr.in`, an amazing console-oriented weather forecasting service.\n\n``` js\nasync function getWeather(city) {\n    const url = \\`https://wttr.in/\\${encodeURIComponent(city)}?format=%c+%C+%t+%h+%T\\`;\n\n    const response = await fetch(url);\n    if (!response.ok) throw new Error(\\`Request failed\\`);\n\n    const data = await response.text(); \n    return data.trim(); // Returns e.g., \"☀️ Clear +22°C 45%\"\n}\n```\n\nTo keep our MVP robust against API key exhaustion, we built a fallback mechanism when calling OpenRouter. We use `try/catch` to attempt the primary key, and automatically fail over to a backup client if an error is thrown.\n\n``` js\nconst client1 = new OpenAI({ baseURL: \"https://openrouter.ai/api/v1\", apiKey: process.env.API_KEY });\nconst client2 = new OpenAI({ baseURL: \"https://openrouter.ai/api/v1\", apiKey: process.env.OPEN_ROUTER_KEY_2 });\n\nconst callOpenRouterModel = async function (messages) {\n    const requestPayload = {\n        model: \"liquid/lfm-2.5-2.6b:free\",\n        messages: messages,\n        response_format: { type: \"json_object\" }\n    };\n\n    try {\n        return await client1.chat.completions.create(requestPayload);\n    } catch (error) {\n        console.error(\"Primary key failed, trying fallback...\", error.message);\n        return await client2.chat.completions.create(requestPayload);\n    }\n}\n```\n\nWe wrap this entire logic inside a simple Express.js server (`app.js`). To support multiple users concurrently without race conditions, we store chat states in a `sessions` map, indexed by a unique `sessionId` generated on the frontend.\n\nOn the frontend (`index.ejs`), we have a sleek dark-mode UI that generates the `sessionId` via `sessionStorage` and polls the `GET /messages?sessionId=...` endpoint every second to stream in the AI's thoughts.\n\nWe even styled the intermediate \"thinking\" steps differently than the final answer so users can peek into the AI's reasoning without it being visually distracting!\n\nBuilding Agentic pipelines doesn't require massive frameworks. By enforcing JSON schemas and writing a simple `while` loop, you can give LLMs access to the outside world.\n\nFeel free to check out the [GitHub repo](https://github.com/OriginalAnkit/ai-rag-mausam-ai) and tinker with it yourself!", "url": "https://wpnews.pro/news/building-an-mvp-agentic-tool-use-bot-with-node-js-and-openrouter", "canonical_source": "https://dev.to/ankit_halder_7840b622b962/building-an-mvp-ai-weather-agent-with-nodejs-and-openrouter-49eg", "published_at": "2026-09-08 05:12:20+00:00", "updated_at": "2026-09-08 05:31:53.913624+00:00", "lang": "en", "topics": ["artificial-intelligence", "large-language-models", "ai-agents", "developer-tools"], "entities": ["Ankit", "Mausam AI", "Node.js", "OpenRouter", "wttr.in", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/building-an-mvp-agentic-tool-use-bot-with-node-js-and-openrouter", "markdown": "https://wpnews.pro/news/building-an-mvp-agentic-tool-use-bot-with-node-js-and-openrouter.md", "text": "https://wpnews.pro/news/building-an-mvp-agentic-tool-use-bot-with-node-js-and-openrouter.txt", "jsonld": "https://wpnews.pro/news/building-an-mvp-agentic-tool-use-bot-with-node-js-and-openrouter.jsonld"}}