nah
This commit is contained in:
@@ -7,6 +7,7 @@ while preserving the most important information.
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
@@ -157,6 +158,140 @@ class ContextCompactor:
|
||||
break
|
||||
return "\n".join(lines)
|
||||
|
||||
def compact_conversation(
|
||||
self,
|
||||
messages: list[dict[str, Any]],
|
||||
max_tokens: int,
|
||||
recent_raw_limit: int = 2,
|
||||
raw_char_limit: int = 2000,
|
||||
) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
||||
"""Compact conversation history while preserving the latest user turn."""
|
||||
total = sum(self._estimate_message_tokens(m) for m in messages)
|
||||
meta = {
|
||||
"budget_tokens": max_tokens,
|
||||
"input_tokens": total,
|
||||
"output_tokens": total,
|
||||
"messages_input": len(messages),
|
||||
"messages_output": len(messages),
|
||||
"messages_compacted": 0,
|
||||
"tool_messages_compacted": 0,
|
||||
"assistant_messages_compacted": 0,
|
||||
"user_messages_compacted": 0,
|
||||
"raw_tool_results_kept": 0,
|
||||
}
|
||||
if total <= max_tokens:
|
||||
return messages, meta
|
||||
|
||||
compacted = [dict(m) for m in messages]
|
||||
last_user_idx = max(
|
||||
(i for i, m in enumerate(compacted) if m.get("role") == "user"),
|
||||
default=-1,
|
||||
)
|
||||
tool_indexes = [i for i, m in enumerate(compacted) if m.get("role") == "tool"]
|
||||
keep_raw_tool_indexes = (
|
||||
set(tool_indexes[-recent_raw_limit:])
|
||||
if recent_raw_limit > 0
|
||||
else set()
|
||||
)
|
||||
|
||||
for idx in keep_raw_tool_indexes:
|
||||
content = compacted[idx].get("content", "")
|
||||
if isinstance(content, str) and content:
|
||||
truncated = content[:raw_char_limit]
|
||||
if truncated != content:
|
||||
compacted[idx]["content"] = truncated
|
||||
meta["messages_compacted"] += 1
|
||||
meta["tool_messages_compacted"] += 1
|
||||
meta["raw_tool_results_kept"] += 1
|
||||
|
||||
total = sum(self._estimate_message_tokens(m) for m in compacted)
|
||||
if total > max_tokens:
|
||||
for idx in tool_indexes:
|
||||
if idx in keep_raw_tool_indexes:
|
||||
continue
|
||||
content = compacted[idx].get("content", "")
|
||||
if not isinstance(content, str) or not content:
|
||||
continue
|
||||
compacted[idx]["content"] = self._summarize_message_content(
|
||||
content,
|
||||
prefix="[TOOL RESULT COMPACTADO]",
|
||||
max_chars=max(180, raw_char_limit // 4),
|
||||
)
|
||||
meta["messages_compacted"] += 1
|
||||
meta["tool_messages_compacted"] += 1
|
||||
total = sum(self._estimate_message_tokens(m) for m in compacted)
|
||||
if total <= max_tokens:
|
||||
break
|
||||
|
||||
if total > max_tokens:
|
||||
for idx, message in enumerate(compacted):
|
||||
if idx == last_user_idx or message.get("role") != "assistant":
|
||||
continue
|
||||
content = message.get("content", "")
|
||||
if not isinstance(content, str) or not content:
|
||||
continue
|
||||
message["content"] = self._summarize_message_content(
|
||||
content,
|
||||
prefix="[ASSISTANT COMPACTADO]",
|
||||
max_chars=max(240, raw_char_limit // 3),
|
||||
)
|
||||
meta["messages_compacted"] += 1
|
||||
meta["assistant_messages_compacted"] += 1
|
||||
total = sum(self._estimate_message_tokens(m) for m in compacted)
|
||||
if total <= max_tokens:
|
||||
break
|
||||
|
||||
if total > max_tokens:
|
||||
for idx, message in enumerate(compacted):
|
||||
if idx == last_user_idx or message.get("role") != "user":
|
||||
continue
|
||||
content = message.get("content", "")
|
||||
if not isinstance(content, str) or not content:
|
||||
continue
|
||||
message["content"] = self._summarize_message_content(
|
||||
content,
|
||||
prefix="[USER CONTEXT COMPACTADO]",
|
||||
max_chars=max(220, raw_char_limit // 3),
|
||||
)
|
||||
meta["messages_compacted"] += 1
|
||||
meta["user_messages_compacted"] += 1
|
||||
total = sum(self._estimate_message_tokens(m) for m in compacted)
|
||||
if total <= max_tokens:
|
||||
break
|
||||
|
||||
if total > max_tokens:
|
||||
for idx in tool_indexes:
|
||||
if idx in keep_raw_tool_indexes:
|
||||
compacted[idx]["content"] = self._summarize_message_content(
|
||||
compacted[idx].get("content", ""),
|
||||
prefix="[TOOL RESULT COMPACTADO]",
|
||||
max_chars=max(180, raw_char_limit // 5),
|
||||
)
|
||||
total = sum(self._estimate_message_tokens(m) for m in compacted)
|
||||
if total <= max_tokens:
|
||||
break
|
||||
|
||||
if total > max_tokens:
|
||||
for idx, message in enumerate(compacted):
|
||||
if idx == last_user_idx:
|
||||
continue
|
||||
role = message.get("role", "")
|
||||
content = message.get("content", "")
|
||||
if not isinstance(content, str) or not content:
|
||||
continue
|
||||
if role == "tool":
|
||||
message["content"] = "[TOOL RESULT COMPACTADO]"
|
||||
elif role == "assistant":
|
||||
message["content"] = "[ASSISTANT COMPACTADO]"
|
||||
elif role == "user":
|
||||
message["content"] = "[USER CONTEXT COMPACTADO]"
|
||||
total = sum(self._estimate_message_tokens(m) for m in compacted)
|
||||
if total <= max_tokens:
|
||||
break
|
||||
|
||||
meta["output_tokens"] = total
|
||||
return compacted, meta
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Internals
|
||||
# ------------------------------------------------------------------
|
||||
@@ -186,6 +321,45 @@ class ContextCompactor:
|
||||
compacted.append(line)
|
||||
return "\n".join(compacted)
|
||||
|
||||
def _summarize_message_content(
|
||||
self,
|
||||
content: str,
|
||||
prefix: str,
|
||||
max_chars: int,
|
||||
) -> str:
|
||||
stripped = content.strip()
|
||||
compacted = self._compact_text(content)
|
||||
if len(compacted) <= max_chars:
|
||||
if compacted != stripped:
|
||||
summary = f"{prefix} {compacted}".strip()
|
||||
if len(summary) > max_chars:
|
||||
summary = summary[:max_chars].rstrip() + "…"
|
||||
return summary
|
||||
return compacted
|
||||
|
||||
lines = [l.strip() for l in compacted.splitlines() if l.strip()]
|
||||
if not lines:
|
||||
return prefix
|
||||
if len(lines) == 1:
|
||||
return f"{prefix} {lines[0][:max_chars]}".strip()
|
||||
|
||||
first = lines[0][: max_chars // 2]
|
||||
last = lines[-1][: max_chars // 3]
|
||||
summary = f"{prefix} First: {first}"
|
||||
if last and last != first:
|
||||
summary += f" | Last: {last}"
|
||||
if len(summary) > max_chars:
|
||||
summary = summary[:max_chars].rstrip() + "…"
|
||||
return summary
|
||||
|
||||
@staticmethod
|
||||
def _estimate_message_tokens(message: dict[str, Any]) -> int:
|
||||
content = message.get("content", "")
|
||||
tokens = estimate_tokens(content if isinstance(content, str) else str(content))
|
||||
if message.get("tool_calls"):
|
||||
tokens += estimate_tokens(json.dumps(message.get("tool_calls", []), ensure_ascii=False))
|
||||
return tokens
|
||||
|
||||
def _extract_facts(self, raw_output: str) -> list[str]:
|
||||
"""Extract short factual claims from tool output."""
|
||||
facts: list[str] = []
|
||||
|
||||
Reference in New Issue
Block a user