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

7.2 KiB

id type title status decision_date updated_at simco_version stakeholders tags
ADR-0003 ADR Estrategia LLM Agnostica Accepted 2026-01-04 2026-01-10 3.8.0
Equipo MiChangarrito
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

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

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

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


Fecha decision: 2026-01-06 Autores: Backend Team