Bagikan melalui


Menambahkan kode asli sebagai plugin

Cara term mudah untuk menyediakan agen AI dengan kemampuan yang tidak didukung secara asli adalah dengan membungkus kode asli ke dalam plugin. Ini memungkinkan Anda memanfaatkan keterampilan yang ada sebagai pengembang aplikasi untuk memperluas kemampuan agen AI Anda.

Di balik layar, Kernel Semantik kemudian akan menggunakan deskripsi yang Anda berikan, bersama dengan refleksi, untuk secara semantik menggambarkan plugin ke agen AI. Ini memungkinkan agen AI untuk memahami kemampuan plugin dan cara berinteraksi dengannya.

Memberikan informasi yang tepat kepada LLM

Saat menulis plugin, Anda perlu memberikan informasi yang tepat kepada agen AI untuk memahami kemampuan plugin dan fungsinya. Ini termasuk:

  • Nama plugin tersebut
  • Nama fungsi
  • Deskripsi fungsi
  • Parameter fungsi
  • Skema pada parameter
  • Skema nilai retur

Nilai Kernel Semantik adalah bahwa ia dapat secara otomatis menghasilkan sebagian besar informasi ini dari kode itu sendiri. Sebagai pengembang, ini hanya berarti Bahwa Anda harus memberikan deskripsi semantik fungsi dan parameter sehingga agen AI dapat memahaminya. Namun, jika Anda mengomentari dan membuat anotasi kode dengan benar, Anda mungkin sudah memiliki informasi ini.

Di bawah ini, kita akan menelusuri dua cara berbeda untuk menyediakan kode asli agen AI Anda dan cara memberikan informasi semantik ini.

Menentukan plugin menggunakan kelas

Cara termudah untuk membuat plugin asli adalah dengan memulai dengan kelas dan kemudian menambahkan metode yang dianotasi dengan atribut KernelFunction. Disarankan juga untuk secara liberal menggunakan Description anotasi untuk memberikan informasi yang diperlukan kepada agen AI untuk memahami fungsi.

Petunjuk / Saran

LightsPlugin berikut ini menggunakan LightModel yang sudah ditentukan di sini.

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")]
   public async Task<List<LightModel>> GetLightsAsync()
   {
      return _lights;
   }

   [KernelFunction("change_state")]
   [Description("Changes the state of the light")]
   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 Annotated

from semantic_kernel.functions import kernel_function

