네이티브 코드를 플러그 인으로 추가
기본적으로 지원되지 않는 기능을 AI 에이전트에 제공하는 가장 쉬운 방법은 네이티브 코드를 플러그 인으로 래핑하는 것입니다. 이를 통해 앱 개발자로서 기존 기술을 활용하여 AI 에이전트의 기능을 확장할 수 있습니다.
그런 다음, 의미 체계 커널은 리플렉션과 함께 제공하는 설명을 사용하여 AI 에이전트에 대한 플러그 인을 의미적으로 설명합니다. 이를 통해 AI 에이전트는 플러그 인의 기능과 상호 작용하는 방법을 이해할 수 있습니다.
LLM에 올바른 정보 제공
플러그 인을 작성할 때 플러그 인의 기능과 해당 기능을 이해하기 위한 올바른 정보를 AI 에이전트에 제공해야 합니다. 다음 내용이 포함됩니다.
- 플러그 인의 이름
- 함수의 이름
- 함수에 대한 설명
- 함수의 매개 변수
- 매개 변수의 스키마
의미 체계 커널의 값은 코드 자체에서 이 정보의 대부분을 자동으로 생성할 수 있다는 것입니다. 개발자는 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 에이전트가 함수 및 해당 매개 변수를 더 잘 이해하는 데 도움이 됩니다.
함수에 복잡한 개체가 입력 변수로 있는 경우 의미 체계 커널도 해당 개체에 대한 스키마를 생성하여 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;
}
}
참고 항목
이는 "재미있는" 예제이지만 플러그 인의 매개 변수가 얼마나 복잡한지 보여주는 좋은 작업을 수행합니다. 이 단일 경우 정수, 문자열, 부울 및 열거형의 네 가지 속성 형식이 있는 복합 개체가 있습니다. 의미 체계 커널의 값은 이 개체에 대한 스키마를 자동으로 생성하여 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 에이전트와 함께 사용하는 방법을 알아볼 수 있습니다. 플러그 인에 추가한 함수 유형에 따라 따라 따라 다른 패턴을 따라야 합니다. 검색 함수의 경우 검색 함수 사용 문서를 참조 하세요 . 작업 자동화 함수의 경우 작업 자동화 함수 사용 문서를 참조 하세요 .