atendentepro 0.6.5__tar.gz → 0.6.6__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.
Files changed (48) hide show
  1. {atendentepro-0.6.5 → atendentepro-0.6.6}/CHANGELOG.md +23 -0
  2. {atendentepro-0.6.5 → atendentepro-0.6.6}/PKG-INFO +177 -1
  3. {atendentepro-0.6.5 → atendentepro-0.6.6}/README.md +176 -0
  4. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/__init__.py +14 -0
  5. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/network.py +14 -0
  6. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/utils/__init__.py +15 -0
  7. atendentepro-0.6.6/atendentepro/utils/user_loader.py +335 -0
  8. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro.egg-info/SOURCES.txt +2 -1
  9. {atendentepro-0.6.5 → atendentepro-0.6.6}/pyproject.toml +1 -1
  10. {atendentepro-0.6.5 → atendentepro-0.6.6}/LICENSE +0 -0
  11. {atendentepro-0.6.5 → atendentepro-0.6.6}/MANIFEST.in +0 -0
  12. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/README.md +0 -0
  13. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/agents/__init__.py +0 -0
  14. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/agents/answer.py +0 -0
  15. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/agents/confirmation.py +0 -0
  16. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/agents/escalation.py +0 -0
  17. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/agents/feedback.py +0 -0
  18. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/agents/flow.py +0 -0
  19. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/agents/interview.py +0 -0
  20. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/agents/knowledge.py +0 -0
  21. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/agents/onboarding.py +0 -0
  22. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/agents/triage.py +0 -0
  23. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/agents/usage.py +0 -0
  24. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/config/__init__.py +0 -0
  25. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/config/settings.py +0 -0
  26. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/guardrails/__init__.py +0 -0
  27. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/guardrails/manager.py +0 -0
  28. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/license.py +0 -0
  29. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/models/__init__.py +0 -0
  30. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/models/context.py +0 -0
  31. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/models/outputs.py +0 -0
  32. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/prompts/__init__.py +0 -0
  33. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/prompts/answer.py +0 -0
  34. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/prompts/confirmation.py +0 -0
  35. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/prompts/escalation.py +0 -0
  36. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/prompts/feedback.py +0 -0
  37. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/prompts/flow.py +0 -0
  38. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/prompts/interview.py +0 -0
  39. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/prompts/knowledge.py +0 -0
  40. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/prompts/onboarding.py +0 -0
  41. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/prompts/triage.py +0 -0
  42. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/templates/__init__.py +0 -0
  43. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/templates/manager.py +0 -0
  44. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/utils/openai_client.py +0 -0
  45. {atendentepro-0.6.5 → atendentepro-0.6.6}/atendentepro/utils/tracing.py +0 -0
  46. {atendentepro-0.6.5 → atendentepro-0.6.6}/requirements.txt +0 -0
  47. {atendentepro-0.6.5 → atendentepro-0.6.6}/setup.cfg +0 -0
  48. {atendentepro-0.6.5 → atendentepro-0.6.6}/setup.py +0 -0
@@ -5,6 +5,29 @@ 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.6] - 2025-01-21
9
+
10
+ ### Added
11
+ - **User Loader Module**: Sistema de carregamento automático de usuários cadastrados
12
+ - `create_user_loader()`: Factory para criar loaders customizados
13
+ - `run_with_user_context()`: Função helper para executar agentes com carregamento automático
14
+ - `extract_phone_from_messages()`: Extrai telefone das mensagens
15
+ - `extract_email_from_messages()`: Extrai email das mensagens
16
+ - `extract_user_id_from_messages()`: Extrai user_id/CPF das mensagens
17
+ - `load_user_from_csv()`: Helper para carregar usuários de CSV
18
+ - **Novos parâmetros em `create_standard_network`**:
19
+ - `user_loader`: Função para carregar dados do usuário das mensagens
20
+ - `auto_load_user`: Flag para carregar usuário automaticamente
21
+ - **Atributos em `AgentNetwork`**:
22
+ - `user_loader`: Loader configurado na network
23
+ - `loaded_user_context`: Contexto do usuário carregado
24
+ - **Exemplos completos**: Pasta `docs/examples/user_loader/` com exemplos de CSV e banco de dados
25
+
26
+ ### Documentation
27
+ - README atualizado com seção completa de Carregamento de Usuários
28
+ - Exemplos práticos de uso com CSV, banco de dados e múltiplos identificadores
29
+ - Documentação de integração com onboarding
30
+
8
31
  ## [0.6.5] - 2025-01-21
