remove reasoning guard
This commit is contained in:
+6
-283
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user