File size: 13,763 Bytes
03b3c9a
 
578774c
 
03b3c9a
 
 
 
 
 
578774c
 
 
7c68366
ceb558e
 
 
03b3c9a
578774c
 
 
 
5f7cb64
 
 
578774c
03b3c9a
 
 
 
 
578774c
5f7cb64
578774c
 
5f7cb64
578774c
 
 
 
 
 
03b3c9a
578774c
7c68366
 
 
 
578774c
7c68366
578774c
7c68366
 
 
 
 
 
ceb558e
7c68366
578774c
7c68366
 
578774c
7c68366
 
 
03b3c9a
578774c
03b3c9a
578774c
7c68366
 
5f7cb64
 
 
7c68366
 
 
 
 
 
 
5f7cb64
 
 
 
 
 
 
 
 
7c68366
 
 
 
 
578774c
7c68366
 
 
 
 
 
 
 
 
 
 
 
 
 
578774c
 
7c68366
03b3c9a
578774c
7c68366
578774c
 
 
7c68366
 
578774c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7c68366
578774c
 
 
 
 
 
 
 
 
 
7c68366
 
578774c
 
 
5f7cb64
03b3c9a
 
578774c
17d5260
7c68366
578774c
17d5260
 
7c68366
578774c
 
 
17d5260
5f7cb64
 
 
 
 
 
 
 
17d5260
 
 
 
 
 
5f7cb64
17d5260
 
7c68366
578774c
 
 
ceb558e
578774c
ceb558e
 
578774c
ceb558e
578774c
7c68366
578774c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5f7cb64
17d5260
578774c
7c68366
17d5260
 
578774c
 
 
 
 
5f7cb64
578774c
 
 
5f7cb64
578774c
 
 
 
 
 
 
 
 
 
 
7c68366
578774c
 
 
 
17d5260
578774c
 
 
 
 
 
 
 
 
 
 
 
ceb558e
578774c
 
 
 
 
 
 
 
 
 
 
17d5260
ceb558e
03b3c9a
17d5260
4509f5e
578774c
 
 
03b3c9a
7c68366
 
578774c
 
 
 
 
5f7cb64
7c68366
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
import os
import gradio as gr
import threading
import time
from huggingface_hub import login
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForCausalLM, Trainer, TrainingArguments, DataCollatorForLanguageModeling, pipeline
from peft import get_peft_model, LoraConfig, TaskType, PeftModel
import json

# --- CONFIGURACIÓN DEL MODELO Y ENTRENAMIENTO ---
BASE_MODEL = "bigcode/santacoder"  # Modelo base de programación
LORA_PATH = "./lora_output"        # Ruta donde se guarda el modelo adaptado
DATASET_FILE = "codesearchnet_lora_dataset.json"   
MAX_TOKEN_LENGTH = 256             
NUM_SAMPLES_TO_PROCESS = 1000      
DEFAULT_EPOCHS = 10                

# Configuración del ciclo AUTÓNOMO (Inicia reentrenamiento cada 5 interacciones)
GENERATION_LIMIT_TO_TRAIN = 5 
AUTONOMOUS_EPOCHS = 3           

# Nueva configuración para manejo de modelos grandes
TEMP_OFFLOAD_FOLDER = "./temp_offload"

# --- ESTADO GLOBAL Y THREADING ---
tokenizer = None
lora_model = None
tokenized_dataset = None
lora_generator = None

# Variables de estado
version_number = 1.1 # Asumimos que el último entrenamiento llegó a V1.1
is_trained = os.path.exists(LORA_PATH) 
generations_since_last_train = 0
training_status_message = f"Modelo V{version_number:.1f} listo."

# Lock para proteger las variables compartidas entre hilos (CRÍTICO para estabilidad)
global_lock = threading.Lock() 

# --- LÓGICA DE PREPARACIÓN Y SETUP ---

