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 empty ifs means "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.