# Civitai Best Practices

This guide covers optimization strategies, cost management, and production
recommendations for using Civitai in the Oshun platform.

## Cost Optimization

### Understanding Buzz

Civitai uses "Buzz" as its credit system. Understanding cost factors helps
optimize spending.

#### Image Generation Cost Factors

| Factor         | Impact                     | Optimization              |
| -------------- | -------------------------- | ------------------------- |
| Base Model     | SD1=2, SDXL=5, Flux=8 Buzz | Use SD1.5 for prototyping |
| LoRAs/Networks | +0.5-2 Buzz per network    | Minimize network count    |
| Batch Size     | Linear multiplier          | Batch similar requests    |
| Draft Mode     | 40% savings on SDXL        | Use for previews          |

#### Cost Optimization Strategies

```typescript
// 1. Use draft mode for previews
const preview = await provider.generateImageWithPreset(
  model,
  prompt,
  'fast' // 12 steps, lower CFG
);

// 2. Batch similar generations
const jobs = await Promise.all([
  provider.generateImage({ model, params: { prompt: 'A' }, batchSize: 4 }),
  // Instead of 4 separate requests
]);

// 3. Cache generated images
const cacheKey = generateCacheKey(prompt, model, seed);
const cached = await cache.get(cacheKey);
if (!cached) {
  const result = await provider.generateImageAndWait(request);
  await cache.set(cacheKey, result);
}

// 4. Track and budget costs
const budget = 1000; // Buzz
let spent = 0;

provider.on('cost:recorded', ({ cost }) => {
  spent += cost;
  if (spent > budget * 0.8) {
    console.warn('80% of budget used!');
  }
});
```

### Video Generation Cost Management

```typescript
// 1. Start with shorter durations
const testVideo = await provider.generateVideo({
  model: 'wan-2.1',
  params: {
    prompt: 'Test motion',
    duration: 2, // Start short
    quality: 'draft', // 50% cost
  },
});

// 2. Use appropriate models
// ltxv/mochi = 8 Buzz/s, veo3 = 25 Buzz/s
const economicalJob = await provider.generateVideo({
  model: 'ltxv', // Most economical
  params: { prompt: 'Simple animation', duration: 4 },
});

// 3. Validate prompts before generation
function validateVideoPrompt(prompt: string): boolean {
  // Ensure prompt is specific enough
  return prompt.length > 20 && prompt.includes('motion');
}
```

### Training Cost Optimization

```typescript
import {
  estimateTrainingCost,
  calculateTotalSteps,
} from '@oshun/civitai-training';

// 1. Estimate before training
const steps = calculateTotalSteps(images.length, repeats, batchSize, epochs);
const estimatedCost = estimateTrainingCost('sdxl', steps);

if (estimatedCost > budget) {
  // Reduce epochs or images
  epochs = Math.floor(epochs * (budget / estimatedCost));
}

// 2. Use smaller ranks for styles (less training needed)
const styleConfig = {
  network: { rank: 32 }, // Not 128
  epochs: 10, // Not 20
};

// 3. Start with fast-test preset
const testJob = await training.startTrainingWithPreset(
  'fast-test', // 3 epochs, rank 8
  dataset,
  'test-run'
);
// Check results before full training
```

## API Optimization

### Rate Limiting Best Practices

```typescript
// 1. Pre-check rate limit state
const rateLimitState = service.getRateLimitState();
if (rateLimitState.remaining < 5) {
  // Wait or use cached data
  await sleep(rateLimitState.getWaitTimeMs());
}

// 2. Implement request queuing
class RequestQueue {
  private queue: Array<() => Promise<void>> = [];
  private processing = false;

  async add<T>(request: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.queue.push(async () => {
        try {
          const result = await request();
          resolve(result);
        } catch (error) {
          reject(error);
        }
      });
      this.processQueue();
    });
  }

  private async processQueue(): Promise<void> {
    if (this.processing) return;
    this.processing = true;

    while (this.queue.length > 0) {
      const request = this.queue.shift()!;
      await request();
      await sleep(1000); // Minimum delay between requests
    }

    this.processing = false;
  }
}

// 3. Use event-based rate limit handling
service.on('rate_limited', async ({ waitTime }) => {
  console.log(`Rate limited. Waiting ${waitTime}ms...`);
  await sleep(waitTime);
});
```

### Caching Strategies

