플러그 인이란?
플러그 인은 의미 체계 커널의 핵심 구성 요소입니다. Microsoft 365의 ChatGPT 또는 Copilot 확장에서 플러그 인을 이미 사용한 경우 이미 익숙한 것입니다. 플러그 인을 사용하면 기존 API를 AI에서 사용할 수 있는 컬렉션에 캡슐화할 수 있습니다. 이렇게 하면 AI가 달리 수행할 수 없는 작업을 수행할 수 있는 기능을 제공할 수 있습니다.
이면에서 의미 체계 커널은 LLM이 계획을 수행하고 API를 호출할 수 있도록 대부분의 최신 LLM의 기본 기능인 함수 호출을 활용합니다. 함수 호출을 사용하면 LLM이 특정 함수를 요청(즉, 호출)할 수 있습니다. 그런 다음 의미 체계 커널은 요청을 코드베이스의 적절한 함수로 마샬링하고 LLM이 최종 응답을 생성할 수 있도록 결과를 LLM에 다시 반환합니다.
모든 AI SDK에 플러그 인과 유사한 개념이 있는 것은 아닙니다(대부분 함수 또는 도구만 포함). 그러나 엔터프라이즈 시나리오에서 플러그 인은 엔터프라이즈 개발자가 이미 서비스 및 API를 개발하는 방법을 반영하는 기능 집합을 캡슐화하기 때문에 유용합니다. 또한 플러그 인은 종속성 주입으로 잘 재생됩니다. 플러그 인의 생성자 내에서 플러그 인(예: 데이터베이스 연결, HTTP 클라이언트 등)의 작업을 수행하는 데 필요한 서비스를 삽입할 수 있습니다. 플러그 인이 부족한 다른 SDK에서는 이 작업을 수행하기가 어렵습니다.
플러그 인의 해부학
개략적으로 플러그 인은 AI 앱 및 서비스에 노출될 수 있는 함수 그룹입니다. 그런 다음, 플러그 인 내의 함수를 AI 애플리케이션에서 오케스트레이션하여 사용자 요청을 수행할 수 있습니다. 의미 체계 커널 내에서 함수 호출을 사용하여 이러한 함수를 자동으로 호출할 수 있습니다.
참고 항목
다른 플랫폼에서는 함수를 "도구" 또는 "작업"이라고도 합니다. 의미 체계 커널에서는 일반적으로 코드베이스에서 네이티브 함수로 정의되므로 "functions"라는 용어를 사용합니다.
그러나 함수를 제공하는 것만으로는 플러그 인을 만들기에 충분하지 않습니다. 함수 호출을 사용하여 자동 오케스트레이션에 전원을 공급하려면 플러그 인이 동작 방식을 의미적으로 설명하는 세부 정보도 제공해야 합니다. 함수의 입력, 출력 및 부작용의 모든 내용은 AI가 이해할 수 있는 방식으로 설명해야 합니다. 그렇지 않으면 AI가 함수를 올바르게 호출하지 않습니다.
예를 들어 오른쪽의 샘플 WriterPlugin
플러그 인에는 각 함수가 수행하는 작업을 설명하는 의미 체계 설명이 포함된 함수가 있습니다. 그런 다음 LLM은 이러한 설명을 사용하여 사용자의 요청을 충족하기 위해 호출할 최상의 함수를 선택할 수 있습니다.
오른쪽 그림에서 LLM은 제공된 의미 체계 설명 덕분에 사용자가 요청하는 것을 만족시키기 위해 함수 및 StoryGen
함수를 호출 ShortPoem
할 가능성이 높습니다.
다양한 유형의 플러그 인 가져오기
네이티브 코드를 사용하거나 OpenAPI 사양을 사용하는 두 가지 기본 방법으로 플러그 인을 의미 체계 커널로 가져올 수 있습니다. 전자를 사용하면 기존 코드베이스에서 이미 가지고 있는 종속성 및 서비스를 활용할 수 있는 플러그 인을 작성할 수 있습니다. 후자를 사용하면 여러 프로그래밍 언어 및 플랫폼에서 공유할 수 있는 OpenAPI 사양에서 플러그 인을 가져올 수 있습니다.
아래에서는 네이티브 플러그 인을 가져오고 사용하는 간단한 예제를 제공합니다. 이러한 다양한 유형의 플러그 인을 가져오는 방법에 대한 자세한 내용은 다음 문서를 참조하세요.
팁
시작할 때 네이티브 코드 플러그 인을 사용하는 것이 좋습니다. 애플리케이션이 완성되고 플랫폼 간 팀에서 작업할 때 OpenAPI 사양을 사용하여 다양한 프로그래밍 언어 및 플랫폼에서 플러그 인을 공유하는 것이 좋습니다.
다양한 유형의 플러그 인 함수
플러그 인 내에는 일반적으로 RAG(검색 보강 생성)에 대한 데이터를 검색하는 함수와 작업을 자동화하는 두 가지 유형의 함수가 있습니다. 각 형식은 기능적으로 동일하지만 일반적으로 의미 체계 커널을 사용하는 애플리케이션 내에서 다르게 사용됩니다.
예를 들어 검색 함수를 사용하면 전략을 사용하여 성능을 향상시킬 수 있습니다(예: 요약을 위해 저렴한 중간 모델 캐싱 및 사용). 태스크 자동화 함수를 사용하는 반면, 휴먼 인 더 루프 승인 프로세스를 구현하여 태스크가 올바르게 완료되도록 할 수 있습니다.
다양한 유형의 플러그 인 함수에 대해 자세히 알아보려면 다음 문서를 참조하세요.
플러그 인 시작
의미 체계 커널 내에서 플러그 인을 사용하는 것은 항상 3단계 프로세스입니다.
아래에서는 의미 체계 커널 내에서 플러그 인을 사용하는 방법에 대한 개략적인 예제를 제공합니다. 플러그 인을 만들고 사용하는 방법에 대한 자세한 내용은 위의 링크를 참조하세요.
1) 플러그 인 정의
플러그 인을 만드는 가장 쉬운 방법은 클래스를 정의하고 해당 메서드 KernelFunction
에 특성을 주석으로 추가하는 것입니다. 여기서 의미 체계 커널은 이것이 AI에서 호출하거나 프롬프트에서 참조할 수 있는 함수임을 알 수 있습니다.
OpenAPI 사양에서 플러그 인을 가져올 수도 있습니다.
아래에서는 조명 상태를 검색하고 상태를 변경할 수 있는 플러그 인을 만듭니다.
팁
대부분의 LLM은 함수 호출을 위해 Python으로 학습되었으므로 C# 또는 Java SDK를 사용하는 경우에도 함수 이름 및 속성 이름에 뱀 대/소문자를 사용하는 것이 좋습니다.
using System.ComponentModel;
using Microsoft.SemanticKernel;
public class LightsPlugin
{
// Mock data for the lights
private readonly List<LightModel> lights = new()
{
new LightModel { Id = 1, Name = "Table Lamp", IsOn = false, Brightness = 100, Hex = "FF0000" },
new LightModel { Id = 2, Name = "Porch light", IsOn = false, Brightness = 50, Hex = "00FF00" },
new LightModel { Id = 3, Name = "Chandelier", IsOn = true, Brightness = 75, Hex = "0000FF" }
};
[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("get_state")]
[Description("Gets the state of a particular light")]
[return: Description("The state of the light")]
public async Task<LightModel?> GetStateAsync([Description("The ID of the light")] int id)
{
// Get the state of the light with the specified ID
return lights.FirstOrDefault(light => light.Id == id);
}
[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(int id, LightModel LightModel)
{
var light = lights.FirstOrDefault(light => light.Id == id);
if (light == null)
{
return null;
}
// Update the light with the new state
light.IsOn = LightModel.IsOn;
light.Brightness = LightModel.Brightness;
light.Hex = LightModel.Hex;
return light;
}
}
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 byte? Brightness { get; set; }
[JsonPropertyName("hex")]
public string? Hex { get; set; }
}
from typing import TypedDict, Annotated
class LightModel(TypedDict):
id: int
name: str
is_on: bool | None
brightness: int | None
hex: str | None
class LightsPlugin:
lights: list[LightModel] = [
{"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"},
]
@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 get_state(
self,
id: Annotated[int, "The ID of the light"]
) -> Annotated[LightModel | None], "The state of the light"]:
"""Gets the state of a particular light."""
for light in self.lights:
if light["id"] == id:
return light
return None
@kernel_function
async def change_state(
self,
id: Annotated[int, "The ID of the light"],
new_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"] == id:
light["is_on"] = new_state.get("is_on", light["is_on"])
light["brightness"] = new_state.get("brightness", light["brightness"])
light["hex"] = new_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));
lights.put(2, new LightModel(2, "Porch light", false));
lights.put(3, new LightModel(3, "Chandelier", true));
}
@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 = "id", description = "The ID of the light to change") int id,
@KernelFunctionParameter(name = "isOn", description = "The new state of the light") boolean isOn) {
System.out.println("Changing light " + id + " " + isOn);
if (!lights.containsKey(id)) {
throw new IllegalArgumentException("Light not found");
}
lights.get(id).setIsOn(isOn);
return lights.get(id);
}
}
함수, 반환 값 및 매개 변수에 대한 설명을 제공합니다. 이는 AI가 함수의 기능 및 사용 방법을 이해하는 데 중요합니다.
팁
AI에서 함수를 호출하는 데 문제가 있는 경우 함수에 대한 자세한 설명을 제공하는 것을 두려워하지 마세요. 몇 가지 예제, 함수를 사용해야 하는 경우(사용하지 않는 경우) 권장 사항 및 필요한 매개 변수를 가져올 위치에 대한 지침이 모두 유용할 수 있습니다.
2) 커널에 플러그 인 추가
플러그 인을 정의한 후에는 플러그 인의 새 인스턴스를 만들고 커널의 플러그 인 컬렉션에 추가하여 커널에 추가할 수 있습니다.
이 예제에서는 메서드를 사용하여 클래스를 플러그 인 AddFromType
으로 추가하는 가장 쉬운 방법을 보여 줍니다. 플러그 인을 추가하는 다른 방법에 대해 알아보려면 네이 티브 플러그 인 추가 문서를 참조하세요.
var builder = new KernelBuilder();
builder.Plugins.AddFromType<LightsPlugin>("Lights")
Kernel kernel = builder.Build();
kernel = Kernel()
kernel.add_plugin(
LightsPlugin(),
plugin_name="Lights",
)
// 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();
3) 플러그 인의 함수 호출
마지막으로 함수 호출을 사용하여 AI가 플러그 인의 함수를 호출하게 할 수 있습니다. 다음은 조명을 켜기 위해 함수를 호출하기 전에 플러그 인에서 Lights
함수를 호출 get_lights
change_state
하도록 AI를 동축하는 방법을 보여 주는 예제입니다.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
// Create a kernel with Azure OpenAI chat completion
var builder = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(modelId, endpoint, apiKey);
// Build the kernel
Kernel kernel = builder.Build();
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
// Add a plugin (the LightsPlugin class is defined below)
kernel.Plugins.AddFromType<LightsPlugin>("Lights");
// Enable planning
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
// Create a history store the conversation
var history = new ChatHistory();
history.AddUserMessage("Please turn on the lamp");
// Get the response from the AI
var result = await chatCompletionService.GetChatMessageContentAsync(
history,
executionSettings: openAIPromptExecutionSettings,
kernel: kernel);
// Print the results
Console.WriteLine("Assistant > " + result);
// Add the message from the agent to the chat history
history.AddAssistantMessage(result);
import asyncio
from semantic_kernel import Kernel
from semantic_kernel.functions import kernel_function
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
AzureChatPromptExecutionSettings,
)
async def main():
# Initialize the kernel
kernel = Kernel()
# Add Azure OpenAI chat completion
chat_completion = AzureChatCompletion(
deployment_name="your_models_deployment_name",
api_key="your_api_key",
base_url="your_base_url",
)
kernel.add_service(chat_completion)
# Add a plugin (the LightsPlugin class is defined below)
kernel.add_plugin(
LightsPlugin(),
plugin_name="Lights",
)
# Enable planning
execution_settings = AzureChatPromptExecutionSettings()
execution_settings.function_call_behavior = FunctionChoiceBehavior.Auto()
# Create a history of the conversation
history = ChatHistory()
history.add_message("Please turn on the lamp")
# Get the response from the AI
result = await chat_completion.get_chat_message_content(
chat_history=history,
settings=execution_settings,
kernel=kernel,
)
# Print the results
print("Assistant > " + str(result))
# Add the message from the agent to the chat history
history.add_message(result)
# Run the main function
if __name__ == "__main__":
asyncio.run(main())
// Enable planning
InvocationContext invocationContext = new InvocationContext.Builder()
.withReturnMode(InvocationReturnMode.LAST_MESSAGE_ONLY)
.withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true))
.build();
// Create a history to store the conversation
ChatHistory history = new ChatHistory();
history.addUserMessage("Turn on light 2");
List<ChatMessageContent<?>> results = chatCompletionService
.getChatMessageContentsAsync(history, kernel, invocationContext)
.block();
System.out.println("Assistant > " + results.get(0));
위의 코드를 사용하면 다음과 같은 응답을 받아야 합니다.
역할 | 메시지 |
---|---|
🔵사용자 | 램프를 켜세요. |
🔴도우미(함수 호출) | Lights.get_lights() |
🟢도구 | [{ "id": 1, "name": "Table Lamp", "isOn": false, "brightness": 100, "hex": "FF0000" }, { "id": 2, "name": "Porch light", "isOn": false, "brightness": 50, "hex": "00FF00" }, { "id": 3, "name": "Chandelier", "isOn": true, "brightness": 75, "hex": "0000FF" }] |
🔴도우미(함수 호출) | Lights.change_state(1, { "isOn": true }) |
🟢도구 | { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" } |
🔴도우미 | 램프가 켜져 있습니다. |
팁
플러그 인 함수를 직접 호출할 수 있지만 AI가 호출할 함수를 결정해야 하기 때문에 권장되지 않습니다. 호출되는 함수를 명시적으로 제어해야 하는 경우 플러그 인 대신 코드베이스에서 표준 메서드를 사용하는 것이 좋습니다.