Гайд · TNWS AI

Как хранить историю агента в MongoDBSession

6 мин

Подключаем MongoDBSession.from_uri, выполняем два хода, проверяем память и корректно закрываем принадлежащий сессии клиент.

Задача и когда применять

Этот гайд решает конкретную задачу: многопроцессная session memory в приложении, где уже используется MongoDB. Мы работаем с точным интерфейсом MongoDBSession.from_uri(...), а результат проверяем кодом. Материал полезен разработчику Python-сервиса, который уже выбрал OpenAI Agents SDK и хочет получить воспроизводимое поведение памяти. Если приложение не хранит многоходовый контекст, отдельная session memory ему не нужна.

Проверка документации выполнена 12 сентября 2026 года. Основной первоисточник — официальное руководство OpenAI Agents SDK по сессиям. В нём описаны контракт, импорты и особенности жизненного цикла. Цены, тарифы и модельные лимиты здесь не приводятся: они не нужны для этой операции и могут меняться.

Что именно должно получиться

from_uri создаёт AsyncMongoClient и передаёт владение сессии. Поэтому close завершает клиент и делает эту session terminal; подключение, жизненным циклом которого управляет приложение, передают через client=. Готовность означает не «код не упал», а наблюдаемый итог из поля ожидаемого результата. Сохраните идентификатор сессии как ключ предметной области: например, номер тикета или идентификатор диалога. Не используйте один и тот же session_id для разных пользователей.

До изменения сделайте три проверки: убедитесь, что тест работает с отдельной базой или namespace; не помещайте реальные персональные данные в демонстрационный ввод; включите журналирование только метаданных операции, без полного текста сообщений. Для сетевого backend отдельно проверьте доступность сервиса. Для локального backend сохраните резервную копию тестовой базы, если она уже содержит нужные данные.

Пошаговая настройка

  1. Создайте отдельное виртуальное окружение и установите пакет командой pip install openai-agents. Для Dapr, MongoDB и шифрования используйте extra, указанный в официальном разделе соответствующего backend.
  2. Скопируйте пример ниже в отдельный файл. Значения session_id, адреса и имена коллекций вынесите в конфигурацию приложения; секреты берите из менеджера секретов или переменных окружения.
  3. Запустите пример на тестовом хранилище. Сначала добейтесь прохождения встроенных assert. Они проверяют состояние, а не качество текста модели.
  4. Повторите запуск с тем же session_id. Заранее запишите ожидаемое поведение: история должна сохраниться для постоянного backend или начаться заново для памяти процесса.
  5. Выполните негативный сценарий из отдельного раздела. Ошибка соединения, пустая история и истёкший TTL — разные состояния; не объединяйте их в один catch с сообщением «памяти нет».
  6. Добавьте метрики уровня приложения: название операции, длительность, количество элементов до и после, тип исключения. Содержимое сообщений в метрики не отправляйте.
  7. Перед выпуском прогоните два параллельных обращения к разным session_id. Их результаты не должны смешиваться. Для общего backend дополнительно проверьте два процесса или два worker.

Готовый код

import asyncio
from agents import Agent, Runner
from agents.extensions.memory import MongoDBSession

async def main():
    session = MongoDBSession.from_uri(
        "customer-318", uri="mongodb://localhost:27017", database="agents"
    )
    try:
        await session.ping()
        agent = Agent(name="Assistant", instructions="Отвечай кратко")
        await Runner.run(agent, "Мой проект называется Север", session=session)
        result = await Runner.run(agent, "Как называется проект?", session=session)
        print(result.final_output)
        assert len(await session.get_items()) > 0
    finally:
        await session.close()

asyncio.run(main())

Код намеренно использует явные проверки. Заменять assert обычным print в CI не стоит: печать позволяет сборке пройти при неверном состоянии. В production обработайте ожидаемые исключения на границе сервиса, но не скрывайте их внутри адаптера хранения.

Реалистичный пример входа и результата

Вход: Первый запрос сообщает название проекта «Север», второй спрашивает название.

Ожидаемый результат: ping проходит, второй ответ содержит «Север», история читается до close().

Запишите результат в автотесте структурно. Для списка сравнивайте количество, порядок, роли и ключевое содержимое. Для сетевого хранилища выполняйте повторное чтение новым объектом сессии: так тест не пройдёт случайно из-за локального кэша. Если пример запускает модель, отдельно проверяйте состояние session через get_items — формулировка ответа модели может немного меняться.

