LLM генерируют код невероятно быстро, но чтобы гарантировать, что они создают именно то, что задумано, им нужны четкие границы. Абстракции и предметно-ориентированные языки (DSL) создают надежную оболочку, которая направляет LLM с самого начала. Пример Tickloom — предметной модели и DSL для иллюстрации поведения распределенных систем — показывает, как мы можем использовать LLM в качестве партнера для итеративного создания DSL и как естественно-языковой интерфейс для его использования. Такой DSL может выступать в качестве единственного источника истины для программных систем в мире LLM.
Современные LLM обладают невероятными возможностями. Они могут генерировать большие объемы кода, а иногда и целые системы, основываясь лишь на высокоуровневом описании на естественном языке. Важное предположение здесь заключается в том, что «намерение» относительно того, что необходимо построить, четко сформулировано с помощью точных слов, которые LLM могут сопоставить со строительными блоками кода. Однако здесь стоит отметить два важных момента: ограничения предварительной спецификации и то, как дизайн выявляется в процессе реализации.
Об авторе: Unmesh Joshi
Unmesh is a Distinguished Engineer at Thoughtworks, based in Pune, India.
He is the author of Patterns of Distributed Systems.
Ограничения предварительной спецификации
Создание больших систем включает огромное количество небольших проектных решений, и все их невозможно знать заранее или полностью определять с помощью высокоуровневой спецификации. Спецификация в лучшем случае является исходной гипотезой: реальные ограничения, компромиссы и пограничные случаи выявляются итеративно по мере продвижения реализации. Мы подробно обсуждали это в предыдущей статье, где назвали это невозможностью предварительной спецификации. Суть не в том, что спецификации бесполезны, а в том, что первая спецификация — это гипотеза, которую предстоит пересматривать, а не законченный чертеж.
Естественная реакция — итерировать: уточнять спецификацию, генерировать код, проверять результат и использовать полученные знания в следующем цикле. Этот цикл хорошо работает, когда каждый раунд дает небольшое изменение, которое можно проверить.
Дизайн выявляется в процессе реализации
Проверка кода, особенно пока мы еще выявляем дизайн, — это не то же самое, что его написание. Проверяя сгенерированный код, мы просматриваем его фрагменты, оценивая, соответствуют ли они нашему замыслу, и ищем возможные подводные камни. Но проверка редко заставляет нас по-настоящему разбираться с проектными решениями. Написание кода, напротив, заставляет нас обдумывать конкретные решения — например, где должна находиться определенная ответственность или какие границы следует открыть, чтобы дизайн можно было в дальнейшем расширять. Именно в процессе принятия этих решений дизайн раскрывается наиболее полно.
Что такое код?
Код имеет две различные, но тесно связанные цели. Это набор инструкций для машины, а также концептуальная модель предметной области. Хорошо спроектированная кодовая база представляет собой отражение словаря предметной области. Эти абстракции проявляются только по мере того, как разработчики создают программное обеспечение. Языки программирования выступают инструментами мышления, позволяя создавать концептуальную модель, которая поддерживает дальнейшее развитие. В случае с LLM код выступает в качестве важнейшего контекста: хорошие абстракции, исполняемое поведение, тесты, типы и инварианты помогают ограничить модель и сделать ее результат более полезным.
Язык программирования и парадигма, в рамках которых мы пишем код, формируют получаемое нами понимание дизайна. Функциональный подход к дизайну или объектно-ориентированный подход раскрывают разные аспекты дизайна, наряду с идиомами и паттернами, естественными для соответствующей парадигмы.
Так какое же место здесь занимают LLM? Я вижу для LLM две роли. Они отлично помогают нам формировать дизайн и его словарь, выступая партнерами для мозгового штурма, помогая исследовать пространство дизайна и находить подходящие абстракции. После того как словарь сформирован, LLM становятся отличным интерфейсом на естественном языке для работы с ним.
Предметные абстракции и DSL
Полезно рассмотреть это через призму предметно-ориентированного проектирования. Его ключевая идея — создание в коде общей концептуальной модели предметной области, а затем использование этой модели — которую DDD называет единым языком — как для развития кодовой базы, так и для формирования словаря, на котором команда может мыслить и общаться. Часто оказывается очень эффективным построить поверх этой модели предметно-ориентированный язык: ограниченный синтаксис для выражения понятий и операций предметной области. Если смотреть на это с такой точки зрения, большая часть разработки — это процесс построения модели предметной области и ее использование для развития системы. LLM играет две разные роли в зависимости от того, существует ли уже модель предметной области. В этой статье я сосредоточусь на том, как предметно-ориентированные языки (DSL) работают с LLM.
Почему DSL так хорошо работают с LLM
Многие на практике сталкиваются с тем, что DSL хорошо работают с LLM. PlantUML, Mermaid и Graphviz — это предметно-ориентированные языки для визуального моделирования; SQL — DSL для запросов к базам данных; YAML для Kubernetes — DSL для описания облачной инфраструктуры. Это не языки программирования общего назначения — они намеренно ограничены и предназначены для выражения узкого набора понятий в одной предметной области. Поэтому неудивительно, что LLM замечательно умеют генерировать диаграммы Mermaid, SQL-запросы или манифесты Kubernetes по обычному описанию на английском языке.
Мое наблюдение заключается в том, что DSL делают LLM более надежными, потому что те так хорошо реагируют на несколько примеров, представленных в контексте. Язык общего назначения вроде Java предоставляет множество корректных способов выразить одно и то же намерение. DSL убирает эту вариативность. Достаточно дать модели несколько примеров, чтобы она надежно генерировала правильный синтаксис. Стоит отметить, что передовые модели уже хорошо знакомы с PlantUML или fluent-интерфейсами Java благодаря обучению, поэтому они не начинают с нуля. Будет интересно посмотреть, как небольшие и более ограниченные модели справятся с действительно новым DSL.
Для агента — LLM, работающей в автономном цикле генерации и проверки, а не выполняющей однократную генерацию, — есть еще одно преимущество. DSL почти всегда поставляется с детерминированным валидатором: парсером, JSON Schema, средством проверки типов или компилятором. Агент может сгенерировать вариант, передать его валидатору и исправить на основе полученной ошибки — и все это без участия человека. Что особенно важно, ошибки формулируются на уровне предметной области — «нельзя выбрать действие, пока не выбран клиент», — а не в виде stack trace, затерянного глубоко в сгенерированном коде. Сам набор инструментов DSL выступает в качестве отличной оболочки. Мы увидим это на конкретных примерах Tickloom ниже, где грамматика DSL проверяется компилятором языка, на котором он реализован, а результаты выполнения автоматически проверяются.
Важно отметить, что это не универсальное решение. Преимущество сохраняется до тех пор, пока DSL остается достаточно небольшим и ограниченным, чтобы несколько примеров в контексте могли передать особенности его использования. Кроме того, разработка и поддержка самого языка и его семантической модели требуют реальных первоначальных затрат. Поэтому отдача сосредоточена в хорошо спроектированных, действительно ограниченных DSL, подкрепленных валидатором.
Пример: использование LLM для генерации презентаций PowerPoint с большим количеством диаграмм
LLM значительно упрощают создание специализированных инструментов. Преподавая распределенные системы, я часто сталкиваюсь с необходимостью создавать презентации, в которых в основном используются диаграммы, объясняющие распределенные операции в кластере. UML-диаграммы последовательностей отлично для этого подходят, но показывать целиком диаграмму последовательности при объяснении потока сообщений через кластер не очень полезно. Мне понадобился инструмент, который позволял бы показывать диаграмму последовательности пошагово в презентации PowerPoint. С помощью LLM мне удалось создать инструмент, который обрабатывает YAML с описанием структуры презентации и ссылками на диаграммы PlantUML и генерирует презентацию PowerPoint. Диаграммы PlantUML размечаются шагами, и инструмент создает отдельный слайд для каждого шага. Это значительно упростило создание презентаций с большим количеством диаграмм.
Generate a PlantUML sequence diagram showing a cluster of three nodes athens, byzantium and cyrene. Put a box to mark the cluster. Actor Alice sends a message “title”, “After Dawn” to athens, Athens sends message to itself. Put a note to show state 'title: After Dawn'. Athens sends message to byzantium which fails. Athens sends to cyrene. Put a note placed to the right of cyrene. athens then checks with isQuorumReached and returning a synchronous Success arrow back to Alice. Put a '[step] marker after each message.
Этот промпт генерирует следующий код PlantUML с маркерами шагов:
@startuml
actor Alice
box "Cluster" #lightblue
participant athens
participant byzantium
participant cyrene
end box
'[step]
Alice -> athens: "title", "After Dawn"
'[step]
athens -> athens: save()
note right of athens
state:
title: After Dawn
end note
'[step]
athens -[#red]x byzantium: "title", "After Dawn"
'[step]
athens -> cyrene: "title", "After Dawn"
note right of cyrene
state:
title: After Dawn
end note
'[step]
athens -> athens: isQuorumReached()
'[step]
athens --> Alice: Success
@enduml
Я использовал его для создания серии слайдов в презентации PowerPoint. Для этого я разработал небольшую спецификацию YAML, описывающую структуру презентации и диаграммы, которые должны использоваться на каждом слайде. Это позволило мне использовать LLM для создания презентаций, описывающих сложные концепции распределенных систем, без необходимости вручную создавать анимации на слайдах. Пример промпта для генерации YAML-спецификации слайда может быть таким простым:
Create a slide YAML referring to the diagram 'quorum-write' with a title 'Quorum Write Example'
Это генерирует YAML-спецификацию слайда следующего вида:
- slide:
title: "Quorum Write Example"
diagram: "quorum-write"
Важно отметить, что хотя в промпте сказано создать YAML слайда, речь идет не о произвольной YAML-спецификации. Поскольку инструмент для генерации презентации PowerPoint и понимаемая этим инструментом YAML-спецификация используются в качестве контекста промпта, LLM способна сгенерировать корректную YAML-спецификацию, которую можно непосредственно использовать инструменту для создания презентации PowerPoint.
Полную YAML-спецификацию можно посмотреть в этом репозитории Github.
Обратите внимание, что в этом единственном примере LLM выполняла две разные роли. Сначала она была соавтором дизайна — помогала сформировать расширение PlantUML с разметкой шагов и YAML для слайдов поверх существующих инструментов PlantUML. Затем, когда этот небольшой DSL уже существовал, она стала интерфейсом на естественном языке, превращающим запрос на английском языке в корректную спецификацию. К этому разделению труда мы вернемся в конце статьи.
Создание семантической модели
Пример, который мы рассмотрели в предыдущем разделе, был достаточно простым. YAML использовался в качестве синтаксиса-переносчика, а я напрямую обрабатывал разобранное синтаксическое дерево, фактически используя само синтаксическое дерево в качестве семантической модели (хотя это связывает синтаксис с семантикой выполнения). Но в более сложных предметных областях, таких как распределенные системы, нам нужны более сложные семантические модели, чтобы представлять понятия предметной области и проектные решения, которые мы приняли в кодовой базе. Давайте рассмотрим пример, основанный на небольшом фреймворке, который я создал для быстрой разработки и тестирования распределенных систем.
Пример: Tickloom — семантическая модель для распределенных систем
Реализация распределенных систем, таких как хранилища ключ-значение на основе кворума или протоколы консенсуса вроде Raft и Paxos, — непростая задача. Даже если реализация пошагово направляется с помощью промптов, спецификаций или тщательно подготовленных .md-файлов с инструкциями для скиллов, асинхронные среды выполнения по-прежнему оставляют огромное пространство возможных проектных решений. Модели многопоточности, сетевые паттерны, координация хранилища, поведение при повторных попытках и семантика времени — все это остается переплетенным в сгенерированном коде. Проблема заключается не только в сложности генерации кода, но и в сложности верификации. Пространство состояний, возникающее из всех возможных комбинаций планирования потоков, сетевых задержек, приостановок процессов и расхождений часов, становится настолько большим, что систематически проверять и валидировать корректность всех взаимодействующих вариантов поведения практически невозможно. Именно поэтому мы видим, что тесты Jepsen находят ошибки даже в наиболее тщательно протестированных распределенных системах.
Именно здесь семантическая модель оказывается полезной. Tickloom — небольшой фреймворк, который я создал для построения и тестирования распределенных алгоритмов. Его абстракции — не универсальная среда выполнения; это набор проектных решений о том, как ведет себя распределенный процесс. Каждый узел работает в однопоточном цикле тиков: каждый вызов tick() продвигает логические часы на один шаг и обрабатывает ожидающую работу в фиксированном детерминированном порядке (сеть, затем шина сообщений, затем процесс, затем хранилище). Время измеряется в тиках, а не в миллисекундах. Сообщения представляют собой обычные Java records. Координация между репликами выражается через базовый класс Replica, который уже знает об узлах-пирах, широковещательной рассылке и кворумах.
Многопоточность, временные характеристики и доставка сообщений по сети больше не являются открытыми вопросами, которые нужно заново решать в каждом промпте. Для автора алгоритма остается собственно логика протокола. Например, реплика, работающая с кворумом, представляет собой просто набор обработчиков сообщений, выраженных на языке фреймворка.
Поскольку фреймворк предоставляет словарь — Replica, quorumRequest, countResponseIf, MessageType, Handler — промпт может оставаться на уровне протокола, а не заниматься инфраструктурными деталями:
Using the Tickloom Replica abstraction, implement a quorum-based key-value store. A client GET should collect values from a majority and return the one with the highest timestamp (last-writer-wins). Apply a write locally only if its timestamp is newer than the stored one.
Это высокоуровневое описание порождает код следующего вида:
@Override
protected Map<MessageType, Handler> initialiseHandlers() {
return Map.of(
LWWMessageType.CLIENT_SET_REQUEST, this::handleClientSetRequest,
LWWMessageType.CLIENT_GET_REQUEST, this::handleClientGetRequest,
LWWMessageType.INTERNAL_SET_REQUEST, this::handleInternalSetRequest,
LWWMessageType.INTERNAL_GET_REQUEST, this::handleInternalGetRequest,
LWWMessageType.INTERNAL_SET_RESPONSE, this::handleInternalSetResponse,
LWWMessageType.INTERNAL_GET_RESPONSE, this::handleInternalGetResponse
);
}
private void handleClientGetRequest(Message message) {
var req = deserializePayload(message.payload(), ClientGetRequest.class);
var internalReq = new InternalGetRequest(req.key());
this.<InternalGetResponse>quorumRequest(LWWMessageType.INTERNAL_GET_REQUEST, internalReq)
.countResponseIf(r -> true) // any response is fine, just need a majority
.send()
.whenComplete((responses, error) -> {
if (error != null) {
send(createMessage(message.source(),
message.correlationId(),
new ClientGetResponse(req.key(), null, false),
LWWMessageType.CLIENT_GET_RESPONSE));
return;
}
byte[] highestValue = null;
long highestTimestamp = -1;
for (InternalGetResponse r : responses.values()) {
if (r.value() != null && r.timestamp() > highestTimestamp) {
highestTimestamp = r.timestamp();
highestValue = r.value();
}
}
boolean found = highestValue != null;
send(createMessage(message.source(),
message.correlationId(),
new ClientGetResponse(req.key(), highestValue, found),
LWWMessageType.CLIENT_GET_RESPONSE));
});
}
Сама семантическая модель выступает в качестве контекста. В промпте называются понятия, которые существуют в кодовой базе как конкретные типы, поэтому LLM не изобретает модель многопоточности или сетевой уровень — она заполняет логику протокола, опираясь на фиксированную, хорошо определенную основу.
Даже хорошие абстракции помогают — без DSL
DSL — это один из полюсов спектра, и его создание непросто. Прежде чем браться за разработку собственного языка, стоит заметить, что чистый набор абстракций уже является более легкой версией той же идеи — и, подобно тому как фреймворк предоставлял словарь для описанного выше хранилища на основе кворума, именованные типы и методы библиотеки сами по себе являются словарем, на который можно опереться при работе с моделью. Семантическая модель Tickloom фактически состоит всего из четырех таких границ — Process/Replica для вычислений и обработки сообщений, Network для коммуникации, Storage для хранения данных и логические Clock для времени — и этого разделения достаточно, чтобы выполнить большую часть работы, не вводя вообще никакого нового синтаксиса.
Именно поэтому абстракции, а не только DSL, хорошо сочетаются с LLM. У промпта «реализуй Raft как Tickloom Replica» пространство для исследования ограничено. Существующий QuorumReplica можно использовать в контексте как готовый пример.
Пример: создание DSL для тестирования сценариев распределенных систем
Реализовать алгоритм — это одно, а проверить его в работе — совсем другое. Тонкие ошибки в распределенных системах возникают при определенных порядках событий: запись реплицируется на один узел до того, как изменяется кворум чтения, разделение сети устраняется в самый неподходящий момент, часы двух координаторов расходятся. Написание такого сценария непосредственно с использованием тестового набора требует жонглирования futures и ручных циклов tick(). Вот сценарий с расхождением часов, написанный таким образом:
Cluster cluster = new Cluster()
.withProcessIds(Arrays.asList(ATHENS, BYZANTIUM, CYRENE))
.useSimulatedNetwork()
.build(QuorumReplica::new);
cluster.start();
try {
cluster.tickUntil(cluster::areAllNodesInitialized);
cluster.setTimeForProcess(ATHENS, 1000L);
cluster.setTimeForProcess(BYZANTIUM, 2000L);
QuorumReplicaClient alice = cluster.newClientConnectedTo(ALICE, ATHENS, QuorumReplicaClient::new);
QuorumReplicaClient bob = cluster.newClientConnectedTo(BOB, BYZANTIUM, QuorumReplicaClient::new);
QuorumReplicaClient reader = cluster.newClientConnectedTo(READER, ATHENS, QuorumReplicaClient::new);
TickCompletableFuture<SetResponse> bobWrite = bob.set(KEY.getBytes(StandardCharsets.UTF_8), "B".getBytes(StandardCharsets.UTF_8));
cluster.tickUntilComplete(bobWrite);
TickCompletableFuture<SetResponse> aliceWrite = alice.set(KEY.getBytes(StandardCharsets.UTF_8), "A".getBytes(StandardCharsets.UTF_8));
cluster.tickUntilComplete(aliceWrite);
TickCompletableFuture<GetResponse> read = reader.get(KEY.getBytes(StandardCharsets.UTF_8));
cluster.tickUntilComplete(read);
assertEquals("B", new String(read.getResult().value(), StandardCharsets.UTF_8));
} finally {
cluster.close();
}
Задумано — «Боб записывает через BYZANTIUM, Алиса записывает через ATHENS, читатель видит значение Боба, потому что часы Византии опережали» — теряется за механикой. Такой код также сложно проверять: в нем десятки второстепенных решений (когда вызвать tick(), как закодировать байты, какую перегрузку фабрики вызвать), в которых LLM может допустить трудноуловимую ошибку, а рецензенту приходится проверять каждое из них.
Поэтому поверх семантической модели я создал внутренний DSL, словарь которого соответствует самому языку сценария — серверы, клиенты, кто с кем соединен, что делает каждый клиент и какие сбои действуют в момент выполнения его действий. Тот же сценарий теперь выглядит так:
Scenario<QuorumReplicaClient> scenario =
QuorumStepBuilder.scenario("LWW lost update via server clock skew")
.servers(ATHENS, BYZANTIUM, CYRENE)
.clients(ALICE, BOB, READER)
.client(ALICE).connectedTo(ATHENS)
.client(BOB).connectedTo(BYZANTIUM)
.given(g -> g.serverTimeAt(ATHENS, 1_000L)
.serverTimeAt(BYZANTIUM, 2_000L))
.steps(s -> {
s.client(BOB).writes(KEY, "B").expectSuccess();
s.client(ALICE).writes(KEY, "A").expectSuccess();
s.client(READER).reads(KEY)
.expectResponse(v -> "B".equals(v));
});
DSL представляет собой тонкий декларативный слой, который компилируется в чистое промежуточное представление — Scenario, состоящее из Step, где каждый шаг содержит Action (чтение или запись) и необязательные ClusterEvent (сбои, такие как разделение сети и задержки сообщений). Сбои также читаются как английский текст: partition(BYZANTIUM).from(CYRENE), reconnect(BYZANTIUM), delay(INTERNAL_SET_REQUEST).from(ATHENS).to(BYZANTIUM, CYRENE).byTicks(100). Грамматика обеспечивается системой типов через последовательные интерфейсы — нельзя объявить шаг до описания топологии или действие до выбора клиента — поэтому целые классы некорректных сценариев просто не компилируются. Поскольку DSL является внутренним DSL, реализованным на Java, компилятор хост-языка бесплатно проверяет грамматику, и результат некорректной генерации возвращается как ошибка компиляции, точно указывающая на недопустимый шаг, а не как неожиданная ошибка во время выполнения.
После того как DSL создан, описание сценария сбоя на естественном языке почти напрямую преобразуется в него. Промпт вроде:
Using the Tickloom scenario DSL, write a scenario reproducing the DDIA §10.6 non-linearizable quorum read. A writer connected to Athens sets the key, then updates it while replication from Athens to the other replicas is delayed. Alice, reading through Byzantium, is forced onto a quorum that includes Athens and sees the new value; Bob, reading later through a quorum of Byzantium and Cyrene, still sees the old value.
дает сценарий, который полностью остается в рамках ограниченного словаря DSL:
Scenario<QuorumReplicaClient> scenario =
QuorumStepBuilder.scenario("Non-linearizable quorum read")
.servers(ATHENS, BYZANTIUM, CYRENE)
.clients(WRITER, ALICE, BOB)
.client(WRITER).connectedTo(ATHENS)
.client(ALICE).connectedTo(BYZANTIUM)
.client(BOB).connectedTo(BYZANTIUM)
.steps(s -> {
// Writer sets the key to VOLD initially and it replicates fully
s.client(WRITER).writes(KEY, VOLD).expectSuccess();
// Writer updates to VNEW, but replication from Athens is delayed
s.client(WRITER).writes(KEY, VNEW)
.whileClusterEvent(delay(QuorumMessageTypes.INTERNAL_SET_REQUEST)
.from(ATHENS).to(BYZANTIUM, CYRENE).byTicks(100))
.expectSuccess();
// Alice reads through Byzantium. Force quorum to include Athens by partitioning Cyrene.
// She will read VNEW from Athens.
s.client(ALICE).reads(KEY)
.whileClusterEvent(partition(BYZANTIUM).from(CYRENE))
.expectResponse(v -> VNEW.equals(v));
// Bob reads later through Byzantium. Force quorum to include Cyrene (and exclude Athens).
// The delayed VNEW replication hasn't arrived at Cyrene, so Bob reads VOLD.
s.client(BOB).reads(KEY)
.whileClusterEvent(reconnect(BYZANTIUM))
.whileClusterEvent(partition(BYZANTIUM).from(ATHENS))
.expectResponse(v -> VOLD.equals(v));
});
ScenarioResult result = scenario.run();
Поскольку этот слой настолько мал — а пространство допустимого кода, который он может генерировать, значительно меньше пространства допустимых Java-программ, — у LLM остается очень мало возможностей для галлюцинаций, а рецензент может воспринимать результат как описание эксперимента, а не как код, который нужно проверять построчно. Даже если LLM галлюцинирует, внутренний DSL не скомпилируется, что позволит LLM исправить допущенные ошибки.
Две фазы работы с LLM
Из рассмотренных выше примеров вырисовывается закономерность: в каждом из них LLM была полезна двумя совершенно разными способами.
Первая фаза — проектирование самой абстракции или DSL. Здесь к LLM лучше относиться как к партнеру для мозгового штурма, а не как к генератору кода. Как отмечалось в начале этой статьи, проектные решения, из которых складывается семантическая модель, невозможно полностью определить заранее — мы выявляем ограничения, компромиссы и пограничные случаи по мере реализации. Поэтому эта фаза по своей природе итеративна и основана на обратной связи: вы предлагаете структуру, проверяете ее на реальном примере, смотрите, где она оказывается неудобной, и используете полученные знания в следующем цикле. LLM ускоряет этот цикл — предлагает варианты, критикует дизайн, переносит идею с одного языка на другой, — но вы по-прежнему твердо остаетесь за рулем, потому что именно эти решения вам необходимо понимать и принимать на себя. Структуры, которые делают DSL удобным в использовании, например последовательные интерфейсы, заставляющие некорректный сценарий завершаться ошибкой компиляции, или семантическая модель, отделенная от строителя, который ее создает, — это то, к чему приходят итеративно, а не путем написания спецификации и генерации кода.
Вторая фаза начинается после того, как абстракция или DSL уже созданы. Теперь роль LLM меняется: она становится интерфейсом на естественном языке к тому, что вы построили. Промпты из этой статьи — примеры такого подхода: «реализуй хранилище на основе кворума как Tickloom Replica», «напиши сценарий, воспроизводящий чтение из DDIA §10.6», «создай YAML слайда для этой диаграммы». В каждом случае описание на английском языке почти напрямую отображается на определенный вами словарь, а LLM становится надежным генератором именно потому, что абстракция предоставляет и контекст, который задает смысл промпта, и оболочку, проверяющую результат.
DSL как источник истины
Растет тенденция рассматривать промпты как основной источник истины. Хорошо спроектированный DSL принципиально меняет эту динамику. Одно из ключевых преимуществ работы с DSL, которое я наблюдаю, заключается в том, что сама сгенерированная программа часто становится артефактом, который поддерживают люди. Поскольку DSL плотный, выразительный и в значительной степени свободен от случайного шаблонного кода, он фиксирует основной замысел решения в форме, которая остается читаемой еще долго после генерации. Если LLM генерирует сценарий сбоя Tickloom по запросу на естественном языке, результирующий сценарий уже выражен на языке предметной области. Если в следующем месяце сценарий потребуется изменить, нет необходимости разыскивать исходный промпт и генерировать все заново. DSL содержит достаточно контекста, чтобы LLM понимала замысел и могла работать с ним. Долговечным активом является не промпт, а DSL и семантическая модель.
Подпишитесь на канал Agentic Enterpise — о жизни ИИ‑агентов в кровавом энтерпрайзе

Автор: stas_makarov


