Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions openapi.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
openapi: 3.1.0
info:

Check warning on line 2 in openapi.yml

View workflow job for this annotation

GitHub Actions / Lint / Lint

info-license

Info object should contain `license` field.
title: µEd API - Educational Microservices
version: 0.1.0
contact:
Expand All @@ -12,6 +12,16 @@
- **Evaluate Task**: automatic feedback and grading for student submissions. <br>
- **Chat**: conversational interactions around tasks, submissions, or general
learning questions.
<br><br>
**Progress streaming.** Any operation MAY support an opt-in Server-Sent
Events progress stream, selected per request with the
`Accept: text/event-stream` header. The stream is a sequence of
`SseProgressStep` frames, optional keep-alive comments, and exactly one
terminal frame carrying the operation's normal `200` body (on success) or an
`ErrorResponse` (on failure); the HTTP status stays `200` throughout.
Services advertise support via `supportsStreaming` in their `/health`
capabilities. This is distinct from `configuration.llm.stream`, which
governs token-level streaming from the LLM provider.

tags:
- name: evaluate
Expand Down
6 changes: 6 additions & 0 deletions paths/chat/chat-health.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ get:
supportsChat: true
supportsUserPreferences: true
supportsStreaming: true
supportedProgressStages:
- "preparing"
- "starting"
- "thinking"
- "completed"
- "failed"
"supportsDataPolicy": "NOT_SUPPORTED"
supportedLanguages:
- "en"
Expand Down
64 changes: 63 additions & 1 deletion paths/chat/chat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,35 @@ post:
temperature: 0.5
responses:
"200":
description: Successful chat response.
description: >
Successful chat response.


### Streaming variant (opt-in)


If the request sends `Accept: text/event-stream`, the response is a
Server-Sent Events progress stream instead of a single JSON body. The
HTTP status stays `200` for the entire stream, including failures. It
consists of:


- zero or more progress frames — the SSE `event:` field is the stage
name and `data:` is an `SseProgressStep` JSON object;

- optional keep-alive comment lines (each starting with `:`);

- exactly one terminal frame — `event: completed` with `data:` an
`SseChatTerminalFrame` (the `200` body as `output` / `metadata`, plus
a `steps` replay), or `event: failed` with `data:` an
`SseChatTerminalFrame` whose `error` is an `ErrorResponse`.


Failures are always delivered as a terminal `failed` frame, never as an
HTTP error status. `X-Request-Id` and `X-Api-Version` are sent once, as
response headers, when the stream opens. This is independent of
`configuration.llm.stream`, which governs token-level streaming from the
LLM provider.
headers:
X-Request-Id:
description: Request id for tracing this request across services.
Expand Down Expand Up @@ -213,6 +241,40 @@ post:
model: "gpt-5.2"
temperature: 0.5
outputTokens: 143
text/event-stream:
schema:
$ref: "./schemas/SseChatTerminalFrame.yml"
examples:
completedFrame:
summary: >
Terminal `event: completed` frame `data` (preceded on the wire by
`event: preparing` / `event: thinking` progress frames whose
`data` is an SseProgressStep, and optional `:` keep-alive lines).
value:
output:
role: ASSISTANT
content: "Polymorphism lets one interface represent many underlying forms."
metadata:
responseTimeMs: 1800
steps:
- stage: "preparing"
message: "Preparing worker"
timestamp: "2025-12-10T11:15:00Z"
- stage: "thinking"
message: "Composing response"
timestamp: "2025-12-10T11:15:02Z"
failedFrame:
summary: "Terminal `event: failed` frame `data` (HTTP status is still 200)."
value:
output: null
error:
title: "Chat failed"
message: "The LLM provider connection failed."
code: "LLM_PROVIDER_ERROR"
steps:
- stage: "preparing"
message: "Preparing worker"
timestamp: "2025-12-10T11:15:00Z"
"400":
$ref: "./responses/400-BadRequest.yml"
"403":
Expand Down
5 changes: 2 additions & 3 deletions paths/chat/schemas/ChatCapabilities.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ required:
- supportsChat
- supportsDataPolicy
additionalProperties: true
allOf:
- $ref: "../../shared/schemas/StreamingCapabilities.yml"
properties:
supportsChat:
type: boolean
description: Indicates whether the /chat endpoint is implemented and usable.
supportsUserPreferences:
type: boolean
description: Indicates whether the service supports adapting to user preferences.
supportsStreaming:
type: boolean
description: Indicates whether the service supports streaming responses.
supportsDataPolicy:
$ref: "../../shared/enums/DataPolicySupport.yml"
supportedLanguages:
Expand Down
36 changes: 36 additions & 0 deletions paths/chat/schemas/SseChatTerminalFrame.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
type: object
description: >
Data payload of the single terminal frame of a `POST /chat` SSE progress
stream.

