wip
This commit is contained in:
+86
-19
@@ -295,8 +295,8 @@ namespace AnotherReplayReader
|
||||
var budget = AiContextBudget.GetContextBudget(model);
|
||||
var hardCap = model.ContextLength > 0
|
||||
? (int)(model.ContextLength * AiContextBudget.HardUsageRatio)
|
||||
: 48_000;
|
||||
var effectiveBudget = budget > 0 ? budget : hardCap;
|
||||
: 128_000; // 上下文未知时的保守默认;仅用于“预算为 0 时的单 slice”判断。
|
||||
var effectiveBudget = budget > 0 ? budget : 0;
|
||||
var headroom = AiContextBudget.GetOutputHeadroom(requestContext.Provider, model);
|
||||
var fixedOverhead = AiContextBudget.EstimateTokens(_systemPrompt)
|
||||
+ 4_000 // digest
|
||||
@@ -304,7 +304,25 @@ namespace AnotherReplayReader
|
||||
+ 1_500 // instruction
|
||||
+ 10_000 // findings reserve
|
||||
+ headroom;
|
||||
var sliceBudget = Math.Max(effectiveBudget - fixedOverhead, MechanicalSegmenter.MinSliceTokens);
|
||||
var logTokens = AiContextBudget.EstimateTokens(replayData);
|
||||
int sliceBudget;
|
||||
if (effectiveBudget <= 0)
|
||||
{
|
||||
// 没有上下文预算(128K 及以下或未知):只允许短录像整局作为一个 slice。
|
||||
if (logTokens + fixedOverhead > hardCap)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"模型没有足够的上下文预算({FormatNumber(budget, false)} token),日志约 {FormatNumber(logTokens, false)} token,"
|
||||
+ $"加上固定开销后超过单 slice 上限 {FormatNumber(hardCap, false)} token。"
|
||||
+ "请改用更长上下文的模型,或缩短操作记录。");
|
||||
}
|
||||
sliceBudget = Math.Max(logTokens, MechanicalSegmenter.MinSliceTokens);
|
||||
AppendLog("上下文预算提示", "模型上下文较短,仅按单 slice 处理整局;长录像质量不保证。", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
sliceBudget = Math.Max(effectiveBudget - fixedOverhead, MechanicalSegmenter.MinSliceTokens);
|
||||
}
|
||||
var (slices, sliceWarnings) = MechanicalSegmenter.Slice(
|
||||
replayData, eventSpans, sliceBudget, sliceBudget / 12);
|
||||
_slices = slices;
|
||||
@@ -321,7 +339,7 @@ namespace AnotherReplayReader
|
||||
"v2 管线准备",
|
||||
$"上下文预算: {FormatNumber(budget, false)} token(0 = 不支持长录像)\r\n"
|
||||
+ $"分段预算: {FormatNumber(sliceBudget, false)} token,实际切分 {_slices.Length} 段\r\n"
|
||||
+ $"日志总量: {FormatNumber(AiContextBudget.EstimateTokens(replayData), false)} token\r\n"
|
||||
+ $"日志总量: {FormatNumber(logTokens, false)} token\r\n"
|
||||
+ $"[AI: {requestContext.Provider.Name}/{requestContext.Model.ModelId}]",
|
||||
false);
|
||||
|
||||
@@ -334,15 +352,25 @@ namespace AnotherReplayReader
|
||||
var overviewMessages = BuildSystemMessages(_systemPrompt, _digest, null)
|
||||
.Add(new AIAnalyze.ChatMessage("user", AIAnalyze.BuildOverviewUserPrompt(_slices)));
|
||||
CheckAndLogContextUsage(overviewMessages, requestContext);
|
||||
var overviewResult = await _analyzer.CompleteAsync(
|
||||
overviewMessages,
|
||||
requestContext,
|
||||
OnChunk,
|
||||
_linkedCts.Token);
|
||||
UpdateTokenDisplay(overviewResult);
|
||||
_overview = OverviewParser.Parse(overviewResult.Response);
|
||||
_overviewNarrative = _overview.Narrative;
|
||||
AppendLog("整局总览", overviewResult.Response, false);
|
||||
AIAnalyze.Result overviewResult = default;
|
||||
var overviewAttempt = 0;
|
||||
do
|
||||
{
|
||||
overviewResult = await _analyzer.CompleteAsync(
|
||||
overviewMessages,
|
||||
requestContext,
|
||||
OnChunk,
|
||||
_linkedCts.Token);
|
||||
UpdateTokenDisplay(overviewResult);
|
||||
_overview = OverviewParser.Parse(overviewResult.Response);
|
||||
_overviewNarrative = _overview.Narrative;
|
||||
AppendLog(
|
||||
overviewAttempt == 0 ? "整局总览" : "整局总览(重试)",
|
||||
overviewResult.Response,
|
||||
false);
|
||||
overviewAttempt++;
|
||||
}
|
||||
while (_overview.Segments.IsEmpty && overviewAttempt < 2);
|
||||
if (_overview.Segments.IsEmpty)
|
||||
{
|
||||
AppendLog("总览解析警告", "未解析到 [分段概述] 块,将直接使用机械分段。", false);
|
||||
@@ -413,11 +441,22 @@ namespace AnotherReplayReader
|
||||
_lastContentParagraph = null;
|
||||
|
||||
var eventCount = _eventCounts?.Query(slice.Start, slice.End) ?? slice.EventCount;
|
||||
var title = _overview.Segments.FirstOrDefault(s => s.Index == segmentIndex)?.Title;
|
||||
var overviewEntry = _overview.Segments.FirstOrDefault(s => s.Index == segmentIndex);
|
||||
var title = overviewEntry?.Title;
|
||||
var sliceText = slice.GetText(_replayData);
|
||||
var findingsText = string.Join("\n\n", _findings);
|
||||
var modelBudget = AiContextBudget.GetContextBudget(requestContext.Model);
|
||||
var maxFindingsTokens = modelBudget > 0
|
||||
? Math.Min(10_000, Math.Max(2_000, modelBudget / 10))
|
||||
: 4_000;
|
||||
var findingsText = LimitFindings(_findings, maxFindingsTokens);
|
||||
var instruction = AIAnalyze.BuildSegmentUserPromptV2(
|
||||
_currentSegmentIndex, totalSegments, slice, eventCount, title);
|
||||
_currentSegmentIndex,
|
||||
totalSegments,
|
||||
slice,
|
||||
eventCount,
|
||||
title,
|
||||
overviewEntry?.Description,
|
||||
overviewEntry?.BackqueryHints);
|
||||
|
||||
var messages = BuildSystemMessages(_systemPrompt, _digest, _overviewNarrative)
|
||||
.Add(new AIAnalyze.ChatMessage("user", sliceText));
|
||||
@@ -441,6 +480,8 @@ namespace AnotherReplayReader
|
||||
string segmentResponse;
|
||||
while (true)
|
||||
{
|
||||
// 回查会增长同一会话的消息列表,每次请求前都重新做预算检查。
|
||||
CheckAndLogContextUsage(segmentMessages, requestContext);
|
||||
var segmentResult = await _analyzer!.CompleteAsync(
|
||||
segmentMessages,
|
||||
requestContext,
|
||||
@@ -506,10 +547,13 @@ namespace AnotherReplayReader
|
||||
if (validationResult.RequiresRevision)
|
||||
{
|
||||
var issueCount = validationResult.Issues.Count(
|
||||
i => i.Severity == AIValidationSeverity.Contradiction);
|
||||
i => i.Severity is AIValidationSeverity.Contradiction
|
||||
or AIValidationSeverity.Fatal
|
||||
or AIValidationSeverity.Warning
|
||||
or AIValidationSeverity.WeakEvidence);
|
||||
AppendLog(
|
||||
$"第{segmentIndex}段修订",
|
||||
$"验证器发现 {issueCount} 个矛盾问题,正在请求 AI 修正...",
|
||||
$"验证器发现 {issueCount} 个需要修正/降级的问题,正在请求 AI 修正...",
|
||||
false);
|
||||
var relevantFacts = RelevantFactsFormatter.Format(validationResult.Claims, _factIndex!);
|
||||
var revisionPrompt = AIAnalyze.BuildRevisionUserPrompt(
|
||||
@@ -655,7 +699,7 @@ namespace AnotherReplayReader
|
||||
{
|
||||
total += AiContextBudget.EstimateTokens(message.Content);
|
||||
}
|
||||
var check = AiContextBudget.CheckPromptUsage(
|
||||
var check = AiContextBudget.CheckRequestUsage(
|
||||
total, requestContext.Provider, requestContext.Model);
|
||||
if (check.Block)
|
||||
{
|
||||
@@ -678,6 +722,29 @@ namespace AnotherReplayReader
|
||||
return text.Substring(0, chars) + "\n…(已截断)";
|
||||
}
|
||||
|
||||
/// <summary>已发现事实按 token 预算保留最近若干条,避免跨段事实无限增长。</summary>
|
||||
private static string LimitFindings(IReadOnlyList<string> findings, int maxTokens)
|
||||
{
|
||||
if (findings.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
var selected = new List<string>();
|
||||
var totalTokens = 0;
|
||||
for (var i = findings.Count - 1; i >= 0; --i)
|
||||
{
|
||||
var entry = findings[i];
|
||||
var tokens = AiContextBudget.EstimateTokens(entry);
|
||||
if (selected.Count > 0 && totalTokens + tokens > maxTokens)
|
||||
{
|
||||
break;
|
||||
}
|
||||
selected.Insert(0, entry);
|
||||
totalTokens += tokens;
|
||||
}
|
||||
return string.Join("\n\n", selected);
|
||||
}
|
||||
|
||||
// ---- 块追加与折叠 ----
|
||||
private void OnChunk(AIAnalyze.AIChunk chunk)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user