atendentepro 0.3.0__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 +890 -0
- atendentepro/__init__.py +215 -0
- atendentepro/agents/__init__.py +45 -0
- atendentepro/agents/answer.py +62 -0
- atendentepro/agents/confirmation.py +69 -0
- atendentepro/agents/flow.py +64 -0
- atendentepro/agents/interview.py +68 -0
- atendentepro/agents/knowledge.py +296 -0
- atendentepro/agents/onboarding.py +65 -0
- atendentepro/agents/triage.py +57 -0
- atendentepro/agents/usage.py +56 -0
- atendentepro/config/__init__.py +19 -0
- atendentepro/config/settings.py +134 -0
- atendentepro/guardrails/__init__.py +21 -0
- atendentepro/guardrails/manager.py +419 -0
- atendentepro/license.py +502 -0
- atendentepro/models/__init__.py +21 -0
- atendentepro/models/context.py +21 -0
- atendentepro/models/outputs.py +118 -0
- atendentepro/network.py +325 -0
- atendentepro/prompts/__init__.py +35 -0
- atendentepro/prompts/answer.py +114 -0
- atendentepro/prompts/confirmation.py +124 -0
- atendentepro/prompts/flow.py +112 -0
- atendentepro/prompts/interview.py +123 -0
- atendentepro/prompts/knowledge.py +135 -0
- atendentepro/prompts/onboarding.py +146 -0
- atendentepro/prompts/triage.py +42 -0
- atendentepro/templates/__init__.py +51 -0
- atendentepro/templates/manager.py +530 -0
- atendentepro/utils/__init__.py +19 -0
- atendentepro/utils/openai_client.py +154 -0
- atendentepro/utils/tracing.py +71 -0
- atendentepro-0.3.0.dist-info/METADATA +306 -0
- atendentepro-0.3.0.dist-info/RECORD +39 -0
- atendentepro-0.3.0.dist-info/WHEEL +5 -0
- atendentepro-0.3.0.dist-info/entry_points.txt +2 -0
- atendentepro-0.3.0.dist-info/licenses/LICENSE +25 -0
- atendentepro-0.3.0.dist-info/top_level.txt +1 -0
atendentepro/license.py
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
AtendentePro - Sistema de Licenciamento
|
|
4
|
+
|
|
5
|
+
Este módulo gerencia a validação de tokens de acesso para uso da biblioteca.
|
|
6
|
+
|
|
7
|
+
Uso:
|
|
8
|
+
from atendentepro import activate
|
|
9
|
+
|
|
10
|
+
# Ativar com token
|
|
11
|
+
activate("seu-token-de-acesso")
|
|
12
|
+
|
|
13
|
+
# Agora pode usar a biblioteca normalmente
|
|
14
|
+
from atendentepro import create_standard_network
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import hashlib
|
|
19
|
+
import hmac
|
|
20
|
+
import time
|
|
21
|
+
import json
|
|
22
|
+
from typing import Optional
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
# Chave secreta para validação local (pode ser alterada em produção)
|
|
27
|
+
_SECRET_KEY = "atendentepro-bemonkai-2024"
|
|
28
|
+
|
|
29
|
+
# Estado global de ativação
|
|
30
|
+
_license_state = {
|
|
31
|
+
"activated": False,
|
|
32
|
+
"token": None,
|
|
33
|
+
"expiration": None,
|
|
34
|
+
"features": [],
|
|
35
|
+
"organization": None,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class LicenseInfo:
|
|
41
|
+
"""Informações da licença ativa."""
|
|
42
|
+
valid: bool
|
|
43
|
+
organization: Optional[str] = None
|
|
44
|
+
expiration: Optional[str] = None
|
|
45
|
+
features: list = None
|
|
46
|
+
message: str = ""
|
|
47
|
+
|
|
48
|
+
def __post_init__(self):
|
|
49
|
+
if self.features is None:
|
|
50
|
+
self.features = []
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class LicenseError(Exception):
|
|
54
|
+
"""Erro de licenciamento."""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class LicenseNotActivatedError(LicenseError):
|
|
59
|
+
"""Biblioteca não foi ativada com um token válido."""
|
|
60
|
+
|
|
61
|
+
def __init__(self):
|
|
62
|
+
super().__init__(
|
|
63
|
+
"\n\n"
|
|
64
|
+
"╔══════════════════════════════════════════════════════════════╗\n"
|
|
65
|
+
"║ ATENDENTEPRO - LICENÇA NÃO ATIVADA ║\n"
|
|
66
|
+
"╠══════════════════════════════════════════════════════════════╣\n"
|
|
67
|
+
"║ ║\n"
|
|
68
|
+
"║ A biblioteca AtendentePro requer ativação para uso. ║\n"
|
|
69
|
+
"║ ║\n"
|
|
70
|
+
"║ Para ativar, use: ║\n"
|
|
71
|
+
"║ ║\n"
|
|
72
|
+
"║ from atendentepro import activate ║\n"
|
|
73
|
+
"║ activate('seu-token-de-acesso') ║\n"
|
|
74
|
+
"║ ║\n"
|
|
75
|
+
"║ Ou defina a variável de ambiente: ║\n"
|
|
76
|
+
"║ ║\n"
|
|
77
|
+
"║ export ATENDENTEPRO_LICENSE_KEY='seu-token' ║\n"
|
|
78
|
+
"║ ║\n"
|
|
79
|
+
"║ Para obter um token, entre em contato: ║\n"
|
|
80
|
+
"║ 📧 contato@bemonkai.com ║\n"
|
|
81
|
+
"║ ║\n"
|
|
82
|
+
"╚══════════════════════════════════════════════════════════════╝\n"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class LicenseExpiredError(LicenseError):
|
|
87
|
+
"""Token de licença expirado."""
|
|
88
|
+
|
|
89
|
+
def __init__(self, expiration: str):
|
|
90
|
+
super().__init__(
|
|
91
|
+
f"\n\n"
|
|
92
|
+
f"╔══════════════════════════════════════════════════════════════╗\n"
|
|
93
|
+
f"║ ATENDENTEPRO - LICENÇA EXPIRADA ║\n"
|
|
94
|
+
f"╠══════════════════════════════════════════════════════════════╣\n"
|
|
95
|
+
f"║ ║\n"
|
|
96
|
+
f"║ Sua licença expirou em: {expiration:<36}║\n"
|
|
97
|
+
f"║ ║\n"
|
|
98
|
+
f"║ Para renovar, entre em contato: ║\n"
|
|
99
|
+
f"║ 📧 contato@bemonkai.com ║\n"
|
|
100
|
+
f"║ ║\n"
|
|
101
|
+
f"╚══════════════════════════════════════════════════════════════╝\n"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class InvalidTokenError(LicenseError):
|
|
106
|
+
"""Token inválido."""
|
|
107
|
+
|
|
108
|
+
def __init__(self):
|
|
109
|
+
super().__init__(
|
|
110
|
+
"\n\n"
|
|
111
|
+
"╔══════════════════════════════════════════════════════════════╗\n"
|
|
112
|
+
"║ ATENDENTEPRO - TOKEN INVÁLIDO ║\n"
|
|
113
|
+
"╠══════════════════════════════════════════════════════════════╣\n"
|
|
114
|
+
"║ ║\n"
|
|
115
|
+
"║ O token fornecido não é válido. ║\n"
|
|
116
|
+
"║ ║\n"
|
|
117
|
+
"║ Verifique se o token está correto ou entre em contato: ║\n"
|
|
118
|
+
"║ 📧 contato@bemonkai.com ║\n"
|
|
119
|
+
"║ ║\n"
|
|
120
|
+
"╚══════════════════════════════════════════════════════════════╝\n"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _generate_token(
|
|
125
|
+
organization: str,
|
|
126
|
+
expiration_timestamp: int = None,
|
|
127
|
+
features: list = None,
|
|
128
|
+
secret_key: str = None
|
|
129
|
+
) -> str:
|
|
130
|
+
"""
|
|
131
|
+
Gera um token de licença.
|
|
132
|
+
|
|
133
|
+
USO INTERNO - Para gerar tokens para clientes.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
organization: Nome da organização
|
|
137
|
+
expiration_timestamp: Unix timestamp de expiração (None = sem expiração)
|
|
138
|
+
features: Lista de features habilitadas
|
|
139
|
+
secret_key: Chave secreta para assinatura
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Token codificado em base64
|
|
143
|
+
"""
|
|
144
|
+
import base64
|
|
145
|
+
|
|
146
|
+
if secret_key is None:
|
|
147
|
+
secret_key = _SECRET_KEY
|
|
148
|
+
|
|
149
|
+
if features is None:
|
|
150
|
+
features = ["full"]
|
|
151
|
+
|
|
152
|
+
# Payload do token
|
|
153
|
+
payload = {
|
|
154
|
+
"org": organization,
|
|
155
|
+
"exp": expiration_timestamp,
|
|
156
|
+
"feat": features,
|
|
157
|
+
"v": 1, # versão do token
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Serializar payload
|
|
161
|
+
payload_json = json.dumps(payload, separators=(",", ":"))
|
|
162
|
+
payload_b64 = base64.urlsafe_b64encode(payload_json.encode()).decode()
|
|
163
|
+
|
|
164
|
+
# Gerar assinatura
|
|
165
|
+
signature = hmac.new(
|
|
166
|
+
secret_key.encode(),
|
|
167
|
+
payload_b64.encode(),
|
|
168
|
+
hashlib.sha256
|
|
169
|
+
).hexdigest()[:16]
|
|
170
|
+
|
|
171
|
+
# Token final: payload.signature
|
|
172
|
+
token = f"ATP_{payload_b64}.{signature}"
|
|
173
|
+
|
|
174
|
+
return token
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _validate_token_local(token: str, secret_key: str = None) -> LicenseInfo:
|
|
178
|
+
"""
|
|
179
|
+
Valida um token localmente.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
token: Token de licença
|
|
183
|
+
secret_key: Chave secreta para validação
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
LicenseInfo com informações da validação
|
|
187
|
+
"""
|
|
188
|
+
import base64
|
|
189
|
+
|
|
190
|
+
if secret_key is None:
|
|
191
|
+
secret_key = _SECRET_KEY
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
# Verificar formato
|
|
195
|
+
if not token.startswith("ATP_"):
|
|
196
|
+
return LicenseInfo(valid=False, message="Formato de token inválido")
|
|
197
|
+
|
|
198
|
+
token_body = token[4:] # Remover "ATP_"
|
|
199
|
+
|
|
200
|
+
if "." not in token_body:
|
|
201
|
+
return LicenseInfo(valid=False, message="Token malformado")
|
|
202
|
+
|
|
203
|
+
payload_b64, signature = token_body.rsplit(".", 1)
|
|
204
|
+
|
|
205
|
+
# Verificar assinatura
|
|
206
|
+
expected_signature = hmac.new(
|
|
207
|
+
secret_key.encode(),
|
|
208
|
+
payload_b64.encode(),
|
|
209
|
+
hashlib.sha256
|
|
210
|
+
).hexdigest()[:16]
|
|
211
|
+
|
|
212
|
+
if not hmac.compare_digest(signature, expected_signature):
|
|
213
|
+
return LicenseInfo(valid=False, message="Assinatura inválida")
|
|
214
|
+
|
|
215
|
+
# Decodificar payload
|
|
216
|
+
try:
|
|
217
|
+
payload_json = base64.urlsafe_b64decode(payload_b64).decode()
|
|
218
|
+
payload = json.loads(payload_json)
|
|
219
|
+
except Exception:
|
|
220
|
+
return LicenseInfo(valid=False, message="Payload inválido")
|
|
221
|
+
|
|
222
|
+
# Verificar expiração
|
|
223
|
+
expiration = payload.get("exp")
|
|
224
|
+
expiration_str = None
|
|
225
|
+
|
|
226
|
+
if expiration is not None:
|
|
227
|
+
expiration_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(expiration))
|
|
228
|
+
if time.time() > expiration:
|
|
229
|
+
return LicenseInfo(
|
|
230
|
+
valid=False,
|
|
231
|
+
expiration=expiration_str,
|
|
232
|
+
message="Token expirado"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Token válido
|
|
236
|
+
return LicenseInfo(
|
|
237
|
+
valid=True,
|
|
238
|
+
organization=payload.get("org", "Unknown"),
|
|
239
|
+
expiration=expiration_str,
|
|
240
|
+
features=payload.get("feat", ["full"]),
|
|
241
|
+
message="Token válido"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
except Exception as e:
|
|
245
|
+
return LicenseInfo(valid=False, message=f"Erro na validação: {str(e)}")
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _validate_token_online(token: str, api_url: str = None) -> LicenseInfo:
|
|
249
|
+
"""
|
|
250
|
+
Valida um token online através de API.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
token: Token de licença
|
|
254
|
+
api_url: URL da API de validação
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
LicenseInfo com informações da validação
|
|
258
|
+
"""
|
|
259
|
+
# Implementação futura para validação online
|
|
260
|
+
# Por enquanto, faz validação local
|
|
261
|
+
return _validate_token_local(token)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def activate(
|
|
265
|
+
token: str = None,
|
|
266
|
+
validate_online: bool = False,
|
|
267
|
+
silent: bool = False
|
|
268
|
+
) -> LicenseInfo:
|
|
269
|
+
"""
|
|
270
|
+
Ativa a biblioteca AtendentePro com um token de licença.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
token: Token de licença. Se não fornecido, tenta usar
|
|
274
|
+
a variável de ambiente ATENDENTEPRO_LICENSE_KEY
|
|
275
|
+
validate_online: Se True, valida o token online (requer internet)
|
|
276
|
+
silent: Se True, não imprime mensagens de sucesso
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
LicenseInfo com informações da licença
|
|
280
|
+
|
|
281
|
+
Raises:
|
|
282
|
+
InvalidTokenError: Se o token for inválido
|
|
283
|
+
LicenseExpiredError: Se o token estiver expirado
|
|
284
|
+
|
|
285
|
+
Exemplo:
|
|
286
|
+
>>> from atendentepro import activate
|
|
287
|
+
>>> activate("ATP_eyJvcmciOiJNaW5oYUVtcHJlc2EiLCJleHAiOm51bGwsImZlYXQiOlsiZnVsbCJdLCJ2IjoxfQ.abc123")
|
|
288
|
+
✅ AtendentePro ativado para: MinhaEmpresa
|
|
289
|
+
"""
|
|
290
|
+
global _license_state
|
|
291
|
+
|
|
292
|
+
# Tentar obter token de variável de ambiente
|
|
293
|
+
if token is None:
|
|
294
|
+
token = os.environ.get("ATENDENTEPRO_LICENSE_KEY")
|
|
295
|
+
|
|
296
|
+
if token is None:
|
|
297
|
+
raise LicenseNotActivatedError()
|
|
298
|
+
|
|
299
|
+
# Validar token
|
|
300
|
+
if validate_online:
|
|
301
|
+
license_info = _validate_token_online(token)
|
|
302
|
+
else:
|
|
303
|
+
license_info = _validate_token_local(token)
|
|
304
|
+
|
|
305
|
+
if not license_info.valid:
|
|
306
|
+
if "expirado" in license_info.message.lower():
|
|
307
|
+
raise LicenseExpiredError(license_info.expiration or "Data desconhecida")
|
|
308
|
+
raise InvalidTokenError()
|
|
309
|
+
|
|
310
|
+
# Atualizar estado global
|
|
311
|
+
_license_state["activated"] = True
|
|
312
|
+
_license_state["token"] = token
|
|
313
|
+
_license_state["expiration"] = license_info.expiration
|
|
314
|
+
_license_state["features"] = license_info.features
|
|
315
|
+
_license_state["organization"] = license_info.organization
|
|
316
|
+
|
|
317
|
+
if not silent:
|
|
318
|
+
exp_msg = f" (expira: {license_info.expiration})" if license_info.expiration else " (sem expiração)"
|
|
319
|
+
print(f"✅ AtendentePro ativado para: {license_info.organization}{exp_msg}")
|
|
320
|
+
|
|
321
|
+
return license_info
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def deactivate():
|
|
325
|
+
"""Desativa a biblioteca (útil para testes)."""
|
|
326
|
+
global _license_state
|
|
327
|
+
_license_state = {
|
|
328
|
+
"activated": False,
|
|
329
|
+
"token": None,
|
|
330
|
+
"expiration": None,
|
|
331
|
+
"features": [],
|
|
332
|
+
"organization": None,
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def is_activated() -> bool:
|
|
337
|
+
"""Verifica se a biblioteca está ativada."""
|
|
338
|
+
return _license_state["activated"]
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def get_license_info() -> LicenseInfo:
|
|
342
|
+
"""Retorna informações da licença atual."""
|
|
343
|
+
if not _license_state["activated"]:
|
|
344
|
+
return LicenseInfo(valid=False, message="Não ativado")
|
|
345
|
+
|
|
346
|
+
return LicenseInfo(
|
|
347
|
+
valid=True,
|
|
348
|
+
organization=_license_state["organization"],
|
|
349
|
+
expiration=_license_state["expiration"],
|
|
350
|
+
features=_license_state["features"],
|
|
351
|
+
message="Ativo"
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def require_activation():
|
|
356
|
+
"""
|
|
357
|
+
Decorator/função para verificar se a biblioteca está ativada.
|
|
358
|
+
|
|
359
|
+
Raises:
|
|
360
|
+
LicenseNotActivatedError: Se não estiver ativada
|
|
361
|
+
"""
|
|
362
|
+
# Primeiro, tentar ativar automaticamente via variável de ambiente
|
|
363
|
+
if not _license_state["activated"]:
|
|
364
|
+
env_token = os.environ.get("ATENDENTEPRO_LICENSE_KEY")
|
|
365
|
+
if env_token:
|
|
366
|
+
try:
|
|
367
|
+
activate(env_token, silent=True)
|
|
368
|
+
except LicenseError:
|
|
369
|
+
pass
|
|
370
|
+
|
|
371
|
+
if not _license_state["activated"]:
|
|
372
|
+
raise LicenseNotActivatedError()
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def has_feature(feature: str) -> bool:
|
|
376
|
+
"""Verifica se uma feature específica está habilitada."""
|
|
377
|
+
if not _license_state["activated"]:
|
|
378
|
+
return False
|
|
379
|
+
|
|
380
|
+
features = _license_state.get("features", [])
|
|
381
|
+
return "full" in features or feature in features
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
# ============================================================================
|
|
385
|
+
# UTILITÁRIO PARA GERAR TOKENS (USO ADMINISTRATIVO)
|
|
386
|
+
# ============================================================================
|
|
387
|
+
|
|
388
|
+
def generate_license_token(
|
|
389
|
+
organization: str,
|
|
390
|
+
days_valid: int = None,
|
|
391
|
+
features: list = None
|
|
392
|
+
) -> str:
|
|
393
|
+
"""
|
|
394
|
+
Gera um token de licença para uma organização.
|
|
395
|
+
|
|
396
|
+
USO ADMINISTRATIVO - Para gerar tokens para clientes.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
organization: Nome da organização/cliente
|
|
400
|
+
days_valid: Dias de validade (None = sem expiração)
|
|
401
|
+
features: Lista de features ["full", "basic", "knowledge", etc]
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
Token de licença
|
|
405
|
+
|
|
406
|
+
Exemplo:
|
|
407
|
+
>>> generate_license_token("MinhaEmpresa", days_valid=365)
|
|
408
|
+
'ATP_eyJvcmciOiJNaW5oYUVtcHJlc2EiLCJleHAiOjE3MzU2ODkw...'
|
|
409
|
+
"""
|
|
410
|
+
expiration = None
|
|
411
|
+
if days_valid is not None:
|
|
412
|
+
expiration = int(time.time()) + (days_valid * 24 * 60 * 60)
|
|
413
|
+
|
|
414
|
+
return _generate_token(
|
|
415
|
+
organization=organization,
|
|
416
|
+
expiration_timestamp=expiration,
|
|
417
|
+
features=features or ["full"]
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
# ============================================================================
|
|
422
|
+
# AUTO-ATIVAÇÃO VIA VARIÁVEL DE AMBIENTE
|
|
423
|
+
# ============================================================================
|
|
424
|
+
|
|
425
|
+
def _try_auto_activate():
|
|
426
|
+
"""Tenta ativar automaticamente via variável de ambiente."""
|
|
427
|
+
env_token = os.environ.get("ATENDENTEPRO_LICENSE_KEY")
|
|
428
|
+
if env_token and not _license_state["activated"]:
|
|
429
|
+
try:
|
|
430
|
+
activate(env_token, silent=True)
|
|
431
|
+
except LicenseError:
|
|
432
|
+
pass # Silenciosamente ignora erros de auto-ativação
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
# Tentar auto-ativar ao importar o módulo
|
|
436
|
+
_try_auto_activate()
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# ============================================================================
|
|
440
|
+
# CLI PARA GERAR TOKENS
|
|
441
|
+
# ============================================================================
|
|
442
|
+
|
|
443
|
+
def _cli_generate_token():
|
|
444
|
+
"""
|
|
445
|
+
Ponto de entrada CLI para gerar tokens.
|
|
446
|
+
|
|
447
|
+
Uso:
|
|
448
|
+
atendentepro-generate-token "MinhaEmpresa" --days 365
|
|
449
|
+
"""
|
|
450
|
+
import argparse
|
|
451
|
+
|
|
452
|
+
parser = argparse.ArgumentParser(
|
|
453
|
+
prog="atendentepro-generate-token",
|
|
454
|
+
description="Gera tokens de licença para o AtendentePro"
|
|
455
|
+
)
|
|
456
|
+
parser.add_argument(
|
|
457
|
+
"organization",
|
|
458
|
+
help="Nome da organização/cliente"
|
|
459
|
+
)
|
|
460
|
+
parser.add_argument(
|
|
461
|
+
"--days",
|
|
462
|
+
type=int,
|
|
463
|
+
default=None,
|
|
464
|
+
help="Dias de validade (padrão: sem expiração)"
|
|
465
|
+
)
|
|
466
|
+
parser.add_argument(
|
|
467
|
+
"--features",
|
|
468
|
+
default="full",
|
|
469
|
+
help="Features habilitadas, separadas por vírgula (padrão: full)"
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
args = parser.parse_args()
|
|
473
|
+
|
|
474
|
+
# Processar features
|
|
475
|
+
features = [f.strip() for f in args.features.split(",")]
|
|
476
|
+
|
|
477
|
+
# Gerar token
|
|
478
|
+
token = generate_license_token(
|
|
479
|
+
organization=args.organization,
|
|
480
|
+
days_valid=args.days,
|
|
481
|
+
features=features
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
# Validar o token gerado
|
|
485
|
+
info = _validate_token_local(token)
|
|
486
|
+
|
|
487
|
+
print("\n" + "=" * 70)
|
|
488
|
+
print("TOKEN DE LICENÇA ATENDENTEPRO")
|
|
489
|
+
print("=" * 70)
|
|
490
|
+
print(f"\n📋 Organização: {info.organization}")
|
|
491
|
+
print(f"⏰ Expiração: {info.expiration or 'Sem expiração'}")
|
|
492
|
+
print(f"🎯 Features: {', '.join(info.features)}")
|
|
493
|
+
print(f"\n🔑 Token:\n")
|
|
494
|
+
print(token)
|
|
495
|
+
print("\n" + "=" * 70)
|
|
496
|
+
print("\n📌 Para usar, adicione ao ambiente ou código:")
|
|
497
|
+
print(f'\nexport ATENDENTEPRO_LICENSE_KEY="{token}"')
|
|
498
|
+
print("\nou no código Python:")
|
|
499
|
+
print(f'\nfrom atendentepro import activate')
|
|
500
|
+
print(f'activate("{token}")')
|
|
501
|
+
print("\n" + "=" * 70 + "\n")
|
|
502
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Models module for AtendentePro library."""
|
|
3
|
+
|
|
4
|
+
from .context import ContextNote
|
|
5
|
+
from .outputs import (
|
|
6
|
+
FlowTopic,
|
|
7
|
+
FlowOutput,
|
|
8
|
+
InterviewOutput,
|
|
9
|
+
KnowledgeToolResult,
|
|
10
|
+
GuardrailValidationOutput,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ContextNote",
|
|
15
|
+
"FlowTopic",
|
|
16
|
+
"FlowOutput",
|
|
17
|
+
"InterviewOutput",
|
|
18
|
+
"KnowledgeToolResult",
|
|
19
|
+
"GuardrailValidationOutput",
|
|
20
|
+
]
|
|
21
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Context models for AtendentePro agents."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ContextNote(BaseModel):
|
|
10
|
+
"""
|
|
11
|
+
Shared context model used across all agents.
|
|
12
|
+
|
|
13
|
+
Contains structured summaries generated by previous agents
|
|
14
|
+
to guide subsequent handoffs.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
handoff_summaries: dict[str, dict] = Field(
|
|
18
|
+
default_factory=dict,
|
|
19
|
+
description="Resumos estruturados gerados por agentes anteriores para orientar próximos handoffs.",
|
|
20
|
+
)
|
|
21
|
+
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Output models for AtendentePro agents."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FlowTopic(str, Enum):
|
|
13
|
+
"""Enumeration of available flow topics."""
|
|
14
|
+
|
|
15
|
+
GENERIC = "generic"
|
|
16
|
+
CUSTOM = "custom"
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def from_label(cls, label: str) -> "FlowTopic":
|
|
20
|
+
"""Create a topic from a label string."""
|
|
21
|
+
normalized = label.lower().replace(" ", "_").replace("-", "_")
|
|
22
|
+
for topic in cls:
|
|
23
|
+
if topic.value == normalized:
|
|
24
|
+
return topic
|
|
25
|
+
return cls.CUSTOM
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class FlowOutput(BaseModel):
|
|
29
|
+
"""Output model for the Flow Agent."""
|
|
30
|
+
|
|
31
|
+
topic: FlowTopic = Field(
|
|
32
|
+
description="O tópico identificado para o fluxo de atendimento."
|
|
33
|
+
)
|
|
34
|
+
confidence: float = Field(
|
|
35
|
+
default=0.0,
|
|
36
|
+
ge=0.0,
|
|
37
|
+
le=1.0,
|
|
38
|
+
description="Nível de confiança na classificação do tópico (0-1)."
|
|
39
|
+
)
|
|
40
|
+
reasoning: str = Field(
|
|
41
|
+
default="",
|
|
42
|
+
description="Explicação do raciocínio para a classificação."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class InterviewOutput(BaseModel):
|
|
47
|
+
"""Output model for the Interview Agent."""
|
|
48
|
+
|
|
49
|
+
topic: str = Field(
|
|
50
|
+
description="O tópico sendo entrevistado."
|
|
51
|
+
)
|
|
52
|
+
questions_asked: List[str] = Field(
|
|
53
|
+
default_factory=list,
|
|
54
|
+
description="Lista de perguntas já realizadas."
|
|
55
|
+
)
|
|
56
|
+
answers_collected: dict = Field(
|
|
57
|
+
default_factory=dict,
|
|
58
|
+
description="Respostas coletadas do usuário."
|
|
59
|
+
)
|
|
60
|
+
is_complete: bool = Field(
|
|
61
|
+
default=False,
|
|
62
|
+
description="Se a entrevista foi completada."
|
|
63
|
+
)
|
|
64
|
+
missing_info: List[str] = Field(
|
|
65
|
+
default_factory=list,
|
|
66
|
+
description="Informações ainda faltando."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class KnowledgeToolResult(BaseModel):
|
|
71
|
+
"""Output model for Knowledge Agent RAG tool."""
|
|
72
|
+
|
|
73
|
+
answer: str = Field(
|
|
74
|
+
description="Resposta sintetizada usando o contexto recuperado."
|
|
75
|
+
)
|
|
76
|
+
context: str = Field(
|
|
77
|
+
description="Trechos dos documentos utilizados para resposta."
|
|
78
|
+
)
|
|
79
|
+
sources: List[str] = Field(
|
|
80
|
+
default_factory=list,
|
|
81
|
+
description="Documentos consultados."
|
|
82
|
+
)
|
|
83
|
+
confidence: float = Field(
|
|
84
|
+
default=0.0,
|
|
85
|
+
description="Confiança média estimada a partir da similaridade dos trechos."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class GuardrailValidationOutput(BaseModel):
|
|
90
|
+
"""Output model for guardrail validation."""
|
|
91
|
+
|
|
92
|
+
is_in_scope: bool = Field(
|
|
93
|
+
description="Se a mensagem está dentro do escopo do agente."
|
|
94
|
+
)
|
|
95
|
+
reasoning: str = Field(
|
|
96
|
+
description="Explicação do raciocínio para a decisão."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class AnswerOutput(BaseModel):
|
|
101
|
+
"""Output model for the Answer Agent."""
|
|
102
|
+
|
|
103
|
+
response: str = Field(
|
|
104
|
+
description="Resposta final sintetizada para o usuário."
|
|
105
|
+
)
|
|
106
|
+
sources_used: List[str] = Field(
|
|
107
|
+
default_factory=list,
|
|
108
|
+
description="Fontes utilizadas para compor a resposta."
|
|
109
|
+
)
|
|
110
|
+
follow_up_needed: bool = Field(
|
|
111
|
+
default=False,
|
|
112
|
+
description="Se é necessário acompanhamento adicional."
|
|
113
|
+
)
|
|
114
|
+
follow_up_reason: Optional[str] = Field(
|
|
115
|
+
default=None,
|
|
116
|
+
description="Razão para o acompanhamento, se necessário."
|
|
117
|
+
)
|
|
118
|
+
|