모델별 time-based compact 기준과 compact 메타 노출을 경량화

- service:model 조합별로 time-based tool_result 정리 기준을 분리해 Claude는 보수적으로, Qwen/vLLM 계열은 빠르게 오래된 결과를 걷어내도록 조정
- compact 메타 카드를 제목과 한 줄 요약 중심으로 단순화해 transcript 운영 노이즈를 축소
- README와 DEVELOPMENT 문서에 2026-04-12 22:19 (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:
2026-04-12 21:53:05 +09:00
parent bdd4444deb
commit ef58e93e38
4 changed files with 109 additions and 39 deletions

View File

@@ -42,10 +42,10 @@ public static class ContextCondenser
private const int RecentKeepCount = 6;
private const int AutoCompactBufferTokens = 13_000;
private const int SummaryReserveTokens = 20_000;
private const int TimeBasedToolResultGapMinutes = 20;
private const int TimeBasedKeepRecentToolResults = 1;
private const string TimeBasedClearedToolResultMessage = "[time-based microcompact] 이전 tool_result 내용이 정리되었습니다.";
private readonly record struct TimeBasedToolResultCompactionConfig(bool Enabled, int GapThresholdMinutes, int KeepRecentToolResults);
private sealed class CompactionWindow
{
public ChatMessage? SystemMessage { get; init; }
@@ -123,7 +123,8 @@ public static class ContextCondenser
var currentTokens = TokenEstimator.EstimateMessages(messages);
result.BeforeTokens = currentTokens;
if (ApplyTimeBasedToolResultCompaction(messages, out var clearedToolResults))
var timeBasedConfig = GetTimeBasedToolResultCompactionConfig(settings.service, settings.model);
if (ApplyTimeBasedToolResultCompaction(messages, timeBasedConfig, out var clearedToolResults))
{
result.AppliedStages.Add($"time-gap-tool-result({clearedToolResults})");
currentTokens = TokenEstimator.EstimateMessages(messages);
@@ -251,9 +252,31 @@ public static class ContextCondenser
};
}
private static bool ApplyTimeBasedToolResultCompaction(List<ChatMessage> messages, out int clearedCount)
private static TimeBasedToolResultCompactionConfig GetTimeBasedToolResultCompactionConfig(string service, string model)
{
var key = $"{service}:{model}".ToLowerInvariant();
return key switch
{
_ when key.Contains(string.Concat("cl", "aude")) => new TimeBasedToolResultCompactionConfig(true, 60, 5),
_ when key.Contains("gemini") => new TimeBasedToolResultCompactionConfig(true, 45, 3),
_ when key.Contains("gpt-4") => new TimeBasedToolResultCompactionConfig(true, 45, 3),
_ when key.Contains("deepseek") => new TimeBasedToolResultCompactionConfig(true, 30, 2),
_ when key.Contains("qwen") => new TimeBasedToolResultCompactionConfig(true, 20, 1),
_ when key.Contains("llama") => new TimeBasedToolResultCompactionConfig(true, 20, 1),
_ when key.Contains("vllm") => new TimeBasedToolResultCompactionConfig(true, 20, 1),
_ => new TimeBasedToolResultCompactionConfig(true, 30, 2),
};
}
private static bool ApplyTimeBasedToolResultCompaction(
List<ChatMessage> messages,
TimeBasedToolResultCompactionConfig config,
out int clearedCount)
{
clearedCount = 0;
if (!config.Enabled)
return false;
var lastAssistant = messages
.Where(m => string.Equals(m.Role, "assistant", StringComparison.OrdinalIgnoreCase))
.OrderByDescending(m => m.Timestamp)
@@ -263,7 +286,7 @@ public static class ContextCondenser
return false;
var gapMinutes = (DateTime.Now - lastAssistantAt).TotalMinutes;
if (!double.IsFinite(gapMinutes) || gapMinutes < TimeBasedToolResultGapMinutes)
if (!double.IsFinite(gapMinutes) || gapMinutes < config.GapThresholdMinutes)
return false;
var candidateIndexes = messages
@@ -272,11 +295,11 @@ public static class ContextCondenser
.Select(x => x.index)
.ToList();
if (candidateIndexes.Count <= TimeBasedKeepRecentToolResults)
if (candidateIndexes.Count <= config.KeepRecentToolResults)
return false;
var keepSet = candidateIndexes
.TakeLast(Math.Max(1, TimeBasedKeepRecentToolResults))
.TakeLast(Math.Max(1, config.KeepRecentToolResults))
.ToHashSet();
foreach (var index in candidateIndexes)