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. --- src/Agent.Core/Ollama/OllamaClient.cs | 5 +++-- src/Agent.Core/Ollama/OllamaModels.cs | 7 ++++--- src/Agent.Core/Ollama/OllamaSettings.cs | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) (limited to 'src/Agent.Core/Ollama') diff --git a/src/Agent.Core/Ollama/OllamaClient.cs b/src/Agent.Core/Ollama/OllamaClient.cs index 428bca9..fea5dfb 100644 --- a/src/Agent.Core/Ollama/OllamaClient.cs +++ b/src/Agent.Core/Ollama/OllamaClient.cs @@ -57,7 +57,7 @@ public sealed class OllamaClient : IOllamaClient Model = _settings.Model, Messages = messages, Stream = false, - Think = thinkOverride ?? _settings.Think, + Think = thinkOverride is null ? _settings.Think : thinkOverride.Value, KeepAlive = _settings.KeepAlive, Options = options, Tools = tools?.Select(static tool => new OllamaToolDefinition("function", new OllamaFunctionDefinition(tool.Name, tool.Description, tool.Parameters))).ToArray(), @@ -85,7 +85,8 @@ public sealed class OllamaClient : IOllamaClient ?? throw new OllamaException("Ollama returned an empty response.", false); if (!string.IsNullOrWhiteSpace(result.Error)) throw new OllamaException(result.Error, false); if (result.Message is null) throw new OllamaException("Ollama response has no message.", false); - return new OllamaResult(result.Message.Content, result.PromptEvalCount, result.EvalCount, result.Message.ToolCalls ?? [], result.PromptEvalCount); + return new OllamaResult(result.Message.Content, result.PromptEvalCount, result.EvalCount, + result.Message.ToolCalls ?? [], result.PromptEvalCount, result.Message.Thinking); } catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException && !cancellationToken.IsCancellationRequested) { diff --git a/src/Agent.Core/Ollama/OllamaModels.cs b/src/Agent.Core/Ollama/OllamaModels.cs index f6ff092..9e6103e 100644 --- a/src/Agent.Core/Ollama/OllamaModels.cs +++ b/src/Agent.Core/Ollama/OllamaModels.cs @@ -9,7 +9,8 @@ public sealed record OllamaMessage( [property: JsonPropertyName("content")] string Content, [property: JsonPropertyName("images"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] IReadOnlyList? Images = null, [property: JsonPropertyName("tool_name"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? ToolName = null, - [property: JsonPropertyName("tool_calls"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] IReadOnlyList? ToolCalls = null); + [property: JsonPropertyName("tool_calls"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] IReadOnlyList? ToolCalls = null, + [property: JsonPropertyName("thinking"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? Thinking = null); public sealed record OllamaToolCall([property: JsonPropertyName("function")] OllamaFunctionCall Function); public sealed record OllamaFunctionCall( @@ -29,7 +30,7 @@ public sealed class OllamaChatRequest [JsonPropertyName("model")] public required string Model { get; init; } [JsonPropertyName("messages")] public required IReadOnlyList Messages { get; init; } [JsonPropertyName("stream")] public bool Stream { get; init; } - [JsonPropertyName("think")] public bool Think { get; init; } + [JsonPropertyName("think")] public required object Think { get; init; } [JsonPropertyName("keep_alive")] public required string KeepAlive { get; init; } [JsonPropertyName("options")] public required IReadOnlyDictionary Options { get; init; } [JsonPropertyName("tools"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IReadOnlyList? Tools { get; init; } @@ -44,4 +45,4 @@ public sealed class OllamaChatResponse [JsonPropertyName("error")] public string? Error { get; init; } } -public sealed record OllamaResult(string Content, long InputTokens, long OutputTokens, IReadOnlyList? ToolCalls = null, long PromptEvalCount = 0); +public sealed record OllamaResult(string Content, long InputTokens, long OutputTokens, IReadOnlyList? ToolCalls = null, long PromptEvalCount = 0, string? Thinking = null); diff --git a/src/Agent.Core/Ollama/OllamaSettings.cs b/src/Agent.Core/Ollama/OllamaSettings.cs index 94ab3ef..9f33208 100644 --- a/src/Agent.Core/Ollama/OllamaSettings.cs +++ b/src/Agent.Core/Ollama/OllamaSettings.cs @@ -5,6 +5,6 @@ namespace Agent.Core.Ollama; public sealed record OllamaSettings( Uri Endpoint, string Model, - bool Think, + object Think, string KeepAlive, IReadOnlyDictionary Options); -- cgit v1.2.3