Delen via


Systeemeigen code toevoegen als invoegtoepassing

De eenvoudigste manier om een AI-agent mogelijkheden te bieden die niet systeemeigen worden ondersteund, is door systeemeigen code in een invoegtoepassing te verpakken. Hierdoor kunt u uw bestaande vaardigheden als app-ontwikkelaar gebruiken om de mogelijkheden van uw AI-agents uit te breiden.

Achter de schermen gebruikt Semantische kernel vervolgens de beschrijvingen die u opgeeft, samen met reflectie, om de invoegtoepassing semantisch te beschrijven aan de AI-agent. Hierdoor kan de AI-agent de mogelijkheden van de invoegtoepassing begrijpen en ermee communiceren.

De LLM de juiste informatie verstrekken

Bij het ontwerpen van een invoegtoepassing moet u de AI-agent de juiste informatie verstrekken om inzicht te hebben in de mogelijkheden van de invoegtoepassing en de bijbehorende functies. Dit zijn onder andere de nieuwe mogelijkheden:

  • De naam van de invoegtoepassing
  • De namen van de functies
  • De beschrijvingen van de functies
  • De parameters van de functies
  • Het schema van de parameters

De waarde van Semantische kernel is dat de meeste van deze informatie automatisch kan worden gegenereerd op basis van de code zelf. Als ontwikkelaar betekent dit alleen dat u de semantische beschrijvingen van de functies en parameters moet opgeven, zodat de AI-agent deze kan begrijpen. Als u uw code op de juiste manier commentaar geeft en er aantekeningen op plaatst, hebt u deze informatie waarschijnlijk al bij de hand.

Hieronder doorlopen we de twee verschillende manieren om uw AI-agent systeemeigen code te bieden en hoe u deze semantische informatie kunt opgeven.

Een invoegtoepassing definiëren met behulp van een klasse

De eenvoudigste manier om een systeemeigen invoegtoepassing te maken, is door te beginnen met een klasse en vervolgens methoden toe te voegen die zijn voorzien van het KernelFunction kenmerk. Het wordt ook aanbevolen om de Description aantekening vrijelijk te gebruiken om de AI-agent de benodigde informatie te bieden om de functie te begrijpen.

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(
        name="get_lights",
        description="Gets a list of lights and their current state",
    )
    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(
        name="change_state",
        description="Changes the state of the light",
    )
    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

Tip

Omdat de LLM's voornamelijk worden getraind in Python-code, is het raadzaam om snake_case te gebruiken voor functienamen en -parameters (zelfs als u C# of Java gebruikt). Hierdoor krijgt de AI-agent meer inzicht in de functie en de bijbehorende parameters.

Als uw functie een complex object heeft als invoervariabele, genereert Semantische kernel ook een schema voor dat object en geeft deze door aan de AI-agent. Net als bij functies moet u aantekeningen opgeven Description voor eigenschappen die niet duidelijk zijn voor de AI. Hieronder ziet u de definitie voor de LightState klasse en de Brightness opsomming.

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, Optional

class LightModel(TypedDict):
    id: int
    name: str
    is_on: Optional[bool]
    brightness: Optional[int]
    hex: Optional[str]

Notitie

Hoewel dit een 'leuk' voorbeeld is, is het handig om te laten zien hoe complex de parameters van een invoegtoepassing kunnen zijn. In dit ene geval hebben we een complex object met vier verschillende typen eigenschappen: een geheel getal, tekenreeks, booleaanse waarde en opsomming. De waarde van Semantic Kernel is dat het automatisch het schema voor dit object kan genereren en doorgeeft aan de AI-agent en de parameters die door de AI-agent zijn gegenereerd, in het juiste object kunnen doorgeven.

Zodra u klaar bent met het ontwerpen van uw invoegtoepassingsklasse, kunt u deze aan de kernel toevoegen met behulp van de AddFromType<> of AddFromObject methoden.

Tip

Wanneer u een functie maakt, vraagt u zich altijd af hoe kan ik de AI aanvullende hulp geven bij het gebruik van deze functie? Dit kan omvatten het gebruik van specifieke invoertypen (vermijd waar mogelijk tekenreeksen), het verstrekken van beschrijvingen en voorbeelden.

