247 lines
7.1 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|