- claw-code post-autocompact turn tracking 흐름을 참고해 AX AgentLoop가 compact 직후 첫 턴을 별도 상태로 추적하도록 보강함 - compact 이후 첫 턴에서는 LLM 요청 중·프롬프트 제출·무료 티어 대기 같은 boilerplate Thinking 로그를 억제하고 compact pill 중심으로 보이도록 정리함 - 개발자용 전체 통계에 compact 로그 축약 건수를 포함하고 README.md, docs/DEVELOPMENT.md에 2026-04-05 00:17 (KST) 기준 이력을 반영함 - 검증: 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:
@@ -51,6 +51,11 @@ public partial class AgentLoopService
|
||||
/// <summary>문서 생성 폴백 재시도 여부 (루프당 1회만).</summary>
|
||||
private bool _docFallbackAttempted;
|
||||
private string _currentRunId = "";
|
||||
private bool _runPendingPostCompactionTurn;
|
||||
private int _runPostCompactionTurnCounter;
|
||||
private int _runPostCompactionSuppressedThinkingCount;
|
||||
private string _runLastCompactionStageSummary = "";
|
||||
private int _runLastCompactionSavedTokens;
|
||||
|
||||
/// <summary>일시정지 제어용 세마포어. 1이면 진행, 0이면 대기.</summary>
|
||||
private readonly SemaphoreSlim _pauseSemaphore = new(1, 1);
|
||||
@@ -437,7 +442,7 @@ public partial class AgentLoopService
|
||||
// Context Condenser: 토큰 초과 시 이전 대화 자동 압축
|
||||
// 첫 반복에서도 실행 (이전 대화 복원으로 이미 긴 경우 대비)
|
||||
{
|
||||
var condensed = await ContextCondenser.CondenseIfNeededAsync(
|
||||
var compactionResult = await ContextCondenser.CondenseWithStatsAsync(
|
||||
messages,
|
||||
_llm,
|
||||
llm.MaxContextTokens,
|
||||
@@ -445,8 +450,17 @@ public partial class AgentLoopService
|
||||
llm.ContextCompactTriggerPercent,
|
||||
false,
|
||||
ct);
|
||||
if (condensed)
|
||||
EmitEvent(AgentEventType.Thinking, "", "컨텍스트 압축 완료 — 입력 토큰을 절감했습니다");
|
||||
if (compactionResult.Changed)
|
||||
{
|
||||
MarkRunPostCompaction(compactionResult, runState);
|
||||
var compactSummary = !string.IsNullOrWhiteSpace(compactionResult.StageSummary)
|
||||
? compactionResult.StageSummary
|
||||
: "기본";
|
||||
EmitEvent(
|
||||
AgentEventType.Thinking,
|
||||
"",
|
||||
$"컨텍스트 압축 완료 — {compactSummary} · {Services.TokenEstimator.Format(compactionResult.SavedTokens)} tokens 절감");
|
||||
}
|
||||
}
|
||||
|
||||
EmitEvent(AgentEventType.Thinking, "", $"LLM에 요청 중... (반복 {iteration}/{maxIterations})");
|
||||
@@ -497,6 +511,7 @@ public partial class AgentLoopService
|
||||
runState);
|
||||
runState.ContextRecoveryAttempts = 0;
|
||||
runState.TransientLlmErrorRetries = 0;
|
||||
NotifyPostCompactionTurnIfNeeded(runState);
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
@@ -1439,6 +1454,11 @@ public partial class AgentLoopService
|
||||
|
||||
IsRunning = false;
|
||||
_currentRunId = "";
|
||||
_runPendingPostCompactionTurn = false;
|
||||
_runPostCompactionTurnCounter = 0;
|
||||
_runPostCompactionSuppressedThinkingCount = 0;
|
||||
_runLastCompactionStageSummary = "";
|
||||
_runLastCompactionSavedTokens = 0;
|
||||
|
||||
// 일시정지 상태 리셋
|
||||
if (IsPaused)
|
||||
@@ -1480,10 +1500,13 @@ public partial class AgentLoopService
|
||||
var retryQuality = retryTotal > 0
|
||||
? $"{(statsRecoveredAfterFailure * 100.0 / retryTotal):F0}%"
|
||||
: "100%";
|
||||
var compactNoiseSummary = _runPostCompactionSuppressedThinkingCount > 0
|
||||
? $" | compact 로그 축약 {_runPostCompactionSuppressedThinkingCount}건"
|
||||
: "";
|
||||
var topFailed = BuildTopFailureSummary(failedToolHistogram);
|
||||
var summary = $"📊 전체 통계: LLM {iteration}회 호출 | 도구 {totalToolCalls}회 (성공 {statsSuccessCount}, 실패 {statsFailCount}) | " +
|
||||
$"토큰 {statsInputTokens:N0}→{statsOutputTokens:N0} (합계 {totalTokens:N0}) | " +
|
||||
$"소요 {durationSec:F1}초 | 재시도 품질 {retryQuality} (복구 {statsRecoveredAfterFailure}, 차단 {statsRepeatedFailureBlocks}) | " +
|
||||
$"소요 {durationSec:F1}초 | 재시도 품질 {retryQuality} (복구 {statsRecoveredAfterFailure}, 차단 {statsRepeatedFailureBlocks}){compactNoiseSummary} | " +
|
||||
$"실패 상위: {topFailed} | 사용 도구: {toolList}";
|
||||
EmitEvent(AgentEventType.StepDone, "total_stats", summary);
|
||||
}
|
||||
@@ -4339,6 +4362,12 @@ public partial class AgentLoopService
|
||||
long elapsedMs = 0, int inputTokens = 0, int outputTokens = 0,
|
||||
string? toolInput = null, int iteration = 0, bool? successOverride = null)
|
||||
{
|
||||
if (type == AgentEventType.Thinking && ShouldSuppressPostCompactionThinking(summary))
|
||||
{
|
||||
_runPostCompactionSuppressedThinkingCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
// AgentLogLevel에 따라 이벤트 필터링
|
||||
var logLevel = _settings.Settings.Llm.AgentLogLevel;
|
||||
|
||||
@@ -4381,6 +4410,62 @@ public partial class AgentLoopService
|
||||
}
|
||||
}
|
||||
|
||||
private void MarkRunPostCompaction(ContextCompactionResult result, RunState runState)
|
||||
{
|
||||
runState.PendingPostCompactionTurn = true;
|
||||
runState.PostCompactionTurnCounter = 0;
|
||||
runState.LastCompactionStageSummary = result.StageSummary;
|
||||
runState.LastCompactionSavedTokens = result.SavedTokens;
|
||||
SyncRunPostCompactionState(runState);
|
||||
}
|
||||
|
||||
private void NotifyPostCompactionTurnIfNeeded(RunState runState)
|
||||
{
|
||||
if (!runState.PendingPostCompactionTurn)
|
||||
return;
|
||||
|
||||
runState.PendingPostCompactionTurn = false;
|
||||
runState.PostCompactionTurnCounter++;
|
||||
SyncRunPostCompactionState(runState);
|
||||
|
||||
var stage = string.IsNullOrWhiteSpace(runState.LastCompactionStageSummary)
|
||||
? "기본"
|
||||
: runState.LastCompactionStageSummary;
|
||||
var saved = runState.LastCompactionSavedTokens > 0
|
||||
? $" · {Services.TokenEstimator.Format(runState.LastCompactionSavedTokens)} tokens 절감"
|
||||
: "";
|
||||
EmitEvent(
|
||||
AgentEventType.Thinking,
|
||||
"",
|
||||
$"compact 이후 {runState.PostCompactionTurnCounter}번째 턴 · {stage}{saved}");
|
||||
}
|
||||
|
||||
private void SyncRunPostCompactionState(RunState runState)
|
||||
{
|
||||
_runPendingPostCompactionTurn = runState.PendingPostCompactionTurn;
|
||||
_runPostCompactionTurnCounter = runState.PostCompactionTurnCounter;
|
||||
_runLastCompactionStageSummary = runState.LastCompactionStageSummary ?? "";
|
||||
_runLastCompactionSavedTokens = runState.LastCompactionSavedTokens;
|
||||
}
|
||||
|
||||
private bool ShouldSuppressPostCompactionThinking(string summary)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(summary))
|
||||
return false;
|
||||
|
||||
var postCompactActive = _runPendingPostCompactionTurn || _runPostCompactionTurnCounter > 0;
|
||||
if (!postCompactActive)
|
||||
return false;
|
||||
|
||||
if (_runPostCompactionTurnCounter > 1)
|
||||
return false;
|
||||
|
||||
return summary.StartsWith("LLM에 요청 중", StringComparison.Ordinal)
|
||||
|| summary.StartsWith("사용자 프롬프트 제출", StringComparison.Ordinal)
|
||||
|| summary.StartsWith("무료 티어 모드", StringComparison.Ordinal)
|
||||
|| summary.StartsWith("컨텍스트 압축 완료", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>영향 범위 기반 의사결정 체크. 확인이 필요하면 메시지를 반환, 불필요하면 null.</summary>
|
||||
private string? CheckDecisionRequired(LlmService.ContentBlock call, AgentContext context)
|
||||
{
|
||||
|
||||
@@ -1507,6 +1507,10 @@ public partial class AgentLoopService
|
||||
public int DocumentVerificationGateRetry;
|
||||
public int NoProgressRecoveryRetry;
|
||||
public int TerminalEvidenceGateRetry;
|
||||
public bool PendingPostCompactionTurn;
|
||||
public int PostCompactionTurnCounter;
|
||||
public string LastCompactionStageSummary = "";
|
||||
public int LastCompactionSavedTokens;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user