Головна / Статті

Пояснення пам’яті агента: вікно контексту, RAG та стабільний стан

Пам’ять агента — це три різні компоненти. Дізнайтеся, чим відрізняються вікно контексту, механізм пошуку та стабільний стан, скільки кожен з них коштує та як спочатку вибрати правильний варіант.

Видавець: REACTAPP.TOP10 хв читання2195 слів

Джерела: Agent Memory Explained

Мало які слова спричиняють стільки плутанини у розробці агентів, як „пам’ять“. Коли хтось каже, що його агент її має, це може означати, що розмова довга, або що підключена векторна база даних, або що факти зберігаються у файлі, який залишається після перезапуску. Це три окремі системи з різною вартістю, різними способами збоїв та різними причинами існування.

Одне речення усуває більшу частину плутанини. У моделі немає пам’яті — лише вхідні дані, а те, що люди називають пам’яттю, — це просто правило вибору того, що потрапить у ці вхідні дані.

Модель є безстановою. Кожен виклик API починається з нуля, без жодних спогадів про ваше попереднє повідомлення, попередню сесію чи ім’я, яке ви їй дали. Те, що вона знає під час виклику, — це лише те, що міститься у запиті, і нічого більше.

Отже, важливе питання полягає не у тому, чи має агент пам’ять. Йдеться про те, що потрапляє до цього запиту та звідки воно походить. Існує три варіанти відповідей, і завдання полягає у тому, щоб розрізнити їх.

Модель без стану, додаток із станом

Початківці вважають, що модель веде розмову. Це не так. Розмову веде додаток, який її повторює. Коли ви надсилаєте четверте повідомлення, ваш код щоразу передає всі повідомлення 1, 2, 3 та 4. Відчуття безперервності виникає через повторну передачу історії, а модель просто отримує довший вхідний даний, ніж раніше.

