--- 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 { 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