extension-inference
Original:🇺🇸 English
Translated
MANDATORY recipe for every Caffeine build that calls an LLM, chatbot, GPT, or ChatGPT **on Caffeine Inference** (no user-pasted OpenAI key). The ONLY supported path is the `caffeineai-inference-client` mops package with `Config.fromEnv<system>()`, which hands the canister a ready-to-use authenticated config — the app never asks for, stores, or returns a key. Hand-rolling `ic.http_request` to `inference.caffeine.ai` (or `api.openai.com`) is a FORBIDDEN anti-pattern. Load this skill whenever the user, spec, or any prior task wants an LLM in a Caffeine app — and BEFORE writing any code that talks to an LLM host. Use `extension-openai` only when the spec explicitly requires a user- or admin-pasted `sk-...` key against `api.openai.com`.
217installs
Sourcecaffeinelabs/skills
Added on
NPX Install
npx skill4agent add caffeinelabs/skills extension-inferenceTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Caffeine Inference
LLM extension for Caffeine AI.
Orchestrator routing notes
Treat “use an LLM / GPT / chatbot / summarise with AI” as a first-class
platform feature. The default path is Caffeine Inference: an
OpenAI-compatible chat endpoint that Caffeine hosts, authenticates, and bills
for the app. The canister gets its credentials from the platform at runtime;
nobody pastes an API key, and the app never stores or returns one.
| User intent | Capability |
|---|---|
| Chat / summarise / classify with an LLM in a Caffeine app | |
Call | |
Do not load for a normal Caffeine-app LLM. Do not
ask the user for an OpenAI API key. Do not add endpoints, a
key-settings page, or a model picker.
extension-openaisetApiKeyBackend
1. Add caffeineai-inference-client
to mops.toml
caffeineai-inference-clientmops.tomlbash
mops add caffeineai-inference-client@0.1.0Requires Mops ≥ 2.13. Minimum version: .
caffeineai-inference-client ≥ 0.1.02. Config comes from the platform
Config.fromEnv<system>()Configis_replicated = ?false- Call inside the
fromEnv<system>()method, or in ashared-parameterised helper, on every request. A module-level<system>will not compile, and a cachedlet config = fromEnvcan go stale when the platform rotates credentials on a running canister.Config - It traps when the app has no inference credentials. That is a platform condition, not something the app can fix — do not add a "configure AI" empty state or a key-input fallback for it.
- Never log the , never copy its
Configinto actor state, and never return it (or any part of it) from aauth/queryfunction.shared
3. is_replicated = ?false
is REQUIRED
is_replicated = ?falsefromEnv?truenull- Security. A replicated outcall sends the bearer from every replica.
- Billing. Replicated outcalls multiply inference spend by subnet size.
- Determinism. LLM bodies are sampled; consensus would fail.
4. Canonical layout
motoko
import Inference "lib/inference";
actor {
public shared func chat(prompt : Text) : async Text {
await* Inference.runChat<system>(prompt);
};
};motoko
import { fromEnv } "mo:caffeineai-inference-client/Config";
import ChatApi "mo:caffeineai-inference-client/Apis/ChatApi";
import ChatCompletionRequest "mo:caffeineai-inference-client/Models/ChatCompletionRequest";
import ChatCompletionRequestMessageOneOf2 "mo:caffeineai-inference-client/Models/ChatCompletionRequestMessageOneOf2";
import Runtime "mo:core/Runtime";
module {
public func runChat<system>(prompt : Text) : async* Text {
let config = fromEnv<system>();
let userMessage = ChatCompletionRequestMessageOneOf2.JSON.init({
content = #string(prompt);
role = #user;
});
let req = ChatCompletionRequest.JSON.init({
messages = [#user(userMessage)];
model = "router";
});
let resp = await* ChatApi.createChatCompletion(config, req);
if (resp.choices.size() == 0) {
Runtime.trap("Inference returned no choices");
};
resp.choices[0].message.content
?? Runtime.trap("Inference returned no text content");
};
};5. model = "router"
— the platform picks the model
model = "router""router""router"model- Always send .
model = "router" - Do not add a model dropdown, a "use GPT-4" toggle, or a parameter on the backend endpoint. There is nothing for the user to choose.
model - Steer quality with the prompt and with the declared sampling fields
(,
temperature,top_p), not with model selection.max_completion_tokens
6. Call shapes
- Function form: — use
ChatApi.createChatCompletion(config, req) : async*.await* - Suite form: .
let api = ChatApi(config); api.createChatCompletion(req) : async
7. Available API surface — chat completions
caffeineai-inference-client@0.1.0public-api-v0.1.0| Module | Entry point | Route |
|---|---|---|
| | |
| | |
motoko
import ChatApi "mo:caffeineai-inference-client/Apis/ChatApi";
import { fromEnv } "mo:caffeineai-inference-client/Config";Chat completions are the whole product surface. Not available on this host
(404, and not in the package): embeddings, images, audio, moderations, files,
legacy completions, Assistants, Responses, and raw . If the
spec genuinely needs an OpenAI-only API with a pasted , switch to
.
ic.http_requestsk-...extension-openai8. Cycles
defaultConfig.cycles = 30_000_000_000motoko
{ fromEnv<system>() with cycles = 100_000_000_000 }Streaming () is unsupported — management-canister HTTP returns
the full body. Leave .
stream = ?truestream = null9. Things that will bite you
- Call inside the
fromEnv<system>()method (or asharedhelper). A module-level<system>will not compile.let config = fromEnv - — not
model = "router". See §5."gpt-4o-mini" - User turns are .
#user(ChatCompletionRequestMessageOneOf2.JSON.init({ content = #string(prompt); role = #user })) - for required fields; layer optionals with record update. Do not hand-list every
JSON.init.null - is
resp.choices[0].message.content. Check?Textfirst.choices.size() - One chat call is one HTTP outcall inside an update call: budget seconds, not milliseconds.
Frontend
The app is ready to chat on first load — there is nothing to configure.
- No API-key UI. No settings page, no password input, no "configured?" indicator, no localStorage. If a spec or mock shows an "AI settings" screen, drop it.
- No model picker. See §5.
- Call the backend chat endpoint () and render the returned text. There is no frontend LLM SDK — the canister is the client, so the credentials never reach the browser.
chat(prompt) - Show a pending state while the call is in flight (an outcall round-trip takes seconds) and surface a retry on trap.