-
[C#] Semantic Kernel로 로컬 Ollama 모델에 C# 함수 호출(Function Calling) 붙이기Microsoft .NET/C# 2026. 11. 11. 12:01

로컬 Ollama 모델에 C# 함수를 붙이면 모델이 스스로 함수를 골라 호출하고, 그 결과로 답을 만듭니다. Semantic Kernel의 Ollama 커넥터와 자동 함수 호출(FunctionChoiceBehavior)로 최소 구성하는 절차입니다.
절차
- Ollama 모델 준비와 tool calling 지원 확인
- 패키지 설치와 커널 구성
- 플러그인(함수) 정의
- 자동 함수 호출 설정
- 실행과 호출 추적
- 실패하는 지점과 대응
1. 모델 준비
# 서버 기동 (기본 포트 11434) ollama serve # tool calling을 지원하는 모델 예시 ollama pull qwen3:8b # 대화 확인 ollama run qwen3:8b "안녕"모델이 tool calling을 지원하지 않으면 함수 호출이 발생하지 않고, 모델은 함수 정의를 텍스트로만 참고합니다. 어떤 모델을 쓸지는 Ollama 모델 페이지의 tools 표기로 확인합니다.
2. 패키지와 커널 구성
dotnet add package Microsoft.SemanticKernel dotnet add package Microsoft.SemanticKernel.Connectors.Ollama --prereleaseusing Microsoft.SemanticKernel; var builder = Kernel.CreateBuilder(); builder.AddOllamaChatCompletion( modelId: "qwen3:8b", endpoint: new Uri("http://localhost:11434")); Kernel kernel = builder.Build();모델 ID는
ollama list에 나오는 이름과 정확히 일치해야 합니다. 태그를 생략하면latest가 붙은 이름으로 조회되므로, 태그까지 적는 편이 안전합니다.3. 플러그인(함수) 정의
C# 메서드에
KernelFunction과Description을 붙이면 함수 목록에 등록됩니다. 설명은 모델이 함수를 고르는 근거이므로 호출 조건과 반환 형식을 구체적으로 적습니다.using System.ComponentModel; using Microsoft.SemanticKernel; public class ServerPlugin { [KernelFunction] [Description("서버 이름의 현재 CPU 사용률을 퍼센트로 반환합니다. 예: 'web-01'")] public double GetCpuUsage( [Description("서버 이름. 예: web-01, db-01")] string serverName) { // 실무에서는 모니터링 API 호출로 대체 return serverName switch { "web-01" => 37.5, "db-01" => 82.1, _ => throw new ArgumentException($"unknown server: {serverName}") }; } [KernelFunction] [Description("서버 목록을 반환합니다.")] public string[] ListServers() => ["web-01", "db-01"]; }kernel.Plugins.AddFromType<ServerPlugin>();4. 자동 함수 호출 설정
프롬프트 실행 설정에
FunctionChoiceBehavior.Auto()를 넣으면, 모델이 함수 호출을 요청할 때 커널이 실제로 실행하고 결과를 모델에 다시 넘깁니다. 호출 루프를 직접 작성하지 않습니다.using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.Ollama; var settings = new OllamaPromptExecutionSettings { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }; var chat = kernel.GetRequiredService<IChatCompletionService>(); var history = new ChatHistory(); history.AddSystemMessage("너는 서버 운영 도우미다. 수치는 함수 호출 결과만 사용한다."); history.AddUserMessage("db-01 CPU 사용률 알려줘"); var reply = await chat.GetChatMessageContentAsync(history, settings, kernel); Console.WriteLine(reply.Content);GetChatMessageContentAsync에kernel을 넘기는 것이 핵심입니다. 넘기지 않으면 모델은 함수 호출을 요청하지만 실행할 주체가 없어 빈 응답이나 호출 요청만 남습니다.5. 실행과 호출 추적
자동 호출은 조용히 일어나므로, 어떤 함수가 어떤 인자로 실행됐는지 로그로 남깁니다.
var invoked = new List<string>(); kernel.FunctionInvocationFilters.Add(new FunctionInvocationFilter(invoked)); public sealed class FunctionInvocationFilter(List<string> log) : IFunctionInvocationFilter { public async Task OnFunctionInvocationAsync( FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next) { log.Add($"{context.Function.PluginName}.{context.Function.Name}"); await next(context); log.Add($"result={context.Result}"); } }foreach (var line in invoked) Console.WriteLine(line);호출 기록이 비어 있다면 모델이 함수를 선택하지 않은 것입니다. 시스템 메시지에 "함수 호출 결과만 사용한다"는 제약을 넣고, 함수 설명을 더 구체적으로 고쳐 다시 확인합니다.
6. 실패하는 지점과 대응
- 모델이 tool calling을 지원하지 않으면 함수가 호출되지 않습니다. 모델을 바꾸는 것이 가장 빠릅니다.
- 함수 설명이 모호하면 엉뚱한 함수를 고릅니다. 이름·설명에 호출 조건과 인자 형식을 적습니다.
- 함수가 예외를 던지면 자동 호출 전체가 실패합니다. 플러그인 안에서 예외를 잡아 문자열 결과로 돌려주는 편이 안전합니다.
- 응답이 길어지면 컨텍스트 한도를 넘습니다. 대화 이력을 잘라 넣거나 요약을 앞에 붙입니다.
- 로컬 모델은 첫 토큰까지 시간이 깁니다.
HttpClient타임아웃을 기본값보다 길게 잡습니다.
var http = new HttpClient { Timeout = TimeSpan.FromMinutes(5) }; builder.AddOllamaChatCompletion( modelId: "qwen3:8b", endpoint: new Uri("http://localhost:11434"), httpClient: http);스트리밍 응답과 자동 함수 호출을 함께 쓸 때는 함수 실행이 끝난 뒤의 토큰만 흘러나옵니다. 첫 토큰 지연을 사용자에게 표시해야 하는 화면이라면 스트리밍을 포기하고 일반 호출로 처리하는 편이 단순합니다.
정리
Ollama 커넥터로 커널을 만들고, 플러그인에 함수를 등록한 뒤 실행 설정에
FunctionChoiceBehavior.Auto()를 넣으면 모델이 함수를 골라 호출합니다. 실행 시kernel을 함께 넘기고, 필터로 호출 기록을 남기면 동작을 확인할 수 있습니다.'Microsoft .NET > C#' 카테고리의 다른 글
[C#] .NET에서 로컬 벡터 DB(Qdrant) 연동과 RAG 검색 파이프라인 최소 구현 (0) 2026.11.11 [C# 15] 드디어 도입된 Union 타입(discriminated union) 문법과 실무 활용 (0) 2026.11.11 [C#] DateTime vs DateTimeOffset, 서버 시간은 UTC로 관리하기 (0) 2026.11.10 [C#] async/await 데드락 원인과 ConfigureAwait 정리 (0) 2026.11.10 [C#] HttpClient 재시도, IHttpClientFactory와 표준 리질리언스 정책 (0) 2026.11.10