9
32
 
10
33
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atendentepro
3
- Version: 0.6.5
3
+ Version: 0.6.6
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
 
@@ -157,6 +157,10 @@ class AgentNetwork:
157
157
  templates_root: Optional[Path] = None
158
158
  current_client: str = "standard"
159
159
 
160
+ # User loading
161
+ user_loader: Optional[Callable[[List], Optional["UserContext"]]] = None
162
+ loaded_user_context: Optional["UserContext"] = None
163
+
160
164
  def get_all_agents(self) -> List:
161
165
  """Get list of all configured agents."""
162
166
  agents = []
@@ -216,6 +220,9 @@ def create_standard_network(
216
220
  agent_filters: Optional[Dict[str, AccessFilter]] = None,
217
221
  conditional_prompts: Optional[Dict[str, List[FilteredPromptSection]]] = None,
218
222
  filtered_tools: Optional[Dict[str, List[FilteredTool]]] = None,
223
+ # User loading
224
+ user_loader: Optional[Callable[[List], Optional[UserContext]]] = None,
225
+ auto_load_user: bool = False,
219
226
  ) -> AgentNetwork:
220
227
  """
221
228
  Create a standard agent network with proper handoff configuration.
@@ -259,6 +266,10 @@ def create_standard_network(
259
266
  agent_filters: Dict mapping agent names to AccessFilter (controls agent access).
260
267
  conditional_prompts: Dict mapping agent names to list of FilteredPromptSection.
261
268
  filtered_tools: Dict mapping agent names to list of FilteredTool.
269
+ user_loader: Optional function to load user data from messages. Receives list of
270
+ messages and returns UserContext or None. See create_user_loader().
271
+ auto_load_user: If True, automatically loads user context before agent execution.
272
+ Requires user_loader to be configured.
262
273
 
263
274
  Returns:
264
275
  Configured AgentNetwork instance.
@@ -686,6 +697,9 @@ def create_standard_network(
686
697
  network.onboarding = onboarding
687
698
  onboarding.handoffs = [triage]
688
699
 
700
+ # Store user_loader in network for later use
701
+ network.user_loader = user_loader
702
+
689
703
  return network
690
704
 
691
705
 
@@ -19,6 +19,14 @@ from .tracing import (
19
19
  # Legacy
20
20
  configure_tracing,
21
21
  )
22
+ from .user_loader import (
23
+ create_user_loader,
24
+ run_with_user_context,
25
+ extract_phone_from_messages,
26
+ extract_email_from_messages,
27
+ extract_user_id_from_messages,
28
+ load_user_from_csv,
29
+ )
22
30
 
23
31
  __all__ = [
24
32
  # OpenAI Client
@@ -36,5 +44,12 @@ __all__ = [
36
44
  "configure_application_insights",
37
45
  # Legacy
38
46
  "configure_tracing",
47
+ # User Loader
48
+ "create_user_loader",
49
+ "run_with_user_context",
50
+ "extract_phone_from_messages",
51
+ "extract_email_from_messages",
52
+ "extract_user_id_from_messages",
53
+ "load_user_from_csv",
39
54
  ]
40
55
 
@@ -0,0 +1,335 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ User Loader utilities for AtendentePro.
4
+
5
+ Provides functions to load user data from various sources and create UserContext
6
+ objects for enriching agent conversations with user information.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import csv
12
+ import re
13
+ from pathlib import Path
14
+ from typing import Any, Callable, Dict, List, Optional
15
+
16
+ from atendentepro.models import UserContext
17
+
18
+
19
+ def extract_phone_from_messages(messages: List[Dict[str, Any]]) -> Optional[str]:
20
+ """
21
+ Extract phone number from user messages.
22
+
23
+ Searches for phone patterns in the first user message.
24
+ Supports formats: (11) 99999-9999, 11999999999, +55 11 99999-9999, etc.
25
+
26
+ Args:
27
+ messages: List of message dictionaries with 'role' and 'content' keys.
28
+
29
+ Returns:
30
+ Phone number string if found, None otherwise.
31
+
32
+ Example:
33
+ >>> messages = [{"role": "user", "content": "Meu telefone é (11) 99999-8888"}]
34
+ >>> extract_phone_from_messages(messages)
35
+ '11999998888'
36
+ """
37
+ if not messages:
38
+ return None
39
+
40
+ # Find first user message
41
+ user_message = None
42
+ for msg in messages:
43
+ if msg.get("role") == "user" and msg.get("content"):
44
+ user_message = msg["content"]
45
+ break
46
+
47
+ if not user_message:
48
+ return None
49
+
50
+ # Phone patterns: (XX) XXXXX-XXXX, XX XXXXXXXX, +55 XX XXXXXXXX, etc.
51
+ phone_patterns = [
52
+ r'\(?(\d{2})\)?\s*(\d{4,5})-?(\d{4})', # (11) 99999-8888 or 11 99999-8888
53
+ r'\+?55\s*(\d{2})\s*(\d{4,5})-?(\d{4})', # +55 11 99999-8888
54
+ r'(\d{10,11})', # 11999998888
55
+ ]
56
+
57
+ for pattern in phone_patterns:
58
+ match = re.search(pattern, user_message)
59
+ if match:
60
+ # Extract digits only
61
+ digits = re.sub(r'\D', '', match.group(0))
62
+ # Normalize to 10 or 11 digits (with or without area code)
63
+ if len(digits) >= 10:
64
+ return digits[-10:] if len(digits) > 10 else digits
65
+
66
+ return None
67
+
68
+
69
+ def extract_email_from_messages(messages: List[Dict[str, Any]]) -> Optional[str]:
70
+ """
71
+ Extract email address from user messages.
72
+
73
+ Searches for email patterns in user messages.
74
+
75
+ Args:
76
+ messages: List of message dictionaries with 'role' and 'content' keys.
77
+
78
+ Returns:
79
+ Email address string if found, None otherwise.
80
+
81
+ Example:
82
+ >>> messages = [{"role": "user", "content": "Meu email é joao@example.com"}]
83
+ >>> extract_email_from_messages(messages)
84
+ 'joao@example.com'
85
+ """
86
+ if not messages:
87
+ return None
88
+
89
+ # Find first user message
90
+ user_message = None
91
+ for msg in messages:
92
+ if msg.get("role") == "user" and msg.get("content"):
93
+ user_message = msg["content"]
94
+ break
95
+
96
+ if not user_message:
97
+ return None
98
+
99
+ # Email pattern
100
+ email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
101
+ match = re.search(email_pattern, user_message)
102
+
103
+ if match:
104
+ return match.group(0).lower()
105
+
106
+ return None
107
+
108
+
109
+ def extract_user_id_from_messages(messages: List[Dict[str, Any]]) -> Optional[str]:
110
+ """
111
+ Extract user ID from user messages.
112
+
113
+ Looks for common patterns like "user_id:", "id:", "CPF:", etc.
114
+
115
+ Args:
116
+ messages: List of message dictionaries with 'role' and 'content' keys.
117
+
118
+ Returns:
119
+ User ID string if found, None otherwise.
120
+
121
+ Example:
122
+ >>> messages = [{"role": "user", "content": "Meu CPF é 123.456.789-00"}]
123
+ >>> extract_user_id_from_messages(messages)
124
+ '12345678900'
125
+ """
126
+ if not messages:
127
+ return None
128
+
129
+ # Find first user message
130
+ user_message = None
131
+ for msg in messages:
132
+ if msg.get("role") == "user" and msg.get("content"):
133
+ user_message = msg["content"]
134
+ break
135
+
136
+ if not user_message:
137
+ return None
138
+
139
+ # Patterns for user IDs
140
+ patterns = [
141
+ (r'(?:user[_\s]?id|id[_\s]?usuario|cpf|documento)[:\s]+([\d\.\-\/]+)', lambda m: re.sub(r'\D', '', m.group(1))),
142
+ (r'([\d]{11})', lambda m: m.group(1)), # CPF-like (11 digits)
143
+ (r'([\d]{14})', lambda m: m.group(1)), # CNPJ-like (14 digits)
144
+ ]
145
+
146
+ for pattern, extractor in patterns:
147
+ match = re.search(pattern, user_message, re.IGNORECASE)
148
+ if match:
149
+ return extractor(match)
150
+
151
+ return None
152
+
153
+
154
+ def load_user_from_csv(
155
+ csv_path: Path,
156
+ identifier_field: str,
157
+ identifier_value: str,
158
+ ) -> Optional[Dict[str, Any]]:
159
+ """
160
+ Load user data from a CSV file.
161
+
162
+ Args:
163
+ csv_path: Path to the CSV file.
164
+ identifier_field: Name of the column to search (e.g., "email", "telefone", "cpf").
165
+ identifier_value: Value to search for in the identifier_field column.
166
+
167
+ Returns:
168
+ Dictionary with user data if found, None otherwise.
169
+
170
+ Example:
171
+ >>> from pathlib import Path
172
+ >>> user_data = load_user_from_csv(
173
+ ... Path("users.csv"),
174
+ ... "email",
175
+ ... "joao@example.com"
176
+ ... )
177
+ >>> if user_data:
178
+ ... print(user_data["nome"])
179
+ """
180
+ if not csv_path.exists():
181
+ return None
182
+
183
+ try:
184
+ with open(csv_path, 'r', encoding='utf-8') as f:
185
+ reader = csv.DictReader(f)
186
+
187
+ # Normalize identifier value (remove formatting)
188
+ normalized_value = re.sub(r'\D', '', str(identifier_value).lower())
189
+
190
+ for row in reader:
191
+ # Get value from identifier field
192
+ field_value = row.get(identifier_field, '')
193
+ # Normalize for comparison
194
+ normalized_field = re.sub(r'\D', '', str(field_value).lower())
195
+
196
+ if normalized_field == normalized_value:
197
+ # Return all row data as dict
198
+ return dict(row)
199
+
200
+ except Exception:
201
+ return None
202
+
203
+ return None
204
+
205
+
206
+ def create_user_loader(
207
+ loader_func: Callable[[str], Optional[Dict[str, Any]]],
208
+ identifier_extractor: Optional[Callable[[List[Dict[str, Any]]], Optional[str]]] = None,
209
+ ) -> Callable[[List[Dict[str, Any]]], Optional[UserContext]]:
210
+ """
211
+ Create a user loader function.
212
+
213
+ Factory function that creates a callable that extracts user identifier from
214
+ messages and loads user data using the provided loader function.
215
+
216
+ Args:
217
+ loader_func: Function that receives an identifier (str) and returns
218
+ user data dict or None if not found.
219
+ identifier_extractor: Optional function to extract identifier from messages.
220
+ If None, tries common extractors (phone, email, user_id).
221
+
222
+ Returns:
223
+ Function that receives messages and returns UserContext or None.
224
+
225
+ Example:
226
+ >>> def load_from_db(identifier: str) -> Optional[Dict]:
227
+ ... # Your database lookup logic
228
+ ... return {"user_id": "123", "role": "cliente", "nome": "João"}
229
+ ...
230
+ >>> loader = create_user_loader(
231
+ ... loader_func=load_from_db,
232
+ ... identifier_extractor=extract_email_from_messages
233
+ ... )
234
+ >>>
235
+ >>> messages = [{"role": "user", "content": "joao@example.com"}]
236
+ >>> user_context = loader(messages)
237
+ >>> if user_context:
238
+ ... print(user_context.user_id)
239
+ """
240
+ def load_user(messages: List[Dict[str, Any]]) -> Optional[UserContext]:
241
+ """
242
+ Load user context from messages.
243
+
244
+ Args:
245
+ messages: List of message dictionaries.
246
+
247
+ Returns:
248
+ UserContext if user found, None otherwise.
249
+ """
250
+ # Extract identifier
251
+ identifier = None
252
+ if identifier_extractor:
253
+ identifier = identifier_extractor(messages)
254
+ else:
255
+ # Try common extractors in order
256
+ identifier = (
257
+ extract_phone_from_messages(messages) or
258
+ extract_email_from_messages(messages) or
259
+ extract_user_id_from_messages(messages)
260
+ )
261
+
262
+ if not identifier:
263
+ return None
264
+
265
+ # Load user data
266
+ user_data = loader_func(identifier)
267
+ if not user_data:
268
+ return None
269
+
270
+ # Create UserContext
271
+ return UserContext(
272
+ user_id=user_data.get("user_id") or identifier,
273
+ role=user_data.get("role"),
274
+ metadata={
275
+ **user_data, # Include all user data in metadata
276
+ "loaded_from": "user_loader",
277
+ }
278
+ )
279
+
280
+ return load_user
281
+
282
+
283
+ async def run_with_user_context(
284
+ network: Any,
285
+ agent: Any,
286
+ messages: List[Dict[str, Any]],
287
+ ) -> Any:
288
+ """
289
+ Run agent with automatic user context loading.
290
+
291
+ This function automatically loads user data if a user_loader is configured
292
+ in the network, enriches the network's loaded_user_context, and then runs
293
+ the agent normally.
294
+
295
+ Args:
296
+ network: AgentNetwork instance with optional user_loader configured.
297
+ agent: Agent instance to run.
298
+ messages: List of message dictionaries.
299
+
300
+ Returns:
301
+ RunResult from the agent execution.
302
+
303
+ Example:
304
+ >>> from agents import Runner
305
+ >>> from atendentepro import create_standard_network, create_user_loader
306
+ >>> from pathlib import Path
307
+ >>>
308
+ >>> def load_user(identifier: str):
309
+ ... # Your loading logic
310
+ ... return {"user_id": identifier, "role": "cliente"}
311
+ >>>
312
+ >>> loader = create_user_loader(load_user)
313
+ >>> network = create_standard_network(
314
+ ... templates_root=Path("templates"),
315
+ ... user_loader=loader
316
+ ... )
317
+ >>>
318
+ >>> messages = [{"role": "user", "content": "Olá!"}]
319
+ >>> result = await run_with_user_context(network, network.triage, messages)
320
+ >>> print(result.final_output)
321
+ """
322
+ from agents import Runner
323
+
324
+ # Load user if loader is configured
325
+ if hasattr(network, 'user_loader') and network.user_loader:
326
+ try:
327
+ user_context = network.user_loader(messages)
328
+ if user_context:
329
+ network.loaded_user_context = user_context
330
+ except Exception:
331
+ # Silently fail if loading fails - don't break the conversation
332
+ pass
333
+
334
+ # Run agent normally
335
+ return await Runner.run(agent, messages)
@@ -41,4 +41,5 @@ atendentepro/templates/__init__.py
41
41
  atendentepro/templates/manager.py
42
42
  atendentepro/utils/__init__.py
43
43
  atendentepro/utils/openai_client.py
44
- atendentepro/utils/tracing.py
44
+ atendentepro/utils/tracing.py
45
+ atendentepro/utils/user_loader.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atendentepro"
3
- version = "0.6.5"
3
+ version = "0.6.6"
4
4
  description = "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
  authors = [
6
6
  { name = "BeMonkAI", email = "contato@monkai.com.br" }
File without changes
File without changes
File without changes
File without changes