에이전트 루프 진단과 문서 golden 회귀를 마감한다

- AgentLoopService의 컨텍스트 압축 완료 메시지와 query-view 요약 문자열 조립을 AgentLoopDiagnosticsFormatter로 분리해 루프 본체의 책임을 더 줄였다.

- ChatStorageService 로드 경로에서 legacy .axchat의 누락된 tool_result preview를 synthetic preview로 즉시 복원하도록 보강했다.

- HTML/DOCX golden 회귀와 legacy 저장 경로 회귀 테스트를 추가해 PPTX/XLSX에 이어 문서 품질 고정 범위를 확대했다.

- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_loop_storage_golden\ -p:IntermediateOutputPath=obj\verify_loop_storage_golden\ (경고 0, 오류 0)

- 검증: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter AgentLoopDiagnosticsFormatterTests|ChatStorageServiceTests|HtmlSkillGoldenReportTests|DocxSkillGoldenDocumentTests|AgentMessageInvariantHelperTests|PptxSkillGoldenDeckTests|ExcelSkillGoldenWorkbookTests -p:OutputPath=bin\verify_loop_storage_golden_tests\ -p:IntermediateOutputPath=obj\verify_loop_storage_golden_tests\ (통과 10)
This commit is contained in:
2026-04-15 09:21:55 +09:00
parent 8530ec956a
commit baafd8280c
9 changed files with 451 additions and 13 deletions

View File

@@ -0,0 +1,23 @@
namespace AxCopilot.Services.Agent;
internal static class AgentLoopDiagnosticsFormatter
{
public static string BuildCompactionCompleteMessage(ContextCompactionResult result)
{
var compactSummary = !string.IsNullOrWhiteSpace(result.StageSummary)
? result.StageSummary
: "기본";
return $"컨텍스트 압축 완료 — {compactSummary} · {Services.TokenEstimator.Format(result.SavedTokens)} tokens 절감";
}
public static string BuildQueryViewSummary(AgentQueryContextWindowResult queryView)
{
return
$"query-view {queryView.SourceMessageCount}->{queryView.ViewMessageCount}, " +
$"start={queryView.WindowStartIndex}, " +
$"pairs={queryView.PreservedToolPairCount}, " +
$"tool_result_budget={queryView.TruncatedToolResultCount}, " +
$"tool_result_preview_reuse={queryView.ReusedToolResultPreviewCount}, " +
$"tokens {queryView.TokensBeforeBudget}->{queryView.TokensAfterBudget}";
}
}

View File

@@ -475,13 +475,10 @@ public partial class AgentLoopService
if (compactionResult.Changed)
{
MarkRunPostCompaction(compactionResult, runState);
var compactSummary = !string.IsNullOrWhiteSpace(compactionResult.StageSummary)
? compactionResult.StageSummary
: "기본";
EmitEvent(
AgentEventType.Thinking,
"",
$"컨텍스트 압축 완료 — {compactSummary} · {Services.TokenEstimator.Format(compactionResult.SavedTokens)} tokens 절감");
AgentLoopDiagnosticsFormatter.BuildCompactionCompleteMessage(compactionResult));
}
}
@@ -505,19 +502,12 @@ public partial class AgentLoopService
var queryMessages = queryView.Messages;
if (queryView.BoundaryApplied || queryView.ToolPairExpanded || queryView.TruncatedToolResultCount > 0)
{
var queryViewSummary =
$"query-view {queryView.SourceMessageCount}->{queryView.ViewMessageCount}, " +
$"start={queryView.WindowStartIndex}, " +
$"pairs={queryView.PreservedToolPairCount}, " +
$"tool_result_budget={queryView.TruncatedToolResultCount}, " +
$"tool_result_preview_reuse={queryView.ReusedToolResultPreviewCount}, " +
$"tokens {queryView.TokensBeforeBudget}->{queryView.TokensAfterBudget}";
WorkflowLogService.LogTransition(
_conversationId,
_currentRunId,
iteration,
"query_view",
queryViewSummary);
AgentLoopDiagnosticsFormatter.BuildQueryViewSummary(queryView));
}
var isDebugLog = string.Equals(_settings.Settings.Llm.AgentLogLevel, "debug", StringComparison.OrdinalIgnoreCase);

View File

@@ -78,7 +78,10 @@ public class ChatStorageService : IChatStorageService
{
if (!File.Exists(path)) return null;
var json = CryptoService.DecryptFromFile(path);
return JsonSerializer.Deserialize<ChatConversation>(json, JsonOpts);
var conversation = JsonSerializer.Deserialize<ChatConversation>(json, JsonOpts);
if (conversation != null)
AxCopilot.Services.Agent.AgentMessageInvariantHelper.PopulateMissingToolResultPreviews(conversation.Messages);
return conversation;
}
catch (Exception ex)
{