プラグインとしてネイティブ コードを追加する

ネイティブでサポートされていない機能を AI エージェントに提供する最も簡単な方法は、ネイティブ コードをプラグインにラップすることです。 これにより、アプリ開発者としての既存のスキルを活用して、AI エージェントの機能を拡張できます。

その後、セマンティック カーネルは、提供した説明とリフレクションを使用して、AI エージェントにプラグインを意味的に記述します。 これにより、AI エージェントはプラグインの機能とその操作方法を理解できます。

LLM に適切な情報を提供する

プラグインを作成するときは、プラグインとその機能の機能を理解するための適切な情報を AI エージェントに提供する必要があります。 これには、次のものが含まれます。

  • プラグインの名前
  • 関数の名前
  • 関数の説明
  • 関数のパラメーター
  • パラメーターのスキーマ

セマンティック カーネルの値は、コード自体からこの情報の大部分を自動的に生成できることです。 開発者は、これは、AI エージェントがそれらを理解できるように、関数とパラメーターのセマンティック記述を提供する必要があることを意味します。 ただし、コードに適切にコメントを付け、注釈を付ける場合は、既にこの情報が手元にある可能性があります。

以下では、ネイティブ コードを AI エージェントに提供する 2 つの異なる方法と、このセマンティック情報を提供する方法について説明します。

クラスを使用したプラグインの定義

ネイティブ プラグインを作成する最も簡単な方法は、クラスから始めて、 KernelFunction 属性で注釈が付けられたメソッドを追加することです。 また、 Description 注釈を自由に使用して、機能を理解するために必要な情報を AI エージェントに提供することもお勧めします。

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

ヒント

LLM は主に Python コードでトレーニングされるため、関数名とパラメーターにsnake_caseを使用することをお勧めします (C# または Java を使用している場合でも)。 これは、AI エージェントが関数とそのパラメーターをより深く理解するのに役立ちます。

関数に入力変数として複雑なオブジェクトがある場合、セマンティック カーネルはそのオブジェクトのスキーマも生成し、それを AI エージェントに渡します。 関数と同様に、AI には明らかではないプロパティに対して Description 注釈を指定する必要があります。 LightState クラスとBrightness列挙型の定義を次に示します。

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

Note

これは 「楽しい」例ですが、プラグインのパラメーターがどれだけ複雑であるかを示す良い仕事です。 この 1 つのケースでは、 four さまざまな種類のプロパティ (整数、文字列、ブール値、列挙型) を持つ複合オブジェクトがあります。 セマンティック カーネルの値は、このオブジェクトのスキーマを自動的に生成し、それを AI エージェントに渡し、AI エージェントによって生成されたパラメーターを正しいオブジェクトにマーシャリングできることです。

プラグイン クラスの作成が完了したら、 AddFromType<> メソッドまたは AddFromObject メソッドを使用してカーネルに追加できます。

ヒント

関数を作成するときは、常に「この関数を使用するために AI に追加のヘルプを提供するにはどうすればよいですか?」と自問してください。これには、特定の入力型の使用 (可能な場合は文字列を避ける)、説明の提供、例が含まれます。

AddFromObject メソッドを使用したプラグインの追加

AddFromObjectメソッドを使用すると、プラグインの構築方法を直接制御する場合に備えて、プラグイン クラスのインスタンスをプラグイン コレクションに直接追加できます。

たとえば、 LightsPlugin クラスのコンストラクターにはライトの一覧が必要です。 この場合は、プラグイン クラスのインスタンスを作成し、プラグイン コレクションに追加できます。

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

AddFromType<> メソッドを使用したプラグインの追加

AddFromType<> メソッドを使用すると、カーネルは自動的に依存関係の挿入を使用してプラグイン クラスのインスタンスを作成し、プラグイン コレクションに追加します。

これは、コンストラクターでサービスまたはその他の依存関係をプラグインに挿入する必要がある場合に役立ちます。 たとえば、 LightsPlugin クラスでは、ライトのリストではなく、ロガーとライト サービスを挿入する必要があります。

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

Dependency Injection を使用すると、カーネルをビルドする前に、必要なサービスとプラグインをカーネル ビルダーに追加できます。

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

関数のコレクションを使用したプラグインの定義

あまり一般的ではありませんが、それでも便利なのは、関数のコレクションを使用してプラグインを定義することです。 これは、実行時に一連の関数からプラグインを動的に作成する必要がある場合に特に便利です。

このプロセスを使用するには、関数ファクトリを使用して、プラグインに追加する前に個々の関数を作成する必要があります。

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

依存関係の挿入を使用してネイティブ コードを追加するための追加の戦略

依存関係の挿入を使用している場合は、カーネルにプラグインを作成して追加するために実行できる追加の戦略があります。 依存関係挿入を使用してプラグインを追加する方法の例を次に示します。

プラグイン コレクションを挿入する

ヒント

プラグイン コレクションは変更可能であるため、使用するたびに破棄されるように、プラグイン コレクションを一時的なサービスにすることをお勧めします。 使用ごとに新しいプラグイン コレクションを作成するのは安価であるため、パフォーマンスの問題ではありません。

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

ヒント

カーネルの記事で説明したようにカーネルは非常に軽量であるため、一時的な使用ごとに新しいカーネルを作成することはパフォーマンスの問題ではありません。

シングルトンとしてプラグインを生成する

プラグインは変更できないため、通常はシングルトンとして作成しても安全です。 これを行うには、プラグイン ファクトリを使用し、結果のプラグインをサービス コレクションに追加します。

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

add_plugin メソッドを使用したプラグインの追加

add_plugin メソッドを使用すると、プラグイン インスタンスをカーネルに追加できます。 LightsPlugin クラスを構築してカーネルに追加する方法の例を次に示します。

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

createFromObject メソッドを使用したプラグインの追加

createFromObject メソッドを使用すると、注釈付きメソッドを使用して Object からカーネル プラグインをビルドできます。

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

その後、このプラグインをカーネルに追加できます。

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

次のステップ

プラグインを作成する方法がわかったら、AI エージェントでプラグインを使用する方法を学習できるようになりました。 プラグインに追加した関数の種類に応じて、従う必要があるパターンが異なります。 取得関数については、 取得関数の使用に関する記事 参照してください。 タスク自動化関数については、 タスク自動化関数の使用に関する記事 参照してください。