Акты Становления

800: Паккет/Schemistry

Библиотека, которая утверждает Схему как единственный источник истины для приложения. Она управляет проверкой на этапе выполнения, типами TypeScript, интеграцией с LLM и логикой приложения из единого определения, гарантируя, что данные времени выполнения и типы времени сборки всегда синхронизированы.

Schemistry — это фундаментальная библиотека, которая лежит в основе структур данных всей экосистемы. Её основная задача — сконцентрировать всю мощь в Схеме, сделав её авторитетным определением для всего: от проверки данных до когнитивных способностей агентов.

Философия и ключевые концепции

Единственный источник истины

Во многих системах существует разрыв между типами на этапе компиляции (интерфейсы TypeScript) и структурами данных на этапе выполнения (JSON-схемы или логика валидации). Это часто приводит к дублированию кода, расхождениям и ошибкам.

Schemistry решает эту проблему, устанавливая Схему в качестве единственного источника истины. Определяя структуры данных один раз в схеме, мы автоматически получаем:

  • Проверка на этапе выполнения: Обеспечение целостности данных на границах системы.
  • Типы TypeScript: Обеспечение статической безопасности при разработке.
  • Интеграция с LLM: Предоставление агентам точных, структурированных описаний данных, с которыми они работают.
  • Логика приложения: Управление динамическим поведением на основе структуры схемы.

Такой подход поддерживает разработку, управляемую схемой (schema-driven development), гарантируя, что реальность времени выполнения и теория времени сборки всегда синхронизированы без ручного вмешательства или сложных шагов по генерации кода.

Парадигма «From Schema»

Центральный механизм библиотеки — это инструмент «from schema» — макрос TypeScript, который вычисляет типы непосредственно из определений схемы.

  • Динамический вывод типов: Мы динамически вычисляем типы из определения константы JSON-схемы с помощью утилиты FromSchema.
  • Типобезопасность: Эти выведенные типы распространяются по всей кодовой базе. Если схема меняется, типы обновляются автоматически, и любой код, нарушающий новую структуру, не скомпилируется.

Реестр схем

Для управления сложностью растущей системы Schemistry включает встроенный механизм реестра.

  • Плоская структура: Схемы регистрируются на верхнем уровне и ссылаются друг на друга по идентификаторам ($ref). Это предотвращает «ад вложенности» из-за глубоких, встроенных определений.
  • Автоматическое разрешение: Внутренние функции автоматически обрабатывают эти ссылки, позволяя разработчикам создавать сложные структуры данных из простых, многократно используемых блоков.

Каноническая схема и семантическое расхождение

Мы используем концепцию Канонической Схемы. Внутри библиотеки схема может быть развёрнута в выражение, оптимизированное для её функций.

Мы допускаем, что тип TypeScript, описывающий схему (выведенный через FromSchema), и структура схемы на этапе выполнения могут структурно расходиться, при условии, что они сохраняют одинаковое семантическое значение. Например, схема может быть структурно определена как пересечение allOf для целей валидации, но её выведенный тип TypeScript будет объединённым объектом. Это расхождение является преднамеренным: оно упрощает использование для разработчика, при этом строго обеспечивая проверку формы данных во время выполнения.

Производительность и кэширование

Глубокий вывод типов — это вычислительно затратная операция. Для решения этой проблемы в Schemistry реализована надёжная стратегия кэширования:

  • Кэширование: Вспомогательные функции возвращают объекты с предварительно вычисленными и закэшированными типами.
  • Повторное использование: Типы кэшируются в самом объекте схемы. При последовательном вызове операций мы передаём закэшированный тип дальше, избегая необходимости заново выводить его с нуля на каждом шаге.

Эволюция: от составного к строгому

