네이티브 코드를 플러그 인으로 추가
기본적으로 지원되지 않는 기능을 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")]
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());
}
}
팁
LLM은 주로 Python 코드에서 학습되므로 함수 이름 및 매개 변수에 snake_case 사용하는 것이 좋습니다(C# 또는 Java를 사용하는 경우에도). 이렇게 하면 AI 에이전트가 함수 및 해당 매개 변수를 더 잘 이해하는 데 도움이 됩니다.
팁
함수는 Kernel
, KernelArguments
, ILoggerFactory
, ILogger
, IAIServiceSelector
, CultureInfo
, IFormatProvider
, CancellationToken
매개 변수로 지정할 수 있으며 이러한 매개 변수는 LLM에 보급되지 않으며 함수가 호출될 때 자동으로 설정됩니다.
명시적 입력 인수 대신 KernelArguments
사용하는 경우 코드는 형식 변환을 수행합니다.
함수에 복잡한 개체가 입력 변수로 있는 경우 의미 체계 커널도 해당 개체에 대한 스키마를 생성하여 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 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;
}
}
참고 항목
이는 "재미있는" 예제이지만 플러그 인의 매개 변수가 얼마나 복잡한지 보여주는 좋은 작업을 수행합니다. 이 단일 경우 정수, 문자열, 부울 및 열거형의 네 가지 속성 형식이 있는 복합 개체가 있습니다. 의미 체계 커널의 값은 이 개체에 대한 스키마를 자동으로 생성하여 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")]
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);
}
}
종속성 주입을 사용하면 커널을 빌드하기 전에 커널 작성기에서 필요한 서비스 및 플러그 인을 추가할 수 있습니다.
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();
함수 반환 유형 스키마를 LLM에 제공하기
현재 AI 모델에 함수 반환 형식 메타데이터를 제공하기 위한 업계 전반의 잘 정의된 표준은 없습니다. 이러한 표준이 설정될 때까지 LLM이 콘텐츠를 추론할 수 있도록 반환 형식 속성의 이름이 부족하거나 시나리오를 모델링하거나 향상시키기 위해 추가 컨텍스트 또는 처리 지침을 반환 형식과 연결해야 하는 시나리오에 대해 다음 기술을 고려할 수 있습니다.
이러한 기술을 사용하기 전에 반환 형식 속성에 대해 보다 설명적인 이름을 제공하는 것이 좋습니다. 이는 반환 형식에 대한 LLM의 이해를 향상시키는 가장 간단한 방법이며 토큰 사용 측면에서 비용 효율적이기 때문에 권장됩니다.
함수 설명에 함수 반환 형식 정보 제공
이 기술을 적용하려면 함수의 설명 특성에 반환 형식 스키마를 포함합니다. 스키마는 다음 예제와 같이 속성 이름, 설명 및 형식을 자세히 설명해야 합니다.
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)
{
...
}
}
일부 모델에는 함수 설명의 크기에 제한이 있을 수 있으므로 스키마를 간결하게 유지하고 필수 정보만 포함하는 것이 좋습니다.
형식 정보가 중요하지 않고 토큰 사용 최소화가 우선 순위인 경우 전체 스키마 대신 함수의 설명 특성에 반환 형식에 대한 간략한 설명을 제공하는 것이 좋습니다.
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)
{
...
}
}
위에서 언급한 두 방법 모두 반환 형식 스키마를 수동으로 추가하고 반환 형식이 변경될 때마다 업데이트해야 합니다. 이를 방지하려면 다음 기술을 고려하세요.
함수 반환 형식 스키마를 함수 반환 값의 일부로 제공
이 기술에는 함수의 반환 값과 해당 스키마를 반환 값이 아닌 LLM에 제공하는 작업이 포함됩니다. 이렇게 하면 LLM에서 스키마를 사용하여 반환 값의 속성을 추론할 수 있습니다.
이 기술을 구현하려면 자동 함수 호출 필터를 만들고 등록해야 합니다. 자세한 내용은 자동 함수 호출 필터 문서를 참조하세요. 이 필터는 원래 반환 값과 해당 스키마를 모두 포함하는 사용자 지정 개체에서 함수의 반환 값을 래핑해야 합니다. 다음은 예제입니다.
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());
이제 필터를 등록하면 반환 형식 및 해당 속성에 대한 설명을 제공할 수 있습니다. 그러면 의미 체계 커널에서 자동으로 추출됩니다.
[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; }
}
이 방법을 사용하면 의미 체계 커널에서 스키마를 자동으로 추출하므로 반환 형식이 변경될 때마다 반환 형식 스키마를 수동으로 제공하고 업데이트할 필요가 없습니다.
다음 단계
이제 플러그 인을 만드는 방법을 알게 되었으므로 이제 AI 에이전트와 함께 사용하는 방법을 알아볼 수 있습니다. 플러그 인에 추가한 함수 유형에 따라 따라 따라 다른 패턴을 따라야 합니다. 검색 함수의 경우 검색 함수 사용 문서를 참조 하세요 . 작업 자동화 함수의 경우 작업 자동화 함수 사용 문서를 참조 하세요 .