首页 / 文章 / 实用提示:您的 Terraform Agent 有一半时间可能是错误的

实用提示:您的 Terraform Agent 有一半时间可能是错误的

《实用笔记》操作指南:您的 Terraform Agent 有一半时间可能是错误的——适用于采用该模式的团队的合约、检查机制以及即插即用代码模块。

3334 词

以下内容为针对“你的 Terraform Agent 很可能有一半时间都在出错”这一问题的实用解决方案。重点在于契约、检查机制以及可直接插入的代码占位符,而非激励性表述。 在完成概览阶段时,首先明确契约内容:所需输入、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 将此阶段视为输入与经过验证的输出之间的契约。为相关文件命名,定义成功检查标准,并杜绝无声的半完成状态。

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

我们所设计的代理在被视为可度量的对象时表现最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。 在功能结果旁同时记录执行时间以及令牌或查询成本。提前了解成本情况,就能避免在从演示环境过渡到共享环境时出现意外费用。 保持图结构简洁且类型明确。嵌套的数据块会掩盖哪个节点修改了哪个字段的信息,且在中断后会导致恢复失败。

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
)

故障的实际表现形式

将“What故障”的实际表现视为可测量的界面来处理最为有效。在扩大范围之前,先记录一份核心日志、一个故障案例以及回滚说明。 将配置与应用程序代码分开。环境文件、密钥存储和功能标志应集中存放于一处,这样操作人员无需查看整个结构就能进行审计。 保持数据结构的层次简单且类型明确。嵌套的数据块会掩盖哪个节点修改了哪个字段的信息,还会导致在中断后无法继续处理。

  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"?
$ 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

框架之前的评分系统:Terraform自我评估

将“阶段前的A评分器”视为可度量的对象使用效果最佳。在扩大范围之前,先记录一个理想案例、一个失败案例以及回滚说明。 同时记录正常流程与恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的一部分,而非后续的优化工作。 保持图表状态简洁且类型明确。嵌套的数据块会掩盖哪个节点修改了哪个字段,还会在中断后导致流程无法继续。 将“阶段前的A评分器”视为可度量的对象使用效果最佳。在扩大范围之前,先记录一个理想案例、一个失败案例以及回滚说明。 把这一阶段视为输入与已验证输出之间的契约。为相关成果命名,明确成功标准,绝不允许出现无声的半完成状态。

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
}
$ cd eval/tasks && ./check_policies.sh
  ...
  ✓ vpc-subnet-firewall              good=0 bad=2

✅ 38/38 policies verified in both directions

采用DSPy

在采用 DSPy 的阶段,应在修改代码之前明确输入参数、该步骤的负责人以及退出标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。除了功能结果外,还需记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。对于会消耗资金或修改生产数据的操作,必须经过人工审批。编译时的配置并不等同于业务功能的完整性。

uv add dspy
uv sync

第一阶段:编程

在第一阶段编程时,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,以便操作人员无需查看整个系统结构即可进行审计。 对于涉及资金支出或修改生产数据的操作,必须经过人工审批。编译时的连接方式并不等同于业务功能的完整性。

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'."
    )
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
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)

第二阶段:评估

在第二阶段评估中,修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程与异常恢复路径。重试机制、人工审核环节以及错误处理都是产品不可或缺的部分,而非后续需要补充的内容。 对于涉及资金支出或修改生产数据的操作,必须经过人工审批。编译时的逻辑连接并不等同于业务功能的完整性。 在第二阶段评估中,修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 应将此阶段视为输入与验证后输出之间的契约。为相关成果命名,明确成功判定标准,绝不允许出现无声无息的半完成状态。

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
]
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
evaluate = dspy.Evaluate(devset=valset, metric=verify_metric,
                         num_threads=8, display_table=True)
evaluate(program)
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.

第三阶段:优化

