Semantic Kernel 커널 이해하기

Semantic Kernel 커널 이해하기

출처: 공식문서

커널(kernel)은 Semantic Kernel의 중심 컴포넌트예요. 가장 단순하게 말하면, 커널은 AI 애플리케이션을 실행하는 데 필요한 모든 서비스와 플러그인을 관리하는 의존성 주입(Dependency Injection) 컨테이너예요. 모든 서비스와 플러그인을 커널에 제공하면, AI가 필요할 때 그대로 사용할 수 있어요.

커널이 중심에 있다

커널은 네이티브 코드와 AI 서비스를 실행하는 데 필요한 모든 서비스와 플러그인을 갖고 있기 때문에, Semantic Kernel SDK의 거의 모든 컴포넌트가 에이전트를 구동할 때 커널을 사용해요. 즉 Semantic Kernel에서 어떤 프롬프트나 코드를 실행하더라도, 커널은 항상 필요한 서비스와 플러그인을 꺼내 쓸 수 있게 준비돼 있어요.

The kernel is at the center of everything in Semantic Kernel

이것은 매우 강력한데, 개발자가 AI 에이전트를 설정하고, 무엇보다 모니터링하는 단일 지점을 갖게 되기 때문이에요. 예를 들어 커널에서 프롬프트를 호출한다고 해 볼게요. 그럴 때 커널은...

  1. 프롬프트를 실행할 최적의 AI 서비스를 선택해요.
  2. 주어진 프롬프트 템플릿으로 프롬프트를 구성해요.
  3. 프롬프트를 AI 서비스에 보내요.
  4. 응답을 받아 파싱해요.
  5. 마지막으로 LLM의 응답을 애플리케이션에 반환해요.

이 전체 과정에서 각 단계마다 트리거되는 이벤트와 미들웨어를 만들 수 있어요. 로깅을 수행하거나, 사용자에게 상태 업데이트를 제공하거나, 무엇보다 책임 있는 AI(responsible AI)를 위한 동작을 하는 식이에요. 이 모든 걸 단일 지점에서 처리할 수 있어요.

서비스와 플러그인으로 커널 구성하기

커널을 구성하기 전에, 먼저 존재하는 두 가지 유형의 컴포넌트를 이해해야 해요.

컴포넌트 설명
서비스(Services) AI 서비스(예: 채팅 완성)와 애플리케이션 실행에 필요한 기타 서비스(예: 로깅, HTTP 클라이언트)로 구성돼 있어요. 모든 언어에서 의존성 주입을 지원하도록 .NET의 Service Provider 패턴을 본떠 모델링했어요.
플러그인(Plugins) AI 서비스와 프롬프트 템플릿이 작업을 수행할 때 사용하는 컴포넌트예요. 예를 들어 AI 서비스는 플러그인을 사용해 데이터베이스에서 데이터를 조회하거나, 외부 API를 호출해 작업을 수행할 수 있어요.

::: zone pivot="programming-language-csharp" 커널을 만들기 시작하려면 파일 상단에서 필요한 패키지를 가져와요.

:::code language="csharp" source="~/../semantic-kernel-samples/dotnet/samples/LearnResources/MicrosoftLearn/UsingTheKernel.cs" id="NecessaryPackages":::

다음으로 서비스와 플러그인을 추가할 수 있어요. 아래는 Azure OpenAI 채팅 완성, 로거, 시간 플러그인을 추가하는 예시예요.

// Create a kernel with a logger and Azure OpenAI chat completion service
var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatCompletion(modelId, endpoint, apiKey);
builder.Services.AddLogging(c => c.AddDebug().SetMinimumLevel(LogLevel.Trace));
builder.Plugins.AddFromType<TimePlugin>();
Kernel kernel = builder.Build();

::: zone-end

::: zone pivot="programming-language-python" 필요한 패키지를 가져와요.

from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.core_plugins.time_plugin import TimePlugin

다음으로 커널을 만들 수 있어요.

# Initialize the kernel
kernel = Kernel()

마지막으로 필요한 서비스와 플러그인을 추가할 수 있어요. 아래는 Azure OpenAI 채팅 완성, 로거, 시간 플러그인을 추가하는 예시예요.

# Add the Azure OpenAI chat completion service
kernel.add_service(AzureChatCompletion(model_id, endpoint, api_key))

# Add a plugin
kernel.add_plugin(
    TimePlugin(),
    plugin_name="TimePlugin",
)

커널에서 MCP 서버 만들기

이제 Semantic Kernel 인스턴스에 등록한 함수에서 MCP 서버를 만드는 것을 지원해요.

이렇게 하려면 평소처럼 커널을 만든 다음,そこ에서 MCP 서버를 만들면 돼요.

from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.functions import kernel_function
from semantic_kernel.prompt_template import InputVariable, PromptTemplateConfig

kernel = Kernel()

@kernel_function()
def echo_function(message: str, extra: str = "") -> str:
    """Echo a message as a function"""
    return f"Function echo: {message} {extra}"

kernel.add_service(OpenAIChatCompletion(service_id="default"))
kernel.add_function("echo", echo_function, "echo_function")
kernel.add_function(
    plugin_name="prompt",
    function_name="prompt",
    prompt_template_config=PromptTemplateConfig(
        name="prompt",
        description="This is a prompt",
        template="Please repeat this: {{$message}} and this: {{$extra}}",
        input_variables=[
            InputVariable(
                name="message",
                description="This is the message.",
                is_required=True,
                json_schema='{ "type": "string", "description": "This is the message."}',
            ),
            InputVariable(
                name="extra",
                description="This is extra.",
                default="default",
                is_required=False,
                json_schema='{ "type": "string", "description": "This is the message."}',
            ),
        ],
    ),
)
server = kernel.as_mcp_server(server_name="sk")

위에서 만든 server 객체는 mcp 패키지에서 온 것이에요. 리소스나 다른 기능을 추가하는 식으로 더 확장할 수 있어요. 그런 다음 온라인으로 띄워서, 예를 들어 Stdio와 함께 사용할 수 있어요.

import anyio
from mcp.server.stdio import stdio_server

async def handle_stdin(stdin: Any | None = None, stdout: Any | None = None) -> None:
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, server.create_initialization_options())

anyio.run(handle_stdin)

또는 SSE와 함께 사용할 수 있어요.

import uvicorn
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.routing import Mount, Route

sse = SseServerTransport("/messages/")

async def handle_sse(request):
    async with sse.connect_sse(request.scope, request.receive, request._send) as (read_stream, write_stream):
        await server.run(read_stream, write_stream, server.create_initialization_options())

starlette_app = Starlette(
    debug=True,
    routes=[
        Route("/sse", endpoint=handle_sse),
        Mount("/messages/", app=sse.handle_post_message),
    ],
)

uvicorn.run(starlette_app, host="0.0.0.0", port=8000)

프롬프트 템플릿을 MCP 프롬프트로 노출하기

서로 다른 Semantic Kernel 프롬프트 템플릿을 MCP 프롬프트로 노출해서 활용할 수도 있어요. 이렇게 말이죠.

from semantic_kernel.prompt_template import InputVariable, KernelPromptTemplate, PromptTemplateConfig

prompt = KernelPromptTemplate(
    prompt_template_config=PromptTemplateConfig(
        name="release_notes_prompt",
        description="This creates the prompts for a full set of release notes based on the PR messages given.",
        template=template,
        input_variables=[
            InputVariable(
                name="messages",
                description="These are the PR messages, they are a single string with new lines.",
                is_required=True,
                json_schema='{"type": "string"}',
            )
        ],
    )
)

server = kernel.as_mcp_server(server_name="«redacted:sk_…»", prompts=[prompt])

::: zone-end

::: zone pivot="programming-language-java"

커널 구성하기

커널은 Kernel.builder()를 사용해 만들 수 있어요. 여기에 필요한 AI 서비스와 플러그인을 추가할 수 있어요.

Kernel kernel = Kernel.builder()
    .withAIService(ChatCompletionService.class, chatCompletionService)
    .withPlugin(lightPlugin)
    .build();

::: zone-end

::: zone pivot="programming-language-csharp"

의존성 주입 사용하기

C#에서는 의존성 주입(Dependency Injection)을 사용해 커널을 만들 수 있어요. ServiceCollection을 만들고 거기에 서비스와 플러그인을 추가하는 방식이에요. 아래는 의존성 주입을 사용해 커널을 만드는 예시예요.

[!TIP] 커널은 트랜셔널(transient) 서비스로 만들어 매번 사용 후 폐기하는 걸 권장해요. 플러그인 컬렉션은 변경 가능(mutable)하기 때문이에요. 커널은 (서비스와 플러그인의 컨테이너일 뿐이라) 매우 가벼워서, 매번 새 커널을 만들어도 성능 문제가 되지 않아요.

using Microsoft.SemanticKernel;

var builder = Host.CreateApplicationBuilder(args);

// Add the OpenAI chat completion service as a singleton
builder.Services.AddOpenAIChatCompletion(
    modelId: "gpt-4",
    apiKey: ***
    orgId: "YOUR_ORG_ID", // Optional; for OpenAI deployment
    serviceId: "YOUR_SERVICE_ID" // Optional; for targeting specific services within Semantic Kernel
);

// Create singletons of your plugins
builder.Services.AddSingleton(() => new LightsPlugin());
builder.Services.AddSingleton(() => new SpeakerPlugin());

// Create the plugin collection (using the KernelPluginFactory to create plugins from objects)
builder.Services.AddSingleton<KernelPluginCollection>((serviceProvider) => 
    [
        KernelPluginFactory.CreateFromObject(serviceProvider.GetRequiredService<LightsPlugin>()),
        KernelPluginFactory.CreateFromObject(serviceProvider.GetRequiredService<SpeakerPlugin>())
    ]
);

// Finally, create the Kernel service with the service provider and plugin collection
builder.Services.AddTransient((serviceProvider)=> {
    KernelPluginCollection pluginCollection = serviceProvider.GetRequiredService<KernelPluginCollection>();

    return new Kernel(serviceProvider, pluginCollection);
});

[!TIP] C#에서 의존성 주입을 사용하는 더 많은 샘플은 개념 샘플을 참고해요.

::: zone-end

::: zone pivot="programming-language-java"

::: zone-end

더 알아보기 (Learn more)

이제 커널을 이해했으니, 커널에 추가할 수 있는 다양한 AI 서비스에 대해 알아볼 수 있어요.