Spaces:
Runtime error
Runtime error
| # ✅ API FastAPI de chunking sémantique intelligent avec fallback automatique | |
| from fastapi import FastAPI | |
| from pydantic import BaseModel | |
| from typing import Optional | |
| # ✅ LlamaIndex (version >= 0.10.0) | |
| from llama_index.core import Document | |
| from llama_index.core.settings import Settings | |
| from llama_index.core.node_parser import SemanticSplitterNodeParser, RecursiveCharacterTextSplitter | |
| from llama_index.llms.llama_cpp import LlamaCPP | |
| from llama_index.core.base.llms.base import BaseLLM | |
| # ✅ Embedding local (basé sur transformers + torch) | |
| from transformers import AutoTokenizer, AutoModel | |
| import torch | |
| import torch.nn.functional as F | |
| import os | |
| # ✅ Initialisation de l'application FastAPI | |
| app = FastAPI() | |
| # ✅ Configuration du cache local de Hugging Face pour économiser l'espace dans le container | |
| CACHE_DIR = "/app/cache" | |
| os.environ["HF_HOME"] = CACHE_DIR | |
| os.environ["TRANSFORMERS_CACHE"] = CACHE_DIR | |
| os.environ["HF_MODULES_CACHE"] = CACHE_DIR | |
| os.environ["HF_HUB_CACHE"] = CACHE_DIR | |
| # ✅ Modèle d'embedding local utilisé pour vectoriser les textes | |
| MODEL_NAME = "BAAI/bge-small-en-v1.5" | |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, cache_dir=CACHE_DIR) | |
| model = AutoModel.from_pretrained(MODEL_NAME, cache_dir=CACHE_DIR) | |
| def get_embedding(text: str): | |
| """Fonction pour générer un embedding dense normalisé à partir d’un texte.""" | |
| with torch.no_grad(): | |
| inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True) | |
| outputs = model(**inputs) | |
| embeddings = outputs.last_hidden_state[:, 0] # On prend le vecteur [CLS] | |
| return F.normalize(embeddings, p=2, dim=1).squeeze().tolist() | |
| # ✅ Schéma des données attendues dans le POST | |
| class ChunkRequest(BaseModel): | |
| text: str | |
| max_tokens: Optional[int] = 1000 | |
| overlap: Optional[int] = 350 | |
| source_id: Optional[str] = None | |
| titre: Optional[str] = None | |
| source: Optional[str] = None | |
| type: Optional[str] = None | |
| async def chunk_text(data: ChunkRequest): | |
| try: | |
| print(f"\n✅ Texte reçu ({len(data.text)} caractères) : {data.text[:200]}...", flush=True) | |
| # ✅ Chargement du modèle LLM CodeLlama quantifié (GGUF) via URL Hugging Face | |
| llm = LlamaCPP( | |
| model_url="https://huggingface.co/TheBloke/CodeLlama-7B-Instruct-GGUF/resolve/main/codellama-7b-instruct.Q4_K_M.gguf", | |
| temperature=0.1, | |
| max_new_tokens=512, | |
| context_window=2048, | |
| generate_kwargs={"top_p": 0.95}, | |
| model_kwargs={"n_gpu_layers": 1}, | |
| ) | |
| print("✅ Modèle CodeLlama-7B chargé avec succès !") | |
| # ✅ Embedding local pour LlamaIndex | |
| class SimpleEmbedding: | |
| def get_text_embedding(self, text: str): | |
| return get_embedding(text) | |
| # ✅ Configuration du moteur dans LlamaIndex | |
| assert isinstance(llm, BaseLLM), "❌ Le LLM n'est pas compatible avec Settings.llm" | |
| Settings.llm = llm | |
| Settings.embed_model = SimpleEmbedding() | |
| print("✅ Configuration du LLM et de l'embedding terminée.") | |
| # ✅ Document à découper | |
| doc = Document(text=data.text) | |
| # ✅ Split intelligent (semantic) | |
| parser = SemanticSplitterNodeParser.from_defaults(llm=llm) | |
| try: | |
| nodes = parser.get_nodes_from_documents([doc]) | |
| print(f"✅ Semantic Splitter : {len(nodes)} chunks générés") | |
| if not nodes: | |
| raise ValueError("Aucun chunk généré par SemanticSplitter") | |
| except Exception as e: | |
| print(f"⚠️ Fallback vers RecursiveCharacterTextSplitter suite à : {e}") | |
| splitter = RecursiveCharacterTextSplitter( | |
| chunk_size=data.max_tokens, | |
| chunk_overlap=data.overlap | |
| ) | |
| nodes = splitter.get_nodes_from_documents([doc]) | |
| print(f"♻️ Recursive Splitter : {len(nodes)} chunks générés") | |
| # ✅ Construction de la réponse | |
| return { | |
| "chunks": [node.text for node in nodes], | |
| "metadatas": [node.metadata for node in nodes], | |
| "source_id": data.source_id, | |
| "titre": data.titre, | |
| "source": data.source, | |
| "type": data.type, | |
| "error": None # utile pour n8n ou tout autre client | |
| } | |
| except Exception as e: | |
| print(f"❌ Erreur critique : {e}") | |
| return {"error": str(e)} | |
| # ✅ Lancement du serveur si exécution directe (mode debug) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run("app:app", host="0.0.0.0", port=7860) | |