On `event: completed`, `output` / `metadata` hold exactly the endpoint's
normal `200 application/json` body (`ChatResponse`) and `error` is absent.

On `event: failed`, `output` is null and `error` holds an `ErrorResponse` with
the same `title` / `code` / `details` shape clients already handle for `400` /
`500` responses. The HTTP status stays `200` regardless.

`steps` is always present: the ordered replay of every progress step emitted
during the stream.
allOf:
- $ref: "../../shared/schemas/SseTerminalSteps.yml"
- type: object
properties:
output:
description: The generated assistant response; null on a `failed` frame.
anyOf:
- $ref: "./Message.yml"
- type: "null"
metadata:
type:
- object
- "null"
description: Optional metadata about response generation.
additionalProperties: true
error:
type:
- object
- "null"
description: Present only on a `failed` frame.
allOf:
- $ref: "../../shared/schemas/ErrorResponse.yml"
7 changes: 7 additions & 0 deletions paths/evaluate/evaluate-health.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ get:
supportsFormativeFeedback: true
supportsSummativeFeedback: true
supportsDataPolicy: "PARTIAL"
supportsStreaming: true
supportedProgressStages:
- "preparing"
- "starting"
- "evaluating"
- "completed"
- "failed"
supportedArtefactProfiles:
- type: "TEXT"
supportedFormats: ["plain", "markdown"]
Expand Down
68 changes: 64 additions & 4 deletions paths/evaluate/evaluate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@
- criterionId: "crit-1"
name: "Correctness"
context: "The explanation of polymorphism is conceptually correct."
maxPoints: 10

Check warning on line 108 in paths/evaluate/evaluate.yml

View workflow job for this annotation

GitHub Actions / Lint / Lint

no-invalid-media-type-examples

Example value must conform to the schema: `0` property must NOT have unevaluated properties `maxPoints`.
- criterionId: "crit-2"
name: "Clarity"
context: "The explanation is clear, well-structured, and easy to understand."
maxPoints: 5

Check warning on line 112 in paths/evaluate/evaluate.yml

View workflow job for this annotation

GitHub Actions / Lint / Lint

no-invalid-media-type-examples

Example value must conform to the schema: `1` property must NOT have unevaluated properties `maxPoints`.
preSubmissionFeedback:
enabled: false
configuration:
Expand Down Expand Up @@ -339,7 +339,35 @@
version: 1
responses:
"200":
description: Successfully generated feedback.
description: >
Successfully generated feedback.


### Streaming variant (opt-in)


If the request sends `Accept: text/event-stream`, the response is a
Server-Sent Events progress stream instead of a single JSON body. The
HTTP status stays `200` for the entire stream, including failures. It
consists of:


- zero or more progress frames — the SSE `event:` field is the stage
name and `data:` is an `SseProgressStep` JSON object;

- optional keep-alive comment lines (each starting with `:`);

- exactly one terminal frame — `event: completed` with `data:` an
`SseEvaluateTerminalFrame` (the `200` body under `feedback`, plus a
`steps` replay), or `event: failed` with `data:` an
`SseEvaluateTerminalFrame` whose `error` is an `ErrorResponse`.


Failures are always delivered as a terminal `failed` frame, never as an
HTTP error status. `X-Request-Id` and `X-Api-Version` are sent once, as
response headers, when the stream opens. This is independent of
`configuration.llm.stream`, which governs token-level streaming from the
LLM provider.
headers:
X-Request-Id:
description: Request id for tracing this request across services.
Expand All @@ -352,9 +380,7 @@
content:
application/json:
schema:
type: array
items:
$ref: "./schemas/Feedback.yml"
$ref: "./schemas/EvaluateResponse.yml"
examples:
exampleResponse:
summary: Example feedback response
Expand All @@ -379,6 +405,40 @@
- feedbackId: "fb-2"
title: "Overall structure"
message: "The overall structure of your answer is clear and easy to follow."
text/event-stream:
schema:
$ref: "./schemas/SseEvaluateTerminalFrame.yml"
examples:
completedFrame:
summary: >
Terminal `event: completed` frame `data` (preceded on the wire by
`event: preparing` / `event: evaluating` progress frames whose
`data` is an SseProgressStep, and optional `:` keep-alive lines).
value:
feedback:
- feedbackId: "fb-1"
title: "Clarify your definition"
message: "Distinguish subtype from parametric polymorphism."
awardedPoints: 2.5
steps:
- stage: "preparing"
message: "Preparing worker"
timestamp: "2025-12-16T09:30:01Z"
- stage: "evaluating"
message: "Ran 3/10 checks"
timestamp: "2025-12-16T09:30:04Z"
failedFrame:
summary: "Terminal `event: failed` frame `data` (HTTP status is still 200)."
value:
feedback: null
error:
title: "Evaluation failed"
message: "The worker exited before returning feedback."
code: "LLM_PROVIDER_ERROR"
steps:
- stage: "preparing"
message: "Preparing worker"
timestamp: "2025-12-16T09:30:01Z"
"202":
$ref: "./responses/202-Accepted.yml"
"400":
Expand Down
2 changes: 2 additions & 0 deletions paths/evaluate/schemas/EvaluateCapabilities.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ required:
- supportsSummativeFeedback
- supportsDataPolicy
additionalProperties: true
allOf:
- $ref: "../../shared/schemas/StreamingCapabilities.yml"
properties:
supportsEvaluate:
type: boolean
Expand Down
4 changes: 4 additions & 0 deletions paths/evaluate/schemas/EvaluateResponse.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type: array
description: Ordered list of feedback items generated for the submission.
items:
$ref: "./Feedback.yml"
32 changes: 32 additions & 0 deletions paths/evaluate/schemas/SseEvaluateTerminalFrame.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
type: object
description: >
Data payload of the single terminal frame of a `POST /evaluate` SSE progress
stream.

