* [LLM Classification] Do not HTML-encode re-used tags again The tags of an entry are stored HTML-encoded: `applyClassification()` encodes the tags returned by the LLM with `htmlspecialchars()`, like SimplePie does for the categories of the feeds. When an already classified entry is updated without any change to its prompt, the extension re-uses the stored tags instead of calling the LLM again, but passed them as-is to `applyClassification()`, which encoded them a second time. So a tag such as `R&D` became `R&D` in the database after classification, then `R&D` after the next such update, and so on. When a list of allowed tags is configured, the encoded tags did not match the list anymore (`R&D` is not `R&D`), and were silently removed from the entry. Decode the stored tags before re-using them, so that they go through the same encoding and allow-list check as the tags returned by the LLM. Decoding first also lets tags match a configured prefix containing such characters. Bump the extension version to 0.3.2. * [LLM Classification] Decode single quotes in re-used tags too
FreshRSS - LLM Classification extension
This FreshRSS extension automatically tags incoming RSS articles by calling an OpenAI-compatible LLM API. It classifies articles on insertion using a customizable prompt, and applies tags based on the LLM response.
Features
- Compatible with any OpenAI-compatible API (OpenAI, Ollama, LocalAI, etc.)
- Customizable user prompt with template placeholders for article data
- Tag prefixing (e.g.
llm/) - Entry filtering via FreshRSS Boolean search syntax
- Response caching to avoid redundant API calls
- Content truncation to control token usage
- Skips re-classifying updated articles without any prompt-relevant change, avoiding redundant LLM API calls
Requirements
- FreshRSS 1.28.2+
- An OpenAI-compatible LLM API endpoint
Installation
To use it, ensure that the ./xExtension-LlmClassification/ directory can be found under FreshRSS ./extensions/ directory, and enable it on the extension panel in FreshRSS.
Configuration
API Configuration
| Setting | Default | Description |
|---|---|---|
| API URL | (empty) | OpenAI-compatible API base URL (e.g. https://api.openai.com/v1) |
| API Key | (empty) | Bearer token for API authentication (optional for local APIs) |
| Model | gpt-4o-mini |
Model name |
| Timeout | 30 |
HTTP request timeout in seconds (1–300) |
Prompts
The system prompt is read-only and constrains the LLM to return a JSON object with the structure {"tags": ["tag1", "tag2"]}.
The user prompt is an editable template. The following placeholders are replaced with article data before each API call:
| Placeholder | Value |
|---|---|
{title} |
Article title |
{content} |
Article content (HTML stripped, truncated if needed) |
{author} |
Article author(s) |
{url} |
Article link |
{feed_url} |
Feed URL |
{feed_name} |
Feed name |
{date} |
Article date |
{tags} |
Existing article tags |
Max content length (default: 4000 characters): Maximum number of characters for the {content} placeholder. Set to 0 for unlimited.
Tag Classification
| Setting | Default | Description |
|---|---|---|
| Enable tag classification | Off | Master toggle for the extension |
| Tag prefix | (empty) | Prefix prepended to each LLM-generated tag (e.g. llm/) |
| Allowed tags | (empty) | Whitelist of accepted tags (one per line). If set, only these tags are kept from the LLM response. Empty = all tags allowed |
Conditions for tagging
Search filters (one per line): Only entries matching at least one filter are classified. Uses FreshRSS Boolean search syntax. Leave empty to classify all entries.
How it works
- A new article arrives in FreshRSS
- The extension checks if tag classification is enabled and the article matches the configured search filters
- The user prompt is built by replacing placeholders with article data
- For updates of previously-classified articles, the new prompt is compared against the stored hash; if unchanged, the previous tags are restored and no API call is made
- Otherwise, the LLM API is called with the system prompt and the user prompt
- The returned tags are validated (prefix prepended, whitelist enforced) and applied to the article
- The prompt hash is stored along the entry