Download app.py from UgoCodee/SDV: direct link, hf CLI and curl.
- Browser
- Download file 3.48 kB
-
https://huggingface.co/spaces/UgoCodee/SDV/resolve/main/app.py
- Command line
-
hf download hf://spaces/UgoCodee/SDV/app.py
-
curl -L -o app.py https://huggingface.co/spaces/UgoCodee/SDV/resolve/main/app.py
3.48 kB
| import os, urllib.request | |
| from contextlib import asynccontextmanager | |
| from dotenv import load_dotenv | |
| from fastapi import FastAPI | |
| from pydantic import BaseModel | |
| from langchain_community.document_loaders import PyPDFLoader | |
| from langchain_text_splitters import RecursiveCharacterTextSplitter | |
| from langchain_nvidia_ai_endpoints import ChatNVIDIA, NVIDIAEmbeddings | |
| from langchain_chroma import Chroma | |
| from langchain_core.prompts import ChatPromptTemplate | |
| load_dotenv() | |
| DOCS_DIR = "/tmp/documents/" | |
| rag_state = {} | |
| def telecharger_documents(): | |
| os.makedirs(DOCS_DIR, exist_ok=True) | |
| dest = os.path.join(DOCS_DIR, "CONVENTION_SYNTEC.pdf") | |
| if os.path.exists(dest): | |
| print(f"Le fichier {dest} existe deja — telechargement ignore.") | |
| else: | |
| print("Telechargement de CONVENTION_SYNTEC.pdf...") | |
| urllib.request.urlretrieve( | |
| "https://github.com/archiducarmel/SupDeVinci_M1_MachineLearning_DeepLearning/releases/download/datas/CONVENTION_SYNTEC.pdf", | |
| dest, | |
| ) | |
| print("OK.") | |
| size_kb = os.path.getsize(dest) / 1024 | |
| print(f"\n✅ {dest} ({size_kb:.0f} Ko) pret dans ./documents/") | |
| def extract_answer(response): | |
| text = (response.content or "").strip() | |
| if not text: | |
| text = (response.additional_kwargs.get("reasoning_content", "") or "").strip() | |
| return text | |
| def format_docs(docs): | |
| return "\n\n".join( | |
| f"[{d.metadata.get('source', '?').split('/')[-1]} — page {d.metadata.get('page')}] {d.page_content}" | |
| for d in docs | |
| ) | |
| async def lifespan(app: FastAPI): | |
| telecharger_documents() | |
| all_docs = [] | |
| for f in sorted(os.listdir(DOCS_DIR)): | |
| if f.endswith(".pdf"): | |
| all_docs.extend(PyPDFLoader(os.path.join(DOCS_DIR, f)).load()) | |
| chunks = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100).split_documents(all_docs) | |
| embeddings = NVIDIAEmbeddings(model="nvidia/llama-nemotron-embed-1b-v2", truncate="NONE") | |
| vector_store = Chroma.from_documents(chunks, embeddings) | |
| prompt = ChatPromptTemplate.from_template( | |
| "Tu es un assistant RH expert de la convention collective Syntec. " | |
| "Réponds à la QUESTION en t'appuyant UNIQUEMENT sur le CONTEXTE ci-dessous.\n" | |
| "Si l'information n'y figure pas, réponds exactement : « Je ne sais pas ».\n" | |
| "Sois concis et cite la source (document et numéro de page).\n\n" | |
| "CONTEXTE :\n{context}\n\nQUESTION : {input}" | |
| ) | |
| llm = ChatNVIDIA(model="openai/gpt-oss-120b", temperature=0.2, max_completion_tokens=2048) | |
| rag_state["retriever"] = vector_store.as_retriever(search_kwargs={"k": 3}) | |
| rag_state["generation"] = prompt | llm | extract_answer | |
| yield | |
| rag_state.clear() | |
| app = FastAPI(title="Assistant RH — Convention Syntec — API RAG", lifespan=lifespan) | |
| class QuestionIn(BaseModel): | |
| question: str | |
| def rag_answer(question): | |
| docs = rag_state["retriever"].invoke(question) | |
| answer = rag_state["generation"].invoke({"context": format_docs(docs), "input": question}) | |
| return {"answer": answer, "context": docs} | |
| def health(): | |
| return {"status": "ok"} | |
| def ask(payload: QuestionIn): | |
| result = rag_answer(payload.question) | |
| sources = [ | |
| {"document": d.metadata.get("source", "?").split("/")[-1], "page": d.metadata.get("page")} | |
| for d in result["context"] | |
| ] | |
| return {"answer": result["answer"], "sources": sources} | |