chatgraph 0.1.1__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.
Potentially problematic release.
This version of chatgraph might be problematic. Click here for more details.
- chatgraph-0.1.1/LICENSE +21 -0
- chatgraph-0.1.1/PKG-INFO +142 -0
- chatgraph-0.1.1/README.md +125 -0
- chatgraph-0.1.1/chatgraph/__init__.py +20 -0
- chatgraph-0.1.1/chatgraph/auth/credentials.py +71 -0
- chatgraph-0.1.1/chatgraph/bot/chatbot_model.py +171 -0
- chatgraph-0.1.1/chatgraph/bot/chatbot_router.py +85 -0
- chatgraph-0.1.1/chatgraph/error/chatbot_error.py +57 -0
- chatgraph-0.1.1/chatgraph/error/route_error.py +26 -0
- chatgraph-0.1.1/chatgraph/messages/base_message_consumer.py +33 -0
- chatgraph-0.1.1/chatgraph/messages/rabbitMQ_message_consumer.py +172 -0
- chatgraph-0.1.1/chatgraph/types/message_types.py +25 -0
- chatgraph-0.1.1/chatgraph/types/output_state.py +42 -0
- chatgraph-0.1.1/chatgraph/types/route.py +75 -0
- chatgraph-0.1.1/chatgraph/types/user_state.py +74 -0
- chatgraph-0.1.1/pyproject.toml +46 -0
chatgraph-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Irisson Lima
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
chatgraph-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: chatgraph
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A user-friendly chatbot library
|
|
5
|
+
Home-page: https://github.com/irissonnlima/chatgraph
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: chatbot,rabbitmq,messaging,routing,chatgraph,python
|
|
8
|
+
Author: Irisson N. Lima
|
|
9
|
+
Author-email: irisson.lima@verdecard.com.br
|
|
10
|
+
Requires-Python: >=3.12,<4.0
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: pika (>=1.3.2,<2.0.0)
|
|
15
|
+
Project-URL: Repository, https://github.com/irissonnlima/chatgraph
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# ChatGraph
|
|
19
|
+
|
|
20
|
+
**ChatGraph** é uma biblioteca Python projetada para facilitar a construção e gerenciamento de chatbots com roteamento flexível.
|
|
21
|
+
|
|
22
|
+
Ela oferece uma estrutura para definir rotas de chatbot, processar mensagens e gerenciar o estado do usuário de maneira eficiente.
|
|
23
|
+
|
|
24
|
+
## Instalação
|
|
25
|
+
|
|
26
|
+
Você pode instalar o ChatGraph diretamente do PyPI:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install chatgraph
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Link do PyPI: [ChatGraph no PyPI](https://pypi.org/project/chatgraph/)
|
|
33
|
+
|
|
34
|
+
## Estrutura da Biblioteca
|
|
35
|
+
|
|
36
|
+
### 1. `ChatbotApp`
|
|
37
|
+
|
|
38
|
+
A classe `ChatbotApp` é o núcleo da aplicação do chatbot. Ela gerencia as rotas definidas, processa as mensagens recebidas e lida com a lógica de navegação dentro do chatbot.
|
|
39
|
+
|
|
40
|
+
- **Métodos principais:**
|
|
41
|
+
- `include_router`: Inclui um conjunto de rotas (definido por `ChatbotRouter`) na aplicação.
|
|
42
|
+
- `route`: Decorador para associar funções a rotas específicas.
|
|
43
|
+
- `start`: Inicia o consumo de mensagens do RabbitMQ.
|
|
44
|
+
- `process_message`: Processa a mensagem recebida e executa a função correspondente à rota atual do usuário.
|
|
45
|
+
|
|
46
|
+
### 2. `ChatbotRouter`
|
|
47
|
+
|
|
48
|
+
A classe `ChatbotRouter` permite definir e agrupar rotas que podem ser facilmente incluídas na aplicação principal do chatbot.
|
|
49
|
+
|
|
50
|
+
- **Métodos principais:**
|
|
51
|
+
- `route`: Decorador para adicionar uma função como uma rota no roteador.
|
|
52
|
+
- `include_router`: Inclui outro roteador dentro do roteador atual, permitindo a construção modular das rotas.
|
|
53
|
+
|
|
54
|
+
### 3. `MessageConsumer` e `RabbitMessageConsumer`
|
|
55
|
+
|
|
56
|
+
A classe abstrata `MessageConsumer` define a interface para consumidores de mensagens no sistema do chatbot. A implementação concreta `RabbitMessageConsumer` consome mensagens de uma fila RabbitMQ, processa essas mensagens e envia respostas de acordo.
|
|
57
|
+
|
|
58
|
+
- **Métodos principais:**
|
|
59
|
+
- `start_consume`: Inicia o consumo de mensagens do RabbitMQ.
|
|
60
|
+
- `on_request`: Processa uma mensagem recebida e envia a resposta correspondente.
|
|
61
|
+
- `load_dotenv`: Carrega as configurações do RabbitMQ a partir de variáveis de ambiente.
|
|
62
|
+
|
|
63
|
+
### 4. `UserState` e `SimpleUserState`
|
|
64
|
+
|
|
65
|
+
A classe abstrata `UserState` define a interface para o gerenciamento do estado do usuário. A implementação `SimpleUserState` usa um dicionário em memória para armazenar o estado atual do menu para cada usuário.
|
|
66
|
+
|
|
67
|
+
- **Métodos principais:**
|
|
68
|
+
- `get_menu`: Retorna o menu atual associado a um ID de cliente.
|
|
69
|
+
- `set_menu`: Define o menu atual para um ID de cliente.
|
|
70
|
+
|
|
71
|
+
### 5. `Message`
|
|
72
|
+
|
|
73
|
+
A classe `Message` encapsula os dados de uma mensagem enviada ou recebida pelo chatbot.
|
|
74
|
+
|
|
75
|
+
- **Atributos:**
|
|
76
|
+
- `type`: Tipo da mensagem (ex. texto, imagem).
|
|
77
|
+
- `text`: Conteúdo da mensagem.
|
|
78
|
+
- `customer_id`: ID do cliente que enviou ou recebeu a mensagem.
|
|
79
|
+
- `channel`: Canal de comunicação utilizado (ex. WhatsApp, SMS).
|
|
80
|
+
- `customer_phone`: Número de telefone do cliente.
|
|
81
|
+
- `company_phone`: Número de telefone da empresa.
|
|
82
|
+
- `status`: Status da mensagem (opcional).
|
|
83
|
+
|
|
84
|
+
### 6. `ChatbotResponse` e `RedirectResponse`
|
|
85
|
+
|
|
86
|
+
Essas classes são usadas para definir a resposta do chatbot após o processamento de uma mensagem.
|
|
87
|
+
|
|
88
|
+
- **`ChatbotResponse`**: Contém uma mensagem de resposta e uma rota opcional.
|
|
89
|
+
- **`RedirectResponse`**: Define uma rota para a qual o chatbot deve redirecionar o fluxo.
|
|
90
|
+
|
|
91
|
+
### 7. `Route` e `RouteError`
|
|
92
|
+
|
|
93
|
+
A classe `Route` gerencia a navegação entre diferentes partes do fluxo do chatbot, permitindo obter a rota anterior e calcular a próxima rota com base na entrada do usuário.
|
|
94
|
+
|
|
95
|
+
- **Métodos principais:**
|
|
96
|
+
- `get_previous`: Retorna o caminho anterior ao atual.
|
|
97
|
+
- `get_next`: Monta e retorna o próximo caminho com base em uma parte fornecida.
|
|
98
|
+
|
|
99
|
+
A classe `RouteError` é uma exceção personalizada usada para indicar problemas relacionados à navegação nas rotas.
|
|
100
|
+
|
|
101
|
+
## Exemplo de Uso
|
|
102
|
+
|
|
103
|
+
Aqui está um exemplo básico de como configurar e utilizar o ChatGraph:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from chatgraph import ChatbotApp, ChatbotRouter, SimpleUserState, RabbitMessageConsumer, ChatbotResponse
|
|
107
|
+
|
|
108
|
+
# Definindo rotas com ChatbotRouter
|
|
109
|
+
router = ChatbotRouter()
|
|
110
|
+
|
|
111
|
+
@router.route("START")
|
|
112
|
+
def say_hello():
|
|
113
|
+
return ChatbotResponse(message="Hello! How can I assist you today?", route="HELP")
|
|
114
|
+
|
|
115
|
+
@router.route("/HELP")
|
|
116
|
+
def provide_help():
|
|
117
|
+
return ChatbotResponse(message="Here are some things I can help with: ...")
|
|
118
|
+
|
|
119
|
+
# Configurando a aplicação do chatbot
|
|
120
|
+
user_state = SimpleUserState()
|
|
121
|
+
message_consumer = RabbitMessageConsumer.load_dotenv()
|
|
122
|
+
|
|
123
|
+
app = ChatbotApp(user_state=user_state, message_consumer=message_consumer)
|
|
124
|
+
app.include_router(router, prefix="")
|
|
125
|
+
|
|
126
|
+
# Iniciando o chatbot
|
|
127
|
+
app.start()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Neste exemplo, o chatbot responde "Hello! How can I assist you today?" quando a rota `HELLO` é acessada e depois direciona o usuário para a rota `HELP`.
|
|
131
|
+
|
|
132
|
+
## Contribuição
|
|
133
|
+
|
|
134
|
+
Se você encontrar bugs ou tiver sugestões de melhorias, sinta-se à vontade para abrir uma issue ou enviar um pull request no repositório do projeto.
|
|
135
|
+
|
|
136
|
+
## Licença
|
|
137
|
+
|
|
138
|
+
Este projeto está licenciado sob a licença MIT. Consulte o arquivo LICENSE para obter mais detalhes.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
Com **ChatGraph**, você tem a flexibilidade e a simplicidade necessárias para construir chatbots poderosos e altamente configuráveis, integrados, por enquanto, com RabbitMQ para um processamento robusto de mensagens.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# ChatGraph
|
|
2
|
+
|
|
3
|
+
**ChatGraph** é uma biblioteca Python projetada para facilitar a construção e gerenciamento de chatbots com roteamento flexível.
|
|
4
|
+
|
|
5
|
+
Ela oferece uma estrutura para definir rotas de chatbot, processar mensagens e gerenciar o estado do usuário de maneira eficiente.
|
|
6
|
+
|
|
7
|
+
## Instalação
|
|
8
|
+
|
|
9
|
+
Você pode instalar o ChatGraph diretamente do PyPI:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install chatgraph
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Link do PyPI: [ChatGraph no PyPI](https://pypi.org/project/chatgraph/)
|
|
16
|
+
|
|
17
|
+
## Estrutura da Biblioteca
|
|
18
|
+
|
|
19
|
+
### 1. `ChatbotApp`
|
|
20
|
+
|
|
21
|
+
A classe `ChatbotApp` é o núcleo da aplicação do chatbot. Ela gerencia as rotas definidas, processa as mensagens recebidas e lida com a lógica de navegação dentro do chatbot.
|
|
22
|
+
|
|
23
|
+
- **Métodos principais:**
|
|
24
|
+
- `include_router`: Inclui um conjunto de rotas (definido por `ChatbotRouter`) na aplicação.
|
|
25
|
+
- `route`: Decorador para associar funções a rotas específicas.
|
|
26
|
+
- `start`: Inicia o consumo de mensagens do RabbitMQ.
|
|
27
|
+
- `process_message`: Processa a mensagem recebida e executa a função correspondente à rota atual do usuário.
|
|
28
|
+
|
|
29
|
+
### 2. `ChatbotRouter`
|
|
30
|
+
|
|
31
|
+
A classe `ChatbotRouter` permite definir e agrupar rotas que podem ser facilmente incluídas na aplicação principal do chatbot.
|
|
32
|
+
|
|
33
|
+
- **Métodos principais:**
|
|
34
|
+
- `route`: Decorador para adicionar uma função como uma rota no roteador.
|
|
35
|
+
- `include_router`: Inclui outro roteador dentro do roteador atual, permitindo a construção modular das rotas.
|
|
36
|
+
|
|
37
|
+
### 3. `MessageConsumer` e `RabbitMessageConsumer`
|
|
38
|
+
|
|
39
|
+
A classe abstrata `MessageConsumer` define a interface para consumidores de mensagens no sistema do chatbot. A implementação concreta `RabbitMessageConsumer` consome mensagens de uma fila RabbitMQ, processa essas mensagens e envia respostas de acordo.
|
|
40
|
+
|
|
41
|
+
- **Métodos principais:**
|
|
42
|
+
- `start_consume`: Inicia o consumo de mensagens do RabbitMQ.
|
|
43
|
+
- `on_request`: Processa uma mensagem recebida e envia a resposta correspondente.
|
|
44
|
+
- `load_dotenv`: Carrega as configurações do RabbitMQ a partir de variáveis de ambiente.
|
|
45
|
+
|
|
46
|
+
### 4. `UserState` e `SimpleUserState`
|
|
47
|
+
|
|
48
|
+
A classe abstrata `UserState` define a interface para o gerenciamento do estado do usuário. A implementação `SimpleUserState` usa um dicionário em memória para armazenar o estado atual do menu para cada usuário.
|
|
49
|
+
|
|
50
|
+
- **Métodos principais:**
|
|
51
|
+
- `get_menu`: Retorna o menu atual associado a um ID de cliente.
|
|
52
|
+
- `set_menu`: Define o menu atual para um ID de cliente.
|
|
53
|
+
|
|
54
|
+
### 5. `Message`
|
|
55
|
+
|
|
56
|
+
A classe `Message` encapsula os dados de uma mensagem enviada ou recebida pelo chatbot.
|
|
57
|
+
|
|
58
|
+
- **Atributos:**
|
|
59
|
+
- `type`: Tipo da mensagem (ex. texto, imagem).
|
|
60
|
+
- `text`: Conteúdo da mensagem.
|
|
61
|
+
- `customer_id`: ID do cliente que enviou ou recebeu a mensagem.
|
|
62
|
+
- `channel`: Canal de comunicação utilizado (ex. WhatsApp, SMS).
|
|
63
|
+
- `customer_phone`: Número de telefone do cliente.
|
|
64
|
+
- `company_phone`: Número de telefone da empresa.
|
|
65
|
+
- `status`: Status da mensagem (opcional).
|
|
66
|
+
|
|
67
|
+
### 6. `ChatbotResponse` e `RedirectResponse`
|
|
68
|
+
|
|
69
|
+
Essas classes são usadas para definir a resposta do chatbot após o processamento de uma mensagem.
|
|
70
|
+
|
|
71
|
+
- **`ChatbotResponse`**: Contém uma mensagem de resposta e uma rota opcional.
|
|
72
|
+
- **`RedirectResponse`**: Define uma rota para a qual o chatbot deve redirecionar o fluxo.
|
|
73
|
+
|
|
74
|
+
### 7. `Route` e `RouteError`
|
|
75
|
+
|
|
76
|
+
A classe `Route` gerencia a navegação entre diferentes partes do fluxo do chatbot, permitindo obter a rota anterior e calcular a próxima rota com base na entrada do usuário.
|
|
77
|
+
|
|
78
|
+
- **Métodos principais:**
|
|
79
|
+
- `get_previous`: Retorna o caminho anterior ao atual.
|
|
80
|
+
- `get_next`: Monta e retorna o próximo caminho com base em uma parte fornecida.
|
|
81
|
+
|
|
82
|
+
A classe `RouteError` é uma exceção personalizada usada para indicar problemas relacionados à navegação nas rotas.
|
|
83
|
+
|
|
84
|
+
## Exemplo de Uso
|
|
85
|
+
|
|
86
|
+
Aqui está um exemplo básico de como configurar e utilizar o ChatGraph:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from chatgraph import ChatbotApp, ChatbotRouter, SimpleUserState, RabbitMessageConsumer, ChatbotResponse
|
|
90
|
+
|
|
91
|
+
# Definindo rotas com ChatbotRouter
|
|
92
|
+
router = ChatbotRouter()
|
|
93
|
+
|
|
94
|
+
@router.route("START")
|
|
95
|
+
def say_hello():
|
|
96
|
+
return ChatbotResponse(message="Hello! How can I assist you today?", route="HELP")
|
|
97
|
+
|
|
98
|
+
@router.route("/HELP")
|
|
99
|
+
def provide_help():
|
|
100
|
+
return ChatbotResponse(message="Here are some things I can help with: ...")
|
|
101
|
+
|
|
102
|
+
# Configurando a aplicação do chatbot
|
|
103
|
+
user_state = SimpleUserState()
|
|
104
|
+
message_consumer = RabbitMessageConsumer.load_dotenv()
|
|
105
|
+
|
|
106
|
+
app = ChatbotApp(user_state=user_state, message_consumer=message_consumer)
|
|
107
|
+
app.include_router(router, prefix="")
|
|
108
|
+
|
|
109
|
+
# Iniciando o chatbot
|
|
110
|
+
app.start()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Neste exemplo, o chatbot responde "Hello! How can I assist you today?" quando a rota `HELLO` é acessada e depois direciona o usuário para a rota `HELP`.
|
|
114
|
+
|
|
115
|
+
## Contribuição
|
|
116
|
+
|
|
117
|
+
Se você encontrar bugs ou tiver sugestões de melhorias, sinta-se à vontade para abrir uma issue ou enviar um pull request no repositório do projeto.
|
|
118
|
+
|
|
119
|
+
## Licença
|
|
120
|
+
|
|
121
|
+
Este projeto está licenciado sob a licença MIT. Consulte o arquivo LICENSE para obter mais detalhes.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
Com **ChatGraph**, você tem a flexibilidade e a simplicidade necessárias para construir chatbots poderosos e altamente configuráveis, integrados, por enquanto, com RabbitMQ para um processamento robusto de mensagens.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .auth.credentials import Credential
|
|
2
|
+
from .bot.chatbot_model import ChatbotApp
|
|
3
|
+
from .bot.chatbot_router import ChatbotRouter
|
|
4
|
+
from .messages.rabbitMQ_message_consumer import RabbitMessageConsumer
|
|
5
|
+
from .types.message_types import Message
|
|
6
|
+
from .types.output_state import ChatbotResponse, RedirectResponse
|
|
7
|
+
from .types.route import Route
|
|
8
|
+
from .types.user_state import SimpleUserState
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'ChatbotApp',
|
|
12
|
+
'Credential',
|
|
13
|
+
'SimpleUserState',
|
|
14
|
+
'Message',
|
|
15
|
+
'ChatbotRouter',
|
|
16
|
+
'ChatbotResponse',
|
|
17
|
+
'RedirectResponse',
|
|
18
|
+
'RabbitMessageConsumer',
|
|
19
|
+
'Route',
|
|
20
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Credential:
|
|
5
|
+
"""
|
|
6
|
+
Classe para gerenciar credenciais de usuário e senha, com suporte a variáveis de ambiente.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, username: str | None = None, password: str | None = None):
|
|
10
|
+
"""
|
|
11
|
+
Inicializa a classe Credential com um nome de usuário e senha.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
username (str | None): O nome de usuário. Pode ser None se for carregado de uma variável de ambiente.
|
|
15
|
+
password (str | None): A senha do usuário. Pode ser None se for carregada de uma variável de ambiente.
|
|
16
|
+
"""
|
|
17
|
+
self.__username = username
|
|
18
|
+
self.__password = password
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def password(self) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Retorna a senha do usuário.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
ValueError: Se a senha não estiver definida.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
str: A senha do usuário.
|
|
30
|
+
"""
|
|
31
|
+
if not self.__password:
|
|
32
|
+
raise ValueError('Senha vazia!')
|
|
33
|
+
return self.__password
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def username(self) -> str:
|
|
37
|
+
"""
|
|
38
|
+
Retorna o nome de usuário.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: Se o nome de usuário não estiver definido.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
str: O nome de usuário.
|
|
45
|
+
"""
|
|
46
|
+
if not self.__username:
|
|
47
|
+
raise ValueError('Usuário vazio!')
|
|
48
|
+
return self.__username
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def load_dotenv(cls, user_env: str = 'CHATBOT_USER', pass_env: str = 'CHATBOT_PASS') -> 'Credential':
|
|
52
|
+
"""
|
|
53
|
+
Carrega as credenciais de variáveis de ambiente e retorna uma instância da classe Credential.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
user_env (str): Nome da variável de ambiente que armazena o nome de usuário. Padrão é 'CHATBOT_USER'.
|
|
57
|
+
pass_env (str): Nome da variável de ambiente que armazena a senha. Padrão é 'CHATBOT_PASS'.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ValueError: Se o nome de usuário ou a senha não estiverem definidas nas variáveis de ambiente.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Credential: Uma instância da classe Credential com as credenciais carregadas.
|
|
64
|
+
"""
|
|
65
|
+
username = os.getenv(user_env)
|
|
66
|
+
password = os.getenv(pass_env)
|
|
67
|
+
|
|
68
|
+
if not username or not password:
|
|
69
|
+
raise ValueError('Corrija as variáveis de ambiente!')
|
|
70
|
+
|
|
71
|
+
return cls(username=username, password=password)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from logging import debug
|
|
5
|
+
|
|
6
|
+
from ..error.chatbot_error import ChatbotError, ChatbotMessageError
|
|
7
|
+
from ..messages.base_message_consumer import MessageConsumer
|
|
8
|
+
from ..types.message_types import Message
|
|
9
|
+
from ..types.output_state import ChatbotResponse, RedirectResponse
|
|
10
|
+
from ..types.route import Route
|
|
11
|
+
from ..types.user_state import UserState
|
|
12
|
+
from .chatbot_router import ChatbotRouter
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ChatbotApp(ABC):
|
|
16
|
+
"""
|
|
17
|
+
Classe principal para a aplicação do chatbot, gerencia as rotas e a lógica de processamento de mensagens.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, user_state: UserState, message_consumer: MessageConsumer):
|
|
21
|
+
"""
|
|
22
|
+
Inicializa a classe ChatbotApp com um estado de usuário e um consumidor de mensagens.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
user_state (UserState): O estado do usuário, que contém informações persistentes sobre as interações do usuário.
|
|
26
|
+
message_consumer (MessageConsumer): O consumidor de mensagens que lida com a entrada de mensagens no sistema.
|
|
27
|
+
"""
|
|
28
|
+
self.__message_consumer = message_consumer
|
|
29
|
+
self.__user_state = user_state
|
|
30
|
+
self.__routes = {}
|
|
31
|
+
|
|
32
|
+
def include_router(self, router: ChatbotRouter, prefix: str):
|
|
33
|
+
"""
|
|
34
|
+
Inclui um roteador de chatbot com um prefixo nas rotas da aplicação.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
router (ChatbotRouter): O roteador contendo as rotas a serem adicionadas.
|
|
38
|
+
prefix (str): O prefixo a ser adicionado às rotas do roteador.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ChatbotError: Se a rota 'START' não for encontrada no roteador.
|
|
42
|
+
"""
|
|
43
|
+
if 'START' not in router.routes.keys():
|
|
44
|
+
raise ChatbotError('Erro ao incluir rota, START não encontrado!')
|
|
45
|
+
|
|
46
|
+
prefixed_routes = {
|
|
47
|
+
(
|
|
48
|
+
f'START{prefix.upper()}'
|
|
49
|
+
if key.upper() == 'START'
|
|
50
|
+
else f'START{prefix.upper()}{key.upper().replace("START", "")}'
|
|
51
|
+
): value
|
|
52
|
+
for key, value in router.routes.items()
|
|
53
|
+
}
|
|
54
|
+
self.__routes.update(prefixed_routes)
|
|
55
|
+
|
|
56
|
+
def route(self, route_name: str):
|
|
57
|
+
"""
|
|
58
|
+
Decorador para adicionar uma função como uma rota na aplicação do chatbot.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
route_name (str): O nome da rota para a qual a função deve ser associada.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
function: O decorador que adiciona a função à rota especificada.
|
|
65
|
+
"""
|
|
66
|
+
route_name = route_name.strip().upper()
|
|
67
|
+
|
|
68
|
+
if 'START' not in route_name:
|
|
69
|
+
route_name = f'START{route_name}'
|
|
70
|
+
|
|
71
|
+
def decorator(func):
|
|
72
|
+
params = dict()
|
|
73
|
+
signature = inspect.signature(func)
|
|
74
|
+
output_param = signature.return_annotation
|
|
75
|
+
|
|
76
|
+
for name, param in signature.parameters.items():
|
|
77
|
+
param_type = (
|
|
78
|
+
param.annotation
|
|
79
|
+
if param.annotation != inspect.Parameter.empty
|
|
80
|
+
else 'Any'
|
|
81
|
+
)
|
|
82
|
+
params[param_type] = name
|
|
83
|
+
debug(f'Parameter: {name}, Type: {param_type}')
|
|
84
|
+
|
|
85
|
+
self.__routes[route_name] = {
|
|
86
|
+
'function': func,
|
|
87
|
+
'params': params,
|
|
88
|
+
'return': output_param,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@wraps(func)
|
|
92
|
+
def wrapper(*args, **kwargs):
|
|
93
|
+
return func(*args, **kwargs)
|
|
94
|
+
|
|
95
|
+
return wrapper
|
|
96
|
+
|
|
97
|
+
return decorator
|
|
98
|
+
|
|
99
|
+
def start(self):
|
|
100
|
+
"""
|
|
101
|
+
Inicia o consumo de mensagens pelo chatbot, processando cada mensagem recebida.
|
|
102
|
+
"""
|
|
103
|
+
self.__message_consumer.start_consume(self.process_message)
|
|
104
|
+
|
|
105
|
+
def process_message(self, message: Message):
|
|
106
|
+
"""
|
|
107
|
+
Processa uma mensagem recebida, identificando a rota correspondente e executando a função associada.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
message (Message): A mensagem a ser processada.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ChatbotMessageError: Se nenhuma rota for encontrada para o menu atual do usuário.
|
|
114
|
+
ChatbotError: Se o tipo de retorno da função associada à rota for inválido.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
str: A resposta gerada pela função da rota, que pode ser uma mensagem ou o resultado de uma redireção.
|
|
118
|
+
"""
|
|
119
|
+
customer_id = message.customer_id
|
|
120
|
+
|
|
121
|
+
menu = self.__user_state.get_menu(customer_id)
|
|
122
|
+
menu = menu.upper()
|
|
123
|
+
handler = self.__routes.get(menu, None)
|
|
124
|
+
|
|
125
|
+
if not handler:
|
|
126
|
+
raise ChatbotMessageError(
|
|
127
|
+
customer_id, f'Rota não encontrada para {menu}!'
|
|
128
|
+
)
|
|
129
|
+
func = handler['function']
|
|
130
|
+
message_name = handler['params'].get(Message, None)
|
|
131
|
+
route_state_name = handler['params'].get(Route, None)
|
|
132
|
+
|
|
133
|
+
kwargs = dict()
|
|
134
|
+
if message_name:
|
|
135
|
+
kwargs[message_name] = message
|
|
136
|
+
if route_state_name:
|
|
137
|
+
kwargs[route_state_name] = Route(menu, list(self.__routes.keys()))
|
|
138
|
+
|
|
139
|
+
message_response = func(**kwargs)
|
|
140
|
+
|
|
141
|
+
if type(message_response) in (str, float, int):
|
|
142
|
+
return message_response
|
|
143
|
+
elif type(message_response) == ChatbotResponse:
|
|
144
|
+
route = self.__adjust_route(message_response.route, menu)
|
|
145
|
+
self.__user_state.set_menu(customer_id, route)
|
|
146
|
+
return message_response.message
|
|
147
|
+
elif type(message_response) == RedirectResponse:
|
|
148
|
+
route = self.__adjust_route(message_response.route, menu)
|
|
149
|
+
self.__user_state.set_menu(customer_id, route)
|
|
150
|
+
return self.process_message(message)
|
|
151
|
+
else:
|
|
152
|
+
raise ChatbotError('Tipo de retorno inválido!')
|
|
153
|
+
|
|
154
|
+
def __adjust_route(self, route: str, absolute_route: str) -> str:
|
|
155
|
+
"""
|
|
156
|
+
Ajusta a rota fornecida para incluir o prefixo necessário, se não estiver presente.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
route (str): A rota que precisa ser ajustada.
|
|
160
|
+
absolute_route (str): A rota completa atual, usada como referência.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
str: A rota ajustada.
|
|
164
|
+
"""
|
|
165
|
+
if not route:
|
|
166
|
+
return absolute_route
|
|
167
|
+
|
|
168
|
+
if 'START' not in route:
|
|
169
|
+
route = absolute_route + route
|
|
170
|
+
|
|
171
|
+
return route
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from logging import debug
|
|
4
|
+
|
|
5
|
+
from ..error.chatbot_error import ChatbotError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ChatbotRouter:
|
|
9
|
+
"""
|
|
10
|
+
Classe responsável por gerenciar e registrar as rotas do chatbot, associando-as a funções específicas.
|
|
11
|
+
|
|
12
|
+
Atributos:
|
|
13
|
+
routes (dict): Um dicionário que armazena as rotas do chatbot e suas funções associadas.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
"""
|
|
18
|
+
Inicializa a classe ChatbotRouter com um dicionário vazio de rotas.
|
|
19
|
+
"""
|
|
20
|
+
self.routes = {}
|
|
21
|
+
|
|
22
|
+
def route(self, route_name: str):
|
|
23
|
+
"""
|
|
24
|
+
Decorador para adicionar uma função como uma rota no roteador do chatbot.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
route_name (str): O nome da rota para a qual a função deve ser associada.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
function: O decorador que adiciona a função à rota especificada.
|
|
31
|
+
"""
|
|
32
|
+
if 'START' not in route_name:
|
|
33
|
+
route_name = f'START{route_name}'
|
|
34
|
+
|
|
35
|
+
def decorator(func):
|
|
36
|
+
params = dict()
|
|
37
|
+
signature = inspect.signature(func)
|
|
38
|
+
output_param = signature.return_annotation
|
|
39
|
+
|
|
40
|
+
for name, param in signature.parameters.items():
|
|
41
|
+
param_type = (
|
|
42
|
+
param.annotation
|
|
43
|
+
if param.annotation != inspect.Parameter.empty
|
|
44
|
+
else 'Any'
|
|
45
|
+
)
|
|
46
|
+
params[param_type] = name
|
|
47
|
+
debug(f'Parameter: {name}, Type: {param_type}')
|
|
48
|
+
|
|
49
|
+
self.routes[route_name.strip().upper()] = {
|
|
50
|
+
'function': func,
|
|
51
|
+
'params': params,
|
|
52
|
+
'return': output_param,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@wraps(func)
|
|
56
|
+
def wrapper(*args, **kwargs):
|
|
57
|
+
return func(*args, **kwargs)
|
|
58
|
+
|
|
59
|
+
return wrapper
|
|
60
|
+
|
|
61
|
+
return decorator
|
|
62
|
+
|
|
63
|
+
def include_router(self, router: 'ChatbotRouter', prefix: str):
|
|
64
|
+
"""
|
|
65
|
+
Inclui outro roteador com um prefixo nas rotas do roteador atual.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
router (ChatbotRouter): O roteador contendo as rotas a serem adicionadas.
|
|
69
|
+
prefix (str): O prefixo a ser adicionado às rotas do roteador.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ChatbotError: Se a rota 'START' não for encontrada no roteador fornecido.
|
|
73
|
+
"""
|
|
74
|
+
if 'START' not in router.routes.keys():
|
|
75
|
+
raise ChatbotError('Erro ao incluir rota, START não encontrado!')
|
|
76
|
+
|
|
77
|
+
prefixed_routes = {
|
|
78
|
+
(
|
|
79
|
+
f'{prefix.upper()}'
|
|
80
|
+
if key.upper() == 'START'
|
|
81
|
+
else f'START{prefix.upper()}{key.upper().replace("START", "")}'
|
|
82
|
+
): value
|
|
83
|
+
for key, value in router.routes.items()
|
|
84
|
+
}
|
|
85
|
+
self.routes.update(prefixed_routes)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
class ChatbotError(Exception):
|
|
2
|
+
"""
|
|
3
|
+
Exceção personalizada para erros gerais relacionados ao chatbot.
|
|
4
|
+
|
|
5
|
+
Atributos:
|
|
6
|
+
message (str): A mensagem de erro descrevendo o problema.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str):
|
|
10
|
+
"""
|
|
11
|
+
Inicializa a exceção ChatbotError com uma mensagem de erro.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
message (str): A mensagem de erro descrevendo o problema.
|
|
15
|
+
"""
|
|
16
|
+
self.message = message
|
|
17
|
+
super().__init__(self.message)
|
|
18
|
+
|
|
19
|
+
def __str__(self):
|
|
20
|
+
"""
|
|
21
|
+
Retorna a representação em string da exceção ChatbotError.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
str: Uma string formatada que inclui o nome da exceção e a mensagem de erro.
|
|
25
|
+
"""
|
|
26
|
+
return f'ChatbotError: {self.message}'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ChatbotMessageError(Exception):
|
|
30
|
+
"""
|
|
31
|
+
Exceção personalizada para erros relacionados a mensagens de clientes no chatbot.
|
|
32
|
+
|
|
33
|
+
Atributos:
|
|
34
|
+
customer_id (str): O ID do cliente relacionado ao erro.
|
|
35
|
+
message (str): A mensagem de erro descrevendo o problema, incluindo o ID do cliente.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, customer_id: str, message: str):
|
|
39
|
+
"""
|
|
40
|
+
Inicializa a exceção ChatbotMessageError com um ID de cliente e uma mensagem de erro.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
customer_id (str): O ID do cliente relacionado ao erro.
|
|
44
|
+
message (str): A mensagem de erro descrevendo o problema.
|
|
45
|
+
"""
|
|
46
|
+
self.customer_id = customer_id
|
|
47
|
+
self.message = f'{message} ID recebido: {customer_id}'
|
|
48
|
+
super().__init__(self.message)
|
|
49
|
+
|
|
50
|
+
def __str__(self):
|
|
51
|
+
"""
|
|
52
|
+
Retorna a representação em string da exceção ChatbotMessageError.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
str: Uma string formatada que inclui o nome da exceção e a mensagem de erro.
|
|
56
|
+
"""
|
|
57
|
+
return f'ChatbotMessageError: {self.message}'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class RouteError(Exception):
|
|
2
|
+
"""
|
|
3
|
+
Exceção personalizada para erros relacionados a rotas no sistema do chatbot.
|
|
4
|
+
|
|
5
|
+
Atributos:
|
|
6
|
+
message (str): A mensagem de erro descrevendo o problema.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str):
|
|
10
|
+
"""
|
|
11
|
+
Inicializa a exceção RouteError com uma mensagem de erro.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
message (str): A mensagem de erro descrevendo o problema.
|
|
15
|
+
"""
|
|
16
|
+
self.message = message
|
|
17
|
+
super().__init__(self.message)
|
|
18
|
+
|
|
19
|
+
def __str__(self):
|
|
20
|
+
"""
|
|
21
|
+
Retorna a representação em string da exceção RouteError.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
str: Uma string formatada que inclui o nome da exceção e a mensagem de erro.
|
|
25
|
+
"""
|
|
26
|
+
return f'RouteError: {self.message}'
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Callable
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MessageConsumer(ABC):
|
|
6
|
+
"""
|
|
7
|
+
Classe base abstrata para consumidores de mensagens no sistema do chatbot.
|
|
8
|
+
|
|
9
|
+
Esta classe define a interface que todos os consumidores de mensagens devem implementar para serem usados no sistema do chatbot.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def start_consume(self, process_message: Callable) -> Any:
|
|
14
|
+
"""
|
|
15
|
+
Inicia o consumo de mensagens e processa cada mensagem usando a função fornecida.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
process_message (Callable): Função de callback que processa cada mensagem recebida.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Any: O resultado do processo de consumo de mensagens, dependendo da implementação concreta.
|
|
22
|
+
"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def load_dotenv(self) -> 'MessageConsumer':
|
|
27
|
+
"""
|
|
28
|
+
Carrega variáveis de ambiente para configurar o consumidor de mensagens.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
MessageConsumer: A instância do consumidor de mensagens configurado.
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from logging import debug, info
|
|
3
|
+
import os
|
|
4
|
+
import pika
|
|
5
|
+
from typing import Callable
|
|
6
|
+
from ..auth.credentials import Credential
|
|
7
|
+
from ..types.message_types import Message
|
|
8
|
+
from .base_message_consumer import MessageConsumer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RabbitMessageConsumer(MessageConsumer):
|
|
12
|
+
"""
|
|
13
|
+
Implementação de MessageConsumer para consumir mensagens de uma fila RabbitMQ.
|
|
14
|
+
|
|
15
|
+
Atributos:
|
|
16
|
+
__virtual_host (str): O host virtual usado para a conexão RabbitMQ.
|
|
17
|
+
__prefetch_count (int): O número de mensagens pré-carregadas que o consumidor pode processar.
|
|
18
|
+
__queue_consume (str): O nome da fila de consumo.
|
|
19
|
+
__amqp_url (str): A URL de conexão AMQP do RabbitMQ.
|
|
20
|
+
__credentials (pika.PlainCredentials): Credenciais do RabbitMQ para autenticação.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
credential: Credential,
|
|
26
|
+
amqp_url: str,
|
|
27
|
+
queue_consume: str,
|
|
28
|
+
prefetch_count: int = 1,
|
|
29
|
+
virtual_host: str = '/',
|
|
30
|
+
) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Inicializa o consumidor de mensagens RabbitMQ com as configurações fornecidas.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
credential (Credential): Credenciais de autenticação para o RabbitMQ.
|
|
36
|
+
amqp_url (str): A URL de conexão AMQP do RabbitMQ.
|
|
37
|
+
queue_consume (str): O nome da fila de consumo.
|
|
38
|
+
prefetch_count (int, opcional): O número de mensagens pré-carregadas. Padrão é 1.
|
|
39
|
+
virtual_host (str, opcional): O host virtual do RabbitMQ. Padrão é '/'.
|
|
40
|
+
"""
|
|
41
|
+
self.__virtual_host = virtual_host
|
|
42
|
+
self.__prefetch_count = prefetch_count
|
|
43
|
+
self.__queue_consume = queue_consume
|
|
44
|
+
self.__amqp_url = amqp_url
|
|
45
|
+
self.__credentials = pika.PlainCredentials(
|
|
46
|
+
credential.username, credential.password
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def load_dotenv(
|
|
51
|
+
cls,
|
|
52
|
+
user_env: str = 'RABBIT_USER',
|
|
53
|
+
pass_env: str = 'RABBIT_PASS',
|
|
54
|
+
uri_env: str = 'RABBIT_URI',
|
|
55
|
+
queue_env: str = 'RABBIT_QUEUE',
|
|
56
|
+
prefetch_env: str = 'RABBIT_PREFETCH',
|
|
57
|
+
vhost_env: str = 'RABBIT_VHOST',
|
|
58
|
+
) -> 'RabbitMessageConsumer':
|
|
59
|
+
"""
|
|
60
|
+
Carrega as configurações do RabbitMQ a partir de variáveis de ambiente e retorna uma instância de RabbitMessageConsumer.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
user_env (str): Nome da variável de ambiente para o usuário do RabbitMQ. Padrão é 'RABBIT_USER'.
|
|
64
|
+
pass_env (str): Nome da variável de ambiente para a senha do RabbitMQ. Padrão é 'RABBIT_PASS'.
|
|
65
|
+
uri_env (str): Nome da variável de ambiente para a URL do RabbitMQ. Padrão é 'RABBIT_URI'.
|
|
66
|
+
queue_env (str): Nome da variável de ambiente para a fila de consumo do RabbitMQ. Padrão é 'RABBIT_QUEUE'.
|
|
67
|
+
prefetch_env (str): Nome da variável de ambiente para o prefetch count. Padrão é 'RABBIT_PREFETCH'.
|
|
68
|
+
vhost_env (str): Nome da variável de ambiente para o host virtual do RabbitMQ. Padrão é 'RABBIT_VHOST'.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
ValueError: Se qualquer uma das variáveis de ambiente necessárias não estiver definida.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
RabbitMessageConsumer: Uma instância configurada do RabbitMessageConsumer.
|
|
75
|
+
"""
|
|
76
|
+
username = os.getenv(user_env)
|
|
77
|
+
password = os.getenv(pass_env)
|
|
78
|
+
url = os.getenv(uri_env)
|
|
79
|
+
queue = os.getenv(queue_env)
|
|
80
|
+
prefetch = os.getenv(prefetch_env, 1)
|
|
81
|
+
vhost = os.getenv(vhost_env, '/')
|
|
82
|
+
|
|
83
|
+
if not username or not password or not url or not queue:
|
|
84
|
+
raise ValueError('Corrija as variáveis de ambiente!')
|
|
85
|
+
|
|
86
|
+
return cls(
|
|
87
|
+
credential=Credential(username=username, password=password),
|
|
88
|
+
amqp_url=url,
|
|
89
|
+
queue_consume=queue,
|
|
90
|
+
prefetch_count=int(prefetch),
|
|
91
|
+
virtual_host=vhost,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def start_consume(self, process_message: Callable) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Inicia o consumo de mensagens da fila RabbitMQ e processa cada mensagem usando a função fornecida.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
process_message (Callable): Função de callback que processa cada mensagem recebida.
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
pika.exceptions.StreamLostError: Se a conexão com o RabbitMQ for perdida, tentará reconectar automaticamente.
|
|
103
|
+
"""
|
|
104
|
+
try:
|
|
105
|
+
connection = pika.BlockingConnection(
|
|
106
|
+
pika.ConnectionParameters(
|
|
107
|
+
host=self.__amqp_url,
|
|
108
|
+
virtual_host=self.__virtual_host,
|
|
109
|
+
credentials=self.__credentials,
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
channel = connection.channel()
|
|
113
|
+
|
|
114
|
+
channel.basic_qos(prefetch_count=self.__prefetch_count)
|
|
115
|
+
channel.basic_consume(
|
|
116
|
+
queue=self.__queue_consume,
|
|
117
|
+
on_message_callback=lambda c, m, p, b: self.on_request(
|
|
118
|
+
c, m, p, b, process_message
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
info('[x] Aguardando solicitações RPC')
|
|
123
|
+
channel.start_consuming()
|
|
124
|
+
except pika.exceptions.StreamLostError as e:
|
|
125
|
+
debug(e)
|
|
126
|
+
self.start_consume(process_message)
|
|
127
|
+
|
|
128
|
+
def on_request(self, ch, method, props, body, process_message) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Processa uma mensagem recebida e publica a resposta de volta na fila especificada.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
ch: Canal do RabbitMQ.
|
|
134
|
+
method: Método de entrega do RabbitMQ.
|
|
135
|
+
props: Propriedades da mensagem do RabbitMQ.
|
|
136
|
+
body: Corpo da mensagem recebida.
|
|
137
|
+
process_message (Callable): Função que processa a mensagem e retorna uma resposta.
|
|
138
|
+
"""
|
|
139
|
+
message = body.decode()
|
|
140
|
+
message_json = json.loads(message)
|
|
141
|
+
pure_message = self.__transform_message(message_json)
|
|
142
|
+
response = process_message(pure_message)
|
|
143
|
+
|
|
144
|
+
ch.basic_publish(
|
|
145
|
+
exchange='',
|
|
146
|
+
routing_key=props.reply_to,
|
|
147
|
+
properties=pika.BasicProperties(
|
|
148
|
+
correlation_id=props.correlation_id
|
|
149
|
+
),
|
|
150
|
+
body=str(response),
|
|
151
|
+
)
|
|
152
|
+
ch.basic_ack(delivery_tag=method.delivery_tag)
|
|
153
|
+
|
|
154
|
+
def __transform_message(self, message: dict) -> Message:
|
|
155
|
+
"""
|
|
156
|
+
Transforma o dicionário JSON recebido em uma instância de Message.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
message (dict): Dicionário contendo os dados da mensagem.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Message: Uma instância da classe Message com os dados extraídos do dicionário.
|
|
163
|
+
"""
|
|
164
|
+
return Message(
|
|
165
|
+
type=message.get('type', ''),
|
|
166
|
+
text=message.get('text', ''),
|
|
167
|
+
customer_id=message.get('customer_id', ''),
|
|
168
|
+
channel=message.get('channel', ''),
|
|
169
|
+
customer_phone=message.get('customer_phone', ''),
|
|
170
|
+
company_phone=message.get('company_phone', ''),
|
|
171
|
+
status=message.get('status'),
|
|
172
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class Message:
|
|
7
|
+
"""
|
|
8
|
+
Representa uma mensagem recebida ou enviada pelo chatbot.
|
|
9
|
+
|
|
10
|
+
Atributos:
|
|
11
|
+
type (str): O tipo da mensagem (por exemplo, texto, imagem, etc.).
|
|
12
|
+
text (str): O conteúdo textual da mensagem.
|
|
13
|
+
customer_id (str): O ID do cliente que enviou ou recebeu a mensagem.
|
|
14
|
+
channel (str): O canal pelo qual a mensagem foi enviada ou recebida (por exemplo, WhatsApp, SMS, etc.).
|
|
15
|
+
customer_phone (str): O número de telefone do cliente.
|
|
16
|
+
company_phone (str): O número de telefone da empresa que está enviando ou recebendo a mensagem.
|
|
17
|
+
status (Optional[str]): O status da mensagem (por exemplo, enviada, recebida, lida, etc.). Este campo é opcional.
|
|
18
|
+
"""
|
|
19
|
+
type: str
|
|
20
|
+
text: str
|
|
21
|
+
customer_id: str
|
|
22
|
+
channel: str
|
|
23
|
+
customer_phone: str
|
|
24
|
+
company_phone: str
|
|
25
|
+
status: Optional[str] = None
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
messageTypes = Union[str, float, int, None]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ChatbotResponse:
|
|
7
|
+
"""
|
|
8
|
+
Representa a resposta do chatbot, contendo a mensagem a ser enviada ao usuário e a rota a ser seguida.
|
|
9
|
+
|
|
10
|
+
Atributos:
|
|
11
|
+
message (messageTypes): A mensagem de resposta do chatbot. Pode ser uma string, um número, ou None.
|
|
12
|
+
route (str, opcional): A rota para a qual o chatbot deve direcionar após esta mensagem. Padrão é None.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, message: messageTypes = None, route: str = None) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Inicializa a resposta do chatbot com uma mensagem e uma rota opcional.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
message (messageTypes, opcional): A mensagem a ser enviada ao usuário. Pode ser uma string, um número, ou None.
|
|
21
|
+
route (str, opcional): A rota para a qual o chatbot deve direcionar após esta mensagem. Padrão é None.
|
|
22
|
+
"""
|
|
23
|
+
self.message = message
|
|
24
|
+
self.route = route
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RedirectResponse:
|
|
28
|
+
"""
|
|
29
|
+
Representa uma resposta que redireciona o fluxo do chatbot para uma nova rota.
|
|
30
|
+
|
|
31
|
+
Atributos:
|
|
32
|
+
route (str): A rota para a qual o chatbot deve redirecionar.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, route: str) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Inicializa a resposta de redirecionamento com a rota especificada.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
route (str): A rota para a qual o chatbot deve redirecionar.
|
|
41
|
+
"""
|
|
42
|
+
self.route = route
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from ..error.route_error import RouteError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Route:
|
|
5
|
+
"""
|
|
6
|
+
Representa uma rota no sistema do chatbot, gerenciando a navegação entre diferentes partes do fluxo.
|
|
7
|
+
|
|
8
|
+
Atributos:
|
|
9
|
+
current (str): A rota atual.
|
|
10
|
+
routes (list[str]): A lista de todas as rotas disponíveis no fluxo.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, current: str, routes: list[str]):
|
|
14
|
+
"""
|
|
15
|
+
Inicializa a rota com a rota atual e a lista de rotas disponíveis.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
current (str): A rota atual.
|
|
19
|
+
routes (list[str]): A lista de todas as rotas disponíveis no fluxo.
|
|
20
|
+
"""
|
|
21
|
+
self.current = current
|
|
22
|
+
self.routes = routes
|
|
23
|
+
|
|
24
|
+
def get_previous(self) -> str:
|
|
25
|
+
"""
|
|
26
|
+
Retorna o caminho anterior ao caminho atual.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
RouteError: Se a rota atual for 'START', indicando que não há caminho anterior.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
str: O caminho anterior à rota atual.
|
|
33
|
+
"""
|
|
34
|
+
if self.current == 'START':
|
|
35
|
+
raise RouteError('Não há caminho anterior ao START')
|
|
36
|
+
|
|
37
|
+
previous_route = '/'.join(self.current.split('/')[:-1])
|
|
38
|
+
return previous_route
|
|
39
|
+
|
|
40
|
+
def get_next(self, next_part: str) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Monta e retorna o próximo caminho com base na parte fornecida.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
next_part (str): A parte do caminho a ser adicionada à rota atual.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
RouteError: Se a próxima rota montada não estiver na lista de rotas disponíveis.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
str: O próximo caminho construído a partir da rota atual e da parte fornecida.
|
|
52
|
+
"""
|
|
53
|
+
next_part = next_part.strip().upper()
|
|
54
|
+
next_route = f"{self.current.rstrip('/')}{next_part}"
|
|
55
|
+
if next_route not in self.routes:
|
|
56
|
+
raise RouteError(f'Rota não encontrada: {next_route}')
|
|
57
|
+
return next_route
|
|
58
|
+
|
|
59
|
+
def __str__(self):
|
|
60
|
+
"""
|
|
61
|
+
Retorna uma representação em string da rota atual.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
str: A representação em string da rota atual.
|
|
65
|
+
"""
|
|
66
|
+
return f'Route(current={self.current})'
|
|
67
|
+
|
|
68
|
+
def __repr__(self):
|
|
69
|
+
"""
|
|
70
|
+
Retorna a representação oficial da rota, que é a mesma que a representação em string.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
str: A representação oficial da rota.
|
|
74
|
+
"""
|
|
75
|
+
return self.__str__()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class UserState(ABC):
|
|
5
|
+
"""
|
|
6
|
+
Classe abstrata para gerenciar o estado do usuário no fluxo do chatbot.
|
|
7
|
+
|
|
8
|
+
Esta classe define a interface para implementar o gerenciamento de estado do usuário, incluindo métodos para obter e definir o menu atual do usuário.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def get_menu(self, customer_id: str) -> str:
|
|
13
|
+
"""
|
|
14
|
+
Retorna o menu atual para o ID de cliente fornecido.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
customer_id (str): O ID do cliente.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
str: O menu atual associado ao cliente.
|
|
21
|
+
"""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def set_menu(self, customer_id: str, menu: str) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Define o menu atual para o ID de cliente fornecido.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
customer_id (str): O ID do cliente.
|
|
31
|
+
menu (str): O menu a ser definido para o cliente.
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SimpleUserState(UserState):
|
|
37
|
+
"""
|
|
38
|
+
Implementação simples de UserState que armazena o estado do usuário em um dicionário em memória.
|
|
39
|
+
|
|
40
|
+
Atributos:
|
|
41
|
+
states (dict): Dicionário que armazena o estado de menu atual para cada cliente.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self):
|
|
45
|
+
"""
|
|
46
|
+
Inicializa o estado do usuário com um dicionário vazio.
|
|
47
|
+
"""
|
|
48
|
+
self.states = {}
|
|
49
|
+
|
|
50
|
+
def get_menu(self, customer_id: str) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Retorna o menu atual para o ID de cliente fornecido. Se o cliente não tiver um menu definido, define 'START' como padrão.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
customer_id (str): O ID do cliente.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
str: O menu atual associado ao cliente.
|
|
59
|
+
"""
|
|
60
|
+
menu = self.states.get(customer_id, 'START')
|
|
61
|
+
if menu == 'START':
|
|
62
|
+
self.set_menu(customer_id, menu)
|
|
63
|
+
return menu
|
|
64
|
+
|
|
65
|
+
def set_menu(self, customer_id: str, menu: str | None = None) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Define o menu atual para o ID de cliente fornecido. Converte o nome do menu para maiúsculas.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
customer_id (str): O ID do cliente.
|
|
71
|
+
menu (str | None): O menu a ser definido para o cliente. Se None, não faz nenhuma alteração.
|
|
72
|
+
"""
|
|
73
|
+
if menu:
|
|
74
|
+
self.states[customer_id] = menu.upper()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "chatgraph"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "A user-friendly chatbot library"
|
|
5
|
+
authors = ["Irisson N. Lima <irisson.lima@verdecard.com.br>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
homepage = "https://github.com/irissonnlima/chatgraph"
|
|
8
|
+
repository = "https://github.com/irissonnlima/chatgraph"
|
|
9
|
+
keywords = ["chatbot", "rabbitmq", "messaging", "routing", "chatgraph", "python"]
|
|
10
|
+
license = "MIT"
|
|
11
|
+
|
|
12
|
+
[tool.poetry.dependencies]
|
|
13
|
+
python = "^3.12"
|
|
14
|
+
pika = "^1.3.2"
|
|
15
|
+
|
|
16
|
+
[tool.poetry.group.dev.dependencies]
|
|
17
|
+
ruff = "^0.5.1"
|
|
18
|
+
pytest = "^8.2.2"
|
|
19
|
+
pytest-cov = "^5.0.0"
|
|
20
|
+
taskipy = "^1.13.0"
|
|
21
|
+
|
|
22
|
+
[tool.pytest.ini_options]
|
|
23
|
+
pythonpath = "."
|
|
24
|
+
addopts = "-p no:warnings"
|
|
25
|
+
|
|
26
|
+
[tool.ruff]
|
|
27
|
+
line-length = 79
|
|
28
|
+
extend-exclude = ['migrations']
|
|
29
|
+
|
|
30
|
+
[tool.ruff.lint]
|
|
31
|
+
preview = true
|
|
32
|
+
select = ['I', 'F', 'E', 'W', 'PL', 'PT']
|
|
33
|
+
|
|
34
|
+
[tool.ruff.format]
|
|
35
|
+
preview = true
|
|
36
|
+
quote-style = 'single'
|
|
37
|
+
|
|
38
|
+
[tool.taskipy.tasks]
|
|
39
|
+
test = 'pytest --cov=chatgraph --cov-report=html'
|
|
40
|
+
start_cov = 'start htmlcov/index.html'
|
|
41
|
+
lint = 'ruff check . && ruff check . --diff'
|
|
42
|
+
format = 'ruff format . && ruff check . --fix'
|
|
43
|
+
|
|
44
|
+
[build-system]
|
|
45
|
+
requires = ["poetry-core"]
|
|
46
|
+
build-backend = "poetry.core.masonry.api"
|