- 1. Introducción a Semantic Kernel con C#: Construyendo tu Primera Aplicación de IA
- 2.Creando Plugins Personalizados en Semantic Kernel
- 3. Clasificación de Intenciones con LLMs en .NET
- 4. Servicios de Chat Completion con Azure OpenAI y Semantic Kernel
- 5. Vector Embeddings y Búsqueda Semántica con .NET
- 6. Prompt Engineering: Mejores Prácticas para LLMs
- 7. Integración de Azure OpenAI en Aplicaciones .NET
- 8. Configuración de Temperatura y Tokens en Modelos LLM
- 9. Estrategias de Caché para Servicios de IA
- 10. Manejo de Errores en Aplicaciones de IA con .NET
- 11. Salida JSON Estructurada con LLMs
- 12. Testing de Servicios de IA en .NET
- 13. Inyección de Dependencias con Semantic Kernel
- 14. Comprensión de Consultas con Lenguaje Natural
- 15. Sistemas de Búsqueda Semántica en Producción
- 16. Configuración de HttpClient para Servicios de IA
- 17. Guardrails y Validación en Sistemas de IA
- 18. Workflows Multi-Paso con IA
- 19. Optimización de Costos en Aplicaciones de IA
- 20. Monitoreo y Observabilidad de Servicios de IA
- 21. Arquitectura de Microservicios con IA
- 22. Seguridad en Aplicaciones de IA
- 23. Implementación de Routers Conversacionales Inteligentes
- 24. Filtrado por Relevancia Semántica en Búsquedas
- 25. Normalización y Preprocesamiento de Datos para IA
Introducción
La clasificación de intenciones es fundamental en aplicaciones conversacionales. Permite determinar qué quiere hacer el usuario basándose en su mensaje. En este tutorial aprenderás a implementar un sistema robusto de clasificación de intenciones usando Semantic Kernel y Azure OpenAI.
¿Qué es la Clasificación de Intenciones?
La clasificación de intenciones determina la acción que el usuario desea realizar. Por ejemplo:
- “Quiero comprar un libro” → Intención:
comprar - “¿Cuál es el estado de mi pedido?” → Intención:
consultar_estado - “Necesito ayuda” → Intención:
solicitar_ayuda
Arquitectura del Sistema
Un clasificador de intenciones efectivo tiene estos componentes:
- Servicio de Clasificación: Procesa el mensaje y determina la intención
- Sistema de Prompts: Define las instrucciones para el LLM
- Modelo de Resultado: Estructura que contiene la intención y metadatos
- Guardrails: Validaciones para garantizar resultados confiables
Implementación del Servicio
Modelo de Resultado
public record IntentClassificationResult
{
public required string Intent { get; init; }
public double Confidence { get; init; }
public Dictionary<string, object>? Entities { get; init; }
public string? Category { get; init; }
}
// Constantes para intenciones
public static class Intents
{
public const string BuyProduct = "buy_product";
public const string CheckStatus = "check_status";
public const string RequestHelp = "request_help";
public const string CancelOrder = "cancel_order";
public const string Unknown = "unknown";
}
Servicio de Clasificación
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.Extensions.Logging;
using System.Text.Json;
public class IntentClassificationService
{
private readonly Kernel _kernel;
private readonly ILogger<IntentClassificationService> _logger;
private const double MinimumConfidenceThreshold = 0.65;
public IntentClassificationService(
Kernel kernel,
ILogger<IntentClassificationService> logger)
{
_kernel = kernel;
_logger = logger;
}
public async Task<IntentClassificationResult> ClassifyAsync(
string messageText,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(messageText))
{
return CreateUnknownResult("Mensaje vacío");
}
try
{
var chatService = _kernel.GetRequiredService<IChatCompletionService>();
var systemPrompt = BuildSystemPrompt();
var userPrompt = BuildUserPrompt(messageText);
var chatHistory = new ChatHistory();
chatHistory.AddSystemMessage(systemPrompt);
chatHistory.AddUserMessage(userPrompt);
// Configuración para respuesta JSON estructurada
var settings = new OpenAIPromptExecutionSettings
{
Temperature = 0.1, // Baja temperatura para clasificación consistente
ResponseFormat = "json_object"
};
var response = await chatService.GetChatMessageContentAsync(
chatHistory,
settings,
_kernel,
cancellationToken);
var jsonContent = response.Content ?? string.Empty;
_logger.LogDebug("Respuesta de clasificación: {Response}", jsonContent);
var result = ParseClassificationResponse(jsonContent, messageText);
result = ApplyGuardrails(result, messageText);
_logger.LogInformation(
"Intención clasificada: {Intent} (confianza: {Confidence:F2})",
result.Intent, result.Confidence);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error clasificando intención");
return CreateUnknownResult("Error en clasificación");
}
}
private string BuildSystemPrompt()
{
return """
Eres un clasificador experto de intenciones para un sistema de e-commerce.
Tu tarea es determinar QUÉ quiere hacer el usuario.
Tipos de intención (en orden de prioridad):
1. **buy_product**: Usuario quiere comprar algo (ej: "quiero comprar un libro", "añadir al carrito")
2. **check_status**: Usuario consulta el estado de algo (ej: "¿dónde está mi pedido?", "estado de mi compra")
3. **cancel_order**: Usuario quiere cancelar (ej: "cancelar mi pedido", "no quiero el producto")
4. **request_help**: Usuario pide ayuda o información (ej: "ayuda", "¿cómo funciona?", "soporte")
5. **unknown**: No está clara la intención
REGLAS CRÍTICAS:
- Analiza el CONTEXTO completo del mensaje
- Si detectas producto mencionado en compra, extrae el nombre en "product_name"
- Si hay número de pedido, extráelo en "order_id"
- Confidence debe ser 0.0-1.0 basado en tu certeza
- Si la intención es ambigua, usa "unknown"
Responde SOLO con JSON válido:
{
"intent": "buy_product|check_status|cancel_order|request_help|unknown",
"confidence": 0.0-1.0,
"entities": {
"product_name": "string|null",
"order_id": "string|null",
"quantity": number|null
},
"category": "string|null"
}
Ejemplos:
- "Quiero comprar tres camisetas" → {"intent":"buy_product","confidence":0.95,"entities":{"product_name":"camisetas","quantity":3}}
- "¿Dónde está mi pedido 12345?" → {"intent":"check_status","confidence":0.98,"entities":{"order_id":"12345"}}
- "Ayuda con mi cuenta" → {"intent":"request_help","confidence":0.90,"entities":{}}
""";
}
private string BuildUserPrompt(string messageText)
{
return $"""
Clasifica la intención de este mensaje:
Mensaje: "{messageText}"
Responde con JSON válido siguiendo el esquema.
""";
}
private IntentClassificationResult ParseClassificationResponse(
string jsonContent,
string originalText)
{
try
{
// Limpiar respuesta de markdown si existe
jsonContent = jsonContent.Trim();
if (jsonContent.StartsWith("```json"))
jsonContent = jsonContent[7..];
if (jsonContent.StartsWith("```"))
jsonContent = jsonContent[3..];
if (jsonContent.EndsWith("```"))
jsonContent = jsonContent[..^3];
jsonContent = jsonContent.Trim();
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
var parsed = JsonSerializer.Deserialize<JsonClassificationResult>(
jsonContent, options);
if (parsed == null)
throw new JsonException("Resultado nulo");
return new IntentClassificationResult
{
Intent = NormalizeIntent(parsed.Intent),
Confidence = parsed.Confidence,
Entities = parsed.Entities,
Category = parsed.Category
};
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error parseando JSON: {Json}", jsonContent);
return CreateUnknownResult("Error parsing JSON");
}
}
private IntentClassificationResult ApplyGuardrails(
IntentClassificationResult result,
string originalText)
{
// Guardrail 1: Confianza baja → marcar como unknown
if (result.Confidence < MinimumConfidenceThreshold)
{
_logger.LogInformation(
"Confianza baja ({Confidence:F2}) para '{Text}', marcando como unknown",
result.Confidence, originalText);
return result with { Intent = Intents.Unknown };
}
// Guardrail 2: Validar entidades requeridas
if (result.Intent == Intents.BuyProduct)
{
if (result.Entities == null ||
!result.Entities.ContainsKey("product_name"))
{
_logger.LogWarning(
"buy_product sin product_name para '{Text}'", originalText);
return result with { Intent = Intents.Unknown };
}
}
return result;
}
private string NormalizeIntent(string? intent)
{
if (string.IsNullOrWhiteSpace(intent))
return Intents.Unknown;
return intent.ToLowerInvariant().Replace("_", "").Replace("-", "") switch
{
"buyproduct" or "buy" or "purchase" => Intents.BuyProduct,
"checkstatus" or "status" => Intents.CheckStatus,
"cancelorder" or "cancel" => Intents.CancelOrder,
"requesthelp" or "help" or "ayuda" => Intents.RequestHelp,
_ => Intents.Unknown
};
}
private IntentClassificationResult CreateUnknownResult(string reason)
{
_logger.LogInformation("Creando resultado unknown: {Reason}", reason);
return new IntentClassificationResult
{
Intent = Intents.Unknown,
Confidence = 0.0,
Entities = null,
Category = null
};
}
// Modelo interno para deserialización
private class JsonClassificationResult
{
public string? Intent { get; set; }
public double Confidence { get; set; }
public Dictionary<string, object>? Entities { get; set; }
public string? Category { get; set; }
}
}
Uso del Clasificador
Ejemplo Básico
var classifier = new IntentClassificationService(kernel, logger);
var result = await classifier.ClassifyAsync("Quiero comprar un libro de programación");
Console.WriteLine($"Intención: {result.Intent}");
Console.WriteLine($"Confianza: {result.Confidence:F2}");
if (result.Entities != null && result.Entities.ContainsKey("product_name"))
{
Console.WriteLine($"Producto: {result.Entities["product_name"]}");
}
Integración con Router
public class MessageRouter
{
private readonly IntentClassificationService _classifier;
private readonly Dictionary<string, Func<IntentClassificationResult, Task<string>>> _handlers;
public MessageRouter(IntentClassificationService classifier)
{
_classifier = classifier;
_handlers = new()
{
[Intents.BuyProduct] = HandleBuyProduct,
[Intents.CheckStatus] = HandleCheckStatus,
[Intents.CancelOrder] = HandleCancelOrder,
[Intents.RequestHelp] = HandleRequestHelp,
[Intents.Unknown] = HandleUnknown
};
}
public async Task<string> RouteMessageAsync(string message)
{
var classification = await _classifier.ClassifyAsync(message);
if (_handlers.TryGetValue(classification.Intent, out var handler))
{
return await handler(classification);
}
return "No pude procesar tu mensaje. ¿Puedes reformularlo?";
}
private async Task<string> HandleBuyProduct(IntentClassificationResult result)
{
if (result.Entities?.ContainsKey("product_name") == true)
{
var productName = result.Entities["product_name"].ToString();
return $"Perfecto, te ayudo a comprar {productName}. ¿Cuántas unidades necesitas?";
}
return "¿Qué producto te gustaría comprar?";
}
private async Task<string> HandleCheckStatus(IntentClassificationResult result)
{
if (result.Entities?.ContainsKey("order_id") == true)
{
var orderId = result.Entities["order_id"].ToString();
return $"Buscando información del pedido {orderId}...";
}
return "Por favor, proporciona tu número de pedido para consultar el estado.";
}
private async Task<string> HandleCancelOrder(IntentClassificationResult result)
{
return "Entiendo que quieres cancelar un pedido. ¿Puedes darme el número de pedido?";
}
private async Task<string> HandleRequestHelp(IntentClassificationResult result)
{
return "¡Estoy aquí para ayudarte! ¿Qué necesitas saber?";
}
private async Task<string> HandleUnknown(IntentClassificationResult result)
{
return "No estoy seguro de entender. ¿Puedes explicar qué necesitas?";
}
}
Clasificación Multi-Intención
Para casos más complejos donde un mensaje puede tener múltiples intenciones:
public class MultiIntentClassificationService
{
private readonly Kernel _kernel;
public async Task<List<IntentClassificationResult>> ClassifyMultipleAsync(
string messageText,
CancellationToken cancellationToken = default)
{
var systemPrompt = """
Analiza el mensaje y extrae TODAS las intenciones presentes.
Un mensaje puede tener múltiples intenciones.
Por ejemplo: "Quiero comprar un libro y cancelar mi pedido anterior"
Tiene dos intenciones: buy_product y cancel_order
Responde con JSON array:
[
{"intent": "buy_product", "confidence": 0.95, "entities": {"product_name": "libro"}},
{"intent": "cancel_order", "confidence": 0.90, "entities": {}}
]
""";
var chatService = _kernel.GetRequiredService<IChatCompletionService>();
var chatHistory = new ChatHistory();
chatHistory.AddSystemMessage(systemPrompt);
chatHistory.AddUserMessage($"Mensaje: {messageText}");
var settings = new OpenAIPromptExecutionSettings
{
Temperature = 0.1,
ResponseFormat = "json_object"
};
var response = await chatService.GetChatMessageContentAsync(
chatHistory, settings, _kernel, cancellationToken);
// Parsear array de intenciones
var results = JsonSerializer.Deserialize<List<JsonClassificationResult>>(
response.Content ?? "[]");
return results?.Select(r => new IntentClassificationResult
{
Intent = r.Intent ?? Intents.Unknown,
Confidence = r.Confidence,
Entities = r.Entities,
Category = r.Category
}).ToList() ?? new List<IntentClassificationResult>();
}
}
Clasificación con Contexto
public class ContextAwareIntentClassifier
{
private readonly Kernel _kernel;
public async Task<IntentClassificationResult> ClassifyWithContextAsync(
string messageText,
List<string> conversationHistory,
CancellationToken cancellationToken = default)
{
var chatHistory = new ChatHistory();
chatHistory.AddSystemMessage(BuildSystemPrompt());
// Agregar historial de conversación
foreach (var previousMessage in conversationHistory.TakeLast(5))
{
chatHistory.AddUserMessage(previousMessage);
}
// Agregar mensaje actual
chatHistory.AddUserMessage($"Clasifica esta intención: {messageText}");
var chatService = _kernel.GetRequiredService<IChatCompletionService>();
var response = await chatService.GetChatMessageContentAsync(
chatHistory,
kernel: _kernel,
cancellationToken: cancellationToken);
return ParseResponse(response.Content ?? "");
}
private string BuildSystemPrompt()
{
return """
Clasifica la intención considerando el CONTEXTO de la conversación.
Si el usuario dice "sí" o "no", determina a qué se refiere basándote
en los mensajes anteriores.
Responde con JSON válido.
""";
}
private IntentClassificationResult ParseResponse(string content)
{
// Implementación de parsing
return new IntentClassificationResult
{
Intent = Intents.Unknown,
Confidence = 0.0
};
}
}
Testing del Clasificador
using Xunit;
using Microsoft.Extensions.Logging.Abstractions;
public class IntentClassificationServiceTests
{
[Theory]
[InlineData("Quiero comprar un libro", Intents.BuyProduct)]
[InlineData("¿Dónde está mi pedido?", Intents.CheckStatus)]
[InlineData("Cancelar mi orden", Intents.CancelOrder)]
[InlineData("Necesito ayuda", Intents.RequestHelp)]
public async Task ClassifyAsync_IdentifiesCorrectIntent(
string message,
string expectedIntent)
{
// Arrange
var kernel = CreateTestKernel();
var logger = NullLogger<IntentClassificationService>.Instance;
var classifier = new IntentClassificationService(kernel, logger);
// Act
var result = await classifier.ClassifyAsync(message);
// Assert
Assert.Equal(expectedIntent, result.Intent);
Assert.True(result.Confidence >= 0.65);
}
[Fact]
public async Task ClassifyAsync_ExtractsEntities()
{
// Arrange
var kernel = CreateTestKernel();
var logger = NullLogger<IntentClassificationService>.Instance;
var classifier = new IntentClassificationService(kernel, logger);
// Act
var result = await classifier.ClassifyAsync("Quiero comprar 3 libros");
// Assert
Assert.Equal(Intents.BuyProduct, result.Intent);
Assert.NotNull(result.Entities);
Assert.True(result.Entities.ContainsKey("product_name"));
Assert.Equal("libros", result.Entities["product_name"]);
}
private Kernel CreateTestKernel()
{
// Configuración de kernel para tests
var builder = Kernel.CreateBuilder();
// Configurar con mock o test deployment
return builder.Build();
}
}
Mejores Prácticas
1. Temperatura Baja para Consistencia
var settings = new OpenAIPromptExecutionSettings
{
Temperature = 0.1 // Muy baja para clasificación consistente
};
2. Validación de Resultados
private bool IsValidResult(IntentClassificationResult result)
{
if (result.Confidence < 0.0 || result.Confidence > 1.0)
return false;
if (string.IsNullOrWhiteSpace(result.Intent))
return false;
return true;
}
3. Logging Detallado
_logger.LogInformation(
"Clasificación: Intent={Intent}, Confidence={Confidence:F2}, Entities={Entities}",
result.Intent,
result.Confidence,
JsonSerializer.Serialize(result.Entities));
4. Caché de Clasificaciones
public class CachedIntentClassifier
{
private readonly IMemoryCache _cache;
private readonly IntentClassificationService _classifier;
public async Task<IntentClassificationResult> ClassifyAsync(string message)
{
var cacheKey = $"intent_{message.GetHashCode()}";
if (_cache.TryGetValue<IntentClassificationResult>(cacheKey, out var cached))
{
return cached;
}
var result = await _classifier.ClassifyAsync(message);
_cache.Set(cacheKey, result, TimeSpan.FromMinutes(30));
return result;
}
}
Conclusión
La clasificación de intenciones es crucial para aplicaciones conversacionales efectivas. Con Semantic Kernel y Azure OpenAI puedes crear clasificadores robustos que entienden el contexto y manejan casos complejos. Los guardrails y validaciones aseguran resultados confiables en producción.
Palabras clave: intent classification, NLU, Semantic Kernel, Azure OpenAI, chatbot, conversational AI, C#
Share this content: