Consume Recommendations¶
Read recommendations and search from the AI Engine API. Point the UI at the API base (the same
tunnel host, or http://localhost:8000 locally).
Recommendations¶
const res = await fetch(`${API}/api/recommend?user_id=${userId}&limit=10`);
const { result } = await res.json();
// result.strategy is "warm" or "cold"; result.filter echoes the filter you sent
result.items.forEach(it => {
render({
id: it.id,
title: it.content?.title,
score: it.relevance_score,
why: it.breakdown // {semantic: 0.41, tag: 0.33} -> explanation
});
});
Response shape (the payload is wrapped in result):
{
"result": {
"user_id": "u1",
"strategy": "warm",
"filter": null,
"request_id": "9f1c...",
"items": [
{ "id": "842", "rank": 1, "relevance_score": 0.74, "role": "target",
"breakdown": { "semantic": 0.41, "tag": 0.33 },
"content": { "id": "842", "title": "...", "content_type": "text_item" } }
],
"diagnostics": {}
}
}
Each item carries a breakdown you can surface as a "why this" cue (see
Explainability) and a role (target or distractor).
Echo the request_id (closes the learning loop)
The response carries a request_id. Keep it with the items you render, and echo it on
the resulting CONTENT_VIEW_ENDED event (properties.details.request_id) when the visitor
finishes a recommended item. That join lets the bandit's reward find the exact served
context (the feature vector it scored). Without the echo, served impressions and outcomes
cannot be joined and the learned ranker gets no signal. See
Send Events and
Bandit / online learning.
X-Tenant-Id
On a multi-tenant deployment, send X-Tenant-Id: <id> on every request (and event).
user_id and content_id only resolve within a tenant. No header means the default
tenant. See Multi-tenancy.
If the deployment locks reads (SERVING_REQUIRES_KEY=1), send the tenant's
X-API-Key instead: the tenant is derived from the key, so X-Tenant-Id isn't
needed. See Authentication.
Query parameters¶
| Param | Purpose |
|---|---|
user_id |
required, the visitor id |
limit |
cap the number of items (1 to 50) |
filter |
restrict candidates to a single tag, for example a location: filter=AiARLocationBarrack3 |
near_lat / near_lon |
the visitor's current GPS; re-ranks by proximity (geo scorer), independent of filter |
geo_radius_m |
also hard-restrict candidates to this radius (metres) around near_lat/lon |
include_content |
false returns a compact result (ids and scores only, no content blob) |
filter (a discrete tag) and near_lat/near_lon (live GPS) are orthogonal; given together
they compose by intersection. See Ranking -> geo.
// Filter recommendations to a location/tag
fetch(`${API}/api/recommend?user_id=${userId}&filter=AiARLocationBarrack3&limit=10`);
// Proximity re-rank, then a hard radius cutoff
fetch(`${API}/api/recommend?user_id=${userId}&near_lat=52.7579&near_lon=9.9048&geo_radius_m=500`);
// Compact payload (ids + scores, no content blob)
fetch(`${API}/api/recommend?user_id=${userId}&include_content=false`);
Test without events
POST /api/recommend/preview recommends from a hand-authored user model (tag affinity,
liked items, demographics) without sending any events. Handy for trying the engine or for
LLM evaluation. Same filter and include_content options apply.
Search and narrative¶
// Semantic search
const s = await (await fetch(`${API}/api/search?q=${encodeURIComponent(q)}`)).json();
s.result.forEach(item => render(item)); // item.score, item.highlight, item.title
// Geo search
fetch(`${API}/api/search/geo?lat=52.7579&lon=9.9048&radius_meters=2000`);
// Narrative from a set of items
fetch(`${API}/api/narrative`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items: selectedItems })
});
Inspect the user model (debug)¶
fetch(`${API}/api/usermodel?user_id=${userId}`); // positives, negatives, tag_affinity, taste_vector
Full request/response detail with a try-it console: the interactive AI Engine API.
CORS
The API allows all origins by default (demo posture). For production, restrict it to your UI origin. See Cloud and production.