Ever get that feeling where you want to understand the "magic" behind an AI assistant, but every tutorial suggests connecting a dozen libraries and getting a ready-made box? LangChain, LlamaIndex, AutoGen — powerful tools, but they hide the most interesting part: how the conversation between your code and the language model actually works.
And the principle is surprisingly simple: you sent a list of messages — you got a response. That's it. Seriously. Let's build a chatbot in a minute and figure out what happens behind the scenes of every AI assistant.
What We'll End Up With
A console chatbot that:
Two Steps Before Starting
Before writing code, you need two things:
1. API Key from OpenRouter
Go to https://openrouter.ai, create an account and generate an API key. A free model will work for starters — no credit card needed.
2. Create a .env file in the project root
OPENROUTER_API_KEY=your_key_here
Important: this file should never end up in a git repository. Add it to .gitignore right away — this is basic security practice.
Imports and Setup
import os, requests, json
import dotenv
dotenv.load_dotenv()
url = "https://openrouter.ai/api/v1/chat/completions"
api_key = os.getenv("OPENROUTER_API_KEY")
model_name = "openrouter/free"
Here we load environment variables from the .env file. dotenv is the only external dependency in our project, and it's needed only for convenience: to avoid storing secrets directly in code.
The `url` points to the standard OpenAI-compatible API. Almost all modern LLM providers use the same request format — which means our bot is easily adaptable for OpenAI, Anthropic, or any other models.
Managing Dialog Context
class Context:
def __init__(self):
self.prompt = []
def add(self, role, message):
self.prompt.append({"role": role, "content": message})
This is the most important part of the code. Language models don't "remember" previous messages — they receive the entire list of messages at once with each request. That's why we store the entire conversation history in the `self.prompt` list.
Each message consists of two parts: role (`system`, `user`, `assistant`) and text. The system message sets the bot's behavior, user messages are your questions, assistant responses are its reactions.
Sending a Request to the Model
def send_to_llm(context):
data = {
"model": model_name,
"messages": context.prompt
}
headers = {
"Authorization": "Bearer " + api_key
}
response = requests.post(url,
headers=headers, data=json.dumps(data)
)
response.raise_for_status()
result = response.json()
return result["choices"][0]["message"]["content"]
requests.post sends a POST request with JSON data to the API endpoint. In response, the model returns JSON, from which we only need the text — `result["choices"][0]["message"]["content"]`.
Note `response.raise_for_status()` — this is an error check. If the API returns an error (wrong key, request limit), the code will stop with a clear message instead of silently glitching.
Main Communication Loop
context = Context()
context.add("system", "You are a helper for programmers")
print("Enter your query")
while True:
print("> ", end="")
query = input().strip()
if query == "":
continue
if query.lower() in ["q", "quit"]:
break
context.add("user", query)
answer = send_to_llm(context)
print(answer)
context.add("assistant", answer)
A simple infinite loop: read input → added to context → sent to model → printed response → added response to context. Notice that we add both the question and the answer — so the model "sees" the entire conversation on the next request.
A small nuance: the model has no memory of its own. Each time, the entire conversation history is sent to it again. On long conversations this can become expensive — but for getting started, this is the perfect approach.
Why This Works Better Than You'd Think
Many people think that creating an AI bot requires complex infrastructure. But in reality, the dialog format is standard — the same for GPT, Claude, Llama, and dozens of other models. Mastering this pattern, you understand the principle behind any AI assistant: it's simply a function that takes a list of messages and returns a new one.
Here's what you can already do with this code:
A simple AI chatbot in Python — it's not an evening training project, but the foundation for understanding how modern AI assistants work. Just a few lines of code, one external library, and a free API key — and you get a working bot with dialog context.
The key takeaway: behind every AI is an ordinary HTTP request. The model doesn't "think" and doesn't "understand" — it predicts the next word based on a list of messages you sent it. Understanding this principle is the key to creating any AI tools, from simple bots to complex agentic systems.
Try running this code, experiment with system messages and models. Then ask yourself: what if I gave the bot access to a database? Questions are more important than ready-made answers.