AI Model Configuration
Loquent uses 17 distinct AI operations (areas), each backed by an OpenRouter model. Super admins can override the default model for any area through the admin panel. Changes take effect immediately — no restart needed.
How It Works
Section titled “How It Works”Service needs AI → resolve_model(AiArea::ExtractTodos) → Query ai_model_config for "extract_todos" → Override exists? Use it. No row? Use hardcoded default. → Build OpenRouter request with resolved model IDThe ai_model_config table stores overrides only. No row means the area uses its hardcoded default. Resetting an area to its default deletes the override row.
Database Schema
Section titled “Database Schema”ai_model_config table
Section titled “ai_model_config table”| Column | Type | Notes |
|---|---|---|
id | UUID | Primary key |
area | Text | Unique — maps to AiArea::key() (e.g., "extract_todos") |
model_id | Text | OpenRouter model ID (e.g., "google/gemini-2.0-flash-001") |
updated_by | UUID | FK to user.id, cascade delete |
updated_at | Timestamptz |
Migration: m20260320_130000_create_ai_model_config_table.rs
AI Areas
Section titled “AI Areas”Each area has a stable key, display label, default model, and complexity tier.
| Area | Key | Tier |
|---|---|---|
| Auto Tag Contact | auto_tag_contact | Simple |
| Identify Speakers | identify_speakers | Simple |
| Knowledge Query | knowledge_query | Simple |
| Summarize Call | summarize_call | Medium |
| Update System Note | update_system_note | Medium |
| Enrich Contact | enrich_contact | Medium |
| Enrich Contact From Messages | enrich_contact_from_messages | Medium |
| Extract Todos | extract_todos | Medium |
| Analyze Call | analyze_call | Medium |
| Execute Todo | execute_todo | Complex |
| Generate Instructions | generate_instructions | Complex |
| Edit Instructions | edit_instructions | Complex |
| Custom Edit Instructions | custom_edit_instructions | Complex |
| Execute Plan | execute_plan | Complex |
| Execute Plan (Fallback) | execute_plan_fallback | Complex |
| Assess Plan Template Start Condition | assess_plan_template_start_condition | Simple |
| Instantiate Plan | instantiate_plan | Plan Creator |
The AiArea enum lives in src/mods/ai/types/ai_models_type.rs. Each variant exposes key(), label(), default_model(), and tier(). Plan creation uses a two-step flow: a cheap assessment model checks start-condition relevance, then the full plan creator model generates the title and description (see Plan — AI model split).
Resolving a Model
Section titled “Resolving a Model”Call resolve_model() anywhere a service needs an AI model:
use crate::mods::ai::{resolve_model, AiArea};
let model_name = resolve_model(AiArea::ExtractTodos).await?;
let model = Openrouter::<DynamicModel>::builder() .api_key(openrouter_conf.openrouter_api_key) .model_name(&model_name) .build()?;File: src/mods/ai/services/resolve_model_service.rs
Fallback behavior: If the ai_model_config table doesn’t exist yet (migration not run), resolve_model() logs a warning and returns the hardcoded default. AI operations never break due to missing config.
Plan executor — primary + fallback
Section titled “Plan executor — primary + fallback”The plan executor uses two areas (ExecutePlan and ExecutePlanFallback) for automatic retry with a different model:
let models = [ resolve_model(AiArea::ExecutePlan).await?, resolve_model(AiArea::ExecutePlanFallback).await?,];
for model_name in &models { match build_and_call(model_name).await { Ok(response) => break, Err(_) => continue, // try fallback }}Admin API
Section titled “Admin API”All endpoints require super admin access.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/admin/ai-model-configs | List all 17 areas with current model |
| PUT | /api/admin/ai-model-config | Update one area’s model |
| GET | /api/admin/available-models | Fetch live model list from OpenRouter |
Update request
Section titled “Update request”{ "area": "extract_todos", "model_id": "anthropic/claude-sonnet-4-20250514"}Setting model_id to the area’s default model deletes the override row (reset to default).
Config response entry
Section titled “Config response entry”struct AiModelConfigEntry { area: String, // DB key label: String, // UI label tier: String, // "Simple" | "Medium" | "Complex" | "Plan Creator" model_id: String, // Current model (override or default) default_model_id: String, // Hardcoded default is_default: bool, // true if no override exists updated_by: Option<String>, updated_at: Option<String>,}Admin Panel UI
Section titled “Admin Panel UI”Navigate to Admin → AI Models to configure models. The tab groups areas by tier, with each row showing:
- Area label and status badge (
DefaultorCustom) - Searchable model dropdown (fetched live from OpenRouter)
- Reset button (visible only for overridden areas)
- Save status indicator and last-updated metadata
Component: src/mods/admin/components/admin_ai_models_tab_component.rs
Audit Trail
Section titled “Audit Trail”Every model change records an audit entry:
| Action | When |
|---|---|
config.ai_model.update | Model changed to a non-default value |
config.ai_model.reset | Model reset to its default |
Entries include the old and new model IDs in the summary, and the area key as a tag. View them in the admin System tab’s audit log.
Key Files
Section titled “Key Files”| File | Purpose |
|---|---|
src/mods/ai/types/ai_models_type.rs | AiArea enum with 17 variants |
src/mods/ai/services/resolve_model_service.rs | resolve_model() — DB lookup with fallback |
src/mods/admin/services/admin_ai_model_config_service.rs | Get/update config, audit logging |
src/mods/admin/api/get_ai_model_configs_api.rs | List all area configs |
src/mods/admin/api/update_ai_model_config_api.rs | Update one area |
src/mods/admin/api/get_available_models_api.rs | Fetch OpenRouter model list |
src/mods/admin/components/admin_ai_models_tab_component.rs | Admin panel UI |
migration/src/m20260320_130000_create_ai_model_config_table.rs | Table migration |