將機器碼新增為外掛程式
提供 AI 代理程式功能的最簡單方式是將原生程式代碼包裝至外掛程式。 這可讓您利用您身為應用程式開發人員的現有技能,擴充 AI 代理程式的功能。
在幕後,Semantic Kernel 接著會使用您提供的描述以及反映,以語意方式描述 AI 代理程式的外掛程式。 這可讓 AI 代理程式瞭解外掛程式的功能,以及如何與其互動。
提供 LLM 的正確資訊
撰寫外掛程式時,您必須提供 AI 代理程式正確的資訊,以瞭解外掛程式及其功能的功能。 這包括:
- 外掛程式的名稱
- 函式的名稱
- 函式的描述
- 函式的參數
- 參數的架構
Semantic Kernel 的值是,它可以從程式碼本身自動產生大部分的資訊。 身為開發人員,這隻表示您必須提供函式和參數的語意描述,讓 AI 代理程式可以了解它們。 不過,如果您正確批註並標註程式代碼,您可能已經手頭有此資訊。
接下來,我們將逐步解說兩種不同的方式,為您的 AI 代理程式提供原生程式代碼,以及如何提供此語意資訊。
使用類別定義外掛程式
建立原生外掛程式最簡單的方式是從 類別開始,然後新增以 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 代理程式進一步瞭解函式及其參數。
如果您的函式具有複雜物件做為輸入變數,Semantic Kernel 也會產生該對象的架構,並將它傳遞至 AI 代理程式。 與函式類似,您應該為 AI 不明顯的屬性提供 Description
註釋。 以下是 類別和Brightness
列舉的定義LightState
。
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;
}
}
注意
雖然這是「有趣的」範例,但它會執行良好作業,顯示外掛程式的參數有多複雜。 在此單一案例中,我們有一個具有 四 種不同類型屬性的複雜物件:整數、字串、布爾值和列舉。 語意核心的值是,它可以自動產生此對象的架構,並將其傳遞至 AI 代理程式,並將 AI 代理程式所產生的參數封送處理至正確的物件。
完成撰寫外掛程式類別之後,您可以使用 或 AddFromObject
方法將其新增至核心AddFromType<>
。
提示
建立函式時,請一律問自己「如何提供 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);
}
}
使用相依性插入,您可以在建置核心之前,將必要的服務和外掛程式新增至核心產生器。
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 代理程式搭配使用。 根據您新增至外掛程式的函式類型而定,您應該遵循不同的模式。 如需擷取函式,請參閱 使用擷取函 式一文。 如需工作自動化函式,請參閱 使用工作自動化函 式一文。