remove reasoning guard

This commit is contained in:
2026-09-08 18:39:04 +02:00
parent acad70aaac
commit 6dae58bf42
9 changed files with 29 additions and 1536 deletions
+6 -283
View File
@@ -7,7 +7,6 @@ using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
@@ -26,48 +25,13 @@ namespace AnotherReplayReader.Utils
Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)] string? Content = null,
[property: System.Text.Json.Serialization.JsonPropertyName("reasoning_content"),
System.Text.Json.Serialization.JsonIgnore(
Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)] string? ReasoningContent = null,
[property: System.Text.Json.Serialization.JsonPropertyName("tool_calls"),
System.Text.Json.Serialization.JsonIgnore(
Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)] IReadOnlyList<ChatMessage.ToolCall>? ToolCalls = null,
[property: System.Text.Json.Serialization.JsonPropertyName("tool_call_id"),
System.Text.Json.Serialization.JsonIgnore(
Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)] string? ToolCallId = null)
Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)] string? ReasoningContent = null)
{
public sealed record ToolCall(
[property: System.Text.Json.Serialization.JsonPropertyName("id")] string Id,
[property: System.Text.Json.Serialization.JsonPropertyName("type")] string Type,
[property: System.Text.Json.Serialization.JsonPropertyName("function")] ToolCallFunction Function);
public sealed record ToolCallFunction(
[property: System.Text.Json.Serialization.JsonPropertyName("name")] string Name,
[property: System.Text.Json.Serialization.JsonPropertyName("arguments")] string Arguments);
public static ChatMessage Assistant(string? content, string? reasoningContent = null) =>
new(
"assistant",
string.IsNullOrEmpty(content) ? null : content,
string.IsNullOrEmpty(reasoningContent) ? null : reasoningContent);
public static ChatMessage AssistantToolCall(
string? reasoningContent,
string toolCallId,
string toolName,
string arguments) =>
new(
"assistant",
null,
string.IsNullOrEmpty(reasoningContent) ? null : reasoningContent,
new[]
{
new ToolCall(
toolCallId,
"function",
new ToolCallFunction(toolName, arguments))
});
public static ChatMessage ToolResult(string toolCallId, string content) =>
new("tool", content, null, null, toolCallId);
}
public static string GetSystemPrompt(
@@ -1277,8 +1241,6 @@ PlayerA: 开始出兵
public enum AIChunkType
{
Reasoning,
ReasoningGuard,
ReasoningGuardRequest,
Content,
Error,
Json
@@ -1294,13 +1256,6 @@ PlayerA: 开始出兵
{
public string Response;
public string Reasoning;
public string? ReasoningFormatState;
public bool ReasoningInterrupted;
public bool ContinuationApplied;
public string? ReasoningContinuationError;
public string? RequestJson;
public string? ReasoningOriginalRequestJson;
public string? ReasoningContinuationRequestJson;
public int? PromptTokens;
public int? CompletionTokens;
public int? TotalTokens;
@@ -1329,226 +1284,19 @@ PlayerA: 开始出兵
Action<AIChunk>? onChunk,
CancellationToken cancellationToken)
{
var model = requestContext.Model;
var guardEnabled = model.ReasoningGuardEnabled && model.IsStream;
if (!guardEnabled)
{
return await Task.Run(
() => DoRequest(_http, messages, requestContext, onChunk, cancellationToken),
cancellationToken);
}
var tokenLimit = AiReasoningGuard.GetEffectiveTokenLimit(
requestContext.Provider, model);
var firstResult = await Task.Run(
() => DoRequest(
_http,
messages,
requestContext,
onChunk,
cancellationToken,
tokenLimit),
return await Task.Run(
() => DoRequest(_http, messages, requestContext, onChunk, cancellationToken),
cancellationToken);
if (!firstResult.ReasoningInterrupted)
{
return firstResult;
}
var continuationMessages = BuildReasoningContinuationMessages(
messages,
firstResult.Reasoning,
tokenLimit,
out var formatState);
firstResult.ReasoningFormatState = formatState;
var continuationContext = BuildReasoningToolContext(requestContext);
var originalRequestJson = firstResult.RequestJson;
var continuationRequestJson = SerializeRequestParams(
continuationMessages,
continuationContext,
writeIndented: true);
var continuationPromptTokens = 0;
foreach (var message in continuationMessages)
{
continuationPromptTokens += AiContextBudget.EstimateTokens(
(message.Content ?? string.Empty)
+ "\n"
+ (message.ReasoningContent ?? string.Empty));
}
var continuationCheck = AiContextBudget.CheckRequestUsage(
continuationPromptTokens,
requestContext.Provider,
continuationContext.Model);
var continuationBudgetWarning = string.Empty;
if (continuationCheck.Block)
{
var message = "续写请求未通过预算检查:" + continuationCheck.Message;
if (!string.IsNullOrEmpty(firstResult.Response))
{
firstResult.ReasoningContinuationError = message;
firstResult.ContinuationApplied = false;
firstResult.ReasoningOriginalRequestJson = originalRequestJson;
firstResult.ReasoningContinuationRequestJson =
continuationRequestJson;
return firstResult;
}
throw new InvalidOperationException(message);
}
if (!continuationCheck.IsOk)
{
continuationBudgetWarning = continuationCheck.Message;
}
onChunk?.Invoke(new AIChunk
{
Type = AIChunkType.ReasoningGuardRequest,
Text = JsonSerializer.Serialize(
new
{
originalRequestJson,
continuationRequestJson
},
new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
})
});
Result secondResult = default;
var continuationError = string.Empty;
try
{
secondResult = await Task.Run(
() => DoRequest(
_http,
continuationMessages,
continuationContext,
onChunk,
cancellationToken,
tokenLimit),
cancellationToken);
}
catch (Exception ex) when (
!cancellationToken.IsCancellationRequested
&& ex is not OperationCanceledException)
{
continuationError = ex.Message;
}
if (!string.IsNullOrEmpty(continuationError))
{
if (!string.IsNullOrEmpty(firstResult.Response))
{
firstResult.ReasoningContinuationError =
"续写请求失败,已保留首次部分正文:" + continuationError;
firstResult.ContinuationApplied = false;
firstResult.ReasoningOriginalRequestJson = originalRequestJson;
firstResult.ReasoningContinuationRequestJson =
continuationRequestJson;
return firstResult;
}
throw new InvalidOperationException(
"推理续写失败,且没有可用的部分回答:" + continuationError);
}
if (secondResult.ReasoningInterrupted)
{
if (!string.IsNullOrEmpty(secondResult.Response))
{
secondResult.ReasoningContinuationError =
"续写仍超过推理上限,已使用续写部分正文。";
secondResult.ContinuationApplied = false;
secondResult.ReasoningOriginalRequestJson = originalRequestJson;
secondResult.ReasoningContinuationRequestJson =
continuationRequestJson;
return secondResult;
}
if (!string.IsNullOrEmpty(firstResult.Response))
{
firstResult.ReasoningContinuationError =
"续写仍超过推理上限,已回退到首次部分正文。";
firstResult.ContinuationApplied = false;
firstResult.ReasoningOriginalRequestJson = originalRequestJson;
firstResult.ReasoningContinuationRequestJson =
continuationRequestJson;
return firstResult;
}
throw new InvalidOperationException(
"推理续写仍超过上限,且没有可用的部分回答。");
}
secondResult.ContinuationApplied = true;
secondResult.ReasoningOriginalRequestJson = originalRequestJson;
secondResult.ReasoningContinuationRequestJson =
continuationRequestJson;
if (!string.IsNullOrEmpty(continuationBudgetWarning))
{
secondResult.ReasoningContinuationError = continuationBudgetWarning;
}
return secondResult;
}
private static ImmutableList<ChatMessage> BuildReasoningContinuationMessages(
ImmutableList<ChatMessage> messages,
string reasoning,
int tokenLimit,
out string formatState)
{
var prepared = AiReasoningGuard.PrepareReasoning(reasoning, tokenLimit);
formatState = prepared.FormatState;
var instruction = AiReasoningGuard.BuildToolInstruction();
var arguments = JsonSerializer.Serialize(new { instruction });
return messages
.Add(ChatMessage.AssistantToolCall(
prepared.Text,
AiReasoningGuard.ToolCallId,
AiReasoningGuard.ToolName,
arguments))
.Add(ChatMessage.ToolResult(
AiReasoningGuard.ToolCallId,
AiReasoningGuard.BuildToolResult()));
}
private static AiRequestContext BuildReasoningToolContext(
AiRequestContext context)
{
var model = new AiModel
{
ModelId = context.Model.ModelId,
DisplayName = context.Model.DisplayName,
IsStream = context.Model.IsStream,
ContextLength = context.Model.ContextLength,
ContextBudget = context.Model.ContextBudget,
ReasoningGuardEnabled = context.Model.ReasoningGuardEnabled,
ReasoningGuardTokenLimit = context.Model.ReasoningGuardTokenLimit,
ExtraParameters = new Dictionary<string, object>(
context.Model.ExtraParameters)
};
model.ExtraParameters["tools"] =
new[] { AiReasoningGuard.BuildToolDefinition() };
model.ExtraParameters["tool_choice"] = "none";
return new AiRequestContext(context.Provider, model);
}
private static string SerializeRequestParams(
ImmutableList<ChatMessage> messages,
AiRequestContext context,
bool writeIndented = false)
AiRequestContext context)
{
var requestParams = ProcessRequestParams(
messages,
context.BuildRequestParams());
return JsonSerializer.Serialize(
requestParams,
writeIndented
? new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
}
: null);
return JsonSerializer.Serialize(requestParams);
}
private static async Task<Result> DoRequest(
@@ -1556,8 +1304,7 @@ PlayerA: 开始出兵
ImmutableList<ChatMessage> messages,
AiRequestContext requestContext,
Action<AIChunk>? onChunk,
CancellationToken cancellationToken,
int? reasoningTokenLimit = null)
CancellationToken cancellationToken)
{
var provider = requestContext.Provider;
var isStream = requestContext.Model.IsStream;
@@ -1579,8 +1326,6 @@ PlayerA: 开始出兵
var fullBuilder = new StringBuilder();
var reasoningBuilder = new StringBuilder();
var result = new Result();
result.RequestJson = inputJson;
var interrupted = false;
// 根据模式分别读取响应
// if response.Content.Headers.ContentType is "text/event-stream", then it's stream mode, otherwise it's non-stream mode
@@ -1625,20 +1370,6 @@ PlayerA: 开始出兵
reasoningBuilder,
result,
onChunk);
if (reasoningTokenLimit is { } guardLimit
&& guardLimit > 0
&& AiContextBudget.EstimateTokens(reasoningBuilder.ToString())
>= guardLimit)
{
interrupted = true;
onChunk?.Invoke(new AIChunk
{
Type = AIChunkType.ReasoningGuard,
Text = "检测到推理内容超过保护阈值,正在请求 AI 尽快收尾..."
});
break;
}
}
}
else
@@ -1656,14 +1387,6 @@ PlayerA: 开始出兵
response.EnsureSuccessStatusCode();
result.Reasoning = reasoningBuilder.ToString();
result.ReasoningFormatState = AiReasoningGuard.DescribeFormatState(
result.Reasoning);
if (interrupted)
{
result.Response = fullBuilder.ToString();
result.ReasoningInterrupted = true;
return result;
}
if (fullBuilder.Length == 0)
{