import os from weaviate.connect import ConnectionParams from weaviate import WeaviateClient from langchain_huggingface import HuggingFaceEmbeddings from langchain_weaviate import WeaviateVectorStore from langchain_mistralai import ChatMistralAI from langchain.chains.conversational_retrieval.base import ConversationalRetrievalChain from langchain.memory import ConversationBufferMemory from langchain.prompts import PromptTemplate from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import LLMChainExtractor from langserve import add_routes from fastapi import FastAPI, WebSocket from fastapi.responses import HTMLResponse import warnings import time from threading import Lock import logging from langchain_core.runnables import RunnablePassthrough, RunnableLambda from langchain_core.output_parsers import StrOutputParser from langchain_core.callbacks.base import BaseCallbackHandler from langchain.callbacks.manager import CallbackManager import json import atexit import asyncio from fastapi import WebSocketDisconnect # Logging config logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Rate limiting RATE_LIMIT = 3 RATE_WINDOW = 1 last_request_time = 0 request_lock = Lock() def wait_for_rate_limit(): global last_request_time with request_lock: current_time = time.time() time_since_last_request = current_time - last_request_time if time_since_last_request < RATE_WINDOW / RATE_LIMIT: sleep_time = (RATE_WINDOW / RATE_LIMIT) - time_since_last_request time.sleep(sleep_time) last_request_time = time.time() def setup_mistral(): logger.info("Configuration du modèle Mistral...") llm = ChatMistralAI( # Nom du modèle à utiliser (mistral-large-latest est le plus performant) model="mistral-large-latest", # Clé API pour l'authentification auprès de Mistral AI mistral_api_key=os.getenv("MISTRAL_API_KEY"), # Contrôle la créativité des réponses (0 = très déterministe, 1 = très créatif) temperature=0.2, # Contrôle la diversité des réponses (1 = toutes les options possibles, 0.1 = très sélectif) top_p=0.9, # Pénalité pour la répétition des mots (0 = pas de pénalité, 1 = forte pénalité) # frequency_penalty=0.5, # Pénalité pour encourager la diversité des sujets (0 = pas de pénalité, 1 = forte pénalité) # presence_penalty=0.5 ) logger.info("Modèle Mistral configuré avec succès") return llm def setup_weaviate(): logger.info("Connexion à Weaviate...") client = WeaviateClient( connection_params=ConnectionParams.from_url( os.getenv("WEAVIATE_URL"), 50051 ) ) client.connect() logger.info("Connexion à Weaviate établie avec succès") # Enregistrer la fermeture de la connexion à la sortie atexit.register(client.close) return client def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) def create_custom_prompt(): template = """Tu es un assistant viticole expert en pulvérisateurs et produits phytosanitaires. \ Voici les documents techniques pertinents pour répondre à la question : {context} Question : {question} Instructions : 1. Réponds de manière factuelle uniquement en te basant sur les documents fournis si cela est pertinent 2. Sois précis sur les caractéristiques techniques 3. Si la question n'est pas liée aux documents techniques, réponds de manière générale et concise Réponse :""" return PromptTemplate(template=template, input_variables=["context", "question"]) def setup_rag_chain(): logger.info("Initialisation de la chaîne RAG...") client = setup_weaviate() embeddings = HuggingFaceEmbeddings( model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2", model_kwargs={'device': 'cpu'}, encode_kwargs={'normalize_embeddings': True} ) vectorstore = WeaviateVectorStore( client=client, embedding=embeddings, index_name="Document", text_key="page_content" ) llm = setup_mistral() memory = ConversationBufferMemory( memory_key="chat_history", return_messages=True, output_key="answer" ) retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 30}) prompt = create_custom_prompt() def extract_question(input_data): if isinstance(input_data, dict): question = input_data.get("question", "") else: question = str(input_data) if not question or not isinstance(question, str): raise ValueError("La question doit être une chaîne de caractères non vide") logger.info(f"🔍 Question extraite: {question}") return question chain = ( RunnableLambda(extract_question) | {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm ) logger.info("✅ Chaîne RAG initialisée avec succès") return chain # Init FastAPI app = FastAPI(title="Vitizen Chat API", description="API de chat pour l'assistant viticole", version="1.0.1") @app.get("/", response_class=HTMLResponse) async def home(): return """