- 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
- 23. Filtrado por Relevancia Semántica en Búsquedas
- 25. Normalización y Preprocesamiento de Datos para IA
Introducción
Los plugins (anteriormente llamados Skills) son componentes reutilizables en Semantic Kernel que encapsulan funcionalidad específica. Aprenderás a crear plugins que pueden ser invocados por el kernel o por el propio LLM para realizar tareas específicas.
¿Qué son los Plugins?
Un plugin es una colección de funciones que:
- Realizan tareas específicas (buscar datos, realizar cálculos, llamar APIs)
- Pueden ser invocadas por el LLM automáticamente
- Son reutilizables en diferentes contextos
- Combinan código tradicional con capacidades de IA
Creando tu Primer Plugin
Plugin Simple: Calculadora
using System.ComponentModel;
using Microsoft.SemanticKernel;
public class CalculadoraPlugin
{
[KernelFunction("sumar")]
[Description("Suma dos números")]
public double Sumar(
[Description("Primer número")] double a,
[Description("Segundo número")] double b)
{
return a + b;
}
[KernelFunction("multiplicar")]
[Description("Multiplica dos números")]
public double Multiplicar(
[Description("Primer número")] double a,
[Description("Segundo número")] double b)
{
return a * b;
}
[KernelFunction("calcular_porcentaje")]
[Description("Calcula el porcentaje de un número")]
public double CalcularPorcentaje(
[Description("Número base")] double numero,
[Description("Porcentaje a calcular")] double porcentaje)
{
return numero * (porcentaje / 100);
}
}
Registrar el Plugin
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(deploymentName, endpoint, apiKey)
.Build();
// Registrar plugin como objeto
kernel.Plugins.AddFromObject(new CalculadoraPlugin());
// Ahora el LLM puede usar estas funciones automáticamente
var response = await kernel.InvokePromptAsync(
"Calcula el 15% de 200 y luego multiplícalo por 3");
Console.WriteLine(response);
Plugin Asíncrono: Búsqueda de Datos
using System.ComponentModel; using Microsoft.SemanticKernel; public class BuscadorPlugin { private readonly HttpClient _httpClient; public BuscadorPlugin(HttpClient httpClient) { _httpClient = httpClient; } [KernelFunction("buscar_informacion")] [Description("Busca información sobre un tema específico")] public async Task<string> BuscarInformacionAsync( [Description("Tema a buscar")] string tema, CancellationToken cancellationToken = default) { try { // Simular búsqueda en una API var url ="https://api.ejemplo.com/search?q={Uri.EscapeDataString(tema)}"; var response = await _httpClient.GetStringAsync(url, cancellationToken); return response; } catch (Exception ex) { return"Error al buscar: {ex.Message}"; } } [KernelFunction("verificar_disponibilidad")] [Description("Verifica si un servicio está disponible")] public async Task<bool> VerificarDisponibilidadAsync( [Description("URL del servicio")] string url, CancellationToken cancellationToken = default) { try { var response = await _httpClient.GetAsync(url, cancellationToken); return response.IsSuccessStatusCode; } catch { return false; } } }
Plugin con Dependencias: Acceso a Datos
using System.ComponentModel; using Microsoft.SemanticKernel; using Microsoft.Extensions.Logging; public class RepositorioPlugin { private readonly ILogger<RepositorioPlugin> _logger; private readonly IDatabaseService _database; public RepositorioPlugin( ILogger<RepositorioPlugin> logger, IDatabaseService database) { _logger = logger; _database = database; } [KernelFunction("obtener_usuario")] [Description("Obtiene información de un usuario por ID")] public async Task<string> ObtenerUsuarioAsync( [Description("ID del usuario")] string userId, CancellationToken cancellationToken = default) { _logger.LogInformation("Buscando usuario {UserId}", userId); try { var usuario = await _database.GetUserAsync(userId, cancellationToken); if (usuario == null) { return "Usuario no encontrado"; } return"Usuario: {usuario.Nombre}, Email: {usuario.Email}"; } catch (Exception ex) { _logger.LogError(ex, "Error obteniendo usuario {UserId}", userId); return "Error al obtener usuario"; } } [KernelFunction("guardar_nota")] [Description("Guarda una nota en la base de datos")] public async Task<string> GuardarNotaAsync( [Description("Contenido de la nota")] string contenido, [Description("Categoría de la nota")] string categoria, CancellationToken cancellationToken = default) { _logger.LogInformation("Guardando nota en categoría {Categoria}", categoria); try { var nota = new Nota { Contenido = contenido, Categoria = categoria, FechaCreacion = DateTime.UtcNow}; await _database.SaveNoteAsync(nota, cancellationToken); return"Nota guardada exitosamente con ID: {nota.Id}"; } catch (Exception ex) { _logger.LogError(ex, "Error guardando nota"); return "Error al guardar la nota"; } } }
Plugin Complejo: Procesamiento de Texto
using System.ComponentModel; using System.Text; using System.Text.RegularExpressions; using Microsoft.SemanticKernel; public class TextoPlugin { [KernelFunction("contar_palabras")] [Description("Cuenta las palabras en un texto")] public int ContarPalabras([Description("Texto a analizar")] string texto) { if (string.IsNullOrWhiteSpace(texto)) return 0; return texto.Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries).Length; } [KernelFunction("extraer_emails")] [Description("Extrae todos los emails de un texto")] public List<string> ExtraerEmails([Description("Texto a procesar")] string texto) { if (string.IsNullOrWhiteSpace(texto)) return new List<string>(); var pattern = @"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"; var matches = Regex.Matches(texto, pattern); return matches.Select(m => m.Value).Distinct().ToList(); } [KernelFunction("formatear_texto")] [Description("Formatea un texto según reglas específicas")] public string FormatearTexto( [Description("Texto a formatear")] string texto, [Description("Tipo de formato: uppercase, lowercase, titlecase")] string formato) { if (string.IsNullOrWhiteSpace(texto)) return string.Empty; return formato.ToLower() switch { "uppercase" => texto.ToUpper(), "lowercase" => texto.ToLower(), "titlecase" => System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(texto.ToLower()), _ => texto }; } [KernelFunction("resumen_estadistico")] [Description("Proporciona estadísticas sobre un texto")] public string ResumenEstadistico([Description("Texto a analizar")] string texto) { if (string.IsNullOrWhiteSpace(texto)) return "Texto vacío"; var palabras = ContarPalabras(texto); var caracteres = texto.Length; var lineas = texto.Split('\n').Length; var emails = ExtraerEmails(texto).Count; var sb = new StringBuilder(); sb.AppendLine("Palabras: {palabras}"); sb.AppendLine("Caracteres: {caracteres}"); sb.AppendLine("Líneas: {lineas}"); sb.AppendLine("Emails encontrados: {emails}"); return sb.ToString(); } }
Registro de Plugins con Inyección de Dependencias
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
// Configurar servicios
var services = new ServiceCollection();
services.AddHttpClient();
services.AddLogging();
services.AddSingleton<IDatabaseService, DatabaseService>();
// Crear kernel con DI
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton(services.BuildServiceProvider());
builder.AddAzureOpenAIChatCompletion(deploymentName, endpoint, apiKey);
var kernel = builder.Build();
// Registrar plugins con dependencias resueltas por DI
var serviceProvider = kernel.Services.GetRequiredService<IServiceProvider>();
var httpClient = serviceProvider.GetRequiredService<HttpClient>();
var database = serviceProvider.GetRequiredService<IDatabaseService>();
var logger = serviceProvider.GetRequiredService<ILogger<RepositorioPlugin>>();
kernel.Plugins.AddFromObject(new BuscadorPlugin(httpClient));
kernel.Plugins.AddFromObject(new RepositorioPlugin(logger, database));
kernel.Plugins.AddFromObject(new TextoPlugin());
kernel.Plugins.AddFromObject(new CalculadoraPlugin());
Invocación Manual de Funciones
// Invocar función específica directamente var resultado = await kernel.InvokeAsync( "CalculadoraPlugin", "sumar", new KernelArguments { ["a"] = 10, ["b"] = 20 }); Console.WriteLine("Resultado: {resultado}"); // Invocar con tipo fuerte var textoPlugin = new TextoPlugin(); kernel.Plugins.AddFromObject(textoPlugin, "TextoPlugin"); var estadisticas = await kernel.InvokeAsync( "TextoPlugin", "resumen_estadistico", new KernelArguments { ["texto"] = "Este es un texto de ejemplo para analizar." }); Console.WriteLine(estadisticas);
Plugin con Estado: Sesión de Usuario
using System.ComponentModel; using Microsoft.SemanticKernel; public class SesionPlugin { private readonly Dictionary<string, object> _sessionData = new(); [KernelFunction("guardar_en_sesion")] [Description("Guarda un valor en la sesión del usuario")] public string GuardarEnSesion( [Description("Clave para el valor")] string clave, [Description("Valor a guardar")] string valor) { _sessionData[clave] = valor; return"Valor guardado con clave '{clave}'"; } [KernelFunction("obtener_de_sesion")] [Description("Obtiene un valor de la sesión del usuario")] public string ObtenerDeSesion([Description("Clave del valor")] string clave) { if (_sessionData.TryGetValue(clave, out var valor)) { return valor?.ToString() ?? "Valor nulo"; } return"No se encontró valor para la clave '{clave}'"; } [KernelFunction("limpiar_sesion")] [Description("Limpia todos los datos de la sesión")] public string LimpiarSesion() { var count = _sessionData.Count; _sessionData.Clear(); return"Sesión limpiada. Se eliminaron {count} valores."; } }
Mejores Prácticas
1. Descripciones Claras
Las descripciones ayudan al LLM a decidir cuándo usar cada función:
[KernelFunction("buscar_productos")]
[Description("Busca productos en el catálogo que coincidan con los criterios especificados")]
public async Task<string> BuscarProductosAsync(
[Description("Término de búsqueda, puede ser nombre, categoría o descripción")] string busqueda,
[Description("Precio máximo en euros, opcional")] double? precioMax = null)
{
// Implementación
}
2. Manejo de Errores Robusto
[KernelFunction("procesar_pedido")] [Description("Procesa un pedido de cliente")] public async Task<string> ProcesarPedidoAsync( string pedidoId, CancellationToken cancellationToken) { try { // Validación if (string.IsNullOrWhiteSpace(pedidoId)) return "Error: ID de pedido inválido"; // Procesamiento var resultado = await _service.ProcessOrderAsync(pedidoId, cancellationToken); return"Pedido {pedidoId} procesado exitosamente"; } catch (OperationCanceledException) { return "Operación cancelada por el usuario"; } catch (Exception ex) { _logger.LogError(ex, "Error procesando pedido {PedidoId}", pedidoId); return"Error al procesar el pedido: {ex.Message}"; } }
3. Logging para Debugging
[KernelFunction("analizar_sentimiento")]
[Description("Analiza el sentimiento de un texto")]
public async Task<string> AnalizarSentimientoAsync(
string texto,
CancellationToken cancellationToken)
{
_logger.LogInformation("Analizando sentimiento de texto con {Length} caracteres", texto?.Length ?? 0);
var resultado = await _sentimentService.AnalyzeAsync(texto, cancellationToken);
_logger.LogInformation("Sentimiento detectado: {Sentiment} (confianza: {Confidence})",
resultado.Sentiment, resultado.Confidence);
return resultado.ToString();
}
4. Parámetros Opcionales
[KernelFunction("buscar_articulos")]
[Description("Busca artículos en el blog")]
public async Task<string> BuscarArticulosAsync(
[Description("Término de búsqueda")] string termino,
[Description("Categoría opcional para filtrar")] string? categoria = null,
[Description("Número máximo de resultados, default 10")] int limite = 10,
CancellationToken cancellationToken = default)
{
// Implementación con parámetros opcionales
}
Conclusión
Los plugins son fundamentales para extender Semantic Kernel con funcionalidad personalizada. Te permiten combinar la inteligencia de los LLMs con lógica de negocio específica, acceso a datos y servicios externos. Con los conceptos aprendidos puedes crear plugins robustos y reutilizables para tus aplicaciones de IA.
Share this content: