Aracılığıyla paylaş


Eklenti olarak yerel kod ekleme

Yapay zeka aracısına yerel olarak desteklenmeyen özellikler sağlamanın en kolay yolu, yerel kodu bir eklentiye sarmaktır. Bu, yapay zeka aracılarınızın özelliklerini genişletmek için uygulama geliştiricisi olarak mevcut becerilerinizden yararlanmanızı sağlar.

Semantik Çekirdek, arka planda sağladığınız açıklamaları yansımayla birlikte kullanarak yapay zeka aracısına eklentiyi anlamsal olarak açıklar. Bu, yapay zeka aracısının eklentinin özelliklerini ve eklentiyle nasıl etkileşim kuracaklarını anlamasını sağlar.

LLM'ye doğru bilgileri sağlama

Eklentiyi yazarken, eklentinin ve işlevlerinin özelliklerini anlamak için yapay zeka aracısına doğru bilgileri sağlamanız gerekir. Buna aşağıdakiler dahildir:

  • Eklentinin adı
  • İşlevlerin adları
  • İşlevlerin açıklamaları
  • İşlevlerin parametreleri
  • Parametrelerin şeması
  • Dönüş değerinin şeması

Anlam Çekirdeğinin değeri, bu bilgilerin çoğunu kodun kendisinden otomatik olarak oluşturabilmesidir. Geliştirici olarak bu, yapay zeka aracısının bunları anlayabilmesi için işlevlerin ve parametrelerin anlamsal açıklamalarını sağlamanız gerektiği anlamına gelir. Bununla birlikte, kodunuz için düzgün bir şekilde açıklama ekleyip açıklama eklerseniz, büyük olasılıkla bu bilgilere zaten sahipsinizdir.

Aşağıda yapay zeka aracınıza yerel kod sağlamanın iki farklı yolunu ve bu anlamsal bilgileri sağlamayı inceleyeceğiz.

Sınıf kullanarak eklenti tanımlama

