Add per-answer thinking disclosure

This commit is contained in:
Peer Hogeterp committed 2026-09-10 15:37:43 +02:00
1 parent c7b42bee96
commit bbbcdddc91
29 files changed
+360 -37

No files matched your search

@@ -273,7 +273,6 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
catch (ProviderRequestException e)
{
logger.LogError(e, "The provider request failed for chat generation job '{JobId}'. Status={StatusCode}, Reason='{ReasonPhrase}', Body='{ResponseBody}'", state.Snapshot.JobId, e.StatusCode, e.ReasonPhrase, e.ResponseBody);
RemoveEmptyAIResponse(state);
await this.CompleteChatGenerationAsync(state, AIJobStatus.FAILED, e.UserMessage);
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, e.UserMessage));
}
@@ -302,7 +301,7 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
var aiText = request.AIText;
aiText.InitialRemoteWait = false;
aiText.IsStreaming = false;
aiText.Text = aiText.Text.RemoveThinkTags().Trim();
aiText.FinalizeStreamContent();
RemoveEmptyAIResponse(state);
@@ -367,7 +366,7 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
return;
var aiText = request.AIText;
if (!string.IsNullOrWhiteSpace(aiText.Text))
if (!string.IsNullOrWhiteSpace(aiText.Text) || !string.IsNullOrWhiteSpace(aiText.Thinking))
return;
var aiBlock = request.ChatThread.Blocks
@@ -399,8 +398,7 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
var aiText = state.ChatGenerationRequest.AIText;
aiText.InitialRemoteWait = false;
aiText.IsStreaming = true;
aiText.Text += contentStreamChunk;
aiText.Sources.MergeSources(contentStreamChunk.Sources);
aiText.ApplyStreamChunk(contentStreamChunk);
if (state.Snapshot.Status is not AIJobStatus.RUNNING)
{
@@ -519,4 +517,4 @@ public sealed class AIJobService(SettingsManager settingsManager, MessageBus mes
logger.LogWarning("Skipping AI request because model '{ModelId}' is not available from '{ProviderInstanceName}' (provider={ProviderType}).", chatModel.Id, provider.InstanceName, provider.Provider);
return false;
}
}
}
@@ -28,6 +28,7 @@ public sealed class ToolCallingLoop(ILogger<ToolCallingLoop> logger) : IToolCall
var toolCallCount = 0;
var toolResultCharacterCount = 0L;
var toolSources = new List<Source>();
var hasThinkingOutput = false;
while (true)
{
@@ -47,6 +48,13 @@ public sealed class ToolCallingLoop(ILogger<ToolCallingLoop> logger) : IToolCall
toolSources.MergeSources(round.Sources);
if (!string.IsNullOrWhiteSpace(round.ThinkingOutput))
{
var separator = hasThinkingOutput ? $"{Environment.NewLine}{Environment.NewLine}" : string.Empty;
yield return new ContentStreamChunk(string.Empty, $"{separator}{round.ThinkingOutput}", []);
hasThinkingOutput = true;
}
//
// A call without an ID cannot be answered: the provider correlates the result by that
// ID, and inventing one would have the next request rejected. Nothing can be salvaged
@@ -168,4 +176,4 @@ public sealed class ToolCallingLoop(ILogger<ToolCallingLoop> logger) : IToolCall
private static string GetDisplayName(ToolCallingLoopContext context, string toolName) => context.RunnableTools
.FirstOrDefault(tool => tool.Definition.Function.Name.Equals(toolName, StringComparison.Ordinal))
.Implementation?.GetDisplayName() ?? toolName;
}
}
@@ -5,6 +5,7 @@ namespace AIStudio.Tools.ToolCallingSystem.Harness;
/// longer depends on the provider API it came from.
/// </summary>
/// <param name="TextOutput">The text the model produced, empty when it only requested tool calls.</param>
/// <param name="ThinkingOutput">The human-readable thinking the provider exposed.</param>
/// <param name="Calls">The tool calls the model requested, empty when it answered instead.</param>
/// <param name="Sources">Sources the provider itself attached, such as those of a provider-native web search.</param>
public sealed record ToolCallingRound(string TextOutput, IReadOnlyList<ToolCallingRequestedCall> Calls, IReadOnlyList<ISource> Sources);
public sealed record ToolCallingRound(string TextOutput, string ThinkingOutput, IReadOnlyList<ToolCallingRequestedCall> Calls, IReadOnlyList<ISource> Sources);