Arquitectura de Integración IA
Google Gemini Integration
Configuración del Cliente IA
// Configuración del cliente IA
const genAI = new GoogleGenAI({
apiKey: process.env.NEXT_PUBLIC_GEMINI_API_KEY
});
// Configuración del modelo
export const model = genAI.getGenerativeModel({
model: "gemini-2.5-flash",
generationConfig: {
temperature: 0.7,
topP: 0.8,
topK: 40,
maxOutputTokens: 1024
}
});
Herramientas Disponibles
const tools: Tool[] = [
{
functionDeclarations: [
{
name: "get_rentable_properties",
description: "Obtiene propiedades disponibles para arrendar",
parameters: {
type: Type.OBJECT,
properties: {},
required: []
}
},
{
name: "get_property_details",
description: "Obtiene detalles de una propiedad específica",
parameters: {
type: Type.OBJECT,
properties: {
propertyId: {
type: Type.NUMBER,
description: "ID de la propiedad"
}
},
required: ["propertyId"]
}
},
{
name: "search_properties",
description: "Busca propiedades según criterios específicos",
parameters: {
type: Type.OBJECT,
properties: {
city: { type: Type.STRING, description: "Ciudad donde buscar" },
minPrice: { type: Type.NUMBER, description: "Precio mínimo" },
maxPrice: { type: Type.NUMBER, description: "Precio máximo" },
propertyType: { type: Type.STRING, description: "Tipo de propiedad" }
},
required: []
}
}
]
}
];
Flujo de Conversación IA
Diagrama de Flujo
sequenceDiagram
participant U as Usuario
participant C as ChatBot UI
participant G as Gemini AI
participant API as Backend API
U->>C: "¿Qué propiedades están disponibles?"
C->>G: Enviar mensaje + contexto
G->>G: Analizar intent
G->>API: Function call: get_rentable_properties()
API-->>G: Lista de propiedades
G->>G: Generar respuesta natural
G-->>C: Respuesta formateada
C-->>U: Mostrar propiedades + contactos
Sistema de Instrucciones
const SYSTEM_INSTRUCTIONS = `
Eres AlojaBot, el asistente virtual de AlojaPlus, una plataforma de gestión inmobiliaria en Colombia.
PERSONALIDAD:
- Profesional pero amigable
- Conocedor del mercado inmobiliario colombiano
- Útil y orientado a soluciones
- Usa un lenguaje claro y accesible
CAPACIDADES:
- Ayudar a encontrar propiedades disponibles
- Proporcionar detalles específicos de propiedades
- Buscar propiedades según criterios del usuario
- Explicar procesos de arriendo y compra
- Asistir con preguntas sobre contratos
LIMITACIONES:
- No puedes acceder a información personal de usuarios
- No puedes realizar transacciones financieras
- No puedes modificar datos de propiedades
- Solo proporciona información, no asesoría legal
CONTEXTO COLOMBIANO:
- Precios en pesos colombianos (COP)
- Ciudades principales: Bogotá, Medellín, Cali, Barranquilla
- Términos locales: arriendo, fiador, canon de arrendamiento
`;
Function Calling Architecture
Tool Execution Engine
export async function sendChatMessage(
message: string,
conversationHistory: ChatMessage[],
onToolCall: (toolName: string, toolArgs: any) => Promise<any>
): Promise<string> {
try {
// Construir el historial de conversación
const chatHistory = conversationHistory.map(msg => ({
role: msg.role === 'assistant' ? 'model' as const : 'user' as const,
parts: [{ text: msg.content }]
}));
// Agregar el mensaje actual
const messages = [
...chatHistory,
{
role: 'user' as const,
parts: [{ text: message }]
}
];
// Generar respuesta con herramientas
const result = await model.generateContent({
contents: messages,
tools: tools,
systemInstruction: SYSTEM_INSTRUCTIONS
});
const response = result.response;
// Procesar function calls si existen
if (response.functionCalls?.length > 0) {
const toolResults = await Promise.all(
response.functionCalls.map(async (call) => {
const result = await onToolCall(call.name, call.args);
return {
functionResponse: {
name: call.name,
response: { result }
}
};
})
);
// Generar respuesta final con los resultados de las herramientas
const finalResult = await model.generateContent({
contents: [
...messages,
{
role: 'model',
parts: response.functionCalls.map(call => ({
functionCall: call
}))
},
{
role: 'function',
parts: toolResults
}
],
systemInstruction: SYSTEM_INSTRUCTIONS
});
return finalResult.response.text() || "No se pudo generar respuesta";
}
return response.text() || "No se pudo generar respuesta";
} catch (error) {
console.error('Error en Gemini AI:', error);
throw new Error('Error al comunicarse con el asistente IA');
}
}
Tool Function Implementations
const toolFunctions = {
get_rentable_properties: async () => {
return await get_req('property/available');
},
get_property_details: async ({ propertyId }: { propertyId: number }) => {
return await get_req(`property/get?property_id=${propertyId}`);
},
search_properties: async (filters: any) => {
const params = new URLSearchParams();
Object.keys(filters).forEach(key => {
if (filters[key]) params.append(key, filters[key]);
});
return await get_req(`property/search?${params.toString()}`);
},
calculate_mortgage: async ({ propertyPrice, downPayment, interestRate, years }: any) => {
const principal = propertyPrice - downPayment;
const monthlyRate = interestRate / 100 / 12;
const numPayments = years * 12;
const monthlyPayment = principal *
(monthlyRate * Math.pow(1 + monthlyRate, numPayments)) /
(Math.pow(1 + monthlyRate, numPayments) - 1);
return {
monthlyPayment: Math.round(monthlyPayment),
totalPayment: Math.round(monthlyPayment * numPayments),
totalInterest: Math.round((monthlyPayment * numPayments) - principal)
};
}
};
Context Management
Conversation Memory
interface ChatMessage {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp: Date;
metadata?: {
toolCalls?: ToolCall[];
error?: string;
};
}
// Context window management
const MAX_CONTEXT_MESSAGES = 20;
function trimConversationHistory(messages: ChatMessage[]): ChatMessage[] {
if (messages.length <= MAX_CONTEXT_MESSAGES) {
return messages;
}
// Keep system message and recent messages
const systemMessages = messages.filter(m => m.role === 'system');
const recentMessages = messages
.filter(m => m.role !== 'system')
.slice(-MAX_CONTEXT_MESSAGES + systemMessages.length);
return [...systemMessages, ...recentMessages];
}
Session State
interface ChatSession {
id: string;
userId?: string;
startTime: Date;
lastActivity: Date;
preferences: {
language: string;
city?: string;
priceRange?: { min: number; max: number };
propertyType?: string;
};
context: {
currentSearch?: any;
viewedProperties: string[];
interestedProperties: string[];
};
}
// Session management
const activeSessions = new Map<string, ChatSession>();
function updateSessionContext(sessionId: string, updates: Partial<ChatSession['context']>) {
const session = activeSessions.get(sessionId);
if (session) {
session.context = { ...session.context, ...updates };
session.lastActivity = new Date();
}
}
AI Response Processing
Response Formatting
function formatPropertyResponse(properties: Property[]): string {
if (properties.length === 0) {
return "No encontré propiedades que coincidan con tus criterios. ¿Te gustaría ajustar los filtros de búsqueda?";
}
let response = `Encontré ${properties.length} propiedades que podrían interesarte:\n\n`;
properties.slice(0, 5).forEach((property, index) => {
response += `${index + 1}. **${property.title}**\n`;
response += ` 📍 ${property.address}, ${property.city}\n`;
response += ` 💰 $${property.price.toLocaleString('es-CO')} COP/mes\n`;
response += ` 🏠 ${property.bedrooms} habitaciones, ${property.bathrooms} baños\n`;
response += ` 📐 ${property.area}m²\n\n`;
});
if (properties.length > 5) {
response += `Y ${properties.length - 5} propiedades más disponibles.\n\n`;
}
response += "¿Te gustaría ver más detalles de alguna propiedad específica o ajustar los criterios de búsqueda?";
return response;
}
Error Handling
function handleAIError(error: any): string {
console.error('AI Error:', error);
if (error.message?.includes('quota')) {
return "Lo siento, el servicio está temporalmente saturado. Por favor intenta de nuevo en unos minutos.";
}
if (error.message?.includes('network')) {
return "Parece que hay un problema de conexión. Por favor verifica tu internet e intenta nuevamente.";
}
return "Disculpa, hubo un error procesando tu consulta. ¿Podrías intentar reformular tu pregunta?";
}
Performance Optimization
Caching Strategy
// Cache de respuestas frecuentes
const responseCache = new Map<string, { response: string; timestamp: number }>();
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutos
function getCachedResponse(query: string): string | null {
const cached = responseCache.get(query.toLowerCase());
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
return cached.response;
}
return null;
}
function setCachedResponse(query: string, response: string): void {
responseCache.set(query.toLowerCase(), {
response,
timestamp: Date.now()
});
}
Request Debouncing
// Debounce para evitar requests múltiples
const pendingRequests = new Map<string, Promise<string>>();
async function debouncedAIRequest(message: string, context: ChatMessage[]): Promise<string> {
const key = `${message}-${context.length}`;
if (pendingRequests.has(key)) {
return pendingRequests.get(key)!;
}
const request = sendChatMessage(message, context, executeToolFunction);
pendingRequests.set(key, request);
try {
const response = await request;
return response;
} finally {
pendingRequests.delete(key);
}
}
Analytics and Monitoring
AI Usage Metrics
interface AIMetrics {
totalQueries: number;
successfulResponses: number;
errorResponses: number;
averageResponseTime: number;
toolCallsCount: Record<string, number>;
popularQueries: string[];
}
function trackAIUsage(query: string, responseTime: number, success: boolean, toolsUsed: string[]) {
// Track metrics
const metrics = getAIMetrics();
metrics.totalQueries++;
if (success) {
metrics.successfulResponses++;
} else {
metrics.errorResponses++;
}
metrics.averageResponseTime = (
(metrics.averageResponseTime * (metrics.totalQueries - 1) + responseTime) /
metrics.totalQueries
);
toolsUsed.forEach(tool => {
metrics.toolCallsCount[tool] = (metrics.toolCallsCount[tool] || 0) + 1;
});
saveAIMetrics(metrics);
}
Quality Monitoring
function evaluateResponseQuality(query: string, response: string, userFeedback?: boolean): number {
let score = 0.5; // Base score
// Length appropriateness
if (response.length > 50 && response.length < 2000) {
score += 0.1;
}
// Contains specific information
if (response.includes('$') || response.includes('habitaciones') || response.includes('baños')) {
score += 0.2;
}
// User feedback
if (userFeedback === true) {
score += 0.3;
} else if (userFeedback === false) {
score -= 0.3;
}
return Math.max(0, Math.min(1, score));
}