Tradution en français des prompts

This commit is contained in:
2026-09-06 19:28:54 +02:00
parent 2313d51a62
commit 06d7bad04e
2 changed files with 187 additions and 145 deletions
+67 -45
View File
@@ -42,47 +42,54 @@ api_key = config.API_KEY
# --- Modèles pour la Requête 1 ---
class QuestionOnlyItem(BaseModel):
label: str = Field(description="The unique label of the question (e.g., '1.a', 'Exercice 1')")
question_content: str = Field(description="The source text of the question, strictly extracted from the enonce file, EXCLUDING the label itself.")
label: str = Field(description="Label unique de la question (par exemple '1.a' ou 'Exercice 1').")
question_content: str = Field(description="Texte source de la question, extrait exactement du fichier d’énoncé, SANS le label lui-même.")
class ExamQuestions(BaseModel):
questions: list[QuestionOnlyItem]
# --- Modèles pour la Requête 2 ---
class SolutionOnlyItem(BaseModel):
label: str = Field(description="The exact unique label of the question provided in the input.")
solution_content: str = Field(description="The source text of the solution, strictly extracted from the correction file.")
label: str = Field(description="Label exact de la question fourni en entrée, à conserver sans traduction.")
solution_content: str = Field(description="Texte source de la solution, extrait exactement du fichier de correction.")
class ExamSolutions(BaseModel):
solutions: list[SolutionOnlyItem]
# --- Modèles pour la Requête 3 ---
class ExtractedContext(BaseModel):
target_question_label: str = Field(description="The exact label of the FIRST question that comes immediately AFTER this information in the exam.")
last_question_label: str = Field(description="The exact label of the LAST question that uses or relies on this information.")
context_content: str = Field(description="The source text of the definitions, notations, or hypotheses, extracted from the enonce.")
target_question_label: str = Field(description="Label exact de la PREMIÈRE question située immédiatement APRÈS cette information dans l’énoncé.")
last_question_label: str = Field(description="Label exact de la DERNIÈRE question qui utilise cette information.")
context_content: str = Field(description="Texte source des définitions, notations ou hypothèses, extrait de l’énoncé.")
class ExamContext(BaseModel):
contexts: list[ExtractedContext]
# --- Modèles pour la Requête 4 (Barèmes) ---
class RubricItem(BaseModel):
label: str = Field(description="The exact label of the question.")
rubric_content: str = Field(description="Le barème détaillé en français.")
label: str = Field(description="Label exact de la question, à conserver sans traduction.")
rubric_content: str = Field(description="Barème détaillé sur 4 points : toutes les consignes, explications et justifications doivent être rédigées en français.")
class GroupRubrics(BaseModel):
rubrics: list[RubricItem]
PROMPT_4 = """Je te fournis les questions, le contexte éventuel, et les corrections pour un groupe de questions d'un examen.
Ta tâche :
Établir un barème de correction détaillé en français pour CHAQUE question.
Établir un barème de correction détaillé pour CHAQUE question.
Rédige intégralement en français le contenu de chaque champ `rubric_content`,
y compris les consignes de notation, les explications et les justifications,
même si certains textes fournis sont dans une autre langue.
Conserve les formules mathématiques, les labels exacts des questions et les
clés JSON `rubrics`, `label` et `rubric_content` sans les traduire.
Chaque question DOIT être notée sur exactement 4 points. Propose une répartition logique de ces points.
Par exemple :
- Au moins 2 points si le résultat est correct.
- Mettre la moitié des points si le raisonnement est correct mais pas le résultat.
- Retirer 1.5 points si les hypothèses d'un théorème ou d'une question précédente ne sont pas vérifiées.
- Retirer 1,5 point si les hypothèses d'un théorème ou d'une question précédente ne sont pas vérifiées.
Renvoie le résultat sous forme de liste JSON correspondant aux labels des questions fournies.
Renvoie uniquement un objet JSON contenant une liste `rubrics`. Pour chaque
question fournie, cette liste contient un objet avec son `label` exact et
son barème en français dans `rubric_content`.
"""
# --- Modèle fusionné (pour le reste du script) ---
@@ -102,50 +109,64 @@ class ExamExtraction(BaseModel):
class GroupedExamExtraction(BaseModel):
groups: list[list[QuestionItem | ContextItem]]
PROMPT_1 = """I am providing:
1. A PDF of an exam (`enonce.pdf`)
2. The source code of the exam questions (`enonce` file)
PROMPT_1 = """Je te fournis :
1. Le PDF d'un examen (`enonce.pdf`).
2. Le code source de ses questions (fichier `enonce`).
Your task:
1. Identify all distinct question labels using the PDF document.
These labels should be unique : use `Ex 1 : 1)a)` or `I)1)b)`.
2. For each label, extract its exact corresponding question text
from the `enonce` source file. Do not include the label itself
in this extracted text (nor LaTeX like `item` nor org-mode list
labelling like `2.`).
Return the result as a JSON list in the exact reading order of the document.
Ta tâche :
1. Identifie tous les labels distincts des questions à l'aide du PDF.
Ils doivent être uniques : utilise par exemple `Ex 1 : 1)a)` ou `I)1)b)`.
2. Pour chaque label, extrais exactement le texte de la question
correspondante dans le fichier source `enonce`. N'inclus ni le label
lui-même, ni les commandes de liste LaTeX comme `item`, ni les marques
de liste org-mode comme `2.`.
Ne reformule pas et ne traduis pas le texte extrait ; conserve le LaTeX.
Renvoie les questions dans l'ordre exact de lecture du document, dans la
liste `questions` de l'objet JSON attendu. Conserve les clés `label` et
`question_content`.
"""
PROMPT_2 = """I am providing:
1. A JSON list of question labels and their texts extracted from an exam.
2. The source code of the exam solutions (`correction` file).
PROMPT_2 = """Je te fournis :
1. Une liste JSON des labels des questions d'un examen et de leurs textes.
2. Le code source du corrigé de l'examen (fichier `correction`).
Your task:
For each question label provided in the JSON, extract its exact corresponding solution textual
content from the `correction` source file. Return the result as a JSON list in the exact same order.
Pour chaque label fourni, extrais exactement le texte de la solution
correspondante dans le fichier source `correction`. Ne reformule pas et
ne traduis pas le texte extrait ; conserve le LaTeX.
Renvoie les solutions dans le même ordre que les questions, dans la liste
`solutions` de l'objet JSON attendu. Conserve les clés `label` et
`solution_content` ainsi que les labels exacts des questions.
"""
PROMPT_3 = """I am providing:
1. A JSON list of question labels and their texts extracted from an exam.
2. The source code of the exam questions (`enonce` file).
PROMPT_3 = """Je te fournis :
1. Une liste JSON des labels des questions d'un examen et de leurs textes.
2. Le code source des questions de l'examen (fichier `enonce`).
Your task:
Extract important information necessary to understand the questions (e.g., definitions of objects, global notations, hypotheses, context) that are NOT part of the question texts themselves. Often, this information can be in a previous \\item that is not itself a question, but contains the question items.
Extrais les informations importantes nécessaires à la compréhension des
questions, mais qui ne font PAS partie des textes des questions :
définitions des objets, notations générales, hypothèses ou contexte.
Ces informations figurent souvent dans un \\item précédent qui ne constitue
pas lui-même une question, mais contient une liste de questions.
For example, given LaTeX code like
Par exemple, dans ce code LaTeX :
\\item Let N, M be two commutating matrices
\\item Soient N et M deux matrices qui commutent.
\\begin{itemize}
\\item Prove that N, M have a common eigenvector
\\item Prove that N, M are co-trigonalizable.
\\item Montrer que N et M ont un vecteur propre commun.
\\item Montrer que N et M sont simultanément trigonalisables.
\\end{itemize}
the `Let N, M be two commutating matrices` part is not a question itself, and is important information to understand the next two questions.
La phrase « Soient N et M deux matrices qui commutent » n'est pas une
question ; elle est nécessaire pour comprendre les deux questions suivantes.
For each extracted piece of information, identify:
1. The label of the FIRST question that comes immediately AFTER this information in the exam.
2. The label of the LAST question that uses or relies on this information.
Return the result as a JSON list.
Pour chaque information extraite, identifie :
1. Le label de la PREMIÈRE question située immédiatement APRÈS cette
information dans l'énoncé (`target_question_label`).
2. Le label de la DERNIÈRE question qui utilise cette information
(`last_question_label`).
Conserve le texte source dans `context_content`, sans le reformuler ni le
traduire, et conserve le LaTeX ainsi que les labels exacts.
Renvoie le résultat dans la liste `contexts` de l'objet JSON attendu.
"""
def find_file(folder: Path, base_name: str) -> Path | None:
@@ -245,7 +266,7 @@ def process_exam(
role="user",
parts=[
types.Part.from_text(text=PROMPT_2),
types.Part.from_text(text=f"--- EXTRACTED QUESTIONS ---\n{extracted_questions_json}"),
types.Part.from_text(text=f"--- QUESTIONS EXTRAITES ---\n{extracted_questions_json}"),
types.Part.from_text(text=f"--- CORRECTION SOURCE ({correction_path.name}) ---\n{correction_text}"),
],
)
@@ -281,7 +302,7 @@ def process_exam(
role="user",
parts=[
types.Part.from_text(text=PROMPT_3),
types.Part.from_text(text=f"--- EXTRACTED QUESTIONS ---\n{extracted_questions_json}"),
types.Part.from_text(text=f"--- QUESTIONS EXTRAITES ---\n{extracted_questions_json}"),
types.Part.from_text(text=f"--- ENONCE SOURCE ({enonce_path.name}) ---\n{enonce_text}"),
],
)
@@ -691,6 +712,7 @@ def process_exam(
]
config_4 = types.GenerateContentConfig(
system_instruction="Rédige tous les barèmes et consignes de notation en français. Conserve les clés JSON, les labels et les formules mathématiques.",
temperature=0.2,
response_mime_type="application/json",
response_json_schema=GroupRubrics.model_json_schema(),
+120 -100
View File
@@ -2,89 +2,97 @@ from pathlib import Path
import io
from . import utils
main_prompt = """I'm giving you an image of several written answers to an exam.
main_prompt = """Je te fournis une image contenant plusieurs réponses manuscrites à un examen.
Each answer is separated by a black horizontal line, and underneath,
to the left, is indicated the ID of the answer, from `01` to `50`.
Chaque réponse est séparée de la précédente par une ligne horizontale noire.
En dessous de cette ligne, à gauche, figure l'identifiant de la réponse,
compris entre `01` et `50`.
I want you to score each answer, from 0 to 4, you may score half
points, such as 2.5. Even if a result is wrong, if the reasoning is
correct and could lead to a right answer, you should give at least
half the points.
Attribue à chaque réponse une note de 0 à 4. Les demi-points sont autorisés,
par exemple 2.5. Même si le résultat est faux, accorde au moins la moitié
des points si le raisonnement est correct et pourrait conduire au bon résultat.
You also need to give feedback to the student, in french :
- which part of his answer is wrong,
- why is it wrong
- possibly, what he should have done instead.
Your feedback may contain LaTeX fragments written like `$a^2 + b^2 = c^2$`.
Rédige tous les commentaires destinés à l'élève en français. Indique :
- quelle partie de sa réponse est fausse ;
- pourquoi elle est fausse ;
- éventuellement, ce qu'il aurait fallu faire à la place.
Les commentaires peuvent contenir des fragments LaTeX, par exemple
`$a^2 + b^2 = c^2$`.
If your score is not 4, you should always provide some feedback
explaining what's missing.
Si la note n'est pas 4, fournis toujours un commentaire expliquant ce qui
manque, sauf dans le cas `empty-answer` décrit ci-dessous.
For each piece of feedback, if it is related to a specific part of the
answer that is wrong, you may provide a `box_2d`, to locate this
specific part of the answer. This `box_2d` should be in the form
[ymin, xmin, ymax, xmax] normalized to 0-1000. If you do not provide
one, set `box_2d` to `null`.
Lorsqu'un commentaire concerne une erreur située dans une partie précise
de la réponse, tu peux fournir un champ `box_2d` pour la localiser.
Ses coordonnées doivent être de la forme [ymin, xmin, ymax, xmax],
normalisées entre 0 et 1000. Sinon, attribue la valeur `null` à `box_2d`.
If the answer is correct, there is no need to provide feedback. You do
not have to give positive feedback, but if you do, do not provide a
`box_2d` for it.
Si la réponse est correcte, aucun commentaire n'est nécessaire. Tu n'es pas
obligé de faire des commentaires positifs ; si tu en fais, ne leur associe
pas de `box_2d`.
For example, if the student says a function is continuous when it
isn't, provide the coordinates where the word «continuous» is. If a
calculation went wrong, gives the coordinates of the step where it
goes wrong, and as feedback, what went wrong.
Par exemple, si l'élève affirme à tort qu'une fonction est continue,
localise le mot « continue ». Si un calcul est faux, localise l'étape où
l'erreur apparaît et explique cette erreur dans le commentaire.
Avoid giving feedback about confusing letters `n` with `m`, `x` with
`n` or `h` with `k`. If it looks wrong, assume you read it wrong,
unless the distinction is very important.
Évite les commentaires portant sur une confusion entre les lettres `n`
et `m`, `x` et `n`, ou `h` et `k`. En cas de doute, suppose que tu as mal
lu, sauf si la distinction est très importante.
In some case, you may find that either
- The student didn't answer the right question. Set the score to 0.
Since it could be a labeling error, indicate it by setting `error`
to \"wrong-label\".
- You can find an answer to another question of the exercice (taking
more than a couple of lines). Score the question you are supposed
to score, but set `error` to \"additional-answer\".
- The answer to the question is empty, or the student has only
rewritten the statement of the question. In this case, set `error`
to \"empty-answer\" and do not provide any kind of feedback.
If there's no error, set `error` to `\"\"`.
Certains cas nécessitent une valeur particulière du champ `error` :
- L'élève n'a pas répondu à la bonne question : attribue la note 0 et
indique `wrong-label`, car il peut s'agir d'une erreur de label.
- La réponse contient aussi une réponse à une autre question de
l'exercice, sur plus de quelques lignes : note la question demandée,
mais indique `additional-answer`.
- La réponse est vide, ou l'élève a seulement recopié l'énoncé : indique
`empty-answer` et ne fournis aucun commentaire.
S'il n'y a aucune de ces erreurs, attribue la chaîne vide `""` à `error`.
You will answer using json describing a list of dictionary with a key
\"id\", and a key \"result\" that contains the \"score\", a list
\"feedback\", and possibly an \"error\". Like this example :
Réponds uniquement en JSON, sous la forme d'une liste d'objets contenant
les clés `id` et `result`. L'objet `result` contient `score`, la liste
`feedback` et `error`. Chaque commentaire contient `text` et `box_2d`.
Conserve exactement ces clés, les identifiants et les valeurs techniques
de `error` : ne les traduis pas. Le contenu de chaque champ `text` doit
être en français, même si certains documents fournis sont dans une autre langue.
[{ \"id\": \"01\",
\"result\": {\"score\" : 2.5,
\"feedback\": [{text: \"Un retour générique. Il faut apprendre le cours.\", box_2d: null},
{text: \"Non, la fonction n'est pas forcément continue\", pos: [145, 280, 340, 500]}],
\"error\": \"\"}
},
{ \"id\": \"04\",
\"result\": {\"score\" : 4.,
\"feedback\" : []
\"error\": \"\" }
}
Exemple :
```json
[
{
"id": "01",
"result": {
"score": 2.5,
"feedback": [
{"text": "Il manque la vérification des hypothèses du théorème.", "box_2d": null},
{"text": "Non, la fonction n'est pas forcément continue.", "box_2d": [145, 280, 340, 500]}
],
"error": ""
}
},
{
"id": "04",
"result": {"score": 4.0, "feedback": [], "error": ""}
}
]
```
Here is the text of the exercice (or the relevant part of the problem)
of the exam :
Voici l'énoncé de l'exercice ou la partie pertinente du problème :
```
<<text>>
```
Here is a possible correct answer :
Voici un corrigé possible :
```
<<corr>>
```
<<persp>>
You are asked to score the question or exercice labeled `<<label>>`,
do not score or give feedback to any other question."""
Tu dois noter uniquement la question ou l'exercice portant le label
`<<label>>`. Ne note aucune autre question et ne formule aucun commentaire
sur les autres questions."""
from .utils import get_label_text_content, get_label_sol_content, get_label_persp_content
@@ -95,7 +103,7 @@ def make_prompt(input_dir,full_label):
# print("Debug : l/t/c/p", full_label, text, corr, persp)
if persp:
persp = "\n\nHere are additional scoring instructions : \n\n```\n" + persp +"\n```\n"
persp = "\n\nVoici des consignes de notation complémentaires : \n\n```\n" + persp +"\n```\n"
return main_prompt.replace("<<text>>", text).replace("<<corr>>", corr).replace("<<persp>>", persp).replace("<<label>>", full_label)
@@ -103,17 +111,17 @@ from pydantic import BaseModel, Field, TypeAdapter
from typing import List, Optional, Tuple
class FeedbackItem(BaseModel):
text: str = Field(description="Feedback content")
box_2d: Optional[List[int]] = Field(None, description="box coordinates or null")
text: str = Field(description="Commentaire destiné à l’élève, rédigé en français.")
box_2d: Optional[List[int]] = Field(None, description="Coordonnées [ymin, xmin, ymax, xmax] normalisées entre 0 et 1000, ou null.")
class ResultData(BaseModel):
score: float = Field(description="The numeric score")
feedback: List[FeedbackItem] = Field(description="List of feedback items")
error: str = Field(description="Indicates if an error occurred")
score: float = Field(description="Note numérique de la réponse, sur 4 points.")
feedback: List[FeedbackItem] = Field(description="Liste des commentaires destinés à l’élève, rédigés en français.")
error: str = Field(description="Type derreur : wrong-label, additional-answer, empty-answer, ou chaîne vide.")
class EvaluationEntry(BaseModel):
id: str = Field(description="Entry identifier")
result: ResultData = Field(description="Result details")
id: str = Field(description="Identifiant exact de la réponse.")
result: ResultData = Field(description="Note, commentaires en français et éventuelle erreur pour cette réponse.")
# These nested definitions do not work with the batch api, unroll them
UNROLLED_SCHEMA = {
@@ -121,24 +129,24 @@ UNROLLED_SCHEMA = {
"items": {
"type": "OBJECT",
"properties": {
"id": {"type": "STRING", "description": "Entry identifier"},
"id": {"type": "STRING", "description": "Identifiant exact de la réponse."},
"result": {
"type": "OBJECT",
"properties": {
"score": {"type": "NUMBER", "description": "The numeric score"},
"error": {"type": "STRING", "description": "Indicates if an error occurred"},
"score": {"type": "NUMBER", "description": "Note numérique de la réponse, sur 4 points."},
"error": {"type": "STRING", "description": "Type derreur : wrong-label, additional-answer, empty-answer, ou chaîne vide."},
"feedback": {
"type": "ARRAY",
"description": "List of feedback items",
"description": "Liste des commentaires destinés à l’élève, rédigés en français.",
"items": {
"type": "OBJECT",
"properties": {
"text": {"type": "STRING", "description": "Feedback content"},
"text": {"type": "STRING", "description": "Commentaire destiné à l’élève, rédigé en français."},
"box_2d": {
"type": "ARRAY",
"items": {"type": "INTEGER"},
"nullable": True,
"description": "box coordinates or null"
"description": "Coordonnées [ymin, xmin, ymax, xmax] normalisées entre 0 et 1000, ou null."
}
},
"required": ["text"]
@@ -216,20 +224,27 @@ def request_for_box_correction(pdf_path, original_feedbacks):
localized_feedbacks = [f for f in original_feedbacks if f["box_2d"]]
prompt = f"""
Here is a single student's submission to a question in a written exam. The following JSON contains feedback items with bounding boxes (box_2d) that are incorrect. Each piece of feedback is supposed to be related to a piece of the answer that is wrong.
prompt = f"""Voici la réponse d'un élève à une question d'examen. Le JSON
ci-dessous contient des commentaires dont les rectangles de localisation
(`box_2d`) sont incorrects. Chaque commentaire doit correspondre à la
partie de la réponse où se trouve l'erreur signalée.
For example, if the student says a function is continuous when it
isn't, the coordinates should be where the word «continuous» is. If a
calculation went wrong, the coordinates should be where the step where
it goes wrong, and the feedback is what went wrong.
Par exemple, si l'élève affirme à tort qu'une fonction est continue,
les coordonnées doivent localiser le mot « continue ». Si un calcul est
faux, elles doivent localiser l'étape où apparaît l'erreur expliquée dans
le commentaire.
Please analyze the image and return the same feedback json content, but with ONLY the box_2d coordinates corrected for this specific image.
Coordinates must be [ymin, xmin, ymax, xmax] scaled to 1000. If a box is invalid/not found, return null for it.
Original feedback:
Analyse l'image et renvoie le même contenu JSON en corrigeant UNIQUEMENT
les coordonnées `box_2d` pour cette image. Conserve les commentaires en
français à l'identique : ne les reformule pas et ne les traduis pas.
Conserve les noms des clés JSON.
Les coordonnées doivent être [ymin, xmin, ymax, xmax], normalisées entre
0 et 1000. Si la zone est introuvable ou le rectangle invalide, renvoie
`null` pour ce rectangle.
{json.dumps(localized_feedbacks, indent=2)}
"""
Commentaires d'origine :
{json.dumps(localized_feedbacks, indent=2, ensure_ascii=False)}
"""
@@ -252,20 +267,21 @@ it goes wrong, and the feedback is what went wrong.
def request_for_wrong_label(pdf_path, label, enonce, labels_txt):
prompt = f"""This image is a part of the answer of a student to a written exam.
prompt = f"""Cette image représente une partie de la réponse d'un élève à un examen.
It was initially labeled '{label}' but I suspect this label is wrong. Perhaps the student himself wrote the wrong label.
Elle porte initialement le label '{label}', mais je soupçonne une erreur
de label. L'élève a peut-être lui-même écrit le mauvais label.
You need to analyse this image, and find the label of the question it answers. Do not trust the label written by the student but instead check the content of its answer and the notation he uses to identify the correct label of the question the student answered.
Analyse l'image et identifie le label de la question à laquelle cette
réponse correspond. Ne te fie pas au label écrit par l'élève : examine
le contenu de la réponse et les notations utilisées.
Return ONLY the exact label string.
Here is the full content of the exam :
Renvoie UNIQUEMENT le label exact, sans le modifier ni le traduire.
Voici l'énoncé complet de l'examen :
{enonce}
Here is a list of all possible labels. You need to answer with one of these :
Voici les labels possibles. Ta réponse doit être l'un d'entre eux :
{labels_txt}
"""
@@ -276,22 +292,26 @@ Here is a list of all possible labels. You need to answer with one of these :
return contents, config
def request_for_additional_answer(pdf_path, label, enonce, labels_txt):
prompt = f"""This image is a part of the answer of a student to a written exam.
prompt = f"""Cette image représente une partie de la réponse d'un élève à un examen.
It was initially labeled '{label}' but I suspect this image also contains answers to another, or several other questions.
Elle porte initialement le label '{label}', mais je soupçonne qu'elle
contient aussi des réponses à une ou plusieurs autres questions.
You need to analyse this image, and find the list of the labels of the questions it answers. Return ONLY the list of the exact label strings.
Analyse l'image et identifie les labels des questions auxquelles elle
répond. Renvoie UNIQUEMENT une liste JSON contenant les labels exacts,
sans les modifier ni les traduire.
If the end of the image only contains the first line of an answer to another question, ignore it.
Here is the full content of the exam :
Si le bas de l'image ne contient que la première ligne d'une réponse à
une autre question, ignore cette ligne.
Voici l'énoncé complet de l'examen :
{enonce}
Here is a list of all possible labels. You need to answer with a list one of these :
Voici les labels possibles. Chaque élément de ta liste doit être l'un
d'entre eux :
{labels_txt}
"""
contents = [types.Content(role="user", parts=[
types.Part.from_bytes(data=get_single_image_bytes(pdf_path), mime_type="image/jpeg"),
types.Part.from_text(text=prompt)