michangarrito/docs/97-adr/ADR-0003-llm-agnostic-strategy.md
rckrdmrd 97f407c661 [MIGRATION-V2] feat: Migrar michangarrito a estructura v2
- Prefijo v2: MCH
- TRACEABILITY-MASTER.yml creado
- Listo para integracion como submodulo

Workspace: v2.0.0 | SIMCO: v4.0.0
2026-01-10 11:28:54 -06:00

254 lines
7.2 KiB
Markdown

---
id: ADR-0003
type: ADR
title: "Estrategia LLM Agnostica"
status: Accepted
decision_date: 2026-01-04
updated_at: 2026-01-10
simco_version: "3.8.0"
stakeholders:
- "Equipo MiChangarrito"
tags:
- llm
- openrouter
- ia
- integracion
- arquitectura
- multi-provider
---
# ADR-0003: Estrategia LLM Agnostica
## Metadata
| Campo | Valor |
|-------|-------|
| **ID** | ADR-0003 |
| **Estado** | Accepted |
| **Fecha** | 2026-01-06 |
| **Autor** | Backend Team |
| **Supersede** | - |
---
## Contexto
MiChangarrito usa un modelo de lenguaje (LLM) para:
- Interpretar mensajes de usuarios
- Generar respuestas contextualizadas
- Procesar comandos de voz transcritos
- Asistir en consultas de negocio
El mercado de LLMs esta en rapida evolucion:
- Nuevos modelos cada mes
- Precios cambiantes
- Calidad variable por caso de uso
- Disponibilidad y rate limits diferentes
Se necesita decidir como manejar esta dependencia.
---
## Decision
**Adoptamos una estrategia LLM-agnostica usando OpenRouter como gateway con fallback entre modelos.**
El sistema puede usar multiples proveedores de LLM:
- Claude (Anthropic) como default
- GPT-4/3.5 (OpenAI) como fallback
- Mistral como opcion economica
- Capacidad de cambiar sin modificar codigo
---
## Alternativas Consideradas
### Opcion 1: Single provider (OpenAI)
- **Pros:**
- Simplicidad
- SDK bien documentado
- Comunidad grande
- **Cons:**
- Lock-in a un proveedor
- Sin alternativa si hay caida
- Precios pueden subir
### Opcion 2: Multi-provider directo
- **Pros:**
- Control total
- Negociar precios directos
- **Cons:**
- Multiples integraciones
- Multiples cuentas
- Codigo complejo
### Opcion 3: Gateway LLM (OpenRouter) - Elegida
- **Pros:**
- Una API para multiples modelos
- Facil cambiar de modelo
- Fallback automatico
- Pay-per-use
- Nuevos modelos sin cambios
- **Cons:**
- Dependencia de OpenRouter
- Capa adicional (latencia minima)
- Markup sobre precio base
---
## Consecuencias
### Positivas
1. **Flexibilidad:** Cambiar modelo con una variable de entorno
2. **Resiliencia:** Fallback si un modelo falla
3. **Costos optimizados:** Usar modelo mas economico cuando baste
4. **Futuro-proof:** Nuevos modelos disponibles inmediatamente
5. **Multi-tenant:** Cada tenant puede tener su configuracion
### Negativas
1. **Dependencia:** OpenRouter como intermediario
2. **Costo:** Pequeno markup sobre precio base
3. **Complejidad:** Manejar diferencias entre modelos
### Neutrales
1. **Prompts:** Deben funcionar con multiples modelos
2. **Testing:** Probar con diferentes modelos
---
## Implementacion
### Arquitectura
```
┌─────────────────────────────────────────────────────────┐
│ MCP Server │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┐ │
│ │ LLM Service │ │
│ │ │ │
│ │ - model │──────┐ │
│ │ - fallback │ │ │
│ │ - maxTokens │ │ │
│ └────────────────┘ │ │
│ v │
│ ┌─────────────────────┐ │
│ │ OpenRouter API │ │
│ │ (Gateway) │ │
│ └──────────┬──────────┘ │
│ │ │
└─────────────────────────┼───────────────────────────────┘
┌───────────────┼───────────────┐
v v v
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Claude │ │ GPT-4 │ │ Mistral │
│(Anthropic)│ │ (OpenAI) │ │ │
└──────────┘ └──────────┘ └──────────┘
```
### Codigo
```typescript
// llm.service.ts
@Injectable()
export class LLMService {
private readonly client: OpenAI;
private readonly defaultModel: string;
private readonly fallbackModel: string;
constructor(config: ConfigService) {
this.client = new OpenAI({
apiKey: config.get('OPENROUTER_API_KEY'),
baseURL: 'https://openrouter.ai/api/v1',
});
this.defaultModel = config.get('LLM_MODEL_DEFAULT');
this.fallbackModel = config.get('LLM_MODEL_FALLBACK');
}
async generate(prompt: string): Promise<string> {
try {
return await this.callModel(this.defaultModel, prompt);
} catch (error) {
if (this.shouldFallback(error)) {
return await this.callModel(this.fallbackModel, prompt);
}
throw error;
}
}
private shouldFallback(error: Error): boolean {
return error.message.includes('rate_limit') ||
error.message.includes('overloaded');
}
}
```
### Configuracion por Tenant
```typescript
// Cada tenant puede tener su configuracion
interface TenantLLMConfig {
tenantId: string;
provider: 'openrouter' | 'openai' | 'anthropic';
apiKey?: string; // Si quiere usar su propia key
model: string;
maxTokens: number;
}
```
---
## Validacion
### Criterios de Exito
| Criterio | Medicion |
|----------|----------|
| Cambio de modelo < 5 min | Tiempo de despliegue |
| Fallback funciona | Tests automatizados |
| Latencia < 3s | Metricas |
| Costo por consulta | Dashboard OpenRouter |
### Tests
```typescript
describe('LLM Service', () => {
it('should fallback on rate limit', async () => {
// Mock rate limit error
mockOpenRouter.mockRejectedValueOnce(new RateLimitError());
const response = await llmService.generate('Hola');
expect(response).toBeDefined();
expect(mockOpenRouter).toHaveBeenCalledTimes(2);
});
it('should work with different models', async () => {
const models = ['claude-3-haiku', 'gpt-3.5-turbo', 'mistral-7b'];
for (const model of models) {
const response = await llmService.generate('Test', { model });
expect(response).toBeDefined();
}
});
});
```
---
## Referencias
- [OpenRouter Documentation](https://openrouter.ai/docs)
- [INT-003-openrouter.md](../02-integraciones/INT-003-openrouter.md)
- [Arquitectura Multi-Tenant](../90-transversal/ARQUITECTURA-MULTI-TENANT-INTEGRACIONES.md)
---
**Fecha decision:** 2026-01-06
**Autores:** Backend Team