在进入第三阶段的优化环节时,首先需明确相关要求:所需的输入参数、成功标志,以及部分失败时的处理方式。这份清单能确保后续的代码修改始终符合预期。 在记录功能结果的同时,还需标注执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。 在耗时较高的步骤之后设置检查点。当操作员重新尝试后续节点时,恢复流程不应再次收取相同的LLM调用费用。

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)

优化后工作流仍会持续产生费用

在处理“持续支付工作流”时,首先需写下相关契约:所需的输入参数、成功标志,以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持透明。 应将配置信息与应用程序代码分开存放。环境文件、密钥存储以及功能开关应集中于一处,这样操作人员无需查看整个流程图即可进行审计。 在成本较高的步骤之后设置检查点。当操作人员重新尝试后续节点时,恢复流程不应再次计费相同的大型语言模型调用。

robust = dspy.Refine(module=program, N=3, reward_fn=verify_metric, threshold=1.0)
program.set_lm(dspy.LM("openrouter/qwen/qwen3-coder-30b-a3b-instruct"))
evaluate(program)

真实的结果

在处理“诚实结果”阶段时,首先需写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改保持透明可追溯。 同时记录正常流程与异常恢复路径。重试机制、人工审核环节以及错误消息处理都是产品本身的组成部分,而非后续的优化工作。 在成本较高的步骤之后设置检查点。当操作员重新尝试某个节点时,恢复流程不应再次调用相同的大型语言模型。 在处理“诚实结果”阶段时,首先需写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改保持透明可追溯。 将此阶段视为输入与经过验证的输出之间的契约。为相关成果命名,明确成功判定标准,并拒绝默许部分完成的情况。

结论

将“结论阶段”视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一份优秀的测试结果、一个失败案例以及回滚说明。在功能结果旁同时记录执行时间以及令牌或查询成本。提前了解这些成本信息,就能避免在从演示环境过渡到共享环境时出现意外费用。

操作检查清单

在“操作检查清单阶段”,在修改代码之前需明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏的状态。

优先选择小型且可测试的单元,而非庞大的脚本。当某个步骤失败时,故障应能指向具体的责任主体,而非复杂的流程链。

对于会花费资金或修改生产数据的环节,必须经过人工审批。编译时的配置并不等同于业务上的完整性。

编写一份简短的操作手册:说明如何轮换密钥、如何清空队列、以及如何回滚上一次的处理结果。

将这一阶段视为输入数据与经过验证的输出数据之间的契约。为相关工件命名,明确成功标准,绝不允许出现悄无声息的半完成状态。

对于会花费资金或修改生产数据的环节,必须经过人工审批。编译时的配置并不等同于业务上的完整性。

在升级整个系统之前,先冻结现有版本,为关键流程保存一份标准参考记录,并确认好回滚步骤。共享环境需要设置速率限制、进行租户身份验证,同时要明确密钥轮换的责任人。与其追求花哨的一次性演示,不如注重扎实的可靠性。

c9e85f3f670f的批量处理说明:不要将提供者密钥放入代码仓库,为每个会话设置令牌使用上限,并将转录内容存储在评估测试用例的旁边,以便后续更换模型时仍能保持可比性。

在处理强化安全措施的第0阶段时,首先明确相关规范:所需输入、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合要求。 配置信息应与应用程序代码分开存放。环境文件、密钥存储及功能开关应集中于一个位置,这样操作人员无需查看整个系统结构即可进行审计。

强化安全措施细节0/880:针对该说明记录运行时间、错误类型及令牌消耗情况,然后依据固定的评估标准而非主观判断来决定是否保留该更改。

在将硬化措施视为可测量的表面时,第一阶段的效果最佳。在扩大范围之前,需记录一份理想的测试用例、一个故障案例以及回滚说明。 相比庞大的脚本,应优先选择小型且可测试的单元。当某个步骤出现故障时,故障原因应能指向单一责任主体,而非复杂的流程链。

硬化细节 1/880:需为该措施测量执行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