Yerel eklenti oluşturmanın en kolay yolu, bir sınıfla başlamak ve ardından özniteliğiyle KernelFunction ek açıklamalı yöntemler eklemektir. Ayrıca, yapay zeka aracısına işlevi anlamak için gerekli bilgileri sağlamak için ek açıklamayı serbestçe kullanmanız Description da önerilir.

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

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
    ) -> Optional[LightModel]:
        """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());
    }
}

İpucu

LLM'ler ağırlıklı olarak Python kodu üzerinde eğitildiğinden, işlev adları ve parametreleri (C# veya Java kullanıyor olsanız bile) için snake_case kullanmanız önerilir. Bu, yapay zeka aracısının işlevi ve parametrelerini daha iyi anlamasına yardımcı olur.

İpucu

İşlevleriniz parametre olarak Kernel, KernelArguments, ILoggerFactory, ILogger, IAIServiceSelector, CultureInfo, IFormatProvider, CancellationToken belirtebilir ve bunlar LLM'ye tanıtılmaz ve işlev çağrıldığında otomatik olarak ayarlanır. Açık giriş bağımsız değişkenleri yerine KernelArguments kullanırsanız, kodunuz tür dönüştürmeleri gerçekleştirmekle sorumludur.

İşlevinizin giriş değişkeni olarak karmaşık bir nesnesi varsa, Anlam Çekirdeği de bu nesne için bir şema oluşturur ve bunu yapay zeka aracısına geçirir. İşlevlere benzer şekilde, yapay zeka için belirgin olmayan özellikler için ek açıklamalar sağlamanız Description gerekir. Sınıfın ve numaralandırmanın LightState tanımı aşağıdadır 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 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 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;
    }
}

Not

Bu bir "eğlenceli" örnek olsa da, eklentinin parametrelerinin ne kadar karmaşık olabileceğini gösteren iyi bir iş çıkardı. Bu tek durumda, dört farklı özellik türüne sahip karmaşık bir nesnemiz vardır: tamsayı, dize, boole ve sabit listesi. Semantik Çekirdeğin değeri, bu nesne için şemayı otomatik olarak oluşturup yapay zeka aracısına geçirip yapay zeka aracısı tarafından oluşturulan parametreleri doğru nesneye sıralayabilir.

Eklenti sınıfınızı yazmayı tamamladıktan sonra veya AddFromType<> yöntemlerini kullanarak bunu çekİrDEK'e AddFromObject ekleyebilirsiniz.

İpucu

İşlev oluştururken kendinize her zaman "Bu işlevi kullanmak için yapay zekaya nasıl ek yardım verebilirim?" sorusunu sorun. Bu, belirli giriş türlerini kullanmayı (mümkün olduğunda dizelerden kaçınma), açıklamalar ve örnekler sağlamayı içerebilir.

yöntemini kullanarak AddFromObject eklenti ekleme

yöntemi, AddFromObject eklentinin nasıl derlendiğini doğrudan denetlemek istemeniz durumunda eklenti sınıfının bir örneğini doğrudan eklenti koleksiyonuna eklemenize olanak tanır.

Örneğin, sınıfının oluşturucu LightsPlugin ışık listesini gerektirir. Bu durumda, eklenti sınıfının bir örneğini oluşturabilir ve eklenti koleksiyonuna ekleyebilirsiniz.

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

yöntemini kullanarak AddFromType<> eklenti ekleme

yöntemi kullanılırken AddFromType<> çekirdek otomatik olarak bağımlılık ekleme özelliğini kullanarak eklenti sınıfının bir örneğini oluşturur ve eklenti koleksiyonuna ekler.

Oluşturucunuz eklentiye hizmet veya diğer bağımlılıkların eklenip eklenmediğini gerektiriyorsa bu yararlı olur. Örneğin, sınıfımız LightsPlugin bir günlükçü ve ışık listesi yerine buna bir hafif hizmet eklemek isteyebilir.

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

Bağımlılık Ekleme ile, çekirdeği oluşturmadan önce gerekli hizmetleri ve eklentileri çekirdek oluşturucuya ekleyebilirsiniz.

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

İşlev koleksiyonu kullanarak eklenti tanımlama

Daha az yaygın ama yine de kullanışlı olan, işlev koleksiyonunu kullanarak bir eklenti tanımlamaktır. Bu özellikle çalışma zamanında bir dizi işlevden dinamik olarak bir eklenti oluşturmanız gerekiyorsa kullanışlıdır.

Bu işlemi kullanmak için işlev fabrikasını kullanarak eklentiye eklemeden önce tek tek işlevler oluşturmanız gerekir.

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

Bağımlılık Ekleme ile yerel kod eklemeye yönelik ek stratejiler

Bağımlılık Ekleme ile çalışıyorsanız, çekirdekte eklentiler oluşturmak ve eklemek için ek stratejiler alabilirsiniz. Aşağıda Bağımlılık Ekleme'yi kullanarak eklenti eklemeye dair bazı örnekler verilmiştir.

Eklenti koleksiyonu ekleme

İpucu

Eklenti koleksiyonu değişebilir olduğundan her kullanımdan sonra atılması için eklenti koleksiyonunuzu geçici bir hizmet haline getirmenizi öneririz. Her kullanım için yeni bir eklenti koleksiyonu oluşturmak ucuzdur, bu nedenle performansla ilgili bir sorun olmamalıdır.

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

İpucu

Çekirdek makalesinde belirtildiği gibi, çekirdek son derece hafiftir, bu nedenle geçici olarak her kullanım için yeni bir çekirdek oluşturmak bir performans sorunu değildir.

Eklentilerinizi tekil olarak oluşturma

Eklentiler değiştirilebilir değildir, bu nedenle bunları tekil olarak oluşturmak genellikle güvenlidir. Bu, eklenti fabrikasını kullanarak ve sonuçta elde edilen eklentiyi hizmet koleksiyonunuz için ekleyerek yapılabilir.

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

yöntemini kullanarak add_plugin eklenti ekleme

add_plugin yöntemi, çekİrdeğe bir eklenti örneği eklemenize olanak tanır. Aşağıda sınıfını nasıl oluşturabileceğinize LightsPlugin ve çekirdeğine nasıl ekleyebileceğinize ilişkin bir örnek verilmiştir.

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

yöntemini kullanarak createFromObject eklenti ekleme

yöntemi, createFromObject ek açıklamalı yöntemlerle bir Nesneden çekirdek eklentisi oluşturmanıza olanak tanır.

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

Bu eklenti daha sonra bir çekirdekte eklenebilir.

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

İşlevlerin LLM'ye tür şeması döndürmesini sağlama

Şu anda yapay zeka modellerine işlev dönüş türü meta verileri sağlamak için iyi tanımlanmış, endüstri genelinde bir standart yoktur. Böyle bir standart oluşturulana kadar, dönüş türü özelliklerinin adlarının LLM'lerin içeriği hakkında düşünmesi için yetersiz olduğu veya senaryolarınızı modellemek veya geliştirmek için ek bağlam veya işleme yönergelerinin dönüş türüyle ilişkilendirilmesi gerektiği senaryolar için aşağıdaki teknikler göz önünde bulundurulabilir.

Bu tekniklerden herhangi birini kullanmadan önce, LLM'nin dönüş türünü anlamayı geliştirmenin en kolay yolu olduğundan ve belirteç kullanımı açısından da uygun maliyetli olduğundan, dönüş türü özellikleri için daha açıklayıcı adlar sağlamanız önerilir.

İşlev açıklamasında işlev dönüş türü bilgilerini sağlayın

Bu tekniği uygulamak için işlevin açıklama özniteliğine dönüş türü şemasını ekleyin. Şema, aşağıdaki örnekte gösterildiği gibi özellik adlarını, açıklamalarını ve türlerini ayrıntılandırmalıdır:

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)
   {
      ...
   }
}

Bazı modellerin işlev açıklamasının boyutuyla ilgili sınırlamaları olabilir, bu nedenle şemanın kısa tutulması ve yalnızca temel bilgilerin eklenmesi önerilir.

Tür bilgilerinin kritik olmadığı ve belirteç tüketimini en aza indirgemenin öncelik olduğu durumlarda, tam şema yerine, işlevin description özniteliğinde dönüş türünün kısa bir açıklamasını sağlamayı göz önünde bulundurun.

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)
   {
      ...
   }
}

Yukarıda bahsedilen her iki yaklaşımda da dönüş türü şemasının el ile eklenmesi ve dönüş türü her değiştiğinde güncelleştirilmesi gerekir. Bundan kaçınmak için sonraki tekniği göz önünde bulundurun.

İşlev dönüş türü şemasını işlevin dönüş değerinin bir parçası olarak sağlama

Bu teknik hem işlevin dönüş değerini hem de şemasını yalnızca dönüş değeri yerine LLM'ye sağlamayı içerir. Bu, LLM'nin dönüş değerinin özellikleri hakkında mantık edinmek için şemayı kullanmasına olanak tanır.

Bu tekniği uygulamak için otomatik işlev çağırma filtresi oluşturup kaydetmeniz gerekir. Diğer ayrıntılar için Otomatik İşlev Çağırma Filtresi makalesine bakın. Bu filtre, işlevin dönüş değerini hem özgün dönüş değerini hem de şemasını içeren bir özel nesneye sarmalamalıdır. Aşağıda bir örnek verilmiştir:

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

Filtre kayıtlı durumdaysa, artık dönüş türü ve özellikleri için anlam çekirdeği tarafından otomatik olarak ayıklanacak açıklamalar sağlayabilirsiniz:

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

Bu yaklaşım, şema Anlam Çekirdeği tarafından otomatik olarak ayıklandığından, dönüş türü her değiştiğinde dönüş türü şemasını el ile sağlama ve güncelleştirme gereksinimini ortadan kaldırır.

Sonraki adımlar

Eklenti oluşturmayı öğrendiğinize göre artık bunları yapay zeka aracınızla nasıl kullanacağınızı öğrenebilirsiniz. Eklentilerinize eklediğiniz işlevlerin türüne bağlı olarak, izlemeniz gereken farklı desenler vardır. Alma işlevleri için alma işlevlerini kullanma makalesine bakın. Görev otomasyonu işlevleri için , görev otomasyonu işlevlerini kullanma makalesine bakın.