def prepare_codesearchnet():
    """Descarga y prepara el dataset inicial si no existe."""
    if os.path.exists(DATASET_FILE):
        return
    try:
        raw_csn = load_dataset('Nan-Do/code-search-net-python', split=f'train[:{NUM_SAMPLES_TO_PROCESS}]')
        
        def format_for_lora(example):
            # Formato que entrena a la IA a enlazar descripción (español) con código (inglés)
            prompt_text = (
                f"# Descripción: {example['docstring_summary']}\n"
                f"# Completa la siguiente función:\n"
                f"def {example['func_name']}("
            )
            completion_text = example['code']
            return {"prompt": prompt_text, "completion": completion_text}

        lora_dataset = raw_csn.map(format_for_lora, batched=False, remove_columns=raw_csn["train"].column_names)
        lora_dataset.to_json(DATASET_FILE)
    except Exception as e:
        print(f"Error al cargar dataset. Usando datos mínimos. Error: {e}")
        minimal_dataset = [{"prompt": "# Error de carga. Intenta de nuevo.", "completion": "pass\n"}] * 10
        with open(DATASET_FILE, 'w') as f:
            json.dump(minimal_dataset, f)


def setup_resources():
    """Configura el tokenizer, el modelo base y el adaptador LoRA."""
    global tokenizer, lora_model, tokenized_dataset

    # Crear la carpeta de offload si no existe (CRÍTICO para el error actual)
    os.makedirs(TEMP_OFFLOAD_FOLDER, exist_ok=True) 

    prepare_codesearchnet()
    
    hf_token = os.environ.get("HF_TOKEN")
    if hf_token:
        login(token=hf_token)

    tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
    
    # Añadir safetensors=True y offload_folder
    base_model = AutoModelForCausalLM.from_pretrained(
        BASE_MODEL, 
        device_map="auto",
        offload_folder=TEMP_OFFLOAD_FOLDER, # SOLUCIÓN 1
        low_cpu_mem_usage=True, 
        trust_remote_code=True,
    ) 

    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token

    peft_config = LoraConfig(
        task_type=TaskType.CAUSAL_LM, r=8, lora_alpha=32, lora_dropout=0.1, target_modules=["c_proj", "c_attn"], 
    )
    lora_model = get_peft_model(base_model, peft_config)
    
    try:
        raw_dataset = load_dataset("json", data_files=DATASET_FILE)
        
        def tokenize_function(examples):
            return tokenizer(
                examples["prompt"] + examples["completion"],
                truncation=True,
                padding="max_length",
                max_length=MAX_TOKEN_LENGTH
            )

        tokenized_dataset = raw_dataset.map(tokenize_function, batched=True, remove_columns=raw_dataset["train"].column_names if "train" in raw_dataset else [],)
    except Exception:
        tokenized_dataset = None

# --- FUNCIÓN DE ENTRENAMIENTO (EJECUTADA EN HILO SEPARADO) ---

def autonomous_train_lora(epochs, batch_size, learning_rate):
    """Ejecuta el entrenamiento en un hilo separado para la autonomía."""
    global lora_model, tokenized_dataset, lora_generator, version_number, is_trained, training_status_message

    try:
        with global_lock:
            if tokenized_dataset is None or "train" not in tokenized_dataset:
                training_status_message = "ERROR: No se puede entrenar. Dataset no disponible."
                return
            
            # 1. ACTUALIZAR VERSIÓN (Pre-incremento)
            if is_trained:
                 version_number += 0.1
            else:
                 version_number = 1.0
                 
            # 2. CONFIGURACIÓN E INICIO DEL ENTRENAMIENTO
            training_status_message = f"🧠 ENTRENANDO V{version_number:.1f} (Epochs: {epochs})...."
            print(f"\n[AUTÓNOMO] {training_status_message}")
            
            data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
            training_args = TrainingArguments(
                output_dir=LORA_PATH,
                per_device_train_batch_size=int(batch_size),
                num_train_epochs=float(epochs),
                learning_rate=float(learning_rate),
                save_total_limit=1,
                logging_steps=10,
                push_to_hub=False,
                disable_tqdm=True, 
                report_to="none"
            )

            trainer = Trainer(model=lora_model, args=training_args, train_dataset=tokenized_dataset["train"], data_collator=data_collator)
            
            trainer.train()
            lora_model.save_pretrained(LORA_PATH) 
            tokenizer.save_pretrained(LORA_PATH) 
            
            # 3. Marcar como entrenado
            is_trained = True
            training_status_message = f"✅ ENTRENAMIENTO V{version_number:.1f} COMPLETADO. Modelo listo para Hot Swap."
            print(f"[AUTÓNOMO] {training_status_message}")

    except Exception as e:
        training_status_message = f"ERROR CRÍTICO durante el entrenamiento autónomo: {e}"
        print(f"[AUTÓNOMO] {training_status_message}")

