Оптимизация агента Terraform с помощью DSPy: сначала создайте верификатор, затем оптимизатор
Агенты Terraform создают схемы. Составьте детерминистичный верификатор, установите базовую конфигурацию для агента, затем используйте DSPy и GEPA для оптимизации его файла навыков с точными результатами.
Источники: Your Terraform Agent Is Probably Wrong Half the Time
Агент, который пишет код для инфраструктуры, скорее всего, когда-либо создавал подобный модуль: он кажется совершенно разумным, похож на все модули, одобренные рецензентами, но при этом не проходит проверку terraform validate по причине, которую невозможно заметить при визуальном осмотре.
Этот показатель также измеряется. IaC-Eval — это тестовая среда, включающая 458 тщательно отобранных сценариев Terraform, и, согласно отчетам, GPT-4 справляется с ней с показателем 19,36% pass@1, тогда как у того же моделя на более строгих тестах на Python этот показатель составляет 86,6%. В более новом исследовании генерации инфраструктурного кода с использованием агентов показатели составили 14,0% для Qwen2.5-Coder 7B, 41,4% для GPT-4o и 62,4% для GPT-5.4.
Почему код инфраструктуры настолько сложнее, чем код на Python? Правильная конфигурация должна одновременно соответствовать четырем требованиям: корректной синтаксису, схеме используемой версии провайдера, целостной графике зависимостей и собственной политике организации. Нарушение хотя бы одного из этих требований приводит к ошибке в целом. Python, в котором 90% элементов правильны, часто всё равно работает, тогда как Terraform с таким же процентом правильных элементов не выполняет свои функции.
Результаты исследования также указывают на то, что стандартные решения могут оказаться неэффективными. Методы вроде управления моделью с помощью небольшого количества примеров, последовательного мышления и многократного взаимодействия помогли лишь немного. Однако примерно 79% сбоев связано с отсутствием информации в запросе к модели, причиной не являются ограничения самой модели. Модель была достаточно способной — ей просто не сообщали необходимую информацию.
Это задача оптимизации. Поэтому план заключается в том, чтобы взять рабочего агента Terraform, создать механизм оценки, необходимый для определения того, улучшается ситуация или нет, а затем запустить полный рабочий процесс DSPy на основе этой оценки: написание программы, её оценка, оптимизация. Все это можно запустить. Сопутствующий проект разделен на три папки, по одной на каждый этап:
agent/ the deepagents Terraform agent, its tools, prompts, and skills
eval/ the verifier, 38 tasks with Rego policies, and the benchmark
optimizer/ the DSPy program, metric, and GEPA compile
Исходный агент
Сохраняйте этот раздел кратким, поскольку в нём нет чего-то особенно интересного, и любой агент может его заменить. Данный агент находит реальную схему, создаёт проверяемые файлы и выполняет верификацию перед ответом.
В файле agent/tools.py находятся три инструмента. Агент сначала читает реальную схему поставщика перед записью данных. Файлы, которые он создаёт, форматируются и проверяются с точки зрения синтаксиса по мере их создания. Затем он проверяет всю конфигурацию, исправляет все проблемы, выявленные компилятором, и только после этого отвечает.
Он построен на фреймворке deepagents от LangChain, предназначенном для агентов с инструментами и навыками, хотя сам фреймворк практически не играет роли. Важна лишь структура:
from deepagents import create_deep_agent
agent = create_deep_agent(
model="openrouter:openai/gpt-5.6-luna",
tools=[provider_schema, write_terraform, validate_config],
system_prompt=SYSTEM_PROMPT,
skills=["./agent/skills"], # SKILL.md — the thing we'll optimize
)
Справочник навыков заслуживает более внимательного рассмотрения, поскольку всё остальное здесь способствует его оптимизации. Навык представляет собой файл в формате Markdown с инструкциями, который агент загружает рядом со своим системным промптом в формате, называемом Agent Skills, который быстро набирает популярность: Claude, GitHub Copilot и deepagents все используют один и тот же формат SKILL.md. Этот формат четко разделяет функции — системный промпт отвечает за базовые аспекты (как вызывать инструменты, как форматировать вывод), а навыки содержат специфические знания для определенной области, такие как конвенции Terraform, стандарты безопасности и особенности поставщиков сервисов. Поскольку один и тот же файл может использоваться несколькими агентами, навыки также являются переносимыми.
Большинство описаний DSPy направляют оптимизатор на основной запрос программы. Вопрос, рассматриваемый здесь, немного иной: может ли он оптимизировать отдельную функцию? Настоящие команды тратят большую часть времени на ручную настройку именно этих функций, поскольку в них заключается профессиональный опыт, и они постоянно редактируются в процессе проверки. В данном проекте эта функция написана вручную, хранится в git и проходит код-ревью, как и любой другой файл. К тому же её никогда не измеряли, и это является настоящей проблемой; DSPy разработан для решения этой проблемы, поскольку каждая правка в этом файле меняет поведение системы в производстве без каких-либо тестов.
Как выглядит неудача на практике
Начнем с реального примера до внесения каких-либо исправлений. Он был сгенерирован во время записи базового теста, когда агент не имел никаких инструментов и писал HCL непосредственно в свой ответ. Запрос касался использования контейнера состояния с шифрованием, управляемым клиентом:
encryption {
kms_key_name = var.encryption_key_name
}
lifecycle_rules {
...
Внешне всё в порядке, но проверка с помощью валидатора показывает совсем другую картину:
$ tofu validate
Error: Missing required argument
on main.tf line 18, in resource "google_storage_bucket" "terraform_state":
The argument "default_kms_key_name" is required, but no definition was found.
Error: Unsupported argument
on main.tf line 19, in resource "google_storage_bucket" "terraform_state":
An argument named "kms_key_name" is not expected here.
Error: Unsupported block type
on main.tf line 22, in resource "google_storage_bucket" "terraform_state":
Blocks of type "lifecycle_rules" are not expected here. Did you mean
"lifecycle_rule"?
Три ошибки возникли в одном ресурсе. Требуемым аргументом является default_kms_key_name, в то время как kms_key_name принадлежит другим ресурсам Google, и именно поэтому кажется правильным. Название блока также было приведено к множественному числу, указав несуществующий вариант.
После классификации основных ошибок по типам оказалось, что 80% ошибок проверки соответствия были связаны с вымышленной схемой: аргументы, блоки или атрибуты, которые выглядят как настоящие элементы HCL, но отсутствуют в рассматриваемом ресурсе. У провайдера google около тысячи типов ресурсов, которые меняются с каждым обновлением, поэтому модель запоминает версионированный API из памяти, а память порождает правдоподобные вымыслы.
Эти три инструмента направлены непосредственно на такую диагностику. Они сами по себе добавили 27,7 баллов (уровень успешного прохождения тестов вырос с 41,7% до 69,4% при использовании описанного ниже набора инструментов). Количество ошибок форматирования сократилось до нуля, поскольку write_terraform запускает команду fmt, вместо того чтобы ожидать, что модель сама будет управлять пробелами. Количество ошибок схемы также сократилось до нуля, потому что агент теперь может видеть то же, что и компилятор. Оставшиеся проблемы в основном касались целесообразности действий агента — построил ли он то, что было запрошено, — и именно здесь на сцену выходит DSPy.
Вот та же задача, решаемая с помощью агента, оснащенного инструментами, в его современном виде:
$ uv run python -m agent.run --task eval/tasks/backend-var-interpolation.json
task backend-var-interpolation (opentofu)
model openai/gpt-5.6-luna engine=deepagents
files main.tf, variables.tf
tools {'write_terraform': 2, 'validate_config': 1, 'validate_pass': 1}
elapsed 56602ms
Было две операции записи и одна проверка, и агент отвечает только тогда, когда компилятор дает положительный результат. Файл задачи и цель opentofu взяты из набора для оценки, куда и направляются дальнейшие действия.
Оценщик до появления фреймворка
Чтобы определить, помогает ли что-либо, необходимо ответить на вопрос «хорош ли результат?» с более уверенными аргументами, чем просто ощущения. В большинстве случаев используется оценщик на основе LLM, который сравнивает мнения разных моделей без наличия эталонных данных, после чего сам оценщик также требует проверки.
У Terraform нет такой проблемы, поскольку инструментарий, уже работающий в CI, и является оценщиком:
tofu fmt -checkвыявляет проблемы с форматированием.tofu validateнаходит синтаксические, схематические и ссылочные ошибки.tflintобозначает устаревшие аргументы и недопустимые типы машин.
tofu test с использованием mock_provider проверяет семантику без каких-либо учетных данных облачной платформы.conftest с OPA Rego проверяет целесообразность, определяя, было ли создано агентом то, о чем просили.Эти пять детерминистичных проверок реализованы в файле eval/verifier/run.py. Никакие дополнительные механизмы оценки не используются, вызовы API не стоят ничего, и ни один шаг не может привести к созданию ресурса, за который взимается плата, поскольку в проекте намеренно отсутствует элемент apply.
Большинство людей пропускают последнюю проверку, хотя именно она имеет наибольшее значение. Функция validate подтверждает, что конфигурация компилируется. Rego проверяет, действительно ли в бакете включена шифрование — это именно то, о чем просили, и именно это делает конфигурацию корректной, а не просто допустимой:
package main
import rego.v1
deny contains "bucket must use a customer-managed KMS key" if {
some name
bucket := input.resource.google_storage_bucket[name][_]
not bucket.encryption
}
Команда, которая уже применяет политики с помощью OPA, скорее всего, имеет множество подобных политик в Terraform Cloud или в своей CI-пайплайне, и их можно без изменений повторно использовать. Это подход «shift-left», применяемый к агентам. Terraform Cloud действительно отклоняет некорректный план при его применении, но к тому времени код уже существует, его прочитал рецензент, и запуск уже заложен в очередь. Запуск того же Rego при генерации кода позволяет обнаружить нарушения до того, как будет потрачено столько усилий.
Тестируйте тестировщика. Это шаг, который стоит скопировать в первую очередь. Политика, разрешающая всё, хуже, чем отсутствие политики, поскольку она присваивает каждой версии агента одинаковый балл и молча сообщает о отсутствии изменений. Поэтому каждая политика в папке eval/tasks/policy/ сопровождается одним соответствующим примером и одним примером с нарушением, причем скрипт проверяет оба варианта.
$ cd eval/tasks && ./check_policies.sh
...
✓ vpc-subnet-firewall good=0 bad=2
✅ 38/38 policies verified in both directions
Тридцать восемь задач были сформулированы в таком виде. Они охватывают стандартные настройки безопасности, подключение ресурсов между собой, охват схем поставщиков, целевые диалекты, ограничения (добавляет ли агент что-то, чего никто не хотел), а также структуру файлов. Каждая задача сочетает запрос с политикой Rego, отражающей её цель. Диалектам уделяется особое внимание: в каждой задаче указано, предназначена ли она для terraform или opentofu, поскольку эти инструменты разошлись в функционале (OpenTofu предоставляет шифрование состояния и функцию for_each для поставщиков, а Terraform — концепцию Stacks); поэтому агент, который незаметно использует неверный инструмент, не проходит проверку.
На диаграмме пяти шлюзов также показана граница измерения, которую агент не может пересечь, и легко преувеличивать значение этого факта. В реальных условиях агент может, и, скорее всего, должен, использовать инструмент OPA, который проверяет политики перед ответом. Это тот же принцип переноса проверок на ранние этапы, что позволяет создавать более качественный код.
У границы есть иная функция. Здесь те же самые правила служат критерием для сравнения навыков, моделей и результатов работы оптимизатора. Если агент мог бы итерироваться, используя именно те правила, которые оценивают эксперимент, все варианты начали бы сходиться к успешному результату, и сравнение уже не имело бы смысла. Это принцип отдельного тестового набора, применяемый к инструментам. Правило гласит: агент может видеть любые проверки, кроме той, которая в данный момент оценивает эксперимент. Если агент будет использоваться вместе с инструментом для правил, необходимо также оценить эту конфигурацию, используя отложенные правила или подсчитывая количество необходимых итераций для исправлений, а не определяя, прошел ли эксперимент успешно.
Использование DSPy
DSPy описывает себя как фреймворк для программирования языковых моделей, а не для формулировки запросов к ним. В его документации рабочий процесс разделен на три этапа: программирование, оценка и оптимизация, причем сам порядок служит уроком: оптимизатор обрабатывается в последнюю очередь, а все предшествующие шаги определяют надежность оптимизатора. Оптимизатор использует ровно три элемента: программу, метрику и данные.
Вся работа с DSPy находится в папке optimizer/, а настройка осуществляется с помощью uv:
uv add dspy
uv sync
Этап 1: программирование
Подпись — это типизированный контракт для одного вызова LM, состоящий из входных данных, выходных данных и документации, описывающей задачу. Она находится в файле optimizer/harness.py:
import dspy
class AuthorConfiguration(dspy.Signature):
"""Write a complete, valid Infrastructure-as-Code configuration."""
request: str = dspy.InputField(
desc="What the user wants built, in natural language."
)
target: str = dspy.InputField(
desc="Which dialect to target: 'terraform' or 'opentofu'. These have "
"diverged — code targeting the wrong one will fail validation."
)
config: str = dspy.OutputField(
desc="The complete configuration as fenced HCL blocks. Start each block "
"with a comment naming its file, e.g. '# main.tf'."
)
Именно пропуски и являются сутью. Здесь нет фраз вроде «Вы — эксперт-инженер Terraform», нет примеров решений и нет просьб о соблюдении формата. Названия полей и их описания несут смысл, а оптимизатор заботится о формулировках.
Любой, кто считает это волшебством, может развеять его одной строкой:
dspy.inspect_history(n=1)
Он выводит реальный запрос, сгенерированный адаптером на основе подписи. Реальный вывод будет выглядеть примерно так:
System message:
Your input fields are:
1. `request` (str): What the user wants built, in natural language.
2. `target` (str): Which dialect to target: 'terraform' or 'opentofu'. ...
Your output fields are:
1. `config` (str): The complete configuration as fenced HCL blocks. ...
[[ ## request ## ]]
{request}
[[ ## target ## ]]
{target}
[[ ## config ## ]]
{config}
In adhering to this structure, your objective is:
Write a complete, valid Infrastructure-as-Code configuration ...
Ничего не скрыто. Это структурированный шаблон, сгенерированный на основе подписи, и именно поэтому оптимизатор может безопасно его переписать, а также поэтому его следует рассматривать в первую очередь при необычном поведении.
Модуль применяет ту или иную технику к подписи, причем эти два элемента изменяются независимо друг от друга. Одна подпись может использоваться в виде однократной операции с предварительным анализом или в качестве движущей силы цикла инструмента:
generate = dspy.Predict(AuthorConfiguration) # one shot
generate = dspy.ChainOfThought(AuthorConfiguration) # reason first
generate = dspy.ReAct(AuthorConfiguration, tools=[...]) # run a tool loop
Этот агент использует инструменты, поэтому он монтируется с помощью dspy.ReAct и получает именно те функции инструментов, которые вызывает развернутый агент, импортированные непосредственно из пакета агента. Код находится в файле optimizer/harness_deepagents.py:
from agent.tools import make_tools # the deployed agent's tools
class TerraformAuthoringAgent(dspy.Module):
def __init__(self, seed_instruction: str):
super().__init__()
# Seed the SIGNATURE before constructing ReAct, so DSPy appends its
# tool protocol to your instruction instead of replacing it.
seeded = AuthorConfiguration.with_instructions(seed_instruction)
lc_tools = make_tools(work_dir, binary="terraform", stats={})
self.react = dspy.ReAct(
seeded,
tools=[dspy.Tool(t.func, name=t.name, desc=t.description)
for t in lc_tools.values()],
max_iters=10,
)
def forward(self, request: str, target: str = "terraform"):
return self.react(request=request, target=target)
Самое важное решение по дизайну в данной схеме заключается в следующем: оптимизировать ту же самую программу, которая запускается. В одном из ранних экспериментов был оптимизирован одноразовый генератор, а затем результат его работы передавался агенту, использующему инструменты; это делало все числа бессмысленными, поскольку инструкции были настроены с учётом сбоев, которые реальный агент уже не вызывал.
Исходной инструкцией является существующий файл SKILL.md, поэтому начинать с нуля невозможно, и настоящий вопрос заключается в том, можно ли улучшить рукописный текст. DSPy не знает, что данная строка представляет собой навык, а не основной запрос, поскольку видит только текст инструкции подписи. Именно это делает эксперименты с навыками осуществимыми: результат сохраняется прямо в формате SKILL.md и работает с любым агентом, который читает навыки.
Этап 2: оценка
Тип данных в DSPy — dspy.Example, это словарь, в котором указаны поля, являющиеся входными данными. Загрузчик в файле optimizer/dataset.py преобразует этот набор данных напрямую:
dataset = [
dspy.Example(
task_id=t["id"],
request=t["prompt"],
target=t["target"],
rego=t["rego"], # path to this task's policy
).with_inputs("request", "target")
for t in tasks
]
Поля, которые не отмечены как входные данные, передаются в качестве метаданных. Для поиска соответствующей политики используется rego, и модель никогда не видит этих полей.
Стоит подробно объяснить, как выбираются примеры, поскольку структура набора данных определяет всё, что может изучить оптимизатор. Их не собирали из каких-либо тестовых баз. Они имитируют реальные запросы: например, запрос на шифрованный бакет GCS с ключом, управляемым клиентом, или запрос на предоставление прав доступа сервисному аккаунту только к одному бакету. Таким образом, охватываются основные ресурсы, с которыми работает команда платформы: хранилище, сетевые решения, IAM, SQL и Pub/Sub, а также задачи, направленные на устранение уже известных сценариев сбоев: ресурсы с сложной структурой, где модели выдумывают аргументы, задачи контроля, проверяющие, не добавляет ли агент ресурсы, которых никто не просил, и вышеупомянутые задачи на диалекты. Некоторые из задач сознательно делаются сложными, поскольку набор, проходящий все тесты с первого дня, не может демонстрировать совершенствование.
Тридцать восемь примеров кажутся небольшим количеством, и действительно так. Цель — полное охват каждого примера, поскольку у каждого своя собственная политика, а также честное разделение данных. Сначала производится разделение, затем оптимизация, причем тестовый набор должен оставаться неприкосновенным: обучающий набор служит основой для обучения оптимизатора, набор валидации помогает выбирать кандидатов, а тестовый набор оценивается только один раз в конце. Большинство подходов в области инжиниринга промптов игнорируют эту дисциплину, и именно из-за этого люди убеждают себя, что изменения помогли, хотя на самом деле это не так. Это происходило дважды в этом проекте, и в обоих случаях победа на небольшом наборе данных для валидации сменялась неудачей при увеличении размера набора.
Метрика — это элемент, в который вставляются данные для проверки, и представляет собой функцию, выполняющую три задачи (optimizer/metric.py):
def verify_metric(example, prediction, trace=None, pred_name=None, pred_trace=None):
"""Score with the SAME verifier that produces our benchmark numbers."""
result = verify(
config_dir=materialise(prediction.config),
policy_dir=example.rego,
target=example.target,
)
# Job 1 — bootstrapping (trace is set): a strict bool. Only outputs that
# FULLY pass may become worked examples.
if trace is not None:
return result.passed
# Job 2 — reflective optimization (pred_name is set): score AND feedback.
# GEPA reads the text to understand *why* a candidate failed.
if pred_name is not None:
return dspy.Prediction(score=result.score, feedback=format_failures(result))
# Job 3 — plain evaluation: a float.
return result.score
Сохраняйте простоту. Если метрика повторно реализует какую-либо логику оценки, число, подлежащее оптимизации, и число, публикуемое в результатах, начинают отклоняться друг от друга, в результате чего ничего не измеряется.
Именно в разделе обратной связи этот подход выделяется. Оптимизаторы, основанные на анализе, используют текст, объясняющий сбой, причем такой текст здесь свободен и точен: реальные ошибки компилятора и реальные отказы в выполнении правил, как показано ранее. Поля, зависящие от оценки больших языковых моделей, оплачивают каждую оценку и получают гораздо худшую обратную связь, после чего им ещё предстоит проверять качество оценки модели.
Сначала измерьте базовый уровень с помощью dspy.Evaluate:
evaluate = dspy.Evaluate(devset=valset, metric=verify_metric,
num_threads=8, display_table=True)
evaluate(program)
Результат выполнения теста с 3 примерами выглядит так, а полная версия требует больше времени:
Average Metric: 0.59 / 3 (19.6%): 100%|██████████| 3/3 [01:16<00:00, 25.60s/it]
INFO dspy.evaluate.evaluate: Average Metric: 0.5882 / 3 (19.6%)
WARNING dspy.evaluate.evaluate: Skipping table display since `pandas` is not installed.
Из этого отрывка можно сделать два вывода. Каждый «пример» представляет собой полную серию действий агента, а не один вызов API, поэтому необходимо учитывать это при планировании ресурсов. Кроме того, функция display_table требует библиотеки pandas, поэтому её следует установить для отображения таблицы для каждого примера.
Если из всего этого можно выделить одну привычку, то это необходимость оптимизации только того, что имеет базовый уровень. Именно этот показатель делает результаты оптимизатора значимыми.
Этап 3: оптимизация
Разные оптимизаторы настраивают разные аспекты:
BootstrapFewShotсинтезирует рабочие примеры (демонстрации).MIPROv2корректирует инструкции и демонстрации с использованием метода байесовского поиска.GEPAнастраивает инструкции на основе анализа следов неудач.
Настройка инструкций является подходящим инструментом для кода с ограничениями схемы, поскольку исследование агентных IaC показало, что она эффективнее внедрения демонстраций, именно потому что демоны не устраняют галлюцинации схемы. Поэтому GEPA является лучшим выбором, как указано в файле optimizer/compile.py:
gepa = dspy.GEPA(
metric=verify_metric,
max_metric_calls=150,
reflection_lm=dspy.LM("openrouter/openai/gpt-5.6-luna", max_tokens=16000),
num_threads=8,
track_stats=True,
)
compiled = gepa.compile(student=program, trainset=trainset, valset=valset)
compiled.save("compiled_state.json", save_program=False)
Эти две модели выполняют разные задачи. Модель для выполнения задач пишет код на Terraform и именно она используется в финальной версии; модель для анализа изучает сбои и разрабатывает предварительные инструкции. Она запускается примерно 40 раз по сравнению с примерно 900 запусками модели для выполнения задач, поэтому инвестиции в более мощную модель здесь оправданы.
Несколько важных практических замечаний:
- Установите значение
num_threads. Однажды его забыли установить, и компиляция заняла 69 минут; последующие измерения показали, что машина простаивала в 94% времени, ожидая последовательных запросов к API. - «150 запросов к метрикам» означает 150 эпизодов работы агента, что с учётом цикла инструмента соответствует примерно 900 запросам к модели языка.
- Всегда вызывайте
save()после компиляции. При использовании параметраsave_program=Falseрезультатом будет обычный JSON: оптимизированный текст инструкций вместе с любыми демонстрационными примерами. Этот же JSON позволяет полностью обойтись без DSPy, поскольку примерно двадцать строк (optimizer/to_skill_md.py) снова преобразуют его в файлSKILL.md, который используется агентом. Во время выполнения DSPy никогда не импортируется. Оптимизатор представляет собой лишь этап сборки, а результат его работы поступает в виде diff-файла в pull request, который человек может прочитать и отклонить.
GEPA также обнаруживает реальные проблемы. На более слабой модели агент постоянно прерывал свой цикл работы с инструментами: после одного вызова provider_schema он прекращал действия, ничего не записав. Об этом GEPA не сообщали. Он проанализировал следы сбоев и включил этот вывод в оптимизированную инструкцию: не следует считать задачу завершенной только из-за записи файла, поскольку успешная запись подтверждает лишь то, что формат HCL распознается, а для выявления ошибок в структуре провайдера и недействительных ссылок необходимо использовать функцию validate_config. Это результат анализа реальных следов выполнения, дающий рекомендации, которые при формулировке потребовали бы часов работы над отладкой.
Рабочий процесс продолжает приносить результаты после оптимизации
Ещё два элемента той же системы используют уже существующие показатели эффективности.
Этот метрик также может выполняться во время инференса. Функции BestOfN и Refine из DSPy оборачивают любой модуль, запускают его столько раз, сколько указано в параметре N, и возвращают первый результат, награда за который достигает заданного порога:
robust = dspy.Refine(module=program, N=3, reward_fn=verify_metric, threshold=1.0)
Эта схема должна показаться знакомой, поскольку цикл проверки и ремонта агента использует ту же идею с компилятором в качестве функции вознаграждения. Данные показывают, что это самый эффективный инструмент: в исследовании IaC итеративный ремонт с обратной связью от верификатора позволил модели Qwen 7B повысить показатели с 14,0% до 62,9%, что лучше, чем любые другие применяемые методы стимулирования. В любом случае необходимо предоставлять модели её верификатор во время выполнения.
Замена моделей теперь представляет собой изменение в одну строку кода, и её можно измерять:
program.set_lm(dspy.LM("openrouter/qwen/qwen3-coder-30b-a3b-instruct"))
evaluate(program)
Это оказалось важнее, чем ожидалось. Замена модели на более дешевую на среднем этапе проекта привела к снижению качества с 83,3% до 37,5%, причем почти половина тестов прерывалась без создания каких-либо файлов. В ручном режиме работы этого не проявляется, и первый сигнал появляется от клиента. Двенадцать минут работы тестового набора позволили обнаружить эту проблему.
Честный результат
В случае с этим агентом скомпилированная инструкция не смогла превзойти ручно написанную.
Сравнение проводилось девять раз с использованием двух реализаций агентов и двух моделей, включая отдельный тестовый набор, в котором обе модели показали одинаковый результат — 92,9%. Эта цифра относится к более сильной модели, тогда как ранее указанные показатели 41,7% и 69,4% относятся к более дешевой модели при использовании того же набора тестов. Дважды результат казался однозначным победителем, он фиксировался, но затем менялся при анализе более крупных выборок.
Это самое ценное урок, который можно извлечь, и это не означает, что DSPy не справляется. Конкретная и правдоподобная причина может объяснить, почему он не сработал. Указанные GEPA результаты достигаются благодаря модульным рабочим процессам — конвейерам, в которых каждый шаг использует отдельный промпт, так что оптимизатор может возложить ответственность за сбой на определенный модуль и его исправить. Агент же использует один и тот же промпт на каждом шаге. Критика Бенджамина Андерсона заключается в том, что это устраняет именно ту локальность, от которой зависит GEPA.
Практические рекомендации следующие:
- Для простого конвейера полученные результаты, скорее всего, будут применимы.
- Для цикла агента сначала проведите собственные тесты, прежде чем предполагать, что они сработают.
- В обоих случаях направляйте обратную связь от верификатора непосредственно в сам агент; у этого подхода есть наиболее надежные доказательства — как в опубликованных работах, так и в этих цифрах.
И этот тест возможен благодаря существованию соответствующего набора тестов.
Что это означает
Модели языка с трудом справляются с концепцией инфраструктуры как кода. Результат GPT-4 в 19,36% на тесте IaC-Eval — это не проблема, которую можно решить простыми рекомендациями или оптимистичными ожиданиями, и обычные приемы едва помогают. Тем не менее большинство сбоев в работе обусловлены недостатком информации, а не отсутствием способностей, поэтому реальный потенциал для улучшений существует, и он заключается в инструкциях и инструментах, а не в весах модели.
Рабочий процесс, который доказал свою эффективность, описан в собственной документации DSPy в том порядке, в котором он преподается. Что касается программирования: необходимы сигнатуры контракта, модули, отражающие то, что будет развернуто, а также функция inspect_history при наличии сомнений относительно отправляемых данных. Для оценки: примеры с четко определенными входными данными, простой показатель, служащий одновременно обратной связью для оптимизатора, базовый уровень до любых изменений и набор тестов, который проверяется лишь один раз. Для оптимизации, и только тогда, используется тот же показатель, при этом сохраняется текст, который можно рассмотреть в запросе на интеграцию.
Хотя оптимизатор и не превзошел ручной способ обработки данных, использование DSPy вынудило определить понятие «правильного» результата в виде исполняемого кода. Этот подход продолжал сохранять свою актуальность: он выявил ухудшение качества на 46 пунктов после замены модели, показал, что данный способ обработки данных загружался лишь в 25% случаев (проблема была решена путем переписывания одного предложения, после чего уровень загрузки достиг 100%), а также дважды предотвратил публикацию результатов, полученных в результате шума, как победных.
На самом деле для всего этого не обязательно использовать DSPy, и инструмент проверки здесь его не импортирует. DSPy полезен тем, что заставляет существовать определенные метрики и их повторное использование: одно определение управляет набором тестов на регрессию, сравнениями моделей, попытками выполнения запросов и работой оптимизатора. Команды, уже имеющие необходимую дисциплину, возможно, не нуждаются в этой библиотеке, а те, у кого ее нет, могут ее приобрести. В любом случае следует прекратить выпуск обновлений, качество которых невозможно измерить.
Связанные статьи
- Контроль поведения агента Bedrock: этапы формулировки запросов, парсеры и процесс инференса — Как переопределить функции предварительной обработки, оркестрации и последующей обработки запросов в Bedrock с помощью Terraform, включая использование парсера Lambda, JSON-контракта и настроек инференса для каждого этапа.
- Форма онтологии против размера модели: сколько стоит дизайн графа для ИИ-агента — Как три различные формы одного графа знаний в сфере страхования повлияли на точность, количество вызовов инструментов, количество токенов и стоимость работы небольших и крупных моделей в Azure Cosmos DB.