AI Classification¶
FenLiu classifies each post's text and first image using an external AI model (any OpenAI-compatible API). The result is a recommendation shown in the review interface — it does not auto-reject posts by default.
How it works¶
Two checks run on each newly-fetched post, right after it is saved:
- Text check: the status text and alt text are checked against the text policy. By default this flags promotional content where a cat is only the excuse (fundraisers, books, war victims, and similar). Flagged posts skip the image check.
- Vision check: the post's first image is classified against the vision policy. By default the image must be a real photograph of a cat (not a comic, painting, mural, meme, screenshot, AI-generated image, statue, toy, etc.).
The verdicts are stored in topic-neutral fields: text_flagged (the text
stage recommends rejection) and vision_accepted (the vision stage passes
the image). What "flagged" or "accepted" means is defined entirely by the
prompts — see below.
If a check cannot be completed (e.g. the image can't be downloaded or the API
call fails), the post is left as AI: pending and reviewed manually. Posts with
no still image skip the vision check.
For safety, image URLs are fetched with server-side request forgery (SSRF)
protection: only https:// URLs on public hosts are downloaded, redirects are
followed manually with every hop re-validated (up to five), and anything else
is treated as a failed download — the post stays AI: pending for manual
review.
The combined verdict is stored as ai_would_reject and shown as a badge on each
review card:
| Badge | Meaning |
|---|---|
AI: pending |
Not yet classified |
AI: OK |
Passed both checks |
AI: reject — <category or reason> |
Failed a check — the vision category (e.g. comic) for vision-stage rejects, the text stage's reason (truncated) for text-stage rejects |
Review filters¶
The review page has two AI filters:
- Show only AI-would-reject — list only posts the AI recommends rejecting.
- Hide not-yet-classified — hide posts the AI hasn't processed yet.
Settings¶
ai_auto_reject(default off): when enabled, posts the AI would reject are auto-rejected. Leave off until you trust the classifier.
Prompts¶
The text and vision policies are editable on the Settings page — one textarea per stage. Out of the box both describe the default cat-curation topic, so a fresh FenLiu instance behaves exactly as before. To curate a different topic (birds, books, pottery, …), rewrite both prompts to describe your topic and what should be flagged or accepted.
Two things to know:
- The machine-readable part of the prompt (the JSON output contract) is appended automatically by the code and is not part of the editable text.
- Prompt changes apply to future fetches only — existing posts keep their verdicts.
Configuration¶
| Variable | Default | Description |
|---|---|---|
AI_API_BASE_URL |
https://api.moonshot.ai/v1 |
OpenAI-compatible API base URL |
AI_PROVIDER |
moonshot |
AI back end: moonshot (Moonshot direct) or openrouter (OpenRouter); an unrecognised value fails at startup |
AI_API_KEY |
(none — required) | API key |
AI_TEXT_MODEL |
kimi-k2.6 |
Model for the text check |
AI_VISION_MODEL |
kimi-k2.6 |
Model for the vision check |
FenLiu works against either of these back ends, selected by AI_PROVIDER:
AI_PROVIDER |
Base URL for AI_API_BASE_URL |
Model for AI_TEXT_MODEL / AI_VISION_MODEL |
|---|---|---|
moonshot |
https://api.moonshot.ai/v1 |
kimi-k2.6 |
openrouter |
https://openrouter.ai/api/v1 |
moonshotai/kimi-k2.6 |
The setting only selects the reasoning-disable request parameter. The second
and third columns are the values to set in AI_API_BASE_URL and
AI_TEXT_MODEL/AI_VISION_MODEL when switching back ends — AI_PROVIDER
alone does not change them. Switching also requires an API key issued by that
provider in AI_API_KEY: a Moonshot key will not authenticate against
OpenRouter, and vice versa.
The reasoning-disable request parameter follows the back end: Moonshot's
thinking parameter for the Moonshot API, OpenRouter's unified reasoning
parameter (per OpenRouter's reasoning documentation)
for OpenRouter. Reply handling is independent of the setting —
parsing reads whichever reasoning field the reply actually carries, and the
malformed-reply diagnostics log the serving provider name that OpenRouter
reports.