→ From Coder to Shipper
From Coder to Shipper · AI

AI on your stack — llama-server & OpenRouter

How you actually add AI to a web app: run your own model on your server, or call a hosted API. Both are just another HTTP service.

← / → to navigate · EN/VI switch below

Big picture

Adding AI to your stack

Your app already talks HTTP (Flask, curl, nginx). AI is just one more thing it can call over the same wire — the only question is who runs the model.

🌐 Browser

asks your app a question

▶

⚙️ Flask app

/ask — forwards to a model

▶
🏠 llama-server — your own model, on your box
☁️ OpenRouter — hosted models, one API key

same HTTP call either way — only the address (and who pays) changes

Big picture · the two flavors

Two ways to get AI in

There are exactly two shapes. Everything in this topic is a detail of one of these.

(a) Run your own model

You download a model file and serve it yourself with llama.cpp's llama-server. It becomes a local HTTP endpoint that answers chat requests. You own it, it stays on your machine.

(b) Call a hosted API

Someone else runs the models. You send a request to a URL (e.g. OpenRouter) with an API key and pay per token. No hardware, no setup — just an HTTP call to a provider.

AI is not a new kind of technology for your app. It is just another service you call over HTTP — like your database, like any third-party API. You send a message, it sends text back.
the whole mental model, in one line
01
Part 1

The mental model

The one decision that shapes everything: do you run the model, or does someone else?

Mental model · the core tradeoff

Self-host vs hosted

It's not a technical difference — it's an economics-and-control difference. The code on your side is almost identical.

Self-host — you own it

The model runs on your VPS. Your prompts and data never leave your machine. No per-token fees — you pay for the server and the electricity.

Wins on privacy, control, and running cost. Costs you in setup, hardware, and scale.

Hosted — a provider owns it

The model runs on the provider's GPUs. Your prompts cross the internet to reach it. You pay per token, only when you use it, and you can swap models any time.

Wins on scale, effort, and variety. Costs you in per-token fees and data leaving your box.

Mental model · the axis

One axis, two ends

Most real products sit somewhere in the middle — or pick a different end for different features. You move along this axis as your needs change.

← you pay in effort + hardware you pay per token →

🏠 Self-host (llama-server)

Privacy, control, fixed cost. Your data never leaves the machine. You own the failure too.

☁️ Hosted (OpenRouter)

Scale, variety, zero setup. Pick any model, pay as you go. Your prompts travel to the provider.

the code is the same — only the endpoint and the bill differ

Mental model · side by side

Self-host vs hosted, by the numbers

Same request on your side. These five rows are the whole decision.

Dimension🏠 Self-host☁️ Hosted
Who runs the modelYou — on your VPSThe provider — on their GPUs
Where your data goesStays on your machineCrosses the internet to them
Upfront / fixed costServer + GPU/RAM you must buyNone — no hardware
Per-use costNone per request (you pay the server)Pay per token, only when used
Model choice & scaleLimited by your hardwareHundreds of models, elastic scale
Mental model · the shape

The shape of the conversation

Whatever you call — local or hosted — the request and response always have this shape. That's why the code barely changes.

You send messages

A list of messages — each with a role (system / user / assistant) and text. This is the whole prompt.

It speaks HTTP

A POST to a /v1/chat/completions URL, with a JSON body. The same verb and shape you've used with curl all course.

It returns text

A single JSON answer — or a stream where tokens arrive one by one. Either way: text out, that's the whole job.

02
Part 2

Local LLM — llama.cpp / llama-server

What a model really is, how you serve it, and why model size is the whole game.

Local · what a model is

A model is just a big file

No magic. A trained model is a file of numbers (its weights). You load it, it runs, and it exposes an HTTP endpoint.

Weights, in one file

The model is a single file — often in GGUF format for llama.cpp. Download it and it's yours. 8B ≈ a few GB on disk.

You load and serve it

llama-server reads the file into RAM/VRAM, then listens on a port and answers requests. It behaves like a tiny server.

It speaks an API

The endpoint is OpenAI-compatible — the same /v1/chat/completions shape as every hosted provider. So your code is identical.

Local · run it

Serve a model in two commands

Grab the llama-server binary, make it runnable, and start it pointed at your model file. It now answers on :8080.

