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

manual push

Browse files
Files changed (4) hide show
  1. .env +1 -1
  2. .gitignore +4 -0
  3. app.py +62 -45
  4. requierment.txt → requirements.txt +2 -1
.env CHANGED
@@ -1 +1 @@
1
- NVIDIA_API_KEY = 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
 
1
+ NVIDIA_API_KEY=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
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ .env
2
+ .idea/
3
+ __pycache__/
4
+ *.pyc
app.py CHANGED
@@ -1,4 +1,6 @@
1
- import os, zipfile, urllib.request
 
 
2
  from fastapi import FastAPI
3
  from pydantic import BaseModel
4
  from langchain_community.document_loaders import PyPDFLoader
@@ -7,53 +9,28 @@ 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()
@@ -61,27 +38,67 @@ def extract_answer(response):
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}
 
 
 
 
1
+ import os, urllib.request
2
+ from contextlib import asynccontextmanager
3
+ from dotenv import load_dotenv
4
  from fastapi import FastAPI
5
  from pydantic import BaseModel
6
  from langchain_community.document_loaders import PyPDFLoader
 
9
  from langchain_chroma import Chroma
10
  from langchain_core.prompts import ChatPromptTemplate
11
 
12
+ load_dotenv()
13
+
14
  DOCS_DIR = "/tmp/documents/"
15
 
16
+ rag_state = {}
 
 
 
 
 
17
 
 
18
 
19
+ def telecharger_documents():
20
  os.makedirs(DOCS_DIR, exist_ok=True)
21
  dest = os.path.join(DOCS_DIR, "CONVENTION_SYNTEC.pdf")
 
22
  if os.path.exists(dest):
23
  print(f"Le fichier {dest} existe deja — telechargement ignore.")
24
  else:
25
  print("Telechargement de CONVENTION_SYNTEC.pdf...")
26
+ urllib.request.urlretrieve(
27
+ "https://github.com/archiducarmel/SupDeVinci_M1_MachineLearning_DeepLearning/releases/download/datas/CONVENTION_SYNTEC.pdf",
28
+ dest,
29
+ )
30
  print("OK.")
 
 
31
  size_kb = os.path.getsize(dest) / 1024
32
+ print(f"\n✅ {dest} ({size_kb:.0f} Ko) pret dans ./documents/")
33
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  def extract_answer(response):
36
  text = (response.content or "").strip()
 
38
  text = (response.additional_kwargs.get("reasoning_content", "") or "").strip()
39
  return text
40
 
41
+
42
  def format_docs(docs):
43
+ return "\n\n".join(
44
+ f"[{d.metadata.get('source', '?').split('/')[-1]} — page {d.metadata.get('page')}] {d.page_content}"
45
+ for d in docs
46
+ )
47
 
 
48
 
49
+ @asynccontextmanager
50
+ async def lifespan(app: FastAPI):
51
+ telecharger_documents()
52
+
53
+ all_docs = []
54
+ for f in sorted(os.listdir(DOCS_DIR)):
55
+ if f.endswith(".pdf"):
56
+ all_docs.extend(PyPDFLoader(os.path.join(DOCS_DIR, f)).load())
57
+ chunks = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100).split_documents(all_docs)
58
+
59
+ embeddings = NVIDIAEmbeddings(model="nvidia/llama-nemotron-embed-1b-v2", truncate="NONE")
60
+ vector_store = Chroma.from_documents(chunks, embeddings)
61
+
62
+ prompt = ChatPromptTemplate.from_template(
63
+ "Tu es un assistant RH expert de la convention collective Syntec. "
64
+ "Réponds à la QUESTION en t'appuyant UNIQUEMENT sur le CONTEXTE ci-dessous.\n"
65
+ "Si l'information n'y figure pas, réponds exactement : « Je ne sais pas ».\n"
66
+ "Sois concis et cite la source (document et numéro de page).\n\n"
67
+ "CONTEXTE :\n{context}\n\nQUESTION : {input}"
68
+ )
69
+ llm = ChatNVIDIA(model="openai/gpt-oss-120b", temperature=0.2, max_completion_tokens=2048)
70
+
71
+ rag_state["retriever"] = vector_store.as_retriever(search_kwargs={"k": 3})
72
+ rag_state["generation"] = prompt | llm | extract_answer
73
+
74
+ yield
75
+
76
+ rag_state.clear()
77
+
78
+
79
+ app = FastAPI(title="Assistant RH — Convention Syntec — API RAG", lifespan=lifespan)
80
 
 
81
 
82
  class QuestionIn(BaseModel):
83
  question: str
84
 
85
+
86
+ def rag_answer(question):
87
+ docs = rag_state["retriever"].invoke(question)
88
+ answer = rag_state["generation"].invoke({"context": format_docs(docs), "input": question})
89
+ return {"answer": answer, "context": docs}
90
+
91
+
92
  @app.get("/")
93
  def health():
94
  return {"status": "ok"}
95
 
96
+
97
  @app.post("/ask")
98
  def ask(payload: QuestionIn):
99
  result = rag_answer(payload.question)
100
+ sources = [
101
+ {"document": d.metadata.get("source", "?").split("/")[-1], "page": d.metadata.get("page")}
102
+ for d in result["context"]
103
+ ]
104
+ return {"answer": result["answer"], "sources": sources}
requierment.txt → requirements.txt RENAMED
@@ -5,4 +5,5 @@ langchain-community
5
  langchain-chroma
6
  langchain-nvidia-ai-endpoints
7
  langchain-text-splitters
8
- pypdf
 
 
5
  langchain-chroma
6
  langchain-nvidia-ai-endpoints
7
  langchain-text-splitters
8
+ pypdf
9
+ python-dotenv