mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 00:03:01 +03:00
fix(memory): remove idle archive request fallback
This commit is contained in:
+19
-16
@@ -1064,7 +1064,11 @@ class Consolidator:
|
||||
logger.warning("Consolidation provider returned tool calls, raw-dumping to history")
|
||||
self.store.raw_archive(fallback_messages, session_key=session_key)
|
||||
return None
|
||||
summary = response.content or "[no summary]"
|
||||
summary = response.content
|
||||
if not summary or not summary.strip():
|
||||
logger.warning("Consolidation provider returned no summary, raw-dumping to history")
|
||||
self.store.raw_archive(fallback_messages, session_key=session_key)
|
||||
return None
|
||||
self.store.append_history(
|
||||
summary,
|
||||
max_chars=_ARCHIVE_SUMMARY_MAX_CHARS,
|
||||
@@ -1154,19 +1158,21 @@ class Consolidator:
|
||||
runtime=runtime,
|
||||
)
|
||||
if request_messages is None:
|
||||
return await self.archive(
|
||||
messages,
|
||||
runtime=runtime,
|
||||
session_key=session.key,
|
||||
logger.debug(
|
||||
"Idle consolidation cannot replay the full tail for {}; raw-dumping",
|
||||
session.key,
|
||||
)
|
||||
tools = self._get_tool_definitions()
|
||||
self.store.raw_archive(messages, session_key=session.key)
|
||||
return None
|
||||
budget = self._input_token_budget(runtime)
|
||||
if budget <= 0:
|
||||
return await self.archive(
|
||||
messages,
|
||||
runtime=runtime,
|
||||
session_key=session.key,
|
||||
logger.debug(
|
||||
"Idle consolidation has no safe input budget for {}; raw-dumping",
|
||||
session.key,
|
||||
)
|
||||
self.store.raw_archive(messages, session_key=session.key)
|
||||
return None
|
||||
tools = self._get_tool_definitions()
|
||||
estimated, source = estimate_prompt_tokens_chain(
|
||||
runtime.provider,
|
||||
runtime.model,
|
||||
@@ -1175,17 +1181,14 @@ class Consolidator:
|
||||
)
|
||||
if estimated > budget:
|
||||
logger.debug(
|
||||
"Idle consolidation falling back to bounded archive for {}: {}/{} via {}",
|
||||
"Idle consolidation prefix exceeds budget for {}; raw-dumping: {}/{} via {}",
|
||||
session.key,
|
||||
estimated,
|
||||
budget,
|
||||
source,
|
||||
)
|
||||
return await self.archive(
|
||||
messages,
|
||||
runtime=runtime,
|
||||
session_key=session.key,
|
||||
)
|
||||
self.store.raw_archive(messages, session_key=session.key)
|
||||
return None
|
||||
return await self._archive_request(
|
||||
request_messages=request_messages,
|
||||
fallback_messages=messages,
|
||||
|
||||
@@ -634,6 +634,15 @@ class TestConsolidatorTokenBudget:
|
||||
class TestCompactIdleSession:
|
||||
"""Idle compaction tests."""
|
||||
|
||||
@pytest.fixture
|
||||
def runtime(self, mock_provider):
|
||||
"""Exercise the structured idle-consolidation path by default."""
|
||||
return LLMRuntime.capture(
|
||||
mock_provider,
|
||||
"test-model",
|
||||
context_window_tokens=128_000,
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def real_consolidator(self, store, mock_provider):
|
||||
"""Create a Consolidator with a real SessionManager (not a mock)."""
|
||||
@@ -713,7 +722,6 @@ class TestCompactIdleSession:
|
||||
async def test_new_messages_advance_existing_archive_progress(
|
||||
self, real_consolidator, mock_provider, runtime
|
||||
):
|
||||
runtime = replace(runtime, context_window_tokens=128_000)
|
||||
mock_provider.chat_with_retry.return_value = MagicMock(
|
||||
content="Summary.", finish_reason="stop"
|
||||
)
|
||||
@@ -773,7 +781,6 @@ class TestCompactIdleSession:
|
||||
the recent suffix it retains. Otherwise a late user correction / final
|
||||
result that lands in the kept suffix is excluded from the persisted
|
||||
summary, leaving a stale wrong conclusion in history. Regression for #4264."""
|
||||
runtime = replace(runtime, context_window_tokens=128_000)
|
||||
mock_provider.chat_with_retry.return_value = MagicMock(
|
||||
content="Summary.", finish_reason="stop"
|
||||
)
|
||||
@@ -934,7 +941,6 @@ class TestCompactIdleSession:
|
||||
self, real_consolidator, mock_provider, runtime
|
||||
):
|
||||
"""30 turns with last_consolidated=50 → only unconsolidated tail considered."""
|
||||
runtime = replace(runtime, context_window_tokens=128_000)
|
||||
mock_provider.chat_with_retry.return_value = MagicMock(
|
||||
content="Tail summary.", finish_reason="stop"
|
||||
)
|
||||
@@ -972,7 +978,6 @@ class TestCompactIdleSession:
|
||||
mock_provider,
|
||||
runtime,
|
||||
):
|
||||
runtime = replace(runtime, context_window_tokens=128_000)
|
||||
mock_provider.chat_with_retry.return_value = MagicMock(
|
||||
content="Tail summary.", finish_reason="stop"
|
||||
)
|
||||
@@ -1023,7 +1028,6 @@ class TestCompactIdleSession:
|
||||
store,
|
||||
runtime,
|
||||
):
|
||||
runtime = replace(runtime, context_window_tokens=128_000)
|
||||
tools = [{"type": "function", "function": {"name": "lookup"}}]
|
||||
real_consolidator._get_tool_definitions.return_value = tools
|
||||
mock_provider.chat_with_retry.return_value = LLMResponse(
|
||||
@@ -1102,23 +1106,44 @@ class TestCompactIdleSession:
|
||||
assert sessions.get_or_create("cli:unexpected-tool").last_consolidated == 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_oversized_prefix_falls_back_to_bounded_archive(
|
||||
async def test_empty_response_uses_raw_fallback(
|
||||
self,
|
||||
real_consolidator,
|
||||
mock_provider,
|
||||
store,
|
||||
runtime,
|
||||
):
|
||||
async def bounded_chat(**kwargs):
|
||||
sent_chars = sum(
|
||||
len(str(message.get("content") or ""))
|
||||
for message in kwargs["messages"]
|
||||
)
|
||||
if sent_chars > 20_000:
|
||||
return LLMResponse(content="context too long", finish_reason="error")
|
||||
return LLMResponse(content="Bounded summary.", finish_reason="stop")
|
||||
mock_provider.chat_with_retry.return_value = LLMResponse(
|
||||
content="",
|
||||
finish_reason="stop",
|
||||
)
|
||||
sessions = real_consolidator.sessions
|
||||
session = sessions.get_or_create("cli:empty-summary")
|
||||
session.add_message("user", "remember this")
|
||||
session.add_message("assistant", "important answer")
|
||||
sessions.save(session)
|
||||
|
||||
mock_provider.chat_with_retry.side_effect = bounded_chat
|
||||
result = await real_consolidator.compact_idle_session(
|
||||
"cli:empty-summary",
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
assert result is None
|
||||
entries = store.read_unprocessed_history(since_cursor=0)
|
||||
assert len(entries) == 1
|
||||
assert entries[0]["content"].startswith("[RAW] ")
|
||||
assert "important answer" in entries[0]["content"]
|
||||
assert sessions.get_or_create("cli:empty-summary").last_consolidated == 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_oversized_prefix_raw_archives_without_flattened_llm_retry(
|
||||
self,
|
||||
real_consolidator,
|
||||
mock_provider,
|
||||
store,
|
||||
runtime,
|
||||
):
|
||||
runtime = replace(runtime, context_window_tokens=1_000)
|
||||
sessions = real_consolidator.sessions
|
||||
session = sessions.get_or_create("sdk:oversized")
|
||||
session.add_message("user", "x" * 100_000)
|
||||
@@ -1129,13 +1154,11 @@ class TestCompactIdleSession:
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
assert result == "Bounded summary."
|
||||
sent = mock_provider.chat_with_retry.call_args.kwargs["messages"]
|
||||
assert sum(len(str(message.get("content") or "")) for message in sent) < 20_000
|
||||
assert not any(
|
||||
"[RAW]" in entry["content"]
|
||||
for entry in store.read_unprocessed_history(since_cursor=0)
|
||||
)
|
||||
assert result is None
|
||||
mock_provider.chat_with_retry.assert_not_awaited()
|
||||
entries = store.read_unprocessed_history(since_cursor=0)
|
||||
assert len(entries) == 1
|
||||
assert entries[0]["content"].startswith("[RAW] ")
|
||||
assert sessions.get_or_create("sdk:oversized").last_consolidated == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -1145,7 +1168,6 @@ class TestCompactIdleSession:
|
||||
mock_provider,
|
||||
runtime,
|
||||
):
|
||||
runtime = replace(runtime, context_window_tokens=128_000)
|
||||
mock_provider.chat_with_retry.return_value = LLMResponse(
|
||||
content="Summary.",
|
||||
finish_reason="stop",
|
||||
|
||||
Reference in New Issue
Block a user