If you've been running reasoning models like Qwen3, phi4-reasoning, or DeepSeek-R1 locally through Semantic Kernel's .NET Ollama connector, you may have hit this: GetTextContentsAsync returns an empty string. No exception. No error. Just silence.
This is the story of how I tracked down that bug, built the fix, and got it merged into microsoft/semantic-kernel main.
The Problem
When a reasoning model has thinking enabled by default, its chain-of-thought output lands in a separate thinking stream — not in the standard response field. Semantic Kernel reads the standard response field. So when thinking is active, GetTextContentsAsync comes back empty even though the model ran successfully.
The only workaround was passing think=false in the Ollama request. But OllamaPromptExecutionSettings had no such property — there was simply no API surface to set it.
This was filed as issue #14078.
The Fix
Three parts: add the Think property, wire it through to the request, add full test coverage.
1. Add Think to OllamaPromptExecutionSettings
/// <summary>
/// Controls thinking behavior for Ollama reasoning models
/// (deepseek-r1, qwen3, phi4-reasoning).
/// When null, the model's default is used.
/// Set to false to suppress thinking and receive output
/// in the standard response field.
/// </summary>
[JsonPropertyName("think")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? Think
{
get => this._think;
set
{
this.ThrowIfFrozen();
this._think = value;
}
}
private bool? _think;
bool? is intentional — null means "don't send the parameter at all" (let the model decide). WhenWritingNull keeps the serialized request clean when it isn't set. ThrowIfFrozen() maintains the standard SK immutability contract. Clone() was updated to carry the value across.
2. Wire through to OllamaTextGenerationService
The change required bumping OllamaSharp from 5.4.12 to 5.4.25 — the version that introduced GenerateRequest.Think and the ThinkValue type. This was a necessary dependency bump, not a convenience one.
3. Tests
Full coverage across both paths:
-
ThinkPropertyRoundTripsViaSerialization—trueandfalsesurvive JSON round-trip -
ThinkPropertyIsPreservedByClone—Clone()carries the value -
ThinkPropertyThrowsWhenFrozen— setter throws afterFreeze() -
GetTextContentsShouldSendThinkSettingAsync— serialized payload contains the correctthinkfield -
GetTextContentsShouldNotSendThinkWhenNotSetAsync— payload omitsthinkwhen null -
GetStreamingTextContentsShouldSendThinkSettingAsync— streaming path also propagatesThink
All 115 existing unit tests continued to pass.
How to Use It
Once this ships in a Semantic Kernel release:
var kernel = Kernel.CreateBuilder()
.AddOllamaTextGeneration("qwen3", new Uri("http://localhost:11434"))
.Build();
var settings = new OllamaPromptExecutionSettings
{
Think = false // suppress thinking, get output in standard response
};
var result = await kernel.InvokePromptAsync(
"Explain the difference between IEnumerable and IQueryable in C#",
new KernelArguments(settings)
);
Console.WriteLine(result); // No longer empty!
Set Think = true to explicitly enable thinking (e.g., if you're consuming the thinking stream separately), or leave it null to use the model's default.
What the Contribution Process Taught Me
Read the issue before the code. Issue #14078 was already filed with a clear reproduction case. An existing, acknowledged issue is a green light — the maintainers have validated the problem. You're delivering a solution, not arguing for one.
Match the existing pattern. OllamaPromptExecutionSettings had other nullable properties (TopP, Temperature) with the same JSON serialization pattern. Matching that convention made review smooth — reviewers could see immediately the new property followed the established contract.
Tests aren't optional in SK. The Copilot reviewer flagged two minor things: a nullable simplification and ToLowerInvariant() instead of ToLower(). Both were addressed in a follow-up commit. Having thorough tests from the start meant the logic was never questioned — only the style.
The dependency bump needs justification. Bumping OllamaSharp wasn't just a version change — it was required specifically because 5.4.25 introduced the GenerateRequest.Think API surface. Explaining that in the PR description helped reviewers trust the bump.
The PR
microsoft/semantic-kernel #14122
Merged July 9, 2026 · Reviewed by @rogerbarreto and @westey-m
Semantic Kernel has 28.3k ⭐ — if you're hitting the empty-response issue with reasoning models via Ollama, this fix will be available in the next SK release. And if you find something similar that doesn't work — file the issue, or better yet, fix it.
Originally published on Medium.
Top comments (0)