DEV Community

Jordan Huang
Jordan Huang

Posted on

Give a Free Model a Latency Budget Before You Let It Into CI

I keep hitting the same failure pattern. A merge request passes every test. Then the pipeline stalls. Nothing in the diff changed. The free model route I call for a review is just having a slow minute.

Same prompt. Same payload. Wildly different wall-clock time.

That is not a model problem. It is an unbounded dependency problem.

CI treats unknown latency as zero latency

Most CI examples wire a model call straight into a job.

review:
  script:
    - python review_with_model.py
Enter fullscreen mode Exit fullscreen mode

Then someone sets a generous timeout and walks away.

Why is that risky?

  • A free route can be fast at 10:00 and slow at 16:00.
  • One slow call can hold a runner for minutes.
  • Retrying the job doubles the cost and still may not fix anything.

Before I let a model route into .gitlab-ci.yml, I make it earn a spot.

Measure before you integrate

I run a small probe outside the pipeline. I use MonkeyCode's free model access and free server option to call the route on a schedule without spending my CI minutes.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The probe sends the same tiny payload several times and records the response time.

#!/usr/bin/env python3
import os, time, json, urllib.request

URL = os.environ['MODEL_URL']
TOKEN = os.environ.get('MODEL_TOKEN')
RUNS = int(os.environ.get('RUNS', '10'))
TIMEOUT = float(os.environ.get('TIMEOUT', '20'))

PAYLOAD = {
    'messages': [{
        'role': 'user',
        'content': 'Summarize this diff in one JSON object with keys: summary, risk, needs_attention.'
    }],
    'max_tokens': 64
}

def call_once():
    body = json.dumps(PAYLOAD).encode()
    headers = {'Content-Type': 'application/json'}
    req = urllib.request.Request(URL, data=body, headers=headers)
    if TOKEN:
        req.add_header('Authorization', 'Bearer ' + TOKEN)
    t0 = time.perf_counter()
    with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
        r.read()
    return time.perf_counter() - t0

call_once()  # warmup
times = sorted(call_once() for _ in range(RUNS))
p50 = times[len(times) // 2]
p95 = times[int(len(times) * 0.95)]
print(json.dumps({
    'runs': RUNS,
    'p50_s': round(p50, 3),
    'p95_s': round(p95, 3),
    'raw': [round(t, 3) for t in times]
}))
Enter fullscreen mode Exit fullscreen mode

Run it a few times at different hours. One run is not a decision.

The latency budget table

I use three buckets.

p95 timeout rate action
under 3s 0% Safe for a non-blocking review job.
3s to 8s under 10% Use only with a tight timeout and allow_failure: true.
over 8s over 10% Do not wire into CI. Run manually.

A free model route can move between buckets in the same day. That is fine. The probe makes the movement visible.

Feed the result into GitLab CI

I keep the probe output as a JSON artifact. The CI job reads it and decides whether to call the model at all.

model_probe:
  stage: precheck
  script:
    - python ops/model_probe.py > model_probe.json
  artifacts:
    paths:
      - model_probe.json
  allow_failure: true

use_free_review:
  stage: test
  needs:
    - model_probe
  script:
    - if python -c "import json; exit(0 if json.load(open('model_probe.json'))['p95_s'] > 8 else 1)"; then echo 'skip free review'; exit 0; fi
    - python review_with_model.py
Enter fullscreen mode Exit fullscreen mode

Notice what changed. The slow route can no longer stall a build silently. If p95 is over budget, the job skips the model and moves on.

Where this breaks

  • Ten requests give a noisy p95. Treat it as a smoke signal, not a benchmark.
  • Free routes share capacity. Measure during the hours you actually run CI.
  • Cold starts can show up as latency. Check whether the first call is always the worst.
  • Latency is not quality. A fast model can still produce bad JSON.

Who should skip this

Do not use this as your only gate if:

  • You need a hard SLO on review time.
  • Your pipeline handles regulated or high-risk changes.
  • You call the model in a large concurrent fan-out.

In those cases, use a dedicated endpoint and run a proper load test. A free server is great for experiments. It is not a substitute for capacity planning.

If you already have a free server option, run the probe for a day before you add another CI dependency. The numbers are boring. That is exactly what CI needs.

Top comments (0)