```typescript
// 1. Model metadata caching
const modelCache = new Map<number, { model: Model; expiresAt: Date }>();

async function getModelCached(modelId: number): Promise<Model> {
  const cached = modelCache.get(modelId);
  if (cached && cached.expiresAt > new Date()) {
    return cached.model;
  }

  const model = await service.getModel(modelId);
  modelCache.set(modelId, {
    model,
    expiresAt: new Date(Date.now() + 3600000), // 1 hour
  });
  return model;
}

// 2. Search result caching
const searchCache = new RequestCache<ModelInfo[]>(100, 300);

async function searchModelsCached(
  query: ModelSearchQuery
): Promise<ModelInfo[]> {
  const key = RequestCache.generateKey('search', query);
  let results = searchCache.get(key);

  if (!results) {
    const response = await service.listModels(query);
    results = response.items;
    searchCache.set(key, results);
  }

  return results;
}

// 3. Hash-to-version lookup caching
const hashCache = new Map<string, number>(); // hash -> versionId
```

### Error Recovery

```typescript
// 1. Implement exponential backoff
async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3,
  baseDelay: number = 1000
): Promise<T> {
  let lastError: Error;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;

      if (!isRetryable(error)) throw error;

      const delay = baseDelay * Math.pow(2, attempt);
      const jitter = Math.random() * delay * 0.1;
      await sleep(delay + jitter);
    }
  }

  throw lastError!;
}

function isRetryable(error: unknown): boolean {
  if (error instanceof CivitaiError) {
    return ['RATE_LIMITED', 'TIMEOUT', 'NETWORK_ERROR'].includes(error.code);
  }
  return false;
}

// 2. Circuit breaker integration
service.on('circuit_open', () => {
  // Switch to fallback provider or queue requests
  useFallbackProvider();
});

// 3. Graceful degradation
async function generateWithFallback(
  request: ImageGenerationRequest
): Promise<JobResult> {
  try {
    return await provider.generateImageAndWait(request);
  } catch (error) {
    if (error instanceof CivitaiError && error.code === 'RATE_LIMITED') {
      // Try with lower priority
      return await fallbackProvider.generateImageAndWait({
        ...request,
        params: { ...request.params, steps: 10 }, // Reduce steps
      });
    }
    throw error;
  }
}
```

## Generation Best Practices

### Prompt Engineering

```typescript
// 1. Structure prompts consistently
interface PromptBuilder {
  subject: string;
  style?: string;
  quality?: string[];
  negative?: string[];
}

function buildPrompt(builder: PromptBuilder): string {
  const parts = [builder.subject];

  if (builder.style) {
    parts.push(builder.style);
  }

  if (builder.quality?.length) {
    parts.push(...builder.quality);
  }

  return parts.join(', ');
}

function buildNegativePrompt(builder: PromptBuilder): string {
  const defaults = ['blurry', 'low quality', 'artifacts', 'distorted'];
  return [...defaults, ...(builder.negative || [])].join(', ');
}

// Usage
const prompt = buildPrompt({
  subject: 'A majestic dragon',
  style: 'digital art',
  quality: ['highly detailed', '8k', 'trending on artstation'],
});

// 2. Model-specific prompts
const modelPromptStyles: Record<string, (base: string) => string> = {
  // SDXL responds well to quality tags
  sdxl: (base) => `${base}, masterpiece, best quality`,

  // Flux prefers natural language
  flux: (base) => `A high quality image of ${base}, photorealistic`,

  // Pony uses specific tags
  pony: (base) => `${base}, score_9, score_8_up`,
};
```

### Seed Management

```typescript
// 1. Track seeds for reproducibility
interface GenerationRecord {
  prompt: string;
  model: string;
  seed: number;
  result: string;
}

const generations: GenerationRecord[] = [];

async function generateTracked(
  request: ImageGenerationRequest
): Promise<JobResult> {
  const seed = request.params.seed ?? Math.floor(Math.random() * 2147483647);

  const job = await provider.generateImageAndWait({
    ...request,
    params: { ...request.params, seed },
  });

  generations.push({
    prompt: request.params.prompt,
    model: request.model,
    seed,
    result: job.blobUrl!,
  });

  return job;
}

// 2. Seed variation for similar results
function generateSeedVariations(baseSeed: number, count: number): number[] {
  return Array.from({ length: count }, (_, i) => baseSeed + i);
}
```

### Quality vs Speed Trade-offs

