Spaces:
Running
Running
File size: 10,300 Bytes
8ddbeb8 d217f1b 8ddbeb8 fdbdd19 dcbec3a 8ddbeb8 20fe53d 8ddbeb8 20fe53d 8ddbeb8 20fe53d 8ddbeb8 d217f1b 8ddbeb8 e39a8bb 62d0cf5 8ddbeb8 20fe53d 8ddbeb8 d217f1b 8ddbeb8 d217f1b 68ee069 d217f1b 8ddbeb8 d217f1b 8ddbeb8 d217f1b 8ddbeb8 d217f1b 8ddbeb8 d217f1b 8ddbeb8 d217f1b fdbdd19 d217f1b |
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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
from langchain_core.prompts import ChatPromptTemplate
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_community.document_loaders import DirectoryLoader
from langchain_community.document_loaders import PyPDFLoader
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 langgraph.checkpoint.memory import MemorySaver
from fastapi import FastAPI, UploadFile, Form
from fastapi.middleware.cors import CORSMiddleware
from typing import Optional
from PIL import Image
import base64
from io import BytesIO
import os
import logging
import sys
import matplotlib
matplotlib.use('Agg') # Configuration du backend avant d'importer pyplot
import matplotlib.pyplot as plt
import numpy as np
import re
import json
logger = logging.getLogger('uvicorn.error')
logger.setLevel(logging.DEBUG)
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
logging.basicConfig(
format="%(asctime)s - %(levelname)s - %(message)s",
level=logging.INFO
)
llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0.7)
memory = MemorySaver()
file_path_sec = "./documents/seconde.pdf"
loader_sec = PyPDFLoader(file_path_sec)
sec = loader_sec.load()
file_path_prem = "./documents/premiere.pdf"
loader_prem = PyPDFLoader(file_path_prem)
prem = loader_prem.load()
file_path_term = "./documents/term.pdf"
loader_term = PyPDFLoader(file_path_term)
term = loader_term.load()
plot_graph = False
system = """
Tu es un chatbot spécialisé dans l’aide aux élèves de lycée en mathématiques (niveau seconde, première spécialité, terminale spécialité).
Ton rôle est d’aider les élèves à :
1- Comprendre le cours : Tu dois être capable d’expliquer une notion du programme de manière claire, rigoureuse, précise et adaptée au niveau lycée. Utilise des exemples concrets et du vocabulaire accessible, tout en restant mathématiquement correct.
2- S’exercer : Tu peux proposer des exercices sur la notion demandée, avec des niveaux de difficulté variés. Commence par un ou deux exercices simples, puis propose des questions un peu plus complexes si l’élève se sent à l’aise.
3- Être guidé dans un exercice : Si un élève te donne un énoncé, ne donne jamais directement la réponse. Pose-lui des questions guidées, propose-lui des pistes de réflexion, et encourage-le à progresser pas à pas pour qu’il trouve la solution par lui-même.
Avant de répondre à une question, vérifie toujours que la notion évoquée fait bien partie du programme de mathématiques du lycée :
- Si c’est le cas, réponds normalement.
- Si ce n’est pas au programme du lycée, informe poliment l’élève que la notion dépasse le cadre du lycée, mais tu peux tout de même lui donner une explication simplifiée pour nourrir sa curiosité.
Si jamais tu n’es pas absolument certain de la réponse, ne tente pas de deviner. Dis-le honnêtement à l’élève et conseille-lui de poser la question à son professeur.
**Important** : Tu dois toujours rester centré sur les mathématiques. N’aborde aucun sujet qui n’a pas de lien avec les mathématiques, même si l’élève te le demande.
Tu dois obligatoirement utiliser le format Latex pour écrire les formules mathématiques
"""
prompt = ChatPromptTemplate.from_messages(
[
("system", system),
("human", """
Voici les différents programme officiel qui te permettront d'aider l'élève :
Le programme de la classe de seconde :
{sec}
Le programme de la spécialité mathématiques en classe de première :
{prem}
Le programme de la spécialité mathématiques en classe de terminale :
{term}
Tu trouveras aussi l'historique conversation que tu as eu avec l'élève : \n {historical}
Et enfin l'intervention de l'élève : {question}"),
{graph}
""")
]
)
def format_historical(hist):
historical = []
for i in range(0,len(hist)-2,2):
historical.append("Utilisateur : "+hist[i].content[0]['text'])
historical.append("Assistant : "+hist[i+1].content[0]['text'])
return "\n".join(historical[-20:])
def generate_function_plot(expression, x_range=(-10, 10), num_points=1000):
try:
# Créer les points x
x = np.linspace(x_range[0], x_range[1], num_points)
# Évaluer la fonction
# Remplacer les expressions mathématiques courantes
expression = expression.replace('^', '**')
expression = expression.replace('sin', 'np.sin')
expression = expression.replace('cos', 'np.cos')
expression = expression.replace('tan', 'np.tan')
expression = expression.replace('exp', 'np.exp')
expression = expression.replace('log', 'np.log')
expression = expression.replace('ln', 'np.log')
expression = expression.replace('sqrt', 'np.sqrt')
expression = expression.replace('e', 'np.exp')
expression = expression.replace('exp', 'np.exp')
# Évaluer l'expression
y = eval(expression)
# Créer le graphique
plt.figure(figsize=(10, 6))
plt.plot(x, y)
plt.grid(True)
plt.axhline(y=0, color='k', linestyle='-', alpha=0.3)
plt.axvline(x=0, color='k', linestyle='-', alpha=0.3)
# Sauvegarder le graphique en mémoire
img_buffer = BytesIO()
plt.savefig(img_buffer, format='PNG')
plt.close()
img_buffer.seek(0)
# Convertir en base64
base64_img = base64.b64encode(img_buffer.getvalue()).decode('utf-8')
return base64_img
except Exception as e:
return None
class GraphState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
def should_plot(state: GraphState):
system_prompt = """Tu es un assistant expert en pédagogie et en mathématiques. Ta spécialité est l'enseignement de mathématiques au lycée.
Ta tâche est de déterminer si la demande de l'utilisateur nécessite la représentation graphique d'une fonction.
Si c'est le cas, tu dois extraire l'expression de la fonction.
Réponds sur une seule ligne avec le format suivant :
OUI:expression si un graphique est nécessaire
NON si aucun graphique n'est nécessaire
Exemples de réponses :
OUI:x**2
OUI:sin(x)
NON
"""
prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
("human", "Analyse cette demande : {question}")
])
question = state['messages'][-1].content[0]['text']
response = llm.invoke(prompt.format_messages(question=question))
try:
response_text = response.content.strip()
if response_text.startswith("OUI:"):
plot_graph = True
expression = response_text[4:].strip()
return {"should_plot": True, "expression": expression}
else :
plot_graph = False
return {"should_plot": False, "expression": None}
except Exception as e:
return {"should_plot": False, "expression": None}
def chatbot(state : GraphState):
plot_decision = should_plot(state)
if plot_graph :
msg_graph = "Une représentation graphique de la fonction a été fournis à l'élève, tu dois préciser dans ta réponse que cette représentation graphique a été fournis à l'élève"
else :
msg_graph = ""
question = prompt.invoke({'historical': format_historical(state['messages']),'sec':sec, 'prem':prem, 'term':term, 'graph':msg_graph, 'question' : state['messages'][-1].content[0]['text']})
q = question.messages[0].content + question.messages[1].content
if len(state['messages'][-1].content) > 1 :
response = llm.invoke([HumanMessage(content=[{"type": "text", "text": q},state['messages'][-1].content[1]])])
else :
response = llm.invoke([HumanMessage(content=[{"type": "text", "text": q}])])
if plot_decision["should_plot"] and plot_decision["expression"]:
plot_base64 = generate_function_plot(plot_decision["expression"])
if plot_base64:
return {"messages": [AIMessage(content=[{'type': 'text', 'text': response.content},{'type': 'image_url', 'image_url': {"url": f"data:image/png;base64,{plot_base64}"}}])]}
return {"messages": [AIMessage(content=[{'type': 'text', 'text': response.content}])]}
workflow = StateGraph(GraphState)
workflow.add_node('chatbot', chatbot)
workflow.add_edge(START, 'chatbot')
workflow.add_edge('chatbot', END)
app_chatbot = workflow.compile(checkpointer=memory)
@app.post('/request')
def request(id:Annotated[str, Form()], query:Annotated[str, Form()], image:Optional[UploadFile] = None):
config = {"configurable": {"thread_id": id}}
if image:
try:
img = Image.open(image.file)
img_buffer = BytesIO()
img.save(img_buffer, format='PNG')
byte_data = img_buffer.getvalue()
base64_img = base64.b64encode(byte_data).decode("utf-8")
message = HumanMessage(
content=[
{'type': 'text', 'text': query},
{'type': 'image_url', 'image_url': {"url": f"data:image/jpeg;base64,{base64_img}"}}
])
except:
return {"response":"Attention, vous m'avez fourni autre chose qu'une image. Renouvelez votre demande avec une image."}
rep = app_chatbot.invoke({"messages": message},config, stream_mode="values")
else :
rep = app_chatbot.invoke({"messages": [HumanMessage(content=[{'type': 'text', 'text': query}])]},config, stream_mode="values")
if len(rep['messages'][-1].content) > 1 and rep['messages'][-1].content[1].get('type') == 'image_url':
return {"response": rep['messages'][-1].content[0]['text'],"image": rep['messages'][-1].content[1]['image_url']['url']}
logging.info(query)
logging.info(rep['messages'][-1].content)
return {"response": rep['messages'][-1].content[0]['text']} |