Een invoegtoepassing toevoegen met behulp van de AddFromObject methode

Met AddFromObject de methode kunt u rechtstreeks een exemplaar van de invoegtoepassingsklasse toevoegen aan de invoegtoepassingverzameling voor het geval u rechtstreeks wilt bepalen hoe de invoegtoepassing wordt samengesteld.

De constructor van de LightsPlugin klasse vereist bijvoorbeeld de lijst met lichten. In dit geval kunt u een exemplaar van de invoegtoepassingsklasse maken en deze toevoegen aan de verzameling invoegtoepassingen.

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));

Een invoegtoepassing toevoegen met behulp van de AddFromType<> methode

Wanneer de AddFromType<> methode wordt gebruikt, gebruikt de kernel automatisch afhankelijkheidsinjectie om een exemplaar van de invoegtoepassingsklasse te maken en toe te voegen aan de invoegtoepassingverzameling.

Dit is handig als voor uw constructor services of andere afhankelijkheden moeten worden geïnjecteerd in de invoegtoepassing. Onze klasse kan bijvoorbeeld LightsPlugin vereisen dat een logger en een lichte service erin worden geïnjecteerd in plaats van een lijst met lichten.

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);
   }
}

Met afhankelijkheidsinjectie kunt u de vereiste services en invoegtoepassingen toevoegen aan de kernelbouwer voordat u de kernel bouwt.

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();

Een invoegtoepassing definiëren met behulp van een verzameling functies

Minder gangbaar maar nog steeds nuttig is het definiëren van een invoegtoepassing met behulp van een verzameling functies. Dit is met name handig als u dynamisch een invoegtoepassing moet maken op basis van een set functies tijdens runtime.

Voor het gebruik van dit proces moet u de functiefactory gebruiken om afzonderlijke functies te maken voordat u ze toevoegt aan de invoegtoepassing.

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"
    )
]);

Aanvullende strategieën voor het toevoegen van systeemeigen code met afhankelijkheidsinjectie

Als u met afhankelijkheidsinjectie werkt, zijn er extra strategieën die u kunt nemen om plug-ins te maken en toe te voegen aan de kernel. Hieronder vindt u enkele voorbeelden van hoe u een invoegtoepassing kunt toevoegen met behulp van afhankelijkheidsinjectie.

Een invoegtoepassingsverzameling injecteren

Tip

Het is raadzaam om uw invoegtoepassingverzameling een tijdelijke service te maken, zodat deze na elk gebruik wordt verwijderd omdat de invoegtoepassingverzameling veranderlijk is. Het maken van een nieuwe invoegtoepassingverzameling voor elk gebruik is goedkoop, dus het mag geen prestatieprobleem zijn.

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);
});

Tip

Zoals vermeld in het kernelartikel, is de kernel uiterst lichtgewicht, dus het maken van een nieuwe kernel voor elk gebruik als een tijdelijk probleem is geen prestatieprobleem.

Uw invoegtoepassingen genereren als singletons

Invoegtoepassingen zijn niet veranderlijk, dus het is meestal veilig om ze als singletons te maken. Dit kan worden gedaan met behulp van de plugin factory en het toevoegen van de resulterende invoegtoepassing aan uw serviceverzameling.

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);
});

Een invoegtoepassing toevoegen met behulp van de add_plugin methode

Met de add_plugin methode kunt u een invoegtoepassingexemplaren toevoegen aan de kernel. Hieronder ziet u een voorbeeld van hoe u de LightsPlugin klasse kunt maken en deze aan de kernel kunt toevoegen.

# 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)

Volgende stappen

Nu u weet hoe u een invoegtoepassing maakt, kunt u nu leren hoe u deze kunt gebruiken met uw AI-agent. Afhankelijk van het type functies dat u aan uw invoegtoepassingen hebt toegevoegd, zijn er verschillende patronen die u moet volgen. Raadpleeg het artikel over het ophalen van functies voor het ophalen van functies . Raadpleeg het artikel over het gebruik van taakautomatiseringsfuncties voor taakautomatiseringsfuncties .