chatgraph 1.2.2__tar.gz → 1.2.3__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.
- {chatgraph-1.2.2 → chatgraph-1.2.3}/.github/skills/chatgraph-framework/SKILL.md +23 -1
- {chatgraph-1.2.2 → chatgraph-1.2.3}/.gitignore +1 -2
- {chatgraph-1.2.2 → chatgraph-1.2.3}/PKG-INFO +1 -1
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/logger/__init__.py +1 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/logger/user_logger.py +9 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/messages/message_consumer.py +20 -23
- chatgraph-1.2.3/docs/gaps-go-to-python.md +150 -0
- chatgraph-1.2.3/docs/gaps-python-to-go.md +65 -0
- chatgraph-1.2.3/example.py +98 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/pyproject.toml +80 -80
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/unit/test_message_consumer.py +22 -23
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/unit/test_user_logger.py +34 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/uv.lock +1139 -1139
- chatgraph-1.2.2/example.py +0 -132
- {chatgraph-1.2.2 → chatgraph-1.2.3}/.env.example +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/.github/agents/SkillManager.agent.md +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/.github/agents/architect.agent.md +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/.github/agents/code-reviewer.agent.md +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/.github/agents/developer.agent.md +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/.github/copilot-instructions.md +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/.python-version +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/LICENSE +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/README.md +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/__init__.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/auth/credentials.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/bot/chatbot_model.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/bot/chatbot_router.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/bot/default_functions.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/bot/default_guard.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/cli/__init__.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/container/container.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/error/chatbot_error.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/error/route_error.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/gRPC/gRPCCall.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/logger/logger.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/models/actions.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/models/http_responses.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/models/message.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/models/userstate.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/pb/router.proto +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/pb/router_pb2.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/pb/router_pb2_grpc.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/services/__init__.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/services/router_http_client.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/types/background_task.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/types/end_types.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/types/route.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/chatgraph/types/usercall.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/example2.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/jsons/voll_return.json +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/poetry.lock +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/__init__.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/integration/__init__.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/integration/conftest.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/integration/test_router_client_integration.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/unit/__init__.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/unit/conftest.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/unit/test_default_guard.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/unit/test_guard.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/unit/test_models_actions.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/unit/test_models_message.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/unit/test_models_userstate.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/unit/test_router_http_client.py +0 -0
- {chatgraph-1.2.2 → chatgraph-1.2.3}/tests/unit/test_usercall.py +0 -0
|
@@ -169,6 +169,19 @@ menu = await usercall.get_menu(description='Suporte TI') # por descrição
|
|
|
169
169
|
# Setters diretos (síncronos — não persistem imediatamente, usam loop existente)
|
|
170
170
|
usercall.observation = {'chave': 'valor'} # dict — substitui toda a observação
|
|
171
171
|
usercall.content_message = 'nova mensagem' # str — sobrescreve o texto recebido
|
|
172
|
+
|
|
173
|
+
# Carga/atualização de dados remotos
|
|
174
|
+
identity = await usercall.load_identity() # recarrega UserIdentity da API
|
|
175
|
+
identity = await usercall.load_identity(cpf='cpf') # força busca por CPF específico
|
|
176
|
+
userstate = await usercall.load_userstate() # recarrega UserState completo da API
|
|
177
|
+
|
|
178
|
+
# Associação de CPF
|
|
179
|
+
await usercall.associate_cpf(
|
|
180
|
+
cpf='00000000000',
|
|
181
|
+
source='nome_do_menu', # origem da associação (ex: nome do menu)
|
|
182
|
+
phone='', # telefone da empresa (opcional)
|
|
183
|
+
device_id='', # ID do dispositivo (opcional)
|
|
184
|
+
)
|
|
172
185
|
```
|
|
173
186
|
|
|
174
187
|
---
|
|
@@ -295,11 +308,14 @@ file.send_type = SendType.VIDEO
|
|
|
295
308
|
| Fora de rota (startup, módulo) | `_logger = get_system_logger()` | `chatgraph_logs/system.log` |
|
|
296
309
|
|
|
297
310
|
```python
|
|
298
|
-
from chatgraph.logger import get_system_logger, set_level
|
|
311
|
+
from chatgraph.logger import get_system_logger, get_user_logger, set_level
|
|
299
312
|
|
|
300
313
|
_logger = get_system_logger()
|
|
301
314
|
_logger.info('App iniciada')
|
|
302
315
|
|
|
316
|
+
# Logger de usuário fora de uma rota (raramente necessário — prefira usercall.logger dentro de rotas)
|
|
317
|
+
user_log = get_user_logger('user123', 'empresa456')
|
|
318
|
+
|
|
303
319
|
# Alterar nível de log em runtime (afeta todos os loggers existentes)
|
|
304
320
|
set_level('DEBUG') # ou logging.DEBUG
|
|
305
321
|
# Equivalente: UserLoggerManager.set_level('DEBUG')
|
|
@@ -340,6 +356,12 @@ async def suporte(usercall: UserCall):
|
|
|
340
356
|
async def aguardar(usercall: UserCall):
|
|
341
357
|
await usercall.send(f'Você disse: {usercall.content_message}')
|
|
342
358
|
return RedirectResponse('start')
|
|
359
|
+
|
|
360
|
+
# auth_level também pode ser definido em rotas de router
|
|
361
|
+
@router.route('area_rh', auth_level='internal')
|
|
362
|
+
async def area_rh(usercall: UserCall):
|
|
363
|
+
await usercall.send(f'Olá, {usercall.user.internal.cargo}!')
|
|
364
|
+
return RedirectResponse('start')
|
|
343
365
|
```
|
|
344
366
|
|
|
345
367
|
```python
|
|
@@ -52,6 +52,15 @@ class UserLoggerManager:
|
|
|
52
52
|
cls._loggers[key] = logger
|
|
53
53
|
return logger
|
|
54
54
|
|
|
55
|
+
@classmethod
|
|
56
|
+
def remove_user_logger(cls, user_id: str, company_id: str) -> None:
|
|
57
|
+
key = f"{user_id}_{company_id}"
|
|
58
|
+
logger = cls._loggers.pop(key, None)
|
|
59
|
+
if logger:
|
|
60
|
+
for handler in logger.handlers[:]:
|
|
61
|
+
handler.close()
|
|
62
|
+
logger.removeHandler(handler)
|
|
63
|
+
|
|
55
64
|
@classmethod
|
|
56
65
|
def get_system_logger(cls) -> logging.Logger:
|
|
57
66
|
key = "chatgraph.system"
|
|
@@ -117,29 +117,22 @@ class MessageConsumer:
|
|
|
117
117
|
return f'amqp://{user}:{pwd}@{self.__amqp_url}/{vhost}'
|
|
118
118
|
|
|
119
119
|
async def __declare_queue(self, channel) -> aio_pika.abc.AbstractQueue:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
)
|
|
137
|
-
routing_key = f'chatbot.{self.__queue_consume}'
|
|
138
|
-
await queue.bind(
|
|
139
|
-
exchange=self.__virtual_host,
|
|
140
|
-
routing_key=routing_key,
|
|
141
|
-
)
|
|
142
|
-
return queue
|
|
120
|
+
arguments = {
|
|
121
|
+
'x-dead-letter-exchange': 'log_error',
|
|
122
|
+
'x-expires': 86400000,
|
|
123
|
+
'x-message-ttl': 300000,
|
|
124
|
+
}
|
|
125
|
+
queue = await channel.declare_queue(
|
|
126
|
+
self.__queue_consume,
|
|
127
|
+
durable=True,
|
|
128
|
+
arguments=arguments,
|
|
129
|
+
)
|
|
130
|
+
routing_key = f'chatbot.{self.__queue_consume}'
|
|
131
|
+
await queue.bind(
|
|
132
|
+
exchange=self.__virtual_host,
|
|
133
|
+
routing_key=routing_key,
|
|
134
|
+
)
|
|
135
|
+
return queue
|
|
143
136
|
|
|
144
137
|
async def __connect_and_consume(
|
|
145
138
|
self, amqp_url: str, process_message: Callable
|
|
@@ -175,6 +168,7 @@ class MessageConsumer:
|
|
|
175
168
|
await self.cleanup()
|
|
176
169
|
|
|
177
170
|
async def on_request(self, body: bytes, process_message: Callable):
|
|
171
|
+
pure_message = None
|
|
178
172
|
try:
|
|
179
173
|
message = body.decode()
|
|
180
174
|
message_json = json.loads(message)
|
|
@@ -182,6 +176,9 @@ class MessageConsumer:
|
|
|
182
176
|
await process_message(pure_message)
|
|
183
177
|
except Exception as e:
|
|
184
178
|
_logger.error(f'Erro ao processar mensagem: {e}')
|
|
179
|
+
finally:
|
|
180
|
+
if pure_message is not None:
|
|
181
|
+
UserLoggerManager.remove_user_logger(pure_message.user_id, pure_message.company_id)
|
|
185
182
|
|
|
186
183
|
async def __transform_message(self, message: dict) -> UserCall:
|
|
187
184
|
user_state = message.get('user_state', {})
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Lacunas: Go → Python
|
|
2
|
+
|
|
3
|
+
Funcionalidades presentes no `chatgraph-go` que **não existem** no `chatgraph` (Python).
|
|
4
|
+
Ordenadas por impacto estimado em produção.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Timeout automático de handler
|
|
9
|
+
|
|
10
|
+
**Prioridade: Alta**
|
|
11
|
+
|
|
12
|
+
No Go, cada rota possui um `TimeoutRouteOps{Duration, Route}`. Quando o handler ultrapassa a duração configurada, a execução é cancelada via `context.Context` e o usuário é redirecionado para uma rota de timeout.
|
|
13
|
+
|
|
14
|
+
```go
|
|
15
|
+
engine.RegisterRoute("slow_task", handler, chat.RouterHandlerOptions{
|
|
16
|
+
Timeout: &chat.TimeoutRouteOps{
|
|
17
|
+
Duration: 30 * time.Second,
|
|
18
|
+
Route: "timeout_route",
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**O que falta no Python:**
|
|
24
|
+
- Suporte a `timeout` por rota no decorador `@app.route()` / `@router.route()`
|
|
25
|
+
- Cancelamento do handler assíncrono quando o timeout é atingido
|
|
26
|
+
- Redirect automático para rota de fallback
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2. Loop protection
|
|
31
|
+
|
|
32
|
+
**Prioridade: Alta**
|
|
33
|
+
|
|
34
|
+
No Go, o `Engine` rastreia quantas vezes consecutivas o mesmo redirect ocorreu. Se o limite for atingido (`LoopCountRouteOps{Count, Route}`), o usuário é enviado para uma rota de fallback.
|
|
35
|
+
|
|
36
|
+
```go
|
|
37
|
+
// Padrão: 3 visitas consecutivas → redireciona para "loop_route"
|
|
38
|
+
engine.RegisterRoute("A", func(ctx *chat.Context[Obs]) chat.RouteReturn {
|
|
39
|
+
return &chat.RedirectResponse{TargetRoute: "A"} // loop detectado na 4ª vez
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**O que falta no Python:**
|
|
44
|
+
- Contador de redirects consecutivos para a mesma rota em `UserCall` / `ChatbotApp`
|
|
45
|
+
- Configuração de limite por rota ou global
|
|
46
|
+
- Redirect automático para rota de fallback ao atingir o limite
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 3. Route Triggers (regex globais por rota)
|
|
51
|
+
|
|
52
|
+
**Prioridade: Média-Alta**
|
|
53
|
+
|
|
54
|
+
No Go, `RouteTrigger{Regex, Route}` permite que qualquer mensagem que bata com um padrão seja redirecionada para uma rota específica **antes** da execução normal. Pode ser configurado globalmente no `Engine` ou por rota.
|
|
55
|
+
|
|
56
|
+
```go
|
|
57
|
+
engine := chat.NewEngine[Obs](chat.RouterHandlerOptions{
|
|
58
|
+
Triggers: []chat.RouteTrigger{
|
|
59
|
+
{Regex: `(?i)^cancelar$`, Route: "cancelar_route"},
|
|
60
|
+
{Regex: `(?i)^ajuda$`, Route: "help_route"},
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**O que existe no Python (parcial):**
|
|
66
|
+
O `ChatbotApp` já possui `DEFAULT_FUNCTION` — um dicionário `{regex: callable}` executado antes das rotas (ex: `voltar`). Porém a implementação atual executa uma função diretamente em vez de redirecionar para uma rota nomeada, e não é configurável por rota individualmente.
|
|
67
|
+
|
|
68
|
+
**O que falta:**
|
|
69
|
+
- Suporte a triggers por rota individualmente
|
|
70
|
+
- Semântica de redirect para rota nomeada (em vez de executar função inline)
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 4. Route validation
|
|
75
|
+
|
|
76
|
+
**Prioridade: Média**
|
|
77
|
+
|
|
78
|
+
No Go, `engine.ValidateRoutes()` verifica em tempo de inicialização que todas as rotas referenciadas (timeout, loop, protected, triggers) estão de fato registradas, evitando erros silenciosos em produção.
|
|
79
|
+
|
|
80
|
+
```go
|
|
81
|
+
if err := engine.ValidateRoutes(); err != nil {
|
|
82
|
+
log.Fatal(err)
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**O que falta no Python:**
|
|
87
|
+
- Método `validate_routes()` no `ChatbotApp` que levanta erro se qualquer rota referenciada (em redirects, transfers, etc.) não estiver registrada
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 5. `EngineTester` — helper de teste para handlers
|
|
92
|
+
|
|
93
|
+
**Prioridade: Média**
|
|
94
|
+
|
|
95
|
+
No Go, `EngineTester` fornece um `mockExecutor` que captura todas as ações executadas pelo handler (mensagens enviadas, observações salvas, rotas definidas, arquivos buscados/enviados) e permite validá-las em assertions.
|
|
96
|
+
|
|
97
|
+
```go
|
|
98
|
+
tester := chat.NewEngineTester[Obs](t, engine)
|
|
99
|
+
tester.Execute(
|
|
100
|
+
userState,
|
|
101
|
+
message,
|
|
102
|
+
[]chat.ExpectedAction{
|
|
103
|
+
{Type: chat.ExecSendMessage, Message: &expectedMsg},
|
|
104
|
+
{Type: chat.ExecSetRoute, Route: "next_route"},
|
|
105
|
+
},
|
|
106
|
+
expectedReturn,
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**O que falta no Python:**
|
|
111
|
+
- Classe `ChatbotTester` (ou similar) que mocka o `RouterHTTPClient` e captura chamadas em sequência
|
|
112
|
+
- Tipos `ExpectedAction` para `send_message`, `set_observation`, `set_route`, `get_file`, `upload_file`
|
|
113
|
+
- Integração com `pytest` e as fixtures existentes em `tests/unit/conftest.py`
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 6. Upload de arquivo a partir de bytes (`load_file_bytes`)
|
|
118
|
+
|
|
119
|
+
**Prioridade: Baixa**
|
|
120
|
+
|
|
121
|
+
No Go, `ctx.LoadFileBytes("nome.txt", []byte{...})` permite fazer upload de conteúdo gerado em memória sem precisar de um arquivo em disco.
|
|
122
|
+
|
|
123
|
+
**O que falta no Python:**
|
|
124
|
+
- Método equivalente em `UserCall` para upload a partir de `bytes` diretamente
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 7. Deduplicação de arquivos via SHA256
|
|
129
|
+
|
|
130
|
+
**Prioridade: Baixa**
|
|
131
|
+
|
|
132
|
+
No Go, o upload de um arquivo já enviado anteriormente retorna o registro cacheado (identificado por hash SHA256), evitando uploads duplicados.
|
|
133
|
+
|
|
134
|
+
**O que falta no Python:**
|
|
135
|
+
- Verificação de hash antes do upload em `RouterHTTPClient.upload_file()`
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 8. `DepartmentID` e `LastUpdate` em `EndChatResponse`
|
|
140
|
+
|
|
141
|
+
**Prioridade: Baixa**
|
|
142
|
+
|
|
143
|
+
O `EndAction` do Go possui dois campos extras que o `EndChatResponse` do Python não tem:
|
|
144
|
+
|
|
145
|
+
| Campo | Go (`EndAction`) | Python (`EndChatResponse`) |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `DepartmentID` | `int` | ❌ ausente |
|
|
148
|
+
| `LastUpdate` | `string` (timestamp) | ❌ ausente |
|
|
149
|
+
|
|
150
|
+
---
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Lacunas: Python → Go
|
|
2
|
+
|
|
3
|
+
Funcionalidades presentes no `chatgraph` (Python) que **não existem** no `chatgraph-go`.
|
|
4
|
+
Ordenadas por impacto estimado.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Sistema de autorização por rota (`auth_level` + `default_guard`)
|
|
9
|
+
|
|
10
|
+
**Prioridade: Alta**
|
|
11
|
+
|
|
12
|
+
No Python, cada rota pode declarar um nível de acesso mínimo. O `default_guard` verifica o `AuthLevel` do usuário e redireciona para `menu_id_positiva` se o acesso for insuficiente.
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
@app.route("dados_sensiveis", auth_level="write")
|
|
16
|
+
async def handler(usercall: UserCall):
|
|
17
|
+
...
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`AuthLevel` é um enum ordenado: `blocked < unknown < read < write`, com suporte especial para `internal` (verificação de vínculo interno).
|
|
21
|
+
|
|
22
|
+
**O que existe no Go (parcial):**
|
|
23
|
+
`ProtectedRouteOps{Route}` apenas redireciona para uma rota se o usuário "não tem acesso", mas não define o que é "ter acesso" — a lógica de verificação fica fora do framework.
|
|
24
|
+
|
|
25
|
+
**O que falta:**
|
|
26
|
+
- Enum `AuthLevel` com ordenação (`blocked < unknown < read < write`)
|
|
27
|
+
- Campo `AuthLevel` em `UserState.User.Identity`
|
|
28
|
+
- Lógica de `guard` configurável no `Engine` (equivalente ao `default_guard`)
|
|
29
|
+
- Flag `internal` em `UserState.User`
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. Logger por usuário (`UserLoggerManager`)
|
|
34
|
+
|
|
35
|
+
**Prioridade: Média**
|
|
36
|
+
|
|
37
|
+
No Python, cada `UserCall` expõe um `logger` isolado por `(user_id, company_id)`, com suporte a log em arquivo por usuário via `UserLoggerManager`.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
usercall.logger.info("Usuário entrou na rota de pedidos")
|
|
41
|
+
usercall.logger.debug(f"CPF={usercall.user.identity.cpf}")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**O que falta no Go:**
|
|
45
|
+
- Logger estruturado por usuário acessível via `ctx`
|
|
46
|
+
- Suporte a log em arquivo por usuário (ex: `chatgraph_logs/<user_id>.log`)
|
|
47
|
+
- Nível de log configurável globalmente
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 3. `user_message` em `TransferToMenu`
|
|
52
|
+
|
|
53
|
+
**Prioridade: Baixa**
|
|
54
|
+
|
|
55
|
+
No Python, `TransferToMenu` recebe um `user_message` que é enviado automaticamente após a transferência.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
return TransferToMenu(menu="menu_principal", user_message="inicio")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**O que falta no Go:**
|
|
62
|
+
- Campo `UserMessage string` na struct `TransferToMenu`
|
|
63
|
+
- Envio automático da mensagem pelo adapter após a transferência
|
|
64
|
+
|
|
65
|
+
---
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from chatgraph import (
|
|
2
|
+
ChatbotApp,
|
|
3
|
+
UserCall,
|
|
4
|
+
Route,
|
|
5
|
+
EndChatResponse,
|
|
6
|
+
RedirectResponse,
|
|
7
|
+
Message,
|
|
8
|
+
File,
|
|
9
|
+
Button,
|
|
10
|
+
User,
|
|
11
|
+
UserIdentity,
|
|
12
|
+
UserData,
|
|
13
|
+
UserInternal,
|
|
14
|
+
TextMessage,
|
|
15
|
+
UserState,
|
|
16
|
+
TransferToMenu,
|
|
17
|
+
)
|
|
18
|
+
from chatgraph.logger import get_system_logger
|
|
19
|
+
from dotenv import load_dotenv
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
load_dotenv()
|
|
24
|
+
_logger = get_system_logger()
|
|
25
|
+
_logger.info('Aplicação inicializada')
|
|
26
|
+
app = ChatbotApp()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class Teste:
|
|
31
|
+
atributo1: str
|
|
32
|
+
atributo2: int
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Rota inicial com emojis
|
|
36
|
+
@app.route('start')
|
|
37
|
+
async def start(rota: Route, usercall: UserCall):
|
|
38
|
+
usercall.logger.info('Usuário entrou na rota start')
|
|
39
|
+
welcome_message = Message(
|
|
40
|
+
'Bem-vindo ao nosso chatbot! 😊🚀\n Vou te redirecionar para uma área restrita',
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
await usercall.send(welcome_message)
|
|
44
|
+
return RedirectResponse('perguntar_cpf')
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.route('perguntar_cpf')
|
|
48
|
+
async def perguntar_cpf(rota: Route, usercall: UserCall):
|
|
49
|
+
usercall.logger.info('Usuário entrou na rota perguntar_cpf')
|
|
50
|
+
await usercall.send('Por favor, informe seu CPF:')
|
|
51
|
+
return Route('receber_cpf')
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.route('receber_cpf')
|
|
55
|
+
async def receber_cpf(rota: Route, usercall: UserCall):
|
|
56
|
+
cpf = usercall.content_message
|
|
57
|
+
usercall.logger.info(f'CPF recebido: {cpf}')
|
|
58
|
+
await usercall.add_observation({'cpf': cpf})
|
|
59
|
+
await usercall.send(f'CPF {cpf} recebido com sucesso!')
|
|
60
|
+
return RedirectResponse('area_restrita')
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.route('area_restrita', auth_level='internal/read/write')
|
|
64
|
+
async def area_restrita(usercall: UserCall):
|
|
65
|
+
usercall.logger.info('Usuário acessou área restrita')
|
|
66
|
+
await usercall.send('Você está em uma área protegida. Acesso autorizado!')
|
|
67
|
+
return RedirectResponse('choice_start')
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@app.route('enviar_btns')
|
|
71
|
+
async def enviar_btns(usercall: UserCall):
|
|
72
|
+
usercall.logger.info('Usuário entrou na rota enviar_btns')
|
|
73
|
+
buttons = [
|
|
74
|
+
Button('Reiniciar'),
|
|
75
|
+
Button('Encerrar'),
|
|
76
|
+
]
|
|
77
|
+
message = Message(text_message='Escolha uma opção:', buttons=buttons)
|
|
78
|
+
await usercall.send(message)
|
|
79
|
+
return Route('receber_btns')
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@app.route('receber_btns')
|
|
83
|
+
async def receber_btns(usercall: UserCall):
|
|
84
|
+
usercall.logger.info('Usuário entrou na rota receber_btns')
|
|
85
|
+
choice = usercall.content_message
|
|
86
|
+
|
|
87
|
+
usercall.logger.info(f'Opção escolhida: {choice}')
|
|
88
|
+
if choice == 'Reiniciar':
|
|
89
|
+
return RedirectResponse('start')
|
|
90
|
+
elif choice == 'Encerrar':
|
|
91
|
+
await usercall.send('Encerrando o chat. Até mais!')
|
|
92
|
+
return EndChatResponse('voll_ended')
|
|
93
|
+
else:
|
|
94
|
+
await usercall.send('Opção inválida. Por favor, escolha novamente.')
|
|
95
|
+
return Route('enviar_btns')
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
app.start()
|
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "chatgraph"
|
|
3
|
-
version = "1.2.
|
|
4
|
-
description = "A user-friendly chatbot library"
|
|
5
|
-
authors = [
|
|
6
|
-
{name = "Irisson N. Lima", email = "irisson.lima@verdecard.com.br"}
|
|
7
|
-
]
|
|
8
|
-
readme = "README.md"
|
|
9
|
-
requires-python = ">=3.12,<4.0"
|
|
10
|
-
keywords = ["chatbot", "rabbitmq", "messaging", "routing", "chatgraph", "python"]
|
|
11
|
-
license = {text = "MIT"}
|
|
12
|
-
dependencies = [
|
|
13
|
-
"pika>=1.3.2",
|
|
14
|
-
"rich>=13.8.1",
|
|
15
|
-
"grpcio>=1.67.0",
|
|
16
|
-
"grpcio-tools>=1.67.0",
|
|
17
|
-
"python-dotenv>=1.0.1",
|
|
18
|
-
"typer>=0.12.5",
|
|
19
|
-
"matplotlib>=3.10.0",
|
|
20
|
-
"networkx>=3.4.2",
|
|
21
|
-
"protobuf>=6.31.1",
|
|
22
|
-
"httpx>=0.28.1",
|
|
23
|
-
"aio-pika>=9.5.8",
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
dev-dependencies = [
|
|
27
|
-
"ruff>=0.5.1",
|
|
28
|
-
"pytest>=8.2.2",
|
|
29
|
-
"pytest-cov>=5.0.0",
|
|
30
|
-
"taskipy>=1.13.0",
|
|
31
|
-
"pytest-asyncio>=1.3.0",
|
|
32
|
-
"respx>=0.22.0",
|
|
33
|
-
"pytest-httpx>=0.35.0",
|
|
34
|
-
]
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
[project.urls]
|
|
38
|
-
Homepage = "https://github.com/irissonnlima/chatgraph"
|
|
39
|
-
Repository = "https://github.com/irissonnlima/chatgraph"
|
|
40
|
-
|
|
41
|
-
[project.scripts]
|
|
42
|
-
# chatgraph = "chatgraph.cli:main"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
[tool.pytest.ini_options]
|
|
46
|
-
pythonpath = "."
|
|
47
|
-
addopts = "-p no:warnings"
|
|
48
|
-
testpaths = ["tests"]
|
|
49
|
-
markers = [
|
|
50
|
-
"unit: marks tests as unit tests (fast, isolated, uses mocks)",
|
|
51
|
-
"integration: marks tests as integration tests (slow, requires external services)",
|
|
52
|
-
]
|
|
53
|
-
|
|
54
|
-
[tool.ruff]
|
|
55
|
-
line-length = 79
|
|
56
|
-
extend-exclude = ['migrations']
|
|
57
|
-
|
|
58
|
-
[tool.ruff.lint]
|
|
59
|
-
preview = true
|
|
60
|
-
select = ['I', 'F', 'E', 'W', 'PL', 'PT']
|
|
61
|
-
|
|
62
|
-
[tool.ruff.format]
|
|
63
|
-
preview = true
|
|
64
|
-
quote-style = 'single'
|
|
65
|
-
|
|
66
|
-
[tool.taskipy.tasks]
|
|
67
|
-
test = 'pytest --cov=chatgraph --cov-report=html'
|
|
68
|
-
start_cov = 'start htmlcov/index.html'
|
|
69
|
-
lint = 'ruff check . && ruff check . --diff'
|
|
70
|
-
format = 'ruff format . && ruff check . --fix'
|
|
71
|
-
|
|
72
|
-
[build-system]
|
|
73
|
-
requires = ["hatchling"]
|
|
74
|
-
build-backend = "hatchling.build"
|
|
75
|
-
|
|
76
|
-
[dependency-groups]
|
|
77
|
-
dev = [
|
|
78
|
-
"pytest>=9.0.3",
|
|
79
|
-
"ruff>=0.15.12",
|
|
80
|
-
]
|
|
1
|
+
[project]
|
|
2
|
+
name = "chatgraph"
|
|
3
|
+
version = "1.2.3"
|
|
4
|
+
description = "A user-friendly chatbot library"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Irisson N. Lima", email = "irisson.lima@verdecard.com.br"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.12,<4.0"
|
|
10
|
+
keywords = ["chatbot", "rabbitmq", "messaging", "routing", "chatgraph", "python"]
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
dependencies = [
|
|
13
|
+
"pika>=1.3.2",
|
|
14
|
+
"rich>=13.8.1",
|
|
15
|
+
"grpcio>=1.67.0",
|
|
16
|
+
"grpcio-tools>=1.67.0",
|
|
17
|
+
"python-dotenv>=1.0.1",
|
|
18
|
+
"typer>=0.12.5",
|
|
19
|
+
"matplotlib>=3.10.0",
|
|
20
|
+
"networkx>=3.4.2",
|
|
21
|
+
"protobuf>=6.31.1",
|
|
22
|
+
"httpx>=0.28.1",
|
|
23
|
+
"aio-pika>=9.5.8",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
dev-dependencies = [
|
|
27
|
+
"ruff>=0.5.1",
|
|
28
|
+
"pytest>=8.2.2",
|
|
29
|
+
"pytest-cov>=5.0.0",
|
|
30
|
+
"taskipy>=1.13.0",
|
|
31
|
+
"pytest-asyncio>=1.3.0",
|
|
32
|
+
"respx>=0.22.0",
|
|
33
|
+
"pytest-httpx>=0.35.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/irissonnlima/chatgraph"
|
|
39
|
+
Repository = "https://github.com/irissonnlima/chatgraph"
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
# chatgraph = "chatgraph.cli:main"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
pythonpath = "."
|
|
47
|
+
addopts = "-p no:warnings"
|
|
48
|
+
testpaths = ["tests"]
|
|
49
|
+
markers = [
|
|
50
|
+
"unit: marks tests as unit tests (fast, isolated, uses mocks)",
|
|
51
|
+
"integration: marks tests as integration tests (slow, requires external services)",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[tool.ruff]
|
|
55
|
+
line-length = 79
|
|
56
|
+
extend-exclude = ['migrations']
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
preview = true
|
|
60
|
+
select = ['I', 'F', 'E', 'W', 'PL', 'PT']
|
|
61
|
+
|
|
62
|
+
[tool.ruff.format]
|
|
63
|
+
preview = true
|
|
64
|
+
quote-style = 'single'
|
|
65
|
+
|
|
66
|
+
[tool.taskipy.tasks]
|
|
67
|
+
test = 'pytest --cov=chatgraph --cov-report=html'
|
|
68
|
+
start_cov = 'start htmlcov/index.html'
|
|
69
|
+
lint = 'ruff check . && ruff check . --diff'
|
|
70
|
+
format = 'ruff format . && ruff check . --fix'
|
|
71
|
+
|
|
72
|
+
[build-system]
|
|
73
|
+
requires = ["hatchling"]
|
|
74
|
+
build-backend = "hatchling.build"
|
|
75
|
+
|
|
76
|
+
[dependency-groups]
|
|
77
|
+
dev = [
|
|
78
|
+
"pytest>=9.0.3",
|
|
79
|
+
"ruff>=0.15.12",
|
|
80
|
+
]
|