atendentepro 0.6.5__tar.gz → 0.6.7__tar.gz
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-0.6.5 → atendentepro-0.6.7}/CHANGELOG.md +57 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/PKG-INFO +177 -1
- {atendentepro-0.6.5 → atendentepro-0.6.7}/README.md +176 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/__init__.py +14 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/agents/escalation.py +68 -9
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/agents/feedback.py +117 -12
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/network.py +97 -4
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/templates/manager.py +139 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/utils/__init__.py +15 -0
- atendentepro-0.6.7/atendentepro/utils/user_loader.py +335 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro.egg-info/SOURCES.txt +2 -1
- {atendentepro-0.6.5 → atendentepro-0.6.7}/pyproject.toml +1 -1
- {atendentepro-0.6.5 → atendentepro-0.6.7}/LICENSE +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/MANIFEST.in +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/README.md +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/agents/__init__.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/agents/answer.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/agents/confirmation.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/agents/flow.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/agents/interview.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/agents/knowledge.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/agents/onboarding.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/agents/triage.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/agents/usage.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/config/__init__.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/config/settings.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/guardrails/__init__.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/guardrails/manager.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/license.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/models/__init__.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/models/context.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/models/outputs.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/prompts/__init__.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/prompts/answer.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/prompts/confirmation.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/prompts/escalation.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/prompts/feedback.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/prompts/flow.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/prompts/interview.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/prompts/knowledge.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/prompts/onboarding.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/prompts/triage.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/templates/__init__.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/utils/openai_client.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/atendentepro/utils/tracing.py +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/requirements.txt +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/setup.cfg +0 -0
- {atendentepro-0.6.5 → atendentepro-0.6.7}/setup.py +0 -0
|
@@ -5,6 +5,63 @@ All notable changes to AtendentePro will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.6.7] - 2025-02-03
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Feedback Agent**: Correção crítica - configuração YAML agora é carregada e aplicada
|
|
12
|
+
- Removida validação hardcoded de tipos de ticket (agora configurável via YAML)
|
|
13
|
+
- Adicionada persistência de tickets em arquivo JSON (`feedback_tickets.json`)
|
|
14
|
+
- Tipos de ticket agora são validados contra `feedback_config.yaml`
|
|
15
|
+
- Configurações de email (brand_color, brand_name, sla_message) agora vêm do YAML
|
|
16
|
+
- **Escalation Agent**: Correção crítica - configuração YAML agora é carregada e aplicada
|
|
17
|
+
- Business hours agora configurável via `escalation_config.yaml`
|
|
18
|
+
- Keywords de prioridade (urgent/high) agora configuráveis via YAML
|
|
19
|
+
- Conversão automática de dias da semana (monday, tuesday, etc.) para números
|
|
20
|
+
- **Answer Agent**: Correção crítica - `answer_config.yaml` agora é carregado e usado
|
|
21
|
+
- Template de resposta agora vem do arquivo de configuração
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- **Novos modelos de configuração em TemplateManager**:
|
|
25
|
+
- `FeedbackConfig`: Modelo Pydantic para `feedback_config.yaml`
|
|
26
|
+
- `EscalationConfig`: Modelo Pydantic para `escalation_config.yaml`
|
|
27
|
+
- `AnswerConfig`: Modelo Pydantic para `answer_config.yaml`
|
|
28
|
+
- **Métodos de carregamento**:
|
|
29
|
+
- `load_feedback_config()`: Carrega configuração do Feedback Agent
|
|
30
|
+
- `load_escalation_config()`: Carrega configuração do Escalation Agent
|
|
31
|
+
- `load_answer_config()`: Carrega configuração do Answer Agent
|
|
32
|
+
- **Persistência de tickets**: Sistema de armazenamento em JSON para tickets do Feedback Agent
|
|
33
|
+
- Configurável via variável de ambiente `FEEDBACK_STORAGE_PATH`
|
|
34
|
+
- Carregamento automático ao iniciar
|
|
35
|
+
- Salvamento automático após cada criação/atualização
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- **Feedback Agent**: `create_feedback_agent()` agora aceita `ticket_types` da configuração YAML
|
|
39
|
+
- **Escalation Agent**: `create_escalation_agent()` agora aceita `business_hours` e `priority_keywords` da configuração
|
|
40
|
+
- **Network**: `create_standard_network()` agora carrega e aplica configurações YAML automaticamente
|
|
41
|
+
|
|
42
|
+
## [0.6.6] - 2025-01-21
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
- **User Loader Module**: Sistema de carregamento automático de usuários cadastrados
|
|
46
|
+
- `create_user_loader()`: Factory para criar loaders customizados
|
|
47
|
+
- `run_with_user_context()`: Função helper para executar agentes com carregamento automático
|
|
48
|
+
- `extract_phone_from_messages()`: Extrai telefone das mensagens
|
|
49
|
+
- `extract_email_from_messages()`: Extrai email das mensagens
|
|
50
|
+
- `extract_user_id_from_messages()`: Extrai user_id/CPF das mensagens
|
|
51
|
+
- `load_user_from_csv()`: Helper para carregar usuários de CSV
|
|
52
|
+
- **Novos parâmetros em `create_standard_network`**:
|
|
53
|
+
- `user_loader`: Função para carregar dados do usuário das mensagens
|
|
54
|
+
- `auto_load_user`: Flag para carregar usuário automaticamente
|
|
55
|
+
- **Atributos em `AgentNetwork`**:
|
|
56
|
+
- `user_loader`: Loader configurado na network
|
|
57
|
+
- `loaded_user_context`: Contexto do usuário carregado
|
|
58
|
+
- **Exemplos completos**: Pasta `docs/examples/user_loader/` com exemplos de CSV e banco de dados
|
|
59
|
+
|
|
60
|
+
### Documentation
|
|
61
|
+
- README atualizado com seção completa de Carregamento de Usuários
|
|
62
|
+
- Exemplos práticos de uso com CSV, banco de dados e múltiplos identificadores
|
|
63
|
+
- Documentação de integração com onboarding
|
|
64
|
+
|
|
8
65
|
## [0.6.5] - 2025-01-21
|
|
9
66
|
|
|
10
67
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: atendentepro
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.7
|
|
4
4
|
Summary: Framework de orquestração de agentes IA com tom e estilo customizáveis. Integra documentos (RAG), APIs e bancos de dados em uma plataforma inteligente multi-agente.
|
|
5
5
|
Author-email: BeMonkAI <contato@monkai.com.br>
|
|
6
6
|
Maintainer-email: BeMonkAI <contato@monkai.com.br>
|
|
@@ -96,6 +96,7 @@ Plataforma que unifica múltiplos agentes especializados para resolver demandas
|
|
|
96
96
|
- [Estilo de Comunicação](#-estilo-de-comunicação-agentstyle)
|
|
97
97
|
- [Single Reply Mode](#-single-reply-mode)
|
|
98
98
|
- [Filtros de Acesso](#-filtros-de-acesso-roleuser)
|
|
99
|
+
- [Carregamento de Usuários](#-carregamento-de-usuários-user-loader)
|
|
99
100
|
- [Múltiplos Agentes](#-múltiplos-agentes-multi-interview--knowledge)
|
|
100
101
|
- [Tracing e Monitoramento](#-tracing-e-monitoramento)
|
|
101
102
|
- [Suporte](#-suporte)
|
|
@@ -1040,6 +1041,181 @@ tool_access:
|
|
|
1040
1041
|
|
|
1041
1042
|
---
|
|
1042
1043
|
|
|
1044
|
+
## 👤 Carregamento de Usuários (User Loader)
|
|
1045
|
+
|
|
1046
|
+
O **User Loader** identifica automaticamente usuários cadastrados nas conversas e carrega suas informações para enriquecer o contexto, permitindo personalização e evitando onboarding desnecessário.
|
|
1047
|
+
|
|
1048
|
+
📂 **Exemplos completos**: [docs/examples/user_loader/](docs/examples/user_loader/)
|
|
1049
|
+
|
|
1050
|
+
### Quando Usar
|
|
1051
|
+
|
|
1052
|
+
| Cenário | Solução |
|
|
1053
|
+
|---------|---------|
|
|
1054
|
+
| **Usuário existente** | Identifica automaticamente e pula onboarding |
|
|
1055
|
+
| **Personalização** | Carrega dados do usuário para respostas personalizadas |
|
|
1056
|
+
| **Contexto enriquecido** | Todos os agentes têm acesso a informações do usuário |
|
|
1057
|
+
| **Múltiplas fontes** | Suporta CSV, banco de dados, APIs REST, etc. |
|
|
1058
|
+
|
|
1059
|
+
### Funcionalidades
|
|
1060
|
+
|
|
1061
|
+
1. **Extração automática** de identificadores (telefone, email, CPF, etc.)
|
|
1062
|
+
2. **Carregamento de dados** de múltiplas fontes
|
|
1063
|
+
3. **Criação automática** de `UserContext`
|
|
1064
|
+
4. **Integração transparente** com a rede de agentes
|
|
1065
|
+
|
|
1066
|
+
### Exemplo 1: Carregamento de CSV
|
|
1067
|
+
|
|
1068
|
+
```python
|
|
1069
|
+
from pathlib import Path
|
|
1070
|
+
from atendentepro import (
|
|
1071
|
+
create_standard_network,
|
|
1072
|
+
create_user_loader,
|
|
1073
|
+
load_user_from_csv,
|
|
1074
|
+
extract_email_from_messages,
|
|
1075
|
+
run_with_user_context,
|
|
1076
|
+
)
|
|
1077
|
+
|
|
1078
|
+
# Função para carregar do CSV
|
|
1079
|
+
def load_user(identifier: str):
|
|
1080
|
+
return load_user_from_csv(
|
|
1081
|
+
csv_path=Path("users.csv"),
|
|
1082
|
+
identifier_field="email",
|
|
1083
|
+
identifier_value=identifier
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
# Criar loader
|
|
1087
|
+
loader = create_user_loader(
|
|
1088
|
+
loader_func=load_user,
|
|
1089
|
+
identifier_extractor=extract_email_from_messages
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
# Criar network com loader
|
|
1093
|
+
network = create_standard_network(
|
|
1094
|
+
templates_root=Path("./templates"),
|
|
1095
|
+
user_loader=loader,
|
|
1096
|
+
include_onboarding=True,
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
# Executar com carregamento automático
|
|
1100
|
+
messages = [{"role": "user", "content": "Meu email é joao@example.com"}]
|
|
1101
|
+
result = await run_with_user_context(network, network.triage, messages)
|
|
1102
|
+
|
|
1103
|
+
# Verificar se usuário foi carregado
|
|
1104
|
+
if network.loaded_user_context:
|
|
1105
|
+
print(f"Usuário: {network.loaded_user_context.metadata.get('nome')}")
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
### Exemplo 2: Carregamento de Banco de Dados
|
|
1109
|
+
|
|
1110
|
+
```python
|
|
1111
|
+
import sqlite3
|
|
1112
|
+
from atendentepro import create_user_loader, extract_email_from_messages
|
|
1113
|
+
|
|
1114
|
+
def load_from_db(identifier: str):
|
|
1115
|
+
conn = sqlite3.connect("users.db")
|
|
1116
|
+
cursor = conn.cursor()
|
|
1117
|
+
cursor.execute("SELECT * FROM users WHERE email = ?", (identifier,))
|
|
1118
|
+
row = cursor.fetchone()
|
|
1119
|
+
conn.close()
|
|
1120
|
+
|
|
1121
|
+
if row:
|
|
1122
|
+
return {
|
|
1123
|
+
"user_id": row[0],
|
|
1124
|
+
"role": row[1],
|
|
1125
|
+
"nome": row[2],
|
|
1126
|
+
"email": row[3],
|
|
1127
|
+
}
|
|
1128
|
+
return None
|
|
1129
|
+
|
|
1130
|
+
loader = create_user_loader(load_from_db, extract_email_from_messages)
|
|
1131
|
+
|
|
1132
|
+
network = create_standard_network(
|
|
1133
|
+
templates_root=Path("./templates"),
|
|
1134
|
+
user_loader=loader,
|
|
1135
|
+
)
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
### Exemplo 3: Múltiplos Identificadores
|
|
1139
|
+
|
|
1140
|
+
```python
|
|
1141
|
+
from atendentepro import (
|
|
1142
|
+
create_user_loader,
|
|
1143
|
+
extract_email_from_messages,
|
|
1144
|
+
extract_phone_from_messages,
|
|
1145
|
+
)
|
|
1146
|
+
|
|
1147
|
+
def extract_identifier(messages):
|
|
1148
|
+
# Tenta email primeiro
|
|
1149
|
+
email = extract_email_from_messages(messages)
|
|
1150
|
+
if email:
|
|
1151
|
+
return email
|
|
1152
|
+
|
|
1153
|
+
# Se não encontrou, tenta telefone
|
|
1154
|
+
phone = extract_phone_from_messages(messages)
|
|
1155
|
+
if phone:
|
|
1156
|
+
return phone
|
|
1157
|
+
|
|
1158
|
+
return None
|
|
1159
|
+
|
|
1160
|
+
loader = create_user_loader(
|
|
1161
|
+
loader_func=load_user,
|
|
1162
|
+
identifier_extractor=extract_identifier
|
|
1163
|
+
)
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
### Funções Disponíveis
|
|
1167
|
+
|
|
1168
|
+
#### Extratores de Identificador
|
|
1169
|
+
|
|
1170
|
+
```python
|
|
1171
|
+
from atendentepro import (
|
|
1172
|
+
extract_phone_from_messages, # Extrai telefone
|
|
1173
|
+
extract_email_from_messages, # Extrai email
|
|
1174
|
+
extract_user_id_from_messages, # Extrai CPF/user_id
|
|
1175
|
+
)
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
#### Criar Loader
|
|
1179
|
+
|
|
1180
|
+
```python
|
|
1181
|
+
from atendentepro import create_user_loader
|
|
1182
|
+
|
|
1183
|
+
loader = create_user_loader(
|
|
1184
|
+
loader_func=load_user_function,
|
|
1185
|
+
identifier_extractor=extract_email_from_messages # Opcional
|
|
1186
|
+
)
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
#### Executar com Contexto
|
|
1190
|
+
|
|
1191
|
+
```python
|
|
1192
|
+
from atendentepro import run_with_user_context
|
|
1193
|
+
|
|
1194
|
+
result = await run_with_user_context(
|
|
1195
|
+
network,
|
|
1196
|
+
network.triage,
|
|
1197
|
+
messages
|
|
1198
|
+
)
|
|
1199
|
+
```
|
|
1200
|
+
|
|
1201
|
+
### Integração com Onboarding
|
|
1202
|
+
|
|
1203
|
+
Quando um `user_loader` está configurado:
|
|
1204
|
+
|
|
1205
|
+
- ✅ **Usuário encontrado**: Vai direto para o triage, sem passar pelo onboarding
|
|
1206
|
+
- ✅ **Usuário não encontrado**: É direcionado para o onboarding normalmente
|
|
1207
|
+
- ✅ **Contexto disponível**: Todos os agentes têm acesso a `network.loaded_user_context`
|
|
1208
|
+
|
|
1209
|
+
### Benefícios
|
|
1210
|
+
|
|
1211
|
+
1. ✅ **Experiência personalizada** - Respostas baseadas em dados do usuário
|
|
1212
|
+
2. ✅ **Menos fricção** - Usuários conhecidos não precisam fazer onboarding
|
|
1213
|
+
3. ✅ **Contexto rico** - Todos os agentes têm acesso a informações do usuário
|
|
1214
|
+
4. ✅ **Flexível** - Suporta múltiplas fontes de dados
|
|
1215
|
+
5. ✅ **Automático** - Funciona transparentemente durante a conversa
|
|
1216
|
+
|
|
1217
|
+
---
|
|
1218
|
+
|
|
1043
1219
|
## 🔀 Múltiplos Agentes (Multi Interview + Knowledge)
|
|
1044
1220
|
|
|
1045
1221
|
O AtendentePro suporta criar **múltiplas instâncias** de Interview e Knowledge agents, cada um especializado em um domínio diferente.
|
|
@@ -38,6 +38,7 @@ Plataforma que unifica múltiplos agentes especializados para resolver demandas
|
|
|
38
38
|
- [Estilo de Comunicação](#-estilo-de-comunicação-agentstyle)
|
|
39
39
|
- [Single Reply Mode](#-single-reply-mode)
|
|
40
40
|
- [Filtros de Acesso](#-filtros-de-acesso-roleuser)
|
|
41
|
+
- [Carregamento de Usuários](#-carregamento-de-usuários-user-loader)
|
|
41
42
|
- [Múltiplos Agentes](#-múltiplos-agentes-multi-interview--knowledge)
|
|
42
43
|
- [Tracing e Monitoramento](#-tracing-e-monitoramento)
|
|
43
44
|
- [Suporte](#-suporte)
|
|
@@ -982,6 +983,181 @@ tool_access:
|
|
|
982
983
|
|
|
983
984
|
---
|
|
984
985
|
|
|
986
|
+
## 👤 Carregamento de Usuários (User Loader)
|
|
987
|
+
|
|
988
|
+
O **User Loader** identifica automaticamente usuários cadastrados nas conversas e carrega suas informações para enriquecer o contexto, permitindo personalização e evitando onboarding desnecessário.
|
|
989
|
+
|
|
990
|
+
📂 **Exemplos completos**: [docs/examples/user_loader/](docs/examples/user_loader/)
|
|
991
|
+
|
|
992
|
+
### Quando Usar
|
|
993
|
+
|
|
994
|
+
| Cenário | Solução |
|
|
995
|
+
|---------|---------|
|
|
996
|
+
| **Usuário existente** | Identifica automaticamente e pula onboarding |
|
|
997
|
+
| **Personalização** | Carrega dados do usuário para respostas personalizadas |
|
|
998
|
+
| **Contexto enriquecido** | Todos os agentes têm acesso a informações do usuário |
|
|
999
|
+
| **Múltiplas fontes** | Suporta CSV, banco de dados, APIs REST, etc. |
|
|
1000
|
+
|
|
1001
|
+
### Funcionalidades
|
|
1002
|
+
|
|
1003
|
+
1. **Extração automática** de identificadores (telefone, email, CPF, etc.)
|
|
1004
|
+
2. **Carregamento de dados** de múltiplas fontes
|
|
1005
|
+
3. **Criação automática** de `UserContext`
|
|
1006
|
+
4. **Integração transparente** com a rede de agentes
|
|
1007
|
+
|
|
1008
|
+
### Exemplo 1: Carregamento de CSV
|
|
1009
|
+
|
|
1010
|
+
```python
|
|
1011
|
+
from pathlib import Path
|
|
1012
|
+
from atendentepro import (
|
|
1013
|
+
create_standard_network,
|
|
1014
|
+
create_user_loader,
|
|
1015
|
+
load_user_from_csv,
|
|
1016
|
+
extract_email_from_messages,
|
|
1017
|
+
run_with_user_context,
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
# Função para carregar do CSV
|
|
1021
|
+
def load_user(identifier: str):
|
|
1022
|
+
return load_user_from_csv(
|
|
1023
|
+
csv_path=Path("users.csv"),
|
|
1024
|
+
identifier_field="email",
|
|
1025
|
+
identifier_value=identifier
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
# Criar loader
|
|
1029
|
+
loader = create_user_loader(
|
|
1030
|
+
loader_func=load_user,
|
|
1031
|
+
identifier_extractor=extract_email_from_messages
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
# Criar network com loader
|
|
1035
|
+
network = create_standard_network(
|
|
1036
|
+
templates_root=Path("./templates"),
|
|
1037
|
+
user_loader=loader,
|
|
1038
|
+
include_onboarding=True,
|
|
1039
|
+
)
|
|
1040
|
+
|
|
1041
|
+
# Executar com carregamento automático
|
|
1042
|
+
messages = [{"role": "user", "content": "Meu email é joao@example.com"}]
|
|
1043
|
+
result = await run_with_user_context(network, network.triage, messages)
|
|
1044
|
+
|
|
1045
|
+
# Verificar se usuário foi carregado
|
|
1046
|
+
if network.loaded_user_context:
|
|
1047
|
+
print(f"Usuário: {network.loaded_user_context.metadata.get('nome')}")
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
### Exemplo 2: Carregamento de Banco de Dados
|
|
1051
|
+
|
|
1052
|
+
```python
|
|
1053
|
+
import sqlite3
|
|
1054
|
+
from atendentepro import create_user_loader, extract_email_from_messages
|
|
1055
|
+
|
|
1056
|
+
def load_from_db(identifier: str):
|
|
1057
|
+
conn = sqlite3.connect("users.db")
|
|
1058
|
+
cursor = conn.cursor()
|
|
1059
|
+
cursor.execute("SELECT * FROM users WHERE email = ?", (identifier,))
|
|
1060
|
+
row = cursor.fetchone()
|
|
1061
|
+
conn.close()
|
|
1062
|
+
|
|
1063
|
+
if row:
|
|
1064
|
+
return {
|
|
1065
|
+
"user_id": row[0],
|
|
1066
|
+
"role": row[1],
|
|
1067
|
+
"nome": row[2],
|
|
1068
|
+
"email": row[3],
|
|
1069
|
+
}
|
|
1070
|
+
return None
|
|
1071
|
+
|
|
1072
|
+
loader = create_user_loader(load_from_db, extract_email_from_messages)
|
|
1073
|
+
|
|
1074
|
+
network = create_standard_network(
|
|
1075
|
+
templates_root=Path("./templates"),
|
|
1076
|
+
user_loader=loader,
|
|
1077
|
+
)
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
### Exemplo 3: Múltiplos Identificadores
|
|
1081
|
+
|
|
1082
|
+
```python
|
|
1083
|
+
from atendentepro import (
|
|
1084
|
+
create_user_loader,
|
|
1085
|
+
extract_email_from_messages,
|
|
1086
|
+
extract_phone_from_messages,
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
def extract_identifier(messages):
|
|
1090
|
+
# Tenta email primeiro
|
|
1091
|
+
email = extract_email_from_messages(messages)
|
|
1092
|
+
if email:
|
|
1093
|
+
return email
|
|
1094
|
+
|
|
1095
|
+
# Se não encontrou, tenta telefone
|
|
1096
|
+
phone = extract_phone_from_messages(messages)
|
|
1097
|
+
if phone:
|
|
1098
|
+
return phone
|
|
1099
|
+
|
|
1100
|
+
return None
|
|
1101
|
+
|
|
1102
|
+
loader = create_user_loader(
|
|
1103
|
+
loader_func=load_user,
|
|
1104
|
+
identifier_extractor=extract_identifier
|
|
1105
|
+
)
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
### Funções Disponíveis
|
|
1109
|
+
|
|
1110
|
+
#### Extratores de Identificador
|
|
1111
|
+
|
|
1112
|
+
```python
|
|
1113
|
+
from atendentepro import (
|
|
1114
|
+
extract_phone_from_messages, # Extrai telefone
|
|
1115
|
+
extract_email_from_messages, # Extrai email
|
|
1116
|
+
extract_user_id_from_messages, # Extrai CPF/user_id
|
|
1117
|
+
)
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
#### Criar Loader
|
|
1121
|
+
|
|
1122
|
+
```python
|
|
1123
|
+
from atendentepro import create_user_loader
|
|
1124
|
+
|
|
1125
|
+
loader = create_user_loader(
|
|
1126
|
+
loader_func=load_user_function,
|
|
1127
|
+
identifier_extractor=extract_email_from_messages # Opcional
|
|
1128
|
+
)
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
#### Executar com Contexto
|
|
1132
|
+
|
|
1133
|
+
```python
|
|
1134
|
+
from atendentepro import run_with_user_context
|
|
1135
|
+
|
|
1136
|
+
result = await run_with_user_context(
|
|
1137
|
+
network,
|
|
1138
|
+
network.triage,
|
|
1139
|
+
messages
|
|
1140
|
+
)
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
### Integração com Onboarding
|
|
1144
|
+
|
|
1145
|
+
Quando um `user_loader` está configurado:
|
|
1146
|
+
|
|
1147
|
+
- ✅ **Usuário encontrado**: Vai direto para o triage, sem passar pelo onboarding
|
|
1148
|
+
- ✅ **Usuário não encontrado**: É direcionado para o onboarding normalmente
|
|
1149
|
+
- ✅ **Contexto disponível**: Todos os agentes têm acesso a `network.loaded_user_context`
|
|
1150
|
+
|
|
1151
|
+
### Benefícios
|
|
1152
|
+
|
|
1153
|
+
1. ✅ **Experiência personalizada** - Respostas baseadas em dados do usuário
|
|
1154
|
+
2. ✅ **Menos fricção** - Usuários conhecidos não precisam fazer onboarding
|
|
1155
|
+
3. ✅ **Contexto rico** - Todos os agentes têm acesso a informações do usuário
|
|
1156
|
+
4. ✅ **Flexível** - Suporta múltiplas fontes de dados
|
|
1157
|
+
5. ✅ **Automático** - Funciona transparentemente durante a conversa
|
|
1158
|
+
|
|
1159
|
+
---
|
|
1160
|
+
|
|
985
1161
|
## 🔀 Múltiplos Agentes (Multi Interview + Knowledge)
|
|
986
1162
|
|
|
987
1163
|
O AtendentePro suporta criar **múltiplas instâncias** de Interview e Knowledge agents, cada um especializado em um domínio diferente.
|
|
@@ -158,6 +158,13 @@ from atendentepro.utils import (
|
|
|
158
158
|
run_with_monkai_tracking,
|
|
159
159
|
# Application Insights
|
|
160
160
|
configure_application_insights,
|
|
161
|
+
# User Loader
|
|
162
|
+
create_user_loader,
|
|
163
|
+
run_with_user_context,
|
|
164
|
+
extract_phone_from_messages,
|
|
165
|
+
extract_email_from_messages,
|
|
166
|
+
extract_user_id_from_messages,
|
|
167
|
+
load_user_from_csv,
|
|
161
168
|
)
|
|
162
169
|
|
|
163
170
|
__all__ = [
|
|
@@ -253,5 +260,12 @@ __all__ = [
|
|
|
253
260
|
"run_with_monkai_tracking",
|
|
254
261
|
# Application Insights
|
|
255
262
|
"configure_application_insights",
|
|
263
|
+
# User Loader
|
|
264
|
+
"create_user_loader",
|
|
265
|
+
"run_with_user_context",
|
|
266
|
+
"extract_phone_from_messages",
|
|
267
|
+
"extract_email_from_messages",
|
|
268
|
+
"extract_user_id_from_messages",
|
|
269
|
+
"load_user_from_csv",
|
|
256
270
|
]
|
|
257
271
|
|
|
@@ -47,6 +47,13 @@ DEFAULT_BUSINESS_HOURS = {
|
|
|
47
47
|
"days": [0, 1, 2, 3, 4], # Seg-Sex
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
# Configurable business hours (None = use DEFAULT_BUSINESS_HOURS)
|
|
51
|
+
_configured_business_hours: Optional[Dict[str, Any]] = None
|
|
52
|
+
|
|
53
|
+
# Configurable priority keywords (None = use defaults)
|
|
54
|
+
_priority_keywords_urgent: Optional[List[str]] = None
|
|
55
|
+
_priority_keywords_high: Optional[List[str]] = None
|
|
56
|
+
|
|
50
57
|
|
|
51
58
|
# =============================================================================
|
|
52
59
|
# Storage de Escalações (em memória - substituir por DB em produção)
|
|
@@ -139,22 +146,46 @@ def _notificar_equipe(escalation: Escalation) -> bool:
|
|
|
139
146
|
|
|
140
147
|
def _verificar_disponibilidade() -> Dict[str, Any]:
|
|
141
148
|
"""Verifica se atendimento humano está disponível."""
|
|
149
|
+
global _configured_business_hours
|
|
142
150
|
agora = datetime.now()
|
|
143
151
|
hora = agora.hour
|
|
144
152
|
dia = agora.weekday()
|
|
145
153
|
|
|
146
|
-
#
|
|
147
|
-
|
|
148
|
-
|
|
154
|
+
# Use configured business hours or fall back to defaults
|
|
155
|
+
if _configured_business_hours:
|
|
156
|
+
business_hours = _configured_business_hours
|
|
157
|
+
hora_inicio = business_hours.get("start", DEFAULT_BUSINESS_HOURS["start"])
|
|
158
|
+
hora_fim = business_hours.get("end", DEFAULT_BUSINESS_HOURS["end"])
|
|
159
|
+
days = business_hours.get("days", DEFAULT_BUSINESS_HOURS["days"])
|
|
160
|
+
else:
|
|
161
|
+
# Verificar variáveis de ambiente para horário customizado
|
|
162
|
+
hora_inicio = int(os.getenv("ESCALATION_HOUR_START", DEFAULT_BUSINESS_HOURS["start"]))
|
|
163
|
+
hora_fim = int(os.getenv("ESCALATION_HOUR_END", DEFAULT_BUSINESS_HOURS["end"]))
|
|
164
|
+
days = DEFAULT_BUSINESS_HOURS["days"]
|
|
165
|
+
|
|
166
|
+
# Ensure days is a list of integers
|
|
167
|
+
if isinstance(days, list):
|
|
168
|
+
days_int = [d if isinstance(d, int) else int(d) for d in days]
|
|
169
|
+
else:
|
|
170
|
+
days_int = DEFAULT_BUSINESS_HOURS["days"]
|
|
171
|
+
|
|
172
|
+
disponivel = dia in days_int and hora_inicio <= hora < hora_fim
|
|
149
173
|
|
|
150
|
-
|
|
174
|
+
# Format days for display
|
|
175
|
+
day_names = ["Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado", "Domingo"]
|
|
176
|
+
if len(days_int) == 1:
|
|
177
|
+
dias_atendimento = day_names[days_int[0]]
|
|
178
|
+
elif len(days_int) == 5 and days_int == [0, 1, 2, 3, 4]:
|
|
179
|
+
dias_atendimento = "Segunda a Sexta"
|
|
180
|
+
else:
|
|
181
|
+
dias_atendimento = ", ".join(day_names[d] for d in sorted(days_int))
|
|
151
182
|
|
|
152
183
|
return {
|
|
153
184
|
"disponivel": disponivel,
|
|
154
185
|
"hora_atual": agora.strftime("%H:%M"),
|
|
155
|
-
"dia_semana": [
|
|
186
|
+
"dia_semana": day_names[dia],
|
|
156
187
|
"horario_atendimento": f"{hora_inicio:02d}:00 - {hora_fim:02d}:00",
|
|
157
|
-
"dias_atendimento":
|
|
188
|
+
"dias_atendimento": dias_atendimento,
|
|
158
189
|
}
|
|
159
190
|
|
|
160
191
|
|
|
@@ -166,15 +197,16 @@ def _classificar_prioridade(motivo: str, categoria: str) -> str:
|
|
|
166
197
|
"""
|
|
167
198
|
Classifica automaticamente a prioridade baseado no motivo e categoria.
|
|
168
199
|
"""
|
|
200
|
+
global _priority_keywords_urgent, _priority_keywords_high
|
|
169
201
|
motivo_lower = motivo.lower()
|
|
170
202
|
|
|
171
|
-
#
|
|
172
|
-
palavras_urgentes = [
|
|
203
|
+
# Use configured keywords or defaults
|
|
204
|
+
palavras_urgentes = _priority_keywords_urgent or [
|
|
173
205
|
"urgente", "emergência", "emergencia", "crítico", "critico",
|
|
174
206
|
"não funciona", "parou", "bloqueado", "cancelar", "prejuízo"
|
|
175
207
|
]
|
|
176
208
|
|
|
177
|
-
palavras_alta = [
|
|
209
|
+
palavras_alta = _priority_keywords_high or [
|
|
178
210
|
"reclamação", "reclamacao", "insatisfeito", "problema grave",
|
|
179
211
|
"já tentei", "terceira vez", "não resolve"
|
|
180
212
|
]
|
|
@@ -449,6 +481,9 @@ ESCALATION_TOOLS = [
|
|
|
449
481
|
def create_escalation_agent(
|
|
450
482
|
escalation_triggers: str = "",
|
|
451
483
|
escalation_channels: str = "",
|
|
484
|
+
business_hours: Optional[Dict[str, Any]] = None,
|
|
485
|
+
priority_keywords_urgent: Optional[List[str]] = None,
|
|
486
|
+
priority_keywords_high: Optional[List[str]] = None,
|
|
452
487
|
handoffs: Optional[List] = None,
|
|
453
488
|
tools: Optional[List] = None,
|
|
454
489
|
guardrails: Optional[List["GuardrailCallable"]] = None,
|
|
@@ -472,6 +507,9 @@ def create_escalation_agent(
|
|
|
472
507
|
Args:
|
|
473
508
|
escalation_triggers: Custom triggers for escalation (keywords, situations).
|
|
474
509
|
escalation_channels: Available contact channels description.
|
|
510
|
+
business_hours: Optional dict with "start", "end", and "days" (list of weekday numbers 0-6 or day names).
|
|
511
|
+
priority_keywords_urgent: Optional list of keywords that indicate urgent priority.
|
|
512
|
+
priority_keywords_high: Optional list of keywords that indicate high priority.
|
|
475
513
|
handoffs: List of agents to hand off to (usually triage to return).
|
|
476
514
|
tools: Additional tools (custom notifications, integrations).
|
|
477
515
|
guardrails: List of input guardrails.
|
|
@@ -492,6 +530,27 @@ def create_escalation_agent(
|
|
|
492
530
|
>>> triage.handoffs.append(escalation)
|
|
493
531
|
>>> flow.handoffs.append(escalation)
|
|
494
532
|
"""
|
|
533
|
+
global _configured_business_hours, _priority_keywords_urgent, _priority_keywords_high
|
|
534
|
+
|
|
535
|
+
# Configure business hours
|
|
536
|
+
if business_hours:
|
|
537
|
+
# Convert day names to weekday numbers if needed
|
|
538
|
+
days = business_hours.get("days", [])
|
|
539
|
+
if days and isinstance(days[0], str):
|
|
540
|
+
day_map = {
|
|
541
|
+
"monday": 0, "tuesday": 1, "wednesday": 2, "thursday": 3,
|
|
542
|
+
"friday": 4, "saturday": 5, "sunday": 6,
|
|
543
|
+
}
|
|
544
|
+
days_int = [day_map.get(day.lower(), day) for day in days if day.lower() in day_map]
|
|
545
|
+
business_hours = {**business_hours, "days": days_int}
|
|
546
|
+
_configured_business_hours = business_hours
|
|
547
|
+
else:
|
|
548
|
+
_configured_business_hours = None
|
|
549
|
+
|
|
550
|
+
# Configure priority keywords
|
|
551
|
+
_priority_keywords_urgent = priority_keywords_urgent
|
|
552
|
+
_priority_keywords_high = priority_keywords_high
|
|
553
|
+
|
|
495
554
|
if custom_instructions:
|
|
496
555
|
instructions = f"{RECOMMENDED_PROMPT_PREFIX} {custom_instructions}"
|
|
497
556
|
else:
|