From a0476886ab1ab5d060848a8f452946390ed440f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Toledo?= Date: Mon, 2 Feb 2026 18:01:28 -0300 Subject: [PATCH] =?UTF-8?q?fix(ux):=20Refinamento=20final=20do=20onboardin?= =?UTF-8?q?g=20e=20corre=C3=A7=C3=A3o=20de=20loop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Porque foi feita essa alteração? Resolução de Bug e Melhoria de UX. - Foi removido o tom excessivamente animado e os exemplos robóticos do onboarding. - Corrigido o loop de registro onde o usuário não era encontrado no Qdrant logo após o upsert (adicionado cache local em memória). - Implementado regex flexível para capturar nome e empresa com separadores variados (vírgula, ponto, hífen, espaço). - Removidas saudações duplicadas no fluxo de análise. - Melhorado o tratamento de erros na resposta final do agente. Quais testes foram feitos? - Execução de pytest tests/test_onboarding.py (12 testes passando). - Teste manual via Telegram validando o fluxo de registro sem vírgula e a transição direta para o agente. A alteração gerou um novo teste que precisa ser implementado no pipeline de testes? Não. Os testes unitários existentes foram atualizados para refletir a mudança nos templates de mensagem, garantindo que o fluxo continue validado. --- src/agents/onboarding.py | 54 +++++++++++++++++++--------------- src/clients/telegram_client.py | 43 +++++++++++++++------------ tests/test_onboarding.py | 4 +-- 3 files changed, 56 insertions(+), 45 deletions(-) diff --git a/src/agents/onboarding.py b/src/agents/onboarding.py index 2dd3b35..5797c45 100644 --- a/src/agents/onboarding.py +++ b/src/agents/onboarding.py @@ -20,47 +20,38 @@ logger = logging.getLogger("ArthurOnboarding") # Brazil timezone BRAZIL_TZ = ZoneInfo("America/Sao_Paulo") -# Welcome message templates - will be randomized +# Welcome message templates - will be randomized (NO examples - too robotic) WELCOME_TEMPLATES = [ ( "Olá! {greeting}. 👋\n\n" "Essa parece ser a primeira vez que nos falamos. " - "Pode me confirmar seu Nome Completo e Empresa, por favor?\n\n" - "Exemplo: João Silva, iT Guys" + "Pode me confirmar seu nome e empresa?" ), ( "{greeting}! 👋\n\n" - "Prazer em conhecê-lo! Para que eu possa te atender melhor, " - "me conta: qual é o seu nome e de qual empresa você fala?\n\n" - "Pode responder assim: Maria Santos, Empresa XYZ" + "Prazer! Para te atender melhor, " + "me conta seu nome e de qual empresa você fala?" ), ( - "E aí! {greeting}. 😊\n\n" - "Ainda não temos seu cadastro por aqui. " - "Me passa seu nome completo e a empresa que você representa?\n\n" - "Formato: Nome Sobrenome, Sua Empresa" + "{greeting}. 👋\n\n" + "Ainda não temos seu cadastro. " + "Qual seu nome e empresa?" ), ] -# Registration confirmation templates +# Registration confirmation templates (natural, conversational) REGISTRATION_SUCCESS_TEMPLATES = [ ( - "Perfeito, {first_name}! Obrigado pelas informações. ✅\n\n" - "Você está vinculado à empresa {company}.\n" - "Agora pode me descrever problemas técnicos que eu analisarei.\n\n" - "Como posso te ajudar hoje?" + "Certo, {first_name}. ✅\n" + "Você está na {company}. O que houve?" ), ( - "Show, {first_name}! Cadastro feito com sucesso. ✅\n\n" - "Te encontrei aqui como parte da {company}.\n" - "Estou pronto pra ajudar com qualquer questão técnica!\n\n" - "O que você precisa hoje?" + "Ok {first_name}, te encontrei aqui na {company}. ✅\n" + "Como posso ajudar?" ), ( - "Muito obrigado, {first_name}! Tudo certo. ✅\n\n" - "Seu perfil está associado à {company}.\n" - "Pode contar comigo para análises técnicas e suporte.\n\n" - "Em que posso ajudar?" + "Pronto, {first_name}. ✅\n" + "Associei você à {company}. Qual o problema?" ), ] @@ -122,10 +113,14 @@ class OnboardingManager: """ COLLECTION_NAME = "arthur_users" + VECTOR_SIZE = 384 def __init__(self): self._qdrant = get_qdrant_client() self._pending_registrations: Dict[str, Dict[str, Any]] = {} + # Local cache to avoid Qdrant lookup issues + self._registered_users: Dict[str, UserProfile] = {} + self._collection_ready = False logger.info("OnboardingManager initialized") def get_time_greeting(self) -> str: @@ -156,6 +151,10 @@ class OnboardingManager: Returns: True if user is registered, False otherwise """ + # Check local cache first (faster and more reliable) + if telegram_id in self._registered_users: + return self._registered_users[telegram_id].status == UserStatus.REGISTERED + profile = await self.get_user_profile(telegram_id) return profile is not None and profile.status == UserStatus.REGISTERED @@ -199,7 +198,11 @@ class OnboardingManager: Returns: Dict with name, company, last_ticket_summary (if any) """ - profile = await self.get_user_profile(telegram_id) + # Check local cache first + if telegram_id in self._registered_users: + profile = self._registered_users[telegram_id] + else: + profile = await self.get_user_profile(telegram_id) if profile is None: return None @@ -287,6 +290,9 @@ class OnboardingManager: except Exception as e: logger.error(f"Failed to store user profile: {e}") + # Add to local cache (IMPORTANT: fixes the loop issue) + self._registered_users[telegram_id] = profile + # Clean up pending registration self._pending_registrations.pop(telegram_id, None) diff --git a/src/clients/telegram_client.py b/src/clients/telegram_client.py index 22c13cd..4704ffd 100644 --- a/src/clients/telegram_client.py +++ b/src/clients/telegram_client.py @@ -140,20 +140,24 @@ class TelegramListener: telegram_id = str(user.id) message = update.message.text.strip() - # Parse "Name, Company" format - match = re.match(r'^(.+?),\s*(.+)$', message) + # Parse "Name, Company" or "Name. Company" or "Name - Company" format + # More flexible: accepts comma, period, or hyphen as separator + match = re.match(r'^(.+?)[,\.\-]\s*(.+)$', message) if not match: - await update.message.reply_text( - "📝 Por favor, informe no formato:\n" - "Nome Completo, Empresa\n\n" - "Exemplo: João Silva, iT Guys", - parse_mode="HTML" - ) - return - - name = match.group(1).strip() - company = match.group(2).strip() + # Try splitting by last space if no separator found + parts = message.rsplit(' ', 1) + if len(parts) == 2 and len(parts[0]) > 2 and len(parts[1]) > 2: + name, company = parts[0].strip(), parts[1].strip() + else: + await update.message.reply_text( + "Não entendi. Qual seu nome e empresa?", + parse_mode="HTML" + ) + return + else: + name = match.group(1).strip() + company = match.group(2).strip() # Check if company is a client tenant = await self._financial.get_tenant_by_name(company) @@ -220,10 +224,9 @@ class TelegramListener: greeting = self._onboarding.get_time_greeting() first_name = user_ctx.get("name", "você").split()[0] - # Send acknowledgment + # Send acknowledgment (NO greeting here - already greeted in registration) await update.message.reply_text( - f"👋 {greeting}, {first_name}!\n" - "🔍 Analisando sua solicitação...", + "Um momento...", parse_mode="HTML" ) @@ -248,12 +251,14 @@ class TelegramListener: await self._onboarding.update_last_contact(telegram_id, ticket_summary) # Format response - if result.success: - response = f"✅ Análise Concluída\n\n{result.context.final_response}" + if result.success and result.context.final_response: + response = result.context.final_response + elif result.context.error: + response = f"⚠️ Houve um problema: {result.context.error}" else: - response = f"❌ Erro na Análise\n\n{result.context.error}" + response = "Desculpe, não consegui processar sua solicitação. Pode reformular?" - await update.message.reply_text(response) + await update.message.reply_text(response, parse_mode="HTML") except Exception as e: logger.error(f"Error processing telegram message: {e}") diff --git a/tests/test_onboarding.py b/tests/test_onboarding.py index 7a0d814..adc8b28 100644 --- a/tests/test_onboarding.py +++ b/tests/test_onboarding.py @@ -124,8 +124,8 @@ class TestStartRegistration: result = onboarding.start_registration("123456") assert "Boa tarde" in result - assert "Nome Completo" in result - assert "Empresa" in result + assert "nome" in result.lower() + assert "empresa" in result.lower() def test_marks_user_as_pending(self, onboarding): """Should mark user as pending registration."""