Introducing michi-chat
August 30, 20264 min read
Every small cafe gets the same questions all day long. What time do you open on weekends, do you rent the space for events, do you sell beans, how much is a latte. The answers live in the owner's head and in a pile of Instagram DMs. michi-chat is my take on answering them properly: a small, self-hostable chat assistant platform where every answer comes from the business's own data, and where the bot would rather admit it does not know than make something up.
The first tenant I built it around is Mugshot Artisan Cafe, a specialty coffee shop in Pasig, Philippines. Its knowledge base and live tools are put together from the shop's public website, mugshotmnl.com. Here is what a visitor sees:

One platform, many businesses
michi-chat is multi-tenant from the first migration. A tenant is one business with its own persona, its own set of tools, its own branding and a public embed key. Adding tenant number two is a row in the database and a folder of markdown files, not a fork of the codebase.
The stack is deliberately small: Next.js (App Router) with the OpenAI SDK, a LiteLLM proxy in front of the actual model, and Postgres with pgvector through Drizzle. The app itself only knows two model aliases, michi for chat and judge for evals. Today those aliases point at local Ollama models; pointing them at DashScope or OpenAI instead is one edit in a YAML file. The application code never changes when the provider does.
Answers come from data, not vibes
When you ask about opening hours, the model does not recall them from training data. It calls a tool, the tool reads the tenant's data, and the answer is grounded in what came back. The little "Checking our info" chip in the screenshot is that tool call happening for real:

Structured facts (hours, contacts, events, venue rental) live behind tools. Everything longer lives in a RAG knowledge base: markdown docs per tenant, chunked along their headings, embedded and retrieved per tenant only. Anything that changes often, like events and weekly specials, stays on the tool side so the answer is always current.
The bot that refuses to invent a price
Mugshot does not publish prices online, which makes "how much is a latte" the perfect trap for a language model. An unguarded bot will happily invent a number that sounds right. This one is told, in its persona and in its knowledge base, that prices are only available at the counter and on FoodPanda, and the eval suite keeps it honest:

That eval suite is two golden sets. One measures retrieval recall over the knowledge base. The other runs full questions through the real chat API and has a judge model grade every answer for faithfulness and completeness. The judge runs with the semantic answer cache in the loop on purpose, because a cached wrong answer is still a wrong answer.
The boring parts are the point
Most of the work in a public-facing bot is not the chat loop, it is everything around it:
- A per-tenant daily cap, enforced server side, so a
curlloop cannot run up the model bill. The embed key selects a tenant, it is not treated as a credential. - A bait circuit breaker: three attempts to drag the bot off topic in an hour and the session is done.
- No tenant-supplied URLs anywhere. Tools are code packs a tenant enables and parameterizes, because a free-form URL field in a server that can reach internal services is an SSRF hole. The single exception, a Slack webhook for notifications, is pinned to exactly
https://hooks.slack.com/services/and nothing else. - A privacy mode that stores no conversations at all, and an append-only audit trail for every admin mutation.
- A semantic answer cache for first messages (24 hour TTL, wiped on any knowledge base change), which makes the most common questions answer instantly without ever serving stale facts.
Try it
The code is on GitHub at beany-vu/michi-chat. The whole stack runs with docker compose up -d: the app, LiteLLM and Postgres, with Ollama on the host so the demo costs nothing to run. Point the model aliases at a cloud provider when you outgrow that.
If you run a small business and answer the same five questions every day, this is the shape of tool I would want for you: cheap to run, honest by construction, and easy to walk away from because you host it yourself.