Skip to main content

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:

  1. Embedding AssistantConfig in PackageManifest at build time so the Player knows which AI features are available offline.
  2. Optional enrichment hooks that call ai-gateway-service during build (author-approved, off by default).
  3. No AI calls during runtime — PlayPackage is pure projection; no live AI inference in content-service.

1. AI Surfaces in Content-Service

SurfaceActivationAI Dependency
AssistantConfig in manifestAlways (if tenant has AI enabled)Read-only: embeds config only
Metadata enrichment (duration, objectives)Opt-in per tenant, per courseai-gateway-service call during build
Alternative manifest layouts (e.g., branching suggestions)Opt-in, author-triggeredai-gateway-service call during authoring, not build
Caption + transcript quality checkOpt-inDelegated 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, model from 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

StageAI Involvement
Draft authoringYes, heavy (authoring-service)
Draft published eventNo
Content-service buildOptional enrichment (opt-in, graceful fallback)
Content-service bundleNone
Content-service exportNone
Content-service runtime APINone
Player plays contentYes, via ai-gateway-service directly (uses manifest config)

Content-service is on the AI-frigid path. This keeps build pipelines fast, deterministic, and testable.