← НАЗАД К КУРСУ Этап 3 · Урок 3 из 5

Урок 3.3 — Память агента

AI-модель по умолчанию ничего не помнит. Каждый запрос — как разговор с незнакомцем. Чтобы агент запоминал предыдущие разговоры, предпочтения и факты — ему нужно дать память. Без памяти агент полезен на один сеанс. С памятью — он становится постоянным ассистентом.


Почему модель ничего не помнит

В уроке 2.3 мы делали чат с историей сообщений. Каждый раз, когда отправляли запрос, прикладывали messages=history — всю переписку. Это работает внутри одного разговора. Но стоит закрыть программу — и всё пропало.

Почему? Потому что Claude API — это stateless (без состояния). API не хранит историю. Каждый запрос — отдельный, изолированный. Claude не знает, что вчера с ним говорили.

Сессия 1 (утро):
  Пользователь: Меня зовут Алексей, я работаю в маркетинге
  Claude: Приятно познакомиться, Алексей!

Сессия 2 (вечер):
  Пользователь: Как меня зовут?
  Claude: Я не знаю, как вас зовут. Вы не сообщали мне эту информацию.

Claude не забыл — он никогда не знал. Второй запрос не содержал первый разговор.

Аналогия: API — как ресторан, где официант меняется каждый раз. Новый официант не знает, что ты заказывал вчера. Чтобы он знал — нужна записная книжка (память).


Три вида памяти

Память агента можно разделить на три вида — по тому, как долго и что хранится:

1. Краткосрочная память (история чата)

Это messages — список сообщений в текущем разговоре. Она уже знакома из урока 2.3.

messages = [
    {"role": "user", "content": "Привет, я Алексей"},
    {"role": "assistant", "content": "Привет, Алексей!"},
    {"role": "user", "content": "Как меня зовут?"},
]
# Claude увидит всю переписку и ответит: "Алексей"

Плюс: просто, работает из коробки. Минус: исчезает при закрытии программы. Растёт с каждым сообщением → дорожает (урок 2.5).

2. Долгосрочная память (файлы / база данных)

Факты, которые нужно помнить между сессиями: имя пользователя, предпочтения, история взаимодействий.

# Сохраняем в файл
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)

Плюс: сохраняется между сессиями. Минус: нужно самому решать, что сохранять и когда.

3. Контекстная память (system prompt)

Постоянная информация, которая загружается в каждый запрос: кто агент, как себя вести, какие правила соблюдать.

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: Markdown-файлы

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 Code (инструмент, в котором написан этот курс) тоже использует файловую память: - CLAUDE.md — инструкции и контекст проекта - Файлы в .claude/memory/ — запомненные факты и предпочтения

ChatGPT: Memory API

ChatGPT хранит память на серверах OpenAI. Когда пользователь говорит "запомни, что я вегетарианец" — факт сохраняется и автоматически добавляется в контекст будущих разговоров.

Принцип одинаковый: сохранить → загрузить в контекст → использовать.


Стратегии управления памятью

Проблема: контекстное окно конечно

Из урока 2.5: у Claude контекстное окно = 200,000 токенов. Это много, но:

System prompt:     ~500 токенов
Память (факты):    ~200 токенов
История (50 сообщений): ~20,000 токенов
Вопрос:            ~100 токенов
──────────────────────────
Итого:             ~20,800 токенов

А при 500 сообщениях — уже 200,000+ → не влезает

Стратегия 1: Ограничение истории

Самый простой способ — отправлять только последние N сообщений:

# Отправляем только последние 20 сообщений
messages = all_messages[-20:]

Плюс: просто, предсказуемо. Минус: агент забывает, что было 21 сообщение назад.

Стратегия 2: Суммаризация

Попросить 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-вызов (стоит денег).

Стратегия 3: Автоматическое извлечение фактов

После каждого разговора (или каждых N сообщений) агент сам извлекает важные факты:

# Агент извлекает факты автоматически
# (мы уже это делали в extract_new_facts выше)

Из разговора:
  "Я переехал в Казань, теперь работаю удалённо"

Извлечённые факты:
  - Живёт в Казани
  - Работает удалённо

Плюс: агент запоминает важное автоматически. Минус: может извлечь лишнее или пропустить важное.

Какую стратегию выбрать?

Стратегия Сложность Для кого
Ограничение истории Легко Простые боты, FAQ
Суммаризация Средне Боты с длинными диалогами
Извлечение фактов Средне Персональные ассистенты
Всё вместе Сложно Продакшн-системы

Для начала достаточно ограничения истории (messages[-20:]). Остальное добавляется по мере необходимости.


Стоимость памяти

Память не бесплатна — она тратит токены:

Подход                    Дополнительные токены     Стоимость (Sonnet)
─────────────────────     ────────────────────      ──────────────────
Без памяти                0                         $0
Факты в system prompt     ~200 токенов / запрос     ~$0.0006 / запрос
Суммаризация              ~500 токенов / вызов      ~$0.009 / вызов
Извлечение фактов         ~800 токенов / вызов      ~$0.015 / вызов

Суммаризацию и извлечение фактов лучше делать на Haiku — в 4 раза дешевле, и для таких задач качества хватает.


Практика

Задание 1: Чат с памятью

Скопируй полный пример агента с памятью и запусти. Расскажи ему о себе, закрой программу, запусти снова — проверь, что он помнит.

Задание 2: Посмотри файл памяти

После первого разговора открой agent_memory.json и посмотри, что агент запомнил. Попробуй отредактировать файл вручную — добавь факт — и проверь, что агент его знает.

Задание 3: Ограничение истории

Модифицируй чат так, чтобы отправлялись только последние 10 сообщений. Поговори 15 сообщений и проверь — помнит ли агент, что было в начале разговора?

# Подсказка: замени
messages=messages
# на
messages=messages[-10:]

Задачки на закрепление

Задача 1: Почему Claude не помнит вчерашний разговор, если не реализовать память?

Ответ Потому что Claude API — stateless. Каждый запрос независим. API не хранит историю — нужно самому сохранять данные в файл или базу данных и загружать их при следующем запуске.

Задача 2: Чем отличается краткосрочная память от долгосрочной?

Ответ Краткосрочная — это `messages` (история текущего разговора). Она живёт пока работает программа. Долгосрочная — это данные, сохранённые в файл или базу данных. Они переживают перезапуск программы.

Задача 3: Почему для извлечения фактов лучше использовать Haiku, а не Sonnet?

Ответ Потому что извлечение фактов — простая задача (классификация и структурирование текста). Haiku справляется с ней не хуже Sonnet, но стоит в ~4 раза дешевле. Экономия из урока 2.5: начинай с дешёвой модели, переходи на дорогую только если качество не устраивает.

Задача 4: Агент с историей из 100 сообщений стал дорогим. Как снизить расходы, не теряя контекст?

Ответ Использовать суммаризацию: попросить Claude (Haiku) кратко пересказать старые сообщения, сохранить краткое содержание в memory, и отправлять только последние 10-20 сообщений + краткое содержание. Так контекст сохраняется, а токены экономятся.

Главное


Глоссарий

Термин Что значит
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 + агентный цикл — всё в одном. Это будет первый агент, готовый для реальных задач.

← ПРЕДЫДУЩИЙ СЛЕДУЮЩИЙ →