Spaces:
Sleeping
Sleeping
| import json | |
| from typing import Dict, Any | |
| from . import prompts | |
| from .schemas import ( | |
| ItemExplanationInput, ItemExplanationOutput, | |
| MasteryDiagnosticInput, MasteryDiagnosticOutput, | |
| NextItemSelectorInput, NextItemSelectorOutput, | |
| SkillFeedbackInput, SkillFeedbackOutput, | |
| HintGenerationInput, HintGenerationOutput, | |
| ReflectionInput, ReflectionOutput, | |
| InstructorInsightInput, InstructorInsightRow, | |
| ExplanationCompressionInput, ExplanationCompressionOutput, | |
| QuestionAuthoringInput, QuestionAuthoringOutput, | |
| ToneNormalizerInput, ToneNormalizerOutput, | |
| ) | |
| from .validation import parse_and_validate | |
| from .cache import make_key, get as cache_get, set as cache_set | |
| from .adapters.qwen_adapter import QwenAdapter | |
| PRESETS = { | |
| 'item_explanation': dict(temperature=0.2, max_tokens=256), | |
| 'mastery_diagnostic': dict(temperature=0.2, max_tokens=128), | |
| 'next_item_selector': dict(temperature=0.2, max_tokens=128), | |
| 'skill_feedback': dict(temperature=0.3, max_tokens=256), | |
| 'hint_generation': dict(temperature=0.6, max_tokens=200), | |
| 'reflection': dict(temperature=0.3, max_tokens=120), | |
| 'instructor_insight': dict(temperature=0.2, max_tokens=160), | |
| 'explanation_compression': dict(temperature=0.2, max_tokens=80), | |
| 'question_authoring': dict(temperature=0.6, max_tokens=400), | |
| 'tone_normalizer': dict(temperature=0.2, max_tokens=60), | |
| } | |
| SYSTEMS = { | |
| 'item_explanation': prompts.item_explanation, | |
| 'mastery_diagnostic': prompts.mastery_diagnostic, | |
| 'next_item_selector': prompts.next_item_selector, | |
| 'skill_feedback': prompts.skill_feedback, | |
| 'hint_generation': prompts.hint_generation, | |
| 'reflection': prompts.reflection, | |
| 'instructor_insight': prompts.instructor_insight, | |
| 'explanation_compression': prompts.explanation_compression, | |
| 'question_authoring': prompts.question_authoring, | |
| 'tone_normalizer': prompts.tone_normalizer, | |
| } | |
| INPUT_MODELS = { | |
| 'item_explanation': ItemExplanationInput, | |
| 'mastery_diagnostic': MasteryDiagnosticInput, | |
| 'next_item_selector': NextItemSelectorInput, | |
| 'skill_feedback': SkillFeedbackInput, | |
| 'hint_generation': HintGenerationInput, | |
| 'reflection': ReflectionInput, | |
| 'instructor_insight': InstructorInsightInput, | |
| 'explanation_compression': ExplanationCompressionInput, | |
| 'question_authoring': QuestionAuthoringInput, | |
| 'tone_normalizer': ToneNormalizerInput, | |
| } | |
| OUTPUT_MODELS = { | |
| 'item_explanation': ItemExplanationOutput, | |
| 'mastery_diagnostic': MasteryDiagnosticOutput, | |
| 'next_item_selector': NextItemSelectorOutput, | |
| 'skill_feedback': SkillFeedbackOutput, | |
| 'hint_generation': HintGenerationOutput, | |
| 'reflection': ReflectionOutput, | |
| 'instructor_insight': InstructorInsightRow, # list validated separately | |
| 'explanation_compression': ExplanationCompressionOutput, | |
| 'question_authoring': QuestionAuthoringOutput, | |
| 'tone_normalizer': ToneNormalizerOutput, | |
| } | |
| _adapter = None | |
| SPECIAL_CACHE_KEYS = {'item_explanation', 'hint_generation'} | |
| def _get_adapter(model_id: str) -> QwenAdapter: | |
| global _adapter | |
| if _adapter is None: | |
| _adapter = QwenAdapter(model_name=model_id) | |
| return _adapter | |
| def _cache_key(prompt_name: str, input_data: Dict[str, Any], model_id: str, temperature: float) -> str: | |
| special = None | |
| if prompt_name in SPECIAL_CACHE_KEYS: | |
| if prompt_name == 'item_explanation': | |
| q = input_data.get('question', '') | |
| ua = input_data.get('user_answer', '') | |
| special = f"{q}\u241f{ua}" | |
| elif prompt_name == 'hint_generation': | |
| q = input_data.get('question', '') | |
| special = q | |
| base = json.dumps(input_data, sort_keys=True) | |
| parts = [prompt_name, base, model_id, temperature, special or '-'] | |
| return make_key(*parts) | |
| def run_prompt(prompt_name: str, input_payload: Dict[str, Any], *, model_id: str = 'Qwen/Qwen3-7B-Instruct', seed: int = 42) -> Any: | |
| if prompt_name not in PRESETS: | |
| raise ValueError(f'Unknown prompt: {prompt_name}') | |
| input_model = INPUT_MODELS[prompt_name] | |
| parsed_input = input_model.parse_obj(input_payload) | |
| preset = PRESETS[prompt_name] | |
| ckey = _cache_key(prompt_name, parsed_input.dict(by_alias=True), model_id, preset['temperature']) | |
| cached = cache_get(ckey) | |
| if cached is not None: | |
| return json.loads(cached) | |
| # Get adapter with lazy initialization | |
| adapter = _get_adapter(model_id) | |
| system = SYSTEMS[prompt_name]() | |
| user = json.dumps(parsed_input.dict(by_alias=True), ensure_ascii=False) | |
| text = adapter.generate( | |
| system=system, | |
| user=f"Return JSON only. No commentary.\nInput: {user}", | |
| temperature=preset['temperature'], | |
| max_tokens=preset['max_tokens'], | |
| stop=None, | |
| seed=seed, | |
| ) | |
| if prompt_name == 'instructor_insight': | |
| data = json.loads(text) | |
| if not isinstance(data, list): | |
| raise ValueError('Expected a JSON array') | |
| from .schemas import InstructorInsightRow | |
| validated = [InstructorInsightRow.parse_obj(x).dict() for x in data] | |
| out_obj = validated | |
| else: | |
| out_model = OUTPUT_MODELS[prompt_name] | |
| out_obj = parse_and_validate(out_model, text) | |
| # Handle RootModel (Pydantic v2) | |
| if hasattr(out_obj, 'root'): | |
| out_obj = out_obj.root | |
| elif hasattr(out_obj, 'dict'): | |
| out_obj = out_obj.dict(by_alias=True) | |
| elif hasattr(out_obj, '__root__'): | |
| out_obj = out_obj.__root__ | |
| cache_set(ckey, json.dumps(out_obj, ensure_ascii=False)) | |
| return out_obj | |