How the AI service works
You’re going to use a Large Language Model (LLM) called Claude, made by a company called Anthropic. An LLM is a program that’s read most of the internet and can write text in response to a prompt. We’re going to use it as a friendly explainer.
Here’s the path a button-tap takes:
browser tap
↓ POST /api/ai/explain { sensor_id }
API service (looks up the latest reading)
↓ POST /ai/explain { sensor_id, reading }
AI service (renders the prompt template)
↓ HTTPS
Anthropic Claude Hauiku (returns a short explanation)
↓ stores ai_prompts, ai_responses rows
↓ returns text + tokens + latency
browser shows it
If you don’t have an Anthropic API key, the AI service uses a stub — it returns a canned message about the reading instead of calling Claude. The whole pipeline still runs, prompts and responses still get logged. You can do the entire phase that way and add a real key later.
Pull and rebuild
The AI service has new code (Anthropic SDK, prompt template, persistence) and the API has a new dependency (httpx for forwarding). Rebuild both:
cd /opt/iot-platform
git pull
docker compose build api ai frontend
docker compose up -d
Watch the AI service come up:
docker compose logs -f ai
You should see a line that says loaded template explain-sensor-v1. If ANTHROPIC_API_KEY is empty in .env, you’ll also see a warning about using the offline stub. Both are fine for this step.
Try the offline stub
Make sure the test publisher is running on your laptop (Phase 3 still applies). Open the dashboard and tap a sensor card to open the chart modal. At the bottom you’ll see a new AI explanation section with an “Explain this reading” button.
If ANTHROPIC_API_KEY is empty, you’ll also see a yellow OFFLINE tag next to the section heading. That’s the stub mode.
Tap the button. Within a second you should see a canned message like:
AI explanations are disabled — no Anthropic API key configured.
Sample reading: temperature: 24.3 celsius (from microbit-test-01).
Below the message you’ll see echo-stub, a token count of 0 → 0, and a tiny latency. That’s the stub’s fingerprint — no real model was called.
Three reasons. Cost — you don’t pay to test the UI. Privacy — demo videos don’t leak data to a third party. And reliability — if Claude is down, the dashboard still works, it just shows a less interesting message.
Get an API key
The API key. You don’t need your own — the school account holds it, and you’ll be given a key that’s only for the IoT lab. Never put a personal API key on a shared Pi.
An API key is a long string of letters and numbers, like a very serious password. It identifies your school’s account when the AI service calls Claude. Examples:
sk-ant-api03-aBcDeF...long...string
Don’t commit it to git. Don’t paste it in chat. Don’t screenshot it. The .env file is gitignored on purpose — the key only lives there and inside the running container.
Switch to real Claude
SSH into the Pi and edit .env:
cd /opt/iot-platform
nano .env
Find the line ANTHROPIC_API_KEY= and paste your key after the equals sign. Save with Ctrl+O, Enter, then Ctrl+X.
Restart only the AI service so it picks up the new key:
docker compose up -d ai
Check the logs — the warning about “using offline stub” should be gone. You should instead see Anthropic ready (claude-haiku-4-5-20251001).
Back to the dashboard. Open a sensor card’s modal again. The yellow OFFLINE tag should now be gone. Tap Explain this reading.
This time the response should be something like:
The temperature from microbit-test-01 is 22.4 °C, which is a comfortable
classroom temperature. Nothing unusual — typical of a heated room on a
spring day.
Look at the small grey line below the text: claude-haiku-4-5-20251001 · 178 → 41 tok · 612 ms. That’s the model name, the input/output token count, and how long Claude took to respond. Your numbers will differ but the shape will match.
Each tap is a fresh call — you’ll get slightly different wording each time. That’s normal: LLMs are probabilistic. The meaning stays the same.
Look at the prompts
Every “Explain” tap writes two database rows: one in ai_prompts (what we asked) and one in ai_responses (what Claude said). Open sqlite3 on the Pi and have a look:
sqlite3 data/sqlite/platform.db <<'SQL'
.headers on
.mode column
.width 12 18 60
SELECT id, template_id, substr(rendered_text, 1, 60) AS rendered
FROM ai_prompts
ORDER BY created_at DESC
LIMIT 3;
SELECT substr(prompt_id, 1, 8) AS prompt,
provider,
model,
input_tokens, output_tokens, latency_ms,
status
FROM ai_responses
ORDER BY created_at DESC
LIMIT 3;
SQL
You’ll see the actual prompt that went to Claude (with your sensor’s values plugged in) and the metadata about each call. This is gold for debugging: if a response looks weird, you can read the exact prompt that produced it.
Only the user prompt is stored as rendered_text. The system prompt (the “You are a friendly science teacher…” part) is identical for every call — it’s fixed by the template at boot. Look at services/ai/app/prompts/explain-sensor-v1.md to see it.
Watch token usage
Claude charges per token (roughly: a word or a piece of one). The Settings page shows a rolling 7-day total so you can keep an eye on cost.
Go to Settings in the sidebar. The new AI usage section shows four cards: Calls, Successes, Input tokens, Output tokens. Tap a few sensor cards and tap “Explain” a few times, then refresh the Settings page (with the page reload icon, or by re-entering Settings). The numbers should grow.
You can also pull the same data straight from the API:
curl -s "http://pi-iot-XX.local/api/ai/usage?days=7" | python3 -m json.tool
Each Explain call costs ~180 input tokens and ~40 output tokens. At the time of writing, Haiku is ~£1 per million input tokens and ~£4 per million output tokens. So 1000 explain calls ≈ £0.34. Pocket money for a whole school term.
Final verification
Tick everything below before declaring Phase 6 done.