From 98b6a2aeedd591058c1d81e401d24f3ded162926 Mon Sep 17 00:00:00 2001 From: tanaka takashi Date: Wed, 12 Aug 2026 00:21:23 +0900 Subject: fix: implement native Ollama thinking semantics Model Ollama chat thinking separately from final content, preserve it only across assistant tool-call continuation, and keep private reasoning out of user results, session context, and logs. Accept native boolean and low/medium/high/max think values, normalize YAML booleans, and serialize explicit overrides using the native top-level chat field. Stop forcing think=false for structured planning, skill selection, and context summaries; corrected retries now retain the configured thinking mode. Update the specification, design, audit report, and requirement traceability for OLL-14. Add request/response, configuration, observability, retry, and tool-continuation coverage. Verified with 92 passing Agent.Core tests and a deepseek-v4-flash:cloud think:true MCP operational run. --- tests/Agent.Core.Tests/OllamaClientTests.cs | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) (limited to 'tests/Agent.Core.Tests/OllamaClientTests.cs') diff --git a/tests/Agent.Core.Tests/OllamaClientTests.cs b/tests/Agent.Core.Tests/OllamaClientTests.cs index 94ce8cd..87a3e20 100644 --- a/tests/Agent.Core.Tests/OllamaClientTests.cs +++ b/tests/Agent.Core.Tests/OllamaClientTests.cs @@ -37,6 +37,60 @@ public sealed class OllamaClientTests Assert.Equal(42, root.GetProperty("options").GetProperty("seed").GetInt32()); } + [Fact] + public async Task Thinking_is_separated_from_final_content_and_not_discarded() + { + var handler = new StubHandler((_, _) => Task.FromResult(Json(HttpStatusCode.OK, + """{"message":{"role":"assistant","thinking":"private reasoning","content":"final answer"},"prompt_eval_count":3,"eval_count":5}"""))); + var client = new OllamaClient(new HttpClient(handler), + new OllamaSettings(new Uri("http://127.0.0.1:11434"), "test-model", true, "0", new Dictionary())); + + OllamaResult result = await client.ChatAsync([new OllamaMessage("user", "hello")], null, TestContext.Current.CancellationToken); + + Assert.Equal("final answer", result.Content); + Assert.Equal("private reasoning", result.Thinking); + } + + [Fact] + public async Task Thinking_level_and_assistant_thinking_history_follow_native_chat_schema() + { + string? body = null; + var handler = new StubHandler(async (request, _) => + { + body = await request.Content!.ReadAsStringAsync(); + return Json(HttpStatusCode.OK, """{"message":{"role":"assistant","content":"done"}}"""); + }); + var client = new OllamaClient(new HttpClient(handler), + new OllamaSettings(new Uri("http://127.0.0.1:11434"), "test-model", "high", "0", new Dictionary())); + + await client.ChatAsync([new OllamaMessage("assistant", "", ToolCalls: [], Thinking: "prior reasoning")], null, + TestContext.Current.CancellationToken); + + using JsonDocument document = JsonDocument.Parse(body!); + Assert.Equal("high", document.RootElement.GetProperty("think").GetString()); + Assert.Equal("prior reasoning", document.RootElement.GetProperty("messages")[0].GetProperty("thinking").GetString()); + } + + [Fact] + public async Task Explicit_false_override_is_serialized_as_boolean_false() + { + string? body = null; + var handler = new StubHandler(async (request, _) => + { + body = await request.Content!.ReadAsStringAsync(); + return Json(HttpStatusCode.OK, """{"message":{"role":"assistant","content":"{}"}}"""); + }); + var client = new OllamaClient(new HttpClient(handler), + new OllamaSettings(new Uri("http://127.0.0.1:11434"), "test-model", "high", "0", new Dictionary())); + using JsonDocument schema = JsonDocument.Parse("""{"type":"object"}"""); + + await client.ChatStructuredAsync([new OllamaMessage("user", "json")], null, schema.RootElement.Clone(), false, + TestContext.Current.CancellationToken); + + using JsonDocument document = JsonDocument.Parse(body!); + Assert.False(document.RootElement.GetProperty("think").GetBoolean()); + } + [Fact] public async Task Transient_failure_retries_three_times_with_configured_backoff() { -- cgit v1.2.3