Carta Clara: carta QR, administración y lectura de fotos con Gemini
Una carta QR con panel por restaurante y lectura de fotos mediante Gemini. Código abierto, edición manual y revisión antes de incorporar platos.
Aprende a crear plugins (Skills) en Semantic Kernel con C#: KernelFunction, registro con DI, ejemplos async (calculadora, búsqueda, texto) y buenas prácticas.

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.
Un plugin es una colección de funciones que:
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);
}
}
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);
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;
}
}
}
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";
}
}
}
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();
}
}
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());
// 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);
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.";
}
}
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
}
[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}";
}
}
[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();
}
[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
}
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.