```typescript
// Quality levels with configurations
const qualityConfigs = {
  preview: {
    steps: 8,
    scheduler: 'LCM' as const,
    cfgScale: 2,
    width: 512,
    height: 512,
  },
  draft: {
    steps: 15,
    scheduler: 'EulerA' as const,
    cfgScale: 5,
    width: 768,
    height: 768,
  },
  standard: {
    steps: 25,
    scheduler: 'DPM2MKarras' as const,
    cfgScale: 7,
    width: 1024,
    height: 1024,
  },
  high: {
    steps: 40,
    scheduler: 'DPM2MKarras' as const,
    cfgScale: 7,
    width: 1024,
    height: 1024,
  },
  ultra: {
    steps: 60,
    scheduler: 'DPMSDEKarras' as const,
    cfgScale: 7,
    width: 1024,
    height: 1024,
  },
};

// Use preview for iterations, standard for final
async function iterativeGeneration(prompt: string): Promise<JobResult> {
  // Quick preview
  const preview = await provider.generateImage({
    model,
    params: { prompt, ...qualityConfigs.preview },
  });

  // If user approves preview, generate high quality
  const final = await provider.generateImage({
    model,
    params: { prompt, ...qualityConfigs.standard, seed: preview.seed },
  });

  return final;
}
```

## Model Selection

### Choosing the Right Base Model

| Use Case                 | Recommended Model | Why               |
| ------------------------ | ----------------- | ----------------- |
| Photorealistic portraits | Juggernaut XL     | Best for faces    |
| Anime/illustration       | Animagine XL      | Trained on anime  |
| General purpose          | RealVisXL         | Versatile         |
| Highest quality          | Flux Dev          | Best overall      |
| Fast iteration           | SD 1.5            | Low cost, fast    |
| Video characters         | Hunyuan           | Good LoRA support |
| Video motion             | Wan 2.1/2.2       | Motion training   |

### LoRA Selection

```typescript
// 1. Check LoRA compatibility
function isCompatible(
  loraBaseModel: string,
  checkpointBaseModel: string
): boolean {
  const compatibility: Record<string, string[]> = {
    'SD 1.5': ['SD 1.5', 'SD 1.4'],
    'SDXL 1.0': ['SDXL 1.0', 'SDXL 0.9'],
    Flux: ['Flux.1 D', 'Flux.1 S'],
  };

  return compatibility[checkpointBaseModel]?.includes(loraBaseModel) ?? false;
}

// 2. Stack LoRAs intelligently
function optimizeLoraStack(loras: AdditionalNetwork[]): AdditionalNetwork[] {
  // Sort by importance
  const sorted = [...loras].sort(
    (a, b) => (b.strength ?? 1) - (a.strength ?? 1)
  );

  // Limit to 3-4 LoRAs for best results
  const limited = sorted.slice(0, 4);

  // Reduce total strength if > 1.5
  const totalStrength = limited.reduce((sum, l) => sum + (l.strength ?? 1), 0);
  if (totalStrength > 1.5) {
    const scale = 1.5 / totalStrength;
    return limited.map((l) => ({ ...l, strength: (l.strength ?? 1) * scale }));
  }

  return limited;
}
```

## Training Best Practices

### Dataset Quality

```typescript
// 1. Pre-process images
async function preprocessDataset(
  images: DatasetImage[]
): Promise<DatasetImage[]> {
  return Promise.all(
    images.map(async (img) => {
      // Ensure consistent resolution
      const processed = await resizeImage(img.image, 1024, 1024);

      // Generate caption if missing
      const caption = img.caption ?? (await generateCaption(processed));

      return { ...img, image: processed, caption };
    })
  );
}

// 2. Validate dataset diversity
function analyzeDatasetDiversity(images: DatasetImage[]): {
  issues: string[];
  recommendations: string[];
} {
  const issues: string[] = [];
  const recommendations: string[] = [];

  // Check image count
  if (images.length < 10) {
    issues.push('Dataset too small (< 10 images)');
    recommendations.push('Add more images for better results');
  }

  // Check caption diversity
  const uniqueCaptions = new Set(images.map((i) => i.caption)).size;
  if (uniqueCaptions < images.length * 0.5) {
    issues.push('Low caption diversity');
    recommendations.push('Use more varied captions');
  }

  // Check for repeats balance
  const avgRepeats =
    images.reduce((sum, i) => sum + (i.repeats ?? 1), 0) / images.length;
  if (avgRepeats < 3) {
    recommendations.push('Consider increasing repeats for small datasets');
  }

  return { issues, recommendations };
}
```

### Training Monitoring

```typescript
// 1. Loss monitoring with early stopping
class LossMonitor {
  private history: number[] = [];
  private bestLoss = Infinity;
  private patienceCounter = 0;

  constructor(private patience: number = 5) {}

  update(loss: number): { shouldStop: boolean; reason?: string } {
    this.history.push(loss);

    if (loss < this.bestLoss) {
      this.bestLoss = loss;
      this.patienceCounter = 0;
    } else {
      this.patienceCounter++;
    }

    // Check for overfitting
    if (this.patienceCounter >= this.patience) {
      return { shouldStop: true, reason: 'Loss not improving (overfitting)' };
    }

    // Check for explosion
    if (this.history.length > 10) {
      const recent = this.history.slice(-10);
      const avgRecent = recent.reduce((a, b) => a + b, 0) / recent.length;
      const avgPrevious =
        this.history.slice(-20, -10).reduce((a, b) => a + b, 0) / 10;

      if (avgRecent > avgPrevious * 1.5) {
        return { shouldStop: true, reason: 'Loss exploding' };
      }
    }

    return { shouldStop: false };
  }
}

// 2. Progress reporting
training.on('training:progress', ({ jobId, progress }) => {
  const eta = progress.estimatedTimeRemaining
    ? `${Math.round(progress.estimatedTimeRemaining / 60)}min remaining`
    : 'calculating...';

  console.log(
    [
      `Job: ${jobId}`,
      `Epoch: ${progress.currentEpoch}/${progress.totalEpochs}`,
      `Step: ${progress.currentStep}/${progress.totalSteps}`,
      `Loss: ${progress.loss.currentLoss.toFixed(4)}`,
      `Best: ${progress.loss.bestLoss.toFixed(4)}`,
      `ETA: ${eta}`,
    ].join(' | ')
  );
});
```

## Production Deployment

### Health Monitoring

```typescript
interface HealthStatus {
  healthy: boolean;
  checks: {
    api: { status: 'ok' | 'degraded' | 'down'; latency?: number };
    rateLimit: { status: 'ok' | 'warning' | 'critical'; remaining: number };
    circuitBreaker: { status: 'ok' | 'open'; state: string };
    cache: { hitRate: number };
  };
}

async function checkHealth(): Promise<HealthStatus> {
  const rateLimitState = service.getRateLimitState();
  const circuitState = service.getCircuitBreakerState();
  const cacheStats = service.getCacheStats();

  const checks = {
    api: {
      status:
        circuitState.state === 'open' ? ('down' as const) : ('ok' as const),
    },
    rateLimit: {
      status:
        rateLimitState.remaining < 10
          ? ('critical' as const)
          : rateLimitState.remaining < 30
            ? ('warning' as const)
            : ('ok' as const),
      remaining: rateLimitState.remaining,
    },
    circuitBreaker: {
      status:
        circuitState.state === 'open' ? ('open' as const) : ('ok' as const),
      state: circuitState.state,
    },
    cache: {
      hitRate: cacheStats.hits / (cacheStats.hits + cacheStats.misses) || 0,
    },
  };

  return {
    healthy:
      checks.api.status === 'ok' && checks.circuitBreaker.status === 'ok',
    checks,
  };
}
```

### Metrics Collection

```typescript
// 1. Request metrics
const metrics = {
  requestsTotal: 0,
  requestsSuccessful: 0,
  requestsFailed: 0,
  latencyHistogram: new Array(10).fill(0), // 0-100ms, 100-200ms, etc.
  buzzSpent: 0,
};

service.on('request_complete', ({ duration }) => {
  metrics.requestsTotal++;
  metrics.requestsSuccessful++;

  const bucket = Math.min(Math.floor(duration / 100), 9);
  metrics.latencyHistogram[bucket]++;
});

service.on('request_error', () => {
  metrics.requestsTotal++;
  metrics.requestsFailed++;
});

provider.on('cost:recorded', ({ cost }) => {
  metrics.buzzSpent += cost;
});

// 2. Prometheus-style metrics export
function exportMetrics(): string {
  return [
    `civitai_requests_total ${metrics.requestsTotal}`,
    `civitai_requests_successful ${metrics.requestsSuccessful}`,
    `civitai_requests_failed ${metrics.requestsFailed}`,
    `civitai_buzz_spent_total ${metrics.buzzSpent}`,
    `civitai_cache_hit_rate ${service.getCacheStats().hits / (service.getCacheStats().hits + service.getCacheStats().misses)}`,
  ].join('\n');
}
```

### Alerting

```typescript
// 1. Cost alerting
const dailyBudget = 5000; // Buzz
let dailySpent = 0;

provider.on('cost:recorded', ({ cost }) => {
  dailySpent += cost;

  if (
    dailySpent > dailyBudget * 0.8 &&
    dailySpent - cost <= dailyBudget * 0.8
  ) {
    sendAlert('warning', '80% of daily Buzz budget used');
  }

  if (dailySpent > dailyBudget) {
    sendAlert('critical', 'Daily Buzz budget exceeded');
  }
});

// 2. Error rate alerting
let recentErrors = 0;
let recentRequests = 0;

service.on('request_complete', () => {
  recentRequests++;
});
service.on('request_error', () => {
  recentRequests++;
  recentErrors++;
});

setInterval(() => {
  if (recentRequests > 10) {
    const errorRate = recentErrors / recentRequests;
    if (errorRate > 0.1) {
      sendAlert('warning', `High error rate: ${(errorRate * 100).toFixed(1)}%`);
    }
  }
  recentErrors = 0;
  recentRequests = 0;
}, 60000);

// 3. Circuit breaker alerting
service.on('circuit_open', ({ endpoint }) => {
  sendAlert('critical', `Circuit breaker opened for ${endpoint}`);
});
```

## Security Considerations

### API Key Management

```typescript
// 1. Never hardcode API keys
const apiKey = process.env.CIVITAI_API_KEY;
if (!apiKey) {
  throw new Error('CIVITAI_API_KEY environment variable required');
}

// 2. Rotate keys periodically
// Store key metadata
interface KeyMetadata {
  createdAt: Date;
  lastUsed: Date;
  rotatedAt?: Date;
}

// 3. Audit key usage
service.on('request_start', ({ endpoint }) => {
  auditLog.log({
    action: 'civitai_api_call',
    endpoint,
    timestamp: new Date(),
    keyId: process.env.CIVITAI_KEY_ID,
  });
});
```

### Content Safety

```typescript
// 1. Filter content by rating
const service = createCivitAIService({
  apiKey,
  nsfwEnabled: false, // Disable NSFW content
  contentLevel: 'PG13', // Maximum content level
});

// 2. Validate model content ratings
async function validateModelSafety(modelId: number): Promise<boolean> {
  const model = await service.getModel(modelId);

  if (model.nsfw) {
    console.warn(`Model ${modelId} is NSFW`);
    return false;
  }

  return service.isContentLevelAllowed(model.nsfwLevel);
}

// 3. Content moderation for generated images
async function moderateGeneration(result: JobResult): Promise<boolean> {
  // Implement content moderation
  const isSafe = await contentModerationService.check(result.blobUrl);
  return isSafe;
}
```

## Troubleshooting

### Common Issues

| Issue                   | Cause             | Solution                   |
| ----------------------- | ----------------- | -------------------------- |
| Rate limited frequently | Too many requests | Implement request queuing  |
| High latency            | Large batch sizes | Reduce batch size          |
| Generation failures     | Model unavailable | Use fallback models        |
| Training takes too long | Too many epochs   | Reduce epochs, use presets |
| LoRA has no effect      | Low strength      | Increase LoRA strength     |
| Images look identical   | Seed reuse        | Use random seeds           |

### Debugging

```typescript
// 1. Enable verbose logging
service.on('request_start', (e) => console.log('Request:', e));
service.on('request_complete', (e) => console.log('Complete:', e));
service.on('request_error', (e) => console.error('Error:', e));

// 2. Dump state for debugging
function debugDump(): void {
  console.log('=== Civitai Debug Dump ===');
  console.log('Rate Limit:', service.getRateLimitState());
  console.log('Circuit:', service.getCircuitBreakerState());
  console.log('Cache:', service.getCacheStats());
  console.log('Analytics:', service.getAnalytics());
}

// 3. Test connectivity
async function testConnection(): Promise<boolean> {
  try {
    await service.listTags({ limit: 1 });
    return true;
  } catch (error) {
    console.error('Connection test failed:', error);
    return false;
  }
}
```

## Related Documentation

- [API Documentation](./api.md) - Generation API reference
- [Model Management](./model-management.md) - Model discovery and downloads
- [Training Guide](./training.md) - Training custom LoRAs
