Skip to main content

Prompts

Prompts define how Iris communicates with LLMs. They establish the AI tutor's personality, pedagogical approach, and task-specific behavior. Iris uses a combination of Jinja2 templates and Python prompt builders.

Prompt Location​

All prompts live under src/iris/pipeline/prompts/:

prompts/
├── templates/ # Jinja2 templates for system prompts
│ ├── chat_system_prompt.j2 # The system prompt of every student chat
│ ├── chat_turn_context.j2 # Per-message viewing context, sent after the chat history
│ ├── exercise_chat_guide_prompt.j2
│ ├── mcq_generation_prompt.j2
│ ├── autonomous_tutor_system_prompt.j2
│ ├── tutor_suggestion_chat_system_prompt.j2
│ └── session_title_generation_prompt.j2
├── code_feedback_prompt.txt # Plain text prompt for code feedback
├── citation_prompt.txt # Citation generation prompt
├── citation_keyword_prompt.txt # Citation keyword extraction
├── citation_summary_prompt.txt # Citation summary generation
├── summary_prompt.txt # General summarization
├── competency_extraction.py # Python prompt builder
├── faq_consistency_prompt.py # FAQ consistency check prompts
├── faq_retrieval_prompts.py # FAQ retrieval query rewriting
├── inconsistency_check_prompts.py # Inconsistency check prompts
├── iris_interaction_suggestion_prompts.py # Follow-up question suggestions
├── lecture_retrieval_prompts.py # Lecture RAG query rewriting
├── lecture_unit_summary_prompt.py # Lecture unit summarization
├── lecture_unit_segment_summary_prompt.py # Lecture segment summarization
├── rewriting_prompts.py # Content rewriting prompts
└── transcription_ingestion_prompts.py # Transcription processing prompts

Jinja2 Templates​

Pipelines use Jinja2 templates (.j2 files) for their system prompts. These templates support variable interpolation using {{ variable }} syntax.

The Chat System Prompt​

chat_system_prompt.j2 is the system prompt of every student chat, in every chat mode. One template rather than one per mode: the persona, the pedagogical guidelines and the tool instructions are identical across modes, and copies of them drift apart as soon as anyone edits only the copy they were working on.

The template is a sequence of blocks in a fixed order. Some are unconditional, others render only when the request carries the data they describe:

BlockConditionContent
Languageuser_languageAnswer in the student's language
IntroductionalwaysThe Iris persona
GuidelinesalwaysScaffolding rules, what not to reveal
Course metaalwaysCourse name
Lecture metalecture_nameThe active lecture
Exercise metaexercise_idTitle, problem statement, dates, programming language or submission
ExamplesalwaysWorked examples of good and bad answers
Tool instructionsalwaysHow and when to reach for tools
Context switchingnot mcq_parallelWhen and how to call switch_chat_context
Exercise scenariosprogramming_languageBuild failures, failing tests, stuck students
Lecture retrievalallow_lecture_toolHow to retrieve and cite lecture content, and which page number to quote
Combined-view point-outcan_point_out_in_combined_viewWhen and how to move the student's slides or video
FAQ retrievalallow_faq_toolHow to use FAQ answers
Competencieshas_competenciesHow to talk about competencies
Exerciseshas_exercisesHow to talk about the course exercises
Metricsmetrics_enabledHow to interpret student analytics
Chat historyhas_chat_historyHow to treat earlier turns
Proactive eventseventBehavior for build-failure and stalled-progress events
MCQ generationcourse and lecture modesHow to produce multiple-choice questions
Support levelsupport_levelHow much help the instructor allows
Custom instructionscustom_instructionsThe instructor's own additions

build_system_message() assembles the render context from the DTO and from the flags that prepare_state() resolved:

template_context = {
"chat_mode": self.chat_mode,
"support_level": _support_level(dto),
"current_date": datetime_to_string(datetime.now(tz=pytz.UTC)),
"user_language": dto.user.lang_key,
"course_name": dto.course.name,
"allow_lecture_tool": state.allow_lecture_tool,
"allow_faq_tool": state.allow_faq_tool,
"lecture_name": dto.lecture.title if dto.lecture else None,
"exercise_title": exercise.title if exercise else "",
"programming_language": ...,
"custom_instructions": format_custom_instructions(dto.custom_instructions or ""),
...
}
return self.system_prompt_template.render(template_context)

Note that most blocks key off data presence, not off chat_mode. chat_mode appears in exactly one place, the context switching block, where the agent has to be told which kind of context is currently active. Everything else follows from the entity fields, which keeps the prompt correct after a context switch without a special case.

The Student's Current Position​

Not every block the agent reads lives in the system prompt. Blocks whose content changes from message to message are rendered from a second template, chat_turn_context.j2, and sent as a system message after the chat history. The system prompt is the start of the cached prefix and has to stay byte-identical across the turns of a session, so a per-message value cannot live in it without invalidating that cache on every turn.

current_view_blocks is one of those blocks. It names the slide page or video moment the student currently has open, when the request carried a viewing context and the material at that position is indexed:

The student is currently viewing page 4 of the lecture slides of the lecture unit
Concurrency (lecture unit ID: 31).

The block names the position but never reproduces the material at it. The agent therefore learns where the student is on every turn, and learns what is there only if it calls read_students_current_position. Viewing context explains why the material is kept behind a tool, and why this block names that tool only when it was actually registered.

The Context Switching Block​

This block is what makes agent-initiated context switching work. It gives the agent the procedure rather than only the tool:

  1. Find the target ID with the exercise list tool or the lecture list tool, and never guess an ID.
  2. Call switch_chat_context with the matching mode and that ID.
  3. Answer the question in the new context.

It also states when to switch back: a general or course-level question while an exercise or lecture context is active calls for COURSE_CHAT. The block opens with the currently active context, rendered from chat_mode, so the agent can tell whether the entity the student named is the active one. It is skipped in MCQ parallel mode, where the agent has no tools and only writes an intro.

The block carries a worked example per direction — switching to an exercise, and switching to another lecture and retrieving its content afterwards. Both examples spell out the ID lookup as a separate step, since the instruction to look the ID up carries most of the weight here.

Loading Templates​

Pipelines load Jinja2 templates in their __init__. Since the chat pipeline lives in pipeline/chat/, the path navigates up to the prompts/templates/ directory:

from jinja2 import Environment, FileSystemLoader, select_autoescape

# From ChatPipeline in pipeline/chat/
template_dir = os.path.join(os.path.dirname(__file__), "..", "prompts", "templates")
self.jinja_env = Environment(
loader=FileSystemLoader(template_dir),
autoescape=select_autoescape(["j2"]),
)
self.system_prompt_template = self.jinja_env.get_template("chat_system_prompt.j2")

Pipelines that live directly in pipeline/ (like SessionTitleGenerationPipeline) use a simpler path without the ...

Python Prompt Builders​

Some prompts are constructed in Python files, typically as functions that return prompt strings or ChatPromptTemplate objects. These are used for sub-pipelines and retrieval operations.

For example, lecture_retrieval_prompts.py defines prompt strings for query rewriting during RAG:

# From lecture_retrieval_prompts.py
lecture_retriever_initial_prompt_lecture_pages = """
You write good and performant vector database queries, in particular for Weaviate,
from chat histories between an AI tutor and a student.
The query should be designed to retrieve context information from indexed lecture slides...
"""

Plain Text Prompts​

Simple, single-purpose prompts use .txt files with placeholder variables in {curly_braces}:

code_feedback_prompt.txt
Exercise Problem Statement:
{problem_statement}

Chat History:
{chat_history}

User question:
{question}

Feedbacks (from automated tests):
{feedbacks}
...

These are loaded and formatted using Python's str.format() or passed directly to LangChain prompt templates.

The four citation prompts are the one group where the wording is part of a wire format: citation_prompt.txt and faq_citation_prompt.txt tell the model to copy the provided ids verbatim, and citation_keyword_prompt.txt and citation_summary_prompt.txt forbid the characters that delimit a citation's fields. See citations.

How Prompts Are Assembled​

In AbstractAgentPipeline, the full prompt is assembled in two steps:

1. System Message Construction​

Each pipeline implements build_system_message() to produce the system prompt string:

def build_system_message(self, state):
# Render the Jinja2 template with context
return self.system_prompt_template.render(
current_date=...,
custom_instructions=...,
# ... pipeline-specific variables
)

2. History and Scratchpad Assembly​

The assemble_prompt_with_history() method combines the system message with chat history and the agent scratchpad:

def assemble_prompt_with_history(self, state, system_prompt):
prefix_messages = [
("system", system_prompt.replace("{", "{{").replace("}", "}}"))
]
history_lc_messages = [
convert_iris_message_to_langchain_message(msg)
for msg in state.message_history
]
combined = (
prefix_messages
+ history_lc_messages
+ [("placeholder", "{agent_scratchpad}")]
)
return ChatPromptTemplate.from_messages(combined)
note

The { and } characters in the system prompt are escaped to {{ and }} to prevent LangChain from interpreting them as template variables. Only {agent_scratchpad} remains as a true placeholder.

Prompt Design Patterns​

Pedagogical Guidelines​

Chat prompts consistently enforce these pedagogical principles:

  • Do not solve exercises for students — guide them toward discovering solutions independently.
  • Use tools proactively — look up submission data, build logs, and feedback instead of asking the student.
  • Provide hints, not answers — give clues and best practices to direct the student's attention.
  • Adjust help level — gradually increase assistance if the student is stuck.
  • Respect academic integrity — never provide code that can be directly copied into the exercise.

Context Injection​

Prompts use several patterns to inject runtime context:

PatternExampleUsed For
Jinja2 variables{{ current_date }}Date, custom instructions, exercise metadata
Chat historyConverted to LangChain messagesPrior conversation turns
Tool resultsInjected by agent scratchpadData retrieved during execution
Guide promptsSeparate template rendered and appendedDetailed behavioral instructions

Custom Instructions​

Instructors can provide custom instructions that are injected into the system prompt. These are formatted and included via the format_custom_instructions() utility from pipeline/shared/utils.py.