ops@vps — zsh
$ curl -LO https://github.com/ggml-org/llama.cpp/releases/latest/download/llama-server
$ chmod +x llama-server
$ ./llama-server -m models/llama-3.1-8b.Q4_K_M.gguf --port 8080 --ctx-size 4096
llama server: loading model models/llama-3.1-8b.Q4_K_M.gguf
llama server: loaded in 3.2s
llama server: listening on http://127.0.0.1:8080
Local · size is everything

RAM/VRAM is the hard limit

Two things fight over your machine's memory: the model itself, and the context (the conversation it must remember). Size sets both.

Model size → what fits

An 8B model needs roughly 6–8 GB of RAM/VRAM just to load. A 70B model needs far more — most small VPSes simply can't hold it.

Context size → how much it remembers

Every token of conversation costs memory. Bigger --ctx-size means longer conversations but more RAM. You tune this to fit the model.

Local · the tradeoff

Bigger model, more context — pick one

On a fixed-amount machine you can't have everything. This is the daily tension of running a local model.

Big model, short context

Load the largest model your RAM allows. Smarter answers, but the model can only see a small window of the conversation.

Good for one-shot Q&A on modest hardware.

Smaller model, long context

Pick a smaller model so there's room for a long conversation, a big file, or a long document to process.

Good for processing documents or long chat threads.

Local · a rough map

Size → memory → what fits on a VPS

Rough numbers for 4-bit models. Your exact figures vary by model and quantization — this is the shape to plan around.

Model sizeRough RAM/VRAM to loadTypical fit
~7–8B≈ 6–8 GBA 16 GB VPS, comfortably, with room for context
~13B≈ 10–12 GBA 16 GB VPS, tighter — context shrinks
~30–34B≈ 20–24 GBA 32 GB box, or a GPU with that much VRAM
~70B≈ 40–48 GBMulti-GPU or a big-VRAM machine — not a small VPS
03
Part 3

The API shape

One request in, one answer out. It speaks the OpenAI-compatible shape — so local and hosted look identical.

API · the request

What you actually send

The body is a list of messages plus a few knobs. model names the model, messages is the whole conversation, stream asks for tokens one-by-one.

request.json
{
  "model": "llama-3.1-8b",
  "messages": [
    {"role": "user", "content": "Explain what a reverse proxy is, in one sentence."}
  ],
  "max_tokens": 120,
  "temperature": 0.7,
  "stream": true
}

That's the entire request. roles are system / user / assistant; content is the text.

API · with curl

Ask your local model

A POST to the local endpoint. The -d body is exactly the JSON above — nothing more, nothing less.

bash
# local llama-server, OpenAI-compatible
curl http://localhost:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "llama-3.1-8b",
    "messages": [{"role": "user", "content": "Hello there"}]
  }'

Same verb, same path, same body you'd send to any provider — only the host is 127.0.0.1.

API · single vs stream

One answer, or a stream

The stream flag is the difference between a chat that waits and a chat that types. Same request, different response framing.

stream: false — one blob

The server thinks the whole answer, then returns it as one JSON object. Simple, but the user waits and sees nothing until it's done.

Fine for short, one-shot answers.

stream: true — token by token

The server sends the answer as a series of small chunks, each with the next few tokens. Your UI paints them as they arrive — the typing effect.

The default feel of every modern chat app.

API · streaming

Streaming, one by one

With stream: true, the response is a sequence of data: lines. Each carries the next slice; [DONE] marks the end.

ops@vps — zsh
$ curl -N http://localhost:8080/v1/chat/completions -d '{"stream": true}'
data: {"choices":[{"delta":{"content":"Hi"}}]}
data: {"choices":[{"delta":{"content":"! I'm"}}]}
data: {"choices":[{"delta":{"content":" a model."}}]}
data: [DONE]
04
Part 4

OpenRouter — the hosted API

Skip the hardware. One key opens a gateway to hundreds of models from many providers.

OpenRouter · the idea

One key, many models

OpenRouter is a router in front of lots of providers. You call one URL; it forwards to whichever model you named.

Pick a model per request

The model field is the whole switch — openai/gpt-4o-mini, anthropic/claude, a local model, and more, all in one field.

One API key

A single Bearer token works across every model on the platform. No per-provider keys to juggle in your app.

Pay per use

You're billed per token, per model, only when you send a request. Some smaller models are even free. No server to run.

OpenRouter · the request

An OpenRouter request

Identical shape to the local call — same body, same /v1/chat/completions path. Two things change: the host, and the Authorization header.

bash
# one key, any model — pick the model in the body
curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer $OPENROUTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "messages": [{"role": "user", "content": "Hello"}]
  }'

