Files
simbarag/tests/unit/test_memory_retrieval.py
ryan 891f44c1a9 Keep the chat prompt prefix stable for llama-server prompt caching
llama-server only reuses a byte-identical prompt prefix. Several things
changed the prefix every turn, forcing most of the prompt to be re-prefilled:

- Memories: rank still chooses which memories fit the budget, but the chosen
  ones are emitted in stored order, so the block is identical across queries.
- History trimming: the conversation-evidence summary is only included when
  turns were actually dropped (it duplicated retained turns otherwise). On
  overflow, old turns are dropped at stable cut points down to ~65% of the
  budget, so the retained prefix holds for several turns instead of shifting
  by one turn every request.
- Tools: TOOL_ROUTING_MODE=static (default) binds the full role-appropriate
  tool set in registration order with no discovery tool; admin gating is
  unchanged. TOOL_ROUTING_MODE=dynamic keeps the keyword router.
- Observability: per-call model_calls in the stream_request log now include
  cached_prompt_tokens when the provider reports cache reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:01:46 -04:00

80 lines
2.9 KiB
Python

from unittest.mock import AsyncMock
import pytest
from blueprints.conversation import memory
def test_old_relevant_memory_beats_recent_unrelated():
memories = ["Favorite restaurant is Nori", "Cat Simba takes insulin"]
assert (
memory.rank_memories(memories, "What insulin does Simba take?")[0]
== memories[1]
)
@pytest.mark.parametrize("query", ["", "please", "quantum oscillators"])
def test_empty_or_unknown_query_retains_recency(query):
memories = ["Favorite restaurant is Nori", "Cat Simba takes insulin"]
assert memory.rank_memories(memories, query) == memories
@pytest.mark.asyncio
async def test_prompt_relevance_and_total_memory_budget(monkeypatch):
old = "Simba insulin dose is two units."
recent = "Favorite restaurant is Nori. " * 20
monkeypatch.setattr(
memory, "get_memories_for_user", AsyncMock(return_value=[recent, old])
)
monkeypatch.setattr(memory, "MEMORY_BUDGET_TOKENS", 110)
prompt = await memory.build_system_prompt_with_memories(
"user", query="Simba insulin"
)
assert old in prompt
assert recent not in prompt
assert memory.estimate_tokens(prompt[len(memory.SIMBA_SYSTEM_PROMPT) :]) <= 110
memory.get_memories_for_user.assert_awaited_once_with("user")
@pytest.mark.asyncio
async def test_backwards_compatible_no_query_and_empty_memories(monkeypatch):
fetch = AsyncMock(return_value=[])
monkeypatch.setattr(memory, "get_memories_for_user", fetch)
assert (
await memory.build_system_prompt_with_memories("user")
== memory.SIMBA_SYSTEM_PROMPT
)
fetch.return_value = ["Likes tea"]
assert "Likes tea" in await memory.build_system_prompt_with_memories("user")
@pytest.mark.asyncio
async def test_memory_block_is_identical_across_queries_when_all_fit(monkeypatch):
memories = ["Favorite restaurant is Nori", "Cat Simba takes insulin", "Gym is Y"]
monkeypatch.setattr(
memory, "get_memories_for_user", AsyncMock(return_value=memories)
)
prompts = {
await memory.build_system_prompt_with_memories("user", query=q)
for q in ["", "Simba insulin", "gym", "Nori restaurant"]
}
assert len(prompts) == 1
prompt = prompts.pop()
assert prompt.index("Nori") < prompt.index("insulin") < prompt.index("Gym")
@pytest.mark.asyncio
async def test_rank_chooses_memories_but_stored_order_is_kept(monkeypatch):
filler = "Favorite restaurant is Nori. " * 20
memories = ["Likes tea", filler, "Simba insulin dose is two units."]
monkeypatch.setattr(
memory, "get_memories_for_user", AsyncMock(return_value=memories)
)
monkeypatch.setattr(memory, "MEMORY_BUDGET_TOKENS", 110)
prompt = await memory.build_system_prompt_with_memories(
"user", query="Simba insulin"
)
assert filler not in prompt
# Insulin ranks first but is emitted after the newer memory.
assert prompt.index("Likes tea") < prompt.index("insulin")