{"slug": "prevent-hallucinated-bot-commands-build-an-llm-tool-loop-for-telegram-in-node-js", "title": "Prevent Hallucinated Bot Commands: Build an LLM Tool Loop for Telegram in Node.js", "summary": "A developer outlined a Node.js pattern for Telegram bots that replaces regex and JSON.parse extraction of LLM output with native function calling, defining explicit JSON schemas for actions like get_order and create_booking. In the described approach, the model returns a structured intent that the application executes locally, keeping API keys and database access out of the prompt, with results fed back to the model for a final reply. The writeup argues the naive prompt-only approach fails predictably when models wrap JSON in Markdown fences or fabricate order IDs.", "body_md": "When building a Telegram bot powered by a Large Language Model (LLM), developers often start by feeding the user's message directly to an inference API and piping the raw string output back to `bot.sendMessage()`. This works fine for generic Q&A, but breaks down completely when the user asks the bot to execute a concrete action, such as \"Check the status of order #9482\" or \"Book a table for 2 people at 7 PM\".\n\nIf you rely on prompt engineering alone—asking the LLM to return plain text or raw JSON formatted like `{\"action\": \"check_order\", \"id\": 9482}`—the system will inevitably fail in production. The model will hallucinate missing parameters, output conversational text around the JSON block, format output with unparseable Markdown fences, or generate fabricated database records when an order ID is missing. Even worse, attempting to resolve system tasks by feeding sensitive system context or credentials into the LLM system prompt risks exposing your Telegram bot token or internal database structure.\n\nTo build a reliable transactional bot, you must implement strict function calling (tool use) in Node.js. In this pattern, the LLM does not execute actions directly and never sees your API keys. Instead, the model outputs a structured intent JSON, your Node.js application executes the local code (such as querying PostgreSQL or calling a REST service), and the results are fed back into the model to generate a final human-readable response for the Telegram user.\n\nA common anti-pattern in early AI Telegram bots is attempting to parse intended actions out of freeform assistant responses using regular expressions or `JSON.parse`. \n\nConsider a typical customer support bot built with Node.js and a standard Telegram library like `grammY` or `node-telegram-bot-api`. The naive implementation tries to extract parameters directly from unstructured model text.\n\n```\n// BAD: Relying on unstructured text and regex extraction\nbot.on('message:text', async (ctx) => {\n  const userText = ctx.message.text;\n\n  const prompt = `You are a support bot. If the user wants order status, reply ONLY with JSON: {\"action\": \"get_order\", \"id\": 123}. User: ${userText}`;\n\n  const response = await openai.chat.completions.create({\n    model: 'gpt-4o-mini',\n    messages: [{ role: 'user', content: prompt }],\n  });\n\n  const replyText = response.choices[0].message.content;\n\n  try {\n    // This will fail when the model returns ```\n{% endraw %}\njson ...\n{% raw %}\n ``` or adds text like \"Sure, here is...\"\n    const command = JSON.parse(replyText);\n    if (command.action === 'get_order') {\n      const order = await db.orders.find(command.id);\n      await ctx.reply(`Order status: ${order.status}`);\n      return;\n    }\n  } catch (err) {\n    // Falls back to sending raw text, which often leaks system instructions or fails completely\n    await ctx.reply(replyText);\n  }\n});\n```\n\nThis approach breaks under predictable operational conditions:\n\n`json ...`` JSON.parse()` throws an unhandled syntax error.`1234` or `1` to satisfy the requested JSON structure, causing your bot to query incorrect database records.\nTo fix this, you must separate model intent from execution logic using explicit API schemas and a controlled tool loop.\n\nNative function calling requires defining explicit JSON schemas for every utility function your Telegram bot supports. These schemas inform the LLM about available tools, required parameter types, and property descriptions.\n\nCreate a dedicated definitions file (`tools.js`) that defines two concrete actions: fetching an order status (`get_order`) and creating a table booking (` create_booking`).\n\n``` js\n// tools.js\nexport const botTools = [\n  {\n    type: 'function',\n    function: {\n      name: 'get_order',\n      description: 'Fetch the status and delivery details of an existing customer order.',\n      parameters: {\n        type: 'object',\n        properties: {\n          order_id: {\n            type: 'string',\n            description: 'The unique alphanumeric order identifier, e.g., ORD-5502.',\n          },\n        },\n        required: ['order_id'],\n      },\n    },\n  },\n  {\n    type: 'function',\n    function: {\n      name: 'create_booking',\n      description: 'Reserve a table for a customer at the restaurant.',\n      parameters: {\n        type: 'object',\n        properties: {\n          guest_name: {\n            type: 'string',\n            description: 'Full name of the primary guest making the reservation.',\n          },\n          party_size: {\n            type: 'integer',\n            description: 'Number of guests attending (1 to 10).',\n          },\n          booking_time: {\n            type: 'string',\n            description: 'ISO 8601 formatted date and time string for the reservation.',\n          },\n        },\n        required: ['guest_name', 'party_size', 'booking_time'],\n      },\n    },\n  },\n];\n```\n\nBy specifying `required` fields in the schema, the inference engine knows it must ask the user for clarification if essential values (like `order_id` or `party_size`) are missing from the input message, rather than guessing them.\n\nNever pass database connection strings, internal server tokens, or your Telegram Bot Token into tool descriptions or system messages. The model only receives function arguments; actual execution takes place within your Node.js runtime.\n\nThe core architecture of a reliable Telegram AI agent consists of a multi-turn conversation loop within a single HTTP request handler:\n\n`tool` role.\nHere is the full implementation using Node.js, `grammY`, and the official `@openai/api` SDK.\n\n``` python\n// bot.js\nimport { Bot } from 'grammY';\nimport OpenAI from 'openai';\nimport { botTools } from './tools.js';\n\nconst bot = new Bot(process.env.TELEGRAM_BOT_TOKEN);\nconst openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });\n\n// Local mock database handlers\nasync function getOrderDetails(orderId) {\n  const mockDb = {\n    'ORD-101': { status: 'Shipped', carrier: 'FedEx', tracking: 'FX-99281' },\n    'ORD-102': { status: 'Processing', carrier: 'Pending', tracking: 'N/A' },\n  };\n  return mockDb[orderId] || { error: 'Order not found in database.' };\n}\n\nasync function createTableBooking(guestName, partySize, bookingTime) {\n  // Validate and write to database\n  const bookingId = `BK-${Math.floor(1000 + Math.random() * 9000)}`;\n  return {\n    success: true,\n    booking_id: bookingId,\n    details: { guestName, partySize, bookingTime },\n  };\n}\n\nbot.on('message:text', async (ctx) => {\n  const userMessage = ctx.message.text;\n\n  // Initialize short-term memory array for this request lifecycle\n  const messages = [\n    {\n      role: 'system',\n      content: 'You are an automated Telegram customer support agent. Answer questions concisely using provided tools. Do not invent order statuses or fake reservations.',\n    },\n    { role: 'user', content: userMessage },\n  ];\n\n  try {\n    // Initial call to LLM with tool definitions\n    let response = await openai.chat.completions.create({\n      model: 'gpt-4o-mini',\n      messages: messages,\n      tools: botTools,\n      tool_choice: 'auto',\n    });\n\n    let responseMessage = response.choices[0].message;\n\n    // Process tool calls if requested by the model\n    if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {\n      // Append the assistant's request for tool execution to message history\n      messages.push(responseMessage);\n\n      for (const toolCall of responseMessage.tool_calls) {\n        const functionName = toolCall.function.name;\n        const functionArgs = JSON.parse(toolCall.function.arguments);\n        let functionResult;\n\n        if (functionName === 'get_order') {\n          functionResult = await getOrderDetails(functionArgs.order_id);\n        } else if (functionName === 'create_booking') {\n          functionResult = await createTableBooking(\n            functionArgs.guest_name,\n            functionArgs.party_size,\n            functionArgs.booking_time\n          );\n        } else {\n          functionResult = { error: 'Unknown function call.' };\n        }\n\n        // Return tool output to the model conversation history\n        messages.push({\n          tool_call_id: toolCall.id,\n          role: 'tool',\n          name: functionName,\n          content: JSON.stringify(functionResult),\n        });\n      }\n\n      // Second call to LLM to process tool results into natural response\n      response = await openai.chat.completions.create({\n        model: 'gpt-4o-mini',\n        messages: messages,\n      });\n\n      responseMessage = response.choices[0].message;\n    }\n\n    // Send final synthesized text to Telegram user\n    if (responseMessage.content) {\n      await ctx.reply(responseMessage.content);\n    }\n  } catch (error) {\n    console.error('Execution Error:', error);\n    await ctx.reply('Sorry, I encountered an issue processing your request. Please try again later.');\n  }\n});\n\nbot.start();\n```\n\nWhen moving an LLM-powered Telegram bot to production, relying on basic functionality will leave you open to race conditions, billing spikes, and hanging Telegram updates. Adhere to these key practices:\n\nAn LLM can occasionally loop infinitely by requesting tools back-to-back if a tool returns an error payload. Always wrap your execution loop in a strict iteration counter (e.g., maximum 3 tool turns per user update). If the limit is reached, terminate execution gracefully and inform the user.\n\nTelegram messages hard-cap at 4,096 characters per message payload. When passing database results back to the LLM, truncate large data arrays on your Node.js backend *before* inserting them into the tool output message. If the final model response exceeds 4,000 characters, split the message before invoking `ctx.reply()`.\n\nTelegram Webhooks expect an HTTP `200 OK` response within 10 to 60 seconds (depending on bot architecture). Running multiple LLM inference calls and external database queries in sequence can easily exceed webhook execution budgets. For long-running operations:\n\n`await ctx.replyWithChatAction('typing')`).` bot.api.sendMessage(chatId, text)` once complete.\nTreat arguments produced by `JSON.parse(toolCall.function.arguments)` as untrusted user input. Validate argument types and sanitize strings before feeding them directly into SQL parameters, external shell tools, or secondary API integrations.\n\nBuilding transactional Telegram integrations requires strict tool boundary separation to guarantee stability under heavy production traffic. If you need assistance scaling complex custom workflows or mini-app backends, get in touch with [BotCreator](https://botservice.biz) — studio that ships Telegram bots / Mini Apps.", "url": "https://wpnews.pro/news/prevent-hallucinated-bot-commands-build-an-llm-tool-loop-for-telegram-in-node-js", "canonical_source": "https://dev.to/serhii_a9c08345ac360cf5c8/prevent-hallucinated-bot-commands-build-an-llm-tool-loop-for-telegram-in-nodejs-2l6b", "published_at": "2026-10-26 17:02:27+00:00", "updated_at": "2026-10-26 17:12:13.657232+00:00", "lang": "en", "topics": ["ai-agents", "large-language-models", "ai-tools", "developer-tools"], "entities": ["Telegram", "Node.js", "grammY", "node-telegram-bot-api", "OpenAI", "PostgreSQL"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/prevent-hallucinated-bot-commands-build-an-llm-tool-loop-for-telegram-in-node-js", "markdown": "https://wpnews.pro/news/prevent-hallucinated-bot-commands-build-an-llm-tool-loop-for-telegram-in-node-js.md", "text": "https://wpnews.pro/news/prevent-hallucinated-bot-commands-build-an-llm-tool-loop-for-telegram-in-node-js.txt", "jsonld": "https://wpnews.pro/news/prevent-hallucinated-bot-commands-build-an-llm-tool-loop-for-telegram-in-node-js.jsonld"}}