Как обеспечивать backward compatibility при изменении протокола?

Краткий тезис

Backward compatibility (обратная совместимость) позволяет старым агентам корректно обрабатывать сообщения от новых версий протокола и наоборот. Достигается через эволюцию схемы сообщений (message schema evolution) с использованием форматов вроде Protobuf или Avro, где новые поля добавляются как optional, удаляемые помечаются deprecated, а на уровне взаимодействия вводится version negotiation (согласование версий]]) и adapter layer (слой адаптации) для преобразования между версиями.

1. Термин: Протокол взаимодействия агентов

Протокол — это набор правил и форматов сообщений, по которому обмениваются AI-агенты. В Agentic RAG агенты могут общаться для передачи запросов, документов, метаданных и управляющих команд. Изменение протокола (добавление/удаление полей, изменение типов) ломает совместимость, если не предусмотрена backward compatibility. Без неё при обновлении части агентов система перестаёт работать — старые агенты не поймут новые сообщения или упадут с ошибкой десериализации.

Ключевые принципы:

  • Forward compatibility: старые потребители могут читать сообщения, сгенерированные новыми отправителями (часть backward compatibility).
  • Backward compatibility: новые потребители могут читать сообщения старых отправителей.

2. Message schema evolution (эволюция схемы сообщения)

Чтобы протокол можно было менять без поломок, используется система сериализации с поддержкой эволюции. Самые популярные — Apache Avro и Protocol Buffers (Protobuf). Они предоставляют явное описание схемы (schema) и правила обратной совместимости. JSON Schema тоже подходит, но менее эффективен по размеру и скорости.

Сравнение форматов:

ФорматЭволюцияПоддержка optionalТипизацияСкоростьРазмер
ProtobufЧерез optional, deprecated, reservedЯвно optionalСтрогаяВысокаяМалый
AvroЧерез default values, aliases, unionunion {null, type}СтрогаяВысокаяМалый
JSON SchemaЧерез required / additionalPropertiesnullable (не всегда)СлабаяНизкаяБольшой
MessagePackНет встроенной эволюцииНетСлабаяСредняяСредний

Для агентных систем чаще выбирают Protobuf или Avro из-за компактности и явного управления версиями.

3. Добавление полей: optional и default values

Ключевое правило: при добавлении нового поля оно должно быть optional (необязательным) — ни в одной старой версии не должно требоваться его присутствие. В Protobuf это поле помечается модификатором optional или используется proto3 (все поля по умолчанию optional). Старый агент при десериализации просто проигнорирует новое поле (оно будет отсутствовать). Новый агент, читая старую версию, получит default value (нулевое для чисел, пустая строка и т.д.).

Пример в Protobuf:

syntax = "proto3";

message AgentMessage {
  int32 id = 1;
  string payload = 2;
  // old version without this field
  optional string trace_id = 3;  // added later, optional
}

Правило: не менять tag number поля (= 3) и не переиспользовать удалённые номера (использовать reserved).

4. Удаление полей: пометка deprecated

Нельзя физически удалять поле из схемы — старые сообщения могут его содержать. Вместо этого:

  • Пометить поле как deprecated в комментарии или через атрибут deprecatedProtobuf нет стандартного ключевого слова, используют комментарии; в Avro есть logicalType).
  • Зарезервировать номер поля через reserved в Protobuf, чтобы случайно не использовать его снова.
  • В коде нового агента при чтении можно просто игнорировать deprecated-поля.

Пример с reserved:

message AgentMessage {
  reserved 3;  // was trace_id, now deprecated
  int32 id = 1;
  string payload = 2;
}

5. Version negotiation (согласование версий)

При установлении соединения между агентами (handshake) они обмениваются поддерживаемыми версиями протокола. Каждый агент посылает список версий, которые он может принимать. Выбирается максимальная общая версия.

Протокол handshake (пример на Python):

class Handshake:
    SUPPORTED_VERSIONS = [1, 2, 3]
    def __init__(self):
        self.version = None

    def negotiate(self, peer_versions: List[int]) -> int:
        common = set(self.SUPPORTED_VERSIONS) & set(peer_versions)
        if not common:
            raise IncompatibleProtocol("No common version")
        self.version = max(common)
        return self.version

