chatgraph 0.5.0__tar.gz → 0.5.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.5.0 → chatgraph-0.5.1}/PKG-INFO +1 -1
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/__init__.py +2 -1
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/bot/chatbot_model.py +64 -73
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/gRPC/gRPCCall.py +13 -2
- chatgraph-0.5.1/chatgraph/types/background_task.py +23 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/pyproject.toml +1 -1
- {chatgraph-0.5.0 → chatgraph-0.5.1}/LICENSE +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/README.md +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/auth/credentials.py +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/bot/chatbot_router.py +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/cli/__init__.py +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/error/chatbot_error.py +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/error/route_error.py +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/messages/message_consumer.py +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/pb/router.proto +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/pb/router_pb2.py +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/pb/router_pb2_grpc.py +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/types/end_types.py +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/types/message_types.py +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/types/request_types.py +0 -0
- {chatgraph-0.5.0 → chatgraph-0.5.1}/chatgraph/types/route.py +0 -0
|
@@ -6,6 +6,7 @@ from .types.request_types import UserCall, UserState, ChatID
|
|
|
6
6
|
from .types.end_types import RedirectResponse, EndChatResponse, TransferToHuman
|
|
7
7
|
from .types.message_types import Message, Button
|
|
8
8
|
from .types.route import Route
|
|
9
|
+
from .types.background_task import BackgroundTask
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
11
12
|
'ChatbotApp',
|
|
@@ -22,5 +23,5 @@ __all__ = [
|
|
|
22
23
|
'UserState',
|
|
23
24
|
'Message',
|
|
24
25
|
'Button',
|
|
25
|
-
|
|
26
|
+
'BackgroundTask',
|
|
26
27
|
]
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from functools import wraps
|
|
3
|
-
from logging import debug
|
|
4
|
-
import json
|
|
3
|
+
from logging import debug, error
|
|
5
4
|
import asyncio
|
|
6
|
-
from logging import error
|
|
7
5
|
|
|
8
6
|
from ..error.chatbot_error import ChatbotMessageError
|
|
9
7
|
from ..messages.message_consumer import MessageConsumer
|
|
@@ -28,20 +26,16 @@ class ChatbotApp:
|
|
|
28
26
|
"""
|
|
29
27
|
if not message_consumer:
|
|
30
28
|
message_consumer = MessageConsumer.load_dotenv()
|
|
31
|
-
|
|
29
|
+
|
|
32
30
|
self.__message_consumer = message_consumer
|
|
33
31
|
self.__routes = {}
|
|
34
|
-
|
|
32
|
+
|
|
35
33
|
def include_router(self, router: ChatbotRouter):
|
|
36
34
|
"""
|
|
37
35
|
Inclui um roteador de chatbot com um prefixo nas rotas da aplicação.
|
|
38
36
|
|
|
39
37
|
Args:
|
|
40
38
|
router (ChatbotRouter): O roteador contendo as rotas a serem adicionadas.
|
|
41
|
-
prefix (str): O prefixo a ser adicionado às rotas do roteador.
|
|
42
|
-
|
|
43
|
-
Raises:
|
|
44
|
-
ChatbotError: Se a rota 'start' não for encontrada no roteador.
|
|
45
39
|
"""
|
|
46
40
|
self.__routes.update(router.routes)
|
|
47
41
|
|
|
@@ -58,7 +52,7 @@ class ChatbotApp:
|
|
|
58
52
|
route_name = route_name.strip().lower()
|
|
59
53
|
|
|
60
54
|
def decorator(func):
|
|
61
|
-
params =
|
|
55
|
+
params = {}
|
|
62
56
|
signature = inspect.signature(func)
|
|
63
57
|
output_param = signature.return_annotation
|
|
64
58
|
|
|
@@ -66,20 +60,20 @@ class ChatbotApp:
|
|
|
66
60
|
param_type = (
|
|
67
61
|
param.annotation
|
|
68
62
|
if param.annotation != inspect.Parameter.empty
|
|
69
|
-
else
|
|
63
|
+
else "Any"
|
|
70
64
|
)
|
|
71
65
|
params[param_type] = name
|
|
72
|
-
debug(f
|
|
66
|
+
debug(f"Parameter: {name}, Type: {param_type}")
|
|
73
67
|
|
|
74
68
|
self.__routes[route_name] = {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
"function": func,
|
|
70
|
+
"params": params,
|
|
71
|
+
"return": output_param,
|
|
78
72
|
}
|
|
79
73
|
|
|
80
74
|
@wraps(func)
|
|
81
|
-
def wrapper(*args, **kwargs):
|
|
82
|
-
return func(*args, **kwargs)
|
|
75
|
+
async def wrapper(*args, **kwargs):
|
|
76
|
+
return await func(*args, **kwargs)
|
|
83
77
|
|
|
84
78
|
return wrapper
|
|
85
79
|
|
|
@@ -89,106 +83,103 @@ class ChatbotApp:
|
|
|
89
83
|
"""
|
|
90
84
|
Inicia o consumo de mensagens pelo chatbot, processando cada mensagem recebida.
|
|
91
85
|
"""
|
|
92
|
-
|
|
93
|
-
|
|
94
86
|
self.__message_consumer.reprer()
|
|
95
87
|
asyncio.run(self.__message_consumer.start_consume(self.process_message))
|
|
96
|
-
|
|
97
|
-
def process_message(self, userCall: UserCall):
|
|
88
|
+
|
|
89
|
+
async def process_message(self, userCall: UserCall):
|
|
98
90
|
"""
|
|
99
91
|
Processa uma mensagem recebida, identificando a rota correspondente e executando a função associada.
|
|
100
92
|
|
|
101
93
|
Args:
|
|
102
|
-
userCall (
|
|
94
|
+
userCall (UserCall): A mensagem a ser processada.
|
|
103
95
|
|
|
104
96
|
Raises:
|
|
105
97
|
ChatbotMessageError: Se nenhuma rota for encontrada para o menu atual do usuário.
|
|
106
|
-
ChatbotError: Se o tipo de retorno da função associada à rota for inválido.
|
|
107
|
-
|
|
108
|
-
Returns:
|
|
109
|
-
str: A resposta gerada pela função da rota, que pode ser uma mensagem ou o resultado de uma redireção.
|
|
110
98
|
"""
|
|
111
99
|
user_id = userCall.user_id
|
|
112
100
|
route = userCall.route.lower()
|
|
113
|
-
route_handler = route.split(
|
|
114
|
-
|
|
115
|
-
observation = userCall.observation
|
|
101
|
+
route_handler = route.split(".")[-1]
|
|
102
|
+
|
|
116
103
|
handler = self.__routes.get(route_handler, None)
|
|
117
104
|
|
|
118
105
|
if not handler:
|
|
119
|
-
raise ChatbotMessageError(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
userCall_name = handler['params'].get(UserCall, None)
|
|
125
|
-
route_state_name = handler['params'].get(Route, None)
|
|
106
|
+
raise ChatbotMessageError(user_id, f"Rota não encontrada para {route}!")
|
|
107
|
+
|
|
108
|
+
func = handler["function"]
|
|
109
|
+
userCall_name = handler["params"].get(UserCall, None)
|
|
110
|
+
route_state_name = handler["params"].get(Route, None)
|
|
126
111
|
|
|
127
|
-
kwargs =
|
|
112
|
+
kwargs = {}
|
|
128
113
|
if userCall_name:
|
|
129
114
|
kwargs[userCall_name] = userCall
|
|
130
115
|
if route_state_name:
|
|
131
116
|
kwargs[route_state_name] = Route(route, list(self.__routes.keys()))
|
|
132
117
|
|
|
133
|
-
|
|
134
|
-
|
|
118
|
+
if asyncio.iscoroutinefunction(func):
|
|
119
|
+
userCall_response = await func(**kwargs)
|
|
120
|
+
else:
|
|
121
|
+
loop = asyncio.get_running_loop()
|
|
122
|
+
userCall_response = await loop.run_in_executor(None, lambda: func(**kwargs))
|
|
123
|
+
|
|
135
124
|
if isinstance(userCall_response, (list, tuple)):
|
|
136
125
|
for response in userCall_response:
|
|
137
|
-
self.__process_func_response(response, userCall, route=route)
|
|
126
|
+
await self.__process_func_response(response, userCall, route=route)
|
|
138
127
|
else:
|
|
139
|
-
self.__process_func_response(userCall_response, userCall, route=route)
|
|
128
|
+
await self.__process_func_response(userCall_response, userCall, route=route)
|
|
129
|
+
|
|
130
|
+
async def __process_func_response(
|
|
131
|
+
self, userCall_response, userCall: UserCall, route: str
|
|
132
|
+
):
|
|
133
|
+
"""
|
|
134
|
+
Processa a resposta de uma função associada a uma rota, enviando mensagens ou ajustando estados.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
userCall_response: A resposta gerada pela função da rota.
|
|
138
|
+
userCall (UserCall): O objeto UserCall associado à mensagem processada.
|
|
139
|
+
route (str): O nome da rota atual.
|
|
140
|
+
"""
|
|
141
|
+
loop = asyncio.get_running_loop()
|
|
140
142
|
|
|
141
|
-
def __process_func_response(self, userCall_response, userCall: UserCall, route: str):
|
|
142
|
-
|
|
143
143
|
if isinstance(userCall_response, (str, float, int)):
|
|
144
|
-
|
|
144
|
+
# Envia o resultado como mensagem (executando a chamada síncrona no executor)
|
|
145
|
+
await loop.run_in_executor(None, userCall.send, Message(userCall_response))
|
|
145
146
|
return
|
|
146
147
|
|
|
147
148
|
elif isinstance(userCall_response, Route):
|
|
148
149
|
userCall.route = userCall_response.current
|
|
149
150
|
return
|
|
150
|
-
|
|
151
|
+
|
|
151
152
|
elif isinstance(userCall_response, (Message, Button)):
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
# Envia o objeto Message ou Button
|
|
154
|
+
await loop.run_in_executor(None, userCall.send, userCall_response)
|
|
154
155
|
return
|
|
155
156
|
|
|
156
157
|
elif isinstance(userCall_response, EndChatResponse):
|
|
157
|
-
|
|
158
|
+
await loop.run_in_executor(
|
|
159
|
+
None,
|
|
160
|
+
userCall.end_chat,
|
|
161
|
+
userCall_response.observations,
|
|
162
|
+
userCall_response.tabulation_id,
|
|
163
|
+
)
|
|
158
164
|
return
|
|
159
|
-
|
|
165
|
+
|
|
160
166
|
elif isinstance(userCall_response, TransferToHuman):
|
|
161
|
-
|
|
167
|
+
await loop.run_in_executor(
|
|
168
|
+
None,
|
|
169
|
+
userCall.transfer_to_human,
|
|
170
|
+
userCall_response.observations,
|
|
171
|
+
userCall_response.campaign_id,
|
|
172
|
+
)
|
|
162
173
|
return
|
|
163
174
|
|
|
164
175
|
elif isinstance(userCall_response, RedirectResponse):
|
|
165
|
-
route = route +
|
|
176
|
+
route = route + "." + userCall_response.route
|
|
166
177
|
userCall.route = route
|
|
167
|
-
|
|
178
|
+
await self.process_message(userCall)
|
|
168
179
|
|
|
169
180
|
elif not userCall_response:
|
|
170
181
|
return
|
|
171
182
|
|
|
172
183
|
else:
|
|
173
|
-
error(
|
|
184
|
+
error("Tipo de retorno inválido!")
|
|
174
185
|
return None
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def __adjust_route(self, route: str, absolute_route: str) -> str:
|
|
178
|
-
"""
|
|
179
|
-
Ajusta a rota fornecida para incluir o prefixo necessário, se não estiver presente.
|
|
180
|
-
|
|
181
|
-
Args:
|
|
182
|
-
route (str): A rota que precisa ser ajustada.
|
|
183
|
-
absolute_route (str): A rota completa atual, usada como referência.
|
|
184
|
-
|
|
185
|
-
Returns:
|
|
186
|
-
str: A rota ajustada.
|
|
187
|
-
"""
|
|
188
|
-
if not route:
|
|
189
|
-
return absolute_route
|
|
190
|
-
|
|
191
|
-
if 'start' not in route:
|
|
192
|
-
route = absolute_route + route
|
|
193
|
-
|
|
194
|
-
return route
|
|
@@ -26,6 +26,8 @@ class RouterServiceClient:
|
|
|
26
26
|
request = chatbot_pb2.UserState(**user_state_data)
|
|
27
27
|
try:
|
|
28
28
|
response = self.user_state_stub.InsertUpdateUserState(request)
|
|
29
|
+
if not response.status:
|
|
30
|
+
print(f"Erro ao chamar SendMessage: {response.message}")
|
|
29
31
|
return response
|
|
30
32
|
except grpc.RpcError as e:
|
|
31
33
|
print(f"Erro ao chamar InsertUpdateUserState: {e}")
|
|
@@ -35,6 +37,8 @@ class RouterServiceClient:
|
|
|
35
37
|
request = chatbot_pb2.ChatID(**chat_id_data)
|
|
36
38
|
try:
|
|
37
39
|
response = self.user_state_stub.DeleteUserState(request)
|
|
40
|
+
if not response.status:
|
|
41
|
+
print(f"Erro ao chamar SendMessage: {response.message}")
|
|
38
42
|
return response
|
|
39
43
|
except grpc.RpcError as e:
|
|
40
44
|
print(f"Erro ao chamar DeleteUserState: {e}")
|
|
@@ -50,12 +54,14 @@ class RouterServiceClient:
|
|
|
50
54
|
return None
|
|
51
55
|
|
|
52
56
|
def send_message(self, message_data):
|
|
53
|
-
print(json.dumps(message_data))
|
|
57
|
+
# print(json.dumps(message_data))
|
|
54
58
|
|
|
55
59
|
request = chatbot_pb2.Message(**message_data)
|
|
56
60
|
|
|
57
61
|
try:
|
|
58
62
|
response = self.send_message_stub.SendMessage(request)
|
|
63
|
+
if not response.status:
|
|
64
|
+
print(f"Erro ao chamar SendMessage: {response.message}")
|
|
59
65
|
return response
|
|
60
66
|
except grpc.RpcError as e:
|
|
61
67
|
print(f"Erro ao chamar SendMessage: {e}")
|
|
@@ -65,6 +71,8 @@ class RouterServiceClient:
|
|
|
65
71
|
request = chatbot_pb2.TransferToHumanRequest(**transfer_request_data)
|
|
66
72
|
try:
|
|
67
73
|
response = self.transfer_stub.TransferToHuman(request)
|
|
74
|
+
if not response.status:
|
|
75
|
+
print(f"Erro ao chamar SendMessage: {response.message}")
|
|
68
76
|
return response
|
|
69
77
|
except grpc.RpcError as e:
|
|
70
78
|
print(f"Erro ao chamar TransferToHuman: {e}")
|
|
@@ -74,6 +82,8 @@ class RouterServiceClient:
|
|
|
74
82
|
request = chatbot_pb2.TransferToMenuRequest(**transfer_request_data)
|
|
75
83
|
try:
|
|
76
84
|
response = self.transfer_stub.TransferToMenu(request)
|
|
85
|
+
if not response.status:
|
|
86
|
+
print(f"Erro ao chamar SendMessage: {response.message}")
|
|
77
87
|
return response
|
|
78
88
|
except grpc.RpcError as e:
|
|
79
89
|
print(f"Erro ao chamar TransferToMenu: {e}")
|
|
@@ -83,6 +93,8 @@ class RouterServiceClient:
|
|
|
83
93
|
request = chatbot_pb2.EndChatRequest(**end_chat_request_data)
|
|
84
94
|
try:
|
|
85
95
|
response = self.end_chat_stub.EndChat(request)
|
|
96
|
+
if not response.status:
|
|
97
|
+
print(f"Erro ao chamar SendMessage: {response.message}")
|
|
86
98
|
return response
|
|
87
99
|
except grpc.RpcError as e:
|
|
88
100
|
print(f"Erro ao chamar EndChat: {e}")
|
|
@@ -92,7 +104,6 @@ class RouterServiceClient:
|
|
|
92
104
|
request = chatbot_pb2.CampaignName(**campaign_name)
|
|
93
105
|
try:
|
|
94
106
|
response = self.transfer_stub.GetCampaignID(request)
|
|
95
|
-
print(response)
|
|
96
107
|
return response
|
|
97
108
|
except grpc.RpcError as e:
|
|
98
109
|
print(f"Erro ao chamar GetCampaignID: {e}")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Any, Callable
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
def BackgroundTask(func: Callable, *args: Any, **kwargs: Any) -> None:
|
|
6
|
+
"""
|
|
7
|
+
Inicia uma função de forma assíncrona em segundo plano e printa sua saída.
|
|
8
|
+
|
|
9
|
+
:param func: Função assíncrona a ser executada.
|
|
10
|
+
:param args: Argumentos posicionais para a função.
|
|
11
|
+
:param kwargs: Argumentos nomeados para a função.
|
|
12
|
+
"""
|
|
13
|
+
if asyncio.iscoroutinefunction(func):
|
|
14
|
+
def start_loop():
|
|
15
|
+
loop = asyncio.new_event_loop()
|
|
16
|
+
asyncio.set_event_loop(loop)
|
|
17
|
+
loop.run_until_complete(func(*args, **kwargs))
|
|
18
|
+
|
|
19
|
+
# Executa o loop de eventos em uma thread separada
|
|
20
|
+
thread = threading.Thread(target=start_loop)
|
|
21
|
+
thread.start()
|
|
22
|
+
else:
|
|
23
|
+
raise TypeError("A função fornecida deve ser uma coroutine (async def).")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|