Ollama + 카카오 카나나 모델 + 시맨틱 커널 에이전트 코어 샘플

카카오에서 공개한 카나나 (Kanana) 모델이 허깅 페이스에 널리 공유되고 있는데, Ollama에서 실행할 수 있게 변환한 GGUF 모델을 빠르게 만들어주신 분이 계셔서 이를 이용해서 시맨틱 커널로 에이전트를 만들어 볼 수 있을 것 같아 프로토타이핑해보았습니다. :smiley:

사용한 모델: coolsoon/kanana-1.5-8b

<Query Kind="Statements">
  <NuGetReference Prerelease="true">Microsoft.SemanticKernel.Connectors.Ollama</NuGetReference>
  <NuGetReference>Microsoft.SemanticKernel.Agents.Core</NuGetReference>
  <Namespace>OllamaSharp</Namespace>
  <Namespace>Microsoft.SemanticKernel</Namespace>
  <Namespace>System.Diagnostics.CodeAnalysis</Namespace>
  <Namespace>Microsoft.SemanticKernel.Agents</Namespace>
  <Namespace>Microsoft.SemanticKernel.ChatCompletion</Namespace>
  <Namespace>System.ComponentModel</Namespace>
</Query>

// Reference
// https://laurentkempe.com/2025/03/02/building-local-ai-agents-semantic-kernel-agent-with-functions-in-csharp-using-ollama

var showIndicator = true;

#if LINQPAD
#nullable enable
showIndicator = false;
#endif

var ollamaUri = new Uri("http://localhost:11434/");
var modelName = "coolsoon/kanana-1.5-8b";

var kernelBuilder = Kernel
	.CreateBuilder()
	.AddOllamaChatCompletion(modelName, ollamaUri);
var kernel = kernelBuilder.Build();

var agent = new ChatCompletionAgent()
{
	Kernel = kernel,
	Name = "지역 시간대 정보 에이전트",
	Instructions =
		"""
	    다양한 지역에 대한 질문에 답하세요.
	    프랑스의 경우 시간 형식(HH:MM)을 사용하세요.
	    HH는 00부터 23시까지, MM은 00부터 59분까지입니다.
	    """,
	Arguments = new KernelArguments(new PromptExecutionSettings
	{
		FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
	}),
};

var plugin = KernelPluginFactory.CreateFromFunctions(
    nameof(GetCurrentTime),
	[KernelFunctionFactory.CreateFromMethod(GetCurrentTime)]);

agent.Kernel.Plugins.Add(plugin);

// 상수 정의
const string SystemPrompt = """
You are a helpful AI assistant developed by Kakao with access to specific functions. 한국어와 영어 모두에서 정확한 함수 호출을 수행할 수 있습니다.

함수 호출 규칙:
1. **명시적으로 제공된 함수만 사용** - 함수를 임의로 생성하거나 환상하지 마세요
2. **필요시 함수 호출** - 사용자 요청이 외부 데이터나 함수가 제공할 수 있는 작업을 필요로 할 때 반드시 사용하세요
3. **미래 함수 호출 약속 금지** - 함수 호출이 필요하면 지금 실행하고, 그렇지 않으면 일반적으로 응답하세요
4. **필수 매개변수 누락시 질문** - 필수 매개변수가 누락된 경우 함수를 호출하기 전에 사용자에게 제공을 요청하세요
5. **선택적 매개변수는 요청하지 않음** - 필수 매개변수에 대해서만 명확화를 요청하세요

응답 형식:
- 함수 호출: 지정된 매개변수 이름과 타입에 정확히 맞는 유효한 JSON 반환
- 일반 응답: 도움이 되고 자연스러운 언어 응답 제공  
- 같은 응답에서 함수 호출과 일반 텍스트를 혼합하지 마세요

매개변수 처리:
- **필수 매개변수**: 명시적으로 제공되거나 컨텍스트에서 명확하게 추론 가능해야 함
- **선택적 매개변수**: 사용자가 명시적으로 언급한 경우에만 포함
- **데이터 타입**: 지정된 타입(string, number, boolean, array, object)을 엄격히 준수
- **Enum**: 제공된 enum 목록의 값만 사용

오류 방지:
- 호출 전 모든 필수 매개변수가 있는지 검증
- 정의된 대로 정확한 함수명 사용
- 정확한 JSON 스키마 구조 준수
- 스키마에 정의되지 않은 추가 필드 추가 금지

사용 가능한 함수들이 tools 매개변수로 제공됩니다. 정확성과 스키마 준수는 성공적인 함수 실행을 위해 매우 중요합니다.
""";

