File size: 4,697 Bytes
d11e1fe
 
bec7021
dbd9820
ec7f6a1
037a839
 
 
200fee8
 
ec7f6a1
 
 
 
 
 
d11e1fe
 
ec7f6a1
7f617c9
 
ec7f6a1
 
 
 
 
7f617c9
ec7f6a1
 
073d270
7f617c9
ec7f6a1
 
7f617c9
ec7f6a1
 
 
 
 
 
 
 
d11e1fe
 
2583cf2
 
 
 
 
d11e1fe
 
ec7f6a1
8e038e1
 
2c6bd00
 
8e038e1
ec7f6a1
 
e8bb95c
 
2c6bd00
ec7f6a1
 
 
 
 
 
 
037a839
ec7f6a1
 
 
 
037a839
2c6bd00
 
 
 
 
 
 
d11e1fe
8e038e1
97d2e91
359c625
97d2e91
359c625
97d2e91
359c625
037a839
359c625
 
 
 
 
 
 
 
2c6bd00
359c625
2c6bd00
 
359c625
2c6bd00
359c625
 
 
 
 
 
 
 
 
bec7021
8e038e1
 
 
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
126
127
128
129
130
131
132
133
134
135
136
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
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 en ligne (pas de .gguf local)
        llm = LlamaCPP(
            
            print("✅ ✔️ Modèle LLM chargé sans erreur on continue...")

            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},
        )

        # ✅ Intégration manuelle de l'embedding local dans Settings
        class SimpleEmbedding:
            def get_text_embedding(self, text: str):
                return get_embedding(text)

        try:
            Settings.llm = llm
            Settings.embed_model = SimpleEmbedding()
            print("✅ ✔️ Settings configurés avec LLM et embedding")
        except Exception as e:
            print(f"❌ Erreur dans la configuration des Settings : {e}")
            return {"error": str(e)}


        import sys

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

        parser = SemanticSplitterNodeParser.from_defaults()
        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}")

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



        
        # ✅ Découpage sémantique intelligent
#        parser = SemanticSplitterNodeParser.from_defaults()
#        nodes = parser.get_nodes_from_documents([Document(text=data.text)])

        # ✅ Vérification du nombre de chunks générés
        print(f"✅ Nombre de chunks générés : {len(nodes)}")        

        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)