Skip to content

Commit fb07449

Browse files
Iris: Add context awareness (#598)
Co-authored-by: Benjamin Schmitz <benjamin.schmitz@tum.de>
1 parent 0dc1aba commit fb07449

8 files changed

Lines changed: 812 additions & 6 deletions

File tree

iris/src/iris/domain/chat/chat_pipeline_execution_dto.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
from typing import List, Optional
22

3-
from pydantic import Field
3+
from pydantic import Field, model_validator
44

55
from iris.common.pyris_message import PyrisMessage
66
from iris.domain.data.course_dto import CourseDTO
7+
from iris.domain.data.lecture_context_dto import (
8+
CombinedViewContextDTO,
9+
LectureContextDTO,
10+
)
711
from iris.domain.data.lecture_dto import PyrisLectureDTO
812
from iris.domain.data.metrics.student_metrics_dto import StudentMetricsDTO
913
from iris.domain.data.programming_exercise_dto import ProgrammingExerciseDTO
@@ -34,7 +38,27 @@ class ChatPipelineExecutionDTO(PipelineExecutionDTO):
3438
text_exercise: Optional[TextExerciseDTO] = Field(alias="textExercise", default=None)
3539
lecture: Optional[PyrisLectureDTO] = None
3640
lecture_unit_id: Optional[int] = Field(alias="lectureUnitId", default=None)
41+
context: Optional[List[LectureContextDTO]] = Field(
42+
default=None,
43+
description="Optional array of context objects (video/slides/combinedView) the student is currently viewing",
44+
)
3745
programming_exercise_submission: Optional[ProgrammingSubmissionDTO] = Field(
3846
alias="programmingExerciseSubmission", default=None
3947
)
4048
text_exercise_submission: str = Field(alias="textExerciseSubmission", default="")
49+
50+
@model_validator(mode="after")
51+
def extract_lecture_unit_id_from_combined_view_context(self):
52+
"""Extract lectureUnitId from a combinedView context if present.
53+
54+
This populates the lecture_unit_id field from the combinedView context's
55+
nested slides/video objects, which is used for RAG filtering to scope
56+
retrieval to the specific lecture unit.
57+
"""
58+
# Only extract if lecture_unit_id is not already set
59+
if self.lecture_unit_id is None and self.context:
60+
for ctx in self.context:
61+
if isinstance(ctx, CombinedViewContextDTO):
62+
self.lecture_unit_id = ctx.lecture_unit_id
63+
break
64+
return self
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from typing import Literal, Optional, Union
2+
3+
from pydantic import BaseModel, Field
4+
5+
6+
class VideoContextDTO(BaseModel):
7+
"""Context about a video lecture unit the student is currently viewing.
8+
9+
Sent from Artemis as part of the optional context array.
10+
"""
11+
12+
type: Literal["video"]
13+
lecture_unit_id: int = Field(alias="lectureUnitId", gt=0)
14+
timestamp: float = Field(ge=0) # in seconds
15+
16+
class Config:
17+
populate_by_name = True # Allow both snake_case and camelCase
18+
19+
20+
class SlidesContextDTO(BaseModel):
21+
"""Context about a slides/PDF lecture unit the student is currently viewing.
22+
23+
Sent from Artemis as part of the optional context array.
24+
"""
25+
26+
type: Literal["slides"]
27+
lecture_unit_id: int = Field(alias="lectureUnitId", gt=0)
28+
page: int = Field(ge=1) # PDF pages start at 1
29+
30+
class Config:
31+
populate_by_name = True # Allow both snake_case and camelCase
32+
33+
34+
class CombinedViewContextDTO(BaseModel):
35+
"""Context indicating the student is viewing a lecture unit in the combined
36+
view.
37+
38+
Sent from Artemis as part of the optional context array. It nests an optional
39+
``slides`` and an optional ``video`` object describing the student's current
40+
position; at least one of them is always present. Used for RAG filtering to
41+
scope retrieval to the specific lecture unit.
42+
"""
43+
44+
type: Literal["combinedView"]
45+
slides: Optional[SlidesContextDTO] = None
46+
video: Optional[VideoContextDTO] = None
47+
48+
class Config:
49+
populate_by_name = True # Allow both snake_case and camelCase
50+
51+
@property
52+
def lecture_unit_id(self) -> Optional[int]:
53+
"""Derive the lecture unit id from the nested slides/video objects.
54+
55+
Prefers ``slides.lecture_unit_id`` and falls back to
56+
``video.lecture_unit_id``. Returns ``None`` only in the (unexpected)
57+
case where neither nested object is present.
58+
"""
59+
if self.slides is not None:
60+
return self.slides.lecture_unit_id
61+
if self.video is not None:
62+
return self.video.lecture_unit_id
63+
return None
64+
65+
66+
# Union type for all structured context types
67+
# pylint: disable=invalid-name
68+
LectureContextDTO = Union[VideoContextDTO, SlidesContextDTO, CombinedViewContextDTO]

0 commit comments

Comments
 (0)