import datetime import gradio as gr import torch from cache_system import CacheHandler from header import article, header from newspaper import Article from prompts import summarize_clickbait_short_prompt from transformers import ( AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, GenerationConfig, LogitsProcessorList, TextStreamer, ) from utils import StopAfterTokenIsGenerated total_runs = 0 # Cargar el tokenizador tokenizer = AutoTokenizer.from_pretrained("somosnlp/NoticIA-7B") # Cargamos el modelo en 4 bits para usar menos VRAM # Usamos bitsandbytes por que es lo más sencillo de implementar para la demo aunque no es ni lo más rápido ni lo más eficiente quantization_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True, ) model = AutoModelForCausalLM.from_pretrained( "somosnlp/NoticIA-7B", torch_dtype=torch.bfloat16, device_map="auto", #quantization_config=quantization_config, ) print(f"Model loaded in {model.device}") # Parámetros de generación. generation_config = GenerationConfig( max_new_tokens=128, # Los resúmenes son cortos, no necesitamos más tokens min_new_tokens=1, # No queremos resúmenes vacíos do_sample=True, # Un poquito mejor que greedy sampling num_beams=1, use_cache=True, # Eficiencia top_k=40, top_p=0.1, repetition_penalty=1.1, # Ayuda a evitar que el modelo entre en bucles encoder_repetition_penalty=1.1, # Favorecemos que el modelo cite el texto original temperature=0.15, # temperature baja para evitar que el modelo genere texto muy creativo. ) # Stop words, para evitar que el modelo genere tokens que no queremos. stop_words = [ "", "", "\\n", "[/INST]", "[INST]", "### User:", "### Assistant:", "###", "", "", "\\n", "", ] # Creamos un logits processor para detener la generación cuando el modelo genere un stop word stop_criteria = LogitsProcessorList( [ StopAfterTokenIsGenerated( stops=[ torch.tensor(tokenizer.encode(stop_word, add_special_tokens=False)) for stop_word in stop_words.copy() ], eos_token_id=tokenizer.eos_token_id, ) ] ) def generate_text(url: str) -> (str, str): """ Dada una URL de una noticia, genera un resumen de una sola frase que revela la verdad detrás del titular. Args: url (str): URL de la noticia. Returns: str: Titular de la noticia. str: Resumen de la noticia. """ global cache_handler global total_runs total_runs += 1 print(f"Total runs: {total_runs}. Last run: {datetime.datetime.now()}") url = url.strip() if url.startswith("https://twitter.com/") or url.startswith("https://x.com/"): yield ( "🤖 Vaya, parece que has introducido la url de un tweet. No puedo acceder a tweets, tienes que introducir la URL de una noticia.", "❌❌❌ Si el tweet contiene una noticia, dame la URL de la noticia ❌❌❌", "Error", ) return ( "🤖 Vaya, parece que has introducido la url de un tweet. No puedo acceder a tweets, tienes que introducir la URL de una noticia.", "❌❌❌ Si el tweet contiene una noticia, dame la URL de la noticia ❌❌❌", "Error", ) # 1) Download the article # progress(0, desc="🤖 Accediendo a la noticia") # First, check if the URL is in the cache headline, text, resumen = cache_handler.get_from_cache(url, 0) if headline is not None and text is not None and resumen is not None: yield headline, resumen return headline, resumen else: try: article = Article(url) article.download() article.parse() headline = article.title text = article.text except Exception as e: print(e) headline = None text = None if headline is None or text is None: yield ( "🤖 No he podido acceder a la notica, asegurate que la URL es correcta y que es posible acceder a la noticia desde un navegador.", "❌❌❌ Inténtalo de nuevo ❌❌❌", "Error", ) return ( "🤖 No he podido acceder a la notica, asegurate que la URL es correcta y que es posible acceder a la noticia desde un navegador.", "❌❌❌ Inténtalo de nuevo ❌❌❌", "Error", ) # progress(0.5, desc="🤖 Leyendo noticia") try: prompt = summarize_clickbait_short_prompt(headline=headline, body=text) formatted_prompt = tokenizer.apply_chat_template( [{"role": "user", "content": prompt}], tokenize=False, add_generation_prompt=True, ) model_inputs = tokenizer( [formatted_prompt], return_tensors="pt", add_special_tokens=False ) streamer = TextStreamer(tokenizer=tokenizer, skip_prompt=True) model_output = model.generate( **model_inputs.to(model.device), streamer=streamer, generation_config=generation_config, logits_processor=stop_criteria, ) yield headline, streamer resumen = tokenizer.batch_decode( model_output, skip_special_tokens=True, clean_up_tokenization_spaces=True, )[0].replace("<|end_of_turn|>", "") resumen = resumen.split("GPT4 Correct Assistant:")[-1] except Exception as e: print(e) yield ( "🤖 Error en la generación.", "❌❌❌ Inténtalo de nuevo más tarde ❌❌❌", "Error", ) return ( "🤖 Error en la generación.", "❌❌❌ Inténtalo de nuevo más tarde ❌❌❌", "Error", ) cache_handler.add_to_cache( url=url, title=headline, text=text, summary_type=0, summary=resumen ) yield headline, resumen hits, misses, cache_len = cache_handler.get_cache_stats() print( f"Hits: {hits}, misses: {misses}, cache length: {cache_len}. Percent hits: {round(hits/(hits+misses)*100,2)}%." ) return headline, resumen # Usamos una cache para guardar las últimas URL procesadas # Los usuarios seguramente introducirán en un mismo día la misma URL varias veces, por que # diferentes personas querrán ver el resumen de la misma noticia. # La cache se encarga de guardar los resúmenes de las noticias para que no tengamos que volver a generarlos. # La cache tiene un tamaño máximo de 1000 elementos, cuando se llena, se elimina el elemento más antiguo. cache_handler = CacheHandler(max_cache_size=1000) demo = gr.Interface( generate_text, inputs=[ gr.Textbox( label="🌐 URL de la noticia", info="Introduce la URL de la noticia que deseas resumir.", value="https://somosnlp.org/", interactive=True, ) ], outputs=[ gr.Textbox( label="📰 Titular de la noticia", interactive=False, placeholder="Aquí aparecerá el título de la noticia", ), gr.Textbox( label="🗒️ Resumen", interactive=False, placeholder="Aquí aparecerá el resumen de la noticia.", ), ], # headline="⚔️ Clickbait Fighter! ⚔️", thumbnail="https://huggingface.co/datasets/Iker/NoticIA/resolve/main/assets/logo.png", theme="JohnSmith9982/small_and_pretty", description=header, article=article, cache_examples=False, concurrency_limit=1, examples=[ "https://www.huffingtonpost.es/virales/le-compra-abrigo-abuela-97nos-reaccion-fantasia.html", "https://emisorasunidas.com/2023/12/29/que-pasara-el-15-de-enero-de-2024/", "https://www.huffingtonpost.es/virales/llega-espana-le-llama-atencion-nombres-propios-persona.html", "https://www.infobae.com/que-puedo-ver/2023/11/19/la-comedia-familiar-y-navidena-que-ya-esta-en-netflix-y-puedes-ver-en-estas-fiestas/", "https://www.cope.es/n/1610984", ], submit_btn="Generar resumen", stop_btn="Detener generación", clear_btn="Limpiar", allow_flagging=False, ) demo.queue(max_size=None) demo.launch(share=False)