What I actually run
The architecture behind every guide on this site. If a guide says "tested against a multi-tenant pipeline", this is the pipeline it was tested against.
The stack
| Part | What it is | Notes |
|---|---|---|
| API layer | .NET 8, ASP.NET Core, tenant-isolated by a header on every request | One binary per tenant boundary. The tenant id is checked before any query runs, not after. |
| Retrieval and generation | Python 3.13, FastAPI, embedding index + rerank pass before generation | Retrieval runs before the model call, not after. It is the cheapest way to cut token spend. |
| Browser automation | Playwright with a persistent profile, one worker per platform | This is where most of the failures live: session expiry, captcha, and layout drift. |
| Model access | DeepSeek chat for bulk generation, a long-context model for the final rewrite | Cheapest model for the draft, strongest model for the sentence that has to be right. |
| Storage | MySQL for content and tenant config, object storage for images | Images are normalised to a single format and a single size before they are stored. |
| This site | Astro 4, static output, zero client-side JavaScript | The site itself is a static build. No runtime, no database, nothing to patch. |
How a request actually flows
This is the shape most of the guides describe in detail, because it is the part that breaks: a tenant asks for content, the API resolves the tenant, the retrieval layer pulls the tenant's own knowledge graph and any article that already covers the topic, the model drafts against that material, a cheap model checks the result against the brand and topic rules, and only then does the browser layer publish it.
The ordering matters more than any individual model. Retrieval before generation keeps the prompt small. The compliance check before publication keeps a rejected post from consuming an account's standing. Both of those are covered in the tutorials.
The step that catches people out is the last one. An LLM call that succeeds and an article that publishes are two different problems, and the second one has more failure modes than the first: session expiry, captcha prompts, and platform layout changes that silently break a selector that worked last week.
Services
- Nginx — serves this site as static files, with TLS
- 宝塔面板 — process supervision, certificates, logs, file management
- Docker — the runtime the Python services run under
- Let's Encrypt — certificate issuance and renewal, automated
- MySQL — tenant config, generated content, publish queue
- Playwright workers — the browser layer that posts across platforms
A short list is deliberate. Every additional service is another thing to patch, back up and remember. I remove one before adding one.
What it costs
| Item | Cost |
|---|---|
| Application server | about $24/month |
| Model API spend | the single largest line — see the cost post |
| Object storage | about $1/month |
| Domain | about $12/year |
| TLS | free |
Model spend dominates everything else combined, which is why there is a whole guide about cutting it rather than about tuning the server. Nothing here was sent to me for review, and no link on this site is an affiliate link until a real tracking id is in place. If that ever changes, the guide will say so in its first paragraph.
What I would change
The single biggest gap is evaluation. Right now the quality check is a set of rules and a cheap model call; there is no regression set that runs against every prompt change, so some quality regressions are only caught when a reader notices. A pinned evaluation harness with a few hundred labelled cases is the first thing I would add.
The second is observability. Traces exist, but they are not connected across the generation step and the publish step, so a post that fails to publish cannot be traced back to the prompt that produced it. That gap is exactly why the observability review on this site is worth reading before you buy anything for the job.
Questions about any of this? Email me. Written by Henry Xiang.