UgoCodee commited on
Commit
7e1b74f
·
1 Parent(s): 6e21671

manual push

Browse files
Files changed (3) hide show
  1. Dockerfile +7 -0
  2. app.py +87 -0
  3. requierment.txt +8 -0
Dockerfile ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+ WORKDIR /app
3
+ COPY requirements.txt .
4
+ RUN pip install --no-cache-dir -r requirements.txt
5
+ COPY app.py .
6
+ EXPOSE 7860
7
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, zipfile, urllib.request
2
+ from fastapi import FastAPI
3
+ from pydantic import BaseModel
4
+ from langchain_community.document_loaders import PyPDFLoader
5
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
6
+ from langchain_nvidia_ai_endpoints import ChatNVIDIA, NVIDIAEmbeddings
7
+ from langchain_chroma import Chroma
8
+ from langchain_core.prompts import ChatPromptTemplate
9
+
10
+ # Dossier inscriptible dans le conteneur du Space
11
+ DOCS_DIR = "/tmp/documents/"
12
+
13
+ def telecharger_documents():
14
+ # --- code de téléchargement du notebook, adapté au conteneur ---
15
+ # ============================================================
16
+ # TELECHARGEMENT DU DOCUMENT
17
+ # ============================================================
18
+ # Telechargement direct (Python pur — compatible Windows/Mac/Colab)
19
+
20
+ import os, urllib.request
21
+
22
+ os.makedirs(DOCS_DIR, exist_ok=True)
23
+ dest = os.path.join(DOCS_DIR, "CONVENTION_SYNTEC.pdf")
24
+
25
+ if os.path.exists(dest):
26
+ print(f"Le fichier {dest} existe deja — telechargement ignore.")
27
+ else:
28
+ print("Telechargement de CONVENTION_SYNTEC.pdf...")
29
+ urllib.request.urlretrieve("https://github.com/archiducarmel/SupDeVinci_M1_MachineLearning_DeepLearning/releases/download/datas/CONVENTION_SYNTEC.pdf", dest)
30
+ print("OK.")
31
+
32
+ # Verification
33
+ size_kb = os.path.getsize(dest) / 1024
34
+ print(f"\\n✅ {dest} ({size_kb:.0f} Ko) pret dans ./documents/")
35
+
36
+ telecharger_documents()
37
+
38
+ # ----- Pipeline RAG (identique au notebook) -----
39
+ all_docs = []
40
+ for f in sorted(os.listdir(DOCS_DIR)):
41
+ if f.endswith('.pdf'):
42
+ all_docs.extend(PyPDFLoader(os.path.join(DOCS_DIR, f)).load())
43
+ chunks = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100).split_documents(all_docs)
44
+
45
+ # La clé NVIDIA est lue dans la variable d'environnement NVIDIA_API_KEY (secret du Space)
46
+ embeddings = NVIDIAEmbeddings(model="nvidia/llama-nemotron-embed-1b-v2", truncate="NONE")
47
+ vector_store = Chroma.from_documents(chunks, embeddings)
48
+ retriever = vector_store.as_retriever(search_kwargs={"k": 3})
49
+ llm = ChatNVIDIA(model="openai/gpt-oss-120b", temperature=0.2, max_completion_tokens=2048)
50
+
51
+ prompt = ChatPromptTemplate.from_template(
52
+ "Tu es un assistant RH expert de la convention collective Syntec. "
53
+ "Réponds à la QUESTION en t'appuyant UNIQUEMENT sur le CONTEXTE ci-dessous.\\n"
54
+ "Si l'information n'y figure pas, réponds exactement : « Je ne sais pas ».\\n"
55
+ "Sois concis et cite la source (document et numéro de page).\\n\\n"
56
+ "CONTEXTE :\\n{context}\\n\\nQUESTION : {input}")
57
+
58
+ def extract_answer(response):
59
+ text = (response.content or "").strip()
60
+ if not text:
61
+ text = (response.additional_kwargs.get("reasoning_content", "") or "").strip()
62
+ return text
63
+
64
+ def format_docs(docs):
65
+ return "\\n\\n".join(f"[{d.metadata.get('source', '?').split('/')[-1]} — page {d.metadata.get('page')}] {d.page_content}" for d in docs)
66
+
67
+ generation = prompt | llm | extract_answer
68
+
69
+ def rag_answer(question):
70
+ docs = retriever.invoke(question)
71
+ answer = generation.invoke({"context": format_docs(docs), "input": question})
72
+ return {"answer": answer, "context": docs}
73
+
74
+ app = FastAPI(title="Assistant RH — Convention Syntec — API RAG")
75
+
76
+ class QuestionIn(BaseModel):
77
+ question: str
78
+
79
+ @app.get("/")
80
+ def health():
81
+ return {"status": "ok"}
82
+
83
+ @app.post("/ask")
84
+ def ask(payload: QuestionIn):
85
+ result = rag_answer(payload.question)
86
+ sources = [{"document": d.metadata.get("source", "?").split("/")[-1], "page": d.metadata.get("page")} for d in result["context"]]
87
+ return {"answer": result["answer"], "sources": sources}
requierment.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ langchain
4
+ langchain-community
5
+ langchain-chroma
6
+ langchain-nvidia-ai-endpoints
7
+ langchain-text-splitters
8
+ pypdf