# --- FUNCIÓN DE GENERACIÓN (CORREGIDA PARA HOT SWAP ESTABLE) ---

def generate_text(prompt_text):
    """Genera código y dispara el ciclo de reentrenamiento autónomo si es necesario."""
    global lora_generator, generations_since_last_train, is_trained, version_number, training_status_message

    if not is_trained:
        return "ERROR: El modelo LoRA no ha sido entrenado. Por favor, espere mientras la IA se inicializa con el entrenamiento V1.0.", update_status()


    # 1. HOT SWAP (Verifica si el modelo necesita recargarse con la nueva versión)
    if lora_generator is None:
        with global_lock:
            try:
                # SOLUCIÓN CLAVE: Reintroducir offload_folder y low_cpu_mem_usage aquí también.
                base_model_gen = AutoModelForCausalLM.from_pretrained(
                    BASE_MODEL, 
                    device_map="auto", 
                    offload_folder=TEMP_OFFLOAD_FOLDER, # SOLUCIÓN 2
                    low_cpu_mem_usage=True,
                    trust_remote_code=True,
                )
                model_with_lora = PeftModel.from_pretrained(base_model_gen, LORA_PATH)
                final_model = model_with_lora.merge_and_unload()
                final_model.eval() 
                lora_generator = pipeline("text-generation", model=final_model, tokenizer=tokenizer)
                print(f"[HOT SWAP] 🔄 Modelo de inferencia V{version_number:.1f} recargado y listo.")
            except Exception as e:
                # Si la recarga falla, retorna un error
                return f"Error al cargar el modelo V{version_number:.1f} para inferencia: {e}", update_status()


    # 2. Generación de texto (Lógica de inferencia)
    try:
        # Prepara el prompt para guiar la generación del código
        prompt_with_indent = prompt_text.strip() + "\n    "
        output = lora_generator(prompt_with_indent, max_new_tokens=150, temperature=0.7, top_p=0.9, clean_up_tokenization_spaces=True)
        full_output = output[0]["generated_text"]
        
        # Extrae solo la parte de la compleción (el código generado)
        start_index = full_output.find(prompt_with_indent)
        completion = full_output[start_index + len(prompt_with_indent):] if start_index != -1 else full_output
        
        # 3. Aumentar contador de autonomía
        with global_lock:
            generations_since_last_train += 1
            current_count = generations_since_last_train
            current_version = version_number

        # 4. Verificar si se requiere reentrenamiento (y dispararlo en un nuevo hilo)
        notification = ""
        if current_count >= GENERATION_LIMIT_TO_TRAIN:
            # Verifica que no haya otro hilo de entrenamiento ya corriendo
            if not any(isinstance(t, threading.Thread) and t.name == 'AutonomousTrainer' for t in threading.enumerate()):
                print(f"[AUTONOMÍA] Generación #{current_count} alcanzada. Disparando reentrenamiento autónomo en segundo plano...")
                
                # Reiniciar el contador de generaciones y forzar Hot Swap en la próxima interacción
                with global_lock:
                    generations_since_last_train = 0
                    lora_generator = None 
                    
                trainer_thread = threading.Thread(
                    target=autonomous_train_lora, 
                    args=(AUTONOMOUS_EPOCHS, 2, 5e-5), 
                    name='AutonomousTrainer'
                )
                trainer_thread.daemon = True
                trainer_thread.start()
                
                notification = f"\n\n--- [AUTONOMÍA] La IA ha iniciado el reentrenamiento V{current_version+0.1:.1f} para mejorar la traducción de tu diálogo. La próxima generación cargará la nueva versión. ---"
            
        # Retorna el código Y el estado actualizado
        return completion + notification, update_status()

    except Exception as e:
        # Si falla la generación, retorna el mensaje de error y el estado actual
        return f"Error generando texto: {e}", update_status()

