AI-powered Terraform cost preview and optimization advisor built with LangGraph
Project description
IaCCostAgent
AI-powered Terraform cost preview and optimization advisor — know why your infrastructure is expensive and what to do about it, before you deploy.
IaCCostAgent parses your Terraform (HCL or plan JSON), runs cost estimation via Infracost or OpenInfraQuote, detects cost anti-patterns with a deterministic rule engine, and uses an LLM to produce plain-English explanations, risk-rated optimization suggestions, and PR-ready summaries.
Unlike raw cost tools that show numbers, IaCCostAgent answers: "Why is this expensive, and what can I do about it?"
Demo
Both demos run the same command against the same fixture — iaccostagent analyze tests/fixtures/terraform/overprovisioned --backend infracost --region us-east-1 — differing only in whether the LLM layer is active.
With an LLM (OpenAI gpt-4o-mini)
The LLM produces a natural-language executive summary and refines each optimization with risk level + Terraform-specific implementation notes.
Without an LLM (--no-llm / air-gapped)
Same cost numbers, same patterns detected — but the summary and suggestions fall back to the deterministic rule engine. No LLM contacted.
Features
- Terraform-native: HCL files and Terraform plan JSON both supported
- Wraps best-in-class backends: Infracost (AWS, Azure, GCP) or OpenInfraQuote (AWS, fully local)
- Rule engine: Deterministic detection of oversized instances, old-generation types, gp2-vs-gp3, NAT-gateway sprawl, unattached EIPs
- LLM-powered advice: Plain-English explanations and risk-rated optimization suggestions
- Local-first: Runs entirely locally with Ollama — no API keys for LLM
- Cloud LLM support: Optional OpenAI integration
- Multiple outputs: Terminal (Rich), Markdown, JSON, GitHub PR comment
- CI/CD ready:
--max-cost+--fail-on-exceedfor cost gating - HTTP API: FastAPI server mode for dashboards and integrations
Quick Start
Install
pip install iaccostagent
Or with uv:
uv tool install iaccostagent
Install a cost backend
# Option A — Infracost (recommended, AWS/Azure/GCP)
brew install infracost
infracost auth login
# Option B — OpenInfraQuote (AWS-only, fully local)
brew install openinfraquote/tap/oiq
Install Ollama (for local LLM)
brew install ollama # macOS; Linux: curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen3:8b
Run
# Analyze a local Terraform directory
iaccostagent analyze ./infrastructure
# Analyze a remote git repository (cloned automatically, cleaned up after)
iaccostagent analyze https://github.com/user/repo.git
# Remote repo where Terraform lives under a subdirectory
iaccostagent analyze https://github.com/user/repo.git --subdir infrastructure
# Use OpenInfraQuote against a plan JSON
iaccostagent analyze ./plan.json --backend openinfraquote --region us-east-1
# Fast cost only (no LLM, no pattern engine) — just the Infracost report
iaccostagent estimate ./infrastructure
# Full pipeline WITHOUT the LLM — rule-based patterns + deterministic report
# (useful for air-gapped environments or when you just want reproducible output)
iaccostagent analyze ./infrastructure --no-llm
# Diff two configurations (local or git URLs)
iaccostagent diff ./infra-v1 ./infra-v2
# Diff two repos with the same Terraform layout on both sides
iaccostagent diff \
https://github.com/org/infra.git \
https://github.com/org/infra-next.git \
--subdir terraform
# Diff two repos with DIFFERENT layouts — use per-side subdirs
iaccostagent diff \
https://github.com/org/legacy.git \
https://github.com/org/modern.git \
--before-subdir legacy_infra \
--after-subdir terraform/prod
# CI/CD gating — fail if estimated monthly cost exceeds $5000
iaccostagent analyze ./infrastructure --max-cost 5000 --fail-on-exceed
# Cloud LLM instead of local Ollama
iaccostagent analyze ./infrastructure --llm openai/gpt-4o-mini
Commands at a Glance
| Command | What it does | LLM? | Cost backend? | Typical use |
|---|---|---|---|---|
analyze <path> |
Full pipeline: parse + cost + patterns + suggestions + summary | Yes (or --no-llm) |
Yes | Day-to-day PR review, pre-deploy check |
estimate <path> |
Raw cost breakdown from the backend | No | Yes | Quick "how much will this cost?" answer |
diff <before> <after> |
Compares cost between two configurations | No | Yes | Before/after a refactor, v1 vs v2 |
check-backend |
Verifies binary + API key; --verify does an end-to-end smoke run |
No | Optional | First run, CI health check |
serve |
Starts the FastAPI HTTP API | Optional | Yes (per request) | Team dashboards, integrations |
version |
Prints the installed version | No | No | Debugging |
All three of analyze / estimate / diff accept local paths and git URLs (https/ssh), with optional --subdir to drill into a Terraform subfolder. Git repos are shallow-cloned to a temp directory and deleted automatically when the command exits (even on error).
CLI Reference
iaccostagent analyze
Full LLM-powered pipeline: parse → estimate → detect → suggest → report.
Usage: iaccostagent analyze PATH [OPTIONS]
Arguments:
PATH Terraform dir, .tf file, plan JSON, or git URL
Options:
-b, --backend TEXT Cost backend: infracost | openinfraquote | aws-pricing |
azure-retail | gcp-catalog [default: infracost]
-r, --region TEXT Cloud region (e.g., us-east-1)
-l, --llm TEXT LLM provider/model (default: ollama/qwen3:8b)
--no-llm Skip the LLM entirely. Produces a rule-based report.
-f, --format TEXT Output: terminal, markdown, json, github-comment
-o, --output TEXT Write output to file instead of stdout
--input-format TEXT Force input format: hcl or plan-json
--subdir TEXT For git URLs, analyze this subdirectory of the cloned repo
--max-cost FLOAT Threshold for --fail-on-exceed
--fail-on-exceed Exit code 1 if monthly cost exceeds --max-cost
-v, --verbose Show detailed progress
iaccostagent estimate
Quick cost estimate only. No LLM, no pattern analysis. Accepts local paths and git URLs.
Two "no-LLM" modes
Sometimes you want the cost numbers without any model in the loop — for compliance, speed, or because Ollama/OpenAI isn't reachable. There are two options:
| Command | What you get | Use it when |
|---|---|---|
iaccostagent estimate |
Just the backend's cost breakdown table | You only want dollar numbers |
iaccostagent analyze --no-llm |
Cost + pattern detection + rule-based optimization suggestions + rule-based summary | You want the full report shape without a model |
If the LLM is configured but temporarily unreachable during a regular analyze run, the pipeline automatically falls back to the same rule-based path — so the report always arrives. --no-llm just makes that explicit and skips the failed LLM call.
iaccostagent diff
Compare estimated costs between two Terraform configurations.
iaccostagent check-backend
Verify the selected cost backend is installed and reachable.
# Binary + API key check
iaccostagent check-backend --backend infracost
# Full end-to-end verification: runs the backend on a tiny generated
# snippet and confirms a valid cost is returned.
iaccostagent check-backend --backend infracost --verify
analyze, estimate, and diff run the same pre-flight automatically, so missing binaries or API keys surface before the pipeline starts rather than mid-run.
iaccostagent serve
Start the FastAPI HTTP server. Requires the [server] extras — install with either:
pip install 'iaccostagent[server]'
# or
uv tool install 'iaccostagent[server]'
The single quotes stop bash/zsh from expanding [...] as a glob.
Supported Backends
Five backends ship with the tool; custom backends plug in via the backend registry.
| Backend | Providers | Coverage | Recommended for | Dependencies |
|---|---|---|---|---|
infracost |
AWS, Azure, GCP | Full — 1,100+ resource types | Production cost analysis | Infracost CLI + free API key |
openinfraquote |
AWS | Full AWS coverage | Air-gapped AWS environments | OIQ CLI + pricesheet CSV |
aws-pricing |
AWS | Reference: aws_instance, aws_ebs_volume, aws_nat_gateway, aws_db_instance |
Reference / extension template | None — uses public Price List JSON |
azure-retail |
Azure | Reference: Linux/Windows VMs, managed disks | Reference / extension template | None — uses public Retail Prices API |
gcp-catalog |
GCP | Reference: google_compute_instance |
Reference / extension template | GOOGLE_API_KEY with Cloud Billing API |
Which backend should I use?
For production cost analysis, use
--backend infracost. It has the broadest coverage, handles usage-based estimation, and is actively maintained. Our live parity tests confirm the native backends match Infracost to 0–5% per resource for the types they cover, but Infracost covers 100x more resource types.
The three native backends (aws-pricing, azure-retail, gcp-catalog) are reference implementations shipped to demonstrate the extension pattern described in docs/adding-backends.md. They query the cloud providers' own public pricing APIs directly — the data is authoritative, but the coverage is a deliberate starter subset. Anyone running the CLI with one of these backends sees an explicit warning to that effect.
Use a native backend when:
- You're writing your own backend and want a working template to copy.
- You need zero external service dependencies (no Infracost CLI, no paid API) for a narrow workload — e.g. a simple EC2+RDS+NAT-only shop.
- You want to extend pricing coverage for resources Infracost doesn't handle, or for a provider Infracost doesn't support yet.
Do not use a native backend when:
- You need accurate totals across a realistic mixed-service Terraform project. S3, CloudFront, Lambda, EKS, DynamoDB, App Service, Container Apps, Cloud Run, etc. aren't modeled.
- Your Terraform uses variable interpolation for pricing-relevant attributes (
instance_type = var.size). Only Infracost runs Terraform's HCL evaluator. - Anyone downstream will make billing or budget decisions from the numbers.
Supported Cloud Resources (pattern engine)
The deterministic rule engine flags cost anti-patterns across all three clouds:
| Cloud | Rules shipped |
|---|---|
| AWS | Oversized EC2, older-gen EC2/RDS, gp2→gp3 EBS, unattached EIPs, ≥3 NAT Gateways |
| Azure | Oversized VMs, older-gen VMs (pre-v2), Standard_LRS disks, deprecated PostgreSQL/MySQL single-servers, Standard static public IPs, ≥3 NAT Gateways |
| GCP | Oversized compute instances, older-gen (n1-/g1-/f1-), pd-standard disks, unassigned static IPs, ≥3 Cloud NATs |
See docs/writing-patterns.md for how to add your own.
Adding a Backend
Subclass CostBackend and decorate with @register_backend("my-cloud"):
from iaccostagent.backends.base import CostBackend
from iaccostagent.backends.registry import register_backend
@register_backend("my-cloud")
class MyCloudBackend(CostBackend):
name = "my-cloud"
def is_available(self) -> bool: ...
async def estimate(self, terraform_path: str, region: str | None = None): ...
Once imported, iaccostagent analyze ./tf --backend my-cloud routes to it. See docs/adding-backends.md for full walkthroughs including the three reference native backends.
Architecture
Six-node LangGraph pipeline:
- parse_terraform — HCL or plan JSON → list of TerraformResources
- estimate_costs — Shell out to the chosen backend, normalize to CostEstimate
- analyze_resources — Merge parsed resource attributes with cost data
- detect_patterns — Deterministic rule engine → CostPatterns with dollar estimates
- suggest_optimizations — LLM enriches patterns into risk-rated suggestions (falls back to rule-based output if LLM fails)
- generate_report — LLM writes the executive summary and assembles the report
If no resources are parsed (empty project, unsupported provider), the graph short-circuits straight to generate_report. Built on LangGraph with a Typer CLI and optional FastAPI server.
Data Handling & Privacy
Your Terraform source files are never uploaded by any backend. Here's the precise data-egress picture so you can evaluate this tool for regulated or compliance-sensitive environments.
What each backend sends over the network
| Backend | Where parsing happens | What leaves your machine | Who receives it |
|---|---|---|---|
infracost |
Locally (Infracost's own HCL evaluator) | Per-resource pricing attributes only (instance_type, region, engine, etc.) — not source files, names, tags, or variables |
pricing.api.infracost.io (or your self-hosted CPAPI) |
openinfraquote |
Locally (OIQ's CLI) | Nothing — pricing comes from a CSV on disk | No one |
aws-pricing |
Locally (our HCLParser) |
Nothing — only pulls AWS's public price-list JSON into your machine | pricing.us-east-1.amazonaws.com (download, no data uploaded) |
azure-retail |
Locally (our HCLParser) |
Per-resource query (skuName, armRegionName, serviceName) |
prices.azure.com (public Retail Prices API) |
gcp-catalog |
Locally (our HCLParser) |
Per-resource query + your GOOGLE_API_KEY |
cloudbilling.googleapis.com |
What Infracost does NOT send — the .tf source text, resource names/addresses, tags, variable values (unless a var value is itself a pricing attribute like instance_type), comments, locals, outputs, secrets embedded in the code, private IPs, or AMI IDs beyond what's needed for pricing. All of this is documented publicly in Infracost's FAQ:
- Security and privacy overview
- What data is sent to the Cloud Pricing API
- Does the CLI send the Terraform plan to the Cloud Pricing API?
- Does Infracost need cloud credentials?
- How to allowlist Infracost IP addresses
What the LLM sees (distinct from the cost backend)
When you run analyze (not estimate), the two LLM nodes send a summary of the parsed resources + cost numbers to whichever LLM provider you configured:
- Local LLM (Ollama): stays on your machine / your network.
- Cloud LLM (OpenAI): prompt payload is sent to OpenAI per their data policy.
What's in the LLM prompt: resource addresses, resource types, instance types, sizes, monthly costs, detected anti-patterns. What's not in the prompt: your .tf source files, full tag values, variable values, secrets in comments.
Three ways to skip the LLM entirely: iaccostagent estimate, or analyze --no-llm, or configure a local Ollama model.
Fully air-gapped recipes
Ordered by how much infrastructure you already have:
| Situation | Recipe | Egress |
|---|---|---|
| Just want to try it | --backend openinfraquote --no-llm (AWS only, needs pricesheet CSV) |
None |
| AWS-only, enterprise-proxy-friendly | Same as above | None |
| Multi-cloud, trust Infracost | --backend infracost --llm ollama/qwen3:8b (local LLM) |
Pricing attrs → Infracost only |
| Multi-cloud, no external egress allowed | Self-host Infracost CPAPI + Ollama (see below) | None |
Self-hosting Infracost's Cloud Pricing API
For teams that want Infracost's multi-cloud coverage but zero data leaving their network:
- Contact Infracost for self-hosted access. The self-hosted Cloud Pricing API (CPAPI) isn't a public download — it's currently distributed under Infracost's commercial / enterprise tier. Reach out via the links in the Infracost FAQ: Does Infracost offer a self-hosted option? (search the page for "self-hosted") or email
hello@infracost.io. - Deploy the CPAPI container they provide inside your network. It's a small API service backed by Postgres.
- Load the initial pricing dump (Infracost provides one, accessed with your API key).
- Point iaccostagent at it:
export INFRACOST_PRICING_API_ENDPOINT=https://internal-cpapi.corp.example.com iaccostagent analyze ./tf --backend infracost --llm ollama/qwen3:8b
This environment variable is read by Infracost's own CLI and redirects all pricing lookups to your internal endpoint. Zero external network calls once Ollama is the LLM.
Keeping the pricing data fresh: cloud prices change — AWS drops EC2 prices ~twice a year, Azure adds SKUs, GCP launches regions. Infracost publishes refreshed pricing data on a regular cadence. The usual operational model:
- Deploy the CPAPI container once (or update on CPAPI bugfix releases).
- Set up a nightly or weekly cron that pulls the latest pricing dump from Infracost and reloads Postgres — Infracost documents the update procedure for self-hosted customers. The update is fast (minutes) and non-disruptive.
- The container image itself rarely changes; only the data refreshes.
Rough operational effort once the cron is in place: near-zero monthly.
Note on verification: the self-hosted CPAPI distribution URL has moved more than once over the years. Rather than hard-code a potentially stale link, we recommend reaching out to Infracost directly — they'll give you the current install docs, the correct container registry, and the update cadence tied to your contract.
Opt out of Infracost telemetry
Even when using Infracost's hosted pricing API, you can disable their anonymous usage telemetry:
export INFRACOST_SELF_HOSTED_TELEMETRY=false
export INFRACOST_DISABLE_REPORT_ALL=true
(Put these in your .env if you're using our tool regularly.)
Configuration
Environment Variables
| Variable | Description | Default |
|---|---|---|
IACCOSTAGENT_LLM_PROVIDER |
LLM provider | ollama |
IACCOSTAGENT_LLM_MODEL |
LLM model name | qwen3:8b |
OLLAMA_BASE_URL |
Ollama server URL (for remote Ollama) | http://localhost:11434 |
OPENAI_API_KEY |
OpenAI API key (if using OpenAI) | - |
INFRACOST_API_KEY |
Infracost API key (required for Infracost backend) | - |
INFRACOST_PRICING_API_ENDPOINT |
Self-hosted Infracost CPAPI endpoint (for air-gapped use) | - |
OIQ_PRICESHEET |
Path to OpenInfraQuote pricesheet CSV | - |
GOOGLE_API_KEY |
GCP API key with Cloud Billing API enabled (for gcp-catalog backend) |
- |
IACCOSTAGENT_DEFAULT_REGION |
Default cloud region | - |
IACCOSTAGENT_CACHE_DIR |
Disk cache directory for native backends | ~/.cache/iaccostagent |
LANGSMITH_TRACING |
Enable LangSmith tracing | - |
LANGSMITH_API_KEY |
LangSmith API key | - |
LANGSMITH_PROJECT |
LangSmith project name | - |
CI/CD Integration
name: Infrastructure Cost Review
on:
pull_request:
paths: ['infrastructure/**']
jobs:
cost-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: infracost/actions/setup@v3
with:
api-key: ${{ secrets.INFRACOST_API_KEY }}
- run: pip install iaccostagent
- run: |
iaccostagent analyze ./infrastructure \
--backend infracost \
--llm openai/gpt-4o-mini \
--format github-comment \
--output cost-comment.md \
--max-cost 5000 \
--fail-on-exceed
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
body-path: cost-comment.md
See docs/ci-cd-integration.md for more examples.
HTTP API
iaccostagent serve --port 8888
# Full analysis
curl -X POST http://localhost:8888/api/v1/analyze \
-H "Content-Type: application/json" \
-d '{"project_path": "/path/to/tf", "backend": "infracost"}'
# Fast estimate only
curl -X POST http://localhost:8888/api/v1/estimate \
-H "Content-Type: application/json" \
-d '{"project_path": "/path/to/tf"}'
# Health
curl http://localhost:8888/api/v1/health
Development
git clone https://github.com/chaubes/iaccostagent.git
cd iaccostagent
uv sync --all-extras
make test-unit # Fast, no network, no subprocess
make test-integration # Needs infracost / oiq on PATH
make test-agent # Graph compile; full pipeline needs Ollama
make lint && make typecheck
make run ARGS="analyze tests/fixtures/terraform/simple_web_app"
See CONTRIBUTING.md for the full guide.
License
MIT License. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file iaccostagent-0.1.1.tar.gz.
File metadata
- Download URL: iaccostagent-0.1.1.tar.gz
- Upload date:
- Size: 239.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e33d57596ab8cadd02fe1a9abb8b22412dd6e1c718175c6e02323f85a6a70dbc
|
|
| MD5 |
b302bf127f4836a441ea1a4c831df581
|
|
| BLAKE2b-256 |
153c139b54a7ba586a574cd1b3b477de668fd14e1bbee6c12776f22f2812692b
|
Provenance
The following attestation bundles were made for iaccostagent-0.1.1.tar.gz:
Publisher:
release.yml on chaubes/iaccostagent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
iaccostagent-0.1.1.tar.gz -
Subject digest:
e33d57596ab8cadd02fe1a9abb8b22412dd6e1c718175c6e02323f85a6a70dbc - Sigstore transparency entry: 1337330611
- Sigstore integration time:
-
Permalink:
chaubes/iaccostagent@f43835af9f0d0ddd4f58479935493c90c5b515a9 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/chaubes
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f43835af9f0d0ddd4f58479935493c90c5b515a9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file iaccostagent-0.1.1-py3-none-any.whl.
File metadata
- Download URL: iaccostagent-0.1.1-py3-none-any.whl
- Upload date:
- Size: 68.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ede388d66b23d8aab06976f6de00fc695e1c6dbf7d7db02f96f0c61e5510aeea
|
|
| MD5 |
02a8571e9ab3abca9f3f72e412e0db58
|
|
| BLAKE2b-256 |
c1cab4efa2ea570520225c1b3c14964661b99ce3d6bfdeb3cc8d56fc36a84981
|
Provenance
The following attestation bundles were made for iaccostagent-0.1.1-py3-none-any.whl:
Publisher:
release.yml on chaubes/iaccostagent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
iaccostagent-0.1.1-py3-none-any.whl -
Subject digest:
ede388d66b23d8aab06976f6de00fc695e1c6dbf7d7db02f96f0c61e5510aeea - Sigstore transparency entry: 1337330780
- Sigstore integration time:
-
Permalink:
chaubes/iaccostagent@f43835af9f0d0ddd4f58479935493c90c5b515a9 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/chaubes
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f43835af9f0d0ddd4f58479935493c90c5b515a9 -
Trigger Event:
push
-
Statement type: