Microsoft의 Semantic Kernel에서 발견한 조용한 버그를 수정하고 메인 브랜치에 병합된 과정
요약
Microsoft의 Semantic Kernel에서 Ollama 커넥터 사용 시 추론 모델의 사고(thinking) 스트림으로 인해 발생하는 빈 문자열 반환 버그를 해결하는 과정을 다룹니다. OllamaPromptExecutionSettings에 Think 속성을 추가하여 사고 기능을 제어할 수 있도록 수정되었습니다.
핵심 포인트
- 추론 모델의 사고(CoT) 출력이 별도 스트림으로 전달되어 발생하는 버그 수정
- OllamaPromptExecutionSettings에 Think 속성 추가 및 직렬화 지원
- DeepSeek-R1, Qwen3 등 최신 추론 모델과의 호환성 개선
- 유닛 테스트를 통한 안정적인 기능 구현 및 병합 과정 공유
만약 여러분이 Semantic Kernel의 .NET Ollama 커넥터를 통해 Qwen3, phi4-reasoning, 또는 DeepSeek-R1과 같은 추론 모델 (reasoning models)을 로컬에서 실행하고 있다면, 다음과 같은 상황을 겪었을 수 있습니다: GetTextContentsAsync가 빈 문자열을 반환합니다. 예외(exception)도 없고, 에러(error)도 없습니다. 그저 침묵할 뿐입니다.
이것은 제가 어떻게 그 버그를 추적하고, 수정안을 만들었으며, microsoft/semantic-kernel 메인 브랜치에 병합될 수 있었는지에 대한 이야기입니다.
문제 (The Problem)
추론 모델이 기본적으로 사고(thinking) 기능이 활성화되어 있는 경우, 모델의 사고 사슬 (chain-of-thought) 출력은 표준 응답 필드가 아닌 별도의 thinking 스트림에 담깁니다. Semantic Kernel은 표준 응답 필드를 읽습니다. 따라서 사고 기능이 활성화되어 있으면, 모델이 성공적으로 실행되었음에도 불구하고 GetTextContentsAsync는 빈 값을 반환하게 됩니다.
유일한 임시 방편은 Ollama 요청에 think=false를 전달하는 것이었습니다. 하지만 OllamaPromptExecutionSettings에는 해당 속성이 없었습니다. 즉, 이를 설정할 수 있는 API 표면 (API surface) 자체가 없었습니다.
이 문제는 issue #14078로 접수되었습니다.
해결책 (The Fix)
세 가지 단계로 진행되었습니다: Think 속성 추가, 이를 요청까지 연결, 그리고 완전한 테스트 커버리지 추가.
1. OllamaPromptExecutionSettings에 Think 추가
/// <summary>
/// Ollama 추론 모델(deepseek-r1, qwen3, phi4-reasoning)의 사고 동작을 제어합니다.
...
bool? 타입을 사용한 것은 의도적인 것입니다. null은
ThinkPropertyRoundTripsViaSerialization—true및false가 JSON 라운드트립 (round-trip) 시 유지됨ThinkPropertyIsPreservedByClone—Clone()시 값이 유지됨ThinkPropertyThrowsWhenFrozen—Freeze()호출 후 setter에서 예외 발생GetTextContentsShouldSendThinkSettingAsync— 직렬화된 페이로드 (payload)에 올바른think필드가 포함됨GetTextContentsShouldNotSendThinkWhenNotSetAsync—null일 때 페이로드에서think를 생략함GetStreamingTextContentsShouldSendThinkSettingAsync— 스트리밍 경로에서도Think설정이 전파됨
기존의 115개 유닛 테스트 (unit tests) 모두 통과를 유지했습니다.
사용 방법
이 기능이 Semantic Kernel 릴리스에 포함되면 다음과 같이 사용할 수 있습니다:
var kernel = Kernel.CreateBuilder()
.AddOllamaTextGeneration("qwen3", new Uri("http://localhost:11434"))
.Build();
...
Think = true로 설정하여 사고 (thinking) 기능을 명시적으로 활성화하거나 (예: 사고 스트림을 별도로 처리하는 경우), null로 두어 모델의 기본값을 사용하도록 할 수 있습니다.
기여 과정을 통해 배운 점
코드를 작성하기 전에 이슈를 읽으세요. 이슈 #14078은 이미 명확한 재현 사례와 함께 등록되어 있었습니다. 이미 인지된 이슈가 있다는 것은 초록불과 같습니다. 즉, 메인테이너 (maintainers)들이 해당 문제를 검증했다는 뜻입니다. 당신은 해결책을 논쟁하는 것이 아니라, 해결책을 전달하는 것입니다.
기존 패턴을 따르세요. OllamaPromptExecutionSettings에는 동일한 JSON 직렬화 패턴을 가진 다른 nullable 속성들(TopP, Temperature)이 있었습니다. 이러한 컨벤션 (convention)을 따랐기에 리뷰가 매끄러웠습니다. 리뷰어들은 새로운 속성이 기존에 확립된 계약 (contract)을 따르고 있음을 즉시 확인할 수 있었습니다.
SK에서 테스트는 선택이 아닙니다. Copilot 리뷰어는 두 가지 사소한 사항을 지적했습니다: nullable 단순화와 ToLower() 대신 ToLowerInvariant()를 사용하는 것이었습니다. 두 사항 모두 후속 커밋 (commit)에서 해결되었습니다. 처음부터 철저한 테스트를 갖추고 있었기에 로직에 대한 의문은 제기되지 않았고, 오직 스타일 (style)에 대해서만 논의될 수 있었습니다.
의존성 업데이트 (dependency bump)에는 정당한 이유가 필요합니다. OllamaSharp를 업데이트한 것은 단순한 버전 변경이 아니었습니다. 5.4.25 버전에서 GenerateRequest.Think API 표면 (API surface)이 도입되었기 때문에 반드시 필요했던 조치였습니다. PR (Pull Request) 설명에 이 내용을 명시한 덕분에 리뷰어들이 업데이트의 타당성을 신뢰할 수 있었습니다.
PR
microsoft/semantic-kernel #14122
2026년 7월 9일 병합됨 · @rogerbarreto 및 @westey-m 검토
Semantic Kernel은 28.3k ⭐를 보유하고 있습니다. 만약 Ollama를 통해 추론 모델 (reasoning models)을 사용할 때 빈 응답 (empty-response) 문제가 발생하고 있다면, 이번 수정 사항은 다음 SK 릴리스에서 제공될 예정입니다. 그리고 만약 작동하지 않는 유사한 문제를 발견한다면, 이슈 (issue)를 제기하거나, 더 나아가 직접 수정해 보세요.
원문은 Medium에 게시되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기