AI Integration
:::info Source
Sourced from services/content-service/AI_INTEGRATION.md in the documentation repo.
:::
Companion: 03 ai-gateway-service · DOMAIN_MODEL
Content-service is minimally AI-coupled. It does not originate AI calls during steady-state operation. Its relationship with AI is:
- Embedding
AssistantConfigin PackageManifest at build time so the Player knows which AI features are available offline. - Optional enrichment hooks that call ai-gateway-service during build (author-approved, off by default).
- No AI calls during runtime — PlayPackage is pure projection; no live AI inference in content-service.
1. AI Surfaces in Content-Service
| Surface | Activation | AI Dependency |
|---|---|---|
AssistantConfig in manifest | Always (if tenant has AI enabled) | Read-only: embeds config only |
| Metadata enrichment (duration, objectives) | Opt-in per tenant, per course | ai-gateway-service call during build |
| Alternative manifest layouts (e.g., branching suggestions) | Opt-in, author-triggered | ai-gateway-service call during authoring, not build |
| Caption + transcript quality check | Opt-in | Delegated to media-service; content-service does not call AI |
2. AssistantConfig in Manifest
2.1 Purpose
The Player runtime needs to know, without connectivity, which AI features are enabled for this course and how to invoke them. This config is baked into the manifest at build time.
2.2 Schema
interface AssistantConfig {
enabled: boolean;
// Prompt pinning (tenant-scoped; specified in authoring)
promptId: string; // e.g., 'prompt_tutor_v3'
promptVersion: SemVer; // e.g., '3.1.0'
// Model pinning
model: string; // e.g., 'claude-sonnet-4-20250514'
fallbackModel?: string; // smaller model if primary unavailable
// Feature gates
features: {
questionAnswering: boolean; // "ask the tutor"
summarization: boolean; // "summarize this lesson"
translation: boolean; // cross-locale translation
adaptiveHints: boolean; // assessment hints
};
// Runtime constraints
constraints: {
maxTokensPerSession: number; // tenant-configured budget
contextScope: 'lesson' | 'module' | 'course'; // what AI can see
groundedOnly: boolean; // must cite from course content; refuse otherwise
redactionRequired: boolean; // PII redaction before send
};
// Offline behavior
offlineFallback: {
enabled: boolean; // allow local smaller model
localModelId?: string; // e.g., 'phi-3-mini-on-device'
};
}
2.3 Population at Build Time
1. Check tenant config: is AI enabled for this tenant?
2. Check course config: is AI enabled for this course?
3. If both true:
a. Resolve promptId + version from authoring-service draft metadata
b. Resolve model + constraints from tenant AI policy
c. Populate AssistantConfig
4. If false (or tenant AI disabled):
a. AssistantConfig = { enabled: false, ... defaults }
2.4 Runtime Usage (Player Side)
- On lesson mount, Player reads
manifest.assistant - If
enabled, Player shows AI UI affordances - Player sends AI requests to ai-gateway-service (online) or local model (offline) per
offlineFallback.enabled - All requests include
promptId,promptVersion,modelfrom manifest — ai-gateway-service enforces match
3. Build-Time Enrichment (Opt-In)
For tenants that enable "AI-assisted build", content-service can enrich the manifest during build:
3.1 Duration Estimation
POST {ai-gateway}/v1/inference
{
"promptId": "prompt_duration_estimator_v1",
"tenantId": "ten_01HXYZ…",
"input": {
"lessons": [ { "blocks": [...] } ]
},
"constraints": {
"structured": true,
"outputSchema": { "durationMinutes": "integer" }
}
}
Response is merged into manifest: course.durationMinutes, module.durationMinutes, etc.
3.2 Learning Objective Extraction
POST {ai-gateway}/v1/inference
{
"promptId": "prompt_objective_extractor_v1",
"tenantId": "ten_01HXYZ…",
"input": { "lessonContent": "..." },
"constraints": { "structured": true, "outputSchema": { "objectives": ["string"] } }
}
Results embedded in lesson.metadata.objectives. Author reviews before publish; content-service only consumes the published draft (post-review).
3.3 Author-Approved, Not Automatic
- Content-service does not call AI autonomously during build.
- Enrichment is done during authoring (by authoring-service) with HITL approval.
- Content-service consumes the already-enriched draft.
- This avoids latency and cost variance in the build pipeline.
4. No Runtime AI in Content-Service
Content-service explicitly does not:
- Call ai-gateway-service during bundle creation
- Generate prompts at request time
- Cache AI responses
- Stream AI tokens to clients
All AI runtime traffic flows directly: Player → ai-gateway-service. Content-service is the bookkeeping authority for which AI config is active; the gateway is the execution authority.
5. AI Config Freeze
Once a PlayPackage is built, its AssistantConfig is frozen. To change AI behavior:
- Modify tenant AI policy (affects new builds only)
- Modify course draft and re-publish (produces new PlayPackage)
The Player must honor the frozen config. ai-gateway-service rejects requests whose promptId/version don't match the manifest.
6. Cost Attribution
AI spend happens at ai-gateway, but content-service records:
- Which PlayPackages have AI enabled (for tenant billing reports)
- Estimated AI budget per bundle (for cost forecasting)
Event content.play_package.built.v1 includes manifestSummary.hasAssistant: boolean for analytics.
7. Build-Time AI Calls — Observability
If build-time enrichment is enabled, every AI call is traced:
span: content-service.build.enrich_manifest
attributes:
tenant_id: ten_01HXYZ…
course_version_id: cv_01HXYZ…
prompt_id: prompt_duration_estimator_v1
prompt_version: 1.2.0
model: claude-haiku-4-20250514
input_tokens: 1234
output_tokens: 89
cost_micro_usd: 150
latency_ms: 450
decision_id: dec_01HXYZ…
events:
- type: ai_request_sent
- type: ai_response_received
- type: ai_response_validated
8. AI Provenance in Manifest
When AI enrichment runs, the provenance is recorded in the manifest metadata (not surfaced to Player but kept for audit):
{
"_aiProvenance": [
{
"field": "course.durationMinutes",
"model": "claude-haiku-4-20250514",
"promptId": "prompt_duration_estimator_v1",
"promptVersion": "1.2.0",
"traceId": "00-abc-def-01",
"decisionId": "dec_01HXYZ…",
"generatedAt": "2026-04-15T08:59:30Z",
"reviewedBy": "usr_01HXYZ…",
"reviewedAt": "2026-04-15T08:59:45Z"
}
]
}
This follows the AIProvenance contract defined in 12 Data Models §0.
9. Prompt Injection Mitigation
Content-service is mostly read-only with respect to AI, so prompt injection surface is limited. However, for build-time enrichment:
- Input isolation: Lesson content passed to AI is wrapped in explicit delimiters with system-prompt instructions to ignore nested instructions.
- Classifier: ai-gateway-service runs its prompt-injection classifier; if flagged, build proceeds without that field (fallback to author-set value or null).
- No tool use in enrichment: Enrichment prompts never grant the model tool-use access.
10. AI-Free Fallback
Content-service must function fully without AI:
- If tenant has no AI enabled →
AssistantConfig.enabled = false - If build-time enrichment fails → build continues with author-set values only
- If ai-gateway-service is unavailable → enrichment skipped with warning log; build succeeds
AI is enhancement, not dependency for content-service.
11. Summary of AI Touchpoints
| Stage | AI Involvement |
|---|---|
| Draft authoring | Yes, heavy (authoring-service) |
| Draft published event | No |
| Content-service build | Optional enrichment (opt-in, graceful fallback) |
| Content-service bundle | None |
| Content-service export | None |
| Content-service runtime API | None |
| Player plays content | Yes, via ai-gateway-service directly (uses manifest config) |
Content-service is on the AI-frigid path. This keeps build pipelines fast, deterministic, and testable.