compact 뒤 첨부 참조가 이어지도록 요약/경계 메시지를 보강

- ContextCondenser가 오래된 메시지 구간의 첨부 파일 이름과 이미지 개수를 수집해 microcompact boundary와 요약 메시지에 함께 기록
- 요약 메시지에 AttachedFiles를 보존해 compact 이후 query view에서도 파일 참조 continuity가 유지되도록 조정
- README와 DEVELOPMENT 문서에 2026-04-12 22:36 (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:58:55 +09:00
parent d8cd04aa4f
commit b8f4df1892
3 changed files with 66 additions and 13 deletions

View File

@@ -1651,3 +1651,7 @@ MIT License
- compact 이후 응답/컨텍스트 사용 표시에서 남아 있던 후행 운영 메타를 더 줄였습니다. - compact 이후 응답/컨텍스트 사용 표시에서 남아 있던 후행 운영 메타를 더 줄였습니다.
- [ChatWindow.ResponsePresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.ResponsePresentation.cs)는 응답 하단 토큰 메타에서 `compact 직후` 꼬리표를 제거해, 일반 응답과 같은 밀도로 보이도록 정리했습니다. - [ChatWindow.ResponsePresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.ResponsePresentation.cs)는 응답 하단 토큰 메타에서 `compact 직후` 꼬리표를 제거해, 일반 응답과 같은 밀도로 보이도록 정리했습니다.
- [ChatWindow.ContextUsagePresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.ContextUsagePresentation.cs)는 token usage 팝업 detail에서 `compact 후 첫 응답 대기 중` 문구를 빼고 실제 컨텍스트/압축 정보만 보여주도록 단순화했습니다. - [ChatWindow.ContextUsagePresentation.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Views/ChatWindow.ContextUsagePresentation.cs)는 token usage 팝업 detail에서 `compact 후 첫 응답 대기 중` 문구를 빼고 실제 컨텍스트/압축 정보만 보여주도록 단순화했습니다.
- 업데이트: 2026-04-12 22:36 (KST)
- `claw-code`의 post-compact attachments 흐름을 참고해, AX도 요약/경계 메시지에 오래된 첨부 참조를 다시 실어 compact 뒤 컨텍스트 연속성을 보강했습니다.
- [ContextCondenser.cs](/E:/AX%20Copilot%20-%20Codex/src/AxCopilot/Services/Agent/ContextCondenser.cs)는 오래된 메시지에서 첨부 파일 이름과 이미지 개수를 수집해 `microcompact_boundary`와 요약 메시지에 함께 기록합니다.
- 요약 메시지에는 `AttachedFiles`도 같이 보존해, compact 이후에도 관련 파일 참조가 query view로 이어질 수 있게 맞췄습니다.

View File

@@ -657,3 +657,14 @@ owKindCounts를 함께 남겨 %APPDATA%\\AxCopilot\\perf 기준으로 transcript
- compact 이후에도 transcript와 usage UI가 일반 응답과 더 비슷한 밀도로 이어집니다. - compact 이후에도 transcript와 usage UI가 일반 응답과 더 비슷한 밀도로 이어집니다.
- 운영 상태는 내부 로직/통계에만 남기고, 사용자 화면은 더 `claw-code` 스타일의 얇은 표현에 가까워집니다. - 운영 상태는 내부 로직/통계에만 남기고, 사용자 화면은 더 `claw-code` 스타일의 얇은 표현에 가까워집니다.
## compact 후 첨부 참조 재주입 보강 (2026-04-12 22:36 KST)
- `claw-code``buildPostCompactMessages()`가 attachments를 boundary/summary 뒤에 다시 붙이는 흐름을 참고해, AX도 compact 이후 첨부 참조가 완전히 사라지지 않도록 보강했습니다.
- `src/AxCopilot/Services/Agent/ContextCondenser.cs`
- 오래된 메시지 구간에서 `AttachedFiles``Images` 개수를 수집하는 helper를 추가했습니다.
- `BuildMicrocompactBoundary()`는 compact 경계 메시지에 관련 파일 목록과 이미지 개수를 함께 요약하고, `AttachedFiles`도 메타 메시지에 유지합니다.
- `SummarizeOldMessagesAsync()`는 요약 메시지 하단에 `참고 파일`, `참고 이미지` 줄을 추가하고, `AttachedFiles`도 요약 메시지에 보존합니다.
- 기대 효과
- compact 이후에도 “이 대화가 어떤 파일/이미지를 참고했는지”가 요약 메시지에서 다시 드러납니다.
- query view가 compact 이후 메시지를 다시 보낼 때, 파일 참조 continuity가 이전보다 더 자연스럽게 유지됩니다.

View File

@@ -54,6 +54,12 @@ public static class ContextCondenser
public int PreservedToolPairs { get; init; } public int PreservedToolPairs { get; init; }
} }
private sealed class CompactionAttachmentSummary
{
public required List<string> AttachedFiles { get; init; }
public int ImageCount { get; init; }
}
/// <summary>모델별 입력 토큰 한도 (대략).</summary> /// <summary>모델별 입력 토큰 한도 (대략).</summary>
private static int GetModelInputLimit(string service, string model) private static int GetModelInputLimit(string service, string model)
{ {
@@ -703,17 +709,7 @@ public static class ContextCondenser
!(m.Content ?? "").StartsWith("{\"_tool_use_blocks\"", StringComparison.Ordinal) && !(m.Content ?? "").StartsWith("{\"_tool_use_blocks\"", StringComparison.Ordinal) &&
m.MetaKind == null); m.MetaKind == null);
var attachedFiles = group var attachmentSummary = CollectCompactionAttachmentSummary(group, 4);
.SelectMany(m => m.AttachedFiles ?? Enumerable.Empty<string>())
.Distinct(StringComparer.OrdinalIgnoreCase)
.Select(path =>
{
try { return System.IO.Path.GetFileName(path); }
catch { return path; }
})
.Where(name => !string.IsNullOrWhiteSpace(name))
.Take(4)
.ToList();
var lines = new List<string> var lines = new List<string>
{ {
@@ -724,7 +720,8 @@ public static class ContextCondenser
if (toolCalls > 0) lines.Add($"- 도구 호출 {toolCalls}건 정리"); if (toolCalls > 0) lines.Add($"- 도구 호출 {toolCalls}건 정리");
if (metaEvents > 0) lines.Add($"- 실행 메타/로그 {metaEvents}건 정리"); if (metaEvents > 0) lines.Add($"- 실행 메타/로그 {metaEvents}건 정리");
if (longTexts > 0) lines.Add($"- 긴 설명/출력 {longTexts}건 축약"); if (longTexts > 0) lines.Add($"- 긴 설명/출력 {longTexts}건 축약");
if (attachedFiles.Count > 0) lines.Add($"- 관련 파일: {string.Join(", ", attachedFiles)}"); if (attachmentSummary.AttachedFiles.Count > 0) lines.Add($"- 관련 파일: {string.Join(", ", attachmentSummary.AttachedFiles)}");
if (attachmentSummary.ImageCount > 0) lines.Add($"- 관련 이미지: {attachmentSummary.ImageCount}개");
return new ChatMessage return new ChatMessage
{ {
@@ -733,6 +730,7 @@ public static class ContextCondenser
Timestamp = group.Last().Timestamp, Timestamp = group.Last().Timestamp,
MetaKind = "microcompact_boundary", MetaKind = "microcompact_boundary",
MetaRunId = group.Last().MetaRunId, MetaRunId = group.Last().MetaRunId,
AttachedFiles = attachmentSummary.AttachedFiles.Count > 0 ? attachmentSummary.AttachedFiles : null,
}; };
} }
@@ -747,6 +745,7 @@ public static class ContextCondenser
var systemMsg = window.SystemMessage; var systemMsg = window.SystemMessage;
var oldMessages = window.OldMessages; var oldMessages = window.OldMessages;
var recentMessages = window.RecentMessages; var recentMessages = window.RecentMessages;
var attachmentSummary = CollectCompactionAttachmentSummary(oldMessages, 6);
if (oldMessages.Count < 3) return false; if (oldMessages.Count < 3) return false;
@@ -787,8 +786,9 @@ public static class ContextCondenser
messages.Add(new ChatMessage messages.Add(new ChatMessage
{ {
Role = "user", Role = "user",
Content = $"[이전 대화 요약 — {oldMessages.Count}개 메시지 압축]\n{summary}", Content = BuildSummaryMessageContent(oldMessages.Count, summary, attachmentSummary),
Timestamp = DateTime.Now, Timestamp = DateTime.Now,
AttachedFiles = attachmentSummary.AttachedFiles.Count > 0 ? attachmentSummary.AttachedFiles : null,
}); });
messages.Add(new ChatMessage messages.Add(new ChatMessage
{ {
@@ -806,4 +806,42 @@ public static class ContextCondenser
return false; return false;
} }
} }
private static CompactionAttachmentSummary CollectCompactionAttachmentSummary(IEnumerable<ChatMessage> messages, int maxFiles)
{
var attachedFiles = messages
.SelectMany(m => m.AttachedFiles ?? Enumerable.Empty<string>())
.Distinct(StringComparer.OrdinalIgnoreCase)
.Select(path =>
{
try { return System.IO.Path.GetFileName(path); }
catch { return path; }
})
.Where(name => !string.IsNullOrWhiteSpace(name))
.Take(Math.Max(1, maxFiles))
.ToList();
var imageCount = messages.Sum(m => m.Images?.Count ?? 0);
return new CompactionAttachmentSummary
{
AttachedFiles = attachedFiles,
ImageCount = imageCount,
};
}
private static string BuildSummaryMessageContent(int oldMessageCount, string summary, CompactionAttachmentSummary attachmentSummary)
{
var lines = new List<string>
{
$"[이전 대화 요약 — {oldMessageCount}개 메시지 압축]",
summary.Trim()
};
if (attachmentSummary.AttachedFiles.Count > 0)
lines.Add($"참고 파일: {string.Join(", ", attachmentSummary.AttachedFiles)}");
if (attachmentSummary.ImageCount > 0)
lines.Add($"참고 이미지: {attachmentSummary.ImageCount}개");
return string.Join("\n", lines.Where(x => !string.IsNullOrWhiteSpace(x)));
}
} }