Adición de código nativo como complemento
La manera más fácil de proporcionar a un agente de INTELIGENCIA ARTIFICIAL funcionalidades que no son compatibles de forma nativa es encapsular código nativo en un complemento. Esto le permite aprovechar las aptitudes existentes como desarrollador de aplicaciones para ampliar las funcionalidades de los agentes de IA.
En segundo plano, el kernel semántico usará las descripciones que proporcione, junto con la reflexión, para describir semánticamente el complemento al agente de IA. Esto permite al agente de IA comprender las funcionalidades del complemento y cómo interactuar con él.
Proporcionar a LLM la información correcta
Al crear un complemento, debe proporcionar al agente de IA la información adecuada para comprender las funcionalidades del complemento y sus funciones. Esto incluye:
- Nombre del complemento
- Nombres de las funciones
- Descripciones de las funciones
- Parámetros de las funciones
- Esquema de los parámetros
El valor del kernel semántico es que puede generar automáticamente la mayoría de esta información a partir del propio código. Como desarrollador, esto solo significa que debe proporcionar las descripciones semánticas de las funciones y los parámetros para que el agente de IA pueda comprenderlas. Sin embargo, si comenta correctamente y anota el código, es probable que ya tenga esta información a mano.
A continuación, le guiaremos por las dos maneras diferentes de proporcionar al agente de IA código nativo y cómo proporcionar esta información semántica.
Definición de un complemento mediante una clase
La manera más fácil de crear un complemento nativo es empezar con una clase y, a continuación, agregar métodos anotados con el KernelFunction
atributo . También se recomienda usar liberalmente la Description
anotación para proporcionar al agente de IA la información necesaria para comprender la función.
public class LightsPlugin
{
private readonly List<LightModel> _lights;
public LightsPlugin(LoggerFactory loggerFactory, List<LightModel> lights)
{
_lights = lights;
}
[KernelFunction("get_lights")]
[Description("Gets a list of lights and their current state")]
[return: Description("An array of lights")]
public async Task<List<LightModel>> GetLightsAsync()
{
return _lights;
}
[KernelFunction("change_state")]
[Description("Changes the state of the light")]
[return: Description("The updated state of the light; will return null if the light does not exist")]
public async Task<LightModel?> ChangeStateAsync(LightModel changeState)
{
// Find the light to change
var light = _lights.FirstOrDefault(l => l.Id == changeState.Id);
// If the light does not exist, return null
if (light == null)
{
return null;
}
// Update the light state
light.IsOn = changeState.IsOn;
light.Brightness = changeState.Brightness;
light.Color = changeState.Color;
return light;
}
}
from typing import List, Optional, Annotated
class LightsPlugin:
def __init__(self, lights: List[LightModel]):
self._lights = lights
@kernel_function
async def get_lights(self) -> Annotated[List[LightModel], "An array of lights"]:
"""Gets a list of lights and their current state."""
return self._lights
@kernel_function
async def change_state(
self,
change_state: LightModel
) -> Annotated[Optional[LightModel], "The updated state of the light; will return null if the light does not exist"]:
"""Changes the state of the light."""
for light in self._lights:
if light["id"] == change_state["id"]:
light["is_on"] = change_state.get("is_on", light["is_on"])
light["brightness"] = change_state.get("brightness", light["brightness"])
light["hex"] = change_state.get("hex", light["hex"])
return light
return None
public class LightsPlugin {
// Mock data for the lights
private final Map<Integer, LightModel> lights = new HashMap<>();
public LightsPlugin() {
lights.put(1, new LightModel(1, "Table Lamp", false, LightModel.Brightness.MEDIUM, "#FFFFFF"));
lights.put(2, new LightModel(2, "Porch light", false, LightModel.Brightness.HIGH, "#FF0000"));
lights.put(3, new LightModel(3, "Chandelier", true, LightModel.Brightness.LOW, "#FFFF00"));
}
@DefineKernelFunction(name = "get_lights", description = "Gets a list of lights and their current state")
public List<LightModel> getLights() {
System.out.println("Getting lights");
return new ArrayList<>(lights.values());
}
@DefineKernelFunction(name = "change_state", description = "Changes the state of the light")
public LightModel changeState(
@KernelFunctionParameter(
name = "model",
description = "The new state of the model to set. Example model: " +
"{\"id\":99,\"name\":\"Head Lamp\",\"isOn\":false,\"brightness\":\"MEDIUM\",\"color\":\"#FFFFFF\"}",
type = LightModel.class) LightModel model
) {
System.out.println("Changing light " + model.getId() + " " + model.getIsOn());
if (!lights.containsKey(model.getId())) {
throw new IllegalArgumentException("Light not found");
}
lights.put(model.getId(), model);
return lights.get(model.getId());
}
}
Sugerencia
Dado que los LLM se entrenan principalmente en código de Python, se recomienda usar snake_case para los nombres de función y los parámetros (incluso si usa C# o Java). Esto ayudará al agente de IA a comprender mejor la función y sus parámetros.
Si la función tiene un objeto complejo como una variable de entrada, el kernel semántico también generará un esquema para ese objeto y lo pasará al agente de IA. De forma similar a las funciones, debe proporcionar Description
anotaciones para las propiedades que no son obvias para la inteligencia artificial. A continuación se muestra la definición de la LightState
clase y la Brightness
enumeración.
using System.Text.Json.Serialization;
public class LightModel
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("is_on")]
public bool? IsOn { get; set; }
[JsonPropertyName("brightness")]
public enum? Brightness { get; set; }
[JsonPropertyName("color")]
[Description("The color of the light with a hex code (ensure you include the # symbol)")]
public string? Color { get; set; }
}
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Brightness
{
Low,
Medium,
High
}
from typing import TypedDict
class LightModel(TypedDict):
id: int
name: str
is_on: bool | None
brightness: int | None
hex: str | None
public class LightModel {
private int id;
private String name;
private Boolean isOn;
private Brightness brightness;
private String color;
public enum Brightness {
LOW,
MEDIUM,
HIGH
}
public LightModel(int id, String name, Boolean isOn, Brightness brightness, String color) {
this.id = id;
this.name = name;
this.isOn = isOn;
this.brightness = brightness;
this.color = color;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getIsOn() {
return isOn;
}
public void setIsOn(Boolean isOn) {
this.isOn = isOn;
}
public Brightness getBrightness() {
return brightness;
}
public void setBrightness(Brightness brightness) {
this.brightness = brightness;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
Nota:
Aunque se trata de un ejemplo "divertido", hace un buen trabajo que muestra lo complejo que pueden ser los parámetros de un complemento. En este único caso, tenemos un objeto complejo con cuatro tipos diferentes de propiedades: un entero, una cadena, un valor booleano y una enumeración. El valor del kernel semántico es que puede generar automáticamente el esquema de este objeto y pasarlo al agente de IA y serializar los parámetros generados por el agente de IA en el objeto correcto.
Una vez que haya terminado de crear la clase de complemento, puede agregarla al kernel mediante los AddFromType<>
métodos o AddFromObject
.
Sugerencia
Al crear una función, pregunte siempre "¿cómo puedo proporcionar ayuda adicional a la inteligencia artificial para usar esta función?" Esto puede incluir el uso de tipos de entrada específicos (evitar cadenas siempre que sea posible), proporcionar descripciones y ejemplos.
Adición de un complemento mediante el AddFromObject
método
El AddFromObject
método permite agregar una instancia de la clase de complemento directamente a la colección de complementos en caso de que quiera controlar directamente cómo se construye el complemento.
Por ejemplo, el constructor de la LightsPlugin
clase requiere la lista de luces. En este caso, puede crear una instancia de la clase de complemento y agregarla a la colección de complementos.
List<LightModel> lights = new()
{
new LightModel { Id = 1, Name = "Table Lamp", IsOn = false, Brightness = Brightness.Medium, Color = "#FFFFFF" },
new LightModel { Id = 2, Name = "Porch light", IsOn = false, Brightness = Brightness.High, Color = "#FF0000" },
new LightModel { Id = 3, Name = "Chandelier", IsOn = true, Brightness = Brightness.Low, Color = "#FFFF00" }
};
kernel.Plugins.AddFromObject(new LightsPlugin(lights));
Adición de un complemento mediante el AddFromType<>
método
Al usar el AddFromType<>
método , el kernel usará automáticamente la inserción de dependencias para crear una instancia de la clase de complemento y agregarla a la colección de complementos.
Esto resulta útil si el constructor requiere que los servicios u otras dependencias se inserte en el complemento. Por ejemplo, nuestra LightsPlugin
clase puede requerir que se inserte un registrador y un servicio de luz en ella en lugar de una lista de luces.
public class LightsPlugin
{
private readonly Logger _logger;
private readonly LightService _lightService;
public LightsPlugin(LoggerFactory loggerFactory, LightService lightService)
{
_logger = loggerFactory.CreateLogger<LightsPlugin>();
_lightService = lightService;
}
[KernelFunction("get_lights")]
[Description("Gets a list of lights and their current state")]
[return: Description("An array of lights")]
public async Task<List<LightModel>> GetLightsAsync()
{
_logger.LogInformation("Getting lights");
return lightService.GetLights();
}
[KernelFunction("change_state")]
[Description("Changes the state of the light")]
[return: Description("The updated state of the light; will return null if the light does not exist")]
public async Task<LightModel?> ChangeStateAsync(LightModel changeState)
{
_logger.LogInformation("Changing light state");
return lightService.ChangeState(changeState);
}
}
Con la inserción de dependencias, puede agregar los servicios y complementos necesarios al generador de kernels antes de compilar el kernel.
var builder = Kernel.CreateBuilder();
// Add dependencies for the plugin
builder.Services.AddLogging(loggingBuilder => loggingBuilder.AddConsole().SetMinimumLevel(LogLevel.Trace));
builder.Services.AddSingleton<LightService>();
// Add the plugin to the kernel
builder.Plugins.AddFromType<LightsPlugin>("Lights");
// Build the kernel
Kernel kernel = builder.Build();
Definición de un complemento mediante una colección de funciones
Menos común pero útil es definir un complemento mediante una colección de funciones. Esto es especialmente útil si necesita crear dinámicamente un complemento a partir de un conjunto de funciones en tiempo de ejecución.
El uso de este proceso requiere que use la factoría de funciones para crear funciones individuales antes de agregarlas al complemento.
kernel.Plugins.AddFromFunctions("time_plugin",
[
KernelFunctionFactory.CreateFromMethod(
method: () => DateTime.Now,
functionName: "get_time",
description: "Get the current time"
),
KernelFunctionFactory.CreateFromMethod(
method: (DateTime start, DateTime end) => (end - start).TotalSeconds,
functionName: "diff_time",
description: "Get the difference between two times in seconds"
)
]);
Estrategias adicionales para agregar código nativo con inserción de dependencias
Si está trabajando con la inserción de dependencias, hay estrategias adicionales que puede tomar para crear y agregar complementos al kernel. A continuación se muestran algunos ejemplos de cómo puede agregar un complemento mediante la inserción de dependencias.
Inserción de una colección de complementos
Sugerencia
Se recomienda convertir la colección de complementos en un servicio transitorio para que se elimine después de cada uso, ya que la colección de complementos es mutable. La creación de una nueva colección de complementos para cada uso es barata, por lo que no debe ser un problema de rendimiento.
var builder = Host.CreateApplicationBuilder(args);
// Create native plugin collection
builder.Services.AddTransient((serviceProvider)=>{
KernelPluginCollection pluginCollection = [];
pluginCollection.AddFromType<LightsPlugin>("Lights");
return pluginCollection;
});
// Create the kernel service
builder.Services.AddTransient<Kernel>((serviceProvider)=> {
KernelPluginCollection pluginCollection = serviceProvider.GetRequiredService<KernelPluginCollection>();
return new Kernel(serviceProvider, pluginCollection);
});
Sugerencia
Como se mencionó en el artículo de kernel, el kernel es extremadamente ligero, por lo que la creación de un nuevo kernel para cada uso como transitorio no es un problema de rendimiento.
Generación de complementos como singletons
Los complementos no son mutables, por lo que suele ser seguro crearlos como singletons. Esto se puede hacer mediante el generador de complementos y agregando el complemento resultante a la colección de servicios.
var builder = Host.CreateApplicationBuilder(args);
// Create singletons of your plugin
builder.Services.AddKeyedSingleton("LightPlugin", (serviceProvider, key) => {
return KernelPluginFactory.CreateFromType<LightsPlugin>();
});
// Create a kernel service with singleton plugin
builder.Services.AddTransient((serviceProvider)=> {
KernelPluginCollection pluginCollection = [
serviceProvider.GetRequiredKeyedService<KernelPlugin>("LightPlugin")
];
return new Kernel(serviceProvider, pluginCollection);
});
Adición de un complemento mediante el add_plugin
método
El add_plugin
método permite agregar una instancia de complemento al kernel. A continuación se muestra un ejemplo de cómo puede construir la LightsPlugin
clase y agregarla al kernel.
# Create the kernel
kernel = Kernel()
# Create dependencies for the plugin
lights = [
{"id": 1, "name": "Table Lamp", "is_on": False, "brightness": 100, "hex": "FF0000"},
{"id": 2, "name": "Porch light", "is_on": False, "brightness": 50, "hex": "00FF00"},
{"id": 3, "name": "Chandelier", "is_on": True, "brightness": 75, "hex": "0000FF"},
]
# Create the plugin
lights_plugin = LightsPlugin(lights)
# Add the plugin to the kernel
kernel.add_plugin(lights_plugin)
Adición de un complemento mediante el createFromObject
método
El createFromObject
método permite crear un complemento de kernel a partir de un objeto con métodos anotados.
// Import the LightsPlugin
KernelPlugin lightPlugin = KernelPluginFactory.createFromObject(new LightsPlugin(),
"LightsPlugin");
A continuación, este complemento se puede agregar a un kernel.
// Create a kernel with Azure OpenAI chat completion and plugin
Kernel kernel = Kernel.builder()
.withAIService(ChatCompletionService.class, chatCompletionService)
.withPlugin(lightPlugin)
.build();
Pasos siguientes
Ahora que sabe cómo crear un complemento, ahora puede aprender a usarlos con el agente de IA. Dependiendo del tipo de funciones que haya agregado a los complementos, hay diferentes patrones que debe seguir. Para las funciones de recuperación, consulte el artículo uso de funciones de recuperación. Para las funciones de automatización de tareas, consulte el artículo uso de funciones de automatización de tareas .