Post
How to Use the [ai …] Shortcode: A Practical Walkthrough
This is the hands-on companion to AI in Your Content. That
post argues why build-time AI beats a browser widget; this one gets you from
zero to a working [ai …] in about five minutes.
1. Get a key and keep it out of the repo
You need an endpoint that speaks OpenAI-style chat completions (most providers do) and an API key. Put the key in your environment, never in the config file:
1export OPENAI_KEY="sk-…" # locally
2# in CI: add OPENAI_KEY as a secret and export it in the job
2. Declare a model, then an agent
Think of it in two layers. A model is the endpoint — where to reach the
provider. An agent is a role built on a model — a persona with rules it
must follow and skills it's set up for. You call an agent by name and it carries
its whole role with it.
In your .ssg.yaml:
1ai:
2 default_agent: writer
3 cache_dir: .ai-cache
4 timeout: 30s
5 models: # endpoints — the connection
6 fast:
7 url: https://api.openai.com/v1/chat/completions
8 key: $OPENAI_KEY # ← reads the env var, never a literal
9 model: gpt-4o-mini
10 system: "Answer in one short, factual sentence. No preamble."
11 smart:
12 url: https://api.openai.com/v1/chat/completions
13 key: $OPENAI_KEY
14 model: gpt-4o
15 agents: # roles — built on a model
16 writer:
17 model: fast # runs on the fast model
18 system: "You are the site's copy editor."
19 rules: # guardrails this agent always obeys
20 - "Answer in the page's language."
21 - "Never invent facts or links."
22 skills: # what this agent is set up to do
23 - "Summarise long text into one sentence."
24 researcher:
25 model: smart # the same rules, a stronger model
26 rules: ["Never invent facts or links."]
27 skills: ["Explain a technical topic for a general reader."]
Two models is a common setup: a cheap fast one for summaries, a stronger one for
the occasional harder ask. Agents sit on top: define the role once — tone, rules,
skills — and every [ai agent="writer" …] inherits it. The model's system is
the house style; the agent's system, rules and skills layer on top. Change
any of them and the cache re-queries. Prefer agents; a bare model="fast" is
there for one-off asks that don't need a role.
3. Write your first shortcode
In any post's Markdown:
1## In one line
2
3[ai question="Summarise this post in one sentence for a busy reader."
4 fallback="_summary pending_"]
Build the site. The shortcode is replaced by the answer; the fallback shows only
if the query fails or the feature is off. We set default_agent: writer, so the
shortcode above uses that agent — you only name one when you want a different role:
1[ai agent="researcher" question="Explain WebP in one sentence for a non-expert."]
2[ai model="fast" question="Give me a raw one-liner, no persona."]
Precedence is simple: an explicit agent= wins, then an explicit model=, then
default_agent, then default_model — so with a default set you can drop both.
4. Gate it with ifs
You rarely want every page hitting the API. ifs runs the query only when a
condition over the page's own fields is true:
1[ai question="Write a 150-character meta description for this article."
2 ifs="type == post AND lang == en AND status == publish"
3 fallback=""]
The operators are ==, !=, contains, >, <, >=, <=, joined with AND
/ OR. The left side is any field: lang, status, type, category, tags,
title, or any custom frontmatter key and any site variable. False ⇒ the fallback,
no request.
5. Commit the cache (the important step)
graph LR
A[first build] --> B[query model once]
B --> C[.ai-cache/<hash>.txt]
D[every later build] --> C
C --> E[same answer, no network]
Answers are cached by a hash of the effective request — the model, the composed
prompt (model system + agent persona + rules + skills), the params, and the
question. Commit .ai-cache/ and the win is real: CI rebuilds the exact same
content with no key and no network — the answers are versioned next to your
words. You only re-query when one of those inputs changes. A rebuild after a typo
fix is free.
1git add .ai-cache && git commit -m "cache AI answers"
Recipes worth stealing
- One-line TL;DR at the top of long posts:
[ai question="Summarise in one sentence: {{ paste the intro }}"]. - Meta description, gated to published English posts (see step 4) — consistent across hundreds of pages.
- Plain-language gloss of a jargon paragraph:
[ai question="Rewrite this for a non-expert: …" ifs="category == deep-dive"]. - Draft alt text:
[ai question="One-sentence alt text for an image of …" fallback=""]— then a human checks it.
When something looks wrong
- You see the fallback everywhere. The feature isn't configured (no
ai.models), the key env var is unset, or the endpoint errored — check the build log for⚠️ ai query. - Answers won't refresh. That's the cache doing its job. Change the question,
or delete the relevant
.ai-cache/*.txt(or the whole dir) to re-query. - A page hits the API when it shouldn't. Tighten the
ifs; remember an emptyifsmeans "always".
Keep it to the small, repeatable chores — summaries, descriptions, glosses — commit the cache, and build-time AI stays cheap, reproducible, and invisible to your readers except as finished text.