Це повна навчальна реалізація прикладу зі статті Пам'ять агента: що він пам'ятає і навіщо.
Якщо ти ще не читав статтю, почни з неї. Тут фокус лише на коді і практичній різниці між short-term та long-term memory.
Що цей приклад демонструє
- Як короткострокова пам'ять може втратити ранні інструкції через обмежений контекст
- Як довгострокова пам'ять зберігає налаштування між задачами
- Чому один і той самий запит може дати різний результат залежно від типу пам'яті
- Як будувати простий memory-aware агент без зайвої складності
Структура проєкту
foundations/
└── agent-memory/
└── python/
├── main.py # запускає два сценарії і порівнює результат
├── agent.py # логіка агента з short/long memory
├── memory.py # short-term і long-term сховища
├── tools.py # прості джерела даних і рендер звіту
└── requirements.txt
Як запустити
1. Клонуй репозиторій і перейди в папку:
git clone https://github.com/AgentPatterns-tech/agentpatterns.git
cd foundations/agent-memory/python
2. Встанови залежності (для цього прикладу зовнішніх пакетів немає):
pip install -r requirements.txt
3. Запусти демо:
python main.py
Що ми будуємо в коді
Ми будуємо мінімальний агент, який формує тижневий звіт у двох режимах.
SCENARIO 1: лише short-term memory (контекст обмежений і може "забути" ранні prefs)SCENARIO 2: short-term + long-term memory (prefs збережені між задачами)
Ключова ідея: контекст задачі і пам'ять між задачами мають різні ролі.
Код
memory.py — short-term і long-term пам'ять
from dataclasses import dataclass, field
from typing import Any
@dataclass
class ShortMemory:
max_items: int = 6
items: list[dict[str, Any]] = field(default_factory=list)
def add(self, role: str, content: str) -> None:
self.items.append({"role": role, "content": content})
if len(self.items) > self.max_items:
self.items = self.items[-self.max_items :]
def snapshot(self) -> list[dict[str, Any]]:
return list(self.items)
def clear(self) -> None:
self.items.clear()
@dataclass
class LongMemoryStore:
_prefs: dict[str, dict[str, str]] = field(default_factory=dict)
def save_prefs(self, user_key: str, prefs: dict[str, str]) -> None:
self._prefs[user_key] = dict(prefs)
def load_prefs(self, user_key: str) -> dict[str, str]:
return dict(self._prefs.get(user_key, {}))
tools.py — дані і форматування звіту
def get_sales_total(user_id: int) -> float:
_ = user_id
return 12400.0
def get_orders_count(user_id: int) -> int:
_ = user_id
return 31
def render_report(*, total: float, orders: int, currency: str, report_format: str) -> str:
if report_format == "short-bullets":
return (
f"- Total sales: {total:.2f} {currency}\n"
f"- Orders: {orders}\n"
"- Status: stable"
)
return (
f"Sales report: total={total:.2f} {currency}, "
f"orders={orders}, status=stable"
)
agent.py — логіка вирішення prefs
from memory import LongMemoryStore, ShortMemory
from tools import get_orders_count, get_sales_total, render_report
DEFAULT_PREFS = {
"report_format": "default",
"currency": "USD",
}
def save_user_preferences(
*,
user_key: str,
prefs: dict[str, str],
short_memory: ShortMemory,
long_memory: LongMemoryStore,
) -> None:
short_memory.add("user", f"Save prefs: {prefs}")
long_memory.save_prefs(user_key, prefs)
short_memory.add("assistant", "Preferences saved to long-term memory")
def parse_prefs_from_short_memory(short_memory: ShortMemory) -> dict[str, str]:
# Simplified parser for learning: looks for lines like "pref:key=value".
parsed: dict[str, str] = {}
for item in short_memory.snapshot():
content = item["content"]
if "pref:" not in content:
continue
payload = content.split("pref:", 1)[1]
if "=" not in payload:
continue
key, value = payload.split("=", 1)
parsed[key.strip()] = value.strip()
return parsed
def build_weekly_report(
*,
user_id: int,
user_key: str,
request: str,
short_memory: ShortMemory,
long_memory: LongMemoryStore,
use_long_memory: bool,
) -> dict:
trace: list[str] = []
short_memory.add("user", request)
trace.append(f"request={request}")
short_prefs = parse_prefs_from_short_memory(short_memory)
trace.append(f"short_prefs={short_prefs}")
long_prefs = long_memory.load_prefs(user_key) if use_long_memory else {}
trace.append(f"long_prefs={long_prefs}")
prefs = {**DEFAULT_PREFS, **short_prefs, **long_prefs}
trace.append(f"resolved_prefs={prefs}")
total = get_sales_total(user_id)
orders = get_orders_count(user_id)
report = render_report(
total=total,
orders=orders,
currency=prefs["currency"],
report_format=prefs["report_format"],
)
short_memory.add("assistant", f"Report generated with prefs={prefs}")
return {
"prefs": prefs,
"report": report,
"trace": trace,
"short_memory_snapshot": short_memory.snapshot(),
}
main.py — порівняння двох сценаріїв
from agent import build_weekly_report, save_user_preferences
from memory import LongMemoryStore, ShortMemory
USER_ID = 42
USER_KEY = "user:anna"
def print_result(title: str, result: dict) -> None:
print(f"\n=== {title} ===")
print("Resolved prefs:", result["prefs"])
print("\nReport:")
print(result["report"])
print("\nTrace:")
for line in result["trace"]:
print(" ", line)
def main() -> None:
long_memory = LongMemoryStore()
# Scenario 1: only short-term memory, early instruction falls out of context.
short_memory_1 = ShortMemory(max_items=4)
short_memory_1.add("user", "pref:report_format=short-bullets")
short_memory_1.add("user", "pref:currency=EUR")
short_memory_1.add("assistant", "working...")
short_memory_1.add("assistant", "still working...")
short_memory_1.add("assistant", "collecting data...") # pushes out old prefs
result_short_only = build_weekly_report(
user_id=USER_ID,
user_key=USER_KEY,
request="Build weekly sales report",
short_memory=short_memory_1,
long_memory=long_memory,
use_long_memory=False,
)
print_result("SCENARIO 1: SHORT MEMORY ONLY", result_short_only)
# Scenario 2: persist prefs in long-term memory, then start a new task.
short_memory_2 = ShortMemory(max_items=4)
save_user_preferences(
user_key=USER_KEY,
prefs={"report_format": "short-bullets", "currency": "EUR"},
short_memory=short_memory_2,
long_memory=long_memory,
)
short_memory_2.clear() # new task, short memory resets
result_with_long = build_weekly_report(
user_id=USER_ID,
user_key=USER_KEY,
request="Build weekly sales report like last time",
short_memory=short_memory_2,
long_memory=long_memory,
use_long_memory=True,
)
print_result("SCENARIO 2: WITH LONG MEMORY", result_with_long)
if __name__ == "__main__":
main()
requirements.txt
# No external dependencies for this learning example.
Приклад виводу
python main.py
=== SCENARIO 1: SHORT MEMORY ONLY ===
Resolved prefs: {'report_format': 'default', 'currency': 'USD'}
Report:
Sales report: total=12400.00 USD, orders=31, status=stable
Trace:
request=Build weekly sales report
short_prefs={}
long_prefs={}
resolved_prefs={'report_format': 'default', 'currency': 'USD'}
=== SCENARIO 2: WITH LONG MEMORY ===
Resolved prefs: {'report_format': 'short-bullets', 'currency': 'EUR'}
Report:
- Total sales: 12400.00 EUR
- Orders: 31
- Status: stable
Trace:
request=Build weekly sales report like last time
short_prefs={}
long_prefs={'report_format': 'short-bullets', 'currency': 'EUR'}
resolved_prefs={'report_format': 'short-bullets', 'currency': 'EUR'}
Що видно на практиці
| Лише short memory | Short + long memory | |
|---|---|---|
| Зберігає prefs між задачами | ❌ | ✅ |
| Стійкий до втрати раннього контексту | ❌ | ✅ |
| Формат/валюта відтворюються "як минулого разу" | ❌ | ✅ |
Що змінити в цьому прикладі
- Зменш
max_itemsдо2і подивись, як швидко губляться prefs у short memory - Додай третю user preference (наприклад,
timezone) і протягни її у звіт - Збережи в long memory ще й
last_report_total, а в новій задачі порівняй динаміку - Додай правило: не дозволяти порожні prefs у
save_user_preferences
Повний код на GitHub
У репозиторії лежить повна версія цього демо: short-term/long-term memory, порівняння сценаріїв і trace кроків.
Переглянути повний код на GitHub ↗