Негативный тест

После close() вызовите get_items в отдельном контрактном тесте. Для owned-клиента ожидается RuntimeError, что выявляет повторное использование закрытого объекта.

Негативный тест запускайте в отдельном session_id и очищайте его после проверки. Не проверяйте отказ на рабочем диалоге. В отчёте CI сохраняйте тип ошибки и этап — connect, write, read, pop или clear. Это быстро отличает проблему инфраструктуры от нарушения контракта Session.

Как встроить в сервис

Создавайте session на границе запроса или диалога, но не смешивайте идентификатор пользователя с секретом. Передавайте объект в Runner там, где SDK должен автоматически читать прежние элементы и записывать новые. Прямые операции памяти оставьте административному endpoint или сервисному слою с авторизацией. Так пользовательский handler не сможет случайно очистить чужую историю.

Для идемпотентности назначайте команде операции request_id на уровне приложения. Сам интерфейс Session не обещает дедупликацию бизнес-команд. Повторный add_items после сетевого тайм-аута может оказаться второй записью, если сервер успел принять первую; проверяйте состояние перед повтором. Для destructive-операций сохраняйте журнал факта, но не копию чувствительного текста.

Разделяйте smoke-test и интеграционный тест. Smoke-test отвечает, доступен ли backend. Интеграционный тест доказывает порядок и изоляцию. Контрактный тест гоняет одинаковый набор операций для каждой реализации. Если позже SQLite заменяется на Dapr или MongoDB, те же проверки должны пройти без изменения бизнес-логики.

Шаблон для ревью реализации

Скопируйте этот шаблон в задачу или pull request:

Проверь реализацию session memory.
Операция: MongoDBSession.from_uri(...)
Session ID: <тестовый идентификатор>
Backend: <тип и версия из lock-файла>
Предусловие: <какие элементы существуют>
Действие: <точный вызов>
Ожидается: <число, порядок, роли, содержимое>
Негативный сценарий: <пустая сессия/недоступный backend/повтор>
Изоляция: соседний session_id не изменился
Секреты и тексты сообщений в логах: отсутствуют
Результат повторного чтения новым объектом: <значение>

Шаблон заставляет ревьюера увидеть контракт, а не только синтаксис. Поле версии заполняйте из lock-файла проекта, не из памяти и не из случайной статьи.

Чек-лист финальной проверки

  • Использован уникальный session_id тестового диалога.
  • Импорт и точное имя метода сверены с официальной документацией 12 сентября 2026 года.
  • Позитивный пример проходит и проверяет состояние assert-ами.
  • Негативный сценарий не меняет рабочие данные.
  • Соседний session_id остаётся неизменным.
  • Порядок элементов проверен, а не предполагается.
  • Для постоянного backend выполнено повторное чтение новым объектом.
  • Соединения и owned-клиенты закрываются в finally или async with.
  • В логах нет ключей, токенов и полного текста сообщений.
  • Ошибки чтения не маскируются под пустую историю.

FAQ

Можно ли использовать Session вместе с previous_response_id?

Нет в одном запуске. Официальное руководство прямо разделяет client-side session memory и run-level continuation через conversation_id, previous_response_id или auto_previous_response_id. Выберите один механизм продолжения, иначе источники истории конфликтуют.

Нужно ли вручную вызывать get_items перед каждым Runner.run?

Обычно нет. Когда session передана Runner, SDK сам получает историю перед запуском и сохраняет новые элементы после него. Прямой вызов нужен для аудита, миграции, коррекции, очистки и тестирования.

Почему пустой список нельзя считать доказательством отсутствия истории?

Пустой список может означать новый session_id, успешную очистку или истечение TTL. Ошибка доступа должна оставаться ошибкой. Поэтому проверяйте доступность backend отдельно и не превращайте все исключения в [].

Что считать достаточным тестом изоляции?

Создайте две сессии в одном backend, запишите разные маркеры и выполните целевую операцию только над первой. Затем прочитайте обе новым объектом. В первой ожидается изменение, во второй — исходный маркер.

Где следить за изменениями API?

В официальной документации Sessions и API reference проекта. Перед обновлением зависимости перечитайте раздел конкретного backend и повторно запустите контрактные тесты.

Читайте также

Комментарии

Пока тихо. Скажите первое слово