File size: 4,672 Bytes
d11e1fe
 
bec7021
dbd9820
ec7f6a1
037a839
63ed81d
037a839
200fee8
 
ec7f6a1
 
 
 
 
 
d11e1fe
 
ec7f6a1
7f617c9
 
ec7f6a1
 
 
 
 
7f617c9
ec7f6a1
 
073d270
7f617c9
ec7f6a1
 
 
 
 
 
 
 
 
 
d11e1fe
 
2583cf2
 
 
 
 
d11e1fe
 
ec7f6a1
8e038e1
 
2c6bd00
 
63ed81d
ec7f6a1
 
 
 
 
 
 
 
63ed81d
6a9136a
037a839
63ed81d
ec7f6a1
 
 
037a839
2c6bd00
63ed81d
 
 
 
 
 
2c6bd00
 
 
d11e1fe
97d2e91
 
359c625
63ed81d
 
359c625
 
 
 
 
 
 
 
63ed81d
359c625
2c6bd00
 
359c625
63ed81d
2c6bd00
63ed81d
359c625
 
 
63ed81d
bec7021
 
 
 
 
 
ec7f6a1
5ac0022
bec7021
5ac0022
d11e1fe
 
d00f6f0
 
 
d5e5243
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional

# ✅ Modules de LlamaIndex
from llama_index.core.settings import Settings
from llama_index.core import Document, ServiceContext
from llama_index.llms.llama_cpp import LlamaCPP
from llama_index.core.node_parser import SemanticSplitterNodeParser

# ✅ Pour l'embedding LOCAL via transformers
from transformers import AutoTokenizer, AutoModel
import torch
import torch.nn.functional as F
import os

app = FastAPI()

# ✅ Configuration locale du cache HF pour Hugging Face
# ✅ Définir un chemin autorisé pour le cache (à l'intérieur du container Hugging Face)
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


# ✅ Configuration du modèle d’embedding local (ex: BGE / Nomic / GTE etc.)
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):
    with torch.no_grad():
        inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
        outputs = model(**inputs)
        embeddings = outputs.last_hidden_state[:, 0]
        return F.normalize(embeddings, p=2, dim=1).squeeze().tolist()

# ✅ Données entrantes du POST
class ChunkRequest(BaseModel):
    text: str
    source_id: Optional[str] = None
    titre: Optional[str] = None
    source: Optional[str] = None
    type: Optional[str] = None

@app.post("/chunk")
async def chunk_text(data: ChunkRequest):
    try:
        # ✅ Vérification du texte reçu
        print(f"✅ Texte reçu ({len(data.text)} caractères) : {data.text[:200]}...")
        print("✅ ✔️ Reçu – On passe à la configuration du modèle LLM...")

        # ✅ Chargement du modèle LLM depuis Hugging Face (GGUF distant)
        llm = LlamaCPP(
            model_url="https://huggingface.co/leafspark/Mistral-7B-Instruct-v0.2-Q4_K_M-GGUF/resolve/main/mistral-7b-instruct-v0.2.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 LLM chargé sans erreur on continue...")

        # ✅ Définition d’un wrapper simple pour l’embedding local
        class SimpleEmbedding:
            def get_text_embedding(self, text: str):
                return get_embedding(text)

        try:
            # 🛠️ Remplace Settings.llm + embed_model par ServiceContext
            Settings.service_context = ServiceContext.from_defaults(
                llm=llm,
                embed_model=SimpleEmbedding()
            )
            print("✅ ✔️ Settings configurés via ServiceContext (LLM + Embedding)")
        except Exception as e:
            print(f"❌ Erreur dans la configuration des Settings : {e}")
            return {"error": str(e)}

        print("✅ LLM et embedding configurés - prêt pour le split")
        print("✅ Début du split sémantique...", flush=True)

        # ✅ Utilisation du Semantic Splitter avec le LLM actuel
        parser = SemanticSplitterNodeParser.from_defaults(llm=llm)
        fallback_splitter = Settings.node_parser  # fallback = splitter par défaut

        doc = Document(text=data.text)

        try:
            nodes = parser.get_nodes_from_documents([doc])
            print(f"✅ Nombre de chunks générés : {len(nodes)}")
            print(f"🧩 Exemple chunk : {nodes[0].text[:100]}...")

        except Exception as e:
            import traceback
            traceback.print_exc()
            print(f"❌ Erreur lors du split sémantique : {e}")
            return {"error": str(e)}

            # Fallback option (non utilisé ici)
            nodes = fallback_splitter.get_nodes_from_documents([doc])
            print(f"⚠️ Split fallback utilisé - chunks générés : {len(nodes)}")

        # ✅ Résultat complet pour l’API
        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  # ← essentiel pour que n8n voie "rien à signaler"
        }

    except Exception as e:
        return {"error": str(e)}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("app:app", host="0.0.0.0", port=7860)