Votre Nom commited on
Commit ·
44a12cc
1
Parent(s): 1defa68
Initial deployment with RAG model
Browse files- .DS_Store +0 -0
- RAG.py +117 -0
- app.py +28 -0
- requirements.txt +7 -2
- src/{streamlit_app.py → template.py} +0 -0
.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
RAG.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import faiss
|
| 3 |
+
import pickle
|
| 4 |
+
import numpy as np
|
| 5 |
+
import openai
|
| 6 |
+
import tiktoken
|
| 7 |
+
from dotenv import load_dotenv
|
| 8 |
+
from openai import OpenAI
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from huggingface_hub import hf_hub_download
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# —— CONFIG ——
|
| 14 |
+
load_dotenv()
|
| 15 |
+
# Use the new OpenAI client
|
| 16 |
+
client = OpenAI()
|
| 17 |
+
EMBED_MODEL = "text-embedding-3-small"
|
| 18 |
+
CHAT_MODEL = "o4-mini-2025-04-16"
|
| 19 |
+
FAISS_INDEX_FILE = "tindle_index.faiss"
|
| 20 |
+
IDS_PKL = "tindle_ids.pkl"
|
| 21 |
+
CHUNKS_PKL = "tindle_chunks.pkl"
|
| 22 |
+
TOP_K = 10
|
| 23 |
+
MAX_TOKENS_CONTEXT = 4000
|
| 24 |
+
SYSTEM_PROMPT = (
|
| 25 |
+
"Tu es un assistant expert en droit fiscal. "
|
| 26 |
+
"Fais d’abord appel aux passages fournis pour répondre. "
|
| 27 |
+
"Si ces passages sont insuffisants, utilise tes connaissances générales en le précisant clairement."
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# —— CHARGEMENT DE L'INDEX ——
|
| 31 |
+
|
| 32 |
+
# Télécharger les fichiers depuis le repo Hugging Face
|
| 33 |
+
index_path = hf_hub_download(repo_id="Jordanche/fiscarag", filename=FAISS_INDEX_FILE)
|
| 34 |
+
ids_path = hf_hub_download(repo_id="Jordanche/fiscarag", filename=IDS_PKL)
|
| 35 |
+
chunks_path = hf_hub_download(repo_id="Jordanche/fiscarag", filename=CHUNKS_PKL)
|
| 36 |
+
|
| 37 |
+
# Charger les fichiers
|
| 38 |
+
index = faiss.read_index(index_path)
|
| 39 |
+
with open(ids_path, "rb") as f:
|
| 40 |
+
ids = pickle.load(f)
|
| 41 |
+
with open(chunks_path, "rb") as f:
|
| 42 |
+
chunks_dict = pickle.load(f)
|
| 43 |
+
|
| 44 |
+
# —— TOKEN COUNTER ——
|
| 45 |
+
enc = tiktoken.get_encoding("cl100k_base")
|
| 46 |
+
def num_tokens(s: str) -> int:
|
| 47 |
+
return len(enc.encode(s))
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
# —— FONCTIONS RAG ——
|
| 51 |
+
|
| 52 |
+
def embed_question(question: str) -> list[float]:
|
| 53 |
+
resp = client.embeddings.create(
|
| 54 |
+
model=EMBED_MODEL,
|
| 55 |
+
input=[question]
|
| 56 |
+
)
|
| 57 |
+
# on récupère l'attribut .data, puis .embedding
|
| 58 |
+
return resp.data[0].embedding
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def retrieve_chunks(q_emb: list[float], k: int = TOP_K):
|
| 62 |
+
xq = np.array([q_emb], dtype="float32")
|
| 63 |
+
distances, indices = index.search(xq, k)
|
| 64 |
+
out = []
|
| 65 |
+
for dist, idx in zip(distances[0], indices[0]):
|
| 66 |
+
cid = ids[idx]
|
| 67 |
+
meta = chunks_dict[cid]
|
| 68 |
+
out.append({
|
| 69 |
+
"score": float(dist),
|
| 70 |
+
"id": cid,
|
| 71 |
+
"offset": meta["offset"],
|
| 72 |
+
"text": meta["text"]
|
| 73 |
+
})
|
| 74 |
+
return out
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def build_context(chunks, max_tokens=MAX_TOKENS_CONTEXT):
|
| 78 |
+
parts, tokens = [], 0
|
| 79 |
+
for c in sorted(chunks, key=lambda x: x["score"]):
|
| 80 |
+
piece = f"(Source: {c['id']}@{c['offset']}) {c['text']}"
|
| 81 |
+
nt = num_tokens(piece)
|
| 82 |
+
if tokens + nt > max_tokens:
|
| 83 |
+
break
|
| 84 |
+
parts.append(piece)
|
| 85 |
+
tokens += nt
|
| 86 |
+
return "\n\n".join(parts)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def make_prompt(question: str, context: str):
|
| 90 |
+
return [
|
| 91 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 92 |
+
{"role": "user", "content": f"Question: {question}\n\nContexte:\n{context}"}
|
| 93 |
+
]
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def answer_question(question: str, k: int = TOP_K) -> str:
|
| 97 |
+
# 1) Embed
|
| 98 |
+
q_emb = embed_question(question)
|
| 99 |
+
|
| 100 |
+
# 2) Retrieve
|
| 101 |
+
top_chunks = retrieve_chunks(q_emb, k)
|
| 102 |
+
|
| 103 |
+
# 3) Assemble
|
| 104 |
+
context = build_context(top_chunks)
|
| 105 |
+
|
| 106 |
+
# 4) Prompt
|
| 107 |
+
messages = make_prompt(question, context)
|
| 108 |
+
# 5) Call LLM
|
| 109 |
+
resp = client.chat.completions.create(
|
| 110 |
+
model=CHAT_MODEL,
|
| 111 |
+
messages=messages )
|
| 112 |
+
return resp.choices[0].message.content
|
| 113 |
+
|
| 114 |
+
# —— EXEMPLE ——
|
| 115 |
+
if __name__ == "__main__":
|
| 116 |
+
question = "Quels sont les délais pour la réhabilitation d'hôtels en outre-mer ?"
|
| 117 |
+
print(answer_question(question, k=10))
|
app.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
from RAG import answer_question
|
| 3 |
+
|
| 4 |
+
st.set_page_config(page_title="Tindle RAG Chat", page_icon="💬", layout="centered")
|
| 5 |
+
|
| 6 |
+
st.title("💬 Tindle RAG Chat")
|
| 7 |
+
st.markdown("Posez une question sur le droit fiscal, l'assistant RAG vous répondra en s'appuyant sur les sources indexées.")
|
| 8 |
+
|
| 9 |
+
if "messages" not in st.session_state:
|
| 10 |
+
st.session_state["messages"] = []
|
| 11 |
+
|
| 12 |
+
# Affichage de l'historique du chat
|
| 13 |
+
for msg in st.session_state["messages"]:
|
| 14 |
+
if msg["role"] == "user":
|
| 15 |
+
st.chat_message("user").markdown(msg["content"])
|
| 16 |
+
else:
|
| 17 |
+
st.chat_message("assistant").markdown(msg["content"])
|
| 18 |
+
|
| 19 |
+
# Saisie utilisateur
|
| 20 |
+
if prompt := st.chat_input("Votre question..."):
|
| 21 |
+
st.session_state["messages"].append({"role": "user", "content": prompt})
|
| 22 |
+
with st.spinner("Recherche de la réponse..."):
|
| 23 |
+
try:
|
| 24 |
+
response = answer_question(prompt)
|
| 25 |
+
except Exception as e:
|
| 26 |
+
response = f"Erreur lors de l'appel au RAG : {e}"
|
| 27 |
+
st.session_state["messages"].append({"role": "assistant", "content": response})
|
| 28 |
+
st.rerun()
|
requirements.txt
CHANGED
|
@@ -1,3 +1,8 @@
|
|
| 1 |
altair
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
altair
|
| 2 |
+
streamlit>=1.28.0
|
| 3 |
+
openai>=1.0.0
|
| 4 |
+
faiss-cpu>=1.7.4
|
| 5 |
+
numpy>=1.24.0
|
| 6 |
+
tiktoken>=0.5.0
|
| 7 |
+
python-dotenv>=1.0.0
|
| 8 |
+
pyarrow>=12.0.0
|
src/{streamlit_app.py → template.py}
RENAMED
|
File without changes
|