michangarrito/apps/whatsapp-service/dist/llm/llm.service.js
rckrdmrd 48dea7a5d0 feat: Initial commit - michangarrito
Marketplace móvil para negocios locales mexicanos.

Estructura inicial:
- apps/backend (NestJS API)
- apps/frontend (React Web)
- apps/mobile (Expo/React Native)
- apps/mcp-server (Claude MCP Server)
- apps/whatsapp-service (WhatsApp Business API)
- database/ (PostgreSQL DDL)
- docs/ (Documentación)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 04:41:02 -06:00

254 lines
11 KiB
JavaScript

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var LlmService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LlmService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const axios_1 = __importDefault(require("axios"));
const credentials_provider_service_1 = require("../common/credentials-provider.service");
let LlmService = LlmService_1 = class LlmService {
constructor(configService, credentialsProvider) {
this.configService = configService;
this.credentialsProvider = credentialsProvider;
this.logger = new common_1.Logger(LlmService_1.name);
this.clientCache = new Map();
this.conversationHistory = new Map();
}
getClient(config) {
const cacheKey = config.isFromPlatform
? 'platform'
: `tenant:${config.tenantId}`;
if (!this.clientCache.has(cacheKey)) {
const client = axios_1.default.create({
baseURL: config.baseUrl,
headers: {
Authorization: `Bearer ${config.apiKey}`,
'Content-Type': 'application/json',
},
});
this.clientCache.set(cacheKey, client);
}
return this.clientCache.get(cacheKey);
}
invalidateClientCache(tenantId) {
const cacheKey = tenantId ? `tenant:${tenantId}` : 'platform';
this.clientCache.delete(cacheKey);
if (tenantId) {
this.credentialsProvider.invalidateCache(tenantId);
}
}
async processMessage(userMessage, context) {
try {
const llmConfig = await this.credentialsProvider.getLLMConfig(context.tenantId);
let history = this.conversationHistory.get(context.phoneNumber) || [];
if (history.length === 0) {
const systemPrompt = llmConfig.systemPrompt || this.getSystemPrompt(context);
history = [{ role: 'system', content: systemPrompt }];
}
history.push({ role: 'user', content: userMessage });
if (history.length > 21) {
history = [history[0], ...history.slice(-20)];
}
const response = await this.callLlm(history, llmConfig);
history.push({ role: 'assistant', content: response.message });
this.conversationHistory.set(context.phoneNumber, history);
return response;
}
catch (error) {
this.logger.error(`LLM error: ${error.message}`);
return this.getFallbackResponse(userMessage);
}
}
async callLlm(messages, config) {
const functions = this.getAvailableFunctions();
const client = this.getClient(config);
const requestBody = {
model: config.model,
messages,
temperature: config.temperature,
max_tokens: config.maxTokens,
};
if (functions.length > 0 && config.provider !== 'anthropic') {
requestBody.tools = functions.map((fn) => ({
type: 'function',
function: fn,
}));
requestBody.tool_choice = 'auto';
}
const source = config.isFromPlatform ? 'platform' : `tenant:${config.tenantId}`;
this.logger.debug(`Calling LLM via ${source}, model: ${config.model}`);
const { data } = await client.post('/chat/completions', requestBody);
const choice = data.choices[0];
const assistantMessage = choice.message;
if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
const toolCall = assistantMessage.tool_calls[0];
const functionName = toolCall.function.name;
const functionArgs = JSON.parse(toolCall.function.arguments || '{}');
return {
message: assistantMessage.content || this.getActionMessage(functionName),
action: functionName,
data: functionArgs,
intent: functionName,
confidence: 0.9,
};
}
return {
message: assistantMessage.content || 'Lo siento, no pude procesar tu mensaje.',
intent: 'general_response',
confidence: 0.7,
};
}
getSystemPrompt(context) {
const businessName = context.businessName || 'MiChangarrito';
const businessDescription = context.businessName
? `un negocio local`
: `una tiendita de barrio en Mexico`;
return `Eres el asistente virtual de ${businessName}, ${businessDescription}.
Tu nombre es "Asistente de ${businessName}" y ayudas a los clientes con:
- Informacion sobre productos y precios
- Hacer pedidos
- Consultar su cuenta de fiado (credito)
- Estado de sus pedidos
Informacion del cliente:
- Nombre: ${context.customerName}
- Tiene carrito con ${context.cart?.length || 0} productos
Reglas importantes:
1. Responde siempre en espanol mexicano, de forma amigable y breve
2. Usa emojis ocasionalmente para ser mas amigable
3. Si el cliente quiere hacer algo especifico, usa las funciones disponibles
4. Si no entiendes algo, pide aclaracion de forma amable
5. Nunca inventes precios o productos, di que consultaras el catalogo
6. Para fiados, siempre verifica primero el saldo disponible
7. Se proactivo sugiriendo opciones relevantes
Ejemplos de respuestas:
- "Claro! Te muestro el menu de productos"
- "Perfecto, agrego eso a tu carrito"
- "Dejame revisar tu cuenta de fiado..."`;
}
getAvailableFunctions() {
return [
{
name: 'show_menu',
description: 'Muestra el menu principal de opciones al cliente',
parameters: { type: 'object', properties: {} },
},
{
name: 'show_products',
description: 'Muestra el catalogo de productos o una categoria especifica',
parameters: {
type: 'object',
properties: {
category: {
type: 'string',
description: 'Categoria de productos (bebidas, botanas, abarrotes, lacteos)',
},
},
},
},
{
name: 'show_fiado',
description: 'Muestra informacion de la cuenta de fiado del cliente',
parameters: { type: 'object', properties: {} },
},
{
name: 'add_to_cart',
description: 'Agrega un producto al carrito del cliente',
parameters: {
type: 'object',
properties: {
product_name: {
type: 'string',
description: 'Nombre del producto a agregar',
},
quantity: {
type: 'number',
description: 'Cantidad a agregar',
default: 1,
},
},
required: ['product_name'],
},
},
{
name: 'check_order_status',
description: 'Consulta el estado de los pedidos del cliente',
parameters: { type: 'object', properties: {} },
},
];
}
getActionMessage(action) {
const messages = {
show_menu: 'Te muestro el menu principal...',
show_products: 'Aqui tienes nuestros productos...',
show_fiado: 'Voy a revisar tu cuenta de fiado...',
add_to_cart: 'Agregando a tu carrito...',
check_order_status: 'Consultando tus pedidos...',
};
return messages[action] || 'Procesando tu solicitud...';
}
getFallbackResponse(userMessage) {
const lowerMessage = userMessage.toLowerCase();
if (lowerMessage.includes('producto') || lowerMessage.includes('comprar')) {
return {
message: 'Te muestro nuestros productos disponibles.',
action: 'show_products',
intent: 'view_products',
confidence: 0.6,
};
}
if (lowerMessage.includes('fiado') || lowerMessage.includes('credito') || lowerMessage.includes('deuda')) {
return {
message: 'Voy a revisar tu cuenta de fiado.',
action: 'show_fiado',
intent: 'check_fiado',
confidence: 0.6,
};
}
if (lowerMessage.includes('pedido') || lowerMessage.includes('orden')) {
return {
message: 'Consultando el estado de tus pedidos.',
action: 'check_order_status',
intent: 'check_orders',
confidence: 0.6,
};
}
return {
message: 'Disculpa, no entendi bien. Puedes escribir "menu" para ver las opciones disponibles, o decirme que necesitas.',
intent: 'unknown',
confidence: 0.3,
};
}
cleanupOldConversations(maxAgeMinutes = 30) {
const now = Date.now();
const maxAge = maxAgeMinutes * 60 * 1000;
for (const [phone, history] of this.conversationHistory.entries()) {
if (history.length > 0) {
if (this.conversationHistory.size > 1000) {
this.conversationHistory.delete(phone);
}
}
}
}
};
exports.LlmService = LlmService;
exports.LlmService = LlmService = LlmService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [config_1.ConfigService,
credentials_provider_service_1.CredentialsProviderService])
], LlmService);
//# sourceMappingURL=llm.service.js.map