class LightsPlugin:
    def __init__(self, lights: list[LightModel]):
        self._lights = lights

    @kernel_function
    async def get_lights(self) -> list[LightModel]:
        """Gets a list of lights and their current state."""
        return self._lights

    @kernel_function
    async def change_state(
        self,
        change_state: LightModel
    ) -> LightModel | None:
        """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());
    }
}

Petunjuk / Saran

Karena LLM sebagian besar dilatih pada kode Python, disarankan untuk menggunakan snake_case untuk nama fungsi dan parameter (bahkan jika Anda menggunakan C# atau Java). Ini akan membantu agen AI lebih memahami fungsi dan parameternya.

Petunjuk / Saran

Fungsi Anda dapat menentukan Kernel, KernelArguments, ILoggerFactory, ILogger, IAIServiceSelector, CultureInfo, IFormatProvider, CancellationToken sebagai parameter dan ini tidak akan diiklankan ke LLM dan akan secara otomatis diatur ketika fungsi dipanggil. Jika Anda mengandalkan KernelArguments alih-alih argumen input eksplisit, kode Anda akan bertanggung jawab untuk melakukan konversi jenis.

Jika fungsi Anda memiliki objek kompleks sebagai variabel input, Kernel Semantik juga akan menghasilkan skema untuk objek tersebut dan meneruskannya ke agen AI. Mirip dengan fungsi, Anda harus memberikan Description anotasi untuk properti yang tidak mudah dipahami oleh AI. Di bawah ini adalah definisi untuk LightState kelas dan Brightness enum.

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 Brightness? 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 enum import Enum
from typing import TypedDict

class Brightness(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"

class LightModel(TypedDict):
    id: int
    name: str
    is_on: bool | None
    brightness: Brightness | None
    color: Annotated[str | None, "The color of the light with a hex code (ensure you include the # symbol)"]
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;
    }
}

Catatan

Meskipun ini adalah contoh "menyenangkan", ini dengan baik menunjukkan betapa kompleksnya parameter _plugin_ itu. Dalam kasus tunggal ini, kita memiliki objek kompleks dengan empat jenis properti yang berbeda: bilangan bulat, string, boolean, dan enum. Nilai Semantic Kernel adalah kemampuannya untuk secara otomatis menghasilkan skema untuk objek ini, kemudian meneruskannya ke agen kecerdasan buatan, dan mengatur parameter yang dihasilkan oleh agen tersebut ke dalam objek yang benar.

Setelah selesai menulis kelas plugin, Anda dapat menambahkannya ke kernel menggunakan AddFromType<> metode atau AddFromObject .

Setelah selesai menulis kelas plugin, Anda dapat menambahkannya ke kernel menggunakan metode add_plugin.

Setelah selesai menulis kelas plugin, Anda dapat menambahkannya ke kernel menggunakan AddFromType<> metode atau AddFromObject .

Petunjuk / Saran

Saat membuat fungsi, selalu tanyakan pada diri sendiri "bagaimana cara memberikan bantuan tambahan AI untuk menggunakan fungsi ini?" Ini dapat mencakup penggunaan jenis input tertentu (hindari string jika memungkinkan), memberikan deskripsi, dan contoh.

Menambahkan plugin menggunakan AddFromObject metode

Metode ini AddFromObject memungkinkan Anda untuk menambahkan instans kelas plugin langsung ke koleksi plugin jika Anda ingin langsung mengontrol bagaimana plugin dibangun.

Misalnya, konstruktor LightsPlugin kelas memerlukan daftar lampu. Dalam hal ini, Anda dapat membuat instans kelas plugin dan menambahkannya ke koleksi plugin.

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

Menambahkan plugin menggunakan AddFromType<> metode

Saat menggunakan metode , AddFromType<> kernel akan secara otomatis menggunakan injeksi dependensi untuk membuat instans kelas plugin dan menambahkannya ke koleksi plugin.

Ini berguna jika konstruktor Anda memerlukan layanan atau dependensi lain untuk disuntikkan ke plugin. Misalnya, kelas kami LightsPlugin mungkin memerlukan logger dan layanan lampu untuk diinjeksikan alih-alih daftar lampu.

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")]
   public async Task<List<LightModel>> GetLightsAsync()
   {
      _logger.LogInformation("Getting lights");
      return lightService.GetLights();
   }

   [KernelFunction("change_state")]
   [Description("Changes the state of the light")]
   public async Task<LightModel?> ChangeStateAsync(LightModel changeState)
   {
      _logger.LogInformation("Changing light state");
      return lightService.ChangeState(changeState);
   }
}

Dengan Injeksi Dependensi, Anda dapat menambahkan layanan dan plugin yang diperlukan ke pembuat kernel sebelum membangun 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();

Menentukan plugin menggunakan kumpulan fungsi

Kurang umum tetapi masih berguna adalah menentukan plugin menggunakan kumpulan fungsi. Ini sangat berguna jika Anda perlu membuat plugin secara dinamis dari sekumpulan fungsi saat runtime.

Menggunakan proses ini mengharuskan Anda menggunakan pabrik fungsi untuk membuat fungsi individual sebelum menambahkannya ke plugin.

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

Strategi tambahan untuk menambahkan kode asli dengan Injeksi Dependensi

Jika Anda bekerja dengan Dependency Injection, ada strategi tambahan yang bisa Anda lakukan untuk membuat dan menambahkan plugin ke kernel. Berikut ini adalah beberapa contoh bagaimana Anda dapat menambahkan plugin menggunakan Penyuntikan Ketergantungan.

Memasukkan koleksi plugin

Petunjuk / Saran

Sebaiknya jadikan koleksi plugin Anda sebagai layanan sementara sehingga dibuang setelah setiap penggunaan karena koleksi plugin dapat diubah. Membuat koleksi plugin baru untuk setiap penggunaan murah, jadi seharusnya tidak menjadi masalah performa.

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

Petunjuk / Saran

Seperti disebutkan dalam artikel kernel, kernel sangat ringan, jadi membuat kernel baru untuk setiap penggunaan sebagai sementara bukanlah masalah performa.

Hasilkan plugin Anda sebagai singleton

Plugin tidak dapat diubah, jadi biasanya aman untuk membuatnya sebagai singleton. Ini dapat dilakukan dengan menggunakan pabrik plugin dan menambahkan plugin yang dihasilkan ke koleksi layanan Anda.

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

Menambahkan plugin menggunakan add_plugin metode

Metode ini add_plugin memungkinkan Anda menambahkan instans plugin ke kernel. Di bawah ini adalah contoh bagaimana Anda dapat membangun LightsPlugin kelas dan menambahkannya ke 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)

Menambahkan plugin menggunakan createFromObject metode

Metode createFromObject memungkinkan Anda membuat plugin kernel dari Objek dengan metode yang dianotasi.

// Import the LightsPlugin
KernelPlugin lightPlugin = KernelPluginFactory.createFromObject(new LightsPlugin(),
        "LightsPlugin");

Plugin ini kemudian dapat ditambahkan ke kernel.

// Create a kernel with Azure OpenAI chat completion and plugin
Kernel kernel = Kernel.builder()
        .withAIService(ChatCompletionService.class, chatCompletionService)
        .withPlugin(lightPlugin)
        .build();

Menyediakan fungsi mengembalikan skema jenis ke LLM

Saat ini, tidak ada standar di seluruh industri yang terdefinisi dengan baik untuk menyediakan metadata jenis pengembalian fungsi ke model AI. Sampai standar seperti itu ditetapkan, teknik berikut dapat dipertimbangkan untuk skenario di mana nama properti jenis pengembalian tidak memadai bagi LLM untuk memahami kandungan mereka, atau di mana konteks tambahan atau instruksi penanganan perlu dikaitkan dengan jenis pengembalian untuk memodelkan atau meningkatkan skenario Anda.

Sebelum menggunakan salah satu teknik ini, disarankan untuk memberikan nama yang lebih deskriptif untuk properti jenis pengembalian, karena ini adalah cara paling mudah untuk meningkatkan pemahaman LLM tentang jenis pengembalian dan juga hemat biaya dalam hal penggunaan token.

Berikan informasi jenis pengembalian fungsi dalam deskripsi fungsi

Untuk menerapkan teknik ini, sertakan skema jenis pengembalian dalam atribut deskripsi fungsi. Skema harus merinci nama, deskripsi, dan jenis properti, seperti yang ditunjukkan dalam contoh berikut:

public class LightsPlugin
{
   [KernelFunction("change_state")]
   [Description("""Changes the state of the light and returns:
   {  
       "type": "object",
       "properties": {
           "id": { "type": "integer", "description": "Light ID" },
           "name": { "type": "string", "description": "Light name" },
           "is_on": { "type": "boolean", "description": "Is light on" },
           "brightness": { "type": "string", "enum": ["Low", "Medium", "High"], "description": "Brightness level" },
           "color": { "type": "string", "description": "Hex color code" }
       },
       "required": ["id", "name"]
   } 
   """)]
   public async Task<LightModel?> ChangeStateAsync(LightModel changeState)
   {
      ...
   }
}

Beberapa model mungkin memiliki batasan pada ukuran deskripsi fungsi, jadi disarankan untuk menjaga skema tetap ringkas dan hanya menyertakan informasi penting.

Dalam kasus di mana informasi jenis tidak penting dan meminimalkan konsumsi token adalah prioritas, pertimbangkan untuk memberikan deskripsi singkat tentang jenis pengembalian dalam atribut deskripsi fungsi alih-alih skema lengkap.

public class LightsPlugin
{
   [KernelFunction("change_state")]
   [Description("""Changes the state of the light and returns:
        id: light ID,
        name: light name,
        is_on: is light on,
        brightness: brightness level (Low, Medium, High),
        color: Hex color code.
    """)]
   public async Task<LightModel?> ChangeStateAsync(LightModel changeState)
   {
      ...
   }
}

Kedua pendekatan yang disebutkan di atas memerlukan penambahan skema jenis pengembalian secara manual dan memperbaruinya setiap kali jenis pengembalian berubah. Untuk menghindari hal ini, pertimbangkan teknik berikutnya.

Menyediakan skema jenis pengembalian fungsi sebagai bagian dari nilai pengembalian fungsi

Teknik ini melibatkan penyediaan nilai pengembalian fungsi dan skemanya ke LLM, bukan hanya nilai yang dikembalikan. Ini memungkinkan LLM menggunakan skema untuk memahami properti nilai yang dikembalikan.

Untuk menerapkan teknik ini, Anda perlu membuat dan mendaftarkan filter pemanggilan fungsi otomatis. Untuk detail selengkapnya, lihat artikel Filter Pemanggilan Fungsi Otomatis. Filter ini harus membungkus nilai pengembalian fungsi dalam objek kustom yang berisi nilai pengembalian asli dan skemanya. Di bawah ini adalah contoh:

private sealed class AddReturnTypeSchemaFilter : IAutoFunctionInvocationFilter
{
    public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
    {
        await next(context); // Invoke the original function

        // Crete the result with the schema
        FunctionResultWithSchema resultWithSchema = new()
        {
            Value = context.Result.GetValue<object>(),                  // Get the original result
            Schema = context.Function.Metadata.ReturnParameter?.Schema  // Get the function return type schema
        };

        // Return the result with the schema instead of the original one
        context.Result = new FunctionResult(context.Result, resultWithSchema);
    }

    private sealed class FunctionResultWithSchema
    {
        public object? Value { get; set; }
        public KernelJsonSchema? Schema { get; set; }
    }
}

// Register the filter
Kernel kernel = new Kernel();
kernel.AutoFunctionInvocationFilters.Add(new AddReturnTypeSchemaFilter());

Dengan filter terdaftar, Anda sekarang dapat memberikan deskripsi untuk jenis pengembalian dan propertinya, yang akan secara otomatis diekstraksi oleh Kernel Semantik:

[Description("The state of the light")] // Equivalent to annotating the function with the [return: Description("The state of the light")] attribute
public class LightModel
{
    [JsonPropertyName("id")]
    [Description("The ID of the light")]
    public int Id { get; set; }

    [JsonPropertyName("name")]
    [Description("The name of the light")]
    public string? Name { get; set; }

    [JsonPropertyName("is_on")]
    [Description("Indicates whether the light is on")]
    public bool? IsOn { get; set; }

    [JsonPropertyName("brightness")]
    [Description("The brightness level of the light")]
    public Brightness? 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; }
}

Pendekatan ini menghilangkan kebutuhan untuk menyediakan dan memperbarui skema jenis pengembalian secara manual setiap kali jenis pengembalian berubah, karena skema secara otomatis diekstraksi oleh Kernel Semantik.

Memberikan detail selengkapnya tentang fungsi

Saat membuat plugin di Python, Anda dapat memberikan informasi tambahan tentang fungsi di dekorator kernel_function. Informasi ini akan digunakan oleh agen AI untuk memahami fungsi dengan lebih baik.

from typing import Annotated

from semantic_kernel.functions import kernel_function

class LightsPlugin:
    def __init__(self, lights: list[LightModel]):
        self._lights = lights

    @kernel_function(name="GetLights", description="Gets a list of lights and their current state")
    async def get_lights(self) -> list[LightModel]:
        """Gets a list of lights and their current state."""
        return self._lights

    @kernel_function(name="ChangeState", description="Changes the state of the light")
    async def change_state(
        self,
        change_state: LightModel
    ) -> LightModel | None:
        """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

Sampel di atas menunjukkan cara mengambil alih nama fungsi dan memberikan deskripsi untuk fungsi tersebut. Secara default, nama fungsi adalah nama fungsi dan deskripsi kosong. Jika nama fungsi cukup deskriptif, Anda tidak akan memerlukan deskripsi, yang akan menyimpan token Anda. Namun, jika perilaku fungsi tidak jelas dari namanya, Anda harus memberikan deskripsi untuk AI.

Karena LLM sebagian besar dilatih pada kode Python, disarankan untuk menggunakan nama fungsi yang mengikuti konvensi penamaan Python, yang berarti Anda jarang perlu mengambil alih nama fungsi jika Anda mengikuti konvensi dalam kode Python Anda.

Langkah berikutnya

Sekarang setelah Anda tahu cara membuat plugin, Anda sekarang dapat mempelajari cara menggunakannya dengan agen AI Anda. Tergantung pada jenis fungsi yang telah Anda tambahkan ke plugin Anda, ada berbagai pola yang harus Anda ikuti. Untuk fungsi pengambilan, lihat artikel menggunakan fungsi pengambilan. Untuk fungsi otomatisasi tugas, lihat artikel menggunakan fungsi otomatisasi tugas.