# --- FUNCIÓN PARA INICIALIZACIÓN Y ENTRENAMIENTO V1.0 (Obligatorio) ---

def initialize_and_train_v1():
    """Ejecuta el entrenamiento inicial V1.0 de forma autónoma al iniciar."""
    global version_number, is_trained, training_status_message
    if not is_trained:
        autonomous_train_lora(epochs=DEFAULT_EPOCHS, batch_size=2, learning_rate=5e-5)
    else:
        # Si ya está entrenado, actualiza la versión y el mensaje
        training_status_message = f"✅ Modelo V{version_number:.1f} ya entrenado. Listo."
        print(f"[INICIALIZACIÓN] {training_status_message}")

# --- FUNCIÓN PARA ACTUALIZAR EL ESTADO EN LA UI ---

def update_status():
    """Actualiza la versión y el estado del entrenamiento en la interfaz de Gradio."""
    global training_status_message, version_number
    # Retorna un texto en Markdown que se actualiza constantemente
    return f"**Versión de Comprensión:** V{version_number:.1f} | **Estado del Entrenador:** {training_status_message}"


# --- INTERFAZ GRADIO ---
with gr.Blocks(title="AmorCoderAI - Aprendizaje Continuo") as demo:
    gr.Markdown("# 💙 AmorCoderAI - Asistente de Código con Aprendizaje Continuo")
    
    # Muestra la versión y el estado.
    version_and_status = gr.Markdown(
        f"**Versión de Comprensión:** V{version_number:.1f} | **Estado del Entrenador:** {training_status_message}",
        elem_id="status_display"
    )

    gr.Markdown(f"**Modo Autónomo:** La IA se reentrena automáticamente cada **{GENERATION_LIMIT_TO_TRAIN}** códigos generados. Esto mejora su capacidad para traducir tu español conversacional a código.")

    with gr.Tab("✨ Generación de Código"):
        gr.Markdown("## Escribe tu idea en palabras (¡Usa español fluido!)")
        
        gr.Markdown("Recomendación inicial: Usa el siguiente formato para obtener el mejor código mientras la IA aprende tu idioma:")
        
        prompt = gr.Textbox(
            label="Instrucción de Programación:", 
            lines=4,
            placeholder="# Descripción: Quiero que me hagas un código similar a Google Gemini.\n# Completa la siguiente función:\ndef generar_contenido(prompt, modelo):"
        )
        generate_button = gr.Button("💬 Generar código y disparar Aprendizaje")
        output_box = gr.Textbox(label="Código generado", lines=10)
        
        # Conexión del botón con la función principal
        generate_button.click(
            generate_text, 
            inputs=prompt, 
            outputs=[output_box, version_and_status], 
        )

    # El estado se actualiza solo al cargar la página.
    demo.load(update_status, None, version_and_status)


# --- INICIO DE LA APLICACIÓN ---
if __name__ == "__main__":
    setup_resources()
    
    # Lanza el entrenamiento V1.0 inicial en un hilo para que no congele la UI
    initialization_thread = threading.Thread(target=initialize_and_train_v1, name='InitializationTrainer')
    initialization_thread.daemon = True
    initialization_thread.start()
        
    print(f"\n💻 LANZANDO INTERFAZ GRADIO (El entrenamiento V1.0/V1.1 se ejecuta en segundo plano)")
    demo.launch()