尝试重新思考得更短
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AnotherReplayReader.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 推理保护策略:截断 reasoning_content,并通过研究验证过的 tool call 历史
|
||||
/// 让模型从已有推理自然收尾。
|
||||
/// </summary>
|
||||
internal static class AiReasoningGuard
|
||||
{
|
||||
public const string ToolName = "analysis_hint";
|
||||
public const string TruncationMarker = "[INTERNAL_REASONING_TRUNCATED]";
|
||||
public const string WrapUpStatement =
|
||||
"我已经整理出足够的信息,现在立即结束内部推理,直接输出最终回答。";
|
||||
public const string ToolCallId = "call_reasoning_guard";
|
||||
public const int MinTokenLimit = 4096;
|
||||
|
||||
public static int GetEffectiveTokenLimit(AiProvider provider, AiModel model)
|
||||
{
|
||||
if (model.ReasoningGuardTokenLimit is { } explicitLimit && explicitLimit > 0)
|
||||
{
|
||||
return explicitLimit;
|
||||
}
|
||||
|
||||
return Math.Max(MinTokenLimit, provider.DefaultMaxTokens / 2);
|
||||
}
|
||||
|
||||
public static string BuildToolInstruction()
|
||||
{
|
||||
return "请继续你本次 tool_calls 消息中携带的 reasoning_content。"
|
||||
+ "你已经完成了足够的分析,现在进入收尾阶段:"
|
||||
+ "立即停止继续展开推理,不要再逐条枚举输入事件,不要再做新的检查,也不要继续“再确认一下”。"
|
||||
+ "请直接输出最终结果,包括正文、[机器可读声明] 和 [小结]。"
|
||||
+ "如果某些细节不确定,就用证据等级说明。";
|
||||
}
|
||||
|
||||
public static string BuildToolResult()
|
||||
{
|
||||
return "工具结果:" + BuildToolInstruction()
|
||||
+ "\n你本次 tool_calls 消息中携带的 reasoning_content 的最后一句已经宣告收尾,"
|
||||
+ "请立即执行,不要继续讨论是否还需要分析。"
|
||||
+ "\n这份推理内容末尾有内部标记 " + TruncationMarker
|
||||
+ ",表示前面的推理已被安全截断。"
|
||||
+ "\n如果旧推理中存在未完成的 JSON、JSON 代码块或其他结构性内容,"
|
||||
+ "请丢弃其未完成部分,并重新输出完整、格式正确的结果。"
|
||||
+ "\n不要复述该标记。";
|
||||
}
|
||||
|
||||
public static object BuildToolDefinition()
|
||||
{
|
||||
return new
|
||||
{
|
||||
type = "function",
|
||||
function = new
|
||||
{
|
||||
name = ToolName,
|
||||
description = "为当前内部推理提供收尾提示。",
|
||||
parameters = new
|
||||
{
|
||||
type = "object",
|
||||
properties = new
|
||||
{
|
||||
instruction = new
|
||||
{
|
||||
type = "string",
|
||||
description = "模型应遵循的收尾提示。"
|
||||
}
|
||||
},
|
||||
required = new[] { "instruction" }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保留尽可能多的推理前缀,并在末尾追加截断标记与收尾决定。
|
||||
/// </summary>
|
||||
public static (string Text, string FormatState) PrepareReasoning(
|
||||
string reasoning,
|
||||
int tokenLimit)
|
||||
{
|
||||
if (string.IsNullOrEmpty(reasoning))
|
||||
{
|
||||
return ("\n\n" + TruncationMarker + "\n\n" + WrapUpStatement, "无格式问题");
|
||||
}
|
||||
|
||||
var maxIndex = FindMaxPrefixIndexByTokens(reasoning, tokenLimit);
|
||||
var boundary = FindSafeBoundary(reasoning, maxIndex);
|
||||
var prefix = reasoning.Substring(0, boundary).TrimEnd();
|
||||
var formatState = DescribeFormatState(prefix);
|
||||
return (
|
||||
prefix + "\n\n" + TruncationMarker + "\n\n" + WrapUpStatement,
|
||||
formatState);
|
||||
}
|
||||
|
||||
public static string DescribeFormatState(string text)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
var codeFenceCount = CountOccurrences(text, "```");
|
||||
if (codeFenceCount % 2 == 1)
|
||||
{
|
||||
lines.Add("代码围栏未闭合");
|
||||
}
|
||||
|
||||
var braceDelta = CountDifference(text, '{', '}');
|
||||
var bracketDelta = CountDifference(text, '[', ']');
|
||||
if (braceDelta > 0 || bracketDelta > 0)
|
||||
{
|
||||
lines.Add("JSON/方括号结构可能未闭合");
|
||||
}
|
||||
|
||||
return lines.Count == 0 ? "无格式问题" : string.Join(";", lines);
|
||||
}
|
||||
|
||||
private static int FindMaxPrefixIndexByTokens(string text, int tokenLimit)
|
||||
{
|
||||
var low = 0;
|
||||
var high = text.Length;
|
||||
while (low < high)
|
||||
{
|
||||
var mid = (low + high + 1) / 2;
|
||||
if (AiContextBudget.EstimateTokens(text.Substring(0, mid)) <= tokenLimit)
|
||||
{
|
||||
low = mid;
|
||||
}
|
||||
else
|
||||
{
|
||||
high = mid - 1;
|
||||
}
|
||||
}
|
||||
return low;
|
||||
}
|
||||
|
||||
private static int FindSafeBoundary(string text, int maxIndex)
|
||||
{
|
||||
const int searchBack = 160;
|
||||
var start = Math.Max(0, maxIndex - searchBack);
|
||||
for (var i = maxIndex - 1; i >= start; --i)
|
||||
{
|
||||
var c = text[i];
|
||||
if (c == '\n' || c == '。' || c == ';' || c == ','
|
||||
|| c == ':' || c == ';' || c == '.'
|
||||
|| c == ',' || c == ')' || c == '}' || c == ']')
|
||||
{
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return maxIndex;
|
||||
}
|
||||
|
||||
private static int CountOccurrences(string text, string value)
|
||||
{
|
||||
var count = 0;
|
||||
var index = 0;
|
||||
while ((index = text.IndexOf(value, index, StringComparison.Ordinal)) >= 0)
|
||||
{
|
||||
count++;
|
||||
index += value.Length;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static int CountDifference(string text, char open, char close)
|
||||
{
|
||||
var delta = 0;
|
||||
foreach (var c in text)
|
||||
{
|
||||
if (c == open)
|
||||
{
|
||||
delta++;
|
||||
}
|
||||
else if (c == close)
|
||||
{
|
||||
delta--;
|
||||
}
|
||||
}
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user