draw a diagram in tutor answers: validated JSON from the model, rendered by the app; add the fourth companion tedi
The model ends a shape question with <diagram>{json}</diagram>; rag/services/diagram.py
splits it off before mathtext, validates it against a whitelist and stores it on
Message.diagram (ai_tutor.0006). It reaches the app in the SSE done event and in
conversation history. Prompt tutor-v6-diagrams; a hint next to the question (shape
words only) raised gpt-4.1-mini from 3/12 to 12/12 diagrams, 0/8 on arithmetic.
progress.0005 adds tedi (600 xp) to the companion catalog.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,8 @@ def _annotation(retrieval_meta: dict, generation_meta: dict, *, from_cache: bool
|
||||
'completion_tokens': int(generation_meta.get('completionTokens') or 0),
|
||||
'latency_ms': int(generation_meta.get('latencyMs') or 0),
|
||||
'from_cache': bool(from_cache),
|
||||
# شکلِ اعتبارسنجیشده در rag.services.diagram؛ کشِ قدیمی آن را ندارد.
|
||||
'diagram': generation_meta.get('diagram') if isinstance(generation_meta.get('diagram'), dict) else None,
|
||||
}
|
||||
|
||||
|
||||
@@ -461,6 +463,7 @@ class AskAIView(APIView):
|
||||
'answer': answer_text,
|
||||
'suggestedFollowUps': follow_ups,
|
||||
'remainingQuota': remaining_quota,
|
||||
'diagram': assistant_message.diagram,
|
||||
'usedChunks': [
|
||||
{'title': c.get('title', ''), 'summary': c.get('summary', '')}
|
||||
for c in used_chunks
|
||||
@@ -486,7 +489,7 @@ class AskAIView(APIView):
|
||||
نسخهی SSE از post(): همون منطقِ retrieval + کوتا + idempotency، ولی
|
||||
پاسخ توکنبهتوکن استریم میشه. رویدادها:
|
||||
event: chunk data: {"delta": "..."}
|
||||
event: done data: {conversationId, messageId, remainingQuota, usedChunks[, retrievalDebug]}
|
||||
event: done data: {conversationId, messageId, remainingQuota, diagram, usedChunks[, retrievalDebug]}
|
||||
event: error data: {"detail": "..."}
|
||||
فاز ۱ (رزرو کوتا + conversation + user_message) قبلاً در post() انجام شده.
|
||||
"""
|
||||
@@ -529,11 +532,12 @@ class AskAIView(APIView):
|
||||
for c in used_chunks
|
||||
]
|
||||
parts: list[str] = []
|
||||
stream_result: dict = {}
|
||||
started = time.monotonic()
|
||||
effective_grade = retrieval_meta.get('gradeTitle') or grade_title or ''
|
||||
for delta in stream_answer(
|
||||
v['userQuestion'], used_chunks or None, effective_grade,
|
||||
history=history, focus=focus,
|
||||
history=history, focus=focus, result=stream_result,
|
||||
):
|
||||
parts.append(delta)
|
||||
yield _sse('chunk', {'delta': delta})
|
||||
@@ -548,6 +552,7 @@ class AskAIView(APIView):
|
||||
'promptVersion': PROMPT_VERSION,
|
||||
'latencyMs': int((time.monotonic() - started) * 1000),
|
||||
'hadContext': bool(used_chunks),
|
||||
'diagram': stream_result.get('diagram'),
|
||||
}
|
||||
if cacheable:
|
||||
answer_cache.set(cache_key, {
|
||||
@@ -591,6 +596,7 @@ class AskAIView(APIView):
|
||||
'messageId': str(existing.assistant_message_id),
|
||||
'suggestedFollowUps': existing.suggested_follow_ups,
|
||||
'remainingQuota': max(DAILY_AI_QUOTA - used_today, 0),
|
||||
'diagram': existing.assistant_message.diagram,
|
||||
'usedChunks': [],
|
||||
})
|
||||
return
|
||||
@@ -601,6 +607,7 @@ class AskAIView(APIView):
|
||||
'messageId': str(assistant_message.id),
|
||||
'suggestedFollowUps': follow_ups,
|
||||
'remainingQuota': max(DAILY_AI_QUOTA - used_today, 0),
|
||||
'diagram': assistant_message.diagram,
|
||||
'usedChunks': [
|
||||
{'title': c.get('title', ''), 'summary': c.get('summary', '')}
|
||||
for c in used_chunks
|
||||
@@ -663,6 +670,7 @@ class AskAIView(APIView):
|
||||
'answer': existing.assistant_message.text,
|
||||
'suggestedFollowUps': existing.suggested_follow_ups,
|
||||
'remainingQuota': remaining_quota,
|
||||
'diagram': existing.assistant_message.diagram,
|
||||
}
|
||||
return AskResponseSerializer(data)
|
||||
|
||||
@@ -683,7 +691,8 @@ class ConversationMessagesView(APIView):
|
||||
'conversationId': str(conversation.id),
|
||||
'lessonId': str(conversation.lesson_id) if conversation.lesson_id else None,
|
||||
'messages': [
|
||||
{'messageId': str(m.id), 'role': m.role, 'text': m.text} for m in messages
|
||||
{'messageId': str(m.id), 'role': m.role, 'text': m.text, 'diagram': m.diagram}
|
||||
for m in messages
|
||||
],
|
||||
}
|
||||
return Response(ConversationMessagesResponseSerializer(data).data)
|
||||
|
||||
Reference in New Issue
Block a user