Jegyzet
Az oldalhoz való hozzáférés engedélyezést igényel. Próbálhatod be jelentkezni vagy könyvtárat váltani.
Az oldalhoz való hozzáférés engedélyezést igényel. Megpróbálhatod a könyvtár váltását.
Az AI-ügynökök natívan nem támogatott képességeinek biztosításának legegyszerűbb módja a natív kód beépülő modulba való burkolása. Ez lehetővé teszi, hogy alkalmazásfejlesztőként meglévő készségeit kihasználva bővítse az AI-ügynökök képességeit.
A színfalak mögött a Szemantic Kernel ezután az Ön által megadott leírásokat használja a tükröződés mellett az AI-ügynök beépülő moduljának szemantikai leírására. Ez lehetővé teszi, hogy az AI-ügynök megértse a beépülő modul képességeit és a vele való interakciót.
Az LLM megfelelő információval való ellátása
Beépülő modul létrehozásakor meg kell adnia az AI-ügynöknek a megfelelő információkat a beépülő modul képességeinek és funkcióinak megértéséhez. Ide tartoznak az alábbiak:
- A beépülő modul neve
- A függvények nevei
- A függvények leírása
- A függvények paraméterei
- A paraméterek sémája
- A visszatérési érték sémája
A Szemantic Kernel értéke, hogy automatikusan generálja a legtöbb információt magából a kódból. Fejlesztőként ez csak azt jelenti, hogy meg kell adnia a függvények és paraméterek szemantikai leírását, hogy az AI-ügynök megértse őket. Ha azonban megfelelően megjegyzést fűz a kódhoz, és széljegyzetet fűz hozzá, valószínűleg már rendelkezik ezekkel az információkkal.
Az alábbiakban bemutatjuk az AI-ügynök natív kóddal való biztosításának két különböző módját, valamint azt, hogy hogyan adhatjuk meg ezeket a szemantikai információkat.
Beépülő modul definiálása osztály használatával
Natív beépülő modul létrehozásának legegyszerűbb módja az, ha egy osztályt használ, majd hozzáadja az KernelFunction attribútummal kiegészített metódusokat. Azt is javasoljuk, hogy széleskörűen használja a Description megjegyzést, hogy biztosítsa az AI-ügynök számára a függvény megértéséhez szükséges információkat.
Tipp.
Az alábbiak LightsPlugin az itt definiált adatokat LightModel használják.
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());
}
}
Tipp.
Mivel az LLM-ek túlnyomórészt Python-kódon vannak betanítve, a függvénynevekhez és paraméterekhez ajánlott snake_case használni (még akkor is, ha C#-ot vagy Java-t használ). Ez segít az AI-ügynöknek jobban megérteni a függvényt és annak paramétereit.
Tipp.
A függvények megadhatnak Kernel, KernelArguments, ILoggerFactory, ILogger, IAIServiceSelector, CultureInfo, IFormatProvider, CancellationToken paraméterként, és ezek nem lesznek meghirdetve az LLM-ben, és a függvény meghívásakor automatikusan be lesz állítva.
Ha explicit bemeneti argumentumok helyett KernelArguments támaszkodik, akkor a kód felel a típuskonverziók végrehajtásáért.
Ha a függvény bemeneti változóként összetett objektummal rendelkezik, a Szemantikus Kernel is létrehoz egy sémát az objektumhoz, és átadja az AI-ügynöknek. A függvényekhez hasonlóan széljegyzeteket kell megadnia Description az AI számára nem nyilvánvaló tulajdonságokhoz. Az alábbiakban a LightState osztály és a Brightness enum definíciója látható.
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;
}
}
Feljegyzés
Bár ez egy "szórakoztató" példa, jó munkát végez, amely megmutatja, mennyire összetett egy beépülő modul paraméterei. Az adott esetben egy összetett objektumunk négy különböző tulajdonsággal rendelkezik: egész szám, sztring, logikai érték és enumeráció. A szemantikus kernel értéke az, hogy automatikusan létrehozhatja az objektum sémáját, és átadhatja az AI-ügynöknek, és a megfelelő objektumba továbbítja az AI-ügynök által létrehozott paramétereket.
Miután végzett a beépülő modulosztály szerkesztésével, hozzáadhatja a kernelhez a AddFromType<> vagy AddFromObject metódusok használatával.
Miután végzett a beépülő modulosztály szerkesztésével, hozzáadhatja azt a kernelhez a add_plugin metódussal.
Miután végzett a beépülő modulosztály szerkesztésével, hozzáadhatja a kernelhez a AddFromType<> vagy AddFromObject metódusok használatával.
Tipp.
Függvény létrehozásakor mindig tegye fel magának a kérdést: "Hogyan adhatok további segítséget az AI-nek a függvény használatához?" Ez magában foglalhatja adott bemeneti típusok használatát (lehetőség szerint sztringek elkerülését), leírások és példák megadását.
Plugin hozzáadása a AddFromObject eljárással
A AddFromObject módszer lehetővé teszi, hogy a beépülő modulosztály egy példányát közvetlenül a beépülő modul gyűjteményéhez adja hozzá, ha közvetlenül szabályozni szeretné a beépülő modul létrehozásának módját.
Az osztály konstruktorának LightsPlugin például szüksége van a fények listájára. Ebben az esetben létrehozhatja a beépülő modulosztály egy példányát, és hozzáadhatja a beépülő modulgyűjteményhez.
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));
Plugin hozzáadása a AddFromType<> eljárással
A metódus használatakor a AddFromType<> kernel automatikusan függőséginjektálással hozza létre a beépülő modulosztály egy példányát, és hozzáadja a beépülő modulgyűjteményhez.
Ez akkor hasznos, ha a konstruktorhoz szolgáltatások vagy egyéb függőségek injektálása szükséges a beépülő modulba. Előfordulhat például, hogy a LightsPlugin osztályba a fények listája helyett egy naplózót és egy fényszolgáltatást kell injectálni.
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);
}
}
A Függőséginjektálással a kernel létrehozása előtt hozzáadhatja a szükséges szolgáltatásokat és beépülő modulokat a kernelszerkesztőhöz.
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();
Beépülő modul definiálása függvénygyűjtemény használatával
Kevésbé gyakori, de még mindig hasznos, ha függvények gyűjteményével definiál egy beépülő modult. Ez különösen akkor hasznos, ha futásidőben dinamikusan kell létrehoznia egy beépülő modult egy függvénykészletből.
A folyamat használatához a függvény-előállítót kell használnia az egyes függvények létrehozásához, mielőtt hozzáadja őket a beépülő modulhoz.
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"
)
]);
További stratégiák natív kód hozzáadásához függőséginjektálással
Ha függőséginjektálással dolgozik, további stratégiákkal hozhat létre és adhat hozzá beépülő modulokat a kernelhez. Az alábbiakban néhány példát mutatunk be arra, hogyan adhat hozzá beépülő modult a Függőséginjektálás használatával.
Beépülő modulgyűjtemény injektálása
Tipp.
Javasoljuk, hogy a beépülő modulgyűjteményt tegye átmeneti szolgáltatássá, hogy minden használat után megsemmisüljön, mivel a beépülő modulgyűjtemény módosítható. Egy új beépülő modul gyűjtemény létrehozása minden alkalomra olcsó, így nem kell aggódni a teljesítmény miatt.
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);
});
Tipp.
Ahogy a kernelről szóló cikkben említettük, a kernel rendkívül egyszerű, ezért nem jelent teljesítményproblémát, ha minden egyes használatra létrehozunk egy új kernelt átmenetiként.
Készítse el a beépülő modulokat egyedikként
A beépülő modulok nem módosíthatók, ezért általában biztonságosan hozhatók létre önállóként. Ezt úgy teheti meg, hogy a beépülő modul-előállítót használja, és hozzáadja az eredményként kapott beépülő modult a szolgáltatásgyűjteményhez.
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);
});
Plugin hozzáadása a add_plugin eljárással
A add_plugin metódus lehetővé teszi egy beépülő modulpéldány hozzáadását a kernelhez. Az alábbi példa bemutatja, hogyan hozhatja létre és adhatja hozzá az LightsPlugin osztályt a kernelhez.
# 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)
Plugin hozzáadása a createFromObject eljárással
A createFromObject metódus lehetővé teszi egy kernel beépülő modul összeállítását egy jegyzetekkel ellátott metódusokkal rendelkező objektumból.
// Import the LightsPlugin
KernelPlugin lightPlugin = KernelPluginFactory.createFromObject(new LightsPlugin(),
"LightsPlugin");
Ez a beépülő modul ezután hozzáadható egy kernelhez.
// Create a kernel with Azure OpenAI chat completion and plugin
Kernel kernel = Kernel.builder()
.withAIService(ChatCompletionService.class, chatCompletionService)
.withPlugin(lightPlugin)
.build();
A függvények megadása típussémát ad vissza az LLM-nek
Jelenleg nincs jól definiált, iparági szintű szabvány a függvények visszatérési típusú metaadatainak AI-modellekhez való biztosítására. Az ilyen szabvány létrejöttéig a következő technikákat lehet figyelembe venni olyan helyzetekben, ahol a visszatérési típusú tulajdonságok neve nem elegendő az LLM-ek számára a tartalom okának megállapításához, vagy ha a forgatókönyvek modellezéséhez vagy továbbfejlesztéséhez további kontextust vagy kezelési utasításokat kell társítani a visszatérési típushoz.
Ezen technikák alkalmazása előtt célszerű leíróbb neveket megadni a visszatérési típus tulajdonságaihoz, mivel ez a legegyszerűbb módja annak, hogy az LLM jobban megértse a visszatérési típust, és költséghatékony a tokenhasználat szempontjából is.
Függvény visszatérési típusának megadása a függvény leírásában
A technika alkalmazásához adja meg a visszatérési típus sémáját a függvény leírási attribútumában. A sémának részleteznie kell a tulajdonságneveket, a leírásokat és a típusokat, ahogy az a következő példában látható:
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)
{
...
}
}
Egyes modellek a függvény leírásának méretére vonatkozóan korlátozottak lehetnek, ezért célszerű tömören tartani a sémát, és csak a lényeges információkat tartalmazni.
Azokban az esetekben, amikor a típusinformációk nem kritikusak, és a tokenhasználat minimalizálása prioritást élvez, fontolja meg a függvény leírási attribútumában a visszatérési típus rövid leírását a teljes séma helyett.
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)
{
...
}
}
Mindkét fent említett megközelítéshez manuálisan kell hozzáadni a visszatérési típus sémáját, és frissíteni kell minden alkalommal, amikor a visszatérési típus megváltozik. Ennek elkerülése érdekében fontolja meg a következő technikát.
Függvény visszatérési típusséma megadása a függvény visszatérési értékének részeként
Ez a technika magában foglalja a függvény visszatérési értékét és sémáját is az LLM-nek, nem csak a visszatérési értéknek. Ez lehetővé teszi, hogy az LLM a sémát használva értelmezze a visszatérési érték tulajdonságait.
A technika implementálásához létre kell hoznia és regisztrálnia kell egy automatikus függvényhívási szűrőt. További részletekért lásd az automatikus függvényhívási szűrő cikket. Ennek a szűrőnek a függvény visszatérési értékét egy egyéni objektumba kell burkolnia, amely az eredeti visszatérési értéket és annak sémáját is tartalmazza. Az alábbiakban egy példa látható:
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());
A szűrő regisztrálásával mostantól leírásokat adhat meg a visszatérési típusról és tulajdonságairól, amelyeket a Szemantic Kernel automatikusan kinyer:
[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; }
}
Ez a módszer szükségtelenné teszi a visszatérési típus sémájának manuális megadását és frissítését minden alkalommal, amikor a visszatérési típus megváltozik, mivel a sémát a szemantikai kernel automatikusan kinyeri.
Bővebb információ a függvényekről
Amikor beépülő modult hoz létre a Pythonban, további információkat adhat meg a kernel_function dekorátor funkcióiról. Ezeket az információkat az AI-ügynök fogja használni a függvények jobb megértéséhez.
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
A fenti minta bemutatja, hogyan bírálhatja felül a függvény nevét, és hogyan adhat meg leírást a függvényhez. Alapértelmezés szerint a függvény neve a függvény neve, a leírás pedig üres. Ha a függvény neve elég leíró, nincs szükség leírásra, így tokeneket takaríthat meg. Ha azonban a függvény viselkedése nem egyértelmű a névből, meg kell adnia az AI leírását.
Mivel az LLM-eket túlnyomórészt Python-kódon tanítják be, javasoljuk, hogy olyan függvényneveket használjon, amelyek megfelelnek a Python nevezéktani konvencióknak, ami azt jelenti, hogy ritkán kell módosítania a függvényneveket, ha betartja a konvenciókat a Python-kódjában.
Következő lépések
Most, hogy már tudja, hogyan hozhat létre beépülő modult, megtanulhatja, hogyan használhatja őket az AI-ügynökével. A beépülő modulokhoz hozzáadott függvények típusától függően különböző mintákat kell követnie. A lekérési függvényekkel kapcsolatban tekintse meg a lekérési függvények használatát ismertető cikket. Feladatautomatizálási függvények esetén tekintse meg a feladatautomatizálási függvények használatát ismertető cikket.