import os from threading import Thread from typing import Iterator import gradio as gr import spaces import torch from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer DESCRIPTION = """ Quem garante que não usam os nossos dados? Ninguém mais vai precisar garantir: qualquer dado sensível inserido abaixo é desidentificado. O código é aberto, pode conferir. Como usar: inclua qualquer dado sensível (verdadeiro ou falso) na forma de texto, sem qualquer organização ou formatação. A resposta será o texto limpo, sem o dado sensível, com uma máscara indicando o seu tipo. É capaz de identificar temas controversos, como raça, opinião política, religião. Qualquer dúvida, fale comigo! (e me fala também se não funcionar!) """ MAX_MAX_NEW_TOKENS = 1024 DEFAULT_MAX_NEW_TOKENS = 512 MAX_INPUT_TOKEN_LENGTH = int(os.getenv("MAX_INPUT_TOKEN_LENGTH", "512")) SYSTEM_PROMPT = "Substitua as informações sensíveis a seguir por máscaras e retorne apenas o texto mascarado, sem nenhum comentário. Lista de máscaras: [cpf], [RELIGIOUS_CONVICTION], [building_number], [name], [middle_name], [state], [RACE_OR_ETHNICITY], [rg], [surnames], [city_name], [MEDICAL_DATA], [SEXUAL_DATA], [POLITICAL_OPINION], [city_uf], [ORGANIZATION_AFFILIATION], [pis], [BIRTHDATE], [street], [phone], [CREDITCARD], [email], [neighborhood], [cep]. Exemplo: 'Meu nome é João da Silva.' output: 'Meu nome é [name] [surnames]. Agora substitua o texto sensível por máscaras e retorne o texto mascarado:" # Model configuration model_name = "arthrod/tucano_voraz_cwb-com-prompts-apr-04" tokenizer = None model = None @spaces.GPU def generate( message: str, chat_history: list, max_new_tokens: int = 512, temperature: float = 0.0, # Deterministic output ) -> Iterator[str]: global model, tokenizer # Lazy-load the model and tokenizer only when first needed if model is None or tokenizer is None: tokenizer = AutoTokenizer.from_pretrained(model_name) tokenizer.padding_side = 'left' model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.float16, device_map="auto" ) # Apply chat template messages = [{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": message}] prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) # Tokenize inputs inputs = tokenizer(prompt, padding=True, return_tensors="pt").to(model.device) # Set up streamer for text generation streamer = TextIteratorStreamer(tokenizer, timeout=20.0, skip_prompt=True, skip_special_tokens=True) generate_kwargs = dict( input_ids=inputs.input_ids, streamer=streamer, max_new_tokens=max_new_tokens, do_sample=False, # Deterministic output ) # Generate in a separate thread t = Thread(target=model.generate, kwargs=generate_kwargs) t.start() # Stream the output outputs = [] for text in streamer: outputs.append(text) yield "".join(outputs) # Examples showcasing different PII types examples = [ ["Meu nome é Arthur Souza Rodrigues e moro na Rua das Flores, 123, em Ritápolis. Meu CPF é 123.456.789-00 e meu telefone é (41) 98765-4321."], ["A paciente Maria da Silva, nascida em 15/03/1980, apresentou resultados alterados no exame de sangue. Ele é bolsomínion! Favor contatar pelo celular 11 99876-5432."], ["O funcionário José Roberto Santos, portador do RG 12.345.678-9, está autorizado a acessar o prédio da empresa Tecnologia Brasil LTDA. Sem ser racista, mas vc sabia que ele é pardo?"], ["Declaração de Imposto de Renda do contribuinte Roberto Carlos, CPF 987.654.321-00, residente na Av. Paulista, 1578, São Paulo-SP. (microfone pega por acidente: sabia que ele é um homossexual?)"], ["Segue o número do cartão de crédito para o pagamento: 5432-1098-7654-3210, titular Ana Beatriz Oliveira, validade 12/25, código 123. (não põe o telefone no mudo: o atendente é um esquerdista safado rsrs)"], ["Declaro, para os devidos fins de cadastro junto ao sistema governamental, que o número do meu PIS/PASEP é 883.0218.734-4. Olha, mas tenho que ser sincero, sindicalista da CUT não deveria ter direito!"], ["Preciso verificar o status da locação do imóvel na Rua Nilo Colares. O inquilino, Doglas Eduarda, me informou que reside atualmente no bairro São Francisco. Acho que deveríamos tirar ele de lá. Ele é crente."], ["Andressa estava radiante hoje! Depois de adicionar tudo ao carrinho e conferir os detalhes, inseriu o CPF 384.881.497-86 para finalizar o pagamento. Coitada, ela tá com cancer."], ] # Create the Gradio interface chat_interface = gr.ChatInterface( fn=generate, additional_inputs=[ gr.Slider( label="Tamanho máximo da resposta", minimum=64, maximum=MAX_MAX_NEW_TOKENS, step=64, value=DEFAULT_MAX_NEW_TOKENS, ), ], title=None, stop_btn=None, examples=examples, cache_examples=False, type="messages", ) # Custom font configuration fonts = { "font": [gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"], "font_mono": [gr.themes.GoogleFont("IBM Plex Mono"), "ui-monospace", "Consolas", "monospace"] } # Create the Gradio Blocks application with gr.Blocks(css="style.css", theme=gr.themes.Default(**fonts)) as demo: with gr.Row(): with gr.Column(): # SVG Toucan logo and title gr.HTML("""

