Spaces:
Sleeping
Sleeping
Upload main.py
Browse files
main.py
ADDED
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.prompts import ChatPromptTemplate
|
2 |
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
3 |
+
from langchain_community.document_loaders import DirectoryLoader
|
4 |
+
from langchain_text_splitters import CharacterTextSplitter
|
5 |
+
from langchain_community.document_loaders import PyPDFLoader
|
6 |
+
from langchain_huggingface import HuggingFaceEmbeddings
|
7 |
+
from typing import List
|
8 |
+
from langchain_chroma import Chroma
|
9 |
+
from typing_extensions import TypedDict
|
10 |
+
from typing import Annotated
|
11 |
+
from langgraph.graph.message import AnyMessage, add_messages
|
12 |
+
from langchain_core.messages import HumanMessage, AIMessage
|
13 |
+
from langgraph.graph import END, StateGraph, START
|
14 |
+
from langgraph.checkpoint.memory import MemorySaver
|
15 |
+
from fastapi import FastAPI, UploadFile, Form
|
16 |
+
from fastapi.middleware.cors import CORSMiddleware
|
17 |
+
from typing import Optional
|
18 |
+
from PIL import Image
|
19 |
+
import base64
|
20 |
+
from io import BytesIO
|
21 |
+
import os
|
22 |
+
import logging
|
23 |
+
import sys
|
24 |
+
|
25 |
+
logger = logging.getLogger('uvicorn.error')
|
26 |
+
logger.setLevel(logging.DEBUG)
|
27 |
+
|
28 |
+
app = FastAPI()
|
29 |
+
|
30 |
+
app.add_middleware(
|
31 |
+
CORSMiddleware,
|
32 |
+
allow_origins=["*"],
|
33 |
+
allow_credentials=True,
|
34 |
+
allow_methods=["*"],
|
35 |
+
allow_headers=["*"],
|
36 |
+
)
|
37 |
+
|
38 |
+
QOOGLE_API = "AIzaSyByFsTIeE6ifzSsAl__cqGGWZ20JXznsJE"
|
39 |
+
|
40 |
+
llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0.5, api_key = QOOGLE_API)
|
41 |
+
|
42 |
+
memory = MemorySaver()
|
43 |
+
|
44 |
+
glob_pattern="./*.md"
|
45 |
+
directory_path = "./documents"
|
46 |
+
loader = DirectoryLoader(directory_path, glob=glob_pattern)
|
47 |
+
doc_2025 = loader.load()
|
48 |
+
|
49 |
+
file_path_prob = "./documents/prob.pdf"
|
50 |
+
loader_prob = PyPDFLoader(file_path_prob)
|
51 |
+
prob = loader_prob.load()
|
52 |
+
|
53 |
+
file_path_guide = "./documents/prob.pdf"
|
54 |
+
loader_guide = PyPDFLoader(file_path_guide)
|
55 |
+
guide = loader_guide.load()
|
56 |
+
|
57 |
+
file_path_QR = "./documents/prob.pdf"
|
58 |
+
loader_QR = PyPDFLoader(file_path_QR)
|
59 |
+
QR = loader_QR.load()
|
60 |
+
|
61 |
+
system = """
|
62 |
+
Tu es un assistant expert en pédagogie. Ta spécialité est le **grand oral** (épreuve du bacalauréat technique et général).
|
63 |
+
Ton interlocuteur est un élève qui est en train de préparer son épreuve de grand oral.
|
64 |
+
Ton rôle est d'aider l'élève à préparer son grand oral
|
65 |
+
Tu dois répondre à toutes ses questions : organisation de l'épreuve, but de l'épreuve...
|
66 |
+
Tu dois aussi l'aider à trouver un sujet et à construire sa problématique.
|
67 |
+
Tu dois uniquement discuter du grand oral avec ton interlocuteur
|
68 |
+
Si tu ne connais pas la réponse à une question, propose à l'élève de demander à son professeur.
|
69 |
+
"""
|
70 |
+
|
71 |
+
prompt = ChatPromptTemplate.from_messages(
|
72 |
+
[
|
73 |
+
("system", system),
|
74 |
+
("human", """
|
75 |
+
Voici différents documents qui t'aideront à répondre aux questions des élèves :
|
76 |
+
Le guide du grand oral :
|
77 |
+
{guide}
|
78 |
+
Un ensemble de Question-Réponse sur le Grand Oral:
|
79 |
+
{QR}
|
80 |
+
Un texte qui explique la notion de sujet et de problématique:
|
81 |
+
{prob}
|
82 |
+
Les informations à jour sur le déroulement de l'épreuve (si tu trouves des informations qui te semble contradictoires dans les documents ci-dessous, c'est le documents ci-dessous qui doit être ta source d'informations) :
|
83 |
+
{doc_2025}
|
84 |
+
Tu trouveras aussi l'historique conversation avec l'élève : \n {historical}
|
85 |
+
Et enfin l'intervention de l'élève : {question}"),
|
86 |
+
""")
|
87 |
+
]
|
88 |
+
)
|
89 |
+
|
90 |
+
def format_historical(hist):
|
91 |
+
historical = []
|
92 |
+
for i in range(0,len(hist)-2,2):
|
93 |
+
historical.append("Utilisateur : "+hist[i].content[0]['text'])
|
94 |
+
historical.append("Assistant : "+hist[i+1].content[0]['text'])
|
95 |
+
return "\n".join(historical[-20:])
|
96 |
+
|
97 |
+
|
98 |
+
class GraphState(TypedDict):
|
99 |
+
messages: Annotated[list[AnyMessage], add_messages]
|
100 |
+
|
101 |
+
def chatbot(state : GraphState):
|
102 |
+
question = prompt.invoke({'historical': format_historical(state['messages']),'prob':prob, 'doc_2025':doc_2025, 'guide':guide, 'QR':QR , 'question' : state['messages'][-1].content[0]['text']})
|
103 |
+
q = question.messages[0].content + question.messages[1].content
|
104 |
+
if len(state['messages'][-1].content) > 1 :
|
105 |
+
response = llm.invoke([HumanMessage(
|
106 |
+
content=[
|
107 |
+
{"type": "text", "text": q},
|
108 |
+
state['messages'][-1].content[1]
|
109 |
+
])])
|
110 |
+
else :
|
111 |
+
response = llm.invoke([HumanMessage(
|
112 |
+
content=[
|
113 |
+
{"type": "text", "text": q}
|
114 |
+
])])
|
115 |
+
return {"messages": [AIMessage(content=[{'type': 'text', 'text': response.content}])]}
|
116 |
+
|
117 |
+
workflow = StateGraph(GraphState)
|
118 |
+
workflow.add_node('chatbot', chatbot)
|
119 |
+
|
120 |
+
workflow.add_edge(START, 'chatbot')
|
121 |
+
workflow.add_edge('chatbot', END)
|
122 |
+
|
123 |
+
app_chatbot = workflow.compile(checkpointer=memory)
|
124 |
+
|
125 |
+
@app.post('/request')
|
126 |
+
def request(id:Annotated[str, Form()], query:Annotated[str, Form()], image:Optional[UploadFile] = None):
|
127 |
+
config = {"configurable": {"thread_id": id}}
|
128 |
+
if image:
|
129 |
+
try:
|
130 |
+
img = Image.open(image.file)
|
131 |
+
img_buffer = BytesIO()
|
132 |
+
img.save(img_buffer, format='PNG')
|
133 |
+
byte_data = img_buffer.getvalue()
|
134 |
+
base64_img = base64.b64encode(byte_data).decode("utf-8")
|
135 |
+
message = HumanMessage(
|
136 |
+
content=[
|
137 |
+
{'type': 'text', 'text': query},
|
138 |
+
{'type': 'image_url', 'image_url': {"url": f"data:image/jpeg;base64,{base64_img}"}}
|
139 |
+
])
|
140 |
+
except:
|
141 |
+
return {"response":"Attention, vous m'avez fourni autre chose qu'une image. Renouvelez votre demande avec une image."}
|
142 |
+
rep = app_chatbot.invoke({"messages": message},config, stream_mode="values")
|
143 |
+
else :
|
144 |
+
rep = app_chatbot.invoke({"messages": [HumanMessage(content=[{'type': 'text', 'text': query}])]},config, stream_mode="values")
|
145 |
+
return {"response":rep['messages'][-1].content[0]['text']}
|