mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-11-10 08:28:22 +00:00
* refactor: generalize decision models and add opt-in context compaction * chore: remove compaction-specific license metadata * test: add live decision compaction scenarios and strategy guides * docs: describe current decision model configuration directly * refactor: preserve recoverable tool evidence and add compaction benchmarks * docs: visualize compaction benchmark results with English charts * docs: fix compaction benchmark links with verified permalinks * benchmark: evaluate fixed-summary evidence additions and summary-aware selection * docs: report summary-aware incremental evidence results * docs: unify compaction strategy comparison * refactor: replace compaction studies with one matched-input benchmark * docs: pin matched-input results links * bench: evaluate gold-labelled HotpotQA trajectories collected with Hermes * docs: pin gold-labelled benchmark results * bench: focus on frozen-summary Jev Top-N payment evaluation * docs: pin frozen-summary benchmark results * bench: evaluate fixed-summary fusion across Hermes fixture tasks * docs: pin compaction study references to reviewed results * bench: compare Jev fusion across frozen summary strategies * docs: pin dual-summary benchmark and figure references * docs: specify reproducible compaction settings and Hermes summary scope * docs: plot Jev token overhead by retained Top N * docs: pin reproducibility settings and revised cost figure * docs: price total compaction and answering cost across Top N * docs: pin final cost accounting and reproduction references * docs: explain compaction experiment background setup and conclusions inline * docs: compare compaction-only costs and omit Full from cost panel * docs: pin compaction-only cost references * docs: retain only quality and context-length experiment comparisons * docs: pin streamlined experiment references * docs: add Chinese counterparts for all compaction benchmark guides * docs: link benchmark guides in the matching language * docs: consolidate bilingual compaction benchmark guides * docs: point compaction references to consolidated benchmark * docs: reference Hermes Jev compaction evaluation results * fix(benchmark): validate cached native summary provenance
128 lines
4.3 KiB
Python
128 lines
4.3 KiB
Python
"""Pair and rebuild AgentScope blocks without rewriting conversation text."""
|
|
|
|
from collections import defaultdict
|
|
from dataclasses import dataclass
|
|
import json
|
|
|
|
from ...schema.compaction import CompactionRequest
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ToolPair:
|
|
"""Block locations remain valid until all decisions have been collected."""
|
|
|
|
id: str
|
|
call_id: str
|
|
tool: str
|
|
input: dict
|
|
call_location: tuple[int, int]
|
|
result_location: tuple[int, int]
|
|
result_chars: int
|
|
protected: bool
|
|
source_ref: str | None
|
|
|
|
|
|
def result_text(block: dict) -> str | None:
|
|
"""Only entirely textual outputs can be shortened."""
|
|
output = block.get("output")
|
|
if isinstance(output, str):
|
|
return output
|
|
if (
|
|
isinstance(output, list)
|
|
and output
|
|
and all(
|
|
isinstance(item, dict) and item.get("type") == "text" and isinstance(item.get("text"), str)
|
|
for item in output
|
|
)
|
|
):
|
|
return "\n".join(item["text"] for item in output)
|
|
return None
|
|
|
|
|
|
def collect_pairs(request: CompactionRequest) -> list[ToolPair]:
|
|
"""Ambiguous, incomplete, failed, unknown, or protected calls are never candidates."""
|
|
calls, results = defaultdict(list), defaultdict(list)
|
|
messages = request.messages
|
|
recent = len(messages) - request.options.preserve_recent_messages
|
|
protected = {
|
|
index
|
|
for index, message in enumerate(messages)
|
|
if index == 0
|
|
or index >= recent
|
|
or message.get("role") == "system"
|
|
or message.get("id") in request.protected_message_ids
|
|
}
|
|
for mi, message in enumerate(messages):
|
|
for bi, block in enumerate(message["content"]):
|
|
block_id = block.get("id")
|
|
if not isinstance(block_id, str) or not block_id:
|
|
continue
|
|
if block.get("type") == "tool_call":
|
|
calls[block_id].append((mi, bi))
|
|
elif block.get("type") == "tool_result":
|
|
results[block_id].append((mi, bi))
|
|
pairs = []
|
|
for call_id, locations in calls.items():
|
|
if len(locations) != 1 or len(results[call_id]) != 1:
|
|
continue
|
|
ci, ri = locations[0], results[call_id][0]
|
|
call, result = messages[ci[0]]["content"][ci[1]], messages[ri[0]]["content"][ri[1]]
|
|
if ri <= ci:
|
|
continue
|
|
try:
|
|
inputs = json.loads(call.get("input", ""))
|
|
# Python accepts NaN/Infinity and overflowing numbers in JSON text.
|
|
# Such inputs must stay untouched rather than break other candidates.
|
|
json.dumps(inputs, allow_nan=False)
|
|
except (TypeError, ValueError):
|
|
continue
|
|
if not isinstance(inputs, dict):
|
|
continue
|
|
text = result_text(result)
|
|
pinned = (
|
|
ci[0] in protected
|
|
or ri[0] in protected
|
|
or call_id in request.protected_call_ids
|
|
or call.get("name") not in request.eligible_tool_names
|
|
or call.get("state") != "finished"
|
|
or result.get("state") != "success"
|
|
or result.get("name") != call.get("name")
|
|
or text is None
|
|
or call_id not in request.source_refs
|
|
)
|
|
pairs.append(
|
|
ToolPair(
|
|
f"t{len(pairs) + 1}",
|
|
call_id,
|
|
call.get("name", ""),
|
|
inputs,
|
|
ci,
|
|
ri,
|
|
len(text or ""),
|
|
pinned,
|
|
request.source_refs.get(call_id),
|
|
),
|
|
)
|
|
return pairs
|
|
|
|
|
|
def excerpt(text: str, limit: int) -> str:
|
|
"""Keep both ends verbatim; never exceed the character limit."""
|
|
if len(text) <= limit:
|
|
return text
|
|
marker = "\n…\n"
|
|
if limit < len(marker):
|
|
return text[:limit]
|
|
available = limit - len(marker)
|
|
head = (available + 1) // 2
|
|
tail = available - head
|
|
return text[:head] + marker + (text[-tail:] if tail else "")
|
|
|
|
|
|
def preview_result(block: dict, source_ref: str | None, limit: int) -> str | None:
|
|
"""Build one recoverable replacement without copying or changing the source history."""
|
|
text = result_text(block)
|
|
if text is None or not source_ref:
|
|
return None
|
|
replacement = excerpt(text, limit) + f"\n[Compacted output. Recover from: {source_ref}]"
|
|
return replacement if len(replacement) < len(text) else None
|