Budgets & Control

How do I fail a CI build when a change makes AI calls more expensive?

Run your test suite through the Cognocient proxy with a run id, then call one endpoint that compares the run's cost with a baseline and fails the build on a regression. Base plan.

In the app: CI Cost Gate · Base plan and aboveOpen in app →No account? Start free

A prompt that doubles its context, a switch to a pricier model or a new retry loop usually shows up on the invoice weeks later. The cost gate catches it in the pull request.

How it works

  1. Your tests run with LLM calls going through Cognocient, each sending X-Cost-Run-Id: ci-<commit>.
  2. After the tests, CI calls POST /api/cost-gate/check with that run id.
  3. Cognocient totals the run's calls and compares them with a named baseline (default main).
  4. The response says whether the check passed, and why. The CI step fails if it did not.
  5. On your main branch, update_baseline: true makes a passing run the new baseline.

1. Send the run id from your app

import os
from openai import OpenAI
 
run_id = os.environ.get("COGNOCIENT_RUN_ID")   # only set in CI
client = OpenAI(
    base_url="https://api.cognocient.com/v1",
    api_key=os.environ["COGNOCIENT_KEY"],
    default_headers={"X-Cost-Run-Id": run_id, "X-Cost-Environment": "ci"} if run_id else None,
)

Adding X-Cost-Environment: ci also lets you see and budget CI spend separately.

2. Add the gate to your workflow

- name: Tests (LLM calls go through Cognocient)
  env:
    COGNOCIENT_KEY: ${{ secrets.COGNOCIENT_KEY }}
    COGNOCIENT_RUN_ID: ci-${{ github.sha }}-${{ github.run_attempt }}
  run: pytest
 
- name: Cognocient cost gate
  env:
    COGNOCIENT_KEY: ${{ secrets.COGNOCIENT_KEY }}
  run: |
    RESULT=$(curl -sS -X POST https://api.cognocient.com/api/cost-gate/check \
      -H "Authorization: Bearer $COGNOCIENT_KEY" -H "Content-Type: application/json" \
      -d '{
        "run_id": "ci-${{ github.sha }}-${{ github.run_attempt }}",
        "baseline": "main",
        "max_increase_pct": 10,
        "update_baseline": ${{ github.ref == 'refs/heads/main' }},
        "commit_sha": "${{ github.sha }}",
        "branch": "${{ github.head_ref || github.ref_name }}"
      }')
    echo "$RESULT" | jq -r '.reasons[]? // empty'
    echo "$RESULT" | jq -e '.passed == true' > /dev/null || { echo "$RESULT"; exit 1; }

Any CI system works the same way: it is one HTTP call.

Options

FieldDefaultMeaning
run_idrequiredThe run to check. Letters, digits and . _ : / -, up to 128 characters.
baselinemainName of the baseline to compare with.
max_increase_pct10Fail if cost rises by more than this percentage.
max_cost_usdnoneFail if the run costs more than this, baseline or not.
comparetotaltotal run cost, or per_call average (steadier when the number of tests changes).
max_feature_increase_pctnoneAlso fail if any single X-Cost-Feature rises by more than this.
min_calls1Fewer calls than this is reported as no_data and fails.
settle_seconds10Calls are logged just after each response; the check waits (0-30 s) until the run's call count stops changing.
update_baselinefalseIf the check passes (or no baseline exists yet), make this run the baseline.
commit_sha, branchnoneShown in the dashboard history.

Results

statuspassedMeaning
passtrueWithin every limit.
failfalseOver a limit. reasons says which.
no_baselinetrueNothing to compare with yet (unless max_cost_usd was exceeded).
no_datafalseThe run recorded fewer than min_calls calls. Usually a setup problem.

The response also includes the run and baseline totals, the change in percent and a per-feature breakdown. Control → CI Cost Gate shows the history, lets you inspect each check and accept any run as a baseline.

Runs older than 30 days are not considered. Baseline names use letters, digits and . _ -.

Other endpoints

  • GET /api/cost-gate/checks?limit=50: recent checks
  • GET /api/cost-gate/baselines: baselines
  • POST /api/cost-gate/baselines with {"name": "main", "run_id": "..."}: set a baseline from any run
  • DELETE /api/cost-gate/baselines/{name}

On this page