在强化措施的第2阶段,应在修改代码之前明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新执行该步骤,而无需猜测隐藏状态。应在功能结果旁记录执行时间以及代币或查询成本。提前了解这些成本,可避免在从演示环境过渡到共享环境时出现意外费用。

强化措施细节2/880:需测量该步骤的耗时、错误类型以及代币消耗情况,然后依据固定的评估标准而非主观判断来决定是否保留该更改。

在处理强化措施的第3阶段时,首先写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合要求。

同时记录正常流程和故障恢复流程。重试机制、人工审核环节以及错误处理方式都是产品本身的组成部分,而非后续才添加的完善措施。

强化措施细节3/880:需测量该环节的耗时、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该变更。

将强化措施的第4阶段视为可量化的处理流程最为有效。在扩大范围之前,先记录一份理想的操作示例、一个故障案例以及回滚说明。

应将此阶段视为输入参数与验证后输出结果之间的契约。为相关文档命名,明确成功判定标准,杜绝无声的半完成状态。

强化措施细节4/880:为该记录测量运行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

对于强化措施的第5阶段,在修改代码之前需明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。配置应置于应用程序代码之外,环境文件、密钥存储以及功能标志应集中存放于一个操作人员可以审核的位置,无需查看整个系统结构。

强化措施细节5/880:为该记录测量运行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

在处理强化措施的第6阶段时,首先写下相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合既定标准。 相比冗长的脚本,更应采用小型且可测试的单元。当某个步骤失败时,故障应能指向单一责任点,而非复杂的流程链。

强化措施细节6/880:需测量该步骤的运行时间、错误类型以及代币消耗情况,然后依据固定的评估标准而非主观判断来决定是否保留该修改。

将强化措施的第7阶段视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一份理想的运行示例、一个失败案例以及回滚说明。 在功能结果旁同时记录时间消耗及代币或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外支出。

强化措施细节7/880:为该记录测量运行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

对于强化措施的第8阶段,在修改代码之前需明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。同时需将正常流程与故障恢复流程一并记录下来。重试机制、人工审核环节以及错误处理方式都是产品本身的组成部分,而非后续需要补充的内容。

强化措施细节8/880:为该记录测量运行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

在处理强化措施的第9阶段时,首先写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合约定。 将这一阶段视为输入与验证后输出之间的契约。为相关产物命名,明确成功判定标准,杜绝默许的部分完成情况。

强化措施细节9/880:需测量该步骤的耗时、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该变更。

将强化措施的第10阶段视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一份标准操作流程、一个失败案例以及回滚说明。 应将配置信息与应用程序代码分开。环境文件、密钥存储和功能开关应集中存放于一处,以便操作人员无需查看全部代码结构即可进行审计。

强化措施细节 10/880:记录该任务的执行时间、错误类型以及代币消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该变更。

在强化措施的第11阶段,应在修改代码之前明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。相比复杂的脚本,更应采用小型且可测试的单元。当某一步骤失败时,故障原因应能明确指向某个具体责任方,而非整个混乱的流程。

强化措施细节 11/880:记录该任务的执行时间、错误类型以及代币消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该变更。

在处理强化措施的第12阶段时,首先写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。

在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在系统从演示环境过渡到共享环境时出现意外费用。

强化措施细节12/880:需测量该阶段的实际执行时间、错误类型以及令牌消耗情况,然后依据固定的评估标准而非主观判断来决定是否保留该修改。

将强化措施的第13阶段视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一个理想运行案例、一个失败案例以及回滚说明。

应同时记录正常流程和故障恢复流程。重试机制、人工审核环节以及错误处理方式都是产品本身的组成部分,而非后续需要补充的内容。

强化措施细节13/880:测量该任务的执行时间、错误类型以及代币消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

在强化措施的第14阶段,应在修改代码之前明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。应将此阶段视为输入参数与经过验证的输出结果之间的契约,为相关成果命名、定义成功检测标准,并拒绝默许的半完成状态。

强化措施细节14/880:测量该任务的执行时间、错误类型以及代币消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。