Building an MVP Agentic Tool-Use Bot with Node.js and OpenRouter ๐ŸŒค๏ธ๐Ÿค–
DEV Community

Building an MVP Agentic Tool-Use Bot with Node.js and OpenRouter ๐ŸŒค๏ธ๐Ÿค–

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] Screen shot of Mausam AI Let's dive into how the code works! What is Agentic Tool-Use and Why Do We Need It? 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! 1. The Brains: System Prompts and Agent Logic 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. 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"} . Executing the Loop Safely 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: 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; } } } 2. The Tool: Fetching Weather Data When the AI calls get_mausam , it triggers a simple JavaScript fetch to wttr.in , an amazing console-oriented weather forecasting service. 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%" } 3. Resilient API Calls 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. 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); } } 4. The Express Backend & Chat UI 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! Conclusion 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 and tinker with it yourself! Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.