AI-модель по умолчанию ничего не помнит. Каждый запрос — как разговор с незнакомцем. Чтобы агент запоминал предыдущие разговоры, предпочтения и факты — ему нужно дать память. Без памяти агент полезен на один сеанс. С памятью — он становится постоянным ассистентом.
В уроке 2.3 мы делали чат с историей сообщений. Каждый раз, когда отправляли запрос, прикладывали messages=history — всю переписку. Это работает внутри одного разговора. Но стоит закрыть программу — и всё пропало.
Почему? Потому что Claude API — это stateless (без состояния). API не хранит историю. Каждый запрос — отдельный, изолированный. Claude не знает, что вчера с ним говорили.
Сессия 1 (утро):
Пользователь: Меня зовут Алексей, я работаю в маркетинге
Claude: Приятно познакомиться, Алексей!
Сессия 2 (вечер):
Пользователь: Как меня зовут?
Claude: Я не знаю, как вас зовут. Вы не сообщали мне эту информацию.
Claude не забыл — он никогда не знал. Второй запрос не содержал первый разговор.
Аналогия: API — как ресторан, где официант меняется каждый раз. Новый официант не знает, что ты заказывал вчера. Чтобы он знал — нужна записная книжка (память).
Память агента можно разделить на три вида — по тому, как долго и что хранится:
Это messages — список сообщений в текущем разговоре. Она уже знакома из урока 2.3.
messages = [
{"role": "user", "content": "Привет, я Алексей"},
{"role": "assistant", "content": "Привет, Алексей!"},
{"role": "user", "content": "Как меня зовут?"},
]
# Claude увидит всю переписку и ответит: "Алексей"
Плюс: просто, работает из коробки. Минус: исчезает при закрытии программы. Растёт с каждым сообщением → дорожает (урок 2.5).
Факты, которые нужно помнить между сессиями: имя пользователя, предпочтения, история взаимодействий.
# Сохраняем в файл
memory = {
"user_name": "Алексей",
"role": "маркетолог",
"preferences": ["краткие ответы", "примеры из маркетинга"],
"last_topic": "анализ конкурентов"
}
import json
with open("memory.json", "w") as f:
json.dump(memory, f, ensure_ascii=False)
# При следующем запуске — загружаем
with open("memory.json", "r") as f:
memory = json.load(f)
Плюс: сохраняется между сессиями. Минус: нужно самому решать, что сохранять и когда.
Постоянная информация, которая загружается в каждый запрос: кто агент, как себя вести, какие правила соблюдать.
system_prompt = """
Ты — бизнес-ассистент.
Пользователь: Алексей, маркетолог, предпочитает краткие ответы.
Всегда приводи примеры из маркетинга.
Не используй технический жаргон без объяснения.
"""
Плюс: Claude "знает" это при каждом запросе. Минус: занимает токены, нужно обновлять вручную.
Вот полный пример агента с памятью, которая сохраняется между запусками:
from anthropic import Anthropic
import json
import os
client = Anthropic()
MEMORY_FILE = "agent_memory.json"
# --- Загрузка памяти ---
def load_memory():
if os.path.exists(MEMORY_FILE):
with open(MEMORY_FILE, "r") as f:
return json.load(f)
return {"facts": [], "preferences": [], "history_summary": ""}
# facts — запомненные факты о пользователе
# preferences — предпочтения
# history_summary — краткое содержание прошлых разговоров
def save_memory(memory):
with open(MEMORY_FILE, "w") as f:
json.dump(memory, f, ensure_ascii=False, indent=2)
# --- Формирование system prompt с памятью ---
def build_system_prompt(memory):
prompt = "Ты — персональный AI-ассистент с памятью.\n\n"
if memory["facts"]:
prompt += "Что ты знаешь о пользователе:\n"
for fact in memory["facts"]:
prompt += f"- {fact}\n"
prompt += "\n"
if memory["preferences"]:
prompt += "Предпочтения пользователя:\n"
for pref in memory["preferences"]:
prompt += f"- {pref}\n"
prompt += "\n"
if memory["history_summary"]:
prompt += f"Краткое содержание прошлых разговоров:\n{memory['history_summary']}\n\n"
prompt += """Правила:
- Используй знания о пользователе в ответах
- Если пользователь сообщает новую информацию о себе — запомни (скажи об этом)
- Если не знаешь чего-то о пользователе — спроси
"""
return prompt
# --- Извлечение новых фактов ---
def extract_new_facts(messages, memory):
"""Просим Claude извлечь новые факты из разговора"""
extraction_response = client.messages.create(
model="claude-haiku-4-5-20251001",
# Используем Haiku — дешевле, для простой задачи хватит
max_tokens=500,
system="Извлеки новые факты о пользователе из разговора. Верни JSON-список строк. Если новых фактов нет — верни пустой список [].",
messages=[{
"role": "user",
"content": f"Известные факты: {memory['facts']}\n\nРазговор: {json.dumps(messages[-6:], ensure_ascii=False)}"
# [-6:] — последние 6 сообщений, чтобы не тратить токены
}]
)
try:
new_facts = json.loads(extraction_response.content[0].text)
return new_facts
except:
return []
# --- Основной цикл ---
def chat():
memory = load_memory()
messages = []
print("Чат с памятью (напиши 'выход' чтобы закончить)")
print("-" * 50)
if memory["facts"]:
print(f"[Память загружена: {len(memory['facts'])} фактов]")
while True:
user_input = input("\nТы: ")
if user_input.lower() == "выход":
break
messages.append({"role": "user", "content": user_input})
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=build_system_prompt(memory),
messages=messages
)
assistant_message = response.content[0].text
messages.append({"role": "assistant", "content": assistant_message})
print(f"\nAssistant: {assistant_message}")
# --- После завершения: обновляем память ---
if messages:
new_facts = extract_new_facts(messages, memory)
if new_facts:
memory["facts"].extend(new_facts)
# Убираем дубликаты
memory["facts"] = list(set(memory["facts"]))
print(f"\n[Запомнил {len(new_facts)} новых фактов]")
save_memory(memory)
print("[Память сохранена]")
chat()
Сессия 1:
Ты: Привет, я Алексей, работаю маркетологом
Assistant: Привет, Алексей! Рад познакомиться. Чем могу помочь?
Ты: Мне нравятся краткие ответы с примерами
Assistant: Понял! Буду отвечать кратко и с примерами.
Ты: выход
[Запомнил 3 новых факта]
[Память сохранена]
Сессия 2 (через час / день / неделю):
[Память загружена: 3 факта]
Ты: Привет!
Assistant: Привет, Алексей! Как дела в маркетинге?
Ты: Как меня зовут и чем я занимаюсь?
Assistant: Тебя зовут Алексей, ты работаешь маркетологом.
Между сессиями Claude "помнит" — потому что факты сохранены в файле и загружены в system prompt.
OpenClaw (open-source AI-агент из урока 3.2) хранит всю память в обычных Markdown-файлах:
SOUL.md — личность, стиль поведения
user.md — предпочтения пользователя
memory.md — факты, запомненные между разговорами
HEARTBEAT.md — расписание автоматических задач
Эти файлы загружаются в system prompt при каждом запросе. Это тот же подход, что в нашем примере — но вместо JSON используется Markdown, который удобнее читать и редактировать вручную.
Почему Markdown, а не база данных? Потому что: - Можно открыть и прочитать глазами - Можно отредактировать в любом текстовом редакторе - LLM хорошо понимает Markdown (обучены на нём) - Не нужна настройка базы данных
Claude Code (инструмент, в котором написан этот курс) тоже использует файловую память:
- CLAUDE.md — инструкции и контекст проекта
- Файлы в .claude/memory/ — запомненные факты и предпочтения
ChatGPT хранит память на серверах OpenAI. Когда пользователь говорит "запомни, что я вегетарианец" — факт сохраняется и автоматически добавляется в контекст будущих разговоров.
Принцип одинаковый: сохранить → загрузить в контекст → использовать.
Из урока 2.5: у Claude контекстное окно = 200,000 токенов. Это много, но:
System prompt: ~500 токенов
Память (факты): ~200 токенов
История (50 сообщений): ~20,000 токенов
Вопрос: ~100 токенов
──────────────────────────
Итого: ~20,800 токенов
А при 500 сообщениях — уже 200,000+ → не влезает
Самый простой способ — отправлять только последние N сообщений:
# Отправляем только последние 20 сообщений
messages = all_messages[-20:]
Плюс: просто, предсказуемо. Минус: агент забывает, что было 21 сообщение назад.
Попросить Claude кратко пересказать старую часть разговора:
def summarize_old_messages(messages):
"""Превращаем длинную историю в краткое описание"""
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=300,
messages=[{
"role": "user",
"content": f"Кратко опиши основные темы и решения из этого разговора (3-5 предложений):\n\n{json.dumps(messages, ensure_ascii=False)}"
}]
)
return response.content[0].text
# Когда история становится длинной:
if len(messages) > 30:
summary = summarize_old_messages(messages[:20])
# Сохраняем краткое содержание старых сообщений
memory["history_summary"] = summary
# Оставляем только последние 10
messages = messages[-10:]
Плюс: сохраняется суть, экономия токенов. Минус: детали теряются, дополнительный API-вызов (стоит денег).
После каждого разговора (или каждых N сообщений) агент сам извлекает важные факты:
# Агент извлекает факты автоматически
# (мы уже это делали в extract_new_facts выше)
Из разговора:
"Я переехал в Казань, теперь работаю удалённо"
Извлечённые факты:
- Живёт в Казани
- Работает удалённо
Плюс: агент запоминает важное автоматически. Минус: может извлечь лишнее или пропустить важное.
| Стратегия | Сложность | Для кого |
|---|---|---|
| Ограничение истории | Легко | Простые боты, FAQ |
| Суммаризация | Средне | Боты с длинными диалогами |
| Извлечение фактов | Средне | Персональные ассистенты |
| Всё вместе | Сложно | Продакшн-системы |
Для начала достаточно ограничения истории (messages[-20:]). Остальное добавляется по мере необходимости.
Память не бесплатна — она тратит токены:
Подход Дополнительные токены Стоимость (Sonnet)
───────────────────── ──────────────────── ──────────────────
Без памяти 0 $0
Факты в system prompt ~200 токенов / запрос ~$0.0006 / запрос
Суммаризация ~500 токенов / вызов ~$0.009 / вызов
Извлечение фактов ~800 токенов / вызов ~$0.015 / вызов
Суммаризацию и извлечение фактов лучше делать на Haiku — в 4 раза дешевле, и для таких задач качества хватает.
Скопируй полный пример агента с памятью и запусти. Расскажи ему о себе, закрой программу, запусти снова — проверь, что он помнит.
После первого разговора открой agent_memory.json и посмотри, что агент запомнил. Попробуй отредактировать файл вручную — добавь факт — и проверь, что агент его знает.
Модифицируй чат так, чтобы отправлялись только последние 10 сообщений. Поговори 15 сообщений и проверь — помнит ли агент, что было в начале разговора?
# Подсказка: замени
messages=messages
# на
messages=messages[-10:]
Задача 1: Почему Claude не помнит вчерашний разговор, если не реализовать память?
Задача 2: Чем отличается краткосрочная память от долгосрочной?
Задача 3: Почему для извлечения фактов лучше использовать Haiku, а не Sonnet?
Задача 4: Агент с историей из 100 сообщений стал дорогим. Как снизить расходы, не теряя контекст?
| Термин | Что значит |
|---|---|
| Stateless API | API без состояния — Claude не помнит ничего между запросами, каждый вызов начинается с чистого листа. |
| Краткосрочная память | История сообщений текущего разговора, которая передаётся в каждом запросе к API. |
| Долгосрочная память | Информация, сохранённая между разговорами в файлах или базе данных и загружаемая при необходимости. |
| Контекстная память | Инструкции и факты, помещённые в system prompt, которые Claude учитывает в каждом ответе. |
| Суммаризация | Приём экономии токенов: старые сообщения сжимаются в краткое содержание с помощью быстрой модели вроде Haiku. |
| Sliding window | Метод управления историей, при котором хранятся только последние N сообщений, а более старые удаляются или суммаризируются. |
| Извлечение фактов | Процесс, при котором модель выделяет ключевые факты из разговора и сохраняет их отдельно для долгосрочной памяти. |
| Conversation history | Полный список сообщений разговора (user и assistant), который передаётся в массиве messages при вызове API. |
| System prompt как память | Паттерн, при котором сохранённые факты и предпочтения пользователя загружаются в system prompt перед каждым запросом. |
| База данных для памяти | Внешнее хранилище (файл, SQLite, PostgreSQL), где агент сохраняет информацию между сессиями. |
В следующем уроке — Первый агент на Claude Agent SDK. Мы соберём полноценного агента, используя официальный SDK от Anthropic: tools + memory + агентный цикл — всё в одном. Это будет первый агент, готовый для реальных задач.