Building Kookachat: a Telegram Interview-Prep Bot with LangGraph + OpenRouter
By Almira
I've been wanting to actually build something with LangGraph and OpenRouter instead of just reading about them, so I picked a small, self-contained problem: an interview-prep bot. The user chats with it on Telegram: it generates an interview question (behavioural or technical, for a software engineering role by default, though the role can be changed), the user answers it and it gives the user feedback on how they can reframe it properly. The app should be able to understand if your message is a response to the interview question or a follow-up question for clarification, etc.
This post walks through how it's built, plus a demo at the end.
The workflow is as follows:
The user sends /generate_questions to the bot.
The bot submits a request to the LLM (via OpenRouter) to generate an interview question and sends it back to the user on Telegram.
The user replies with either an attempt at answering it or a follow-up (a clarifying question, a request for a hint, etc.).
The bot classifies which of those two the user message is, then either generates feedback on the user's answer or replies to the user's follow-up.
At a high level, a message flows through the system like this:
Every user request Telegram sends hits the same endpoint. The handler pulls the message text and the sender's chat ID out of the payload, then goes to either GenerateQuestionGraph or HandleUserMessageGraph depending on the sender's message. Both branches end the same way: the graph's output string is passed through to_telegram_markdown_v2, POSTed to the Telegram Bot API's sendMessage endpoint for that chat_id and saved as a new Message row in the database.
This graph is a single node. It just calls the LLM once and returns.
The generate_questions node builds the system prompt from GENERATE_QUESTION_PROMPT (filled in with a question count, question type and target role), POSTs it to OpenRouter's chat completions endpoint and pulls the model's reply into state["final_answer"].
This graph has more branches, since it has to figure out what kind of message the user just sent before it can respond to it.
First it fetches the latest question in the database, then injects that question along with the user's message into a prompt where it calls the LLM to identify whether the user's message is a response to the question or a followup question. If it's a response, it generates feedback; if it's a followup, it answers the user's question; otherwise it returns an error message. All the conversations from the user and the messages sent by the bot are saved to the database so it can accurately answer the user's questions based on the previous conversation.
Go to API Keys in the account settings and click Create Key.
Copy the key (it looks like sk-or-v1-...). This is the OPENROUTER_API_KEY.
Pick a model to use (e.g. a Gemma or Llama model) and set this as MODEL. OpenRouter's models page lists what's available and their pricing; some have free tiers.
Note the chat completions endpoint, https://openrouter.ai/api/v1/chat/completions. This is the OPENROUTER_API_URL.
Telegram delivers messages by sending an HTTPS POST request to a webhook URL registered with it, and that URL needs to be reachable over the public internet. Since the app isn't deployed anywhere yet and is just running on localhost:8000, Telegram's servers can't reach it directly. ngrok solves this by opening a secure tunnel from a public HTTPS URL to the local port, so Telegram can reach a server sitting on the local machine.
It should reply with an interview question. Answer it (or ask a follow-up) and the bot should respond with feedback or a reply, formatted in Telegram's MarkdownV2.
Right now the bot only exists as long as your machine, your server and the ngrok tunnel are all up at once, which is fine for testing but not something you'd actually want to rely on. Next up is deploying it somewhere with a stable URL and pointing the webhook there instead of ngrok, so I can stop babysitting a terminal window every time I want to practice a question.
That's it for this one. If you build your own copy, I'd genuinely like to know what you use it for.