Terry / app.py
CalmeBouquetin76's picture
Update app.py
e90d4f8 verified
Raw History Blame Contribute Delete
3.99 kB
import os
import pdfplumber
import streamlit as st
import time
from dotenv import load_dotenv, find_dotenv
from langchain.embeddings import HuggingFaceEmbeddings
from langchain_chroma import Chroma
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_groq.chat_models import ChatGroq
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA
# ------------------ CONFIG ------------------
load_dotenv(find_dotenv())
PDF_PATH = os.path.join(os.path.dirname(__file__), "docs", "document.pdf")
CHROMA_DIR = "chroma_db"
EMBED_MODEL = "sentence-transformers/all-MiniLM-L6-v2" # Modèle HF
GROQ_MODEL = "llama3-70b-8192"
# ------------------ UTILS ------------------
@st.cache_resource
def load_vectorstore():
embedding = HuggingFaceEmbeddings(model_name=EMBED_MODEL)
if not os.path.exists(CHROMA_DIR):
with pdfplumber.open(PDF_PATH) as pdf:
full_text = ""
for page in pdf.pages:
full_text += page.extract_text() + "\n"
if not full_text.strip():
st.error("❌ Le PDF est vide ou illisible.")
st.stop()
cleaned = " ".join(full_text.split())
doc = Document(page_content=cleaned, metadata={"file": os.path.basename(PDF_PATH)})
splitter = RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=100)
chunks = splitter.split_documents([doc])
return Chroma.from_documents(chunks, embedding=embedding, persist_directory=CHROMA_DIR)
else:
return Chroma(persist_directory=CHROMA_DIR, embedding_function=embedding)
def create_qa_chain(db):
retriever = db.as_retriever(search_type="mmr", search_kwargs={"k": 5})
llm = ChatGroq(model=GROQ_MODEL, api_key=os.getenv("GROQ_API_KEY"))
prompt = PromptTemplate(
template="""
Tu es un assistant agricole expert francophone nommé Terr'Y, créé par Terr'O.
Tu ne dois répondre qu'à partir du document fourni. Si la réponse n'est pas dans le document, dis-le honnêtement.
Ta mission est de répondre **uniquement** à partir du contenu du document fourni.
Si la réponse contient des éléments structurés (ex : primes, cultures, conditions, barèmes, dates, etc.), tu dois les présenter sous forme de **tableau clair**.
Consignes :
- Utilise un ton clair, professionnel et en français.
- Réponds exclusivement à partir des documents.
- Ne complète jamais avec des données absentes.
- Si l’information n’est pas présente, indique-le simplement.
- Tu t'appelles Terr'Y l'assistant agricole créé par Terr'O.
Question : {question}
Contenu : {context}
""",
input_variables=["question", "context"]
)
return RetrievalQA.from_chain_type(
llm=llm,
retriever=retriever,
chain_type="stuff",
chain_type_kwargs={"prompt": prompt}
)
def typewriter(text, delay=0.03):
container = st.empty()
words = text.split(" ")
typed = ""
for word in words:
typed += word + " "
container.markdown(typed)
time.sleep(delay)
# ------------------ INTERFACE STREAMLIT ------------------
st.set_page_config(page_title="Terr'Y - Assistant Collecte", page_icon="🌾", layout="wide")
col1, col2 = st.columns([1, 6])
with col1:
st.image("images/terrY.png", width=100)
with col2:
st.title("🌾 Terr'Y - Assistant Agricole")
st.markdown("Pose une question sur le livret de campagne collecte 2024 📘")
question = st.text_input("Ta question ici :", placeholder="Ex. : Quelles sont les primes pour le blé tendre ?")
if question:
with st.spinner("Terr'Y réfléchit..."):
db = load_vectorstore()
qa = create_qa_chain(db)
response = qa.invoke(question)
answer = response["result"]
st.markdown("### 🧠 Réponse")
typewriter(answer)