Важно: версия протокола должна быть указана в заголовке каждого сообщения, а не только в handshake (для long-lived соединений). Это позволяет агенту динамически выбирать обработчик.

6. Adapter layer (слой адаптации)

Когда handshake не дал общей версии, или нужно поддерживать legacy-агентов, используется adapter layer — промежуточный модуль, который преобразует сообщения одной версии в другую.

Реализация:

  • Хранятся правила трансляции (например, v1 -> v3: добавить missing fields со значениями по умолчанию, удалить deprecated поля).
  • Включается в API-шлюзе или как middleware.

Пример простого адаптера:

class ProtocolAdapter:
    def convert(self, msg: bytes, from_ver: int, to_ver: int) -> bytes:
        if from_ver == 1 and to_ver == 2:
            # добавить поле trace_id = ""
            parsed = parse_v1(msg)
            v2_msg = V2Message(id=parsed.id, payload=parsed.payload, trace_id="")
            return serialize_v2(v2_msg)
        raise UnsupportedConversion()

7. Практический пример эволюции протокола на Protobuf

Предположим, изначально протокол состоял из полей id и payload. Затем понадобилось добавить priority (int, optional) и убрать payload (заменив на data).

Шаг 1 (v1):

message AgentMessage {
  uint32 id = 1;
  string payload = 2;
}

Шаг 2 (v2):

message AgentMessage {
  uint32 id = 1;
  reserved 2;                // payload удалено
  string data = 3;           // новое поле
  optional uint32 priority = 4; // добавили
}

Но такое изменение нарушит совместимость: старый клиент ожидает payload на tag 2, а новый использует tag 3. Лучше не удалять сразу, а ввести новое поле как optional, а старое пометить deprecated. Схема v2 должна быть:

message AgentMessage {
  uint32 id = 1;
  string payload = 2 [deprecated = true]; // ещё присутствует
  string data = 3 [deprecated = false];
  optional uint32 priority = 4;
}

Затем через несколько версий можно зарезервировать tag 2.

8. Продвинутые техники

  • Semantic versioning протокола: версия major.minor.patch. Major — ломающие изменения (требуют адаптера), minor — обратно совместимые добавления, patch — исправления.
  • Test compatibility матрица: CI-тесты для всех комбинаций старых и новых версий.
  • Feature detection: вместо версий агент может явно объявлять поддерживаемые возможности (capabilities), что гибче.
  • Graceful degradation: если новый агент не может передать отсутствующее в старом поле, он генерирует fallback-значение.

9. Пет-проект для закрепления

Задача: Реализовать мини-протокол обмена сообщениями между двумя агентами с поддержкой backward compatibility.

Инструменты: Python, protobuf (или flatbuffers), asyncio для асинхронного взаимодействия.

Шаги:

  1. Определить начальную схему Protobuf с полями msg_id, text.
  2. Создать v1 агентов (клиент и сервер) на отдельных процессах/потоках, обменивающихся сообщениями через TCP.
  3. Выпустить v2 протокола: добавить optional поле source_agent и изменить обязательность text (сделать его optional с default).
  4. Реализовать handshake: при соединении агенты посылают поддерживаемые версии, выбирают максимальную общую.
  5. Для неподдерживаемой версии (тест) — adapter: v1 → v3 (например, заполнить source_agent строкой "unknown").
  6. Написать тесты: старый клиент против нового сервера, новый клиент против старого сервера.
  7. Промоделировать ситуацию с удалённым полем: задепрекейтить text, ввести payload. Проверить, что старый клиент может отправить сообщение, а новый сервер его корректно прочитает.

Ожидаемый результат:

  • Работающий чат между агентами разных версий.
  • Код, использующий reserved, optional, handshake, adapter.
  • Тесты демонстрируют 100% backward и forward compatibility.

10. Связь с другими вопросами

ВопросТема
812Версионирование схем данных в RAG-системах
813Выбор формата сериализации для агентных сообщений
815Handshake и установка соединения между агентами
817Rolling update протокола без downtime
818Мониторинг и логирование версий протокола
810Проектирование протокола взаимодействия агентов

11. Навигация


Навигация