Files

247 lines
7.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AxCopilot.Models;
namespace AxCopilot.Services.Agent;
public static class ContextCondenser
{
private const int MaxToolResultChars = 1500;
private const int RecentKeepCount = 6;
private static int GetModelInputLimit(string service, string model)
{
string text = (service + ":" + model).ToLowerInvariant();
string text2 = text;
if (1 == 0)
{
}
int result = ((!text.Contains("claude")) ? ((!text.Contains("gemini-2.5")) ? ((!text.Contains("gemini-2.0")) ? ((!text.Contains("gemini")) ? ((!text.Contains("gpt-4")) ? 16000 : 120000) : 900000) : 900000) : 900000) : 180000);
if (1 == 0)
{
}
return result;
}
public static async Task<bool> CondenseIfNeededAsync(List<ChatMessage> messages, LlmService llm, int maxOutputTokens, CancellationToken ct = default(CancellationToken))
{
if (messages.Count < 6)
{
return false;
}
(string service, string model) settings = llm.GetCurrentModelInfo();
int inputLimit = GetModelInputLimit(settings.service, settings.model);
int threshold = (int)((double)inputLimit * 0.65);
int currentTokens = TokenEstimator.EstimateMessages(messages);
if (currentTokens < threshold)
{
return false;
}
bool didCompress = false;
didCompress |= TruncateToolResults(messages);
currentTokens = TokenEstimator.EstimateMessages(messages);
if (currentTokens < threshold)
{
return didCompress;
}
bool flag = didCompress;
didCompress = flag | await SummarizeOldMessagesAsync(messages, llm, ct);
if (didCompress)
{
int afterTokens = TokenEstimator.EstimateMessages(messages);
LogService.Info($"Context Condenser: {currentTokens} → {afterTokens} 토큰 (절감 {currentTokens - afterTokens})");
}
return didCompress;
}
private static bool TruncateToolResults(List<ChatMessage> messages)
{
bool result = false;
int num = Math.Max(0, messages.Count - 6);
for (int i = 0; i < num; i++)
{
ChatMessage chatMessage = messages[i];
if (chatMessage.Content == null)
{
continue;
}
if (chatMessage.Content.StartsWith("{\"type\":\"tool_result\"") && chatMessage.Content.Length > 1500)
{
messages[i] = new ChatMessage
{
Role = chatMessage.Role,
Content = TruncateToolResultJson(chatMessage.Content),
Timestamp = chatMessage.Timestamp
};
result = true;
}
else if (chatMessage.Role == "assistant" && chatMessage.Content.Length > 3000 && chatMessage.Content.StartsWith("{\"_tool_use_blocks\""))
{
if (chatMessage.Content.Length > 4500)
{
messages[i] = new ChatMessage
{
Role = chatMessage.Role,
Content = chatMessage.Content.Substring(0, 3000) + "...[축약됨]\"]}",
Timestamp = chatMessage.Timestamp
};
result = true;
}
}
else if (chatMessage.Content.Length > 4500 && chatMessage.Role != "system")
{
messages[i] = new ChatMessage
{
Role = chatMessage.Role,
Content = chatMessage.Content.Substring(0, 1500) + "\n\n...[이전 내용 축약됨 — 원본 " + $"{chatMessage.Content.Length:N0}자 중 {1500:N0}자 유지]",
Timestamp = chatMessage.Timestamp
};
result = true;
}
}
return result;
}
private static string TruncateToolResultJson(string json)
{
int num = json.IndexOf("\"output\":\"", StringComparison.Ordinal);
if (num < 0)
{
return json.Substring(0, Math.Min(json.Length, 1500)) + "...[축약됨]}";
}
int num2 = num + "\"output\":\"".Length;
int num3 = num2;
while (num3 < json.Length)
{
if (json[num3] == '\\')
{
num3 += 2;
continue;
}
if (json[num3] == '"')
{
break;
}
num3++;
}
int num4 = num3 - num2;
if (num4 <= 1500)
{
return json;
}
int num5 = 750;
string text = json.Substring(0, num2);
int num6 = num2;
string text2 = json.Substring(num6, num3 - num6);
num6 = num3;
string text3 = json.Substring(num6, json.Length - num6);
string[] obj = new string[9]
{
text,
text2.Substring(0, num5),
"\\n...[축약됨: ",
$"{num4:N0}",
"자 중 ",
$"{1500:N0}",
"자 유지]\\n",
null,
null
};
num6 = num5;
int length = text2.Length;
int num7 = length - num6;
obj[7] = text2.Substring(num7, length - num7);
obj[8] = text3;
return string.Concat(obj);
}
private static async Task<bool> SummarizeOldMessagesAsync(List<ChatMessage> messages, LlmService llm, CancellationToken ct)
{
ChatMessage systemMsg = messages.FirstOrDefault((ChatMessage chatMessage) => chatMessage.Role == "system");
if (systemMsg == null)
{
}
List<ChatMessage> nonSystemMessages = messages.Where((ChatMessage chatMessage) => chatMessage.Role != "system").ToList();
int keepCount = Math.Min(6, nonSystemMessages.Count);
List<ChatMessage> recentMessages = nonSystemMessages.Skip(nonSystemMessages.Count - keepCount).ToList();
List<ChatMessage> oldMessages = nonSystemMessages.Take(nonSystemMessages.Count - keepCount).ToList();
if (oldMessages.Count < 3)
{
return false;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine("다음 대화 기록을 간결하게 요약하세요.");
sb.AppendLine("반드시 유지할 정보: 사용자 요청, 핵심 결정 사항, 생성/수정된 파일 경로, 작업 진행 상황, 중요한 결과.");
sb.AppendLine("제거할 정보: 도구 실행의 상세 출력, 반복되는 내용, 중간 사고 과정.");
sb.AppendLine("요약은 대화와 동일한 언어로 작성하세요. 글머리 기호(-)로 핵심 사항만 나열하세요.\n---");
foreach (ChatMessage m in oldMessages)
{
string content = m.Content ?? "";
if (content.StartsWith("{\"_tool_use_blocks\""))
{
content = "[도구 호출]";
}
else if (content.StartsWith("{\"type\":\"tool_result\""))
{
content = "[도구 결과]";
}
else if (content.Length > 300)
{
content = content.Substring(0, 300) + "...";
}
StringBuilder stringBuilder = sb;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(4, 2, stringBuilder);
handler.AppendLiteral("[");
handler.AppendFormatted(m.Role);
handler.AppendLiteral("]: ");
handler.AppendFormatted(content);
stringBuilder.AppendLine(ref handler);
}
try
{
List<ChatMessage> summaryMessages = new List<ChatMessage>
{
new ChatMessage
{
Role = "user",
Content = sb.ToString()
}
};
string summary = await llm.SendAsync(summaryMessages, ct);
if (string.IsNullOrEmpty(summary))
{
return false;
}
messages.Clear();
if (systemMsg != null)
{
messages.Add(systemMsg);
}
messages.Add(new ChatMessage
{
Role = "user",
Content = $"[이전 대화 요약 — {oldMessages.Count}개 메시지 압축]\n{summary}",
Timestamp = DateTime.Now
});
messages.Add(new ChatMessage
{
Role = "assistant",
Content = "이전 대화 내용을 확인했습니다. 이어서 작업하겠습니다.",
Timestamp = DateTime.Now
});
messages.AddRange(recentMessages);
return true;
}
catch (Exception ex)
{
LogService.Warn("Context Condenser 요약 실패: " + ex.Message);
return false;
}
}
}