File size: 4,194 Bytes
cfad8ea
43786a1
9e45e2f
7977dd9
9e45e2f
43786a1
c3ec6b8
 
bc04226
cfad8ea
c3ec6b8
cfad8ea
c3ec6b8
 
 
a56b6e1
 
 
cfad8ea
7977dd9
 
 
 
 
 
 
 
 
 
 
 
 
c3ec6b8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43786a1
5b798a1
c3c5a7d
c3ec6b8
43786a1
 
cfad8ea
0a63192
43786a1
9e45e2f
 
 
c3ec6b8
fa62c74
43786a1
cfad8ea
0a63192
 
 
fa62c74
e077d41
6f87108
 
e077d41
 
 
6f87108
 
092620d
75062ee
93053b0
e077d41
408c937
e077d41
7baae85
e077d41
 
75062ee
e077d41
 
75062ee
e077d41
 
75062ee
c3ec6b8
 
2404c70
 
c3ec6b8
0869270
c3ec6b8
 
 
 
 
 
43786a1
c3ec6b8
43786a1
9e45e2f
43786a1
 
9e45e2f
43786a1
 
c3ec6b8
43786a1
cfad8ea
c3ec6b8
fa62c74
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
import gradio as gr
import pickle
import os
import json
from transformers import AutoTokenizer, AutoModel, pipeline
import torch
import faiss
import numpy as np
from spaces import GPU  # IMPORTANTE para ZeroGPU

hf_token = os.getenv("HF_KEY")

# Cargar índice FAISS y los chunks
if os.path.exists("index.pkl"):
    with open("index.pkl", "rb") as f:
        index, chunks = pickle.load(f)
else:
    raise FileNotFoundError("No se encontró el archivo 'index.pkl'.")

# Cargar diccionario de sinónimos
with open("sinonimos.json", "r", encoding="utf-8") as f:
    diccionario_sinonimos = json.load(f)

# Función para expandir palabras clave con sinónimos
def expandir_con_sinonimos(palabras, diccionario):
    resultado = set(palabras)
    for palabra in palabras:
        for clave, sinonimos in diccionario.items():
            if palabra == clave or palabra in sinonimos:
                resultado.update([clave] + sinonimos)
    return list(resultado)

# Modelo de embeddings
model_id = "jinaai/jina-embeddings-v2-base-es"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModel.from_pretrained(model_id)

def generar_embedding(texto):
    inputs = tokenizer(texto, return_tensors="pt", padding=True, truncation=True)
    with torch.no_grad():
        outputs = model(**inputs)
        last_hidden = outputs.last_hidden_state
        mask = inputs["attention_mask"].unsqueeze(-1).expand(last_hidden.size()).float()
        summed = torch.sum(last_hidden * mask, 1)
        counted = torch.clamp(mask.sum(1), min=1e-9)
        mean_pooled = summed / counted
        return mean_pooled.numpy()

# LLM para generar respuesta final
llm = pipeline(
    "text-generation",
    model="meta-llama/Llama-3.2-3B-Instruct",
    token=hf_token,
    trust_remote_code=True
)

@GPU
def responder(pregunta):
    if not pregunta:
        return "Por favor ingresa una pregunta."

    pregunta_embedding = generar_embedding(pregunta)
    distances, indices = index.search(pregunta_embedding.reshape(1, -1), k=10)
    result_chunks = [chunks[i] for i in indices[0]]

    palabras_clave = pregunta.lower().split()
    palabras_expandidas = expandir_con_sinonimos(palabras_clave, diccionario_sinonimos)

    def contar_coincidencias(chunk, palabras):
        return sum(1 for p in palabras if p in chunk.lower())

    filtrados = sorted(
        result_chunks,
        key=lambda c: contar_coincidencias(c, palabras_expandidas),
        reverse=True
    )

    contexto_final = "\n\n".join(filtrados[:5]) if filtrados else "\n\n".join(result_chunks[:5])

    prompt = f"""
Eres un asistente legal especializado en la legislación colombiana, con conocimientos en el Código de Tránsito, el Código Nacional de Policía y el Código Penal. Tu tarea es responder de forma clara, breve y profesional la siguiente pregunta, utilizando únicamente el contexto legal que se te proporciona. Puedes interpretar el significado de los artículos si su relación con la pregunta es evidente, pero no debes inventar leyes, sanciones ni artículos que no estén presentes en el texto.

No utilices conocimiento externo, no cites fuentes adicionales, ni incluyas enlaces o advertencias innecesarias. Si el contexto no ofrece información suficiente para responder, dilo de forma directa y concluye. Siempre que sea posible, sintetiza tu respuesta para que sea comprensible incluso para personas sin formación jurídica.

CONTEXTO LEGAL:
{contexto_final}

PREGUNTA:
{pregunta}

RESPUESTA:
"""

    resultado = llm(
        prompt,
        max_new_tokens=350,
        temperature=0.4,
        top_p=0.9,
        repetition_penalty=1.2
    )[0]["generated_text"]

    if "RESPUESTA:" in resultado:
        solo_respuesta = resultado.split("RESPUESTA:")[-1].strip()
    else:
        solo_respuesta = resultado.strip()

    return solo_respuesta

# Interfaz de usuario con Gradio
demo = gr.Interface(
    fn=responder,
    inputs=gr.Textbox(label="Escribe tu pregunta"),
    outputs=gr.Textbox(label="Respuesta generada"),
    title="Asistente Legal Colombiano",
    description="Consulta el Código de Tránsito, Código de Policía y Código Penal colombiano."
)

if __name__ == "__main__":
    demo.launch()