З огляду на це три шари вже не є абстрактними:

  • Вікно контексту — це те, що зараз знаходиться у запиті.
  • Отримання інформації (RAG) — це спосіб знаходження матеріалу для додавання до запиту.
  • Стійкий стан — це спосіб, яким факти зберігаються після завершення запиту, щоб їх можна було додати знову пізніше.
  • Контейнер, механізм пошуку та сховище — це три різні речі. Якщо їх змішувати, ви починаєте використовувати базу даних векторів для розв’язання проблеми, яку міг би вирішити файл preferences.json.

    Рівень 1: вікно, або робочий стіл моделі

    Уявіть вікно як усе, що модель може бачити наразі. Системний запит, історія, визначення інструментів, результати їх використання, отримані документи та текст відповіді — усе це використовує один спільний бюджет токенів.

    Уявіть робочий стіл. Усе, про що модель мусить розмірковувати, повинно фізично знаходитися на ньому; шухляди в іншій кімнаті не рахуються.

    Що в ньому чудово: миттєвість. Усе, що знаходиться у вікні, повністю видиме без необхідності додаткових пошуків, і модель може аналізувати все це одночасно. Для завдання з обмеженим, відомим набором матеріалів заповнення вікна — це найпростіший та часто правильний підхід.

    Витрати:

    1. Гроші та затримка залежать від того, що ви надсилаєте знову. Пересилання 51 повідомлення з довгого чату означає пересилання 50 повідомлень з історії. Довгий контекст не є безкоштовним лише через те, що він поміщається у межі.
    2. Увага розподіляється нерівномірно. Моделі зазвичай найнадійніше використовують початок та кінець довгого вхідного тексту, а середину — найменше, що є поширеним ефектом „втрати в середині“. Інформація, розташована на 60% від початку дуже довгого запиту, є присутньою, але може ніколи не бути ефективно використана. Те, що поміщається у межі, не означає, що його буде прочитано.
  • Все закінчується. Як тільки сесія завершується, вікно зникає. Контекстне вікно ніколи не зберігає нічого. Це найпоширеніша помилка початківців: довга розмова здається частиною пам’яті, але насправді це лише буфер, а не сховище.
  • Уся ця механіка може бути описана кількома рядками:

    messages = []
    
    def turn(user_text):
        messages.append(
            {"role": "user",
             "content": user_text}
        )
        # every call resends
        # the ENTIRE history
        reply = client.messages.create(
            model="claude-opus-5",
            max_tokens=1024,
            messages=messages,
        )
        messages.append(
            {"role": "assistant",
             "content": reply.content[0].text}
        )
        return reply
    
    # messages grows forever.
    # so does your bill.
    

    Цей список є тут „пам’яттю“. Він існує лише всередині вашого процесу, тож як тільки процес закінчується, список також зникає.

    Саме вікно достатнье, коли завдання є коротким, необхідний матеріал невеликий та відомий заздалегідь, і ніщо не має тривати довше сеансу. Для підсумовувача, ревізора одного файлу чи одноразового вилучення інформації більше нічого не потрібно. Не додавайте туди зайвих механізмів.

    Рівень 2: пошук, бібліотека посилань

    Рано чи пізно матеріал стає занадто об’ємним або більше не вартий оплати. Це можуть бути 10 000 заявок на підтримку, внутрішня Вікі чи кодова база. Ви не можете включати все це до кожного запиту, і не повинні цього робити.

    Генерація з підсиленням пошуком ділить роботу: заздалегідь створюється індекс, а потім шукається необхідна інформація при поступленні запиту.

    Спочатку ви розрізаєте документи на фрагменти, перетворюєте кожен фрагмент на ембеддинг (числовий вектор, який передає сенс) та зберігаєте його. Коли надходить запит, ви також перетворюєте його на ембеддинг, знаходите фрагменти, чиї вектори знаходяться найближче, та вставляєте ці кілька фрагментів у вікно контексту поруч із запитом.

    # 1. AHEAD OF TIME (once, offline)
    for chunk in split(documents):
        store.add(
            text=chunk,
            vector=embed(chunk),
        )
    
    # 2. AT QUESTION TIME (every request)
    def answer(question):
        hits = store.search(
            embed(question), top_k=5
        )
        context = "\n\n".join(h.text for h in hits)
        return client.messages.create(
            model="claude-opus-5",
            max_tokens=1024,
            # retrieved chunks land in the same request
            messages=[{
                "role": "user",
                "content": f"{context}\n\nQ: {question}",
            }],
        )
    

    Зверніть увагу на те, що насправді сталося. RAG не надав моделі пам’яті — він просто надав стратегію вибору даних для роботи з окнем інформації. Отримані фрагменти потрапляють у той самий запит, що й усе інше, тож RAG є своєрідним каналом для передачі даних до першого рівня обробки, і альтернативи йому немає.

    Переваги: масштабованість та свіжість даних. Мільйони документів можуть змінюватися незалежно від вашого коду, тоді як модель бачить лише ті кілька документів, які є важливими для вирішення конкретного запиту.

    Витрати:

    1. Результат визначається заздалегідь. Розмір фрагментів, модель ембеддингу та структура індексу фіксуються під час створення індексу. Факт, який розташований у двох фрагментах чи сформульований не так, як це запитують користувачі, просто не знаходиться, і тоді модель відповідає на основі того, що отримала. Для спостерігача погане пошукове функціонування та галюцинації виглядають однаково.
  • Це переважно тільки для читання. Класичний RAG — це карта бібліотеки, а не зошит. Агент шукає інформацію, але нічого не записує, і те, що він дізнається під час сеансу, не потрапляє до індексу, якщо ви самі не створите відповідний шлях.
  • Є додатковий крок. Вбудовування запиту та пошук у індекс збільшують затримку для кожного запиту.
  • Метод отримання інформації підходить тоді, коли база знань велика, спільна для всіх користувачів та рідко змінюється, наприклад документація, правила, каталоги продуктів та код. Ознакою є те, що один корпус слугує всім користувачам. Якщо те, що ви хочете запам’ятати, належить конкретному користувачеві та змінюється під час розмови, цей метод є неефективним, і тоді потрібен рівень 3.

    Рівень 3: постійний стан, зошит

    Стійкий стан — це те, що залишається після завершення процесу. Це не те, що агент знає про світ, а те, що він навчився та що має зберегтися після завершення сесії: уподобання користувача, контрольні точки завдань, вже прийняті рішення, спроби, які були зроблені та покинуті, стислі узагальнення попередніх виконань. Це невеликі, конкретні, цінні факти, які сам агент записав.

    Паттерн полягає у циклі читання, зміни та запису, який працює протягом усієї сесії:

    # session start: load
    with open("state.json") as f:
        state = json.load(f)
    
    system = (
        "Known about this user:\n"
        f"{json.dumps(state)}"
    )
    
    messages = []
    
    def turn(user_text):
        messages.append(
            {"role": "user",
             "content": user_text}
        )
        reply = client.messages.create(
            model="claude-opus-5",
            max_tokens=1024,
            system=system,   # state in
            messages=messages,
        )
        return reply
    
    # ... along the way ...
    state["prefers"] = "metric units"
    state["last_checkpoint"] = "step_4_done"
    
    # session end: save
    with open("state.json", "w") as f:
        json.dump(state, f)
    

    Концептуально більше нічого не потрібно. Файл JSON вважається справжньою реалізацією; так само як і рядок у базі даних, сховище типу key-value або папка з нотатками у форматі Markdown. Сам медіум є менш важливим — цікаво те, хто вирішує, що зберігати та що завантажувати знову.

    Правильний вибір у цьому питанні є складною частиною, і саме тут розходяться підходи фреймворків агентів. Найбільш прямий сучасний підхід передає цю роботу моделі: за допомогою інструменту пам’яті від Anthropic Claude отримує невеликий набір команд для файлів (view, create, str_replace, insert, delete та rename), які працюють у ізольованій директорії /memories; таким чином агент вибирає те, що варто зберегти, та читає ці нотатки на початку наступної сесії. Ваш власний додаток виконує операції на будь-якому сховищі, яке ви оберете. Інструмент працює на стороні клієнта, що дозволяє тримати файли під вашим контролем.

    Переваги: безперервність, індивідуалізація та тривала робота. Це необхідно агенту, який продовжує виконання багатоденного завдання з того місця, де воно зупинилося, а також тому, хто не повинен двічі запитувати про ваш часовий пояс.

    Витрати:

    1. Хтось мусить керувати процесом. Якщо записати все, вийде непошуковий текст; якщо написати замало, порушується безперервність. Створити якісний стан даних дуже складно.
    2. Старі дані гірші, ніж їх відсутність. Перевага, збережена вісім місяців тому та ніколи не переглянута, застосовується з повною впевненістю. Невдачі під час отримання даних призводять до нечіткості, тоді як старий стан спричиняє помилкову поведінку.
    3. Завантаження — це не безкоштовно. Що б ви не завантажували, це все одно займає місце у вікні, тож шар 3 також впливає на шар 1.

    Використовуйте стійкий стан, якщо той самий користувач або завдання неодноразово ініціюють агента, і те, що дізнається виконання n, має вплинути на виконання n+1.

    Три підходи поруч

    Вікно контексту Отримання даних Стійкий стан
    Роль Робочий стіл Бібліотека Зошит
    Містить Усе з цього запиту Великий спільний корпус даних Короткі факти, записані агентом
    Створено Вашим додатком під час кожного виклику Завданням індексації Агентом або вашим кодом
    Зберігається після перезапуску Ні Так Так
    Основний ризик Витрати, втрата даних по дорозі Пропущені фрагменти Застарілі факти

    Найважливіший рядок — це той, що стосується перезапусків. Він є межею між довгим чатом та агентом із пам’яттю.

    Вони складаються

    Справжній агент використовує всі три елементи, і корисно спостерігати, як вони працюють у одному запиті. Від співробітника підтримки запитують, чи підходить замовлення до подовженого терміну повернення.

    1. Спочатку завантажується стійкий стан: у цього клієнта є бізнес-план та він віддає перевагу коротким відповідям. Два факти, приблизно сорок токенів.
    2. Отримання інформації виконується на основі запитання та завантажує розділ політики повернень для бізнес-планів разом із двома останніми винятками. Три частини, приблизно дванадцятьсот токенів.
    3. Вікно контексту об’єднує все це, а також живий чат та визначення інструментів, і модель аналізує все разом.
  • Після відповіді агент записує один рядок у персистентний стан: було поставлено запитання щодо політики повернення товарів у середині серпня, тому наступна сесія не розглядає користувача як нового.
  • Один запит, три рівні. Вікно — це місце, де все зберігається, тоді як отримання інформації та персистентний стан — це два окремі шляхи до неї.

    Саме тому агентам, які працюють довго, зазвичай радять поєднувати пам’ять із редагуванням контексту: старі результати з інструментів видаляються з вікна, щоб зберегти його легкість, а факти, які варто зберегти, вже були збережені, тож вікно залишається невеликим без втрати знань.

    Який із них вам насправді потрібен?

    Початківці майже завжди спочатку обирають векторну базу даних. Це найбільш помітна частина стеку, але найрідше є правильною відповіддю. Краще підніматися по сходах розвитку.

    Почніть з вікна. Чи поміщається все необхідне в один запит за бажаною ціною? Тоді зупиніться. Жодного індексу, жодного сховища.

    Введіть постійний стан після того, як агент запуститься вдруге. Коли друга сесія має знати те, що дізналася перша сесія, потрібен шар 3, і файл JSON є правильною відповіддю. Робіть це перед отриманням даних; це дешевше, простіше та саме те, чого більшість людей насправді хочуть, коли кажуть, що бажають, аби агент їх пам’ятав.

    Введіть механізм отримання даних, коли корпус більший за обсяг вікна. Це означає велику кількість документів, якими користуються багато користувачів та які редагуються окремо від вашого коду. Це найскладніший шар для створення та постійної роботи, тож використовуйте його лише тоді, коли це дійсно необхідно.

    Одна пастка, яку потрібно уникнути: створення конвеєра RAG для того, що насправді було файлом переваг, а потім здивування через те, чому агент все одно забуває ім’я людини. Механізм пошуку перебирає інформацію з корпусу, і він ніколи не буде містити інформації про те, що людина віддає перевагу метричним одиницям.

    Коротка версія

    • Вікно контексту: стіл. Миттєво видиме, дороге за кожен токен, і очищується одразу після закриття сеансу.
    • Пошук інформації: бібліотека. Величезна, з можливістю пошуку, оновлюється за власним графіком, і агент читає з неї лише інформацію.
    • Постійний стан: зошит. Невеликий, заповнюється агентом, і єдиний шар, який передає уроки сьогодні на завтра.

    Сама модель нічого не зберігає. Так звала пам’ять — це інженерний вибір, який приймається з кожним запитом: що потрапить у це вікно та звідки має походити ця інформація?

    Для подальшого читання інженерні публікації Anthropic про ефективне керування контекстом та створення ефективних агентів, а також її документація щодо інструменту для керування пам’яттю та редагування контексту, детальніше розглядають ці теми.