Created
June 8, 2025 14:50
-
-
Save charlenopires/0886f557f5151d2cd2b8fe73b42e7e82 to your computer and use it in GitHub Desktop.
Agente de Google ADK que lê conteúdo de um Doc em PDF e interage por meio de um Chat em Streamlit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
from pathlib import Path | |
import google.generativeai as genai | |
import pdfplumber | |
import streamlit as st | |
# Tenta importar o Agent do Google ADK. | |
# Se não estiver disponível, define Agent como None para fallback. | |
try: | |
from google.adk.agents import Agent | |
except ImportError: | |
Agent = None # Fallback caso o ADK não esteja instalado | |
from dotenv import load_dotenv | |
# Carrega variáveis de ambiente do arquivo .env localizado um nível acima. | |
load_dotenv(dotenv_path="../.env") | |
class DocumentAnalyzerWithADK: | |
""" | |
Classe principal para analisar documentos PDF usando o Google ADK Agent. | |
Mantém o contexto do PDF para responder a perguntas. | |
""" | |
def __init__(self, pdf_path: str | None = None): | |
""" | |
Inicializa o analisador de documentos. | |
Args: | |
pdf_path (str | None, optional): Caminho para o arquivo PDF. Defaults to None. | |
""" | |
self.pdf_content = "" # Conteúdo textual extraído do PDF | |
self.agent: Agent | None = None # Instância do agente ADK ou None | |
# Se um caminho de PDF for fornecido e o arquivo existir, extrai o conteúdo e inicializa o agente com contexto. | |
if pdf_path and os.path.exists(pdf_path): | |
self.pdf_content = self._extract_pdf_content(pdf_path) | |
self._initialize_agent_with_context() | |
else: | |
# Caso contrário, inicializa um agente básico sem contexto de documento. | |
self._initialize_basic_agent() | |
def _extract_pdf_content(self, pdf_path: str) -> str: | |
""" | |
Extrai o conteúdo textual de um arquivo PDF. | |
Args: | |
pdf_path (str): Caminho para o arquivo PDF. | |
Returns: | |
str: Conteúdo textual extraído do PDF. Retorna string vazia em caso de erro. | |
""" | |
try: | |
with pdfplumber.open(pdf_path) as pdf: | |
text_content = "" | |
# Itera sobre cada página do PDF | |
for page in pdf.pages: | |
page_text = page.extract_text() # Extrai texto da página atual | |
if page_text: | |
text_content += ( | |
page_text + "\n\n" | |
) # Adiciona o texto da página e duas quebras de linha | |
return ( | |
text_content.strip() | |
) # Remove espaços em branco extras do início e fim | |
except Exception as e: | |
st.error(f"Erro ao extrair conteúdo do PDF: {str(e)}") | |
return "" | |
def _initialize_agent_with_context(self): | |
""" | |
Inicializa o agente do Google ADK com o contexto do documento PDF carregado. | |
Define as instruções e o modelo a ser usado pelo agente. | |
""" | |
# Instrução detalhada para o agente, incluindo o conteúdo do PDF. | |
context_instruction = f""" | |
Você é um assistente especializado em análise de documentos. Você já tem conhecimento completo do seguinte documento: | |
CONTEÚDO DO DOCUMENTO: | |
{self.pdf_content} | |
INSTRUÇÕES: | |
- Use o conteúdo do documento acima para responder às perguntas dos usuários | |
- Seja preciso e cite partes específicas do documento quando relevante | |
- Se a pergunta não puder ser respondida com base no documento, informe isso claramente | |
- Mantenha suas respostas organizadas e fáceis de entender | |
- Você pode fazer análises, resumos, extrair informações específicas, etc. | |
""" | |
if Agent is not None: | |
# Se o ADK Agent estiver disponível, cria uma instância. | |
self.agent = Agent( | |
name="DocumentAnalyzer", # Nome do agente | |
model="gemini-2.0-flash", # Modelo de IA a ser usado | |
instruction=context_instruction, # Instruções para o agente | |
) | |
else: | |
# Se o ADK não estiver disponível, define o agente como None e imprime um aviso. | |
self.agent = None | |
print( | |
"Aviso: Google ADK não está disponível. Usando fallback com Gemini API diretamente." | |
) | |
def _initialize_basic_agent(self): | |
""" | |
Inicializa um agente básico do Google ADK sem contexto de documento. | |
Usado quando nenhum PDF é carregado ou encontrado. | |
""" | |
if Agent is not None: | |
# Se o ADK Agent estiver disponível, cria uma instância com instruções genéricas. | |
self.agent = Agent( | |
name="DocumentAnalyzer", | |
model="gemini-2.0-flash", | |
instruction=""" | |
Você é um assistente para análise de documentos. | |
Atualmente não há nenhum documento carregado. | |
Informe ao usuário que precisa carregar um documento primeiro. | |
""", | |
) | |
else: | |
# Se o ADK não estiver disponível, define o agente como None e imprime um aviso. | |
self.agent = None | |
print( | |
"Aviso: Google ADK não está disponível. Usando fallback com Gemini API diretamente." | |
) | |
def chat(self, user_message: str) -> str: | |
""" | |
Processa uma mensagem do usuário e retorna a resposta do agente. | |
Usa a API Gemini diretamente como fallback se o ADK não estiver configurado | |
ou para incluir o contexto do documento dinamicamente. | |
Args: | |
user_message (str): Mensagem enviada pelo usuário. | |
Returns: | |
str: Resposta gerada pelo modelo de IA. | |
""" | |
try: | |
# Configura a API do Google Generative AI com a chave da variável de ambiente. | |
genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) | |
model = genai.GenerativeModel( | |
"gemini-2.0-flash" | |
) # Modelo Gemini para geração de conteúdo | |
if self.pdf_content: | |
# Se houver conteúdo de PDF, monta o prompt com o contexto do documento. | |
prompt = f""" | |
CONTEXTO DO DOCUMENTO: | |
{self.pdf_content} | |
INSTRUÇÕES: | |
- Você é um assistente especializado em análise de documentos | |
- Use o contexto do documento acima para responder à pergunta do usuário | |
- Seja preciso e cite partes específicas do documento quando relevante | |
- Se a pergunta não puder ser respondida com base no documento, informe isso claramente | |
PERGUNTA DO USUÁRIO: {user_message} | |
RESPOSTA: | |
""" | |
else: | |
# Se não houver conteúdo de PDF, monta um prompt informando a necessidade de carregar um documento. | |
prompt = f""" | |
Você é um assistente para análise de documentos, mas atualmente não há nenhum documento carregado. | |
Informe ao usuário que precisa carregar um documento primeiro. | |
Pergunta do usuário: {user_message} | |
""" | |
# Gera a resposta usando o modelo Gemini. | |
response = model.generate_content(prompt) | |
return response.text | |
except Exception as e: | |
return f"Erro ao processar mensagem: {str(e)}" | |
def has_document(self) -> bool: | |
""" | |
Verifica se um documento PDF foi carregado e seu conteúdo extraído. | |
Returns: | |
bool: True se o documento estiver carregado, False caso contrário. | |
""" | |
return bool(self.pdf_content) | |
def get_document_summary(self) -> str: | |
""" | |
Retorna um breve resumo ou preview do documento carregado. | |
Returns: | |
str: Um preview do conteúdo do documento ou uma mensagem indicando que nenhum documento foi carregado. | |
""" | |
if not self.pdf_content: | |
return "Nenhum documento carregado." | |
# Cria um preview com os primeiros 300 caracteres do documento. | |
preview = ( | |
self.pdf_content[:300] + "..." | |
if len(self.pdf_content) > 300 | |
else self.pdf_content | |
) | |
return f"Documento carregado ({len(self.pdf_content)} caracteres). Preview: {preview}" | |
def main(): | |
""" | |
Função principal que executa a aplicação Streamlit para o analisador de documentos. | |
Configura a página, gerencia o estado da sessão e a interação com o usuário. | |
""" | |
# Configurações da página Streamlit | |
st.set_page_config( | |
page_title="Analisador de Documentos com Google ADK", | |
page_icon="📄", | |
layout="wide", | |
) | |
st.title("📄 Analisador de Documentos com Google ADK") | |
st.markdown("Chat com um agente de IA que conhece o conteúdo do documento PDF!") | |
# Verifica se a chave da API do Google está configurada nas variáveis de ambiente. | |
if not os.getenv("GOOGLE_API_KEY"): | |
st.error("❌ Por favor, configure a variável GOOGLE_API_KEY no arquivo .env") | |
st.stop() # Interrompe a execução se a chave não estiver configurada. | |
# Inicializa o agente no estado da sessão se ainda não existir. | |
if "agent" not in st.session_state: | |
# Define o caminho para o arquivo PDF padrão. | |
pdf_path = Path(__file__).parent / "doc.pdf" | |
st.session_state.agent = DocumentAnalyzerWithADK(str(pdf_path)) | |
# Exibe uma mensagem de sucesso ou aviso dependendo se o documento foi carregado. | |
if st.session_state.agent.has_document(): | |
st.success("✅ Documento PDF carregado com sucesso!") | |
else: | |
st.warning("⚠️ Documento PDF não encontrado em doc.pdf") | |
# Inicializa o histórico de mensagens no estado da sessão se ainda não existir. | |
if "messages" not in st.session_state: | |
st.session_state.messages = [] | |
# Adiciona uma mensagem de boas-vindas ao histórico. | |
if st.session_state.agent.has_document(): | |
welcome_msg = "Olá! Eu já conheço o conteúdo do documento PDF. Pode fazer qualquer pergunta sobre ele!" | |
else: | |
welcome_msg = "Olá! Não encontrei o arquivo doc.pdf. Por favor, adicione o documento para começarmos." | |
st.session_state.messages.append({"role": "assistant", "content": welcome_msg}) | |
# Barra lateral (sidebar) para informações do documento e ações. | |
with st.sidebar: | |
st.header("📊 Status do Documento") | |
if st.session_state.agent.has_document(): | |
st.success("✅ Documento carregado") | |
# Expansor para mostrar um resumo do documento. | |
with st.expander("📋 Resumo do Documento"): | |
st.text_area( | |
"Conteúdo:", | |
st.session_state.agent.get_document_summary(), | |
height=200, | |
disabled=True, | |
) | |
else: | |
st.error("❌ Nenhum documento carregado") | |
st.info("Coloque o arquivo doc.pdf na pasta docanalyzer/") | |
# Botão para recarregar o documento. | |
if st.button("🔄 Recarregar Documento"): | |
pdf_path = Path(__file__).parent / "doc.pdf" | |
st.session_state.agent = DocumentAnalyzerWithADK(str(pdf_path)) | |
if st.session_state.agent.has_document(): | |
st.success("✅ Documento recarregado!") | |
st.rerun() # Reinicia a aplicação para refletir as mudanças. | |
else: | |
st.error("❌ Falha ao carregar documento") | |
# Seção principal para o chat com o agente. | |
st.header("💬 Chat com o Agente") | |
# Exibe as mensagens do histórico de chat. | |
for message in st.session_state.messages: | |
with st.chat_message(message["role"]): | |
st.markdown(message["content"]) | |
# Campo de entrada para o usuário enviar mensagens. | |
if prompt := st.chat_input("Faça uma pergunta sobre o documento..."): | |
# Adiciona a mensagem do usuário ao histórico e a exibe. | |
st.session_state.messages.append({"role": "user", "content": prompt}) | |
with st.chat_message("user"): | |
st.markdown(prompt) | |
# Processa a mensagem do usuário com o agente e exibe a resposta. | |
with st.chat_message("assistant"): | |
with st.spinner("🤔 Analisando..."): # Mostra um indicador de carregamento. | |
response = st.session_state.agent.chat(prompt) | |
st.markdown(response) | |
# Adiciona a resposta do assistente ao histórico. | |
st.session_state.messages.append({"role": "assistant", "content": response}) | |
# Botão para limpar o histórico de chat. | |
col1, col2 = st.columns([1, 4]) # Layout em colunas para o botão | |
with col1: | |
if st.button("🗑️ Limpar Chat"): | |
st.session_state.messages = [] # Limpa as mensagens. | |
# Readiciona a mensagem de boas-vindas apropriada. | |
if st.session_state.agent.has_document(): | |
welcome_msg = ( | |
"Chat limpo! Pode continuar fazendo perguntas sobre o documento." | |
) | |
else: | |
welcome_msg = "Chat limpo! Adicione um documento para começar." | |
st.session_state.messages.append( | |
{"role": "assistant", "content": welcome_msg} | |
) | |
st.rerun() # Reinicia a aplicação para refletir o chat limpo. | |
# Ponto de entrada da aplicação: executa a função main se o script for rodado diretamente. | |
if __name__ == "__main__": | |
main() | |
# Para rodar, use o comando: uv run streamlit run agent.py |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment