Optimierung eines Terraform Agents mit DSPy: Erstellen Sie den Verifier vor dem Optimizer
Terraform-Agenten erfinden Schemata. Entwickeln Sie einen deterministischen Verifier, legen Sie einen Baseline-Wert für den Agenten fest und verwenden Sie anschließend DSPy sowie GEPA, um dessen Skill-Datei mit zuverlässigen Ergebnissen zu optimieren.
Quellen: Your Terraform Agent Is Probably Wrong Half the Time
Ein Agent, der Infrastrukturcode schreibt, hat vermutlich zu irgendeinem Zeitpunkt ein solches Modul erstellt: Es sieht völlig plausibel aus, liest sich wie jedes Modul, das je von einem Prüfer genehmigt wurde, und versagt dann bei terraform validate aus einem Grund, den niemand auf den ersten Blick erkennen würde.
Auch das wird gemessen. IaC-Eval ist ein Benchmark mit 458 sorgfältig ausgewählten Terraform-Szenarien, und GPT-4 schafft dem Bericht zufolge lediglich 19,36 % Pass@1, während derselbe Modell bei verschärften Python-Benchmarks 86,6 % erreicht. Eine neuere Studie zur agierenden IaC-Generierung weist für Qwen2.5-Coder 7B 14,0 %, für GPT-4o 41,4 % und für GPT-5.4 62,4 % aus.
Warum ist Code für Infrastrukturprojekte so viel schwieriger als Python? Eine korrekte Konfiguration muss gleichzeitig vier Anforderungen erfüllen: gültige Syntax, Übereinstimmung mit dem Schema der verwendeten Provider-Version, ein zusammenhängendes Abhängigkeitsdiagramm sowie Einhaltung der eigenen Richtlinien der Organisation. Fehlt eine dieser Anforderungen, ist die gesamte Konfiguration fehlerhaft. Ein in 90 % korrekter Python-Code funktioniert oft noch, während ein in 90 % korrekter Terraform-Code nicht angewendet wird.
Die Forschung ergab außerdem Ergebnisse, die die üblichen Lösungsansätze in Frage stellen. Few-shot Prompting, Chain-of-Thought-Verfahren sowie mehrstufige Interaktionen halfen jeweils nur wenig. Rund 79 % der Richtlinienfehler lagen an fehlenden Informationen im Prompt, wobei die Fähigkeiten des Modells nicht die Ursache waren. Das Modell war ausreichend fähig – ihm wurden einfach nicht die richtigen Informationen mitgeteilt.
Das ist ein Optimierungsproblem. Der Plan besteht also darin, einen funktionierenden Terraform-Agenten zu verwenden, die notwendige Bewertung durchzuführen, um festzustellen, ob sich die Situation verbessert, und anschließend den gesamten DSPy-Ablauf anhand dieser Bewertung auszuführen: Programmieren, Bewertung, Optimierung. Alles ist ausführbar. Das begleitende Projekt ist in drei Ordner unterteilt, einen pro Phase:
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
Der Startagent
Halten Sie diesen Abschnitt kurz, da es sich nicht um den interessanten Teil handelt und jeder Agent hierfür geeignet ist. Dieser Agent sucht das tatsächliche Schema auf, erstellt zu überprüfende Dateien und validiert diese vor der Antwort.
Drei Tools befinden sich in agent/tools.py. Der Agent liest das tatsächliche Provider-Schema vor dem Schreiben ein. Die von ihm erstellten Dateien werden dabei formatiert und syntaktisch überprüft. Anschließend validiert er die gesamte Konfiguration und behebt alle vom Compiler festgestellten Probleme, erst danach antwortet er.
Er basiert auf deepagents, LangChains Framework für Agenten mit Tools und Fähigkeiten, obwohl dieses Framework kaum eine Rolle spielt. Entscheidend ist vielmehr die Struktur:
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
)
Der Fähigkeitskatalog verdient eine genaue Betrachtung, denn alles andere hier optimiert ihn. Eine Fähigkeit ist eine Markdown-Datei mit Anweisungen, die ein Agent neben seine Systemanweisung lädt – in einem schnell an Popularität gewinnenden Format namens Agent Skills. Claude, GitHub Copilot und deepagents lesen alle denselben SKILL.md-Aufbau. Dieser trennt die Aufgaben klar voneinander: Die Systemanweisung beinhaltet lediglich grundlegende Anleitungen (wie Tools aufgerufen werden und wie Ausgaben formatiert werden), während die Fähigkeit fachspezifisches Wissen wie Terraform-Vorgaben, Sicherheitsstandards und Besonderheiten der Anbieter enthält. Da dieselbe Datei auf mehreren Agenten verwendet werden kann, sind Fähigkeiten zudem portabel.
Die meisten Beschreibungen zu DSPy lenken den Optimierer auf die Hauptanfrage eines Programms. Die hier gestellte Frage ist etwas anders: Kann er eine Fähigkeit optimieren? Reale Teams verbringen den größten Teil ihrer manuellen Anpassungen mit dieser Fähigkeit, da sie das Fachwissen enthält und ständig während der Überprüfung geändert wird. In diesem Projekt ist sie handgeschrieben, befindet sich in Git und wird wie jede andere Datei koderevisiert. Zudem wurde sie noch nie gemessen – das ist das eigentliche Problem – und DSPy ist dazu konzipiert, dieses Problem zu lösen, denn jede Änderung an dieser Datei verändert das Verhalten in der Produktion, ohne dass es Tests gibt.
Wie ein Versagen tatsächlich aussieht
Beginnen wir mit einem echten Artefakt aus der Zeit vor allen Korrekturen. Es entstand während des aufgezeichneten Baseline-Benchmark-Laufs, bei dem der Agent keine Tools hatte und die HCL direkt in seine Antwort einfügte. Die Anfrage bezog sich auf einen State Bucket unter Verwendung von kundenverwalteter Verschlüsselung:
encryption {
kms_key_name = var.encryption_key_name
}
lifecycle_rules {
...
Es sieht in Ordnung aus, doch der Ausführung des Validators darauf zufolge ist die Situation eine andere:
$ 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"?
Drei Fehler traten bei einer einzigen Ressource auf. Der erforderliche Parameter ist default_kms_key_name, während kms_key_name zu anderen Google-Ressourcen gehört, weshalb er als korrekt erscheint. Auch der Name eines Blocks wurde in eine Form pluralisiert, die nicht existiert.
Als die grundlegenden Fehler nach Typen sortiert wurden, stellten sich 80 % der Validierungsfehler als erfundene Schemata heraus: Parameter, Blöcke oder Attribute, die wie echte HCL-Elemente aussehen, aber in der betreffenden Ressource nicht vorhanden sind. Der google-Anbieter verfügt über etwa tausend Ressourcentypen, die mit jeder Version aktualisiert werden, wodurch das Modell eine versionierte API aus dem Gedächtnis abruft – und das Gedächtnis liefert plausibel klingende Fiktionen.
Die drei Tools richten sich direkt auf diese Diagnose. Allein brachten sie 27,7 Punkte hinzu (die Bestehensrate stieg bei dem unten beschriebenen Set von 41,7 % auf 69,4 %). Formatierungsfehler sanken auf Null, da write_terraform fmt ausführt anstatt davon auszugehen, dass das Modell den Leerzeichenverbrauch steuert. Schemafehler sanken ebenfalls auf Null, weil der Agent endlich erkennen konnte, was der Compiler sieht. Was übrig blieb, bestand fast ausschließlich aus der Absicht – ob der Agent das Gesuchte erstellte – und genau da kommt DSPy ins Spiel.
Hier ist dieselbe Aufgabe, wie sie heute mit dem durch Tools erweiterten Agenten ausgeführt wird:
$ 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
Es gab zwei Schreibvorgänge und eine Validierung, und der Agent antwortet erst, wenn der Compiler zufrieden ist. Die Aufgabendatei sowie das Ziel opentofu stammen aus dem Evaluations-Set, wohin die Dinge anschließend weitergeleitet werden.
Ein Bewertungstool vor einem Framework
Um zu beurteilen, ob etwas hilft, muss man mit etwas Stärkerem als nur einem Gefühl auf die Frage „Ist die Ausgabe gut?“ antworten. Die meisten Bereiche verwenden einen LLM-Bewerter, der die Meinung eines Modells mit der eines anderen ohne objektive Wahrheit vergleicht, wobei der Bewerter selbst anschließend überprüft werden muss.
Terraform hat dieses Problem nicht, denn die bereits im CI laufende Toolkette ist der Bewertungstool:
tofu fmt -checkerkennt Formatierungsfehler.tofu validatefindet Syntax-, Schema- und Referenzfehler.tflintweist veraltete Argumente sowie ungültige Maschinentypen aus.
tofu test mit mock_provider überprüft die Semantik ohne jegliche Cloud-Zugangsdaten.conftest mit OPA Rego prüft die Absicht und stellt fest, ob der Agent das Gebotene erstellt hat.Diese fünf deterministischen Prüfungen sind in eval/verifier/run.py implementiert. Es ist kein „Judge“ beteiligt, die Bewertung kostet nichts an API-Aufrufen, und kein Schritt kann eine gebührenpflichtige Ressource erzeugen, da das Projekt absichtlich überhaupt keine apply-Anweisungen enthält.
Die meisten Menschen überspringen die letzte Prüfung, obwohl sie am wichtigsten ist. validate bestätigt, dass die Konfiguration kompiliert werden kann. Rego stellt sicher, dass tatsächlich die Verschlüsselung aktiviert ist – genau wie angefordert –, was sie nicht nur gültig, sondern auch korrekt macht:
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
}
Ein Team, das bereits Richtlinien über OPA durchsetzt, verfügt wahrscheinlich über viele solcher Richtlinien in Terraform Cloud oder in seinem CI-Pipeline, und diese können unverändert wiederverwendet werden. Es handelt sich dabei um ein Shift-Left-Prinzip, das auf Agenten angewandt wird. Terraform Cloud lehnt zwar einen fehlerhaften Plan zum Zeitpunkt der Anwendung ab, doch zu diesem Zeitpunkt existiert der Code bereits, ein Prüfer hat ihn gelesen und ein Ausführungsvorgang ist bereits in der Warteschlange. Durch das Ausführen desselben Rego-Scripts beim Erstellen des Codes werden Verstöße bereits vor Aufwand entdeckt.
Testen Sie den Tester. Dies ist der Schritt, den am meisten übernommen werden sollte. Eine Richtlinie, die alles genehmigt, ist schlimmer als keine, denn sie verleiht jeder Agentenversion denselben Wert und meldet stillschweigend keine Änderungen. Jede Richtlinie in eval/tasks/policy/ enthält daher ein konformes und ein fehlerhaftes Testbeispiel, und ein Skript überprüft beide Richtungen:
$ cd eval/tasks && ./check_policies.sh
...
✓ vpc-subnet-firewall good=0 bad=2
✅ 38/38 policies verified in both directions
Achtunddreißig Aufgaben wurden auf diese Weise formuliert. Sie behandeln Sicherheitsstandards, die Verkabelung zwischen Ressourcen, die Abdeckung durch Provider-Schemata, Zielsetzungen bezüglich der verwendeten Dialekte, Beschränkungen (fügt der Agent Elemente hinzu, die niemand wollte) sowie die Dateistruktur. Jede Aufgabe verbindet eine Anweisung mit einer Rego-Regel, die deren Absicht widerspiegelt. Bei Dialekten wird eine besondere Behandlung gewährt: Jede Aufgabe gibt an, ob sie sich auf terraform oder opentofu bezieht, da sich diese beiden Tools voneinander entfernt haben (OpenTofu bietet State-Verschlüsselung sowie den Provider-Befehl for_each, während Terraform Stacks anbietet); daher scheitert ein Agent, der heimlich das falsche Tool verwendet, an der Validierung.
Das Diagramm der fünf Tore zeigt außerdem eine Messgrenze, die der Agent nicht überschreiten kann, und es ist leicht, die Bedeutung davon zu übertreiben. In der Produktion kann und sollte der Agent in der Regel ein OPA-Tool erhalten, damit es die Richtlinien vor der Antwortprüft. Das ist derselbe Ansatz des „Shift-Left“-Prinzips und führt zu besseren Codequalitäten.
Die Grenze dient einem anderen Zweck. Hier sind genau diese Richtlinien der Maßstab, mit dem Fähigkeiten, Modelle sowie Optimierungsversuche verglichen werden. Wenn der Agent gegen die gleiche Richtlinie iterieren könnte, die das Experiment bewertet, würden alle Varianten dazu neigen, zu bestehen, und der Vergleich würde nichts aussagen. Das ist das Prinzip des zurückgehaltenen Testsets, angewandt auf Tools. Die Regel lautet: Der Agent darf alle Prüfungen sehen außer jener, die gerade das Experiment bewertet. Wenn der Produktionsagent zusammen mit dem Richtlinien-Tool ausgeliefert wird, muss auch diese Konfiguration bewertet werden – entweder mit zurückgehaltenen Richtlinien oder indem die benötigten Reparaturiterationen gezählt werden, anstatt zu prüfen, ob sie bestanden hat.
DSPy einführen
DSPy bezeichnet sich selbst als Framework für das Programmieren von Sprachmodellen anstelle von deren Ansteuerung durch Prompts. Ihre Dokumentation gliedert den Arbeitsablauf in drei Phasen – Programmierung, Bewertung und Optimierung – und diese Reihenfolge ist bereits eine Lektion: Der Optimierer wird zuletzt angepasst, und alles davor bestimmt, wie zuverlässig der Optimierer ist. Der Optimierer benötigt genau drei Dinge: ein Programm, eine Metrik und Daten.
Sämtliche DSPy-Funktionen befinden sich unter optimizer/, und die Einrichtung stützt sich auf uv:
uv add dspy
uv sync
Phase 1: Programmierung
Eine Signatur ist ein typisierter Vertrag für einen einzigen LM-Aufruf, der aus Eingaben, Ausgaben sowie einer Dokumentation zur Beschreibung der Aufgabe besteht. Diese Signatur befindet sich in 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'."
)
Die Auslassungen sind genau der Punkt. Es gibt weder den Satz „Sie sind ein Experte für Terraform-Entwicklung“ noch funktionierende Beispiele oder Bitten bezüglich des Formats. Die Feldnamen und -beschreibungen tragen die Bedeutung, und der Optimierer kümmert sich um die Formulierung.
Jeder, der das für magisch hält, kann es mit einer einzigen Zeile beseitigen:
dspy.inspect_history(n=1)
Es gibt den tatsächlichen Prompt aus, den der Adapter aus der Signatur erstellt hat. Die tatsächliche Ausgabe sieht so aus:
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 ...
Nichts bleibt verborgen. Es handelt sich um ein strukturiertes Template, das aus der Signatur erzeugt wird – genau deshalb kann ein Optimierer es sicher umschreiben, und genau deshalb sollte man zuerst dorthin schauen, wenn sich das Verhalten seltsam verhält.
Ein Modul wendet eine Technik auf eine Signatur an, wobei sich die beiden unabhängig voneinander unterscheiden. Eine Signatur kann entweder als Einzelausführung mit vorheriger Logikverarbeitung laufen oder als Antrieb für einen Tool-Loop dienen:
generate = dspy.Predict(AuthorConfiguration) # one shot
generate = dspy.ChainOfThought(AuthorConfiguration) # reason first
generate = dspy.ReAct(AuthorConfiguration, tools=[...]) # run a tool loop
Dieser Agent verwendet Werkzeuge, weshalb er mit dspy.ReAct abgebildet wird und genau die Werkzeugfunktionen erhält, die der eingesetzte Agent aufruft – diese werden direkt aus dem Agentenpaket importiert. Der Code befindet sich in 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)
Die wichtigste Gestaltungsentscheidung bei der Einrichtung ist folgende: Optimieren Sie dasselbe Programm, das bereitgestellt wird. Ein frühes Experiment optimierte einen einmaligen Generator und ließ die Ergebnisse anschließend an einen toolverwendenden Agenten weiterleiten, wodurch jede Zahl sinnlos wurde, da die Anweisungen für Fehler angepasst worden waren, die der eigentliche Agent nicht mehr erzeugte.
Die Ausgangsanweisung ist das vorhandene SKILL.md, sodass es keinen Neustart gibt. Die eigentliche Frage ist, ob der handgeschriebene Text verbessert werden kann. DSPy weiß nicht, dass es sich bei dem String um eine Fähigkeit und nicht um eine Hauptanweisung handelt, da es nur den Anweisungstext der Signatur sieht. Genau das macht ein Fähigkeitsexperiment praktikabel: Das Ergebnis wird direkt in Form von SKILL.md gespeichert und funktioniert für jeden Agenten, der Fähigkeiten liest.
Phase 2: Bewertung
Der Datentyp in DSPy ist dspy.Example, ein Dictionary, das kennt, welche Felder Eingaben sind. Der Lader in optimizer/dataset.py wandelt das Suite-Objekt direkt um:
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
]
Felder, die nicht als Eingabefelder markiert sind, werden als Metadaten übertragen. Die Messgröße verwendet rego, um die entsprechende Richtlinie zu finden, und das Modell sieht sie niemals.
Es ist sinnvoll, genau zu erläutern, wie die Beispiele ausgewählt werden, denn das Datensatzformat bestimmt alles, was der Optimierer lernen kann. Sie wurden nicht aus einem Benchmark abgerufen. Sie ahmen vielmehr den Aufbau echter Anfragen nach – beispielsweise eine Anfrage nach einem GCS-State-Bucket, das mit einer vom Kunden verwalteten Schlüssel verschlüsselt ist, oder eine Service-Konto-Erlaubnis, die nur Zugriff auf ein bestimmtes Bucket gewährt. Der Umfang umfasst somit die Kernressourcen, mit denen sich ein Plattformteam beschäftigt: Speicher, Netzwerk, IAM, SQL und Pub/Sub, sowie Aufgabenkategorien für bereits bekannte Fehlermuster: schema-reiche Ressourcen, bei denen Modelle Argumente erfinden, Beschränkungsaufgaben, die sicherstellen, dass der Agent keine vom Benutzer nicht angeforderten Ressourcen hinzufügt, sowie die oben genannten Dialekt-Aufgaben. Einige Aufgaben sind absichtlich schwierig gestaltet, denn ein Set, das bereits am ersten Tag alle Aufgaben meistert, kann keine Verbesserungen mehr zeigen.
Achtunddreißig Beispiele klingen wenig, und das sind sie tatsächlich. Ziel ist eine ausführliche Behandlung jedes einzelnen Beispiels, da jedes seine eigene Richtlinie hat, sowie ehrliche Aufteilungen. Teilen Sie die Daten vor der Optimierung auf und halten Sie den Testdatensatz unangetastet: Die Trainingsdaten dienen dem Optimierer als Lerngrundlage, die Validierungsdaten helfen dabei, Kandidaten auszuwählen, und der Testdatensatz wird am Ende nur einmal bewertet. Die meisten Ansätze im Prompt Engineering überspringen diese Disziplin, und genau dadurch überzeugen sich die Menschen davon, dass eine Änderung geholfen hat, obwohl dem nicht so war. Das ist in diesem Projekt zweimal passiert – beide Male führte ein Erfolg mit einer kleinen Validierungsstichprobe dazu, dass sich das Ergebnis änderte, sobald die Stichprobe größer wurde.
Die Metrik ist der Punkt, an dem der Überprüfer eingreift, und es handelt sich dabei um eine Funktion, die drei Aufgaben erfüllt (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
Halten Sie es einfach. Wenn die Metrik eine eigene Bewertunglogik implementiert, weichen die zu optimierenden Werte von den veröffentlichten Werten ab, wodurch letztendlich nichts mehr gemessen wird.
Der Feedback-Aspekt ist der Punkt, an dem sich dieses Gebiet abhebt. Reflektierende Optimierer nutzen Texte, die einen Fehler erklären, und in diesem Fall sind diese Texte frei und genau: echte Compilerfehler sowie echte Ablehnungen aufgrund von Richtlinien, wie sie zuvor gezeigt wurden. Felder, die von einem LLM bewertet werden, zahlen für jede Bewertung und erhalten deutlich schlechtere Rückmeldungen; außerdem müssen sie den Bewertungsmechanismus selbst noch überprüfen.
Messen Sie zunächst einen Baseline-Wert mithilfe von dspy.Evaluate:
evaluate = dspy.Evaluate(devset=valset, metric=verify_metric,
num_threads=8, display_table=True)
evaluate(program)
Die Ausgabe bei einem Testlauf mit 3 Beispielen sieht so aus, und der vollständige Test dauert länger:
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.
Diese Auszüge liefern zwei Lehren. Jedes „Beispiel“ ist eine vollständige Agentenepisode, nicht nur ein einzelner API-Aufruf – planen Sie dementsprechend. Zudem benötigt display_table heimlich pandas, installieren Sie es daher für die Tabelle pro Beispiel.
Falls sich aus diesem Text eine Gewohnheit ergibt, dann sollte es die sein, nichts zu optimieren, ohne zuvor einen Baseline-Wert zu haben. Nur diese Zahl macht die Ausgabe des Optimizers sinnvoll.
Phase 3: Optimierung
Andere Optimizer justieren unterschiedliche Aspekte:
BootstrapFewShotsynthetisiert funktionierende Beispiele (Demos).MIPROv2passt Anweisungen und Demos mithilfe von Bayesian Search an.GEPAoptimiert Anweisungen, indem es auf Fehlerverläufe zurückblickt.
Instruction Tuning ist das richtige Mittel für schema-begrenzten Code, denn die Studie zu agierenden IaC-Systemen stellte fest, dass es sich gegenüber dem Einsatz von Demonstrationen durchsetzt – gerade weil Demonstrationen die Schema-Halluzinationen nicht beseitigen können. GEPA ist daher die geeignete Wahl, wie in optimizer/compile.py dargelegt:
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)
Die beiden Modelle übernehmen unterschiedliche Aufgaben. Das Task-Modell erstellt den Terraform-Code, der anschließend verwendet wird; das Reflexionsmodell analysiert Fehler und entwirft mögliche Anweisungen. Es wird etwa 40 Mal ausgeführt, im Vergleich zu rund 900 Ausführungen des Task-Modells, wodurch Investitionen in ein leistungsstärkeres Modell hier vertretbar sind.
Einige wichtige praktische Hinweise:
- Setzen Sie
num_threadsfest. Bei einer Ausführung wurde dies vergessen, wodurch die Kompilierung 69 Minuten dauerte; nachträgliche Messungen zeigten, dass der Rechner zu 94 Prozent der Zeit untätig war und auf nacheinander ausgeführte API-Aufrufe wartete. - „150 Metrikaufrufe“ bezeichnen 150 Agentenepisoden, was mit einem Tool-Loop etwa 900 LM-Aufrufe entspricht.
- Rufen Sie immer
save()nach der Kompilierung auf. Mitsave_program=Falsewird reines JSON ausgegeben: der optimierte Anweisungstext zusammen mit allen bootstrappten Beispielen. Dasselbe JSON bietet zudem eine Möglichkeit, DSPy ganz zu vermeiden, da etwa zwanzig Zeilen (optimizer/to_skill_md.py) es erneut in dieSKILL.md-Datei umwandeln, die vom Agenten verwendet wird. Während der Laufzeit wird DSPy niemals importiert. Der Optimierer ist lediglich ein Build-Schritt, und das von ihm erzeugte Ergebnis kommt in Form eines Diffs in einem Pull Request an, den eine Person lesen und ablehnen kann.
GEPA entdeckt auch reale Probleme. Bei einem schwächeren Modell gab der Agent immer wieder seinen Tool-Loop auf: Nach einem Aufruf von provider_schema hörte er auf, ohne etwas zu speichern. Dass dies der Fall war, wurde GEPA nie mitgeteilt. Es las die Fehlerprotokolle und fügte diese Erkenntnis in die optimierten Anweisungen ein: Man solle nicht einfach deshalb aufhören, nur weil eine Datei gespeichert wurde, da ein erfolgreicher Schreibvorgang lediglich beweist, dass der HCL korrekt parset wird, und validate_config ist notwendig, um Fehler im Provider-Schema sowie ungültige Referenzen aufzudecken. Das ist eine Reflexion über tatsächliche Ausführungsprotokolle, die Anleitungen liefert, die sonst Stunden der Fehlersuche erfordert hätten.
Der Workflow zahlt sich auch nach der Optimierung aus
Zwei weitere Komponenten desselben Systems nutzen die bereits vorhandene Metrik erneut.
Die Metrik kann auch zur Zeit der Inferenz ausgeführt werden. DSPys BestOfN und Refine umhüllen jeden Modul, führen es so oft wie N Mal aus und geben das erste Ergebnis zurück, dessen Belohnung einen Schwellenwert erreicht:
robust = dspy.Refine(module=program, N=3, reward_fn=verify_metric, threshold=1.0)
Das Muster sollte vertraut erscheinen, da der Validierungs- und Reparaturzyklus des Agenten dieselbe Idee mit dem Compiler als Belohnungsfunktion anwendet. Die Studien bezeichnen dies als die stärkste Methode überhaupt: In der IaC-Studie führte die iterative Reparatur mit Feedback des Verifiers dazu, dass Qwen 7B von 14,0 % auf 62,9 % kam – besser als jede andere versuchte Prompting-Technik. In jeglicher Form muss dem Modell zum Laufzeitzeitpunkt sein Verifier zur Verfügung gestellt werden.
Modellwechsel werden zu einer einzeiligen Änderung, und jetzt sind sie messbar:
program.set_lm(dspy.LM("openrouter/qwen/qwen3-coder-30b-a3b-instruct"))
evaluate(program)
Das war wichtiger, als erwartet. Ein Wechsel mitten im Projekt auf ein günstigeres Modell führte zu einer Qualitätsabnahme von 83,3 % auf 37,5 %, wobei fast die Hälfte der Durchläufe den Tool-Loop verließ und überhaupt keine Dateien erzeugte. In einem manuellen Arbeitsablauf wird das nicht sichtbar, und das erste Anzeichen käme von einem Kunden. Zwölf Minuten Laufzeit der Evaluierungs-Suite machten dies offensichtlich.
Das ehrliche Ergebnis
Bei diesem Agenten übertraf die kompilierte Anweisung die handgeschriebene nicht.
Der Vergleich wurde neunmal mit zwei Agentenimplementierungen und zwei Modellen durchgeführt, einschließlich eines separaten Testsets, bei dem beide denselben Wert von 92,9 % erzielten. Dieser Wert gilt für das leistungsstärkere Modell, während die zuvor genannten Werte von 41,7 % bis 69,4 % für das günstigere Modell in derselben Suite gelten. Zweimal schien ein Ergebnis eindeutig zu sein, wurde dokumentiert, änderte sich jedoch bei einer größeren Stichprobe.
Das ist das wertvollste Erkenntnisgewinn, und es zeigt nicht, dass DSPy versagt. Ein spezifischer und plausibler Grund kann erklären, warum dies nicht zutraf. Die veröffentlichten Verbesserungen von GEPA stammen aus modularen Arbeitsabläufen – Pipelines, deren Schritte jeweils einen anderen Prompt verwenden – sodass der Optimierer einen Fehler auf ein bestimmtes Modul schieben und es reparieren kann. Ein Agent verwendet hingegen bei jedem Schritt denselben Prompt. Benjamins Andersons Kritik besagt, dass dies gerade die Lokalität beseitigt, auf der GEPA beruht.
Die praktischen Richtlinien lauten wie folgt:
- Bei einem einfachen Pipeline-Workflow übertragen sich die veröffentlichten Verbesserungen wahrscheinlich.
- Bei einem Agent-Loop sollten Sie zunächst Ihren eigenen Testzyklus durchführen, bevor Sie davon ausgehen, dass die Verbesserungen gelten.
- In beiden Fällen sollten Sie die Rückmeldungen des Überprüfungstools direkt an den Agent weiterleiten; dafür gibt es die besten Belege in veröffentlichten Arbeiten sowie in diesen Zahlen.
Und dieser Test ist möglich, weil das entsprechende Testprogramm existiert.
Was bleibt davon übrig
Sprachmodelle haben Schwierigkeiten mit Infrastructure as Code. Der Wert von 19,36 % von GPT-4 bei IaC-Eval ist kein Problem, das durch optimistisches Denken beseitigt werden kann, und die üblichen Tricks helfen kaum. Dennoch entstehen die meisten Strategiefehler durch fehlende Informationen und nicht durch mangelnde Fähigkeiten, sodass tatsächlich Verbesserungsmöglichkeiten bestehen – und diese liegen in Anweisungen und Werkzeugen, nicht in den Gewichten.
Der Arbeitsablauf, der sich bewährt hat, ist jener, den die eigene Dokumentation von DSPy in der von ihr vorgegebenen Reihenfolge lehrt. Bei der Programmierung: Signaturen für den Vertrag, Module, die das Deployte widerspiegeln, sowie inspect_history, wenn Zweifel an dem Gesendeten bestehen. Bei der Bewertung: Beispiele mit expliziten Eingaben, eine einfache Metrik, die gleichzeitig als Feedback für den Optimierer dient, eine Baseline vor jeder Änderung sowie ein Testset, das nur einmal angefasst wird. Für die Optimierung, und nur dann, wird mit derselben Metrik Text gespeichert, der in einem Pull Request überprüft werden kann.
Auch wenn der Optimierer die manuell erstellte Methode nicht übertraf, zwang die Verwendung von DSPy dazu, die Definition von „richtig“ als ausführbaren Code festzuhalten. Dieses Konstrukt behielt weiterhin seine Bedeutung. Es zeigte nach einem Modulwechsel eine Rückgang von 46 Punkten an, machte deutlich, dass die Methode nur zu 25 Prozent der Zeit geladen wurde (was durch einen umgeschriebenen Satz behoben werden konnte, woraufhin die Ladequote 100 Prozent erreichte), und verhinderte zweimal, dass ein fehlerhaftes Ergebnis als Sieg veröffentlicht wurde.
Alles das erfordert eigentlich nicht unbedingt DSPy, und der Verifier hier importiert keines der entsprechenden Komponenten. DSPy kommt ins Spiel, indem es sicherstellt, dass bestimmte Metriken existieren und wiederverwendet werden – somit steuert eine einzige Definition den Regressionstest, die Modellvergleiche, die Wiederholungsversuche während der Inferenz sowie den Optimierer. Teams, die bereits Disziplin haben, benötigen möglicherweise dieses Framework nicht, während solche, die sie fehlen lassen, es dort erlernen können. Auf jeden Fall sollte man auf die Veröffentlichung von Prompt-Änderungen verzichten, die nicht gemessen werden können.
Verwandte Artikel
- Steuerung des Verhaltens des Bedrock-Agenten: Prompt-Phasen, Parser und Inferenz — Wie man die Vorverarbeitungs-, Orchestrierungs- und Nachverarbeitungsprompts von Bedrock in Terraform überschreiben kann, unter Verwendung eines Parser-Lambdas, eines JSON-Vertrags sowie Einstellungen für die Inferenz pro Phase.
- Form der Ontologie versus Modellgröße: Was das Design eines Graphen einen KI-Agenten kostet — Wie drei verlustfreie Formen eines Versicherungskenntnisgraphen die Genauigkeit, Toolaufrufe, Tokenanzahl sowie die Kosten für kleine und große Modelle in Azure Cosmos DB veränderten.