Skip to main content

AI Integration

:::info Source Sourced from services/catalog-service/AI_INTEGRATION.md in the documentation repo. :::

Companion: ../../docs/03-microservices/ai-gateway-service.md · APPLICATION_LOGIC

Catalog is not an AI-producing service. It consumes the ai-gateway-service only for narrowly scoped metadata-assist tasks. No direct model calls.

1. AI Surface Summary

CapabilityStatusTriggerModel family
Metadata localisation (translate title + description into missing locales)S5Admin action POST /courses/{id}/metadata/localizetranslation-grade chat model
Taxonomy suggestion (given a course, suggest nodes)S5Admin action POST /courses/{id}/taxonomy/suggestembedding-based kNN + LLM
Slug suggestion (given a title)S2Authoring-service call GET /internal/v1/catalog/slug/suggestsmall LLM or heuristic
Tag suggestion (given metadata)S5Admin action POST /courses/{id}/tags/suggestembeddings + LLM
Listing blurb suggestion (for marketplace)S5Marketplace delegates to authoring/ai-gatewayLLM

2. Invocation Contract (ai-gateway)

All AI calls use the platform AIRequest envelope defined in ai-gateway-service:

interface AIRequest<T> {
requestId: ULID;
correlationId: ULID;
tenantId: TenantId;
actor: { type: 'user'|'system'; id: string };
capability: string; // 'catalog.metadata.localize' | 'catalog.tags.suggest' | …
input: T;
constraints: {
maxLatencyMs: number;
maxCostMicroUSD: number;
dataResidency: 'us'|'eu'|'me'|'ap';
piiPolicy: 'none'|'strict';
localOnly?: boolean;
};
hitl?: { required: boolean; approverRole?: string };
}

AI gateway returns:

interface AIResponse<R> {
requestId: ULID;
output: R;
provenance: AIProvenance; // see 12-data-models
usage: { microUSD: number; tokens: { in: number; out: number } };
status: 'ok'|'partial'|'refused'|'error';
}

Catalog stores provenance alongside any AI-assisted field in course_audit.diff.

3. Capability: catalog.metadata.localize

Purpose: Given a Course with title + description in defaultLocale, produce translations for the target locales.

Request input

{
"courseId": "crs_01H…",
"defaultLocale": "en",
"targetLocales": ["ar","fr","es"],
"title": { "en": "Intro Physics" },
"description": { "en": "A first course in mechanics, waves, and thermodynamics." },
"domainHint": "/science/physics"
}

Response output

{
"translations": {
"ar": { "title": "مقدمة الفيزياء", "description": "…" },
"fr": { "title": "Introduction à la physique", "description": "…" },
"es": { "title": "Introducción a la física", "description": "…" }
}
}

HITL

Translation output is tagged hitl.required=false by default, but the admin UI surfaces the AI-generated copy as draft until the user confirms. Confirmation writes both the text and the AIProvenance.reviewedBy + reviewedAt fields.

Safety

  • piiPolicy: 'strict' — gateway redacts any detected PII before model call (unlikely in titles but enforced).
  • Cost cap: 10,000 μUSD per request.
  • Rate limit: 100 rpm per tenant.

4. Capability: catalog.tags.suggest

Purpose: Propose 5–15 tags for a course.

Input:

{ "title": { "en":"…" }, "description": { "en":"…" }, "moduleSummaries": [ "…" ] }

Output:

{ "suggestions": [ { "tag":"kinematics","confidence":0.92 }, { "tag":"newtonian-mechanics","confidence":0.88 } ] }
  • Suggestions below confidence 0.6 are filtered client-side.
  • All applied tags require user click-through (HITL enforced at UI).

5. Capability: catalog.taxonomy.suggest

Purpose: Given a course, suggest taxonomy nodes from a given taxonomy.

Flow:

1. catalog builds { course metadata + taxonomy tree }.
2. ai-gateway embeds taxonomy nodes (cached) and course text.
3. ai-gateway returns top-N nodes by cosine similarity, then LLM re-ranks.

Output:

{
"suggestions": [
{ "taxonomyId":"tax_01H…", "nodePath":"/science/physics/mechanics", "score":0.94 },
{ "taxonomyId":"tax_01H…", "nodePath":"/science/physics", "score":0.89 }
]
}

6. Capability: catalog.slug.suggest (S2)

Deterministic by default; AI as fallback when the heuristic yields collisions.

Algorithm:

  1. Normalise title to ASCII, lowercase, kebab-case.
  2. If unused within tenant → return.
  3. Else append a disambiguator via AI (-intro, -2026, -advanced) up to 3 attempts.
  4. Else fall back to base-<shortULID>.

Cost cap per call: 500 μUSD.

7. No Direct Model Access

Catalog service MUST NOT import SDKs for OpenAI, Anthropic, Vertex, Azure OpenAI, Bedrock, or any on-prem model server. All calls are via ai-gateway-service HTTP API. Violation is a build-time lint failure (eslint custom rule no-direct-ai-sdk).

8. Cost & Budget

CapabilityAvg cost / callBudget line (per tenant/mo)
Localize (3 locales)~3000 μUSDincluded in tenant.ai.budget
Tags suggest~200 μUSDincluded
Taxonomy suggest~500 μUSDincluded
Slug suggest~50 μUSDincluded

Catalog publishes ai.usage.reported.v1 via the gateway side (catalog does not emit directly).

9. Failure Modes

Gateway resultCatalog behavior
status=errorReturn 502 upstream to admin UI; do NOT persist partial output
status=refusedReturn 422 with reason (safety/policy)
status=partialPersist what's returned; flag missing locales in UI
timeout (> maxLatencyMs)Return 504; user can retry

10. Audit & Provenance

Every AI-assisted field write creates a course_audit row with:

{
"action": "ai_assist_apply",
"diff": { "title": {"en":"old","ar":"new"} },
"provenance": {
"model": "claude-4.6",
"version": "2026-02-01",
"promptId": "catalog.metadata.localize.v3",
"promptVersion": "3.0.1",
"traceId": "00-…-…-01",
"decisionId": "dec_01H…",
"local": false,
"generatedAt": "2026-03-02T09:00Z",
"reviewedBy": "usr_01H…",
"reviewedAt": "2026-03-02T09:03Z",
"cost": { "microUSD": 2840, "tokens": { "in": 812, "out": 610 } }
}
}

This trail is surfaced in the admin UI as an "AI badge" on the field.

11. Offline & On-device AI

Catalog has no offline AI path. All suggestion endpoints are online-only and return 503 with Retry-After when the client is offline.

12. Testing

  • AI calls mocked in CI; contract fixtures for each capability.
  • Canary tests on staging: localize 10 sample courses → BLEU score ≥ threshold.
  • HITL UX test: AI suggestion must not auto-apply without user confirmation.