491 lines
22 KiB
Markdown
491 lines
22 KiB
Markdown
|
|
## Complete End-to-End Analysis & Restructuring Plan
|
|
|
|
### Current State Summary (from actual database queries)
|
|
|
|
**AIModelConfig (6 records - all needed):**
|
|
| model_name | type | provider | active | default | cost | tokens_per_credit |
|
|
|------------|------|----------|--------|---------|------|-------------------|
|
|
| gpt-4o-mini | text | openai | ✅ | ❌ | $0.15/$0.60 per 1M | 10,000 |
|
|
| gpt-4o | text | openai | ❌ | ❌ | $2.50/$10.00 per 1M | 1,000 |
|
|
| gpt-5.1 | text | openai | ✅ | ✅ | $1.25/$10.00 per 1M | 1,000 |
|
|
| runware:97@1 | image | runware | ✅ | ❌ | $0.012/image | 1 credit/image |
|
|
| dall-e-3 | image | openai | ✅ | ✅ | $0.04/image | 5 credits/image |
|
|
| google:4@2 | image | runware | ✅ | ❌ | $0.14/image | 15 credits/image |
|
|
|
|
---
|
|
|
|
### Current End-to-End Flow (Traced from Code)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ CURRENT ARCHITECTURE │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
1. CONFIGURATION LAYER
|
|
┌────────────────────────────────────────┐
|
|
│ GlobalIntegrationSettings (singleton) │ ← API keys stored here
|
|
│ - openai_api_key │
|
|
│ - runware_api_key │
|
|
│ - anthropic_api_key (unused) │
|
|
│ - bria_api_key (unused) │
|
|
│ - openai_model: gpt-4o-mini ❌ │ ← Should be gpt-5.1
|
|
│ - runware_model: bria:10@1 ❌ │ ← Model doesn't exist!
|
|
│ - HARDCODED CHOICES duplicating DB │
|
|
└────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌────────────────────────────────────────┐
|
|
│ AIModelConfig (database) │ ← Source of truth for models
|
|
│ - model_name, provider, costs │
|
|
│ - is_active, is_default │
|
|
└────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌────────────────────────────────────────┐
|
|
│ constants.py │ ← DUPLICATE/LEGACY
|
|
│ - MODEL_RATES (hardcoded) │
|
|
│ - IMAGE_MODEL_RATES (hardcoded) │
|
|
└────────────────────────────────────────┘
|
|
|
|
2. SETTINGS RESOLUTION LAYER
|
|
┌────────────────────────────────────────┐
|
|
│ settings.py │
|
|
│ get_model_config(function, account) │
|
|
│ - Gets model from GlobalIntegration │
|
|
│ - Gets max_tokens from AIModelConfig │
|
|
│ - Allows IntegrationSettings override │
|
|
└────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌────────────────────────────────────────┐
|
|
│ model_registry.py │
|
|
│ ModelRegistry.get_model(model_id) │
|
|
│ - Try DB (AIModelConfig) first │
|
|
│ - Fallback to constants.py ❌ │ ← Should only use DB
|
|
└────────────────────────────────────────┘
|
|
|
|
3. AI EXECUTION LAYER
|
|
┌────────────────────────────────────────┐
|
|
│ engine.py (AIEngine) │
|
|
│ - Orchestrates all AI functions │
|
|
│ - Progress tracking, cost tracking │
|
|
└────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌────────────────────────────────────────┐
|
|
│ ai_core.py (AICore) │
|
|
│ - _load_account_settings() │ ← Gets API keys from Global
|
|
│ - run_ai_request() for text │
|
|
│ - generate_image() for images │
|
|
│ - Uses IMAGE_MODEL_RATES fallback ❌ │
|
|
└────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌────────────────────────────────────────┐
|
|
│ ai/functions/ │
|
|
│ - generate_images.py │
|
|
│ - generate_content.py │
|
|
│ - auto_cluster.py │
|
|
│ - Each function uses AICore │
|
|
└────────────────────────────────────────┘
|
|
|
|
4. CREDIT CALCULATION LAYER
|
|
┌────────────────────────────────────────┐
|
|
│ CreditCostConfig (database) │
|
|
│ - operation_type │
|
|
│ - tokens_per_credit │
|
|
│ - min_credits │
|
|
│ - price_per_credit_usd │
|
|
│ - For images: 50 tokens/credit, min 5 │
|
|
└────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌────────────────────────────────────────┐
|
|
│ CreditService │
|
|
│ - calculate_credits_from_tokens() │
|
|
│ - deduct_credits_for_operation() │
|
|
│ - Text: tokens → credits AFTER call │
|
|
│ - Images: ??? (not token-based) │
|
|
└────────────────────────────────────────┘
|
|
|
|
5. FRONTEND (Sites/Settings.tsx)
|
|
┌────────────────────────────────────────┐
|
|
│ HARDCODED model choices ❌ │
|
|
│ - QUALITY_TO_CONFIG │
|
|
│ - RUNWARE_MODEL_CHOICES │
|
|
│ - DALLE_MODEL_CHOICES │
|
|
│ - MODEL_LANDSCAPE_SIZES │
|
|
└────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
### Problems Identified
|
|
|
|
| # | Problem | Location | Impact |
|
|
|---|---------|----------|--------|
|
|
| 1 | GlobalIntegrationSettings has hardcoded model choices | global_settings_models.py | Duplicates AIModelConfig |
|
|
| 2 | runware_model = "bria:10@1" but model doesn't exist | GlobalIntegrationSettings | Broken fallback |
|
|
| 3 | API keys mixed with model config | GlobalIntegrationSettings | No separation of concerns |
|
|
| 4 | IMAGE_MODEL_RATES still used as fallback | ai_core.py, model_registry.py | Inconsistent pricing |
|
|
| 5 | Frontend hardcodes model choices | Settings.tsx | Not dynamic |
|
|
| 6 | Image credit calculation unclear | CreditService | Not based on cost_per_image |
|
|
| 7 | constants.py duplicates DB data | constants.py | Maintenance burden |
|
|
|
|
---
|
|
|
|
### Target Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ TARGET ARCHITECTURE │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
1. NEW: IntegrationProvider Model (stores ALL 3rd party API keys)
|
|
┌────────────────────────────────────────┐
|
|
│ IntegrationProvider │ ← Future-proof: ALL integrations
|
|
│ - provider_id: str (primary key) │
|
|
│ Examples: openai, runware, google, │
|
|
│ resend, stripe, etc. │
|
|
│ - display_name: str │
|
|
│ - provider_type: ai | email | payment │
|
|
│ - api_key: encrypted str │
|
|
│ - api_endpoint: URL (optional) │
|
|
│ - is_active: bool │
|
|
│ - config: JSON (rate limits, etc.) │
|
|
└────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
2. CLEANED: AIModelConfig (references IntegrationProvider)
|
|
┌────────────────────────────────────────┐
|
|
│ AIModelConfig │
|
|
│ - model_name: str │
|
|
│ - display_name: str │
|
|
│ - model_type: text | image │
|
|
│ - provider: str → IntegrationProvider │
|
|
│ - cost fields (unchanged) │
|
|
│ + credits_per_image: int (NEW) │ ← For image models
|
|
│ + tokens_per_credit: int (NEW) │ ← For text models
|
|
│ + quality_tier: basic|quality|premium │ ← For UI display
|
|
│ - is_default: bool │ ← Loads automatically
|
|
└────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
3. SIMPLIFIED: GlobalIntegrationSettings
|
|
┌────────────────────────────────────────┐
|
|
│ GlobalIntegrationSettings │
|
|
│ NO hardcoded model names │
|
|
│ NO API keys (in IntegrationProvider) │
|
|
│ Loads defaults from AIModelConfig │
|
|
│ where is_default=True │
|
|
│ - image_style: str │
|
|
│ - max_in_article_images: int │
|
|
│ - image_quality: str │
|
|
└────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
4. UNIFIED: Model Resolution
|
|
┌────────────────────────────────────────┐
|
|
│ ModelRegistry │
|
|
│ - get_default_model(type) → from DB │
|
|
│ - get_model(model_id) → AIModelConfig │
|
|
│ - get_provider(id) → IntegrationProv │
|
|
│ - get_api_key(provider) → key │
|
|
│ - NO fallback to constants │
|
|
│ - NO hardcoded defaults │
|
|
└────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
5. DYNAMIC: Frontend API
|
|
┌────────────────────────────────────────┐
|
|
│ GET /api/v1/system/ai-models/ │
|
|
│ Returns models from DB with defaults │
|
|
│ marked, no hardcoding needed │
|
|
└────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
### Implementation Plan (Complete)
|
|
|
|
#### Phase 1: Database Schema Changes
|
|
|
|
**1.1 Create IntegrationProvider Model (Future-proof for ALL integrations)**
|
|
|
|
File: models.py
|
|
|
|
```python
|
|
class IntegrationProvider(models.Model):
|
|
"""
|
|
Centralized 3rd party integration provider configuration.
|
|
Single location for ALL external service API keys and configs.
|
|
"""
|
|
PROVIDER_TYPE_CHOICES = [
|
|
('ai', 'AI Provider'),
|
|
('email', 'Email Service'),
|
|
('payment', 'Payment Gateway'),
|
|
('storage', 'Storage Service'),
|
|
('other', 'Other'),
|
|
]
|
|
|
|
provider_id = models.CharField(max_length=50, unique=True, primary_key=True)
|
|
# Examples: openai, runware, google, resend, stripe, aws_s3, etc.
|
|
|
|
display_name = models.CharField(max_length=100)
|
|
provider_type = models.CharField(max_length=20, choices=PROVIDER_TYPE_CHOICES, default='ai')
|
|
api_key = models.CharField(max_length=500, blank=True) # Should be encrypted
|
|
api_secret = models.CharField(max_length=500, blank=True) # For services needing secret
|
|
api_endpoint = models.URLField(blank=True) # Custom endpoint if needed
|
|
webhook_secret = models.CharField(max_length=500, blank=True) # For Stripe etc.
|
|
is_active = models.BooleanField(default=True)
|
|
config = models.JSONField(default=dict, blank=True) # Rate limits, regions, etc.
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
db_table = 'igny8_integration_providers'
|
|
verbose_name = 'Integration Provider'
|
|
verbose_name_plural = 'Integration Providers'
|
|
```
|
|
|
|
**1.2 Add fields to AIModelConfig**
|
|
|
|
```python
|
|
# Add to AIModelConfig for IMAGE models
|
|
credits_per_image = models.IntegerField(
|
|
null=True, blank=True,
|
|
help_text="Fixed credits per image generated. For image models only."
|
|
)
|
|
|
|
# Add to AIModelConfig for TEXT models
|
|
tokens_per_credit = models.IntegerField(
|
|
null=True, blank=True,
|
|
help_text="Number of tokens that equal 1 credit. For text models only."
|
|
)
|
|
|
|
# Add quality tier for UI display (image models)
|
|
quality_tier = models.CharField(
|
|
max_length=20,
|
|
choices=[('basic', 'Basic'), ('quality', 'Quality'), ('premium', 'Premium')],
|
|
null=True, blank=True,
|
|
help_text="Quality tier for frontend UI display"
|
|
)
|
|
```
|
|
|
|
**1.3 Migration to populate data**
|
|
|
|
Create IntegrationProvider records:
|
|
```
|
|
| provider_id | display_name | provider_type | Notes |
|
|
|-------------|-------------------|---------------|--------------------------|
|
|
| openai | OpenAI | ai | GPT models, DALL-E |
|
|
| runware | Runware | ai | Image generation |
|
|
| google | Google Cloud | ai | Future: Gemini, etc. |
|
|
| resend | Resend | email | Transactional email |
|
|
| stripe | Stripe | payment | Payment processing |
|
|
```
|
|
|
|
Update AIModelConfig with credit/token values:
|
|
```
|
|
| model_name | type | tokens_per_credit | credits_per_image | quality_tier |
|
|
|---------------|-------|-------------------|-------------------|--------------|
|
|
| gpt-4o-mini | text | 10000 | - | - |
|
|
| gpt-4o | text | 1000 | - | - |
|
|
| gpt-5.1 | text | 1000 | - | - |
|
|
| runware:97@1 | image | - | 1 | basic |
|
|
| dall-e-3 | image | - | 5 | quality |
|
|
| google:4@2 | image | - | 15 | premium |
|
|
```
|
|
|
|
#### Phase 2: Backend Code Changes
|
|
|
|
**2.1 Remove hardcoded constants**
|
|
|
|
File: constants.py
|
|
- Remove: MODEL_RATES, IMAGE_MODEL_RATES
|
|
- Keep: JSON_MODE_MODELS (or move to AIModelConfig.supports_json_mode check)
|
|
|
|
**2.2 Update ModelRegistry**
|
|
|
|
File: model_registry.py
|
|
- Remove fallback to constants.py
|
|
- Add: `get_default_model(model_type) → AIModelConfig where is_default=True`
|
|
- Add: `get_provider(provider_id) → IntegrationProvider`
|
|
- Add: `get_api_key(provider_id) → str`
|
|
- **NO hardcoded model names** - always query DB
|
|
|
|
**2.3 Update AICore**
|
|
|
|
File: ai_core.py
|
|
- Change `_load_account_settings()` to use IntegrationProvider for API keys
|
|
- Remove IMAGE_MODEL_RATES import and usage
|
|
- Use `ModelRegistry.get_default_model('text')` instead of hardcoded model
|
|
- Use `ModelRegistry.calculate_cost()` exclusively
|
|
|
|
**2.4 Update CreditService**
|
|
|
|
File: credit_service.py
|
|
|
|
For IMAGE models:
|
|
```python
|
|
def calculate_credits_for_image(model_name: str, num_images: int) -> int:
|
|
"""Calculate credits for image generation from AIModelConfig"""
|
|
model = AIModelConfig.objects.get(model_name=model_name, is_active=True)
|
|
return model.credits_per_image * num_images
|
|
```
|
|
|
|
For TEXT models:
|
|
```python
|
|
def calculate_credits_from_tokens(model_name: str, total_tokens: int) -> int:
|
|
"""Calculate credits from token usage based on model's tokens_per_credit"""
|
|
model = AIModelConfig.objects.get(model_name=model_name, is_active=True)
|
|
tokens_per_credit = model.tokens_per_credit or 1000 # fallback
|
|
return math.ceil(total_tokens / tokens_per_credit)
|
|
```
|
|
|
|
**2.5 Simplify GlobalIntegrationSettings**
|
|
|
|
File: global_settings_models.py
|
|
- Remove: All API key fields (moved to IntegrationProvider)
|
|
- Remove: All hardcoded CHOICES
|
|
- Remove: Model name fields (defaults loaded from AIModelConfig.is_default)
|
|
- Keep: image_style, max_in_article_images, image_quality
|
|
- Add: Helper methods to get defaults from AIModelConfig
|
|
|
|
#### Phase 3: API Endpoints
|
|
|
|
**3.1 New endpoint: GET /api/v1/system/ai-models/**
|
|
|
|
Returns all active models from database with defaults marked (no hardcoding):
|
|
```json
|
|
{
|
|
"text_models": [
|
|
{
|
|
"model_name": "gpt-5.1",
|
|
"display_name": "GPT-5.1 Premium",
|
|
"is_default": true,
|
|
"tokens_per_credit": 1000,
|
|
"max_output_tokens": 8192
|
|
},
|
|
{
|
|
"model_name": "gpt-4o-mini",
|
|
"display_name": "GPT-4o Mini",
|
|
"is_default": false,
|
|
"tokens_per_credit": 10000,
|
|
"max_output_tokens": 16000
|
|
}
|
|
],
|
|
"image_models": [
|
|
{
|
|
"model_name": "runware:97@1",
|
|
"display_name": "Basic",
|
|
"quality_tier": "basic",
|
|
"is_default": false,
|
|
"credits_per_image": 1,
|
|
"valid_sizes": ["1024x1024", "1280x768"]
|
|
},
|
|
{
|
|
"model_name": "dall-e-3",
|
|
"display_name": "Quality",
|
|
"quality_tier": "quality",
|
|
"is_default": true,
|
|
"credits_per_image": 5,
|
|
"valid_sizes": ["1024x1024", "1792x1024"]
|
|
},
|
|
{
|
|
"model_name": "google:4@2",
|
|
"display_name": "Premium",
|
|
"quality_tier": "premium",
|
|
"is_default": false,
|
|
"credits_per_image": 15,
|
|
"valid_sizes": ["1024x1024", "1376x768"]
|
|
}
|
|
],
|
|
"image_settings": {
|
|
"style": "photorealistic",
|
|
"max_in_article_images": 4,
|
|
"quality": "hd"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Phase 4: Frontend Changes
|
|
|
|
**4.1 Settings.tsx**
|
|
|
|
- Remove: QUALITY_TO_CONFIG, RUNWARE_MODEL_CHOICES, DALLE_MODEL_CHOICES hardcodes
|
|
- Remove: MODEL_LANDSCAPE_SIZES hardcodes
|
|
- Add: Fetch models from `/api/v1/system/ai-models/`
|
|
- Load valid_sizes from API response per model
|
|
- Display to user (no provider/model names visible):
|
|
- **"Basic (1 credit/image)"**
|
|
- **"Quality (5 credits/image)"**
|
|
- **"Premium (15 credits/image)"**
|
|
- Default selection: model where `is_default=true` from API
|
|
|
|
#### Phase 5: Cleanup
|
|
|
|
**5.1 Files to clean/remove**
|
|
- Remove unused fields from GlobalIntegrationSettings: anthropic_*, bria_*, all API key fields, hardcoded model fields
|
|
- Remove deprecated methods from AICore
|
|
- Update all imports removing constants.py usage
|
|
- Remove CreditCostConfig dependency for image operations (use AIModelConfig.credits_per_image directly)
|
|
|
|
---
|
|
|
|
### Credit Calculation Summary
|
|
|
|
**Text Models (token-based):**
|
|
| Model | tokens_per_credit | Example: 5000 tokens |
|
|
|-------|-------------------|----------------------|
|
|
| gpt-5.1 | 1,000 | 5 credits |
|
|
| gpt-4o | 1,000 | 5 credits |
|
|
| gpt-4o-mini | 10,000 | 1 credit |
|
|
|
|
**Image Models (per-image):**
|
|
| Model | credits_per_image | quality_tier | Display |
|
|
|-------|-------------------|--------------|---------|
|
|
| runware:97@1 | 1 | basic | "Basic (1 credit/image)" |
|
|
| dall-e-3 | 5 | quality | "Quality (5 credits/image)" |
|
|
| google:4@2 | 15 | premium | "Premium (15 credits/image)" |
|
|
|
|
---
|
|
|
|
### Migration Order
|
|
|
|
1. Create IntegrationProvider model + migration
|
|
2. Add credits_per_image, tokens_per_credit, quality_tier to AIModelConfig + migration
|
|
3. Data migration: populate IntegrationProvider, update AIModelConfig with credit values
|
|
4. Update ModelRegistry (remove constants fallback, add get_default_model)
|
|
5. Update AICore (use IntegrationProvider for keys)
|
|
6. Update CreditService (model-based credit calculation)
|
|
7. Create API endpoint /api/v1/system/ai-models/
|
|
8. Update frontend (load from API, no hardcodes)
|
|
9. Cleanup GlobalIntegrationSettings (remove API keys, hardcoded choices)
|
|
10. Remove constants.py hardcoded rates
|
|
|
|
---
|
|
|
|
### Files Changed Summary
|
|
|
|
| File | Action |
|
|
|------|--------|
|
|
| models.py | Add IntegrationProvider, update AIModelConfig with credit fields |
|
|
| model_registry.py | Remove constants fallback, add get_default_model(), get_provider() |
|
|
| ai_core.py | Use IntegrationProvider for keys, ModelRegistry for defaults |
|
|
| constants.py | Remove MODEL_RATES, IMAGE_MODEL_RATES |
|
|
| credit_service.py | Model-based credit calculation for both text and images |
|
|
| global_settings_models.py | Remove API keys, hardcoded choices, model fields |
|
|
| backend/igny8_core/api/views/system.py | Add ai-models endpoint |
|
|
| Settings.tsx | Load models from API, remove all hardcodes |
|
|
|
|
---
|
|
|
|
### Key Principles
|
|
|
|
1. **No hardcoded model names** - GlobalIntegrationSettings loads defaults from AIModelConfig where is_default=True
|
|
2. **Single source of truth** - AIModelConfig is THE source for all model info including credit costs
|
|
3. **Future-proof** - IntegrationProvider handles ALL 3rd party integrations (AI, email, payment, etc.)
|
|
4. **Dynamic frontend** - All model choices loaded from API, not hardcoded
|
|
5. **Configurable credits** - Change credits_per_image or tokens_per_credit in admin, no code changes needed |