@@ -40,25 +40,30 @@ def __init__(
4040 self .tool_flags = tool_flags or {}
4141 self ._tool_registry = get_tool_registry ()
4242
43+ MAX_PREVIOUS_QUESTIONS = 20
44+
4345 async def process (
4446 self ,
4547 template : QuestionTemplate ,
4648 user_topic : str = "" ,
4749 preference : str = "" ,
4850 history_context : str = "" ,
51+ previous_questions : list [str ] | None = None ,
4952 ) -> QAPair :
5053 """
5154 Generate one Q-A pair from a template in a single call.
5255 """
5356 available_tools = self ._build_available_tools_text ()
5457 knowledge_context = str (template .metadata .get ("knowledge_context" , "" )).strip ()
58+ prev_q_text = self ._format_previous_questions (previous_questions )
5559 payload = await self ._generate_payload (
5660 template = template ,
5761 user_topic = user_topic ,
5862 preference = preference ,
5963 history_context = history_context ,
6064 knowledge_context = knowledge_context ,
6165 available_tools = available_tools ,
66+ previous_questions = prev_q_text ,
6267 )
6368 payload , validation = await self ._validate_and_repair_payload (
6469 template = template ,
@@ -68,6 +73,7 @@ async def process(
6873 history_context = history_context ,
6974 knowledge_context = knowledge_context ,
7075 available_tools = available_tools ,
76+ previous_questions = prev_q_text ,
7177 )
7278
7379 return QAPair (
@@ -112,6 +118,7 @@ async def _generate_payload(
112118 history_context : str ,
113119 knowledge_context : str ,
114120 available_tools : str ,
121+ previous_questions : str = "" ,
115122 ) -> dict [str , Any ]:
116123 system_prompt = self .get_prompt ("system" , "" )
117124 user_prompt_template = self .get_prompt ("generate" , "" )
@@ -120,31 +127,21 @@ async def _generate_payload(
120127 "Template: {template}\n "
121128 "User topic: {user_topic}\n "
122129 "Preference: {preference}\n "
123- "Previously generated questions (do not repeat):\n {history_context}\n "
130+ "Conversation context: {history_context}\n "
131+ "Previously generated questions (do not repeat):\n {previous_questions}\n "
124132 "Knowledge context: {knowledge_context}\n "
125133 "Enabled tools: {available_tools}\n \n "
126134 'Return JSON {{"question_type":"","question":"","options":{{}},"correct_answer":"","explanation":""}}'
127135 )
128136
129- # Serialize the template without the bulky knowledge_context in metadata —
130- # it is already included via the dedicated {knowledge_context} placeholder
131- # and its presence inside the template JSON would cause it to appear twice,
132- # dominating the prompt and making the LLM generate identical questions for
133- # every template regardless of their individual concentrations.
134- template_dict = template .__dict__ .copy ()
135- if isinstance (template_dict .get ("metadata" ), dict ):
136- stripped_metadata = {
137- k : v
138- for k , v in template_dict ["metadata" ].items ()
139- if k != "knowledge_context"
140- }
141- template_dict ["metadata" ] = stripped_metadata
137+ template_dict = self ._strip_template_knowledge_context (template )
142138
143139 user_prompt = user_prompt_template .format (
144140 template = json .dumps (template_dict , ensure_ascii = False , indent = 2 ),
145141 user_topic = user_topic ,
146142 preference = preference or "(none)" ,
147143 history_context = history_context or "(none)" ,
144+ previous_questions = previous_questions or "(none)" ,
148145 knowledge_context = knowledge_context or "(none)" ,
149146 available_tools = available_tools ,
150147 )
@@ -191,6 +188,7 @@ async def _validate_and_repair_payload(
191188 history_context : str ,
192189 knowledge_context : str ,
193190 available_tools : str ,
191+ previous_questions : str = "" ,
194192 ) -> tuple [dict [str , Any ], dict [str , Any ]]:
195193 expected_type = self ._normalize_question_type (template .question_type )
196194 normalized = self ._normalize_payload_shape (expected_type , payload )
@@ -207,6 +205,7 @@ async def _validate_and_repair_payload(
207205 history_context = history_context ,
208206 knowledge_context = knowledge_context ,
209207 available_tools = available_tools ,
208+ previous_questions = previous_questions ,
210209 )
211210 if repaired_payload :
212211 candidate = self ._normalize_payload_shape (expected_type , repaired_payload )
@@ -234,14 +233,17 @@ async def _repair_payload(
234233 history_context : str ,
235234 knowledge_context : str ,
236235 available_tools : str ,
236+ previous_questions : str = "" ,
237237 ) -> dict [str , Any ]:
238238 expected_type = self ._normalize_question_type (template .question_type )
239+ template_dict = self ._strip_template_knowledge_context (template )
239240 repair_prompt = (
240241 "You are repairing an invalid quiz question JSON.\n \n "
241- f"QuestionTemplate:\n { json .dumps (template . __dict__ , ensure_ascii = False , indent = 2 )} \n \n "
242+ f"QuestionTemplate:\n { json .dumps (template_dict , ensure_ascii = False , indent = 2 )} \n \n "
242243 f"User topic:\n { user_topic or '(none)' } \n \n "
243244 f"User preference:\n { preference or '(none)' } \n \n "
244245 f"Conversation context:\n { history_context or '(none)' } \n \n "
246+ f"Previously generated questions:\n { previous_questions or '(none)' } \n \n "
245247 f"Knowledge context:\n { knowledge_context or '(none)' } \n \n "
246248 f"Enabled tools:\n { available_tools } \n \n "
247249 f"Invalid payload:\n { json .dumps (payload , ensure_ascii = False , indent = 2 )} \n \n "
@@ -397,6 +399,25 @@ def _enabled_tool_names(self) -> list[str]:
397399 enabled_tools .append ("code_execution" )
398400 return enabled_tools
399401
402+ @staticmethod
403+ def _strip_template_knowledge_context (template : QuestionTemplate ) -> dict [str , Any ]:
404+ """Strip knowledge_context from template metadata to avoid prompt duplication."""
405+ template_dict = template .__dict__ .copy ()
406+ if isinstance (template_dict .get ("metadata" ), dict ):
407+ template_dict ["metadata" ] = {
408+ k : v
409+ for k , v in template_dict ["metadata" ].items ()
410+ if k != "knowledge_context"
411+ }
412+ return template_dict
413+
414+ @classmethod
415+ def _format_previous_questions (cls , questions : list [str ] | None ) -> str :
416+ if not questions :
417+ return ""
418+ capped = questions [- cls .MAX_PREVIOUS_QUESTIONS :]
419+ return "\n " .join (f"{ i } . { q } " for i , q in enumerate (capped , 1 ))
420+
400421 @staticmethod
401422 def _parse_json_like (content : str ) -> dict [str , Any ]:
402423 if not content or not content .strip ():
0 commit comments