Bearer token in the header; the model chosen in the body. That's the whole difference from local.

OpenRouter · same shape, two doors

Same body, two addresses

Line up the two calls. The JSON body is word-for-word the same — only the URL and the key differ.

Local — llama-server

POST to 127.0.0.1:8080/v1/chat/completions. No auth header needed — it's your own machine. You pay in hardware.

http://127.0.0.1:8080/v1/chat/completions

Hosted — OpenRouter

POST to openrouter.ai/api/v1/chat/completions with a Bearer key. No hardware — you pay per token.

https://openrouter.ai/api/v1/chat/completions

OpenRouter · why use it

When hosted is the right call

It's not 'better' than self-host — it's better for these situations.

You need scale or variety

Traffic spikes, many users, or you want to try ten models this week. You'd never buy ten GPUs for that — but a key handles it.

Privacy isn't the bottleneck

For general-purpose features where prompts don't contain sensitive user data, sending them to a provider is a fine trade for zero ops.

You want to start today

No model to download, no RAM to size, no server to babysit. Create a key, send a request, ship. You can self-host later.

And keep a local escape hatch

Because the request shape is identical, a feature built on OpenRouter can point at your own llama-server tomorrow — just change the URL and key.

05
Part 5

Using it in your app

Your Flask app becomes a forwarder: it holds the key, sends the messages, and returns the text.

App · the forwarder

Forward it in Flask

A few lines: read the user's message, POST it to the model (local or OpenRouter), return the text. You'll use AI to build the real thing — this is the shape.

app.py
# one endpoint that forwards the user's message to the model
@app.route("/ask", methods=["POST"])
def ask():
    prompt = request.json.get("message")
    r = requests.post(MODEL_URL,  # local /v1 OR OpenRouter /v1
        headers={"Authorization": "Bearer " + MODEL_KEY},
        json={"model": "openai/gpt-4o-mini",
              "messages": [{"role": "user", "content": prompt}]})
    reply = r.json()["choices"][0]["message"]["content"]
    return {"reply": reply}

MODEL_URL + MODEL_KEY are the only two things that differ between local and hosted. Everything else is the same.

App · the flow

One endpoint, one round trip

Follow a question from the user to the answer. Your app holds the key and never exposes it to the browser.

🌐 User

POST /ask

▶

⚙️ Flask /ask

holds the key, builds the messages, forwards

key stays server-side

▶
🏠 llama-server :8080/v1
☁️ OpenRouter /api/v1
◀

…and the model's text streams back up the same path, to the user.

App · practical choices

Three decisions before you ship

Before the code, choose these. They're cheap to get right and expensive to regret.

Model size for your VPS

Match the model to the RAM you have. If you only have 8 GB, an 8B 4-bit model with modest context is your sweet spot — not a 70B dream.

Context length

Set --ctx-size (or the request's limit) to how much you actually need. Too small drops history; too big eats memory for nothing.

Stream or not

Turn streaming on for chat — it feels alive and hides the wait. Keep it off for short, machine-parsed answers where one JSON is simpler.

Big picture · the takeaway

AI is just another service

Your app doesn't talk to 'AI'. It talks to a URL. That URL can be your own llama-server or a provider — and nothing else in your app changes.

⚙️ Your app

sends a message

HTTP ▶ ◀ text
🧠 any model, any provider
🏠 llama-server — self-host, private, yours
☁️ OpenRouter — hosted, many models, pay per use

self-host for privacy and control · a provider for scale and variety — and the code barely moves between them

Big picture · out of scope

What we're deliberately skipping

These are real and important — but they're not your next step, and they don't change how you call the service.

Transformer internals

Attention, layers, how the model actually computes. Fascinating, but irrelevant to wiring it into your app.

Fine-tuning

Training a model further on your own data. Powerful, but a whole project of its own — prompt and API first.

Quantization details

The math behind 4-bit vs 8-bit. You'll just pick a GGUF file that fits your RAM — the why is optional.

GPU tuning & batching

Squeezing tokens-per-second out of a GPU. An optimization topic, far beyond a first integration.

You don't need to understand the model to use it. You need to understand the contract: send messages over HTTP, receive text. Own it for privacy, rent it for scale — and the code barely moves.
the architect's view

Model chosen. Key set. Text flowing.

You now see AI as what it is: another HTTP service. Self-host with llama-server or rent from OpenRouter — and forward it through your Flask app.