플러그인(Plugin)이란?
플러그인(Plugin)이란?
플러그인은 Semantic Kernel의 핵심 컴포넌트예요. ChatGPT나 Microsoft 365의 Copilot 확장 기능에서 플러그인을 써 봤다면 이미 익숙하실 거예요. 플러그인을 쓰면 기존 API를 AI가 사용할 수 있는 모음(collection)으로 묶을 수 있어요. 그렇게 해서 AI가 원래는 하지 못했던 동작을 수행할 능력을 얻게 되죠.
출처: 공식문서
플러그인의 배후: 함수 호출
뒤에서는 Semantic Kernel이 대부분 최신 LLM의 기본 기능인 함수 호출(function calling) 을 활용해 플래닝을 수행하고 여러분의 API를 호출해요. 함수 호출을 쓰면 LLM이 특정 함수를 요청(즉 호출)할 수 있어요. 그러면 Semantic Kernel이 그 요청을 여러분 코드베이스의 적절한 함수로 전달하고, 그 결과를 LLM에 돌려줘서 LLM이 최종 응답을 생성할 수 있게 해요.
일부 AI SDK에는 플러그인과 유사한 개념이 없어요(대부분 함수나 도구만 있을 뿐). 하지만 엔터프라이즈 시나리오에서 플러그인은 귀중한데, 그 이유는 플러그인이 엔터프라이즈 개발자가 서비스와 API를 개발하는 방식과 유사한 기능 집합을 캡슐화하기 때문이에요. 플러그인은 의존성 주입과도 잘 어울려요. 플러그인의 생성자 안에서 플러그인의 작업 수행에 필요한 서비스(예: 데이터베이스 연결, HTTP 클라이언트)를 주입할 수 있어요. 플러그인이 없는 다른 SDK로는 이걸 달성하기 어렵죠.
플러그인의 구조
높은 수준에서 보면 플러그인은 AI 앱과 서비스에 노출될 수 있는 함수들의 그룹이에요. 플러그인 안의 함수들은 AI 애플리케이션이 사용자 요청을 처리하기 위해 오케스트레이션할 수 있고, Semantic Kernel 안에서는 함수 호출로 자동 호출할 수 있어요.
다른 플랫폼에서는 함수를 "도구(tools)"나 "동작(actions)"이라고 부르기도 해요. Semantic Kernel에서는 코드베이스에 보통 네이티브 함수로 정의되기 때문에 "함수(functions)"라는 용어를 사용해요.
다만 함수만 제공한다고 플러그인이 되는 건 아니에요. 함수 호출로 자동 오케스트레이션하려면, 플러그인은 자신이 어떻게 동작하는지 의미적으로 설명하는 세부 정보도 제공해야 해요. 함수의 입력·출력·부수 효과(side effects)까지 전부 AI가 이해할 수 있는 방식으로 설명해야 하죠. 그렇지 않으면 AI가 함수를 제대로 호출하지 못해요.
예를 들어 샘플 WriterPlugin은 각 함수가 무엇을 하는지 설명하는 의미적 설명을 갖고 있어요. LLM은 이 설명들을 보고 사용자 요청을 처리하기에 가장 좋은 함수를 고를 수 있어요.
플러그인 가져오기 유형
Semantic Kernel에 플러그인을 가져오는 방법은 크게 세 가지예요. 네이티브 코드, OpenAPI 규격, MCP 서버가 그것이에요. 전자는 이미 갖고 있는 의존성과 서비스를 활용해 여러분의 기존 코드베이스에서 플러그인을 작성하는 방식이고, 후자 둘은 프로그래밍 언어와 플랫폼을 넘어 공유할 수 있는 OpenAPI 규격이나 MCP 서버에서 플러그인을 가져오는 방식이에요.
처음 시작할 때는 네이티브 코드 플러그인을 권장해요. 애플리케이션이 성숙해지고 크로스 플랫폼 팀과 협업하게 되면 OpenAPI 규격으로 플러그인을 여러 언어·플랫폼에 공유하는 걸 고려해 볼 만해요. 또 커널 인스턴스에서 MCP 서버를 만들 수도 있는데, 이러면 다른 애플리케이션이 여러분의 플러그인을 서비스처럼 소비할 수 있어요.
플러그인 함수의 두 유형
플러그인 안에는 보통 두 가지 유형의 함수가 있어요. 검색 증강 생성(RAG)용 데이터를 가져오는 함수와 작업을 자동화하는 함수예요. 기능적으로는 같지만 Semantic Kernel을 쓰는 애플리케이션에서 다르게 사용되는 경우가 많아요.
예를 들어 검색(retrieval) 함수에서는 성능을 높이기 위해 캐싱이라든지 요약에 더 저렴한 중간 모델을 쓰는 전략을 쓸 수 있어요. 반면 작업 자동화(task automation) 함수에서는 작업이 올바르게 완료되도록 사람이 개입하는 승인(human-in-the-loop approval) 프로세스를 구현하는 편이 좋아요.
플러그인 시작하기
Semantic Kernel에서 플러그인을 쓰는 것은 항상 3단계예요.
- 플러그인을 정의한다.
- 플러그인을 커널에 추가한다.
- 프롬프트에서 함수 호출로 플러그인의 함수를 호출한다.
플러그인을 만드는 가장 쉬운 방법은 클래스를 정의하고 그 메서드에 KernelFunction 속성을 붙이는 거예요. 이러면 Semantic Kernel에게 이것이 AI가 호출하거나 프롬프트에서 참조할 수 있는 함수라고 알려 줘요. OpenAPI 규격에서 플러그인을 가져올 수도 있어요.
대부분의 LLM이 함수 호출에 대해 Python으로 학습됐기 때문에, C#이나 Java SDK를 쓰더라도 함수명과 속성명은 snake case를 쓰는 걸 권장해요.
아래는 조명의 상태를 가져오고 바꾸는 플러그인을 만드는 C# 예시예요.
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")]
public async Task<List<LightModel>> GetLightsAsync()
{
return lights;
}
[KernelFunction("get_state")]
[Description("Gets the state of a particular 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")]
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; }
}
함수와 파라미터에 설명을 달아 줬다는 점을 눈여겨보세요. AI가 함수가 무엇을 하고 어떻게 쓰는지 이해하는 데 중요해요. AI가 함수 호출을 어려워한다면 함수에 상세한 설명을 아끼지 말고 달아 주세요. 몇 개의 예시(few-shot examples), 언제 써야 하고(쓰지 말아야 하는지)에 대한 권장, 필요한 파라미터를 어디서 얻을지에 대한 안내가 모두 도움이 될 수 있어요.
플러그인을 정의했으면 AddFromType 메서드로 커널의 플러그인 컬렉션에 추가해요.
var builder = new KernelBuilder();
builder.Plugins.AddFromType<LightsPlugin>("Lights");
Kernel kernel = builder.Build();
마지막으로 함수 호출로 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);
어떤 함수를 호출할지는 AI가 결정해야 하므로, 플러그인 함수를 직접 호출하는 건 권장하지 않아요. 어떤 함수를 호출할지 명시적으로 제어해야 한다면, 플러그인 대신 코드베이스의 표준 메서드를 쓰는 걸 고려해 보세요.
플러그인 작성 일반 권장사항
- 필요한 플러그인만 가져오세요. 시나리오에 필요한 함수만 담긴 플러그인만 가져오면 입력 토큰 소비를 줄이고 잘못된 함수 호출도 줄여요. OpenAI는 단일 API 호출에 20개 이하의 도구를 쓰길 권장하고, 이상적으로는 10개 이하예요. 도구가 10~20개 사이가 되면 모델이 올바른 도구를 고르는 능력이 떨어진다고 해요.
- 플러그인을 AI 친화적으로 만드세요. 함수명을 설명적이고 간결하게, 파라미터 수를 최소화(기본 타입 선호), 파라미터명을 명확하게 하세요.
DescriptionAttribute는 토큰 소비를 줄이기 위해 필요할 때만 써요. - 함수 수와 책임 사이의 균형을 찾으세요. 단일 책임 함수는 단순하고 여러 시나리오에서 재사용 가능하지만, 함수 호출마다 네트워크 왕복 지연과 토큰 소비 오버헤드가 생겨요. 반대로 여러 책임을 한 함수에 합치면 토큰과 네트워크 오버헤드는 줄지만 파라미터·반환 타입이 복잡해져 모델이 파라미터를 제대로 매칭하지 못할 수 있어요.
- 함수를 변환(transform)할 수 있어요. 기존 함수를 감싸서 동작을 바꾸거나, LLM이 추론할 수 없는 컨텍스트 정보(예: 현재 사용자, 인증 정보)를 호스트 앱에서 공급하거나, 복잡한 시그니처를 LLM이 이해하기 쉬운 형태로 바꾸는 방식이에요.
- 로컬 상태를 활용하세요. 크거나 민감한 데이터셋을 다룰 때는 원본 데이터나 중간 결과를 로컬에 저장하세요. 함수가 상태 id를 주고받게 하면 실제 데이터를 LLM에 보내지 않고 로컬에서 조회할 수 있어요. 데이터 프라이버시와 보안을 지키고 토큰 소비도 줄여요.
- 함수 반환 타입 스키마를 제공하세요. 잘 정의된 반환 타입 스키마는 AI 모델이 의도한 속성을 정확히 식별하게 해서 함수 호출의 정확도를 높여요.
더 알아보기 (Learn more)
- 네이티브 코드 가져오기 — 네이티브 플러그인 추가·호출
- OpenAPI 규격 가져오기 — OpenAPI 규격에서 플러그인 불러오기
- MCP 서버 가져오기 — MCP 서버에서 플러그인 추가
- 데이터 검색 함수 — RAG용 검색 함수
- 작업 자동화 함수 — 작업 자동화 함수