컨텍스트 압축 계층 확장과 세션 누적 계측 보강
Some checks failed
Release Gate / gate (push) Has been cancelled

- claude-code compact 흐름을 참고해 AX에 session memory compact, microcompact, collapse/snip 단계를 추가하고 ContextCondenser를 단계별 결과 반환 구조로 확장함

- ChatConversation과 AX Agent 하단 컨텍스트 카드에 대화별 compact 누적 회수, 절감 토큰, session memory/microcompact/snip 집계를 반영함

- AGENTS.md에 개발 단계부터 최적화와 실행 속도를 우선 고려하는 작업 지침을 추가함

- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ (경고 0 / 오류 0)
This commit is contained in:
2026-04-04 23:49:44 +09:00
parent ac8e9f9686
commit d2f8e39d2b
6 changed files with 458 additions and 38 deletions

View File

@@ -99,6 +99,14 @@ public partial class ChatWindow : Window
private int? _lastCompactionAfterTokens;
private DateTime? _lastCompactionAt;
private bool _lastCompactionWasAutomatic;
private string _lastCompactionStageSummary = "";
private int _sessionCompactionCount;
private int _sessionAutomaticCompactionCount;
private int _sessionManualCompactionCount;
private int _sessionCompactionSavedTokens;
private int _sessionMemoryCompactionCount;
private int _sessionMicrocompactBoundaryCount;
private int _sessionSnipCompactionCount;
private void ApplyQuickActionVisual(Button button, bool active, string activeBg, string activeFg)
{
if (button?.Content is not string text)
@@ -1686,6 +1694,7 @@ public partial class ChatWindow : Window
}
// 대화별 설정 복원 (없으면 전역 기본값)
LoadConversationSettings();
LoadCompactionMetricsFromConversation();
UpdatePermissionUI();
UpdateDataUsageUI();
RefreshContextUsageVisual();
@@ -1711,6 +1720,25 @@ public partial class ChatWindow : Window
_selectedMood = conv?.Mood ?? llm.DefaultMood ?? "modern";
}
private void LoadCompactionMetricsFromConversation()
{
ChatConversation? conv;
lock (_convLock) conv = _currentConversation;
_sessionCompactionCount = conv?.CompactionCount ?? 0;
_sessionAutomaticCompactionCount = conv?.AutomaticCompactionCount ?? 0;
_sessionManualCompactionCount = conv?.ManualCompactionCount ?? 0;
_sessionCompactionSavedTokens = conv?.CompactionSavedTokens ?? 0;
_sessionMemoryCompactionCount = conv?.SessionMemoryCompactionCount ?? 0;
_sessionMicrocompactBoundaryCount = conv?.MicrocompactBoundaryCount ?? 0;
_sessionSnipCompactionCount = conv?.SnipCompactionCount ?? 0;
_lastCompactionAt = conv?.LastCompactionAt;
_lastCompactionWasAutomatic = conv?.LastCompactionWasAutomatic ?? false;
_lastCompactionBeforeTokens = conv?.LastCompactionBeforeTokens;
_lastCompactionAfterTokens = conv?.LastCompactionAfterTokens;
_lastCompactionStageSummary = conv?.LastCompactionStageSummary ?? "";
}
/// <summary>현재 하단 바 설정을 대화에 저장합니다.</summary>
private void SaveConversationSettings()
{
@@ -6500,9 +6528,8 @@ public partial class ChatWindow : Window
ForceScrollToEnd();
var llm = _settings.Settings.Llm;
var beforeTokens = Services.TokenEstimator.EstimateMessages(conv.Messages);
var working = conv.Messages.ToList();
var condensed = await ContextCondenser.CondenseIfNeededAsync(
var compactResult = await ContextCondenser.CondenseWithStatsAsync(
working,
_llm,
llm.MaxContextTokens,
@@ -6510,10 +6537,11 @@ public partial class ChatWindow : Window
llm.ContextCompactTriggerPercent,
true,
CancellationToken.None);
var afterTokens = Services.TokenEstimator.EstimateMessages(working);
RecordCompactionStats(beforeTokens, afterTokens, wasAutomatic: false);
var beforeTokens = compactResult.BeforeTokens;
var afterTokens = compactResult.AfterTokens;
RecordCompactionStats(compactResult, wasAutomatic: false);
if (condensed)
if (compactResult.Changed)
{
lock (_convLock)
{
@@ -6521,8 +6549,8 @@ public partial class ChatWindow : Window
}
}
var assistantText = condensed
? $"컨텍스트 압축을 수행했습니다. 입력 토큰 추정치: {beforeTokens:N0} → {afterTokens:N0}"
var assistantText = compactResult.Changed
? $"컨텍스트 압축을 수행했습니다. 입력 토큰 추정치: {beforeTokens:N0} → {afterTokens:N0}\n- 단계: {compactResult.StageSummary}\n- 절감량: {compactResult.SavedTokens:N0} tokens"
: "현재 대화는 압축할 충분한 이전 컨텍스트가 없어 변경 없이 유지했습니다.";
var assistantMsg = new ChatMessage { Role = "assistant", Content = assistantText };
@@ -6547,7 +6575,7 @@ public partial class ChatWindow : Window
ForceScrollToEnd();
if (StatusTokens != null)
StatusTokens.Text = $"컨텍스트 {Services.TokenEstimator.Format(beforeTokens)} → {Services.TokenEstimator.Format(afterTokens)}";
SetStatus(condensed ? "컨텍스트 압축 완료" : "압축할 컨텍스트 없음", spinning: false);
SetStatus(compactResult.Changed ? "컨텍스트 압축 완료" : "압축할 컨텍스트 없음", spinning: false);
RefreshContextUsageVisual();
RefreshConversationList();
UpdateTaskSummaryIndicators();
@@ -8081,8 +8109,7 @@ public partial class ChatWindow : Window
// ── 전송 전 컨텍스트 사전 압축 ──
{
var llm = _settings.Settings.Llm;
var beforeCompactTokens = Services.TokenEstimator.EstimateMessages(sendMessages);
var condensed = await ContextCondenser.CondenseIfNeededAsync(
var compactResult = await ContextCondenser.CondenseWithStatsAsync(
sendMessages,
_llm,
llm.MaxContextTokens,
@@ -8090,11 +8117,10 @@ public partial class ChatWindow : Window
llm.ContextCompactTriggerPercent,
false,
_streamCts!.Token);
if (condensed)
if (compactResult.Changed)
{
var afterCompactTokens = Services.TokenEstimator.EstimateMessages(sendMessages);
RecordCompactionStats(beforeCompactTokens, afterCompactTokens, wasAutomatic: true);
SetStatus("컨텍스트를 사전 정리했습니다", spinning: true);
RecordCompactionStats(compactResult, wasAutomatic: true);
SetStatus($"컨텍스트를 사전 정리했습니다 · {compactResult.BeforeTokens:N0} → {compactResult.AfterTokens:N0} tokens", spinning: true);
RefreshContextUsageVisual();
}
}
@@ -16497,7 +16523,14 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
? $"\n최근 압축: {(_lastCompactionWasAutomatic ? "" : "")} · {_lastCompactionAt.Value:HH:mm:ss}\n" +
$"절감: {_lastCompactionBeforeTokens.Value:N0} → {_lastCompactionAfterTokens.Value:N0} tokens " +
$"(-{Math.Max(0, _lastCompactionBeforeTokens.Value - _lastCompactionAfterTokens.Value):N0}, " +
$"{Services.TokenEstimator.Format(_lastCompactionBeforeTokens.Value)} → {Services.TokenEstimator.Format(_lastCompactionAfterTokens.Value)})"
$"{Services.TokenEstimator.Format(_lastCompactionBeforeTokens.Value)} → {Services.TokenEstimator.Format(_lastCompactionAfterTokens.Value)})\n" +
$"단계: {(!string.IsNullOrWhiteSpace(_lastCompactionStageSummary) ? _lastCompactionStageSummary : "")}"
: "";
var compactSession = _sessionCompactionCount > 0
? $"\n세션 누적: {_sessionCompactionCount:N0}회 (자동 {_sessionAutomaticCompactionCount:N0} / 수동 {_sessionManualCompactionCount:N0})\n" +
$"세션 절감: {Services.TokenEstimator.Format(_sessionCompactionSavedTokens)} tokens\n" +
$"세션 메모리 {_sessionMemoryCompactionCount:N0}회 · 경계 {_sessionMicrocompactBoundaryCount:N0}건 · snip {_sessionSnipCompactionCount:N0}건"
: "";
TokenUsageCard.ToolTip =
@@ -16506,7 +16539,8 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
$"간단 표기: {Services.TokenEstimator.Format(currentTokens)} / {Services.TokenEstimator.Format(maxContextTokens)}\n" +
$"자동 압축 시작: {triggerPercent}%\n" +
$"현재 입력 초안 포함" +
compactHistory;
compactHistory +
compactSession;
UpdateCircularUsageArc(TokenUsageArc, usageRatio, 18, 18, 14);
PositionThresholdMarker(TokenUsageThresholdMarker, triggerRatio, 18, 18, 14, 3);
@@ -16555,12 +16589,45 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
centerY + radius * Math.Sin(radians));
}
private void RecordCompactionStats(int beforeTokens, int afterTokens, bool wasAutomatic)
private void RecordCompactionStats(ContextCompactionResult result, bool wasAutomatic)
{
_lastCompactionBeforeTokens = Math.Max(0, beforeTokens);
_lastCompactionAfterTokens = Math.Max(0, afterTokens);
var beforeTokens = Math.Max(0, result.BeforeTokens);
var afterTokens = Math.Max(0, result.AfterTokens);
var savedTokens = Math.Max(0, beforeTokens - afterTokens);
_lastCompactionBeforeTokens = beforeTokens;
_lastCompactionAfterTokens = afterTokens;
_lastCompactionAt = DateTime.Now;
_lastCompactionWasAutomatic = wasAutomatic;
_lastCompactionStageSummary = result.StageSummary;
_sessionCompactionCount++;
_sessionCompactionSavedTokens += savedTokens;
_sessionMemoryCompactionCount += result.SessionMemoryApplied ? 1 : 0;
_sessionMicrocompactBoundaryCount += result.MicrocompactBoundaryCount;
_sessionSnipCompactionCount += result.SnippedMessageCount + result.CollapsedBoundaryCount;
if (wasAutomatic)
_sessionAutomaticCompactionCount++;
else
_sessionManualCompactionCount++;
ChatConversation? conv;
lock (_convLock) conv = _currentConversation;
if (conv == null)
return;
conv.CompactionCount = _sessionCompactionCount;
conv.AutomaticCompactionCount = _sessionAutomaticCompactionCount;
conv.ManualCompactionCount = _sessionManualCompactionCount;
conv.CompactionSavedTokens = _sessionCompactionSavedTokens;
conv.SessionMemoryCompactionCount = _sessionMemoryCompactionCount;
conv.MicrocompactBoundaryCount = _sessionMicrocompactBoundaryCount;
conv.SnipCompactionCount = _sessionSnipCompactionCount;
conv.LastCompactionAt = _lastCompactionAt;
conv.LastCompactionWasAutomatic = wasAutomatic;
conv.LastCompactionBeforeTokens = beforeTokens;
conv.LastCompactionAfterTokens = afterTokens;
conv.LastCompactionStageSummary = _lastCompactionStageSummary;
try { _storage.Save(conv); } catch { }
}
private void ScheduleGitBranchRefresh(int delayMs = 400)