Этап 7: долговременная память

- Факты о пользователе в SQLite, в системном промпте с номерами
- Инструменты remember / update_memory / forget, автоматическое запоминание отключается
- Цикл вызова инструментов со стримингом (до 5 кругов), проверка и приведение аргументов
- Откат без инструментов для моделей, которые их не поддерживают, с одним предупреждением
- Действия в журнале, вкладка «Память» в настройках
- Фейковый OpenAI-совместимый сервер для тестов, тесты полного цикла через Assistant

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nid
2026-09-17 04:27:16 +03:00
co-authored by Claude Opus 5
parent 542389c0f9
commit 84d8aaa848
18 changed files with 1168 additions and 37 deletions
+73
View File
@@ -0,0 +1,73 @@
from pathlib import Path
import pytest
from agr_assistent.core.memory import (
SOURCE_AUTO,
SOURCE_USER,
MemoryStore,
memory_prompt,
memory_tools,
)
from agr_assistent.llm.tools import ToolRegistry
@pytest.fixture
def store(tmp_path: Path) -> MemoryStore:
memory = MemoryStore(tmp_path / "memory.sqlite3")
yield memory
memory.close()
def test_facts_survive_reopening(tmp_path: Path) -> None:
path = tmp_path / "memory.sqlite3"
first = MemoryStore(path)
fact = first.add(" Машина пользователя — Škoda Octavia ", SOURCE_USER)
first.close()
second = MemoryStore(path)
assert [(f.id, f.text, f.source) for f in second.facts()] == [
(fact.id, "Машина пользователя — Škoda Octavia", SOURCE_USER)
]
second.close()
def test_duplicates_update_and_delete(store: MemoryStore) -> None:
fact = store.add("Зовут Лео", SOURCE_AUTO)
assert store.add("зовут лео", SOURCE_USER).id == fact.id
assert store.update(fact.id, "Зовут Леонид").text == "Зовут Леонид"
assert store.update(999, "нет такого") is None
assert store.delete(fact.id).text == "Зовут Леонид"
assert store.delete(fact.id) is None
assert store.facts() == []
def test_memory_tools(store: MemoryStore) -> None:
tools = ToolRegistry(memory_tools(store))
saved = tools.execute("remember", '{"text": "Любит кофе", "requested_by_user": false}')
assert saved.ok and saved.display == "Запомнил сам: Любит кофе"
fact_id = store.facts()[0].id
assert store.facts()[0].is_auto
assert tools.execute("update_memory", f'{{"id": {fact_id}, "text": "Любит чай"}}').ok
assert store.facts()[0].text == "Любит чай"
missing = tools.execute("forget", '{"id": 12345}')
assert not missing.ok and "12345" in missing.content
assert tools.execute("forget", f'{{"id": "{fact_id}"}}').ok
assert store.facts() == []
assert not tools.execute("remember", '{"text": " "}').ok
def test_prompt_lists_facts_and_respects_auto_save(store: MemoryStore) -> None:
fact = store.add("Живёт в Казани", SOURCE_USER)
with_auto = memory_prompt(store.facts(), auto_save=True)
without_auto = memory_prompt(store.facts(), auto_save=False)
assert f"[{fact.id}] Живёт в Казани" in with_auto
assert "requested_by_user=false" in with_auto
assert "requested_by_user=false" not in without_auto
assert "пока пусто" in memory_prompt([], auto_save=True)