Изначально библиотека использовала составной, широкий тип для всех схем, предполагая, что любой объект схемы может иметь любое свойство. Однако этот подход оказался ловушкой.

  • Ловушка «составного типа»: Широкий тип мешал сужению типов. Взаимоисключающие свойства (например, в дискриминантных объединениях) не могли быть корректно выведены, потому что широкий тип «поглощал» конкретные ограничения.
  • Переход к строгости: Мы перешли к строгому подходу, где schema.any — это объединение различных типов (object, array, string и т. д.).
  • Решение: Мы полностью отказываемся от составного типа. Составные типы должны быть «развёрнуты» в свои конкретные составляющие типы.

План: Типобезопасная архитектура

Манипулирование схемами

Библиотека предоставляет набор функциональных утилит для манипулирования схемами, таких как Intersection, Pick, Omit и Union. Они позволяют разработчикам динамически составлять и изменять структуры данных, рассматривая схемы как объекты первого класса.

Мы активно работаем над тем, чтобы сделать эти функции манипулирования полностью типобезопасными. Цель состоит в том, чтобы любое преобразование, применённое к объекту схемы во время выполнения, мгновенно и точно отражалось в его статическом типе TypeScript.

Стратегия ToSchema

В дальнейшем Schemistry будет использовать функциональную стратегию для манипулирования схемами под названием ToSchema. Этот подход использует присущую коммутативность между Схемами и типами TypeScript.

Процесс преобразования следует чёткому пути:

  1. Извлечение (FromSchema): Мы переносим схему в область типов TypeScript.
  2. Операция: Мы применяем стандартные, композируемые операции TypeScript (например, Intersection, Union, Pick, Omit).
  3. Восстановление (ToSchema): Мы возвращаем полученный тип обратно в определение Схемы.
Schema.Intersection<A, B>(a: A, b: B): ToSchema<FromSchema<A> & FromSchema<B>>

Почему это работает: Схемы действуют как мост между значениями времени выполнения и статическими типами. Поскольку это отношение коммутативно, мы можем уверенно перемещаться между этими областями. Мы отдаём приоритет структурной корректности, а не сохранению метаданных, что позволяет нам рассматривать схемы как чистые функциональные блоки, которые можно составлять, преобразовывать и восстанавливать с математической точностью.

Цели: Новые утилиты и расширяемость

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

  • Intersection и Union: объединение схем с полной точностью типов.
  • Pick и Omit: манипулирование объектными схемами с сохранением строгой типизации.

Поддерживаемость: Этот подход значительно упрощает реализацию новых утилит. Сосредоточившись на преобразовании типов TypeScript, а не на поддержании идеального соответствия структуры схемы, мы можем создавать надёжные инструменты без огромной сложности обработки каждой возможной комбинации схем на уровне типов.

Поддержка частичных схем

В настоящее время наша строгая типизация требует, чтобы схемы были полностью определены (например, объект должен явно иметь type: 'object'). Однако JSON Schema допускает «частичные» определения (например, определение properties без type), эффективно выводя тип из контекста.

  • Текущее ограничение: Наша реализация FromSchema накладывает ограничения; она не выводит типы из частичных объектов. Схема, содержащая только properties, не рассматривается как настоящий объект, что нарушает вывод типов.
  • Временное решение: В настоящее время мы требуем полностью определённые объекты и типы (явное использование свойства type повсеместно).
  • Внутреннее состояние: Мы используем внутренний тип CouldBePartial, чтобы отметить места, где может быть добавлена поддержка частичных схем.
  • Препятствие: Нам нужно определить, не приведёт ли добавление поддержки частичного вывода в FromSchema к непрактичному увеличению сложности и глубины типов TypeScript.
  • Цель на будущее: Если это будет возможно без серьёзных потерь в производительности, мы планируем включить поддержку частичных схем во всей экосистеме.

Заключение

Устанавливая Схему как единый язык для людей (разработчиков) и машин (агентов), Schemistry создаёт надёжную основу, необходимую для ИИ-центричной системы.

Теперь, когда структуры данных определены, мы можем рассмотреть, как они используются для организации взаимодействия с агентами. 001: Агент/Запрос основывается на этих схемах для определения протокола взаимодействия с LLM.