Building an MVP Agentic Tool-Use Bot with Node.js and OpenRouter 🌤️🤖 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. 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. ⚠️ 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. 🔗 GitHub Repository: https://github.com/OriginalAnkit/ai-rag-mausam-ai https://github.com/OriginalAnkit/ai-rag-mausam-ai Let's dive into how the code works To understand why tool-use is so important, we first need to understand a massive limitation of Large Language Models LLMs : Trained models don't have real-time data. An 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. This is where Agentic workflows come in. Instead 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 The 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 . We achieve this using a strict system prompt and forcing the response format to JSON. js const MAIN SYSTEM PROMPT = You are an AI agent that reply only to queries related to weather, temperate and humidity. STRICT RULES: - output must a single valid json without any extra space, text. NO markdown , No Text, No output tags. - MUST run one step at a time. Do not run multiple steps in parallel. Stop after each step - Strictly follow the Sequence of steps must be START then PLAN then TOOL then OUTPUT - don't run a step more than once for a single query. OUTPUT FORMAT: { "step": START|PLAN|TOOL|OUTPUT, "context": "string", "input": "string", "usefull": "boolean", "toolname": "string" } AVAILABLE TOOL: - get mausam - return temperate, weather and humidity for a given location ; By 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"} . Our 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: js const getConversation = async function messages, context = { let iterations = 0; const MAX ITERATIONS = 5; while iterations < MAX ITERATIONS { iterations++; try { const completion = await callOpenRouterModel messages ; let outputContent = completion?.choices 0 ?.message?.content; // ... Parse JSON Output Safely ... if output.step === "OUTPUT" { // The AI has the final answer context.push { sender: "SYSTEM", message: output.context } ; return; } else if output.step === "TOOL" && output.toolname === "get mausam" { // The AI requested a tool. We fetch the data and feed it back let tool resp = await getWeather output.input ; messages.push { role: "system", content: \ RESPONSE FROM get mausam: \${tool resp}\ } ; } else { // Intermediate thinking steps START, PLAN context.push { sender: "BOT", message: output.context || 'Thinking' + '...' } ; messages.push { role: "system", content: outputContent } ; } await sleep 1000 ; // 1-second safety delay to prevent spamming } catch error { console.error "Agent loop error:", error.message ; context.push { sender: "SYSTEM", message: "An error occurred while processing your request." } ; return; } } } When the AI calls get mausam , it triggers a simple JavaScript fetch to wttr.in , an amazing console-oriented weather forecasting service. js async function getWeather city { const url = \ https://wttr.in/\${encodeURIComponent city }?format=%c+%C+%t+%h+%T\ ; const response = await fetch url ; if response.ok throw new Error \ Request failed\ ; const data = await response.text ; return data.trim ; // Returns e.g., "☀️ Clear +22°C 45%" } To 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. js const client1 = new OpenAI { baseURL: "https://openrouter.ai/api/v1", apiKey: process.env.API KEY } ; const client2 = new OpenAI { baseURL: "https://openrouter.ai/api/v1", apiKey: process.env.OPEN ROUTER KEY 2 } ; const callOpenRouterModel = async function messages { const requestPayload = { model: "liquid/lfm-2.5-2.6b:free", messages: messages, response format: { type: "json object" } }; try { return await client1.chat.completions.create requestPayload ; } catch error { console.error "Primary key failed, trying fallback...", error.message ; return await client2.chat.completions.create requestPayload ; } } We 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. On 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. We 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 Building 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. Feel free to check out the GitHub repo https://github.com/OriginalAnkit/ai-rag-mausam-ai and tinker with it yourself