focus on intervals, and fix provider

This commit is contained in:
2026-09-08 14:44:34 +02:00
parent 579cd8e4b3
commit acad70aaac
11 changed files with 1163 additions and 286 deletions
+195 -179
View File
@@ -1,4 +1,4 @@
using AnotherReplayReader.Apm;
using AnotherReplayReader.Apm;
using AnotherReplayReader.ReplayFile;
using AnotherReplayReader.Utils;
using System;
@@ -97,7 +97,6 @@ namespace AnotherReplayReader
// 输出段落
private (Paragraph? Think, Paragraph? Content)? _currentContent;
private Paragraph? _lastContentParagraph;
private bool _suppressDisplay;
private bool _nextThinkingIsContinuation;
// 进度计时
@@ -228,7 +227,6 @@ namespace AnotherReplayReader
_thinkBlockWasExpanded = false;
_currentContent = null;
_lastContentParagraph = null;
_suppressDisplay = false;
_nextThinkingIsContinuation = false;
_previousTotalTime = TimeSpan.Zero;
@@ -458,6 +456,8 @@ namespace AnotherReplayReader
var structuredKnowledge = StructuredKnowledge.GetForMod(
AIAnalyze.GetKnowledgeModName(_replay));
const int maxBackqueriesPerSegment = 3;
while (_currentSegmentIndex < _slices.Length)
{
_linkedCts.Token.ThrowIfCancellationRequested();
@@ -481,187 +481,229 @@ namespace AnotherReplayReader
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,
overviewEntry?.Description,
overviewEntry?.BackqueryHints);
var messages = BuildSystemMessages(_systemPrompt, _digest, _overviewNarrative)
.Add(new AIAnalyze.ChatMessage("user", sliceText));
if (!string.IsNullOrWhiteSpace(findingsText))
// 段内焦点窗口:数据保持整段切片(尽量长),分析重点落在窗口上。
var windows = FocusPlanner.Plan(slice, _eventSpans);
if (windows.IsEmpty)
{
messages = messages.Add(new AIAnalyze.ChatMessage(
"user", "之前各段的已发现事实:\n" + findingsText));
windows = ImmutableArray.Create(
new FocusWindow(0, slice.Start, slice.End, slice.EventCount, slice.EstimatedTokens));
}
messages = messages.Add(new AIAnalyze.ChatMessage("user", instruction));
AppendLog(
$"让 AI 分析第{segmentIndex}段...",
instruction + $"\r\n[AI: {requestContext.Provider.Name}/{requestContext.Model.ModelId}]",
true);
CheckAndLogContextUsage(messages, requestContext);
StartVersion("版本 1:正文", collapsedByDefault: false);
// 本段已发现事实:段内窗口之间的累积摘要(窗口内也会看到之前的段/窗口发现)。
var segmentFindings = new List<string>();
// 回查循环(M3):同一段会话内,模型可多次请求远处原始区间
const int maxBackqueriesPerSegment = 3;
var segmentMessages = messages;
var backqueryCount = 0;
string segmentResponse;
while (true)
for (var windowIndex = 0; windowIndex < windows.Length; ++windowIndex)
{
// 回查会增长同一会话的消息列表,每次请求前都重新做预算检查。
CheckAndLogContextUsage(segmentMessages, requestContext);
var segmentResult = await _analyzer!.CompleteAsync(
segmentMessages,
requestContext,
OnChunk,
_linkedCts.Token);
UpdateTokenDisplay(segmentResult);
ReportReasoningGuardResult(segmentResult);
segmentResponse = segmentResult.Response;
_linkedCts.Token.ThrowIfCancellationRequested();
var window = windows[windowIndex];
_phaseText.Text =
$"正在分析第 {segmentIndex}/{totalSegments} 段(窗口 {windowIndex + 1}/{windows.Length}";
if (backqueryCount >= maxBackqueriesPerSegment)
FinishCurrentContent();
_lastContentParagraph = null;
// 段内已发现事实:窗口之间共享“已发现事实”,但只保留本段窗口已发现的。
var combinedFindings = new List<string>(_findings);
combinedFindings.AddRange(segmentFindings);
var findingsText = LimitFindings(combinedFindings, maxFindingsTokens);
var windowEventCount =
_eventCounts?.Query(window.Start, window.End) ?? window.EventCount;
var instruction = AIAnalyze.BuildFocusWindowUserPromptV2(
_currentSegmentIndex,
totalSegments,
slice,
windowIndex,
windows.Length,
window,
eventCount,
windowEventCount,
title,
overviewEntry?.Description,
overviewEntry?.BackqueryHints);
// 数据层 = 整段切片(唯一的数据消息),只发一次;窗口指令作为“重点”指令。
var messages = BuildSystemMessages(_systemPrompt, _digest, _overviewNarrative)
.Add(new AIAnalyze.ChatMessage("user", sliceText));
if (!string.IsNullOrWhiteSpace(findingsText))
{
break;
messages = messages.Add(new AIAnalyze.ChatMessage(
"user", "之前的已发现事实:\n" + findingsText));
}
messages = messages.Add(new AIAnalyze.ChatMessage("user", instruction));
var backqueries = BackqueryParser.Parse(segmentResponse);
var pendingTexts = new List<string>();
foreach (var (start, end) in backqueries)
AppendLog(
$"让 AI 分析第{segmentIndex}段窗口 {windowIndex + 1}/{windows.Length}...",
instruction + $"\r\n[AI: {requestContext.Provider.Name}/{requestContext.Model.ModelId}]",
true);
CheckAndLogContextUsage(messages, requestContext);
StartVersion(
$"窗口 {windowIndex + 1}/{windows.Length}:正文",
collapsedByDefault: false);
// 回查循环(M3):同一窗口会话内,模型可多次请求远处原始区间
var windowMessages = messages;
var backqueryCount = 0;
string windowResponse;
while (true)
{
if (backqueryCount + pendingTexts.Count >= maxBackqueriesPerSegment)
// 回查会增长同一会话的消息列表,每次请求前都重新做预算检查。
CheckAndLogContextUsage(windowMessages, requestContext);
var windowResult = await _analyzer!.CompleteAsync(
windowMessages,
requestContext,
OnChunk,
_linkedCts.Token);
UpdateTokenDisplay(windowResult);
ReportReasoningGuardResult(windowResult);
windowResponse = windowResult.Response;
if (backqueryCount >= maxBackqueriesPerSegment)
{
AppendLog("回查限制", "本段回查次数已达上限,剩余区间已忽略。", false);
break;
}
var (text, reason) = BackquerySliceExtractor.Extract(
_replayData, _eventSpans, start, end);
if (text is null)
var backqueries = BackqueryParser.Parse(windowResponse);
var pendingTexts = new List<string>();
foreach (var (start, end) in backqueries)
{
AppendLog("回查失败", reason ?? "未知原因", false);
continue;
if (backqueryCount + pendingTexts.Count >= maxBackqueriesPerSegment)
{
AppendLog("回查限制", "本段回查次数已达上限,剩余区间已忽略。", false);
break;
}
var (text, reason) = BackquerySliceExtractor.Extract(
_replayData, _eventSpans, start, end);
if (text is null)
{
AppendLog("回查失败", reason ?? "未知原因", false);
continue;
}
pendingTexts.Add(
$"[回查 {MatchDigestBuilder.FormatTime(start)}~{MatchDigestBuilder.FormatTime(end)}]\n" + text);
}
pendingTexts.Add(
$"[回查 {MatchDigestBuilder.FormatTime(start)}~{MatchDigestBuilder.FormatTime(end)}]\n" + text);
if (pendingTexts.Count == 0)
{
break;
}
backqueryCount += pendingTexts.Count;
var backqueryPrompt = AIAnalyze.BuildBackqueryUserPrompt(
string.Join("\n\n", pendingTexts));
windowMessages = windowMessages
.Add(new AIAnalyze.ChatMessage("assistant", windowResponse))
.Add(new AIAnalyze.ChatMessage(
"user", backqueryPrompt));
AppendLog(
$"第{segmentIndex}段窗口 {windowIndex + 1} 回查(第 {backqueryCount} 次)",
$"已提供 {pendingTexts.Count} 个区间,继续分析。\n\n用户消息:\n" + backqueryPrompt,
true);
}
if (pendingTexts.Count == 0)
FinishCurrentContent();
var validationResult = AIAnalysisValidation.ValidateMachineReadableClaims(
windowResponse, _factIndex, playerNameToIndex, structuredKnowledge);
AppendMachineJsonAndValidation(windowResponse, validationResult);
if (validationResult.HasIssues)
{
break;
AppendLog(
$"第{segmentIndex}段窗口 {windowIndex + 1} 机器可读声明检查",
AIAnalysisValidation.FormatIssues(validationResult.Issues),
false);
}
backqueryCount += pendingTexts.Count;
var backqueryPrompt = AIAnalyze.BuildBackqueryUserPrompt(
string.Join("\n\n", pendingTexts));
segmentMessages = segmentMessages
.Add(new AIAnalyze.ChatMessage("assistant", segmentResponse))
.Add(new AIAnalyze.ChatMessage(
"user", backqueryPrompt));
AppendLog(
$"第{segmentIndex}段回查(第 {backqueryCount} 次)",
$"已提供 {pendingTexts.Count} 个区间,继续分析。\n\n用户消息:\n" + backqueryPrompt,
true);
}
FinishCurrentContent();
var validationResult = AIAnalysisValidation.ValidateMachineReadableClaims(
segmentResponse, _factIndex, playerNameToIndex, structuredKnowledge);
AppendMachineJsonAndValidation(segmentResponse, validationResult);
if (validationResult.HasIssues)
{
AppendLog(
$"第{segmentIndex}段机器可读声明检查",
AIAnalysisValidation.FormatIssues(validationResult.Issues),
false);
}
// 隐藏修订 passM6):Contradiction 触发,最多 1 次;修订期间不流式显示
var finalResponse = segmentResponse;
var finalValidation = validationResult;
if (validationResult.RequiresRevision)
{
var issueCount = validationResult.Issues.Count(
i => i.Severity is AIValidationSeverity.Contradiction
or AIValidationSeverity.Fatal
or AIValidationSeverity.Warning
or AIValidationSeverity.WeakEvidence);
var relevantFacts = RelevantFactsFormatter.Format(validationResult.Claims, _factIndex!);
var revisionPrompt = AIAnalyze.BuildRevisionUserPrompt(
segmentResponse,
AIAnalysisValidation.FormatIssues(validationResult.Issues),
string.IsNullOrWhiteSpace(relevantFacts) ? "(无额外事实)" : relevantFacts);
AppendLog(
$"第{segmentIndex}段修订",
$"验证器发现 {issueCount} 个需要修正/降级的问题,正在请求 AI 修正...\n\n用户消息:\n"
+ revisionPrompt,
true);
var revisionMessages = segmentMessages
.Add(new AIAnalyze.ChatMessage("assistant", segmentResponse))
.Add(new AIAnalyze.ChatMessage("user", revisionPrompt));
CheckAndLogContextUsage(revisionMessages, requestContext);
_suppressDisplay = true;
AIAnalyze.Result revisionResult;
try
// 隐藏修订 passM6):Contradiction 触发,最多 1 次;
// 修订草稿按时间顺序流式显示,完成后折叠草稿并默认展开最终正文。
var finalResponse = windowResponse;
var finalValidation = validationResult;
if (validationResult.RequiresRevision)
{
revisionResult = await _analyzer!.CompleteAsync(
var issueCount = validationResult.Issues.Count(
i => i.Severity is AIValidationSeverity.Contradiction
or AIValidationSeverity.Fatal
or AIValidationSeverity.Warning
or AIValidationSeverity.WeakEvidence);
var relevantFacts = RelevantFactsFormatter.Format(validationResult.Claims, _factIndex!);
var revisionPrompt = AIAnalyze.BuildRevisionUserPrompt(
windowResponse,
AIAnalysisValidation.FormatIssues(validationResult.Issues),
string.IsNullOrWhiteSpace(relevantFacts) ? "(无额外事实)" : relevantFacts);
AppendLog(
$"第{segmentIndex}段窗口 {windowIndex + 1} 修订",
$"验证器发现 {issueCount} 个需要修正/降级的问题,正在请求 AI 修正...\n\n用户消息:\n"
+ revisionPrompt,
true);
var revisionMessages = windowMessages
.Add(new AIAnalyze.ChatMessage("assistant", windowResponse))
.Add(new AIAnalyze.ChatMessage("user", revisionPrompt));
CheckAndLogContextUsage(revisionMessages, requestContext);
FinishCurrentContent();
var previousVersionTitle = _currentVersionTitle;
var previousVersionUpdate = _currentVersionUpdate;
StartVersion("版本 2:修订草稿", collapsedByDefault: false);
var revisionResult = await _analyzer!.CompleteAsync(
revisionMessages,
requestContext,
OnChunk,
_linkedCts.Token);
}
finally
{
_suppressDisplay = false;
}
UpdateTokenDisplay(revisionResult);
ReportReasoningGuardResult(revisionResult);
UpdateTokenDisplay(revisionResult);
ReportReasoningGuardResult(revisionResult);
if (string.IsNullOrWhiteSpace(revisionResult.Response))
{
AppendLog("修订失败", "修订输出为空,保留原分析。", false);
if (string.IsNullOrWhiteSpace(revisionResult.Response))
{
CollapseCurrentVersion();
AppendLog("修订失败", "修订输出为空,保留原分析。", false);
}
else
{
finalResponse = revisionResult.Response;
finalValidation = AIAnalysisValidation.ValidateMachineReadableClaims(
revisionResult.Response, _factIndex, playerNameToIndex, structuredKnowledge);
CollapseCurrentVersion();
previousVersionUpdate?.Invoke(
previousVersionTitle ?? "版本 1:正文",
true);
StartVersion("版本 3:修订后正文", collapsedByDefault: false);
AppendDirectText(finalResponse);
AppendMachineJsonAndValidation(finalResponse, finalValidation);
AppendLog(
"修订完成",
"已采用修正后的分析。" + (finalValidation.HasIssues
? "\n仍存在的问题:\n" + AIAnalysisValidation.FormatIssues(finalValidation.Issues)
: string.Empty),
false);
}
}
else
{
finalResponse = revisionResult.Response;
finalValidation = AIAnalysisValidation.ValidateMachineReadableClaims(
revisionResult.Response, _factIndex, playerNameToIndex, structuredKnowledge);
CollapseCurrentVersion();
StartVersion("版本 2:修订后正文", collapsedByDefault: false);
AppendDirectText(finalResponse);
AppendMachineJsonAndValidation(finalResponse, finalValidation);
AppendLog(
"修订完成",
"已采用修正后的分析。" + (finalValidation.HasIssues
? "\n仍存在的问题:\n" + AIAnalysisValidation.FormatIssues(finalValidation.Issues)
: string.Empty),
false);
}
}
FinishCurrentContent();
FinishCurrentContent();
// 追加已发现事实
var entry = new StringBuilder();
var summary = ClaimFindingsFormatter.ExtractSummary(finalResponse);
if (!string.IsNullOrWhiteSpace(summary))
{
entry.AppendLine($"[小结] {summary}");
}
var claimsText = ClaimFindingsFormatter.Format(finalValidation.Claims);
if (!string.IsNullOrWhiteSpace(claimsText))
{
entry.AppendLine(claimsText);
}
if (entry.Length > 0)
{
_findings.Add(entry.ToString().TrimEnd());
// 追加本窗口已发现事实(同时进入全局 _findings,供后续段使用)
var entry = new StringBuilder();
var summary = ClaimFindingsFormatter.ExtractSummary(finalResponse);
if (!string.IsNullOrWhiteSpace(summary))
{
entry.AppendLine($"[小结] {summary}");
}
var claimsText = ClaimFindingsFormatter.Format(finalValidation.Claims);
if (!string.IsNullOrWhiteSpace(claimsText))
{
entry.AppendLine(claimsText);
}
if (entry.Length > 0)
{
var entryText = entry.ToString().TrimEnd();
segmentFindings.Add(entryText);
_findings.Add(entryText);
}
_segmentResponses.Add(finalResponse);
AppendLog(
$"第{segmentIndex}段窗口 {windowIndex + 1}/{windows.Length} 完成。",
null,
true);
}
_segmentResponses.Add(finalResponse);
_lastSuccessfulSegment = _currentSegmentIndex;
_currentSegmentIndex++;
AppendLog($"第{segmentIndex}段完成。", null, true);
@@ -1547,32 +1589,6 @@ namespace AnotherReplayReader
{
AutoScroll();
// 修订 pass 期间:不显示中间内容,只统计字符数
if (_suppressDisplay)
{
while (_chunkQueue.TryDequeue(out var data))
{
if (data.Chunk.Type == AIAnalyze.AIChunkType.ReasoningGuard)
{
var text = data.Chunk.Text;
_nextThinkingIsContinuation = true;
_pendingUiActions.Enqueue(() =>
AppendLog("推理保护", text, false, _currentStage));
}
else if (data.Chunk.Type == AIAnalyze.AIChunkType.ReasoningGuardRequest)
{
var text = data.Chunk.Text;
_pendingUiActions.Enqueue(() =>
AppendReasoningDiagnostics(text));
}
else
{
_currentOutputChars += data.Chunk.Text.Length;
}
}
return;
}
var thinkSb = new StringBuilder();
var contentSb = new StringBuilder();
var errorSb = new StringBuilder();