File size: 5,169 Bytes
0a227d1
 
5ac738d
0a227d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ac738d
0a227d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5ac738d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# ENUNCIADO
# rag_engine.py
#
# Este archivo contendrá toda la lógica del motor RAG. Se deben
# implementar obligatoriamente las siguientes funciones (con los nombres y parámetros
# exactos que se indican).
# Al inicio del script se deben cargar:
#   • El modelo de embeddings: SentenceTransformer("MongoDB/mdbr-leaf-ir")
#   • El modelo de lenguaje: PleIAs/Pleias-RAG350M (usando AutoTokenizer y AutoModelForCausalLM de transformers).
#   • Los documentos desde documents.json.
# Función recuperar_documentos(consulta, top_k=2, umbral=0.4)
# Dada una consulta en inglés, recupera los documentos más relevantes de la base de conocimiento.
#   • Parámetros:
#       o consulta (str): pregunta del usuario.
#       o top_k (int): número máximo de documentos a retornar.
#       o umbral (float): valor mínimo de similitud (coseno) para considerar un
#         documento relevante. Los documentos con similitud inferior a este
#         umbral se descartan.
#   • Proceso:
#       1. Calcular el embedding de la consulta y de todos los documentos
#          (preferiblemente una sola vez al cargar el script y almacenarlos para
#          evitar recalcular).
#       2. Calcular la similitud del coseno entre el embedding de la consulta y los
#          embeddings de los documentos.
#       3. Ordenar los documentos de mayor a menor similitud.
#       4. Recorrer en ese orden y seleccionar aquellos cuya similitud sea mayor o
#          igual al umbral, hasta un máximo de top_k documentos.
#   • Retorno: Lista con los textos de los documentos seleccionados.
# Función generar_respuesta(consulta, documentos_recuperados)
# Genera una respuesta usando el modelo de lenguaje, inyectando los documentos
# recuperados como contexto.
# Parámetros:
#   o consulta (str): pregunta original del usuario.
#   o documentos_recuperados (list): lista de textos con los documentos
# relevantes.
# Proceso:
#   1. Se concatenan todos los documentos en un solo string (por ejemplo,
#   separados por espacios).
#   2. Se construye un prompt con el siguiente formato:
#   “””
#   Answer the question based only on the context provided
#   Context: <" ".join(documentos_recuperados)>
#   Question: <consulta>
#   Answer:
#   “””
#   3. Se genera la respuesta con el modelo
# Retorno: Cadena con la respuesta generada.
# Función preguntar(consulta, top_k=2, umbral=0.4)
#   • Descripción:
#       o Función de alto nivel que une la lógica de recuperar_documentos y
#         generar_respuestas
#   • Parámetros: los mismos que recuperar_documentos.
#   • Retorno: La respuesta generada (cadena).

import json
import torch

from sentence_transformers import SentenceTransformer
from transformers import AutoTokenizer, AutoModelForCausalLM

from sklearn.metrics.pairwise import cosine_similarity


# -----------------------------
# Cargar documentos
# -----------------------------

with open("documents.json", "r") as f:
    documents = json.load(f)

# convertir a lista de textos
docs_text = list(documents.values())


# -----------------------------
# Modelo de embeddings
# -----------------------------

embed_model = SentenceTransformer("MongoDB/mdbr-leaf-ir")

# calcular embeddings una sola vez
doc_embeddings = embed_model.encode(docs_text)


# -----------------------------
# Modelo de lenguaje (LLM)
# -----------------------------

model_name = "microsoft/phi-2"

tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)


# -------------------------------------------------
# FUNCION 1
# recuperar_documentos
# -------------------------------------------------


def recuperar_documentos(consulta, top_k=2, umbral=0.4):

    # embedding de la consulta
    query_embedding = embed_model.encode([consulta])

    # calcular similitud coseno
    similitudes = cosine_similarity(query_embedding, doc_embeddings)[0]

    # ordenar índices por similitud
    indices_ordenados = similitudes.argsort()[::-1]

    docs_relevantes = []

    for idx in indices_ordenados:

        if similitudes[idx] >= umbral:
            docs_relevantes.append(docs_text[idx])

        if len(docs_relevantes) >= top_k:
            break

    return docs_relevantes


# -------------------------------------------------
# FUNCION 2
# generar_respuesta
# -------------------------------------------------


def generar_respuesta(consulta, documentos_recuperados):

    contexto = " ".join(documentos_recuperados)

    prompt = f"""
        Answer the question based only on the context provided

        Context: {contexto}

        Question: {consulta}

        Answer:
        """

    inputs = tokenizer(prompt, return_tensors="pt")

    outputs = model.generate(**inputs, max_new_tokens=100)

    respuesta = tokenizer.decode(outputs[0], skip_special_tokens=True)

    return respuesta


# -------------------------------------------------
# FUNCION 3
# preguntar
# -------------------------------------------------


def preguntar(consulta, top_k=2, umbral=0.4):

    docs = recuperar_documentos(consulta, top_k, umbral)

    respuesta = generar_respuesta(consulta, docs)

    return respuesta