1125 lines
30 KiB
Markdown
1125 lines
30 KiB
Markdown
**mkae sure to use exact max tokens and corret syntax based on differnet model for max tokens, adn make it configurebale in backeend for max tokens per ai fucntion.
|
||
|
||
# AI MODELS DATABASE CONFIGURATION - IMPLEMENTATION PLAN
|
||
|
||
**Date**: December 24, 2025
|
||
**Status**: Planning Phase
|
||
**Priority**: HIGH - Architecture Enhancement
|
||
|
||
---
|
||
|
||
## EXECUTIVE SUMMARY
|
||
|
||
Move AI model pricing from hardcoded constants (`MODEL_RATES`, `IMAGE_MODEL_RATES`) to database-driven configuration via new `AIModelConfig` model. This enables dynamic pricing updates, multi-provider support, and full Django Admin control without code deployments.
|
||
|
||
---
|
||
|
||
## CRITICAL UNDERSTANDING: TWO DIFFERENT CREDIT CALCULATION METHODS
|
||
|
||
### **METHOD 1: TEXT MODELS (Token-Based Calculation)**
|
||
|
||
**How It Works:**
|
||
1. User triggers AI function (clustering, content generation, ideas, etc.)
|
||
2. Request sent to OpenAI with prompt
|
||
3. OpenAI returns response with **actual token usage**:
|
||
- `input_tokens`: 2518 (tokens in the prompt)
|
||
- `output_tokens`: 242 (tokens in the response)
|
||
- `model`: "gpt-4o-mini"
|
||
4. **Backend calculates credits AFTER AI call** based on:
|
||
- Total tokens = input_tokens + output_tokens
|
||
- Configuration: `CreditCostConfig.tokens_per_credit` (e.g., 150)
|
||
- Formula: `credits = CEIL(total_tokens ÷ tokens_per_credit)`
|
||
- Apply minimum: `MAX(calculated_credits, min_credits)`
|
||
5. Credits deducted based on **actual usage**, not estimate
|
||
|
||
**Example:**
|
||
```
|
||
Operation: Clustering
|
||
Tokens: 2518 input + 242 output = 2760 total
|
||
Config: 150 tokens per credit
|
||
Calculation: 2760 ÷ 150 = 18.4 → CEIL = 19 credits
|
||
Min Credits: 10
|
||
Final: MAX(19, 10) = 19 credits charged
|
||
```
|
||
|
||
**Models Using This Method:**
|
||
- gpt-4.1
|
||
- gpt-4o-mini
|
||
- gpt-4o
|
||
- gpt-5.1
|
||
- gpt-5.2
|
||
- All text generation models
|
||
|
||
**Key Point:** Credits are **NOT known until after AI response** because we need actual token usage.
|
||
|
||
---
|
||
|
||
### **METHOD 2: IMAGE MODELS (Per-Image Fixed Cost)**
|
||
|
||
**How It Works:**
|
||
1. User triggers image generation
|
||
2. **Credits calculated BEFORE AI call** based on:
|
||
- Number of images requested (n=1, 2, 3, 4)
|
||
- Image size (1024x1024, 1024x1792, 1792x1024, etc.)
|
||
- Model (dall-e-2, dall-e-3)
|
||
3. Fixed cost per image from configuration
|
||
4. Credits deducted before generation
|
||
5. No token calculation involved
|
||
|
||
**Example:**
|
||
```
|
||
Operation: Generate 2 images
|
||
Model: dall-e-3
|
||
Size: 1024x1792
|
||
Config: 5 credits per image (from CreditCostConfig.min_credits)
|
||
Calculation: 2 images × 5 credits = 10 credits
|
||
Final: 10 credits charged (known before AI call)
|
||
```
|
||
|
||
**Models Using This Method:**
|
||
- dall-e-2
|
||
- dall-e-3
|
||
- gpt-image-1
|
||
- gpt-image-1-mini
|
||
|
||
**Key Point:** Credits are **known before AI call** because it's a fixed rate per image.
|
||
|
||
---
|
||
|
||
## WHY THIS MATTERS FOR THE DATABASE MODEL
|
||
|
||
The `AIModelConfig` model must support BOTH calculation methods:
|
||
|
||
| Field | Text Models | Image Models |
|
||
|-------|-------------|--------------|
|
||
| `input_cost_per_1m` | ✅ Required | ❌ Not Used |
|
||
| `output_cost_per_1m` | ✅ Required | ❌ Not Used |
|
||
| `cost_per_image` | ❌ Not Used | ✅ Required |
|
||
| `valid_sizes` | ❌ Not Used | ✅ Required (JSON) |
|
||
| `context_window` | ✅ Required | ❌ Not Used |
|
||
| `max_output_tokens` | ✅ Required | ❌ Not Used |
|
||
|
||
**Credit Calculation Logic:**
|
||
```
|
||
IF model_type == 'text':
|
||
# AFTER AI call
|
||
total_tokens = input_tokens + output_tokens
|
||
cost_usd = (input_tokens × input_cost_per_1m + output_tokens × output_cost_per_1m) ÷ 1,000,000
|
||
credits = calculate_from_tokens(total_tokens, operation_config)
|
||
|
||
ELIF model_type == 'image':
|
||
# BEFORE AI call
|
||
cost_usd = cost_per_image × num_images
|
||
credits = min_credits_per_image × num_images # From CreditCostConfig
|
||
```
|
||
|
||
---
|
||
|
||
## PHASE 1: CREATE NEW DATABASE MODEL
|
||
|
||
**File:** `backend/igny8_core/business/billing/models.py`
|
||
|
||
**New Model:** `AIModelConfig`
|
||
|
||
### **Field Specifications**
|
||
|
||
#### Basic Information
|
||
- `model_name` (CharField, max_length=100, unique=True)
|
||
- Examples: "gpt-4o-mini", "dall-e-3", "gpt-5.1"
|
||
- Used in API calls and configuration
|
||
|
||
- `display_name` (CharField, max_length=200)
|
||
- Examples: "GPT-4o mini - Fast & Affordable", "DALL-E 3 - High Quality Images"
|
||
- Shown in Django Admin and frontend dropdowns
|
||
|
||
- `model_type` (CharField, max_length=20, choices)
|
||
- Choices: "text", "image", "embedding"
|
||
- Determines which pricing fields are used
|
||
|
||
- `provider` (CharField, max_length=50, choices)
|
||
- Choices: "openai", "anthropic", "runware", "google"
|
||
- Future-proof for multi-provider support
|
||
|
||
#### Text Model Pricing (Only for model_type='text')
|
||
- `input_cost_per_1m` (DecimalField, max_digits=10, decimal_places=4, null=True)
|
||
- Cost per 1 million input tokens (USD)
|
||
- Example: 0.15 for gpt-4o-mini
|
||
|
||
- `output_cost_per_1m` (DecimalField, max_digits=10, decimal_places=4, null=True)
|
||
- Cost per 1 million output tokens (USD)
|
||
- Example: 0.60 for gpt-4o-mini
|
||
|
||
- `context_window` (IntegerField, null=True)
|
||
- Maximum input tokens (context length)
|
||
- Example: 16000, 128000
|
||
|
||
- `max_output_tokens` (IntegerField, null=True)
|
||
- Maximum output tokens per request
|
||
- Example: 4096, 16000
|
||
|
||
#### Image Model Pricing (Only for model_type='image')
|
||
- `cost_per_image` (DecimalField, max_digits=10, decimal_places=4, null=True)
|
||
- Fixed cost per image generation (USD)
|
||
- Example: 0.040 for dall-e-3
|
||
|
||
- `valid_sizes` (JSONField, null=True, blank=True)
|
||
- Array of valid image sizes for this model
|
||
- Example: `["1024x1024", "1024x1792", "1792x1024"]` for dall-e-3
|
||
- Example: `["256x256", "512x512", "1024x1024"]` for dall-e-2
|
||
|
||
#### Capabilities
|
||
- `supports_json_mode` (BooleanField, default=False)
|
||
- True for: gpt-4o, gpt-4o-mini, gpt-4-turbo-preview, gpt-5.1, gpt-5.2
|
||
|
||
- `supports_vision` (BooleanField, default=False)
|
||
- True for models that can analyze images
|
||
|
||
- `supports_function_calling` (BooleanField, default=False)
|
||
- True for models with function calling capability
|
||
|
||
#### Status & Configuration
|
||
- `is_active` (BooleanField, default=True)
|
||
- Enable/disable model without deleting
|
||
|
||
- `is_default` (BooleanField, default=False)
|
||
- Mark as default model for its type
|
||
- Only one can be True per model_type
|
||
|
||
- `sort_order` (IntegerField, default=0)
|
||
- Control order in dropdown lists
|
||
- Lower numbers appear first
|
||
|
||
#### Metadata
|
||
- `description` (TextField, blank=True)
|
||
- Admin notes about model usage, strengths, limitations
|
||
|
||
- `release_date` (DateField, null=True, blank=True)
|
||
- When model was released/added
|
||
|
||
- `deprecation_date` (DateField, null=True, blank=True)
|
||
- When model will be removed
|
||
|
||
#### Audit Fields
|
||
- `created_at` (DateTimeField, auto_now_add=True)
|
||
- `updated_at` (DateTimeField, auto_now=True)
|
||
- `updated_by` (ForeignKey to User, null=True, on_delete=SET_NULL)
|
||
|
||
### **Model Meta**
|
||
```
|
||
app_label = 'billing'
|
||
db_table = 'igny8_ai_model_config'
|
||
verbose_name = 'AI Model Configuration'
|
||
verbose_name_plural = 'AI Model Configurations'
|
||
ordering = ['model_type', 'sort_order', 'model_name']
|
||
|
||
indexes:
|
||
- ['model_type', 'is_active']
|
||
- ['provider', 'is_active']
|
||
- ['is_default', 'model_type']
|
||
|
||
constraints:
|
||
- unique_together: None (model_name is unique)
|
||
- check: Ensure correct pricing fields based on model_type
|
||
```
|
||
|
||
### **Model Methods**
|
||
- `__str__()` - Return display_name
|
||
- `save()` - Ensure only one is_default per model_type
|
||
- `get_cost_for_tokens(input_tokens, output_tokens)` - Calculate cost for text models
|
||
- `get_cost_for_images(num_images)` - Calculate cost for image models
|
||
- `validate_size(size)` - Check if size is valid for this model
|
||
- `get_display_with_pricing()` - For dropdowns: "GPT-4o mini - $0.15/$0.60 per 1M"
|
||
|
||
---
|
||
|
||
## PHASE 2: CREATE MIGRATION WITH SEED DATA
|
||
|
||
**File:** `backend/igny8_core/business/billing/migrations/00XX_create_ai_model_config.py`
|
||
|
||
### **Migration Steps**
|
||
|
||
1. **Create Table** - `AIModelConfig` with all fields
|
||
|
||
2. **Seed Text Models** (from current MODEL_RATES):
|
||
```
|
||
gpt-4.1:
|
||
display_name: "GPT-4.1 - $2.00 / $8.00 per 1M tokens"
|
||
model_type: text
|
||
provider: openai
|
||
input_cost_per_1m: 2.00
|
||
output_cost_per_1m: 8.00
|
||
context_window: 8192
|
||
max_output_tokens: 4096
|
||
supports_json_mode: False
|
||
is_active: True
|
||
is_default: False
|
||
sort_order: 10
|
||
|
||
gpt-4o-mini:
|
||
display_name: "GPT-4o mini - $0.15 / $0.60 per 1M tokens"
|
||
model_type: text
|
||
provider: openai
|
||
input_cost_per_1m: 0.15
|
||
output_cost_per_1m: 0.60
|
||
context_window: 128000
|
||
max_output_tokens: 16000
|
||
supports_json_mode: True
|
||
is_active: True
|
||
is_default: True ← DEFAULT
|
||
sort_order: 1
|
||
|
||
gpt-4o:
|
||
display_name: "GPT-4o - $2.50 / $10.00 per 1M tokens"
|
||
model_type: text
|
||
provider: openai
|
||
input_cost_per_1m: 2.50
|
||
output_cost_per_1m: 10.00
|
||
context_window: 128000
|
||
max_output_tokens: 4096
|
||
supports_json_mode: True
|
||
supports_vision: True
|
||
is_active: True
|
||
is_default: False
|
||
sort_order: 5
|
||
|
||
gpt-5.1:
|
||
display_name: "GPT-5.1 - $1.25 / $10.00 per 1M tokens (16K)"
|
||
model_type: text
|
||
provider: openai
|
||
input_cost_per_1m: 1.25
|
||
output_cost_per_1m: 10.00
|
||
context_window: 16000
|
||
max_output_tokens: 16000
|
||
supports_json_mode: True
|
||
is_active: True
|
||
is_default: False
|
||
sort_order: 20
|
||
|
||
gpt-5.2:
|
||
display_name: "GPT-5.2 - $1.75 / $14.00 per 1M tokens (16K)"
|
||
model_type: text
|
||
provider: openai
|
||
input_cost_per_1m: 1.75
|
||
output_cost_per_1m: 14.00
|
||
context_window: 16000
|
||
max_output_tokens: 16000
|
||
supports_json_mode: True
|
||
is_active: True
|
||
is_default: False
|
||
sort_order: 30
|
||
```
|
||
|
||
3. **Seed Image Models** (from current IMAGE_MODEL_RATES):
|
||
```
|
||
dall-e-3:
|
||
display_name: "DALL-E 3 - High Quality - $0.040 per image"
|
||
model_type: image
|
||
provider: openai
|
||
cost_per_image: 0.040
|
||
valid_sizes: ["1024x1024", "1024x1792", "1792x1024"]
|
||
is_active: True
|
||
is_default: True ← DEFAULT
|
||
sort_order: 1
|
||
|
||
dall-e-2:
|
||
display_name: "DALL-E 2 - Standard - $0.020 per image"
|
||
model_type: image
|
||
provider: openai
|
||
cost_per_image: 0.020
|
||
valid_sizes: ["256x256", "512x512", "1024x1024"]
|
||
is_active: True
|
||
is_default: False
|
||
sort_order: 10
|
||
|
||
gpt-image-1:
|
||
display_name: "GPT Image 1 - $0.042 per image"
|
||
model_type: image
|
||
provider: openai
|
||
cost_per_image: 0.042
|
||
valid_sizes: ["1024x1024"]
|
||
is_active: False ← Not valid for OpenAI endpoint
|
||
is_default: False
|
||
sort_order: 20
|
||
|
||
gpt-image-1-mini:
|
||
display_name: "GPT Image 1 Mini - $0.011 per image"
|
||
model_type: image
|
||
provider: openai
|
||
cost_per_image: 0.011
|
||
valid_sizes: ["1024x1024"]
|
||
is_active: False ← Not valid for OpenAI endpoint
|
||
is_default: False
|
||
sort_order: 30
|
||
```
|
||
|
||
---
|
||
|
||
## PHASE 3: DJANGO ADMIN CONFIGURATION
|
||
|
||
**File:** `backend/igny8_core/business/billing/admin.py`
|
||
|
||
**Admin Class:** `AIModelConfigAdmin`
|
||
|
||
### **List View Configuration**
|
||
|
||
**list_display:**
|
||
- `model_name`
|
||
- `display_name`
|
||
- `model_type_badge` (colored badge)
|
||
- `provider_badge` (colored badge)
|
||
- `pricing_display` (formatted based on type)
|
||
- `is_active_icon` (boolean icon)
|
||
- `is_default_icon` (star icon)
|
||
- `sort_order`
|
||
- `updated_at`
|
||
|
||
**list_filter:**
|
||
- `model_type`
|
||
- `provider`
|
||
- `is_active`
|
||
- `is_default`
|
||
- `supports_json_mode`
|
||
- `supports_vision`
|
||
- `supports_function_calling`
|
||
|
||
**search_fields:**
|
||
- `model_name`
|
||
- `display_name`
|
||
- `description`
|
||
|
||
**ordering:**
|
||
- `model_type`, `sort_order`, `model_name`
|
||
|
||
### **Form Configuration**
|
||
|
||
**Fieldsets:**
|
||
|
||
1. **Basic Information**
|
||
- model_name (with help text about API usage)
|
||
- display_name (shown in UI)
|
||
- model_type (radio buttons: text/image/embedding)
|
||
- provider (dropdown)
|
||
- description (textarea)
|
||
|
||
2. **Text Model Pricing** (show only if model_type='text')
|
||
- input_cost_per_1m (with $ prefix)
|
||
- output_cost_per_1m (with $ prefix)
|
||
- context_window (with "tokens" suffix)
|
||
- max_output_tokens (with "tokens" suffix)
|
||
|
||
3. **Image Model Pricing** (show only if model_type='image')
|
||
- cost_per_image (with $ prefix)
|
||
- valid_sizes (JSON editor with validation)
|
||
|
||
4. **Capabilities**
|
||
- supports_json_mode (checkbox)
|
||
- supports_vision (checkbox)
|
||
- supports_function_calling (checkbox)
|
||
|
||
5. **Status & Display**
|
||
- is_active (checkbox)
|
||
- is_default (checkbox with warning)
|
||
- sort_order (number input)
|
||
|
||
6. **Metadata**
|
||
- release_date (date picker)
|
||
- deprecation_date (date picker)
|
||
|
||
7. **Audit** (readonly)
|
||
- created_at
|
||
- updated_at
|
||
- updated_by
|
||
|
||
### **Admin Actions**
|
||
|
||
1. **bulk_activate** - Enable selected models
|
||
2. **bulk_deactivate** - Disable selected models
|
||
3. **set_as_default** - Set one model as default for its type
|
||
4. **test_model_connection** - Test if model is accessible via API
|
||
5. **export_pricing_table** - Export all models and pricing to CSV
|
||
|
||
### **Custom Methods**
|
||
|
||
**pricing_display(obj):**
|
||
```
|
||
If model_type == 'text':
|
||
return f"${obj.input_cost_per_1m}/${obj.output_cost_per_1m} per 1M"
|
||
If model_type == 'image':
|
||
return f"${obj.cost_per_image} per image"
|
||
```
|
||
|
||
**Custom save() override:**
|
||
- If `is_default=True`, unset other defaults for same model_type
|
||
- Validate pricing fields based on model_type
|
||
- Log changes to admin log
|
||
|
||
---
|
||
|
||
## PHASE 4: UPDATE AI CORE (TEXT MODELS)
|
||
|
||
**File:** `backend/igny8_core/ai/ai_core.py`
|
||
|
||
### **Function:** `run_ai_request()` (line ~93-350)
|
||
|
||
**Current Implementation:**
|
||
```
|
||
Line 16: from .constants import MODEL_RATES
|
||
Line 294: rates = MODEL_RATES.get(active_model, {'input': 2.00, 'output': 8.00})
|
||
Line 295: cost = (input_tokens × rates['input'] + output_tokens × rates['output']) ÷ 1_000_000
|
||
```
|
||
|
||
**New Implementation:**
|
||
|
||
**Add new helper function:**
|
||
```
|
||
Function: get_model_pricing(model_name)
|
||
Location: After __init__, before run_ai_request
|
||
Returns: AIModelConfig instance or None
|
||
Purpose: Query database and cache result
|
||
```
|
||
|
||
**Update line 16:**
|
||
- Remove: `from .constants import MODEL_RATES`
|
||
- Add: `from igny8_core.business.billing.models import AIModelConfig`
|
||
|
||
**Update line 161 (model validation):**
|
||
- Replace: `if active_model not in MODEL_RATES:`
|
||
- With: Query `AIModelConfig.objects.filter(model_name=active_model, model_type='text', is_active=True).exists()`
|
||
|
||
**Update line 294 (cost calculation):**
|
||
- Replace: `rates = MODEL_RATES.get(...)`
|
||
- With: Query `AIModelConfig.objects.get(model_name=active_model)`
|
||
- Calculate: `cost = (input_tokens × model.input_cost_per_1m + output_tokens × model.output_cost_per_1m) ÷ 1_000_000`
|
||
|
||
**Update line 819 (cost estimation):**
|
||
- Same replacement for `MODEL_RATES.get()`
|
||
|
||
**Add caching (optional optimization):**
|
||
```
|
||
Cache model configs in memory for 5 minutes
|
||
Key: f"ai_model_config:{model_name}"
|
||
Reduces database queries
|
||
```
|
||
|
||
---
|
||
|
||
## PHASE 5: UPDATE IMAGE GENERATION
|
||
|
||
**File:** `backend/igny8_core/ai/ai_core.py`
|
||
|
||
### **Function:** `generate_image()` (line ~400-600)
|
||
|
||
**Current Implementation:**
|
||
```
|
||
Line 17: from .constants import IMAGE_MODEL_RATES
|
||
Line 581: cost = IMAGE_MODEL_RATES.get(model, 0.040) × n
|
||
```
|
||
|
||
**New Implementation:**
|
||
|
||
**Update line 17:**
|
||
- Remove: `from .constants import IMAGE_MODEL_RATES`
|
||
- Already have: `AIModelConfig` imported
|
||
|
||
**Update size validation:**
|
||
- Add function: `validate_image_size(model_name, size)`
|
||
- Query: `AIModelConfig.objects.get(model_name=model_name)`
|
||
- Check: `size in model.valid_sizes`
|
||
|
||
**Update line 581 (cost calculation):**
|
||
- Replace: `cost = IMAGE_MODEL_RATES.get(model, 0.040) × n`
|
||
- With:
|
||
```
|
||
model_config = AIModelConfig.objects.get(model_name=model, model_type='image')
|
||
cost = model_config.cost_per_image × n
|
||
```
|
||
|
||
**Add validation:**
|
||
- Ensure model is_active=True
|
||
- Ensure model.valid_sizes includes requested size
|
||
- Raise clear error if model not found
|
||
|
||
---
|
||
|
||
## PHASE 6: UPDATE VALIDATORS
|
||
|
||
**File:** `backend/igny8_core/ai/validators.py`
|
||
|
||
### **Function:** `validate_model()` (line ~147-155)
|
||
|
||
**Current Implementation:**
|
||
```
|
||
Line 147: from .constants import MODEL_RATES, VALID_OPENAI_IMAGE_MODELS
|
||
Line 150: if model not in MODEL_RATES:
|
||
```
|
||
|
||
**New Implementation:**
|
||
|
||
**Replace line 147:**
|
||
- Remove: `from .constants import MODEL_RATES`
|
||
- Add: `from igny8_core.business.billing.models import AIModelConfig`
|
||
|
||
**Replace line 150:**
|
||
```
|
||
exists = AIModelConfig.objects.filter(
|
||
model_name=model,
|
||
model_type='text',
|
||
is_active=True
|
||
).exists()
|
||
|
||
if not exists:
|
||
return {
|
||
'valid': False,
|
||
'error': f'Invalid model: {model}. Check available models in Django Admin.'
|
||
}
|
||
```
|
||
|
||
### **Add new function:** `validate_image_model_and_size(model, size)`
|
||
|
||
**Purpose:** Validate image model and size together
|
||
|
||
**Implementation:**
|
||
```
|
||
Query: AIModelConfig.objects.get(model_name=model, model_type='image', is_active=True)
|
||
Check: size in model.valid_sizes
|
||
Return: {'valid': True/False, 'error': '...', 'model': model_config}
|
||
```
|
||
|
||
---
|
||
|
||
## PHASE 7: UPDATE GLOBAL SETTINGS
|
||
|
||
**File:** `backend/igny8_core/modules/system/global_settings_models.py`
|
||
|
||
### **Model:** `GlobalIntegrationSettings`
|
||
|
||
**Current Field (line ~86):**
|
||
```
|
||
openai_model = CharField(
|
||
max_length=100,
|
||
default='gpt-4o-mini',
|
||
choices=[
|
||
('gpt-4.1', 'GPT-4.1 - $2.00 / $8.00'),
|
||
('gpt-4o-mini', 'GPT-4o mini - $0.15 / $0.60'),
|
||
...
|
||
]
|
||
)
|
||
```
|
||
|
||
**New Implementation:**
|
||
|
||
**Keep CharField but make choices dynamic:**
|
||
|
||
**Add method:**
|
||
```
|
||
Function: get_text_model_choices()
|
||
Returns: List of (model_name, display_name) tuples
|
||
Query: AIModelConfig.objects.filter(model_type='text', is_active=True)
|
||
Order: By sort_order
|
||
```
|
||
|
||
**Update admin widget:**
|
||
```
|
||
Use custom widget that loads choices from get_text_model_choices()
|
||
Refreshes on page load
|
||
Shows current pricing in dropdown
|
||
```
|
||
|
||
**Add new fields (optional):**
|
||
```
|
||
dalle_model = CharField (for image generation default)
|
||
anthropic_model = CharField (for future Anthropic support)
|
||
```
|
||
|
||
**Add validation:**
|
||
```
|
||
Clean method: Validate selected model exists in AIModelConfig
|
||
Save method: Ensure model is active
|
||
```
|
||
|
||
---
|
||
|
||
## PHASE 8: UPDATE INTEGRATION SETTINGS
|
||
|
||
**File:** `backend/igny8_core/modules/system/models.py`
|
||
|
||
### **Model:** `IntegrationSettings`
|
||
|
||
**Current:** Model stored in config JSON: `{'model': 'gpt-4o-mini'}`
|
||
|
||
**New Implementation:**
|
||
|
||
**Add validation method:**
|
||
```
|
||
Function: clean_config()
|
||
Purpose: Validate model in config exists and is active
|
||
Check: AIModelConfig.objects.filter(model_name=config['model'], is_active=True)
|
||
Raise: ValidationError if invalid
|
||
```
|
||
|
||
**Update admin:**
|
||
```
|
||
Show available models in help text
|
||
Link to AIModelConfig admin for model management
|
||
```
|
||
|
||
---
|
||
|
||
## PHASE 9: CREATE API ENDPOINT
|
||
|
||
**File:** `backend/igny8_core/api/ai/` (create directory if needed)
|
||
|
||
### **New File:** `views.py`
|
||
|
||
**ViewSet:** `AIModelViewSet(ReadOnlyModelViewSet)`
|
||
|
||
**Endpoint:** `/api/v1/ai/models/`
|
||
|
||
**Methods:**
|
||
- `list()` - Get all models with filters
|
||
- `retrieve()` - Get single model by name
|
||
|
||
**Query Filters:**
|
||
- `?type=text` - Filter by model_type
|
||
- `?type=image`
|
||
- `?provider=openai`
|
||
- `?active=true` - Only active models
|
||
- `?default=true` - Only default models
|
||
|
||
**Response Format:**
|
||
```json
|
||
{
|
||
"count": 5,
|
||
"results": [
|
||
{
|
||
"model_name": "gpt-4o-mini",
|
||
"display_name": "GPT-4o mini - $0.15 / $0.60 per 1M tokens",
|
||
"model_type": "text",
|
||
"provider": "openai",
|
||
"input_cost_per_1m": "0.1500",
|
||
"output_cost_per_1m": "0.6000",
|
||
"context_window": 128000,
|
||
"max_output_tokens": 16000,
|
||
"supports_json_mode": true,
|
||
"supports_vision": false,
|
||
"is_default": true,
|
||
"sort_order": 1
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Permissions:**
|
||
- List: Authenticated users
|
||
- Retrieve: Authenticated users
|
||
- Create/Update/Delete: Admin only (via Django Admin)
|
||
|
||
**Serializer:** `AIModelConfigSerializer`
|
||
- Include all relevant fields
|
||
- Exclude audit fields from API
|
||
- Add computed field: `pricing_display`
|
||
|
||
### **Register in URLs:**
|
||
|
||
**File:** `backend/igny8_core/urls.py` or appropriate router
|
||
|
||
```
|
||
router.register(r'ai/models', AIModelViewSet, basename='ai-models')
|
||
```
|
||
|
||
---
|
||
|
||
## PHASE 10: UPDATE SETTINGS API
|
||
|
||
**File:** `backend/igny8_core/ai/settings.py`
|
||
|
||
### **Function:** `get_model_config()` (line ~20-110)
|
||
|
||
**Current Implementation:**
|
||
- Returns model from GlobalIntegrationSettings or account override
|
||
- Validates against hardcoded MODEL_RATES
|
||
|
||
**New Implementation:**
|
||
|
||
**Update model resolution:**
|
||
```
|
||
1. Check account IntegrationSettings override
|
||
2. If no override, get from GlobalIntegrationSettings
|
||
3. Query AIModelConfig for selected model
|
||
4. Validate model exists and is_active=True
|
||
5. Return model configuration
|
||
```
|
||
|
||
**Update validation:**
|
||
- Replace: `if model not in MODEL_RATES:`
|
||
- With: Query `AIModelConfig` and check exists()
|
||
|
||
**Return enhanced config:**
|
||
```python
|
||
{
|
||
'model': model_config.model_name,
|
||
'max_tokens': model_config.max_output_tokens,
|
||
'temperature': 0.7, # From settings
|
||
'context_window': model_config.context_window,
|
||
'supports_json_mode': model_config.supports_json_mode,
|
||
'pricing': {
|
||
'input': model_config.input_cost_per_1m,
|
||
'output': model_config.output_cost_per_1m
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## PHASE 11: DEPRECATE CONSTANTS
|
||
|
||
**File:** `backend/igny8_core/ai/constants.py`
|
||
|
||
**Current:** Contains MODEL_RATES and IMAGE_MODEL_RATES dicts
|
||
|
||
**New Implementation:**
|
||
|
||
**Add deprecation warnings:**
|
||
```python
|
||
"""
|
||
DEPRECATED: MODEL_RATES and IMAGE_MODEL_RATES are deprecated.
|
||
Use AIModelConfig model instead: billing.models.AIModelConfig
|
||
This file will be removed in version X.X.X
|
||
"""
|
||
|
||
import warnings
|
||
|
||
MODEL_RATES = {
|
||
# ... existing data ...
|
||
}
|
||
|
||
def get_model_rate(model):
|
||
warnings.warn(
|
||
"MODEL_RATES is deprecated. Use AIModelConfig.objects.get(model_name=model)",
|
||
DeprecationWarning,
|
||
stacklevel=2
|
||
)
|
||
return MODEL_RATES.get(model)
|
||
```
|
||
|
||
**Keep for backward compatibility:**
|
||
- Don't remove immediately
|
||
- Mark as deprecated in docstrings
|
||
- Plan removal in next major version
|
||
- All new code should use AIModelConfig
|
||
|
||
**Update imports across codebase:**
|
||
- Search for: `from .constants import MODEL_RATES`
|
||
- Update to: `from igny8_core.business.billing.models import AIModelConfig`
|
||
|
||
---
|
||
|
||
## PHASE 12: UPDATE REPORTS
|
||
|
||
**Files:**
|
||
- `backend/igny8_core/modules/reports/views.py`
|
||
- `backend/igny8_core/modules/reports/ai_cost_analysis.py`
|
||
|
||
**Current:** May reference MODEL_RATES for display
|
||
|
||
**New Implementation:**
|
||
|
||
**Use AIModelConfig for display:**
|
||
```python
|
||
# Get model display name
|
||
model_config = AIModelConfig.objects.get(model_name=model_used)
|
||
display_name = model_config.display_name
|
||
|
||
# Show model capabilities
|
||
supports_json = model_config.supports_json_mode
|
||
```
|
||
|
||
**Cost calculations:**
|
||
- Already using `CreditUsageLog.cost_usd` (correct)
|
||
- No changes needed to calculation logic
|
||
- Only update display/filtering
|
||
|
||
**Add model metadata to reports:**
|
||
- Context window in "Model Details" section
|
||
- Pricing in "Cost Breakdown" section
|
||
- Capabilities in "Model Comparison" table
|
||
|
||
---
|
||
|
||
## PHASE 13: UPDATE TESTS
|
||
|
||
### **New Test File:** `test_ai_model_config.py`
|
||
|
||
**Test Cases:**
|
||
1. Create text model with valid pricing
|
||
2. Create image model with valid pricing
|
||
3. Validate only one default per type
|
||
4. Test cost calculation methods
|
||
5. Test size validation for images
|
||
6. Test model activation/deactivation
|
||
|
||
### **Update Existing Tests:**
|
||
|
||
**Files:**
|
||
- `backend/igny8_core/business/billing/tests/test_credit_service.py`
|
||
- `backend/igny8_core/ai/tests/test_ai_core.py`
|
||
- `backend/igny8_core/api/tests/test_ai_framework.py`
|
||
|
||
**Changes:**
|
||
- Create AIModelConfig fixtures in setUp()
|
||
- Replace MODEL_RATES mocks with database records
|
||
- Update assertions for database queries
|
||
- Test dynamic model loading
|
||
|
||
### **API Tests:** `test_ai_model_api.py`
|
||
|
||
**Test Cases:**
|
||
1. List all models
|
||
2. Filter by type
|
||
3. Filter by provider
|
||
4. Get default model
|
||
5. Permissions (readonly for users)
|
||
|
||
---
|
||
|
||
## PHASE 14: DATA MIGRATION STRATEGY
|
||
|
||
### **For Existing Production Data:**
|
||
|
||
**No Schema Changes Needed:**
|
||
- `CreditUsageLog.model_used` already stores model name
|
||
- `CreditUsageLog.cost_usd` already stores actual cost
|
||
- Historical data remains accurate
|
||
|
||
**Migration Steps:**
|
||
1. ✅ Deploy migration (creates table, seeds data)
|
||
2. ✅ Code continues using constants (no breaking changes)
|
||
3. ✅ Gradually switch code to database (per function)
|
||
4. ✅ Monitor for issues (rollback to constants if needed)
|
||
5. ✅ Mark constants as deprecated
|
||
6. ✅ Remove constants in next major version
|
||
|
||
**Rollback Plan:**
|
||
- If issues occur, code falls back to constants
|
||
- AIModelConfig table can be dropped without data loss
|
||
- No impact on existing credit calculations
|
||
|
||
### **For Zero-Downtime Deployment:**
|
||
|
||
**Step 1:** Deploy migration only
|
||
```bash
|
||
python manage.py migrate billing
|
||
# Creates AIModelConfig table, seeds data
|
||
# Code still uses constants - no breaking changes
|
||
```
|
||
|
||
**Step 2:** Deploy code that reads from both
|
||
```python
|
||
def get_model_pricing(model_name):
|
||
try:
|
||
# Try database first
|
||
return AIModelConfig.objects.get(model_name=model_name)
|
||
except AIModelConfig.DoesNotExist:
|
||
# Fallback to constants
|
||
return MODEL_RATES.get(model_name)
|
||
```
|
||
|
||
**Step 3:** Monitor and verify
|
||
- Check logs for database queries
|
||
- Verify cost calculations match
|
||
- Compare with constant-based calculations
|
||
|
||
**Step 4:** Remove constant fallbacks
|
||
- After verification period (1-2 weeks)
|
||
- All code now uses database only
|
||
|
||
---
|
||
|
||
## PHASE 15: FRONTEND UPDATES
|
||
|
||
### **File:** `frontend/src/pages/Settings/AI.tsx`
|
||
|
||
**Current:** Hardcoded model dropdown
|
||
|
||
**New Implementation:**
|
||
|
||
**Add API call:**
|
||
```typescript
|
||
const { data: models } = useQuery('/api/v1/ai/models/?type=text&active=true')
|
||
```
|
||
|
||
**Update dropdown:**
|
||
```tsx
|
||
<Select>
|
||
{models.map(model => (
|
||
<option value={model.model_name}>
|
||
{model.display_name}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
```
|
||
|
||
**Show model details:**
|
||
- Context window
|
||
- Max output tokens
|
||
- JSON mode support
|
||
- Pricing (input/output costs)
|
||
|
||
### **File:** `frontend/src/pages/Settings/Integration.tsx`
|
||
|
||
**Current:** Shows current model from GlobalIntegrationSettings
|
||
|
||
**New Implementation:**
|
||
|
||
**Display model information:**
|
||
```tsx
|
||
<ModelCard>
|
||
<h3>{model.display_name}</h3>
|
||
<p>Provider: {model.provider}</p>
|
||
<p>Context: {model.context_window.toLocaleString()} tokens</p>
|
||
<p>Pricing: ${model.input_cost_per_1m}/${model.output_cost_per_1m} per 1M</p>
|
||
{model.supports_json_mode && <Badge>JSON Mode</Badge>}
|
||
{model.supports_vision && <Badge>Vision</Badge>}
|
||
</ModelCard>
|
||
```
|
||
|
||
**Add model comparison:**
|
||
- Show all available models in table
|
||
- Compare pricing side-by-side
|
||
- Help users choose best model for their needs
|
||
|
||
---
|
||
|
||
## BENEFITS OF THIS IMPLEMENTATION
|
||
|
||
### **Operational Benefits**
|
||
1. ✅ **No Code Deploys for Pricing Updates** - Update costs in Django Admin
|
||
2. ✅ **Multi-Provider Ready** - Easy to add Anthropic, Google, etc.
|
||
3. ✅ **Model Testing** - Enable/disable models without code changes
|
||
4. ✅ **Granular Control** - Different models for different accounts/plans
|
||
|
||
### **Technical Benefits**
|
||
5. ✅ **Backward Compatible** - Existing code works during migration
|
||
6. ✅ **Zero Downtime** - Gradual migration strategy
|
||
7. ✅ **Fully Tested** - Comprehensive test coverage
|
||
8. ✅ **Audit Trail** - Track all pricing changes with timestamps
|
||
|
||
### **Business Benefits**
|
||
9. ✅ **Dynamic Pricing** - React quickly to OpenAI price changes
|
||
10. ✅ **Cost Forecasting** - Accurate model cost data for projections
|
||
11. ✅ **Model Analytics** - Track usage and costs per model
|
||
12. ✅ **A/B Testing** - Easy to test new models with subset of users
|
||
|
||
### **User Benefits**
|
||
13. ✅ **Model Selection** - Users can choose model based on their needs
|
||
14. ✅ **Transparent Pricing** - See exact costs before using models
|
||
15. ✅ **Better Control** - Enterprise accounts can restrict models
|
||
16. ✅ **Latest Models** - Access new models as soon as they're added
|
||
|
||
---
|
||
|
||
## IMPLEMENTATION TIMELINE
|
||
|
||
### **Week 1: Foundation**
|
||
- Day 1-2: Create AIModelConfig model and migration
|
||
- Day 3: Create Django Admin interface
|
||
- Day 4-5: Seed data and test in development
|
||
|
||
### **Week 2: Backend Integration**
|
||
- Day 1-2: Update ai_core.py to query database
|
||
- Day 3: Update validators and settings
|
||
- Day 4-5: Create API endpoint and serializers
|
||
|
||
### **Week 3: Testing & Migration**
|
||
- Day 1-2: Write comprehensive tests
|
||
- Day 3: Test migration on staging
|
||
- Day 4-5: Deploy to production with monitoring
|
||
|
||
### **Week 4: Frontend & Cleanup**
|
||
- Day 1-2: Update frontend to use new API
|
||
- Day 3: Add deprecation warnings to constants
|
||
- Day 4-5: Documentation and training
|
||
|
||
---
|
||
|
||
## FILES AFFECTED SUMMARY
|
||
|
||
### **New Files** (4)
|
||
1. Migration: `billing/migrations/00XX_create_ai_model_config.py`
|
||
2. Tests: `billing/tests/test_ai_model_config.py`
|
||
3. API Views: `api/ai/views.py`
|
||
4. API Tests: `api/tests/test_ai_model_api.py`
|
||
|
||
### **Modified Files** (12)
|
||
1. `billing/models.py` - Add AIModelConfig model
|
||
2. `billing/admin.py` - Add AIModelConfigAdmin
|
||
3. `ai/ai_core.py` - Replace MODEL_RATES with database queries
|
||
4. `ai/validators.py` - Update model validation
|
||
5. `ai/settings.py` - Update get_model_config()
|
||
6. `ai/constants.py` - Add deprecation warnings
|
||
7. `system/global_settings_models.py` - Dynamic model choices
|
||
8. `system/models.py` - Validate model overrides
|
||
9. `reports/views.py` - Use AIModelConfig for display
|
||
10. Frontend: `Settings/AI.tsx`
|
||
11. Frontend: `Settings/Integration.tsx`
|
||
12. URLs: Register new API endpoint
|
||
|
||
### **Total Changes**
|
||
- 1 new database model
|
||
- 1 new admin interface
|
||
- 1 new API endpoint
|
||
- 4 new test files
|
||
- 12 files updated
|
||
- ~500-800 lines of code
|
||
- All existing data preserved
|
||
- Zero downtime migration
|
||
|
||
---
|
||
|
||
## ROLLBACK PLAN
|
||
|
||
**If Issues Occur:**
|
||
|
||
1. **Database:** Keep AIModelConfig table (no harm)
|
||
2. **Code:** Revert to using constants
|
||
3. **Data:** No CreditUsageLog changes, all historical data intact
|
||
4. **Time:** Can rollback in < 5 minutes
|
||
|
||
**Indicators for Rollback:**
|
||
- Model queries timing out
|
||
- Incorrect cost calculations
|
||
- Missing models causing errors
|
||
- Performance degradation
|
||
|
||
**Prevention:**
|
||
- Thorough testing on staging first
|
||
- Monitor logs and metrics closely
|
||
- Keep constants for 2-4 weeks as backup
|
||
- Gradual rollout to production
|
||
|
||
---
|
||
|
||
## SUCCESS METRICS
|
||
|
||
### **Technical Metrics**
|
||
- ✅ All tests passing (100% coverage for new code)
|
||
- ✅ Database query time < 10ms
|
||
- ✅ API response time < 100ms
|
||
- ✅ Zero downtime during deployment
|
||
|
||
### **Operational Metrics**
|
||
- ✅ Admin can add new model in < 2 minutes
|
||
- ✅ Pricing update takes < 1 minute
|
||
- ✅ Model enable/disable is instant
|
||
- ✅ No code deploys needed for model changes
|
||
|
||
### **Business Metrics**
|
||
- ✅ Cost tracking accuracy: 100%
|
||
- ✅ Model usage data: Available in real-time
|
||
- ✅ Time to market for new models: < 1 day (vs 1 week)
|
||
- ✅ Pricing error rate: 0%
|
||
|
||
---
|
||
|
||
**END OF PLAN**
|