mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-08 14:49:40 +00:00
Add per-answer thinking disclosure
This commit is contained in:
1 parent
c7b42bee96
commit
bbbcdddc91
29 files changed
+360
-37
No files matched your search
@@ -12,5 +12,11 @@ public sealed record AssistantToolCallMessage : IMessageBase
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? ReasoningContent { get; init; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string? Reasoning { get; init; }
|
||||
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public IList<JsonElement>? ReasoningDetails { get; init; }
|
||||
|
||||
public IList<ChatCompletionToolCall> ToolCalls { get; init; } = [];
|
||||
}
|
||||
@@ -13,4 +13,13 @@ public sealed record ChatCompletionDelta
|
||||
|
||||
[JsonIgnore]
|
||||
public string Content => ChatCompletionContent.GetText(this.RawContent) ?? string.Empty;
|
||||
|
||||
public string? ReasoningContent { get; init; }
|
||||
|
||||
public string? Reasoning { get; init; }
|
||||
|
||||
public IList<JsonElement>? ReasoningDetails { get; init; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string Thinking => ThinkingContent.Get(this.ReasoningContent, this.Reasoning, this.ReasoningDetails);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public record ChatCompletionDeltaStreamLine(string Id, string Object, uint Creat
|
||||
public bool ContainsContent() => this.Choices.Count > 0;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ContentStreamChunk GetContent() => new(this.Choices[0].Delta.Content, []);
|
||||
public ContentStreamChunk GetContent() => new(this.Choices[0].Delta.Content, this.Choices[0].Delta.Thinking, []);
|
||||
|
||||
#region Implementation of IAnnotationStreamLine
|
||||
|
||||
@@ -42,4 +42,4 @@ public record ChatCompletionDeltaStreamLine(string Id, string Object, uint Creat
|
||||
public IList<ISource> GetSources() => [];
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -15,5 +15,11 @@ public sealed record ChatCompletionResponseMessage
|
||||
|
||||
public string? ReasoningContent { get; init; }
|
||||
|
||||
public string? Reasoning { get; init; }
|
||||
|
||||
public IList<JsonElement>? ReasoningDetails { get; init; }
|
||||
|
||||
public IList<ChatCompletionToolCall?>? ToolCalls { get; init; }
|
||||
|
||||
public string GetThinkingOutput() => ThinkingContent.Get(this.ReasoningContent, this.Reasoning, this.ReasoningDetails);
|
||||
}
|
||||
@@ -72,6 +72,7 @@ public sealed class ChatCompletionToolCallingAdapter<TRequest>(
|
||||
|
||||
return new ToolCallingRound(
|
||||
responseChoice.Message.Content ?? string.Empty,
|
||||
responseChoice.Message.GetThinkingOutput(),
|
||||
preparedCalls
|
||||
.Select(x => new ToolCallingRequestedCall(x.ToolCall.Id!, x.ToolCall.Function!.Name!, x.ToolCall.Function!.Arguments!, x.IsValid))
|
||||
.ToList(),
|
||||
@@ -83,6 +84,8 @@ public sealed class ChatCompletionToolCallingAdapter<TRequest>(
|
||||
{
|
||||
Content = this.lastResponseMessage?.RawContent,
|
||||
ReasoningContent = this.lastResponseMessage?.ReasoningContent,
|
||||
Reasoning = this.lastResponseMessage?.Reasoning,
|
||||
ReasoningDetails = this.lastResponseMessage?.ReasoningDetails,
|
||||
ToolCalls = this.lastToolCalls,
|
||||
});
|
||||
|
||||
@@ -168,4 +171,4 @@ public sealed class ChatCompletionToolCallingAdapter<TRequest>(
|
||||
}
|
||||
|
||||
private readonly record struct PreparedChatCompletionToolCall(ChatCompletionToolCall ToolCall, bool IsValid);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,11 @@ public record ResponsesDeltaStreamLine(
|
||||
public bool ContainsContent() => this.Delta is not null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ContentStreamChunk GetContent() => new(this.Delta ?? string.Empty, this.GetSources());
|
||||
public ContentStreamChunk GetContent() => this.Type switch
|
||||
{
|
||||
"response.reasoning_summary_text.delta" or "response.reasoning_text.delta" => new(string.Empty, this.Delta ?? string.Empty, this.GetSources()),
|
||||
_ => new(this.Delta ?? string.Empty, string.Empty, this.GetSources()),
|
||||
};
|
||||
|
||||
//
|
||||
// Please note that there are multiple options where LLM providers might stream sources:
|
||||
@@ -36,4 +40,4 @@ public record ResponsesDeltaStreamLine(
|
||||
public IList<ISource> GetSources() => [];
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,11 @@ public sealed record ResponsesResponse
|
||||
}));
|
||||
}
|
||||
|
||||
public string GetThinkingOutput() => string.Concat(this.Output
|
||||
.Where(x => ReadString(x, "type").Equals("reasoning", StringComparison.Ordinal))
|
||||
.SelectMany(x => ReadArrayItems(x, "summary").Concat(ReadArrayItems(x, "content")))
|
||||
.Select(x => ReadString(x, "text")));
|
||||
|
||||
public IReadOnlyList<Source> GetSources() => this.Output
|
||||
.Where(x => ReadString(x, "type").Equals("message", StringComparison.Ordinal))
|
||||
.SelectMany(ReadContentItems)
|
||||
|
||||
@@ -57,6 +57,7 @@ public sealed class ResponsesToolCallingAdapter(Model chatModel, IList<object> b
|
||||
this.lastResponse = response;
|
||||
return new ToolCallingRound(
|
||||
response.GetTextOutput(),
|
||||
response.GetThinkingOutput(),
|
||||
response.GetFunctionCalls()
|
||||
.Select(call => new ToolCallingRequestedCall(
|
||||
call.CallId ?? string.Empty,
|
||||
@@ -102,4 +103,4 @@ public sealed class ResponsesToolCallingAdapter(Model chatModel, IList<object> b
|
||||
.Concat(runnableTools.Select(x => (object)ProviderToolAdapters.ToResponsesTool(x.Definition)))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AIStudio.Provider.OpenAI;
|
||||
|
||||
/// <summary>
|
||||
/// Reads human-readable thinking from OpenAI-compatible response fields.
|
||||
/// </summary>
|
||||
internal static class ThinkingContent
|
||||
{
|
||||
public static string Get(string? reasoningContent, string? reasoning, IEnumerable<JsonElement>? reasoningDetails)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(reasoningContent))
|
||||
return reasoningContent;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(reasoning))
|
||||
return reasoning;
|
||||
|
||||
if (reasoningDetails is null)
|
||||
return string.Empty;
|
||||
|
||||
return string.Concat(reasoningDetails.Select(GetVisibleDetailText));
|
||||
}
|
||||
|
||||
private static string GetVisibleDetailText(JsonElement detail)
|
||||
{
|
||||
if (detail.ValueKind is not JsonValueKind.Object ||
|
||||
!detail.TryGetProperty("type", out var typeProperty) ||
|
||||
typeProperty.ValueKind is not JsonValueKind.String)
|
||||
return string.Empty;
|
||||
|
||||
return typeProperty.GetString() switch
|
||||
{
|
||||
"reasoning.text" => ReadString(detail, "text"),
|
||||
"reasoning.summary" => ReadString(detail, "summary"),
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
private static string ReadString(JsonElement item, string propertyName)
|
||||
{
|
||||
if (!item.TryGetProperty(propertyName, out var property) || property.ValueKind is not JsonValueKind.String)
|
||||
return string.Empty;
|
||||
|
||||
return property.GetString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user