File size: 6,606 Bytes
becae7c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7b2bb29
 
 
 
becae7c
8ec56e7
becae7c
8ec56e7
becae7c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c6677b8
becae7c
 
 
a70474f
d8e18ca
becae7c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65bc7fa
becae7c
 
 
 
 
 
 
 
 
 
 
7b2bb29
becae7c
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import os
from dotenv import load_dotenv
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langgraph.checkpoint.memory import MemorySaver
from typing import List
from typing_extensions import TypedDict
from typing import Annotated
from langgraph.graph.message import AnyMessage, add_messages
from langchain_core.messages import HumanMessage, AIMessage
from langgraph.graph import END, StateGraph, START
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from database import create_table, return_title, return_exercise, return_mot_cle, return_niveau
import logging
from langchain_google_genai import ChatGoogleGenerativeAI

create_table()
logging.basicConfig(
    format="%(asctime)s - %(levelname)s - %(message)s",
    level=logging.INFO
)

GOOGLE_API_KEY = ''

llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0.7)


prompt_aide = PromptTemplate.from_template(
"""
Tu es un expert en pédagogie de l'apprentissage de la programmation
Le langage utilisé pour l'apprentissage de la programmation est Python.
Tu dois aider un élève à résoudre un exercice de programmation Python.
Tu ne dois jamais donner la solution de l'exercice (même partiellement) à l'élève, juste lui donner des inddications lui permettant de résoudre lui même l'exercice
Tu dois t'adresser directement à l'élève.
L'élève ne peut pas te poser des questions, il peut juste te proposer son code.
Tu ne dois pas proposer à l'élève de te poser des questions
Il est inutile de proposer à l'élève de tester son code avec les exemples proposés.
Tu ne dois pas proposer aux élèves des modifications du programme qui sorte du cadre de l'exercice. Par exemple, pour l'exercice qui demande d'écrire une fonction moyenne, si dans l'énoncé il est précisé que l'on a un tableau non vide d'entier en paramètre, il est inutile de dire à l'élève que son programme doit gérer les tableaux vides.
Tu dois t'exprimer en français
Voici l'énoncé de l'exercice :
{enonce}
Voici le programme proposé par l'élève pour résoudre l'exercice :
{code}
Pour améliorer ta réponse, tu as aussi à ta disposition l'historique des différents programme proposés par l'élève et les différents conseils que tu lui a déjà donné :
{historique} 
""")

prompt_bilan = PromptTemplate.from_template(
"""
Tu es un expert en pédagogie de l'apprentissage de la programmation
Le langage utilisé pour l'apprentissage de la programmation est Python.
Ton rôle est de proposer un bilan sur la résolution d'un exercice réaliser par un élève.
Tu dois t'adresser directement à l'élève.
Cet élève vient de réussir l'exercice suivant :
{enonce}
Voici l'historique de la résolution de cet exercice (code de l'élève et conseil donnés par un expert)
{historique}
Tu dois faire un bilan sur les points forts de l'élève et les points à travailler
Ton bilan doit absolument être cohérent. Il vaut mieux ne rien mettre que de mettre une information inutile  
Tu dois proposer à l'élève un autre  exercice à résoudre parmi les exercices ci-dessous (pour chaque exercice tu as le titre de l'exercice, une liste de mots clé et un niveau allant de 1 à 4 (le niveau 1 étant le plus facile et le niveau 4 le plus difficile)) :
Tu ne dois UNIQUEMENT proposer un exercice appartenant à la liste ci-dessous.
Tu dois donner uniquement le titre et le numéro de l'exercice que tu proposes à l'élèves (inutile d'indiquer les mots clé liés à l'exercice)
Tu ne dois pas proposer l'exercice qui vient d'être résolu sauf si tu considères que l'élève n'a pas respecté les consignes données dans l'énoncé, à ce moment, tu dois lui demander de refaire l'exercice.
Quand l'élève a réussi un exercice tu dois lui proposer un exercice plus difficile (avec un niveau supérieur)
### LISTE DES EXERCICES
{mot_cle}
""")


descr_exo = ""
lst_titre = return_title()
lst_niveau = return_niveau()
lst_mot_cle = return_mot_cle()
for i in range(len(lst_niveau)):
    descr_exo += f"Exercie n° {lst_mot_cle[i]['id']} => titre : {lst_titre[i]['title']} ; mots clé : {lst_mot_cle[i]['mot_cle']} ; niveau : {lst_niveau[i]['niveau']}\n"

def history(hist):
    historical = ""
    for i in range(len(hist)):
        if i%2 == 0:
            historical += "code de l'éléve : \n"+hist[i].content+"\n"
        else :
            historical += "aide de l'expert : \n"+hist[i].content+"\n"
    return historical

memory = MemorySaver()
app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

class Request(BaseModel):
    id : str
    enonce : str
    code : str
    res_test : str

class AgentState(TypedDict):
    enonce : str
    messages: Annotated[list[AnyMessage], add_messages]
    res_test : str

def routeur(state : AgentState):
    if state['res_test'] == "1" or state['res_test'] == "0":
        return "aide"
    else :
        return "bilan"

def aide(state : AgentState):
    llm_aide = prompt_aide | llm | StrOutputParser()
    response = llm_aide.invoke({'enonce': state['enonce'], 'code' : state['messages'][-1].content, 'historique' : history(state['messages'])})
    return {"messages": [AIMessage(content=response)]}

def bilan(state : AgentState):
    llm_bilan = prompt_bilan | llm | StrOutputParser()
    response = llm_bilan.invoke({'enonce': state['enonce'], 'historique' : history(state['messages']), 'mot_cle': descr_exo})
    return {"messages": [AIMessage(content=response)]}

workflow = StateGraph(AgentState)

workflow.add_node("aide", aide)
workflow.add_node("bilan", bilan)

workflow.add_conditional_edges(
    START,
    routeur,
    {
        "aide": "aide",
        "bilan": "bilan"
    })
workflow.add_edge( "aide",END)
workflow.add_edge( "bilan",END)
graph = workflow.compile(checkpointer=memory)

@app.post('/request')
def request(req: Request):
    config = {"configurable": {"thread_id": req.id}}
    rep = graph.invoke({"enonce" : req.enonce,"messages": req.code, "res_test" : req.res_test},config , stream_mode="values")
    logging.info(rep['messages'][-1].content)
    return {"response":rep['messages'][-1].content}


@app.get('/title')
def get_title():
    tab_title = return_title()
    return {"title":tab_title}

@app.get('/exercise/{id}')
def get_exercise(id : int):
    ex = return_exercise(id)
    logging.info(f"Endpoint /exercise/{id} called")
    return {"title" : ex[1].replace("\n",""), "enonce" : ex[4], "test": ex[5]}