atendentepro 0.3.0__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atendentepro/README.md +649 -72
- atendentepro/__init__.py +15 -1
- atendentepro/agents/__init__.py +11 -0
- atendentepro/agents/answer.py +1 -2
- atendentepro/agents/escalation.py +510 -0
- atendentepro/agents/feedback.py +961 -0
- atendentepro/network.py +115 -15
- atendentepro/prompts/__init__.py +10 -0
- atendentepro/prompts/answer.py +2 -3
- atendentepro/prompts/escalation.py +190 -0
- atendentepro/prompts/feedback.py +232 -0
- {atendentepro-0.3.0.dist-info → atendentepro-0.5.1.dist-info}/METADATA +52 -38
- {atendentepro-0.3.0.dist-info → atendentepro-0.5.1.dist-info}/RECORD +17 -13
- {atendentepro-0.3.0.dist-info → atendentepro-0.5.1.dist-info}/WHEEL +0 -0
- {atendentepro-0.3.0.dist-info → atendentepro-0.5.1.dist-info}/entry_points.txt +0 -0
- {atendentepro-0.3.0.dist-info → atendentepro-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {atendentepro-0.3.0.dist-info → atendentepro-0.5.1.dist-info}/top_level.txt +0 -0
atendentepro/__init__.py
CHANGED
|
@@ -42,7 +42,7 @@ Para obter um token de licença, entre em contato: contato@bemonkai.com
|
|
|
42
42
|
Para mais informações, consulte a documentação.
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
|
-
__version__ = "0.
|
|
45
|
+
__version__ = "0.5.0"
|
|
46
46
|
__author__ = "BeMonkAI"
|
|
47
47
|
__email__ = "contato@bemonkai.com"
|
|
48
48
|
__license__ = "Proprietary"
|
|
@@ -92,6 +92,8 @@ from atendentepro.agents import (
|
|
|
92
92
|
create_confirmation_agent,
|
|
93
93
|
create_usage_agent,
|
|
94
94
|
create_onboarding_agent,
|
|
95
|
+
create_escalation_agent,
|
|
96
|
+
create_feedback_agent,
|
|
95
97
|
TriageAgent,
|
|
96
98
|
FlowAgent,
|
|
97
99
|
InterviewAgent,
|
|
@@ -100,7 +102,12 @@ from atendentepro.agents import (
|
|
|
100
102
|
ConfirmationAgent,
|
|
101
103
|
UsageAgent,
|
|
102
104
|
OnboardingAgent,
|
|
105
|
+
EscalationAgent,
|
|
106
|
+
FeedbackAgent,
|
|
103
107
|
go_to_rag,
|
|
108
|
+
ESCALATION_TOOLS,
|
|
109
|
+
FEEDBACK_TOOLS,
|
|
110
|
+
configure_feedback_storage,
|
|
104
111
|
)
|
|
105
112
|
|
|
106
113
|
# Network
|
|
@@ -178,6 +185,8 @@ __all__ = [
|
|
|
178
185
|
"create_confirmation_agent",
|
|
179
186
|
"create_usage_agent",
|
|
180
187
|
"create_onboarding_agent",
|
|
188
|
+
"create_escalation_agent",
|
|
189
|
+
"create_feedback_agent",
|
|
181
190
|
"TriageAgent",
|
|
182
191
|
"FlowAgent",
|
|
183
192
|
"InterviewAgent",
|
|
@@ -186,7 +195,12 @@ __all__ = [
|
|
|
186
195
|
"ConfirmationAgent",
|
|
187
196
|
"UsageAgent",
|
|
188
197
|
"OnboardingAgent",
|
|
198
|
+
"EscalationAgent",
|
|
199
|
+
"FeedbackAgent",
|
|
189
200
|
"go_to_rag",
|
|
201
|
+
"ESCALATION_TOOLS",
|
|
202
|
+
"FEEDBACK_TOOLS",
|
|
203
|
+
"configure_feedback_storage",
|
|
190
204
|
# Network
|
|
191
205
|
"AgentNetwork",
|
|
192
206
|
"create_standard_network",
|
atendentepro/agents/__init__.py
CHANGED
|
@@ -14,6 +14,8 @@ from .knowledge import create_knowledge_agent, KnowledgeAgent, go_to_rag
|
|
|
14
14
|
from .confirmation import create_confirmation_agent, ConfirmationAgent
|
|
15
15
|
from .usage import create_usage_agent, UsageAgent
|
|
16
16
|
from .onboarding import create_onboarding_agent, OnboardingAgent
|
|
17
|
+
from .escalation import create_escalation_agent, EscalationAgent, ESCALATION_TOOLS
|
|
18
|
+
from .feedback import create_feedback_agent, FeedbackAgent, FEEDBACK_TOOLS, configure_feedback_storage
|
|
17
19
|
|
|
18
20
|
__all__ = [
|
|
19
21
|
# Triage
|
|
@@ -41,5 +43,14 @@ __all__ = [
|
|
|
41
43
|
# Onboarding
|
|
42
44
|
"create_onboarding_agent",
|
|
43
45
|
"OnboardingAgent",
|
|
46
|
+
# Escalation
|
|
47
|
+
"create_escalation_agent",
|
|
48
|
+
"EscalationAgent",
|
|
49
|
+
"ESCALATION_TOOLS",
|
|
50
|
+
# Feedback
|
|
51
|
+
"create_feedback_agent",
|
|
52
|
+
"FeedbackAgent",
|
|
53
|
+
"FEEDBACK_TOOLS",
|
|
54
|
+
"configure_feedback_storage",
|
|
44
55
|
]
|
|
45
56
|
|
atendentepro/agents/answer.py
CHANGED
|
@@ -52,8 +52,7 @@ def create_answer_agent(
|
|
|
52
52
|
name=name,
|
|
53
53
|
handoff_description=(
|
|
54
54
|
"Agente responsável por sintetizar a resposta técnica final usando os dados coletados. "
|
|
55
|
-
"Após concluir a orientação, deve acionar o handoff para o Triage Agent a fim de encerrar o caso.
|
|
56
|
-
"Se identificar lacunas de informação, orienta o que falta e aciona o handoff para o Interview Agent."
|
|
55
|
+
"Após concluir a orientação, deve acionar o handoff para o Triage Agent a fim de encerrar o caso."
|
|
57
56
|
),
|
|
58
57
|
instructions=instructions,
|
|
59
58
|
handoffs=handoffs or [],
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Escalation Agent for AtendentePro.
|
|
4
|
+
|
|
5
|
+
Handles transfers to human support when:
|
|
6
|
+
- User explicitly requests
|
|
7
|
+
- Topic is not covered by the system
|
|
8
|
+
- Agent cannot resolve the issue
|
|
9
|
+
- User shows frustration or confusion
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import uuid
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from typing import List, Optional, Dict, Any, TYPE_CHECKING
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
|
|
20
|
+
from agents import Agent, function_tool
|
|
21
|
+
|
|
22
|
+
from atendentepro.config import RECOMMENDED_PROMPT_PREFIX
|
|
23
|
+
from atendentepro.models import ContextNote
|
|
24
|
+
from atendentepro.prompts.escalation import (
|
|
25
|
+
get_escalation_prompt,
|
|
26
|
+
EscalationPromptBuilder,
|
|
27
|
+
ESCALATION_INTRO,
|
|
28
|
+
DEFAULT_ESCALATION_TRIGGERS,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from atendentepro.guardrails import GuardrailCallable
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Type alias for the Escalation Agent
|
|
36
|
+
EscalationAgent = Agent[ContextNote]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# Configurações de Escalação
|
|
41
|
+
# =============================================================================
|
|
42
|
+
|
|
43
|
+
# Horário de atendimento padrão (pode ser sobrescrito via config)
|
|
44
|
+
DEFAULT_BUSINESS_HOURS = {
|
|
45
|
+
"start": 8,
|
|
46
|
+
"end": 18,
|
|
47
|
+
"days": [0, 1, 2, 3, 4], # Seg-Sex
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# =============================================================================
|
|
52
|
+
# Storage de Escalações (em memória - substituir por DB em produção)
|
|
53
|
+
# =============================================================================
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class Escalation:
|
|
57
|
+
"""Representa uma escalação para atendimento humano."""
|
|
58
|
+
protocolo: str
|
|
59
|
+
motivo: str
|
|
60
|
+
categoria: str # solicitacao, frustração, topico_nao_coberto, incerteza
|
|
61
|
+
nome_usuario: str
|
|
62
|
+
contato: str
|
|
63
|
+
tipo_contato: str # telefone, email, whatsapp
|
|
64
|
+
resumo_conversa: str
|
|
65
|
+
prioridade: str # baixa, normal, alta, urgente
|
|
66
|
+
status: str # pendente, em_atendimento, concluido, cancelado
|
|
67
|
+
data_criacao: datetime = field(default_factory=datetime.now)
|
|
68
|
+
data_atualizacao: datetime = field(default_factory=datetime.now)
|
|
69
|
+
atendente: Optional[str] = None
|
|
70
|
+
observacoes: Optional[str] = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Storage em memória
|
|
74
|
+
_escalations_storage: Dict[str, Escalation] = {}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _gerar_protocolo_escalacao() -> str:
|
|
78
|
+
"""Gera um protocolo único para escalação."""
|
|
79
|
+
timestamp = datetime.now().strftime("%Y%m%d")
|
|
80
|
+
unique_id = uuid.uuid4().hex[:6].upper()
|
|
81
|
+
return f"ESC-{timestamp}-{unique_id}"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _salvar_escalacao(escalation: Escalation) -> None:
|
|
85
|
+
"""Salva escalação no storage."""
|
|
86
|
+
_escalations_storage[escalation.protocolo] = escalation
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _buscar_escalacao(protocolo: str) -> Optional[Escalation]:
|
|
90
|
+
"""Busca escalação pelo protocolo."""
|
|
91
|
+
return _escalations_storage.get(protocolo.upper())
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _listar_escalacoes_pendentes() -> List[Escalation]:
|
|
95
|
+
"""Lista escalações pendentes."""
|
|
96
|
+
return [
|
|
97
|
+
e for e in _escalations_storage.values()
|
|
98
|
+
if e.status == "pendente"
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# =============================================================================
|
|
103
|
+
# Funções de Notificação (implementar conforme necessidade)
|
|
104
|
+
# =============================================================================
|
|
105
|
+
|
|
106
|
+
def _notificar_equipe(escalation: Escalation) -> bool:
|
|
107
|
+
"""
|
|
108
|
+
Notifica a equipe sobre nova escalação.
|
|
109
|
+
|
|
110
|
+
Em produção, integrar com:
|
|
111
|
+
- Email (SMTP)
|
|
112
|
+
- Slack/Teams
|
|
113
|
+
- Sistema de tickets
|
|
114
|
+
- Webhook
|
|
115
|
+
"""
|
|
116
|
+
webhook_url = os.getenv("ESCALATION_WEBHOOK_URL")
|
|
117
|
+
|
|
118
|
+
if webhook_url:
|
|
119
|
+
try:
|
|
120
|
+
import requests
|
|
121
|
+
payload = {
|
|
122
|
+
"protocolo": escalation.protocolo,
|
|
123
|
+
"motivo": escalation.motivo,
|
|
124
|
+
"prioridade": escalation.prioridade,
|
|
125
|
+
"usuario": escalation.nome_usuario,
|
|
126
|
+
"contato": escalation.contato,
|
|
127
|
+
"resumo": escalation.resumo_conversa,
|
|
128
|
+
}
|
|
129
|
+
requests.post(webhook_url, json=payload, timeout=5)
|
|
130
|
+
return True
|
|
131
|
+
except Exception as e:
|
|
132
|
+
print(f"[Escalation] Erro ao notificar: {e}")
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
# Modo simulação
|
|
136
|
+
print(f"[Escalation] Nova escalação: {escalation.protocolo}")
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _verificar_disponibilidade() -> Dict[str, Any]:
|
|
141
|
+
"""Verifica se atendimento humano está disponível."""
|
|
142
|
+
agora = datetime.now()
|
|
143
|
+
hora = agora.hour
|
|
144
|
+
dia = agora.weekday()
|
|
145
|
+
|
|
146
|
+
# Verificar variáveis de ambiente para horário customizado
|
|
147
|
+
hora_inicio = int(os.getenv("ESCALATION_HOUR_START", DEFAULT_BUSINESS_HOURS["start"]))
|
|
148
|
+
hora_fim = int(os.getenv("ESCALATION_HOUR_END", DEFAULT_BUSINESS_HOURS["end"]))
|
|
149
|
+
|
|
150
|
+
disponivel = dia in DEFAULT_BUSINESS_HOURS["days"] and hora_inicio <= hora < hora_fim
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
"disponivel": disponivel,
|
|
154
|
+
"hora_atual": agora.strftime("%H:%M"),
|
|
155
|
+
"dia_semana": ["Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado", "Domingo"][dia],
|
|
156
|
+
"horario_atendimento": f"{hora_inicio:02d}:00 - {hora_fim:02d}:00",
|
|
157
|
+
"dias_atendimento": "Segunda a Sexta",
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# =============================================================================
|
|
162
|
+
# Classificação Automática de Prioridade
|
|
163
|
+
# =============================================================================
|
|
164
|
+
|
|
165
|
+
def _classificar_prioridade(motivo: str, categoria: str) -> str:
|
|
166
|
+
"""
|
|
167
|
+
Classifica automaticamente a prioridade baseado no motivo e categoria.
|
|
168
|
+
"""
|
|
169
|
+
motivo_lower = motivo.lower()
|
|
170
|
+
|
|
171
|
+
# Palavras que indicam urgência
|
|
172
|
+
palavras_urgentes = [
|
|
173
|
+
"urgente", "emergência", "emergencia", "crítico", "critico",
|
|
174
|
+
"não funciona", "parou", "bloqueado", "cancelar", "prejuízo"
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
palavras_alta = [
|
|
178
|
+
"reclamação", "reclamacao", "insatisfeito", "problema grave",
|
|
179
|
+
"já tentei", "terceira vez", "não resolve"
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
# Verificar urgência
|
|
183
|
+
for palavra in palavras_urgentes:
|
|
184
|
+
if palavra in motivo_lower:
|
|
185
|
+
return "urgente"
|
|
186
|
+
|
|
187
|
+
# Verificar alta prioridade
|
|
188
|
+
for palavra in palavras_alta:
|
|
189
|
+
if palavra in motivo_lower:
|
|
190
|
+
return "alta"
|
|
191
|
+
|
|
192
|
+
# Frustração do usuário = alta
|
|
193
|
+
if categoria == "frustracao":
|
|
194
|
+
return "alta"
|
|
195
|
+
|
|
196
|
+
return "normal"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# =============================================================================
|
|
200
|
+
# Tools do Agente de Escalação
|
|
201
|
+
# =============================================================================
|
|
202
|
+
|
|
203
|
+
@function_tool
|
|
204
|
+
def verificar_horario_atendimento() -> str:
|
|
205
|
+
"""
|
|
206
|
+
Verifica se o atendimento humano está disponível no momento atual.
|
|
207
|
+
Use esta ferramenta ANTES de registrar a escalação para informar o usuário.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Status de disponibilidade do atendimento humano
|
|
211
|
+
"""
|
|
212
|
+
info = _verificar_disponibilidade()
|
|
213
|
+
|
|
214
|
+
if info["disponivel"]:
|
|
215
|
+
return f"""✅ **Atendimento Humano DISPONÍVEL**
|
|
216
|
+
|
|
217
|
+
📅 {info['dia_semana']}, {info['hora_atual']}
|
|
218
|
+
⏰ Horário de atendimento: {info['horario_atendimento']}
|
|
219
|
+
|
|
220
|
+
Um atendente poderá retornar em breve após o registro."""
|
|
221
|
+
else:
|
|
222
|
+
return f"""⚠️ **Atendimento Humano FORA DO HORÁRIO**
|
|
223
|
+
|
|
224
|
+
📅 {info['dia_semana']}, {info['hora_atual']}
|
|
225
|
+
⏰ Horário de atendimento: {info['horario_atendimento']}
|
|
226
|
+
📆 Dias: {info['dias_atendimento']}
|
|
227
|
+
|
|
228
|
+
Você pode deixar seus dados e retornaremos no próximo horário disponível."""
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@function_tool
|
|
232
|
+
def registrar_escalacao(
|
|
233
|
+
motivo: str,
|
|
234
|
+
nome_usuario: str,
|
|
235
|
+
contato: str,
|
|
236
|
+
tipo_contato: str = "telefone",
|
|
237
|
+
resumo_conversa: str = "",
|
|
238
|
+
categoria: str = "solicitacao",
|
|
239
|
+
) -> str:
|
|
240
|
+
"""
|
|
241
|
+
Registra uma escalação para atendimento humano e notifica a equipe.
|
|
242
|
+
|
|
243
|
+
IMPORTANTE: Use esta ferramenta quando:
|
|
244
|
+
- O usuário solicitar falar com um humano
|
|
245
|
+
- O tópico não for coberto pelo sistema
|
|
246
|
+
- Não conseguir resolver o problema do usuário
|
|
247
|
+
- O usuário demonstrar frustração
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
motivo: Motivo da escalação (descrição do que o usuário precisa)
|
|
251
|
+
nome_usuario: Nome do usuário para contato
|
|
252
|
+
contato: Telefone, email ou WhatsApp do usuário
|
|
253
|
+
tipo_contato: Tipo de contato preferido ("telefone", "email", "whatsapp")
|
|
254
|
+
resumo_conversa: Breve resumo do que foi discutido antes da escalação
|
|
255
|
+
categoria: Categoria da escalação:
|
|
256
|
+
- "solicitacao": Usuário pediu para falar com humano
|
|
257
|
+
- "frustracao": Usuário demonstrou frustração
|
|
258
|
+
- "topico_nao_coberto": Assunto fora do escopo
|
|
259
|
+
- "incerteza": Agente não conseguiu resolver
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Confirmação com protocolo e próximos passos
|
|
263
|
+
"""
|
|
264
|
+
# Validações
|
|
265
|
+
if not nome_usuario or len(nome_usuario.strip()) < 2:
|
|
266
|
+
return "❌ Por favor, informe seu nome completo para que possamos retornar."
|
|
267
|
+
|
|
268
|
+
if not contato or len(contato.strip()) < 5:
|
|
269
|
+
return "❌ Por favor, informe um contato válido (telefone, email ou WhatsApp)."
|
|
270
|
+
|
|
271
|
+
if not motivo or len(motivo.strip()) < 5:
|
|
272
|
+
return "❌ Por favor, descreva brevemente o motivo do contato."
|
|
273
|
+
|
|
274
|
+
# Normalizar tipo de contato
|
|
275
|
+
tipo_contato_norm = tipo_contato.lower().strip()
|
|
276
|
+
if tipo_contato_norm not in ["telefone", "email", "whatsapp"]:
|
|
277
|
+
tipo_contato_norm = "telefone"
|
|
278
|
+
|
|
279
|
+
# Normalizar categoria
|
|
280
|
+
categorias_validas = ["solicitacao", "frustracao", "topico_nao_coberto", "incerteza"]
|
|
281
|
+
categoria_norm = categoria.lower().strip()
|
|
282
|
+
if categoria_norm not in categorias_validas:
|
|
283
|
+
categoria_norm = "solicitacao"
|
|
284
|
+
|
|
285
|
+
# Classificar prioridade automaticamente
|
|
286
|
+
prioridade = _classificar_prioridade(motivo, categoria_norm)
|
|
287
|
+
|
|
288
|
+
# Criar escalação
|
|
289
|
+
escalation = Escalation(
|
|
290
|
+
protocolo=_gerar_protocolo_escalacao(),
|
|
291
|
+
motivo=motivo.strip(),
|
|
292
|
+
categoria=categoria_norm,
|
|
293
|
+
nome_usuario=nome_usuario.strip(),
|
|
294
|
+
contato=contato.strip(),
|
|
295
|
+
tipo_contato=tipo_contato_norm,
|
|
296
|
+
resumo_conversa=resumo_conversa.strip() if resumo_conversa else "",
|
|
297
|
+
prioridade=prioridade,
|
|
298
|
+
status="pendente",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# Salvar
|
|
302
|
+
_salvar_escalacao(escalation)
|
|
303
|
+
|
|
304
|
+
# Notificar equipe
|
|
305
|
+
notificado = _notificar_equipe(escalation)
|
|
306
|
+
|
|
307
|
+
# Verificar disponibilidade
|
|
308
|
+
disp = _verificar_disponibilidade()
|
|
309
|
+
|
|
310
|
+
# Ícones por prioridade
|
|
311
|
+
icone_prioridade = {
|
|
312
|
+
"baixa": "🟢",
|
|
313
|
+
"normal": "🟡",
|
|
314
|
+
"alta": "🟠",
|
|
315
|
+
"urgente": "🔴",
|
|
316
|
+
}.get(prioridade, "🟡")
|
|
317
|
+
|
|
318
|
+
# Ícones por tipo de contato
|
|
319
|
+
icone_contato = {
|
|
320
|
+
"telefone": "📞",
|
|
321
|
+
"email": "📧",
|
|
322
|
+
"whatsapp": "💬",
|
|
323
|
+
}.get(tipo_contato_norm, "📞")
|
|
324
|
+
|
|
325
|
+
resposta = f"""
|
|
326
|
+
✅ **Escalação Registrada com Sucesso!**
|
|
327
|
+
|
|
328
|
+
═══════════════════════════════════════
|
|
329
|
+
📋 **Protocolo:** {escalation.protocolo}
|
|
330
|
+
═══════════════════════════════════════
|
|
331
|
+
|
|
332
|
+
👤 **Nome:** {escalation.nome_usuario}
|
|
333
|
+
{icone_contato} **Contato ({tipo_contato_norm}):** {escalation.contato}
|
|
334
|
+
{icone_prioridade} **Prioridade:** {prioridade.upper()}
|
|
335
|
+
📅 **Data:** {escalation.data_criacao.strftime('%d/%m/%Y às %H:%M')}
|
|
336
|
+
|
|
337
|
+
📝 **Motivo:**
|
|
338
|
+
{escalation.motivo}
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
if escalation.resumo_conversa:
|
|
342
|
+
resposta += f"""
|
|
343
|
+
📄 **Resumo da conversa:**
|
|
344
|
+
{escalation.resumo_conversa[:200]}{'...' if len(escalation.resumo_conversa) > 200 else ''}
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
resposta += "\n═══════════════════════════════════════\n"
|
|
348
|
+
|
|
349
|
+
if disp["disponivel"]:
|
|
350
|
+
resposta += """
|
|
351
|
+
⏳ **Próximos Passos:**
|
|
352
|
+
Um atendente humano entrará em contato em breve.
|
|
353
|
+
"""
|
|
354
|
+
else:
|
|
355
|
+
resposta += f"""
|
|
356
|
+
⏳ **Próximos Passos:**
|
|
357
|
+
Estamos fora do horário de atendimento ({disp['horario_atendimento']}).
|
|
358
|
+
Um atendente retornará no próximo dia útil.
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
resposta += f"""
|
|
362
|
+
💡 **Dica:** Guarde o protocolo **{escalation.protocolo}** para acompanhamento.
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
if notificado:
|
|
366
|
+
resposta += "\n✅ Nossa equipe foi notificada."
|
|
367
|
+
|
|
368
|
+
return resposta
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@function_tool
|
|
372
|
+
def consultar_escalacao(protocolo: str) -> str:
|
|
373
|
+
"""
|
|
374
|
+
Consulta o status de uma escalação pelo protocolo.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
protocolo: Número do protocolo (ex: ESC-20240105-ABC123)
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Status e detalhes da escalação
|
|
381
|
+
"""
|
|
382
|
+
escalation = _buscar_escalacao(protocolo)
|
|
383
|
+
|
|
384
|
+
if not escalation:
|
|
385
|
+
return f"""❌ **Escalação não encontrada:** {protocolo}
|
|
386
|
+
|
|
387
|
+
Verifique se o número do protocolo está correto.
|
|
388
|
+
Formato esperado: ESC-YYYYMMDD-XXXXXX"""
|
|
389
|
+
|
|
390
|
+
# Ícones de status
|
|
391
|
+
icone_status = {
|
|
392
|
+
"pendente": "🟡",
|
|
393
|
+
"em_atendimento": "🔵",
|
|
394
|
+
"concluido": "🟢",
|
|
395
|
+
"cancelado": "⚫",
|
|
396
|
+
}.get(escalation.status, "⚪")
|
|
397
|
+
|
|
398
|
+
resposta = f"""
|
|
399
|
+
📋 **Consulta de Escalação**
|
|
400
|
+
|
|
401
|
+
═══════════════════════════════════════
|
|
402
|
+
🔖 **Protocolo:** {escalation.protocolo}
|
|
403
|
+
{icone_status} **Status:** {escalation.status.upper().replace('_', ' ')}
|
|
404
|
+
═══════════════════════════════════════
|
|
405
|
+
|
|
406
|
+
👤 **Solicitante:** {escalation.nome_usuario}
|
|
407
|
+
📞 **Contato:** {escalation.contato} ({escalation.tipo_contato})
|
|
408
|
+
⚡ **Prioridade:** {escalation.prioridade.upper()}
|
|
409
|
+
|
|
410
|
+
📅 **Criado em:** {escalation.data_criacao.strftime('%d/%m/%Y às %H:%M')}
|
|
411
|
+
📅 **Atualizado em:** {escalation.data_atualizacao.strftime('%d/%m/%Y às %H:%M')}
|
|
412
|
+
|
|
413
|
+
📝 **Motivo:**
|
|
414
|
+
{escalation.motivo}
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
if escalation.atendente:
|
|
418
|
+
resposta += f"\n👨💼 **Atendente:** {escalation.atendente}"
|
|
419
|
+
|
|
420
|
+
if escalation.observacoes:
|
|
421
|
+
resposta += f"\n\n📌 **Observações:**\n{escalation.observacoes}"
|
|
422
|
+
|
|
423
|
+
return resposta
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
# =============================================================================
|
|
427
|
+
# Lista de Tools
|
|
428
|
+
# =============================================================================
|
|
429
|
+
|
|
430
|
+
ESCALATION_TOOLS = [
|
|
431
|
+
verificar_horario_atendimento,
|
|
432
|
+
registrar_escalacao,
|
|
433
|
+
consultar_escalacao,
|
|
434
|
+
]
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# =============================================================================
|
|
438
|
+
# Triggers e Instruções movidos para atendentepro/prompts/escalation.py
|
|
439
|
+
# =============================================================================
|
|
440
|
+
|
|
441
|
+
# Re-exportar para compatibilidade
|
|
442
|
+
# DEFAULT_ESCALATION_TRIGGERS e ESCALATION_INTRO estão em prompts/escalation.py
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# =============================================================================
|
|
446
|
+
# Criar Escalation Agent
|
|
447
|
+
# =============================================================================
|
|
448
|
+
|
|
449
|
+
def create_escalation_agent(
|
|
450
|
+
escalation_triggers: str = "",
|
|
451
|
+
escalation_channels: str = "",
|
|
452
|
+
handoffs: Optional[List] = None,
|
|
453
|
+
tools: Optional[List] = None,
|
|
454
|
+
guardrails: Optional[List["GuardrailCallable"]] = None,
|
|
455
|
+
name: str = "Escalation Agent",
|
|
456
|
+
custom_instructions: Optional[str] = None,
|
|
457
|
+
) -> EscalationAgent:
|
|
458
|
+
"""
|
|
459
|
+
Create an Escalation Agent instance.
|
|
460
|
+
|
|
461
|
+
The escalation agent handles transfers to human support when:
|
|
462
|
+
- User explicitly requests to talk to a human
|
|
463
|
+
- Topic is not covered by the automated system
|
|
464
|
+
- Agent cannot resolve the issue after attempts
|
|
465
|
+
- User shows frustration or confusion
|
|
466
|
+
|
|
467
|
+
This agent should be added as a handoff option to ALL other agents
|
|
468
|
+
in the network to ensure users can always escalate when needed.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
escalation_triggers: Custom triggers for escalation (keywords, situations).
|
|
472
|
+
escalation_channels: Available contact channels description.
|
|
473
|
+
handoffs: List of agents to hand off to (usually triage to return).
|
|
474
|
+
tools: Additional tools (custom notifications, integrations).
|
|
475
|
+
guardrails: List of input guardrails.
|
|
476
|
+
name: Agent name.
|
|
477
|
+
custom_instructions: Optional custom instructions to override default.
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
Configured Escalation Agent instance.
|
|
481
|
+
|
|
482
|
+
Example:
|
|
483
|
+
>>> escalation = create_escalation_agent(
|
|
484
|
+
... escalation_channels="Telefone: 0800-123-4567 (Seg-Sex 8h-18h)",
|
|
485
|
+
... handoffs=[triage],
|
|
486
|
+
... )
|
|
487
|
+
>>> # Add to all agents
|
|
488
|
+
>>> triage.handoffs.append(escalation)
|
|
489
|
+
>>> flow.handoffs.append(escalation)
|
|
490
|
+
"""
|
|
491
|
+
if custom_instructions:
|
|
492
|
+
instructions = f"{RECOMMENDED_PROMPT_PREFIX} {custom_instructions}"
|
|
493
|
+
else:
|
|
494
|
+
# Usar o prompt builder do módulo prompts
|
|
495
|
+
instructions = f"{RECOMMENDED_PROMPT_PREFIX}\n{get_escalation_prompt(escalation_triggers=escalation_triggers, escalation_channels=escalation_channels)}"
|
|
496
|
+
|
|
497
|
+
# Combinar tools padrão com customizadas
|
|
498
|
+
agent_tools = list(ESCALATION_TOOLS)
|
|
499
|
+
if tools:
|
|
500
|
+
agent_tools.extend(tools)
|
|
501
|
+
|
|
502
|
+
return Agent[ContextNote](
|
|
503
|
+
name=name,
|
|
504
|
+
handoff_description="Transfere para atendimento humano quando o agente não consegue resolver, o tópico não é coberto, ou o usuário solicita.",
|
|
505
|
+
instructions=instructions,
|
|
506
|
+
tools=agent_tools,
|
|
507
|
+
handoffs=handoffs or [],
|
|
508
|
+
input_guardrails=guardrails or [],
|
|
509
|
+
)
|
|
510
|
+
|