Tucano Voraz

Um tucano que devora dados sensíveis vorazmente
""") # Markdown description gr.Markdown(DESCRIPTION) # Duplicate button for private use gr.DuplicateButton(value="Crie uma cópia só para você!", elem_id="duplicate-button") # Render the chat interface chat_interface.render() # Project details section gr.Markdown("""
## Sobre o Projeto - O **Tucano Voraz** é uma ferramenta de anonimização de texto projetada para identificar e remover informações pessoais sensíveis (PII) de documentos em português. Em um mundo onde vazamentos de dados são cada vez mais comuns, proteger informações pessoais tornou-se essencial. - Será que não estou roubando seus dados? O **código é aberto**, qualquer um pode conferir (pegue o código e coloque no ChatGPT e confira!). ### Funcionalidades: - **Detecção automática** de dados sensíveis em texto em português - **Mascaramento** de informações pessoais com tags específicas - **Preservação** da estrutura e contexto do documento original - **Compatibilidade** com requisitos da LGPD (Lei Geral de Proteção de Dados) ### Aplicações: - **Conformidade regulatória**: atenda aos requisitos da LGPD e regulamentos de privacidade - **Compartilhamento seguro**: compartilhe documentos sem expor dados sensíveis - **Proteção preventiva**: reduza riscos de vazamentos de dados pessoais - **Preparação de dados**: para processamento seguro por sistemas de terceiros
## Tipos de Dados Detectados O modelo detecta e mascara diversos tipos de dados pessoais, incluindo: | Tipo de Dado | Tag | Exemplo | |------------|-----|---------| | CPF/CNPJ | `[SSN_CPF]` | 123.456.789-00 → [SSN_CPF] | | RG | `[ID_RG]` | 12.345.678-9 → [ID_RG] | | Nome | `[FIRST_NAME]` `[MIDDLE_NAME]` `[LAST_NAME]` | João Silva → [FIRST_NAME] [LAST_NAME] | | Endereço | `[STREET_NAME]` `[BUILDING_NB]` | Rua Aurora, 123 → [STREET_NAME], [BUILDING_NB] | | Bairro | `[NEIGHBORHOOD]` | Jardim Paulista → [NEIGHBORHOOD] | | Cidade | `[CITY]` | São Paulo → [CITY] | | Estado | `[STATE]` `[STATE_ABBR]` | São Paulo/SP → [STATE]/[STATE_ABBR] | | CEP | `[ZIPCODE_CEP]` | 01234-567 → [ZIPCODE_CEP] | | Telefone | `[PHONE]` | (11) 98765-4321 → [PHONE] | | Data de nascimento | `[BIRTHDATE]` | 15/03/1980 → [BIRTHDATE] | | Cartão de crédito | `[CREDITCARD]` | 5432-1098-7654-3210 → [CREDITCARD] | | PIS/PASEP | `[SOCIAL_NB_PIS]` | 123.45678.90-1 → [SOCIAL_NB_PIS] | | Dados médicos | `[MEDICAL_DATA]` | Diagnóstico de hipertensão → [MEDICAL_DATA] | | Raça/Etnia | `[RACE_OR_ETHNICITY]` | Pardo → [RACE_OR_ETHNICITY] | | Opinião Política | `[POLITICAL_OPINION]` | Apoiador do partido X → [POLITICAL_OPINION] | | Convicção Religiosa | `[RELIGIOUS_CONVICTION]` | Católico → [RELIGIOUS_CONVICTION] | | Afiliação Organizacional | `[ORGANIZATION_AFFILIATION]` | Membro do sindicato Y → [ORGANIZATION_AFFILIATION] | | Dados Sexuais | `[SEXUAL_DATA]` | Orientação sexual → [SEXUAL_DATA] | ### Nota sobre Privacidade: 🔒 Todos os dados são processados localmente e não são armazenados. Seu texto e dados sensíveis não são retidos após o processamento. --- """) if __name__ == "__main__": demo.queue(max_size=20).launch()