On `event: completed`, `feedback` holds exactly the endpoint's normal
`200 application/json` body and `error` is absent.

On `event: failed`, `feedback` is null and `error` holds an `ErrorResponse`
with the same `title` / `code` / `details` shape clients already handle for
`400` / `500` responses. The HTTP status stays `200` regardless.

`steps` is always present: the ordered replay of every progress step emitted
during the stream.
allOf:
- $ref: "../../shared/schemas/SseTerminalSteps.yml"
- type: object
properties:
feedback:
type:
- array
- "null"
description: The generated feedback items; null on a `failed` frame.
items:
$ref: "./Feedback.yml"
error:
type:
- object
- "null"
description: Present only on a `failed` frame.
allOf:
- $ref: "../../shared/schemas/ErrorResponse.yml"
7 changes: 6 additions & 1 deletion paths/shared/schemas/LLMConfiguration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ properties:
type:
- boolean
- "null"
description: Optional flag indicating whether streaming responses are requested.
description: >
Optional flag requesting token-level streaming from the underlying LLM
provider. This is distinct from API-layer progress streaming, which is
opt-in per request via the `Accept: text/event-stream` header and is
advertised by `supportsStreaming` in a service's `/health` capabilities.
A service MAY support either, both, or neither.
credentials:
type:
- object
Expand Down
30 changes: 30 additions & 0 deletions paths/shared/schemas/SseProgressStep.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
type: object
description: >
A single progress step emitted while an operation runs. Carried as the SSE
`data:` payload of an intermediate progress frame (the SSE `event:` field
carries the stage name), and replayed in the terminal frame's `steps` array.
additionalProperties: true
required:
- stage
- timestamp
properties:
stage:
type: string
description: >
Lifecycle stage this step reports. Informative, not a fixed enum:
implementations MAY introduce new stages without a spec change, and clients
MUST tolerate values they do not recognise. Each service advertises the
stage values it emits via `supportedProgressStages` in its `/health`
capabilities. Common values: "preparing", "starting", "evaluating"
(evaluate), "thinking" (chat), and the terminal "completed" / "failed".
message:
type:
- string
- "null"
description: >
Short, human-readable description of the step, suitable for display to a
learner or teacher.
timestamp:
type: string
format: date-time
description: Time this step was generated (RFC 3339 / ISO 8601).
13 changes: 13 additions & 0 deletions paths/shared/schemas/SseTerminalSteps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
type: object
description: >
Shared fragment for the terminal SSE frame: the ordered replay of every
progress step emitted during the stream, so a client that connected late or
dropped intermediate frames still receives the full trace.
required:
- steps
properties:
steps:
type: array
description: Ordered list of every SseProgressStep emitted during the stream.
items:
$ref: "./SseProgressStep.yml"
26 changes: 26 additions & 0 deletions paths/shared/schemas/StreamingCapabilities.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type: object
description: >
Shared capability fragment describing an operation's support for opt-in
Server-Sent Events (SSE) progress streaming. Composed via `allOf` into each
service's capabilities schema so every streaming-capable endpoint advertises
streaming the same way.
additionalProperties: true
properties:
supportsStreaming:
type: boolean
description: >
Indicates whether this operation supports opt-in SSE progress streaming,
selected per request with the `Accept: text/event-stream` header. This is
distinct from `configuration.llm.stream`, which governs token-level
streaming from the underlying LLM provider. A service MAY support either,
both, or neither.
supportedProgressStages:
type:
- array
- "null"
description: >
Optional list of `SseProgressStep.stage` values this service may emit
(e.g. ["preparing", "starting", "evaluating", "completed", "failed"]).
Informative only; clients MUST tolerate stages that are not listed.
items:
type: string
Loading