ReMe/reme/schema/compaction.py
jinliyl 548b08561e
refactor: generalize decision models and add recoverable context compaction (#626)
* 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
2026-10-19 17:56:30 +08:00

83 lines
3.4 KiB
Python

"""JSON contracts for recoverable, tool-only context compaction."""
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, JsonValue, field_validator
class CompactionOptions(BaseModel):
"""Decision request limits and optional host-counted output budget."""
model_config = ConfigDict(extra="forbid", allow_inf_nan=False)
drop_threshold: float = Field(default=0.1, ge=0, le=1)
preserve_recent_messages: int = Field(default=6, ge=0, strict=True)
max_state_tokens: int = Field(default=25000, gt=0, strict=True)
max_request_tokens: int = Field(default=30000, gt=0, strict=True)
evidence_chars: int = Field(default=2000, gt=0, strict=True)
preview_chars: int = Field(default=300, ge=0, strict=True)
max_output_tokens: int | None = Field(default=None, gt=0, strict=True)
max_concurrency: int = Field(default=4, ge=1, le=32, strict=True)
total_timeout_seconds: float = Field(default=30, gt=0)
class CompactionRequest(BaseModel):
"""The host supplies immutable AgentScope snapshots and resolvable archive references."""
model_config = ConfigDict(extra="forbid", allow_inf_nan=False)
messages: list[dict[str, JsonValue]]
goal: str = ""
next_action: str = ""
source_refs: dict[str, str] = Field(default_factory=dict)
protected_message_ids: list[str] = Field(default_factory=list)
protected_call_ids: list[str] = Field(default_factory=list)
eligible_tool_names: list[str] = Field(default_factory=list)
options: CompactionOptions = Field(default_factory=CompactionOptions)
@field_validator("source_refs")
@classmethod
def validate_refs(cls, refs):
"""References are opaque host identifiers, not paths ReMe opens."""
if any(not key.strip() or not value.strip() for key, value in refs.items()):
raise ValueError("Source references need non-empty call IDs and values.")
return refs
@field_validator("messages")
@classmethod
def validate_messages(cls, messages):
"""Reject ambiguous envelopes while retaining unknown block fields."""
for message in messages:
if message.get("role") not in ("user", "assistant", "system"):
raise ValueError("Each message needs a user, assistant, or system role.")
content = message.get("content")
if not isinstance(content, list) or any(not isinstance(block, dict) for block in content):
raise ValueError("Each message needs a list of content block objects.")
return messages
class CallDecision(BaseModel):
"""Every replacement identifies its original tool pair and recoverable source."""
model_config = ConfigDict(extra="forbid", allow_inf_nan=False)
id: str
call_id: str
action: Literal["keep", "preview"]
reason: str
probability: float | None = Field(default=None, ge=0, le=1)
source_ref: str | None = None
class CompactionResult(BaseModel):
"""A proposed snapshot; the host owns archiving, recovery, and commit."""
model_config = ConfigDict(extra="forbid", allow_inf_nan=False)
schema_version: Literal[2] = 2
messages: list[dict[str, JsonValue]]
decisions: list[CallDecision] = Field(default_factory=list)
changed: bool
reason: Literal["compacted", "no_candidates", "no_change", "budget_exceeded"]
budget_met: bool | None = None
stats: dict[str, JsonValue] = Field(default_factory=dict)