- name
- lastchat-catalog
- description
- Use this skill whenever you need to add, edit, or review entries in the LastChat model catalog (catalog/lastchat_catalog.json). Covers the resolution pipeline, family/version/override hierarchy, modality rules, provider UUIDs, and step-by-step checklists for adding new models, families, and providers correctly.
# LastChat Catalog — Authoring Guide
The catalog (`catalog/lastchat_catalog.json`) is the source-of-truth for model metadata,
provider presets, and service provider registries. It is a **layered inheritance system**.
Always express metadata through the highest applicable layer — avoid repeating fields that
a parent layer already provides.
---
## 1. Top-Level Sections
| JSON key | Kotlin type | Purpose |
|---|---|---|
| `providers` | `List<CatalogProvider>` | Preset AI provider configs (base URL, auth type, icon, setup defaults) |
| `model_families` | `List<CatalogModelFamily>` | Pattern-based metadata inherited by all matching model IDs |
| `global_rules` | `List<CatalogModelRule>` | Cross-family rules evaluated before family matching |
| `model_overrides` | `List<CatalogModelOverride>` | Exact-ID entries; primarily bind model IDs to provider UUIDs |
| `search_providers` | `List<CatalogServiceProvider>` | Web search service registry |
| `tts_providers` | `List<CatalogTTSProvider>` | Text-to-speech provider registry |
| `stt_providers` | `List<CatalogServiceProvider>` | Speech-to-text provider registry |
Kotlin data models live in:
`app/src/main/java/me/rerere/rikkahub/data/ai/models/ModelCatalog.kt`
---
## 2. Resolution Pipeline
For every model ID the app encounters, metadata is built up in this exact order.
**Each layer overrides the previous**:
```
1. global_rules — pattern rules across all families (e.g. "embed" → EMBEDDING type)
2. model_families — family defaults: type, icon, modalities, abilities, provider_slug
3. └─ versions — sub-patterns within a family that override specific fields
4. model_overrides — exact ID + provider_id match; sets provider_ids and narrow corrections
```
The builder starts empty and each matched layer is applied with `applyRule` → `applyFamily`
→ `applyVersion` → `applyOverride`. A model is only resolved if at least one rule matched
(`hasMatchedRule = true`).
### Critical: Icon Resolution
`CatalogModelOverride` has **no `icon` field**. Icons are always inherited from the matched
`model_families` entry (set via `builder.iconUrl = matchedFamily.icon?.toCatalogIconUrl()`).
**Never put an icon in a `model_overrides` entry — it will be silently ignored.**
---
## 3. ModelType Values
| Value | Meaning | Auto input modality | Auto output modality |
|---|---|---|---|
| `CHAT` | Conversational LLM | `["TEXT"]` | `["TEXT"]` |
| `EMBEDDING` | Vector embedding model | `["TEXT"]` | `["TEXT"]` |
| `IMAGE` | Image generation | `["TEXT"]` | `["IMAGE"]` |
| `STT` | Speech-to-text transcription | `["AUDIO"]` | `["TEXT"]` |
`toModelTypeOrNull()` in `ModelMetadataResolver.kt` maps these strings:
| String | → ModelType |
|---|---|
| `"chat"` | CHAT |
| `"embedding"` | EMBEDDING |
| `"image"`, `"image_generation"` | IMAGE |
| `"stt"` | STT |
---
## 4. Modality Rules
- `"AUDIO"` must **only** appear in `input_modalities` of `ModelType.STT` entries.
- **Chat models must never have `"AUDIO"` in their `input_modalities`** — there is no
audio-to-chat pipeline in the app.
- The UI modality selector only exposes `TEXT` and `IMAGE` for user-editable chat models.
- `"AUDIO"` in `output_modalities` is not used by any current feature.
---
## 5. Layer-by-Layer Authoring Guide
### 5.1 `model_families` — Define a family when models share a brand/architecture name
**Required fields:** `id`, `match_patterns`, `icon`, `type`
**Optional:** `input_modalities`, `output_modalities`, `abilities`, `provider_slug`, `versions`
```json
{
"id": "whisper",
"aliases": ["whisper"],
"match_patterns": ["whisper", "distil-whisper"],
"icon": "icons/openai.svg",
"type": "STT",
"input_modalities": ["AUDIO"],
"output_modalities": ["TEXT"],
"abilities": [],
"provider_slug": "openai",
"versions": []
}
```
`match_patterns` are regex patterns tested against the model ID and its canonical form.
Use anchors like `(^|[/._-])` and `(?=$|[:/._-])` to avoid false positives.
**Available icons:** All SVG files in `catalog/icons/`. Common ones:
`openai.svg`, `gemini.svg`, `claude.svg`, `groq.svg`, `deepseek.svg`, `mistral.svg`,
`meta.svg`, `qwen.svg`, `siliconflow.svg`, `dashscope.svg`, `deepinfra.svg`,
`fireworks.svg`, `together.svg`, `volcengine.svg`, `minimax.svg`, `zhipu.svg`,
`aihubmix.svg`, `novita.svg`, `gemma.svg`, `internlm.svg`, `baidu.svg`, `spark.svg`
---
### 5.2 `versions` (inside a family) — Override specific fields for model sub-variants
Use a version when a subset of models within a family differs in `type`, `input_modalities`,
`output_modalities`, `abilities`, or `reasoning_behavior`.
**Fields are nullable** — only set what differs from the family default:
```json
{
"id": "gpt-transcribe",
"match_patterns": ["gpt-.*-transcribe"],
"type": "STT",
"input_modalities": ["AUDIO"],
"output_modalities": ["TEXT"],
"abilities": []
}
```
```json
{
"id": "gemini-image",
"match_patterns": ["gemini.*image"],
"type": "IMAGE",
"image_generation_method": "multimodal",
"input_modalities": ["TEXT", "IMAGE"],
"output_modalities": ["IMAGE"],
"abilities": []
}
```
Versions are matched in order — first match wins within a family.
---
### 5.3 `model_overrides` — Bind model IDs to provider UUIDs (and nothing else if possible)
The minimal correct form is **only three fields**:
```json
{
"id": "whisper-large-v3",
"canonical_model_id": "whisper-large-v3",
"provider_ids": ["f9fe0a18-2b30-46e1-9c57-5562654e8d64"]
}
```
Only add extra fields when the model genuinely needs to differ from its matched family:
```json
{
"id": "dall-e-3",
"canonical_model_id": "dall-e-3",
"provider_ids": ["8f9d0c75-8f29-4a27-9c2b-f8d4fd5f3e91"],
"type": "IMAGE",
"image_generation_method": "diffusion",
"input_modalities": ["TEXT"],
"output_modalities": ["IMAGE"]
}
```
---
### 5.4 `reasoning_config` — Declare model reasoning mode & UI behavior
Can be set on `model_families`, `versions`, or `model_overrides`:
```json
{
"reasoning_config": {
"type": "effort",
"supported_levels": ["off", "auto", "low", "medium", "high", "max"]
}
}
```
- `"binary"`: Models with a simple on/off thinking toggle (e.g. DeepSeek-R1, Grok, Doubao, InternLM). UI renders a clean 2-item toggle (Disabled vs Enabled).
- `"effort"`: Models with qualitative depth scales (e.g. OpenAI o1/o3/o4/gpt-5.6, Gemini 3). UI displays qualitative options without misleading token numbers.
- `"budget"`: Models taking token budgets (e.g. Claude 3.7+, Gemini 2.5, DashScope Qwen). Supports `min_tokens`, `max_tokens`, and `preset_tokens`.
### 5.5 `context_window` and `max_images_in_context`
Both fields are optional, but **strongly recommended** for completeness. They can be set at any layer — family, version, or override.
```json
{
"context_window": 1000000,
"max_images_in_context": 100
}
```
**`context_window`** (integer, in tokens): The maximum input context the model supports.
Common correct values by family (as of 2026):
| Family | context_window |
|---|---|
| GPT-5.6 Sol/Terra | 1 050 000 |
| GPT-5.6 Luna | 512 000 |
| GPT-4o, GPT-4o-mini | 128 000 |
| o1, o3, o4-mini | 200 000 |
| Claude 5 (all) | 1 000 000 |
| Claude 3.7/3.5 Sonnet | 1 000 000 (beta) |
| Claude 3.5 Haiku | 200 000 |
| Gemini 1.5 Pro | 2 000 000 |
| Gemini 2.0/2.5/3.0 | 1 000 000 |
| Gemma 4 (31B, 26B A4B) | 256 000 |
| Gemma 4 (E4B, E2B) | 128 000 |
| Gemma 3 (27B, 12B, 4B) | 128 000 |
| Gemma 2 (27B, 9B, 2B) | 8 192 |
| Llama 4 Scout | 10 000 000 |
| Llama 4 Maverick | 1 000 000 |
| DeepSeek-V4 Pro/Flash | 1 000 000 (Max output 384k) |
| DeepSeek-V4 Vision Exp | 1 000 000 (Vision: TEXT+IMAGE) |
| DeepSeek-VL2 | 128 000 (Vision: TEXT+IMAGE) |
| DeepSeek-V3, R1 | 128 000 |
| Mistral Large 2 | 128 000 |
| Mistral Small 4 | 256 000 |
| Pixtral 12B / Large | 128 000 |
| Qwen 3 (3.8-Max, 3.5, 3-VL) | 1 000 000 (Vision: TEXT+IMAGE) |
| Qwen-Plus, Qwen-Turbo, Qwen-Long | 1 000 000 |
| Qwen-Max | 262 144 |
| Qwen 2.5-VL, QvQ-72B | 131 072 (Vision: TEXT+IMAGE) |
| QwQ-32B, Qwen 2.5 | 131 072 |
| Grok 3/3-mini | 131 072 |
| Grok 4 | 256 000 |
| MiniMax-M3 | 1 000 000 |
| MiniMax-M2.x | 204 800 |
| Kimi K3 | 1 000 000 |
| Kimi K2.5 | 256 000 |
| Moonshot V1 | 128 000 |
| Hunyuan-T1, TurboS | 256 000 |
| StepFun 3.5/3.7 Flash | 256 000 |
| GLM-5, 5.2 | 1 000 000 |
| GLM-5V-Turbo, 5.3 Flash | 200 000 |
**`max_images_in_context`** (integer): Maximum images the model can accept in a single request.
Only set when the model actually accepts image input. Omit for text-only and embedding models.
| Family | max_images_in_context |
|---|---|
| Gemini (all vision) | 3 600 |
| Claude (all) | 100 |
| GPT-5.6 Sol/Terra, o3, o4-mini | 50 |
| GPT-4o | 50 |
| Gemma 4 / Gemma 3 (vision) | 10 |
| Llama 4 (multimodal) | 10 |
| Pixtral Large | 30 |
| Pixtral 12B | 30 |
| MiniMax-M3, M2 Vision | 10 |
| Kimi K3, K2.5 | 10 |
| GLM-5V, 5.3 Flash | 10 |
| Qwen VL / 3.8 (vision) | 50 |
> **Tip**: When in doubt about the exact limit, use a conservative lower bound rather than an inflated number. `max_images_in_context` is a UX hint, not enforced at the API call layer.
`api_aliases` can list alternate API IDs that map to the same canonical model:
```json
{
"id": "gemini-2.5-flash-image",
"canonical_model_id": "gemini-2.5-flash-image",
"api_aliases": ["models/gemini-2.5-flash-image", "nano-banana"],
"provider_ids": ["4e28ef61-8b96-4a1e-9f91-15b32ce6d886"]
}
```
---
### 5.4 `global_rules` — Cross-family rules applied to every model ID
Useful for patterns that span families, e.g. auto-classifying any model ID containing
`embed` as an embedding model:
```json
{
"id": "global-embedding",
"match_patterns": ["embed"],
"type": "EMBEDDING",
"input_modalities": ["TEXT"],
"output_modalities": ["TEXT"],
"abilities": []
}
```
---
### 5.5 `providers` — Preset AI provider configs
```json
{
"id": "f9fe0a18-2b30-46e1-9c57-5562654e8d64",
Voir sur GitHub