Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions tests/sessions/test_summarizer_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ def _make_event_with_usage(total_tokens: int) -> Event:
return event


def _make_summary_event() -> Event:
event = _make_event_with_text("Previous conversation summary")
event.set_summary_event(True)
event.set_model_visible(True)
return event


def _make_event_with_text(text: str) -> Event:
return Event(
invocation_id="inv-1",
Expand Down Expand Up @@ -90,6 +97,29 @@ def test_no_usage_metadata(self):
session = _make_session(events=events)
assert checker(session) is False

def test_ignores_tokens_before_latest_summary(self):
checker = set_summarizer_token_threshold(100)
old_event = _make_event_with_usage(1000)
summary_event = _make_summary_event()
new_event = _make_event_with_usage(10)
old_event.timestamp = 1.0
summary_event.timestamp = 2.0
new_event.timestamp = 3.0
session = _make_session(events=[old_event, summary_event, new_event])

assert checker(session) is False

def test_ignores_invisible_tokens_after_latest_summary(self):
checker = set_summarizer_token_threshold(100)
summary_event = _make_summary_event()
invisible_event = _make_event_with_usage(1000)
summary_event.timestamp = 1.0
invisible_event.timestamp = 2.0
invisible_event.set_model_visible(False)
session = _make_session(events=[summary_event, invisible_event])

assert checker(session) is False


class TestEventsCountThreshold:
"""Test set_summarizer_events_count_threshold."""
Expand Down Expand Up @@ -118,6 +148,34 @@ def test_default_threshold(self):
session = _make_session(events=events)
assert checker(session) is True

def test_counts_only_visible_events_after_latest_summary(self):
checker = set_summarizer_events_count_threshold(2)
old_events = [_make_event_with_text(f"old{i}") for i in range(10)]
summary_event = _make_summary_event()
new_events = [_make_event_with_text("new1"), _make_event_with_text("new2")]
for idx, event in enumerate(old_events):
event.timestamp = float(idx)
summary_event.timestamp = 20.0
new_events[0].timestamp = 21.0
new_events[1].timestamp = 22.0
session = _make_session(events=[*old_events, summary_event, *new_events])

assert checker(session) is False

def test_counts_after_the_latest_summary_event(self):
checker = set_summarizer_events_count_threshold(1)
first_summary = _make_summary_event()
event_after_first_summary = _make_event_with_text("already summarized")
latest_summary = _make_summary_event()
new_event = _make_event_with_text("new")
first_summary.timestamp = 1.0
event_after_first_summary.timestamp = 2.0
latest_summary.timestamp = 3.0
new_event.timestamp = 4.0
session = _make_session(events=[first_summary, event_after_first_summary, latest_summary, new_event])

assert checker(session) is False


class TestTimeIntervalThreshold:
"""Test set_summarizer_time_interval_threshold."""
Expand All @@ -143,6 +201,27 @@ def test_default_threshold(self):
session = _make_session(events=[event])
assert checker(session) is False

def test_requires_visible_events_after_latest_summary(self):
checker = set_summarizer_time_interval_threshold(10.0)
old_event = _make_event_with_text("old")
summary_event = _make_summary_event()
old_event.timestamp = time.time() - 100.0
summary_event.timestamp = time.time() - 20.0
session = _make_session(events=[old_event, summary_event])

assert checker(session) is False

def test_ignores_invisible_events_after_latest_summary(self):
checker = set_summarizer_time_interval_threshold(10.0)
summary_event = _make_summary_event()
invisible_event = _make_event_with_text("invisible")
summary_event.timestamp = time.time() - 20.0
invisible_event.timestamp = time.time() - 20.0
invisible_event.set_model_visible(False)
session = _make_session(events=[summary_event, invisible_event])

assert checker(session) is False


class TestImportantContentThreshold:
"""Test set_summarizer_important_content_threshold."""
Expand Down Expand Up @@ -182,6 +261,29 @@ def test_event_with_whitespace_only(self):
session = _make_session(events=events)
assert checker(session) is False

def test_ignores_important_content_before_latest_summary(self):
checker = set_summarizer_important_content_threshold(5)
old_important_event = _make_event_with_text("This old content is important")
summary_event = _make_summary_event()
new_short_event = _make_event_with_text("short")
old_important_event.timestamp = 1.0
summary_event.timestamp = 2.0
new_short_event.timestamp = 3.0
session = _make_session(events=[old_important_event, summary_event, new_short_event])

assert checker(session) is False

def test_ignores_invisible_important_content_after_latest_summary(self):
checker = set_summarizer_important_content_threshold(5)
summary_event = _make_summary_event()
invisible_important_event = _make_event_with_text("This invisible content is important")
summary_event.timestamp = 1.0
invisible_important_event.timestamp = 2.0
invisible_important_event.set_model_visible(False)
session = _make_session(events=[summary_event, invisible_important_event])

assert checker(session) is False


class TestConversationThreshold:
"""Test set_summarizer_conversation_threshold."""
Expand Down
42 changes: 37 additions & 5 deletions trpc_agent_sdk/sessions/_summarizer_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,34 @@
from typing import Callable
from typing import List

from trpc_agent_sdk.events import Event
from trpc_agent_sdk.log import logger

from ._session import Session

CheckSummarizerFunction = Callable[[Session], bool]


def _latest_summary_event(session: Session) -> Event | None:
"""Return the latest summary event in the session, if any."""
for event in reversed(session.events):
if event.is_summary_event():
return event
return None


def _events_after_latest_summary(session: Session) -> List[Event]:
"""Return visible events created after the latest summary event."""
latest_summary_event = _latest_summary_event(session)
if latest_summary_event is None:
return [event for event in session.events if event.is_model_visible()]

return [
event for event in session.events if event.is_model_visible() and not event.is_summary_event()
and event.timestamp > latest_summary_event.timestamp
]


def set_summarizer_token_threshold(token_count: int) -> CheckSummarizerFunction:
"""Set the token threshold for summarizer.

Expand All @@ -30,7 +51,9 @@ def set_summarizer_token_threshold(token_count: int) -> CheckSummarizerFunction:

def _decorator(session: Session) -> bool:
# Filter events with usage_metadata
events_with_metadata = [event for event in session.events if event.usage_metadata is not None]
events_with_metadata = [
event for event in _events_after_latest_summary(session) if event.usage_metadata is not None
]

# If no events have usage_metadata, log a warning and return False
if not events_with_metadata:
Expand Down Expand Up @@ -60,7 +83,7 @@ def set_summarizer_events_count_threshold(event_count: int = 30) -> CheckSummari

def _decorator(session: Session) -> bool:
# Check if we have enough events to warrant summarization
return len(session.events) > event_count
return len(_events_after_latest_summary(session)) > event_count

return _decorator

Expand All @@ -76,8 +99,17 @@ def set_summarizer_time_interval_threshold(time_interval: float = 300.0) -> Chec
"""

def _decorator(session: Session) -> bool:
# Check if it's been long enough since the last summarization
return time.time() - session.events[-1].timestamp > time_interval
latest_summary_event = _latest_summary_event(session)
events_after_summary = _events_after_latest_summary(session)
if not events_after_summary:
return False

if latest_summary_event is not None:
# Only trigger after new visible events have accumulated past the latest summary.
return time.time() - latest_summary_event.timestamp > time_interval

# No summary exists yet; preserve the original inactivity-based behavior.
return time.time() - events_after_summary[-1].timestamp > time_interval

return _decorator

Expand All @@ -94,7 +126,7 @@ def set_summarizer_important_content_threshold(important_content_count: int = 10

def _decorator(session: Session) -> bool:
# Check if there's important content to summarize
for event in session.events:
for event in _events_after_latest_summary(session):
if event.content and event.content.parts:
for part in event.content.parts:
if part.text and len(part.text.strip()) > important_content_count:
Expand Down
Loading