Каждая зависимость от платформы — это ставка. Ставка на то, что этот вендор окажется правильным выбором через два года — по цене, по возможностям, по надёжности. Большинство ставок стареют плохо.
Стандартный совет — «выбирайте мудро». Берите вендора с лучшей репутацией, крупнейшей экосистемой, самым стабильным API. Добротный совет. И — недостаточный.
Настоящая проблема — связность, не выбор
Vendor lock-in возникает не потому, что выбрали не того вендора. Он возникает потому, что бизнес-логика выучила форму его API. Промпты знают формат сообщений OpenAI. Запросы знают синтаксис фильтров Pinecone. Воркфлоу знают модель активностей Temporal.
Когда появляется лучшая альтернатива — а она появится — переход требует переписывания бизнес-логики, которая ссылается на эти формы. Эта стоимость почти всегда выше, чем стоимость остаться. Поэтому остаются. Не потому что вендор лучший — потому что уйти дорого.
Контракты как структурное решение
Бизнес-логика никогда не должна ссылаться на конкретные типы вендора. Она должна ссылаться на контракт — интерфейс, описывающий что нужно, а не как это реализовано.
// С привязкой — бизнес-логика знает об OpenAI
import OpenAI from 'openai';
const res = await openai.chat.completions.create({
model: 'gpt-4o', messages, tools
});
// Без привязки — бизнес-логика знает о контракте
import type { ILLM } from '@kb-labs/contracts';
const res = await llm.chat({ messages, model: 'default' });Слой адаптеров переводит контракт в то, что требует вендор. Меняешь адаптер, а не логику.
Не 2 контракта, а 12
Мы применяем этот паттерн не только к LLM. KB Labs определяет 12 ключевых контрактных интерфейсов по всей поверхности платформы:
- ILLM — chat completions, стриминг, tool calling. Адаптеры для OpenAI, Anthropic, локальных моделей
- ICache — get/set/delete плюс сортированные множества для планирования. In-memory или Redis
- IStorage — чтение/запись/листинг с опциональным стримингом. Локальная ФС или S3-совместимые
- IVectorStore — поиск/upsert/удаление. Qdrant, Chroma или любой провайдер
- IEmbeddings — embed/embedBatch с трекингом размерности
- ISQLDatabase / IDocumentDatabase / IKeyValueDatabase — три контракта для трёх паттернов доступа
- IEventBus — publish/subscribe. In-memory, Redis Pub/Sub или своё
- ILogger — структурированное логирование с дочерними логгерами. Адаптер Pino — в комплекте
Каждый адаптер реализует ровно один контракт. Каждый плагин ссылается только на контракты, никогда на адаптеры. Граница принудительно соблюдается графом зависимостей: плагины зависят от @kb-labs/contracts, никогда от @kb-labs/adapter-*.
Агрегат IPlatformAdapters
Все 12 контрактов сходятся в единый интерфейс PlatformContainer — шлюз, через который каждый плагин получает доступ к инфраструктуре. Вы один раз настраиваете, какой адаптер стоит за каким контрактом — при старте. После этого platform.llm, platform.cache, platform.storage просто работают — независимо от того, что под ними.
# Один конфиг, одна замена
adapters:
llm: '@kb-labs/adapter-openai' # → меняем на anthropic, local и т.д.
cache: '@kb-labs/adapter-redis' # → меняем на in-memory для dev
storage: '@kb-labs/adapter-fs' # → меняем на S3 для прода
vectorStore: '@kb-labs/adapter-qdrant'Тест, который мы применяем
Каждая новая абстракция проходит один вопрос: может ли разработчик заменить эту зависимость, не трогая бизнес-логику? Если нет — абстракция течёт.
Реальная цена есть. Больше интерфейсов для поддержки. Больше кода адаптеров. Иногда — абстракция, которая не идеально ложится на причудливую API-поверхность вендора.
Мы считаем, что эта цена работает в вашу пользу со временем. Первая замена — это работа. Вторая — рутина. К третьей команда воспринимает инфраструктуру как действительно взаимозаменяемую — потому что она такова.
К этой свободе мы и движемся. Не свобода от вендоров — свобода выбирать их по достоинствам, в любое время.