Skip to content

Commit a0ce450

Browse files
committed
refactor: separate previous_questions from history_context in quiz generation
Fixes semantic overloading introduced by PR #281 — history_context (conversation history) is no longer mixed with the generated-questions dedup list. - Add dedicated `previous_questions` parameter through Generator pipeline - Extract `_strip_template_knowledge_context()` reused by both generate and repair paths - Add `_format_previous_questions()` with MAX_PREVIOUS_QUESTIONS=20 cap - Move language labels into YAML templates (en/zh) to avoid language mixing - Simplify coordinator to pass raw list instead of string concatenation Made-with: Cursor
1 parent 43ea51b commit a0ce450

4 files changed

Lines changed: 46 additions & 30 deletions

File tree

deeptutor/agents/question/agents/generator.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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():

deeptutor/agents/question/coordinator.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -286,25 +286,14 @@ async def _generation_loop(
286286
},
287287
)
288288

289-
# Build cumulative context from questions already generated in this
290-
# session so the Generator can avoid producing duplicates.
291-
cumulative_history = history_context
292-
if generated_questions:
293-
previous = "\n".join(
294-
f"Q{i}: {q}" for i, q in enumerate(generated_questions, start=1)
295-
)
296-
if cumulative_history:
297-
cumulative_history = f"{cumulative_history}\n\nQuestions generated in this session:\n{previous}"
298-
else:
299-
cumulative_history = f"Questions generated in this session:\n{previous}"
300-
301289
success = True
302290
try:
303291
qa_pair = await generator.process(
304292
template=template,
305293
user_topic=user_topic,
306294
preference=preference,
307-
history_context=cumulative_history,
295+
history_context=history_context,
296+
previous_questions=generated_questions or None,
308297
)
309298
except Exception as exc:
310299
success = False

deeptutor/agents/question/prompts/en/generator.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ generate: |
1919
Tools enabled by the user:
2020
{available_tools}
2121
22-
Previously generated questions in this session (your new question MUST be different from all of these):
22+
Conversation context:
2323
{history_context}
2424
25+
Previously generated questions in this session (your new question MUST be different from all of these):
26+
{previous_questions}
27+
2528
Requirements:
2629
- Keep strict alignment with template.concentration and template.difficulty.
2730
- Respect template.question_type exactly. Do not silently change it.

deeptutor/agents/question/prompts/zh/generator.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ generate: |
1919
用户当前启用的工具:
2020
{available_tools}
2121
22-
本次会话中已生成的题目(你的新题目必须与以下所有题目不同)
22+
对话上下文
2323
{history_context}
2424
25+
本次会话中已生成的题目(你的新题目必须与以下所有题目不同):
26+
{previous_questions}
27+
2528
要求:
2629
- 严格对齐 template.concentration 与 template.difficulty。
2730
- 必须严格遵循 template.question_type,不要私自改题型。

0 commit comments

Comments
 (0)