Pipelines That Call Models
Review (gl2) reads; this chapter is about pipelines that produce — a job that calls a model to generate a changelog, release notes, a test, or a summary and commits or attaches it. You'll build a generation job in both Claude and OpenAI, and learn the key decision: GitLab Duo's built-in AI versus your own API-calling jobs.
Learning objectives
- Build a CI job that calls a model to generate an artifact.
- Commit generated output back to the repo or attach it to a release.
- Choose between GitLab Duo and self-rolled API jobs.
- Keep generation jobs cheap and scoped (the cheap-model side of routing).
1 · The generation job essential
The mirror image of review: instead of reading changes and commenting, the job creates something and keeps it. The canonical example is a changelog — on every release, summarize the commits since the last tag into human-readable notes. The same shape covers generating release notes, drafting a test for a new function, summarizing a long MR, or writing a migration guide. Each is "call a model, capture its output, do something with it."
The crucial difference from review is what happens to the output. A review becomes a comment; a generated artifact gets committed back to the repo, attached to a release, or written to a file another job consumes. That "write back" is the new move, and it's where you must be careful — a job with the credentials to push commits is a job you secure tightly (gl5).
The common mistake is reaching for the biggest, most expensive model for generation out of habit. Summarizing commits or drafting boilerplate is easy work — the opposite of review. This is where a cheap/fast model shines, and getting that split right (strong model for review, cheap for generation) is the heart of model routing in gl4.
- The blue box is the input the job gathers — here, commit messages since the last release tag.
- The middle box is the model call — a cheap/fast model, because summarizing is easy work.
- The green box is the new move vs review: the output is written back (committed, or attached to a release), not just shown.
In short: review comments; generation commits. The write-back is the power and the risk — secure the token (gl5).
2 · A changelog generator intermediate
Here's the generation call in both SDKs: feed it the commit log, get back formatted notes. Generation is easy work, so this uses a cheaper model and low effort — the deliberate opposite of the review call in gl2.
The generation call — in Claude or OpenAI
Same prompt, same commit input — only the SDK call differs. Note the deliberate choice of a smaller/cheaper model and (OpenAI) low reasoning effort: summarizing commits doesn't need the reasoning muscle review does. Toggle the tab to switch SDK.
changelog.pyimport subprocess
from anthropic import Anthropic
client = Anthropic()
commits = subprocess.check_output(
["git", "log", "--pretty=%s", "HEAD...$(git describe --tags --abbrev=0)"],
text=True)
resp = client.messages.create(
model="claude-haiku-4-5", max_tokens=800, # cheap model — easy work
system="Turn these commit subjects into grouped markdown release notes "
"(Features / Fixes / Other). Terse, user-facing.",
messages=[{"role":"user","content": commits}],
)
notes = next(b.text for b in resp.content if b.type=="text")
open("CHANGELOG_NEW.md", "w").write(notes)
changelog.pyimport subprocess
from openai import OpenAI
client = OpenAI()
commits = subprocess.check_output(
["git", "log", "--pretty=%s", "HEAD...$(git describe --tags --abbrev=0)"],
text=True)
resp = client.responses.create(
model="gpt-5.5", max_output_tokens=800,
reasoning={"effort": "low"}, # easy work — don't overpay
instructions="Turn these commit subjects into grouped markdown release notes "
"(Features / Fixes / Other). Terse, user-facing.",
input=commits,
)
notes = resp.output_text
open("CHANGELOG_NEW.md", "w").write(notes)
git log --pretty=%sgathers commit subjects since the last tag — the raw material, captured in the job script.- The model groups them into user-facing notes. Note the cheap model and low effort — the deliberate opposite of gl2's review call, because summarizing is easy.
- The output is written to a file (
CHANGELOG_NEW.md) — which the next pipeline step commits back or attaches to the release.
Try this: swap the task to "draft a pytest test for the changed function" — same job shape, different prompt. Generation jobs are a template you refill.
3 · Committing the output back intermediate
A generated file is useless if it stays on the runner. The job commits it back (or attaches it to a GitLab release). Committing from CI needs a token with write access and a guard against infinite loops — a commit from CI can trigger another pipeline.
.gitlab-ci.ymlgenerate-changelog:
stage: build
rules:
- if: '$CI_COMMIT_TAG' # only on release tags
script:
- pip install anthropic openai
- python changelog.py
- git add CHANGELOG_NEW.md
- git commit -m "docs: changelog for $CI_COMMIT_TAG [skip ci]" # [skip ci] stops a loop
- git push "https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" HEAD:main
[skip ci] marker in the commit message tells GitLab not to run a pipeline for that commit. Forgetting it is the classic self-inflicted outage of CI automation.4 · GitLab Duo vs self-rolled jobs advanced
You don't always have to write the job. GitLab ships GitLab Duo — a suite of built-in AI features (code suggestions in the IDE, chat, merge-request summaries, and more) that run as a managed product. For some of what this track builds by hand, Duo offers a turnkey equivalent. So the real question is build vs buy.
Reach for GitLab Duo when its built-in feature already does what you need — it's maintained for you, integrated into the UI, and requires no API keys or job code. Write a self-rolled API job (what this track teaches) when you need: a specific model (Duo chooses for you), custom logic (your review rubric, your output format), multi-vendor routing or fallback (gl4), or a task Duo simply doesn't offer. The two coexist — Duo for the common path, your jobs for control.
| Use… | When |
|---|---|
| GitLab Duo (built-in) | a turnkey feature fits — code suggestions, chat, MR summaries; no keys/code to maintain |
| Self-rolled API job | you need a specific model, custom logic/output, multi-vendor routing/fallback, or a task Duo lacks |
5 · Tech-lead — generation guardrails tech-lead
Generation jobs write to your repo, so they carry more risk than review jobs that only comment. The lead-level guardrails: give the CI token the minimum scope needed (write to one repo, not your whole group), always [skip ci] on bot commits, route generation to a cheap model (it's easy work — paying flagship rates here is pure waste), and treat anything the model commits as a draft a human can review, not gospel. For irreversible or published artifacts (a release note that goes to customers), keep a human approval step before the output ships. Build-vs-buy is also a standing decision: adopt Duo where it fits to reduce what you maintain, and reserve self-rolled jobs for where control genuinely pays.
🪜 Practice ladder beginner → industry
- Beginner: run
changelog.pylocally against a repo's commit log. - Easy: switch the generator from Claude to OpenAI via the tabs and compare output.
- Core: wire the tag-triggered job (Lab G3.2) with
[skip ci]on a scratch repo. - Stretch: repoint the job to generate a pytest test for a changed function instead.
- Hard: attach the generated notes to a GitLab Release via the Releases API instead of committing.
- Industry: write the build-vs-buy decision for three AI features your team wants (Duo vs self-rolled), with reasons.
✓ Checkpoint — you can move on when you can…
- Build a CI job that calls a model to generate an artifact.
- Commit the output back with
[skip ci]to avoid a loop. - Choose GitLab Duo vs a self-rolled API job for a need.
- Route generation to a cheap model and secure the write token.
Knowledge check check yourself
Why does a generation job typically use a cheaper model than a review job, and why must a CI commit include [skip ci]?
Show answer
[skip ci] because a job that pushes a commit would otherwise trigger a new pipeline, which pushes another commit — an infinite loop that burns runner minutes; [skip ci] tells GitLab not to run a pipeline for that commit.When should you use GitLab Duo versus a self-rolled API job?