var exitCommands = new[] { "/exit", "/bye", "/quit", };
var clearCommands = new[] { "/clear", "/reset", "/restart", };
var helpCommands = new[] { "/help", "/h", "?" };

var helpText = $$"""
{{agent.Name}}

=== 명령어 ===
{{string.Join(", ", clearCommands)}} - 초기화
{{string.Join(", ", helpCommands)}} - 도움말
{{string.Join(", ", exitCommands)}} - 종료

=== 예시 ===
프랑스 일자흐의 시간은 몇 시입니까?
대한민국 서울과 뉴욕 사이의 시간차는 몇 시간인가요?
""";

var chat = new ChatHistory([
	new ChatMessageContent(AuthorRole.System, SystemPrompt)
]);

Console.WriteLine(helpText);

while (true)
{
	if (showIndicator)
	    Console.Write("You: ");

	string? input = Console.ReadLine();

	if (string.IsNullOrWhiteSpace(input)) continue;

	var cmd = input.ToLowerInvariant();

	// 명령어 처리
	if (exitCommands.Contains(cmd))
	{
		Console.WriteLine();
		Console.WriteLine("대화를 종료합니다. 안녕히 가세요!");
		break;
	}

	if (clearCommands.Contains(cmd))
	{
		chat.Clear();
		chat.Add(new ChatMessageContent(AuthorRole.System, SystemPrompt));

		Console.WriteLine();
		Console.WriteLine("대화 기록이 초기화되었습니다.");
		Console.WriteLine();
		continue;
	}

	if (helpCommands.Contains(cmd))
    {
		Console.WriteLine();
        Console.WriteLine(helpText);
        continue;
    }
    
    // AI와 대화
    chat.Add(new ChatMessageContent(AuthorRole.User, input));
	
	if (showIndicator)
	    Console.Write("AI: ");
    
    try
    {
        await foreach (var response in agent.InvokeAsync(chat))
        {
            if (!string.IsNullOrEmpty(response.Message?.Content))
                Console.Write(response.Message.Content);
            chat.Add(response);
        }

        Console.WriteLine();
		Console.WriteLine();
	}
	catch (Exception ex)
    {
        Console.WriteLine($"오류: {ex.Message}");
		Console.WriteLine();
	}
}

[KernelFunction]
[Description(
	"""
	특정 도시의 현재 시간을 반환합니다.
	AI는 사용자가 입력한 도시명을 Windows 표준 시간대 ID로 변환해서 이 함수를 호출해야 합니다.
	
	주요 시간대 ID:
	- 서울/한국: Korea Standard Time
	- 도쿄/일본: Tokyo Standard Time  
	- 베이징/중국: China Standard Time
	- 뉴욕/미동부: Eastern Standard Time
	- 시카고/미중부: Central Standard Time
	- LA/미서부: Pacific Standard Time
	- 런던/영국: GMT Standard Time
	- 파리/독일: W. Europe Standard Time
	- 시드니/호주: AUS Eastern Standard Time
	"""
	)]
string GetCurrentTime(
	[Description("Windows 표준 시간대 ID (예: 'Korea Standard Time')")] string timeZoneId,
	[Description("사용자가 요청한 도시명 (표시용)")] string city = "")
{
	try
	{
		var timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
		var currentTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, timeZone);
		var locationName = !string.IsNullOrEmpty(city) ? city : timeZoneId;

		return $"{locationName} 지역의 현재 시간은 {currentTime:HH:mm}입니다.";
	}
	catch (TimeZoneNotFoundException)
	{
		return $"시간대 '{timeZoneId}'를 찾을 수 없습니다. 올바른 Windows 시간대 ID를 사용해주세요.";
	}
	catch (Exception ex)
	{
		return $"시간 조회 중 오류가 발생했습니다: {ex.Message}";
	}
}
7개의 좋아요

카카오 모델이 성능이 좋다고 하던데 다른 한국어에 특화된 모델에 비해 속도나 결과가 어떤가요?

1개의 좋아요

예전에 LG에서 출시한 Exaone 정도만 아주 잠깐 써본거라서 성능이 어디가 더 뛰어나다고 평가하진 못하겠습니다. 다만 이번 카나나 모델은 Tool Calling이 되는 한국어 모델이라서 기능 면에 있어서는 제가 찾던 모델이라 의미가 있다고 생각합니다. ㅎㅎ

2개의 좋아요