Оптимізація агента Terraform за допомогою DSPy: створіть верифікатор перед оптимізатором
Агенти Terraform створюють схеми. Створіть детерміністичний верифікатор, встановіть базові параметри для агента, а потім використайте DSPy та GEPA для оптимізації його файлу навичок з чесними результатами.
Джерела: Your Terraform Agent Is Probably Wrong Half the Time
Агент, який пише код інфраструктури, ймовірно, колись створював подібний модуль: він виглядає цілком розумно, схожий на всі модулі, які коли-небудь схвалювали рецензенти, але при цьому провалює тест terraform validate з причини, яку неможливо помітити на перший погляд.
Це також вимірюється. IaC-Eval — це тестовий набір з 458 ретельно підібраних сценаріїв Terraform, і, за повідомленнями, GPT-4 досягає лише 19,36% успішних результатів@1 у цьому тесті, на відміну від 86,6% для того ж моделі у посилених тестах на Python. У новішому дослідженні генерації інфраструктурного коду за допомогою агентів було зафіксовано 14,0% для Qwen2.5-Coder 7B, 41,4% для GPT-4o та 62,4% для GPT-5.4.
Чому код інфраструктури настільки складніший за Python? Правильна конфігурація має водночас відповідати чотирьом вимогам: коректній синтакси, відповідності схемі версії постачальника, цілісній діаграмі залежностей та вимогам власної політики організації. Відсутність будь-якої з цих вимог робить всю конфігурацію некоректною. Python, у якого 90% елементів правильні, часто все одно працює, тоді як Terraform, у якого 90% елементів правильні, не функціонує.
Дослідження також показало результати, які роблять стандартні рішення сумнівними. Методи типу short-shot prompting, chain-of-thought та багатокрокова взаємодія допомогли лише трохи. Однак приблизно 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 викликам моделі LM.
- Завжди викликайте
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.