chatgraph 1.3.1__tar.gz → 1.4.0__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.3.1 → chatgraph-1.4.0}/.env.example +24 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/.github/skills/chatgraph-framework/SKILL.md +201 -7
- chatgraph-1.4.0/CLAUDE.md +130 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/PKG-INFO +3 -1
- {chatgraph-1.3.1 → chatgraph-1.4.0}/PROGRESS.md +17 -4
- chatgraph-1.4.0/chatgraph/__init__.py +123 -0
- chatgraph-1.4.0/chatgraph/agent/__init__.py +28 -0
- chatgraph-1.4.0/chatgraph/agent/agent.py +539 -0
- chatgraph-1.4.0/chatgraph/agent/content.py +138 -0
- chatgraph-1.4.0/chatgraph/agent/context.py +212 -0
- chatgraph-1.4.0/chatgraph/agent/errors.py +53 -0
- chatgraph-1.4.0/chatgraph/agent/executor.py +653 -0
- chatgraph-1.4.0/chatgraph/agent/history_bridge.py +206 -0
- chatgraph-1.4.0/chatgraph/agent/openrouter.py +383 -0
- chatgraph-1.4.0/chatgraph/agent/prompts.py +306 -0
- chatgraph-1.4.0/chatgraph/agent/protocol.py +459 -0
- chatgraph-1.4.0/chatgraph/agent/schema.py +259 -0
- chatgraph-1.4.0/chatgraph/agent/types.py +108 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/bot/chatbot_model.py +81 -29
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/bot/chatbot_router.py +19 -21
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/bot/default_functions.py +0 -1
- chatgraph-1.4.0/chatgraph/bot/route_registry.py +64 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/history/__init__.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/history/entry.py +15 -1
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/history/keys.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/history/store.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/messages/__init__.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/messages/log_publisher.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/messages/message_consumer.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/__init__.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/log_envelope.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/message.py +8 -2
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/platform_state.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/types/end_types.py +6 -1
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/types/route.py +8 -3
- {chatgraph-1.3.1 → chatgraph-1.4.0}/docs/gaps-go-to-python.md +47 -13
- chatgraph-1.4.0/docs/gaps-python-to-go.md +69 -0
- chatgraph-1.4.0/examples/agent_smoke.py +259 -0
- chatgraph-1.4.0/examples/agents_bot.py +127 -0
- chatgraph-1.4.0/examples/content_insight.py +63 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/pyproject.toml +6 -1
- chatgraph-1.4.0/tests/unit/test_agent_content.py +168 -0
- chatgraph-1.4.0/tests/unit/test_agent_context.py +241 -0
- chatgraph-1.4.0/tests/unit/test_agent_core.py +352 -0
- chatgraph-1.4.0/tests/unit/test_agent_executor.py +764 -0
- chatgraph-1.4.0/tests/unit/test_agent_history_bridge.py +208 -0
- chatgraph-1.4.0/tests/unit/test_agent_openrouter.py +401 -0
- chatgraph-1.4.0/tests/unit/test_agent_prompts.py +190 -0
- chatgraph-1.4.0/tests/unit/test_agent_protocol.py +265 -0
- chatgraph-1.4.0/tests/unit/test_agent_schema.py +323 -0
- chatgraph-1.4.0/tests/unit/test_agent_validate_config.py +146 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_chatbot_app_log_publisher.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_guard.py +1 -1
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_history_integration.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_history_store.py +0 -0
- chatgraph-1.4.0/tests/unit/test_history_tool_entries.py +88 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_log_envelope.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_log_publisher.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_message_consumer_log_publisher.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_models_message.py +30 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_models_platform_state.py +0 -0
- chatgraph-1.4.0/tests/unit/test_route_registry_integration.py +236 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/uv.lock +0 -0
- chatgraph-1.3.1/chatgraph/__init__.py +0 -71
- chatgraph-1.3.1/docs/gaps-python-to-go.md +0 -65
- {chatgraph-1.3.1 → chatgraph-1.4.0}/.github/agents/SkillManager.agent.md +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/.github/agents/architect.agent.md +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/.github/agents/code-reviewer.agent.md +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/.github/agents/developer.agent.md +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/.github/copilot-instructions.md +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/.gitignore +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/.python-version +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/LICENSE +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/README.md +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/auth/credentials.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/bot/default_guard.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/cli/__init__.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/container/container.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/error/chatbot_error.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/error/route_error.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/gRPC/gRPCCall.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/logger/__init__.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/logger/logger.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/logger/user_logger.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/actions.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/http_responses.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/userstate.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/pb/router.proto +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/pb/router_pb2.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/pb/router_pb2_grpc.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/services/__init__.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/services/router_http_client.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/types/background_task.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/types/usercall.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/example.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/example2.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/jsons/voll_return.json +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/poetry.lock +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/__init__.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/integration/__init__.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/integration/conftest.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/integration/test_router_client_integration.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/__init__.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/conftest.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_default_guard.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_message_consumer.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_models_actions.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_models_userstate.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_router_http_client.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_user_logger.py +0 -0
- {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_usercall.py +0 -0
|
@@ -36,6 +36,30 @@ ROUTER_API_PASSWORD=f078b1cf-b4c8-11f0-b4fe-0242ac140002
|
|
|
36
36
|
TEST_USER_ID=6107
|
|
37
37
|
TEST_COMPANY_ID=business-tech
|
|
38
38
|
|
|
39
|
+
# ====================================
|
|
40
|
+
# Agentes de IA (extra opcional: pip install chatgraph[agent])
|
|
41
|
+
# ====================================
|
|
42
|
+
# OpenRouter (provedor de LLM)
|
|
43
|
+
OPENROUTER_API_KEY=sk-or-...
|
|
44
|
+
# OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 # opcional
|
|
45
|
+
|
|
46
|
+
# Modelo usado pelo Agent (qualquer modelo do OpenRouter)
|
|
47
|
+
AGENT_MODEL=openai/gpt-4o-mini
|
|
48
|
+
# AGENT_TEMPERATURE=0.7 # opcional (default 0.7)
|
|
49
|
+
# AGENT_MAX_TOKENS=2048 # opcional (default 2048)
|
|
50
|
+
# AGENT_MAX_TOOL_LOOPS=5 # opcional (default 5)
|
|
51
|
+
# AGENT_HISTORY_LIMIT=20 # opcional (default 20)
|
|
52
|
+
# AGENT_SYSTEM_PROMPT_FILE=prompts/personalidade.md # opcional
|
|
53
|
+
|
|
54
|
+
# Diagnóstico: loga o AgentContext COMPLETO (inclui dados do usuário
|
|
55
|
+
# na observation). Use apenas em debug local.
|
|
56
|
+
# LOG_AGENT_CONTEXT=1
|
|
57
|
+
|
|
58
|
+
# Bots com agente: um turno de LLM pode levar vários segundos; com
|
|
59
|
+
# RABBIT_PREFETCH=1 o consumer processa 1 mensagem por vez e a fila
|
|
60
|
+
# serializa. Considere aumentar (com o caveat de ordenação por chat).
|
|
61
|
+
# RABBIT_PREFETCH=4
|
|
62
|
+
|
|
39
63
|
# Log Publisher (RabbitMQ - opcional)
|
|
40
64
|
# Defina LOG_RABBIT_QUEUE para ativar o envio de logs de erro para fila RabbitMQ.
|
|
41
65
|
# O exchange e routing key padrão seguem o padrão do Go: exchange = vhost, routing key = chatbot.{queue}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: chatgraph-framework
|
|
3
|
-
description: "Use when: implementing a chatbot with chatgraph, creating routes with @app.route or @router.route, using UserCall, Route, Message, File, Button, EndChatResponse, RedirectResponse, TransferToMenu,
|
|
3
|
+
description: "Use when: implementing a chatbot with chatgraph, creating routes with @app.route or @router.route, using UserCall, Route, Message, File, Button, EndChatResponse, RedirectResponse, TransferToMenu, BackgroundTask, setting up ChatbotApp, ChatbotRouter, include_router, configuring RabbitMQ consumer, adding per-user file logging with UserLoggerManager, integrating chatgraph in a new Python project."
|
|
4
4
|
argument-hint: "Descreva o fluxo de rotas que deseja implementar (opcional)"
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -200,22 +200,28 @@ return RedirectResponse('choice_start')
|
|
|
200
200
|
return Route('aguardando_resposta')
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
-
### `EndChatResponse(end_chat_id, end_chat_name?, observations?)` — Encerra o chat
|
|
203
|
+
### `EndChatResponse(end_chat_id, end_chat_name?, observations?)` — Encerra o chat OU transfere para humano
|
|
204
|
+
No chatbot-router, encerrar o atendimento e transferir para um atendente humano são a
|
|
205
|
+
**mesma operação**: uma tabulação de encerramento com ID/nome próprio. Não existe um tipo de
|
|
206
|
+
retorno separado para "transferir para humano" — use `EndChatResponse` com a end action de
|
|
207
|
+
atendimento humano configurada no router.
|
|
204
208
|
```python
|
|
205
209
|
return EndChatResponse('voll_ended')
|
|
206
210
|
return EndChatResponse('', end_chat_name='Encerrado pelo usuário', observations='motivo')
|
|
211
|
+
return EndChatResponse('ea_atendimento_humano') # transferência para humano é um end_chat_id específico
|
|
207
212
|
```
|
|
208
213
|
|
|
209
|
-
### `TransferToMenu(menu, user_message, route?)` — Transfere para
|
|
214
|
+
### `TransferToMenu(menu, user_message, route?)` — Transfere para OUTRO BOT (menu)
|
|
215
|
+
`TransferToMenu` é só bot→bot (outro menu/robô configurado no router). **Nunca** use para chegar a
|
|
216
|
+
um atendente humano — isso é `EndChatResponse` (ver acima). Confundir os dois faz o handler prometer
|
|
217
|
+
uma transferência que o router rejeita, porque o "menu" de atendimento humano não existe como menu.
|
|
210
218
|
```python
|
|
211
219
|
return TransferToMenu('p0299_suporte_ti', 'Transferindo...')
|
|
212
220
|
return TransferToMenu('p0299_suporte_ti', 'Transferindo...', route='etapa_inicial') # inicia em rota específica
|
|
213
221
|
```
|
|
214
222
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return TransferToHuman(campaign_name='Suporte N2')
|
|
218
|
-
```
|
|
223
|
+
> `TransferToHuman` (`chatgraph.types.end_types`) é **obsoleto**: não tem efeito no pipeline HTTP
|
|
224
|
+
> (só existia no caminho gRPC legado). Não é mais exportado por `chatgraph/__init__.py`.
|
|
219
225
|
|
|
220
226
|
### `BackgroundTask(async_func, *args, **kwargs)` — Executa tarefa em background e encadeia o retorno
|
|
221
227
|
```python
|
|
@@ -526,3 +532,191 @@ async def aguardar_escolha(usercall: UserCall):
|
|
|
526
532
|
|
|
527
533
|
app.start()
|
|
528
534
|
```
|
|
535
|
+
|
|
536
|
+
## 14. Agentes de IA (`chatgraph.agent`)
|
|
537
|
+
|
|
538
|
+
Requer o extra opcional: `pip install chatgraph[agent]` (pydantic>=2).
|
|
539
|
+
Port do protocolo single-agent do `chatgraph-go`: uma chamada de LLM
|
|
540
|
+
decide navegação E escreve a resposta, com tool calling declarativo.
|
|
541
|
+
|
|
542
|
+
### Conceitos
|
|
543
|
+
|
|
544
|
+
- **`Agent`**: configura LLM client, modelo, prompt de personalidade,
|
|
545
|
+
tools locais, menus, end actions e o modelo pydantic da observation.
|
|
546
|
+
- **`description` da rota é o prompt de comportamento** daquela rota —
|
|
547
|
+
injetada no system prompt do protocolo. `ai_visible=True` expõe a
|
|
548
|
+
rota ao roteamento do agente.
|
|
549
|
+
- **Tools locais** (`@agent.tool`): o modelo as chama via ação
|
|
550
|
+
`call_tool`; o resultado entra na observation sob `result_key` e o
|
|
551
|
+
agente decide de novo (até `max_tool_loops`, default 5).
|
|
552
|
+
- **Ações do protocolo** mapeiam nos tipos de retorno existentes:
|
|
553
|
+
`send_message`→`Message`, `next_route`→`Route`,
|
|
554
|
+
`redirect`→`RedirectResponse`, `set_observation`→merge aditivo na
|
|
555
|
+
observation.
|
|
556
|
+
- **`end_session`→`EndChatResponse`** cobre TANTO encerrar o
|
|
557
|
+
atendimento QUANTO transferir para um atendente humano — no
|
|
558
|
+
chatbot-router as duas são a mesma operação (uma tabulação de
|
|
559
|
+
encerramento). Seleção por **name** do `EndActionInfo` configurado
|
|
560
|
+
em `Agent(end_actions=[...])`.
|
|
561
|
+
- **`transfer_menu`→`TransferToMenu`** é só bot→bot (seleção por
|
|
562
|
+
**name** do `MenuInfo`). **Nunca** configure um menu para representar
|
|
563
|
+
"atendimento humano" — isso pertence a `end_actions`/`end_session`.
|
|
564
|
+
Confundir os dois faz o agente prometer uma transferência que o
|
|
565
|
+
router rejeita (o "menu" de humano não existe como menu).
|
|
566
|
+
- **Histórico**: com `history_store` configurado no `ChatbotApp`, o
|
|
567
|
+
agente recebe a conversa (janela `history_limit`, default 20) com o
|
|
568
|
+
pareamento assistant/tool preservado.
|
|
569
|
+
- **Ação inválida do modelo** (menu/end_action_id que não existe na
|
|
570
|
+
lista configurada) dispara um re-prompt corretivo automático (1
|
|
571
|
+
tentativa por turno, via `AgentContext.last_action_error`); se a
|
|
572
|
+
correção também falhar, o turno termina com uma mensagem neutra em
|
|
573
|
+
vez de silêncio ou uma promessa que não vai se cumprir.
|
|
574
|
+
|
|
575
|
+
### Env vars
|
|
576
|
+
|
|
577
|
+
```
|
|
578
|
+
OPENROUTER_API_KEY=sk-or-... # obrigatória p/ OpenRouterClient
|
|
579
|
+
OPENROUTER_BASE_URL=... # opcional
|
|
580
|
+
AGENT_MODEL=openai/gpt-4o-mini # obrigatória p/ Agent.load_dotenv
|
|
581
|
+
AGENT_TEMPERATURE / AGENT_MAX_TOKENS / AGENT_MAX_TOOL_LOOPS /
|
|
582
|
+
AGENT_HISTORY_LIMIT / AGENT_SYSTEM_PROMPT_FILE # opcionais
|
|
583
|
+
LOG_AGENT_CONTEXT=1 # debug: loga contexto completo (PII!)
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
### Exemplo
|
|
587
|
+
|
|
588
|
+
```python
|
|
589
|
+
from pydantic import BaseModel
|
|
590
|
+
from chatgraph import (
|
|
591
|
+
Agent, ChatbotApp, EndActionInfo, MenuInfo, OpenRouterClient, Route,
|
|
592
|
+
UserCall,
|
|
593
|
+
)
|
|
594
|
+
from chatgraph.history.store import MemoryHistoryStore
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
class PedidoObs(BaseModel):
|
|
598
|
+
cpf: str = ''
|
|
599
|
+
numero_pedido: str = ''
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
agent = Agent(
|
|
603
|
+
llm_client=OpenRouterClient.load_dotenv(),
|
|
604
|
+
model='openai/gpt-4o-mini',
|
|
605
|
+
system_prompt='Você é o assistente da loja. Responda em pt-BR.',
|
|
606
|
+
observation_model=PedidoObs,
|
|
607
|
+
# transfer_menu: só bot→bot (outro robô configurado no router).
|
|
608
|
+
menus=[MenuInfo(name='suporte_ti', description='Bot de suporte técnico.')],
|
|
609
|
+
# end_session: encerrar OU transferir para atendente humano — NÃO
|
|
610
|
+
# é um menu. "id" é opcional (o seletor por name já basta).
|
|
611
|
+
end_actions=[
|
|
612
|
+
EndActionInfo(name='humano', description='Atendente humano.'),
|
|
613
|
+
EndActionInfo(name='resolvido', description='Atendimento concluído.'),
|
|
614
|
+
],
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
# Opcional, no boot do bot (fora do caminho de mensagem): confirma que
|
|
618
|
+
# os menus/end_actions acima existem de verdade no chatbot-router,
|
|
619
|
+
# falhando cedo em vez de só na primeira transferência de um usuário.
|
|
620
|
+
# await agent.validate_config(router_client)
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
@agent.tool(
|
|
624
|
+
description='Consulta pedido por CPF e número.',
|
|
625
|
+
parameters={
|
|
626
|
+
'type': 'object',
|
|
627
|
+
'properties': {
|
|
628
|
+
'cpf': {'type': 'string'},
|
|
629
|
+
'numero_pedido': {'type': 'string'},
|
|
630
|
+
},
|
|
631
|
+
'required': ['cpf', 'numero_pedido'],
|
|
632
|
+
},
|
|
633
|
+
)
|
|
634
|
+
async def consultar_pedido(cpf: str, numero_pedido: str) -> dict:
|
|
635
|
+
return {'status': 'Entregue'}
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
app = ChatbotApp(history_store=MemoryHistoryStore())
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
@app.route(
|
|
642
|
+
'buscar_pedido',
|
|
643
|
+
description="Order lookup — collect cpf and numero_pedido. Call "
|
|
644
|
+
"consultar_pedido with result_key='dados_pedido', then "
|
|
645
|
+
'redirect to exibir_pedido.',
|
|
646
|
+
ai_visible=True,
|
|
647
|
+
)
|
|
648
|
+
async def buscar_pedido(call: UserCall, route: Route):
|
|
649
|
+
return await agent.execute(call, route)
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### Regras e cuidados
|
|
653
|
+
|
|
654
|
+
- Handlers com agente devem ser `async` (sync roda em thread e não
|
|
655
|
+
pode `await`).
|
|
656
|
+
- Um turno de agente pode levar vários segundos (até `max_tool_loops`
|
|
657
|
+
chamadas de LLM). Com `RABBIT_PREFETCH=1` a fila serializa —
|
|
658
|
+
considere aumentar o prefetch para bots com agente.
|
|
659
|
+
- Redirects têm limite global (`MAX_REDIRECT_DEPTH=5` em
|
|
660
|
+
`chatbot_model.py`): um agente que redirecionar em ciclo é
|
|
661
|
+
interrompido com erro no log.
|
|
662
|
+
- `await agent.validate_config(router_client)` é opcional e não é
|
|
663
|
+
chamado automaticamente — confere no boot se os `menus`/`end_actions`
|
|
664
|
+
configurados existem de verdade no chatbot-router. Chame fora do
|
|
665
|
+
caminho de mensagem (uma vez, ao subir o bot).
|
|
666
|
+
- Proteções do executor (port do Go): dedup de tool por `result_key`,
|
|
667
|
+
máx. 2 tentativas por tool com degradação, self-redirect vira
|
|
668
|
+
"none", alvo de rota desconhecido vira "none" com warning, redirect
|
|
669
|
+
é silencioso (a rota destino responde), transfer inválido falha
|
|
670
|
+
ANTES de qualquer side effect.
|
|
671
|
+
- Exemplo completo: `examples/agents_bot.py`.
|
|
672
|
+
|
|
673
|
+
## 15. Geração de conteúdo desacoplada (`generate_content`)
|
|
674
|
+
|
|
675
|
+
Requer o mesmo extra opcional: `pip install chatgraph[agent]`.
|
|
676
|
+
|
|
677
|
+
**Para dados internos do bot, não para turno de chat.** Caso de uso:
|
|
678
|
+
o bot tem acesso a uma base interna (ex.: uma consulta a banco) e
|
|
679
|
+
precisa que a IA analise esse conteúdo e devolva um insight — não é
|
|
680
|
+
uma mensagem de cliente, não tem rota, não tem menu, não passa por
|
|
681
|
+
`UserCall`/`Route`/`AgentContext`. `generate_content` é uma função
|
|
682
|
+
solta, independente de `Agent`: funciona com qualquer `LLMClient`
|
|
683
|
+
(inclusive `OpenRouterClient` já existente), sem exigir menus, tools,
|
|
684
|
+
observation_model ou histórico de conversa configurados.
|
|
685
|
+
|
|
686
|
+
```python
|
|
687
|
+
import json
|
|
688
|
+
|
|
689
|
+
from pydantic import BaseModel
|
|
690
|
+
from chatgraph import OpenRouterClient, generate_content
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
class VendasInsight(BaseModel):
|
|
694
|
+
resumo: str
|
|
695
|
+
tendencia: str
|
|
696
|
+
produtos_em_alerta: list[str] = []
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
async def gerar_relatorio(dados_do_banco: list[dict]) -> VendasInsight | None:
|
|
700
|
+
result = await generate_content(
|
|
701
|
+
OpenRouterClient.load_dotenv(),
|
|
702
|
+
model='openai/gpt-4o-mini',
|
|
703
|
+
content=json.dumps(dados_do_banco, ensure_ascii=False),
|
|
704
|
+
system_prompt='Você é um analista de vendas. Responda em pt-BR.',
|
|
705
|
+
response_model=VendasInsight, # opcional; sem ele, result.data é None
|
|
706
|
+
)
|
|
707
|
+
return result.data # ou result.text, se response_model não foi passado
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
- **`content`** é uma string já serializada pelo chamador (JSON, CSV,
|
|
711
|
+
texto) — a função não sabe de onde o dado vem.
|
|
712
|
+
- **`response_model`** opcional (mesmo padrão do `observation_model` do
|
|
713
|
+
`Agent`): com ele, o Structured Output usa o schema fechado do
|
|
714
|
+
modelo e `result.data` vem parseado; sem ele, só `result.text`.
|
|
715
|
+
- **`history`** opcional para follow-up sobre o mesmo conteúdo (ex.:
|
|
716
|
+
uma segunda pergunta sobre o mesmo relatório) — sem `HistoryStore`,
|
|
717
|
+
cada chamada é isolada por padrão.
|
|
718
|
+
- **O retorno não passa pelo pipeline** (`Message`/`Route`/etc.): é
|
|
719
|
+
consumido diretamente por quem chamou — envie via `usercall.send(...)`
|
|
720
|
+
se estiver dentro de um handler, ou grave em log/banco se for um job
|
|
721
|
+
agendado sem turno de chat nenhum.
|
|
722
|
+
- Exemplo completo: `examples/content_insight.py`.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
`chatgraph` é uma **biblioteca** (não uma aplicação) para construir chatbots orientados a rotas.
|
|
6
|
+
Este arquivo trata de desenvolver a biblioteca. Para *usá-la* em um bot, a referência de API
|
|
7
|
+
está em `.github/skills/chatgraph-framework/SKILL.md`.
|
|
8
|
+
|
|
9
|
+
## Comandos
|
|
10
|
+
|
|
11
|
+
O ambiente real de trabalho é o `.venv/` do repositório. `taskipy` **não** está instalado nele,
|
|
12
|
+
então as tasks `poetry run task test|lint|format` documentadas no README e no PROGRESS.md não
|
|
13
|
+
funcionam — use os binários diretamente:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
.venv/bin/python -m pytest tests/unit -q # suíte rápida (500 testes, ~20s)
|
|
17
|
+
.venv/bin/python -m pytest tests/unit/test_agent_executor.py -v # um arquivo
|
|
18
|
+
.venv/bin/python -m pytest tests/unit/test_agent_executor.py::TestNome::test_caso -v # um teste
|
|
19
|
+
.venv/bin/python -m pytest -m integration -v # integração (auto-skip sem env vars)
|
|
20
|
+
.venv/bin/ruff check chatgraph # lint
|
|
21
|
+
.venv/bin/ruff format . # format (aspas simples, linha 79)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`pytest-asyncio` roda em modo strict (sem `asyncio_mode` no `pyproject.toml`): todo teste async
|
|
25
|
+
precisa de `@pytest.mark.asyncio` explícito.
|
|
26
|
+
|
|
27
|
+
**O lint não está limpo e isso é pré-existente.** Em 2026-08-14, `ruff check .` reportava 775
|
|
28
|
+
erros (174 em `chatgraph/`, 590 em `tests/`), dominados por `PLR6301` (no-self-use nos testes) e
|
|
29
|
+
`E501`; `ruff format --check` acusava 7 arquivos. Não trate esses erros como regressão da sua
|
|
30
|
+
mudança, e não faça um mutirão de correção junto com uma feature — verifique apenas os arquivos
|
|
31
|
+
que você tocou.
|
|
32
|
+
|
|
33
|
+
Testes de integração leem `ROUTER_API_BASE_URL` (+ `_USERNAME`/`_PASSWORD`) e são **pulados
|
|
34
|
+
silenciosamente** se ela não estiver definida — um "tudo verde" pode significar "nada rodou".
|
|
35
|
+
A lista completa de env vars está em `.env.example`.
|
|
36
|
+
|
|
37
|
+
## Arquitetura
|
|
38
|
+
|
|
39
|
+
Pipeline de um turno:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
RabbitMQ → MessageConsumer → ChatbotApp.process_message → default_functions (regex)
|
|
43
|
+
→ guard (auth_level) → handler da rota → __process_func_response → UserCall → RouterHTTPClient
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Pontos que não se deduzem lendo um arquivo só:
|
|
47
|
+
|
|
48
|
+
- **O despacho usa apenas o último segmento da rota.** `process_message` calcula
|
|
49
|
+
`route.split('.')[-1]` e busca essa chave em `self.__routes`, enquanto os decorators registram
|
|
50
|
+
o nome literal passado. Ou seja: registre handlers com o **nome folha** (`@app.route('choice')`),
|
|
51
|
+
nunca com o caminho pontuado (`'start.choice'` fica inalcançável). O caminho pontuado é a
|
|
52
|
+
*posição* do usuário, mantida no backend — não a chave de registro. O README ainda mostra o
|
|
53
|
+
formato antigo com ponto; ele está desatualizado.
|
|
54
|
+
- **O retorno do handler é a API de efeitos.** `__process_func_response` faz dispatch por tipo:
|
|
55
|
+
`Message`/`File` envia, `Route` avança e espera nova mensagem, `RedirectResponse` reexecuta o
|
|
56
|
+
pipeline no mesmo turno, `EndChatResponse`/`TransferToMenu` encerram, `BackgroundTask` roda e
|
|
57
|
+
encadeia o próprio retorno, `list`/`tuple` processa em sequência. Retornar `None`/falsy
|
|
58
|
+
**duplica o último segmento** da rota atual (mecanismo de "fica no nó"); `Route.get_previous()`
|
|
59
|
+
desduplica segmentos repetidos ao voltar. Adicionar um novo tipo de resposta = adicionar um
|
|
60
|
+
ramo aqui, e não lógica no handler.
|
|
61
|
+
- **`MAX_REDIRECT_DEPTH = 5`** (`bot/chatbot_model.py`) corta cadeias de `RedirectResponse` no
|
|
62
|
+
mesmo turno. Sem isso um handler (ou agente de IA) em ciclo recursa até estourar a pilha e
|
|
63
|
+
travar o consumer. `MESSAGE_IN` só é gravado no histórico em `_redirect_depth == 0`, senão a
|
|
64
|
+
mesma mensagem entraria várias vezes com rotas diferentes.
|
|
65
|
+
- **Handlers podem ser sync ou async.** Sync roda em `run_in_executor`, então um handler sync
|
|
66
|
+
**não pode** `await` — handlers com agente de IA precisam ser `async`.
|
|
67
|
+
- **Injeção de parâmetros é por anotação de tipo**, não por nome: `build_route_entry`
|
|
68
|
+
(`bot/route_registry.py`) mapeia `{tipo: nome_do_param}`. Dois parâmetros com a mesma anotação
|
|
69
|
+
colidem (o último vence) — limitação conhecida e documentada no módulo.
|
|
70
|
+
- **Não existe estado local.** Todo estado de usuário/sessão vive na API externa via
|
|
71
|
+
`RouterHTTPClient`; `Container.get_router_client()` é o singleton (lê `ROUTER_URL`/`ROUTER_TOKEN`).
|
|
72
|
+
Não introduza cache nem banco local sem decisão explícita.
|
|
73
|
+
- **Histórico e log de erro são fire-and-forget.** Os hooks de `HistoryStore` e o `LogPublisher`
|
|
74
|
+
capturam `BaseException` e só emitem warning — observabilidade nunca derruba o turno. Se você
|
|
75
|
+
adicionar um hook, mantenha esse contrato. `history_store=None` e `LogPublisher` sem
|
|
76
|
+
`LOG_RABBIT_QUEUE` são no-ops válidos (retrocompatibilidade).
|
|
77
|
+
|
|
78
|
+
### `chatgraph/agent/` — extra opcional
|
|
79
|
+
|
|
80
|
+
Port fiel do protocolo **single-agent** do projeto irmão `chatgraph-go` (uma chamada de LLM decide
|
|
81
|
+
navegação *e* escreve a resposta). Regras que orientam mudanças aqui:
|
|
82
|
+
|
|
83
|
+
- `pydantic` é dependência **só** deste módulo (extra `chatgraph[agent]`). Os símbolos de agente
|
|
84
|
+
são exportados de forma lazy no `chatgraph/__init__.py` via `__getattr__` (PEP 562), para que
|
|
85
|
+
quem não instalou o extra não pague o custo nem quebre. Ao adicionar um export de agente,
|
|
86
|
+
registre-o em `_AGENT_EXPORTS` **e** em `__all__` (o `# ruff: noqa: F822` no topo existe por isso).
|
|
87
|
+
- O executor **não executa efeitos**: ele devolve uma lista dos tipos de retorno já existentes do
|
|
88
|
+
framework, que o pipeline processa sem alteração. Preserve essa separação.
|
|
89
|
+
- Paridade com o Go é o critério de projeto: `prompts.py` e `schema.py` são ports literais.
|
|
90
|
+
Divergências devem ser conscientes e registradas (a atual: `transfer_menu` seleciona menu por
|
|
91
|
+
**name**, porque o client Python não transfere por queue).
|
|
92
|
+
- Proteções do turno que existem por motivo específico, não as remova sem substituto: dedup de
|
|
93
|
+
tool por `result_key`, `MAX_ATTEMPTS_PER_TOOL = 2`, guard de self-redirect, alvo de rota
|
|
94
|
+
desconhecido vira "none", transfer inválido falha **antes** de qualquer side effect.
|
|
95
|
+
|
|
96
|
+
## Convenções
|
|
97
|
+
|
|
98
|
+
- **Commits em português do Brasil**, Conventional Commits (`feat`, `fix`, `chore`, `refactor`,
|
|
99
|
+
`test`, `docs`, `style`, `perf`) — ex.: `feat(agent): adiciona guardrails de saída`.
|
|
100
|
+
- **Logs sempre via `UserLoggerManager`**, nunca `print`: dentro de rota use `usercall.logger`
|
|
101
|
+
(vai para `chatgraph_logs/{user_id}_{company_id}.log`); fora de rota,
|
|
102
|
+
`UserLoggerManager.get_system_logger()`.
|
|
103
|
+
- **Injeção de dependência manual** por construtor; sem framework de DI, sem prefixo `I` em
|
|
104
|
+
interfaces. Contratos plugáveis usam `typing.Protocol` (ex.: `HistoryStore`), não ABC.
|
|
105
|
+
- **Docstrings e comentários em português**, seguindo o código existente. Comentário bom aqui
|
|
106
|
+
explica *por que* a proteção existe (ver `MAX_REDIRECT_DEPTH`), não o que a linha faz.
|
|
107
|
+
- Testes agrupados em classes por unidade sob teste (`Test<Nome>`), fixtures em `conftest.py`,
|
|
108
|
+
HTTP mockado com `respx`, marcadores `@pytest.mark.unit` / `@pytest.mark.integration`.
|
|
109
|
+
|
|
110
|
+
## Notas do repositório
|
|
111
|
+
|
|
112
|
+
- **A CLI não está instalada.** `[project.scripts]` no `pyproject.toml` está comentado, então o
|
|
113
|
+
comando `chatgraph` documentado no README não existe; `chatgraph/cli/` só roda via
|
|
114
|
+
`python -m` / import direto. Também é o módulo mais antigo (usa gRPC, imports fora do padrão).
|
|
115
|
+
- `[project] dev-dependencies` no `pyproject.toml` é um campo **não padrão** e ignorado pelos
|
|
116
|
+
instaladores; as dev deps efetivas estão em `[dependency-groups] dev`. Existem `poetry.lock` e
|
|
117
|
+
`uv.lock` no repo — confirme qual gerenciador está sendo usado antes de mexer em dependências.
|
|
118
|
+
- Não há CI configurado (`.github/workflows/` não existe): a validação antes de commitar é manual.
|
|
119
|
+
- `chatgraph/pb/` é **gerado** a partir do protobuf — não edite à mão.
|
|
120
|
+
|
|
121
|
+
## Contexto adicional
|
|
122
|
+
|
|
123
|
+
- `PROGRESS.md` — linha do tempo, decisões técnicas com justificativa e backlog priorizado.
|
|
124
|
+
Mantenha-o atualizado ao concluir uma área.
|
|
125
|
+
- `docs/gaps-go-to-python.md` e `docs/gaps-python-to-go.md` — paridade com o `chatgraph-go`; é a
|
|
126
|
+
fonte de verdade sobre o que falta portar (timeout por rota, route triggers, `validate_routes`,
|
|
127
|
+
helper de teste tipo `EngineTester`).
|
|
128
|
+
- `.github/copilot-instructions.md` define um fluxo de agentes (Architect → Developer →
|
|
129
|
+
CodeReviewer, definidos em `.github/agents/`) usado pelo Copilot; não é carregado pelo Claude
|
|
130
|
+
Code, mas as convenções de código e commit dele são as mesmas resumidas acima.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chatgraph
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: A user-friendly chatbot library
|
|
5
5
|
Project-URL: Homepage, https://github.com/irissonnlima/chatgraph
|
|
6
6
|
Project-URL: Repository, https://github.com/irissonnlima/chatgraph
|
|
@@ -20,6 +20,8 @@ Requires-Dist: protobuf>=6.31.1
|
|
|
20
20
|
Requires-Dist: python-dotenv>=1.0.1
|
|
21
21
|
Requires-Dist: rich>=13.8.1
|
|
22
22
|
Requires-Dist: typer>=0.12.5
|
|
23
|
+
Provides-Extra: agent
|
|
24
|
+
Requires-Dist: pydantic>=2; extra == 'agent'
|
|
23
25
|
Description-Content-Type: text/markdown
|
|
24
26
|
|
|
25
27
|
### **README - ChatGraph**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Progresso do Projeto ChatGraph
|
|
2
2
|
|
|
3
|
-
**Última atualização:** 2026-08-
|
|
3
|
+
**Última atualização:** 2026-08-17
|
|
4
4
|
|
|
5
5
|
## 1. Visão Geral do Projeto
|
|
6
6
|
|
|
@@ -50,6 +50,8 @@ tests/
|
|
|
50
50
|
|
|
51
51
|
| Data | Commit | Descrição |
|
|
52
52
|
|------|--------|-----------|
|
|
53
|
+
| 2026-08-17 | - | **Fix: vocabulário de encerramento do agente + re-prompt corretivo.** Dois turnos de produção em 2026-08-14 falharam por causa raiz comum: o protocolo do agente não distinguia "transferir para outro bot" (`transfer_menu`) de "encerrar atendimento/transferir para humano" (`end_session`, ambos a mesma operação de EndAction no chatbot-router) — um bot acabou configurando um `MenuInfo` chamado `humano`, que o router rejeitou (`sql: no rows in result set`) só depois da promessa já ter sido enviada ao usuário. Correções: (1) `EndActionInfo`/`Agent(end_actions=...)`/`AgentContext.available_end_actions`, seção `AVAILABLE END ACTIONS` no prompt, resolução em `executor.py` (seletor por name/id, falha ANTES de side effect); (2) `AgentActionError` + `AgentContext.last_action_error` — ação inválida (transfer_menu ou end_session com seletor desconhecido) dispara 1 re-prompt corretivo por turno em vez de estourar o turno inteiro; sem correção, fallback é uma mensagem neutra fixa (nunca o `result.response`, que nesses casos costuma ser a promessa que acabou de falhar); (3) `build_agent_result_schema` parametrizado com enum de `menu`/`end_action_id` por Agent (`lru_cache(maxsize=32)`, antes `maxsize=1` sem args); (4) `agent.validate_config(router_client)` opt-in, confere no boot se menus/end_actions existem de verdade; (5) `TransferToHuman` aposentado do público (`__init__.py`, SKILL.md) — código morto no pipeline HTTP, mesma fonte de confusão um nível abaixo. Log DEBUG do raw response da LLM em `openrouter.py` para diagnosticar o próximo caso. Bug lateral corrigido: `Message.from_dict` não convertia `file` de dict para `File`, quebrando o registro de histórico em mensagens com anexo (`'dict' object has no attribute 'to_dict'`). |
|
|
54
|
+
| 2026-08-14 | - | **Agentes de IA (v1.4.0):** novo módulo `chatgraph/agent/` portando o protocolo single-agent do `chatgraph-go`: `Agent` (tools locais via `@agent.tool`, `generate_protocol`), `OpenRouterClient` (httpx, retry/backoff/Retry-After, structured outputs), normalização de JSON Schema (`schema.py`, port do `normalize.go`), prompts portados literalmente, executor com proteções (dedup por `result_key`, máx. 2 tentativas por tool, self-redirect guard, redirect silencioso, parada na primeira ação terminal, merge aditivo de observation), `history_bridge` (pareamento assistant/tool + `trim_safe`). Integração: `description=`/`ai_visible=` nos decorators (builder comum `route_registry.py`), `Route.infos`, `MAX_REDIRECT_DEPTH=5` no pipeline (corrige loop infinito de redirects e MESSAGE_IN duplicado em redirects). Extra opcional `chatgraph[agent]` (pydantic>=2) com exports lazy (PEP 562). Divergência consciente do Go: `transfer_menu` seleciona por **name** (client Python não transfere por queue). 461 testes unitários. |
|
|
53
55
|
| 2026-08-05 | `c24cbbd` | **Fix: acúmulo de `[ChatID: ...]` nos logs:** `ChatIDFilter.filter()` tornado idempotente (verifica `startswith` antes de prefixar); `remove_user_logger()` agora remove instâncias de `ChatIDFilter` antes de fechar handlers; `ChatID.__str__` adicionado (`user_id:company_id`); prefixo hardcoded removido da exceção em `UserCall.__send()`. Testes: `TestChatIDFilter` (3 novos), `test_remove_user_logger_removes_chatid_filters`, `test_remove_then_get_has_exactly_one_chatid_filter`, `test_chatid_str`, `TestSendException`. Revisão: ⚠️ CUIDADO — edge case `startswith` com `record.msg` não-string; teste de exceção poderia validar ausência de prefixo com `assert '[ChatID:' not in ...`. |
|
|
54
56
|
| 2026-08-04 | `672e4df` | **LogPublisher + integração final do histórico:** `LogPublisher` para publicação de erros via RabbitMQ com auto-configuração via `load_dotenv()`; `LogEnvelope`, `ErrorLogPayload`, `EventType` e mapeamento `error_code` por tipo de exceção. Integração completa do `HistoryStore` no pipeline: hooks `MESSAGE_IN`/`MESSAGE_OUT`/`ROUTE_CHANGE`/`TRANSFER`/`END_CHAT` no `UserCall` e `ChatbotApp`; repasse via `MessageConsumer`. Novas properties em `UserCall`: `user_state`, `session_id`, `message`, `history`. Publicação de erros em `ChatbotApp._publish_error_log` e `MessageConsumer._process_message_callback`. |
|
|
55
57
|
| 2026-08-04 | `c24cbbd` | **Testes:** `TestLogPublisher`, `TestLogEnvelope`, `TestChatbotAppLogPublisher`, `TestMessageConsumerLogPublisher`, `TestHistoryStore`, `TestHistoryIntegration`, `TestUserStateProperty`. Total: 283 testes unitários. |
|
|
@@ -74,9 +76,10 @@ tests/
|
|
|
74
76
|
| **HTTP Router Client** | ✅ | RouterHTTPClient para integração com API REST |
|
|
75
77
|
| **Logging** | ✅ | UserLoggerManager com logs por usuário e sistema |
|
|
76
78
|
| **CLI** | ✅ | Comandos básicos (campaigns, delete-ustate) |
|
|
77
|
-
| **
|
|
79
|
+
| **Agentes de IA** | ✅ | Módulo `chatgraph/agent/` (protocolo single-agent do Go): Agent + tools locais, OpenRouterClient, schema normalizer, executor com proteções (inclui re-prompt corretivo de ação inválida), histórico com pareamento assistant/tool, `end_actions`/`validate_config`, `generate_content` desacoplado do turno de chat. Extra `chatgraph[agent]`. Futuro: two-agent, MCP, memória, guardrails |
|
|
80
|
+
| **Testes Unitários** | ✅ | 500 testes passando (agent + histórico + log publisher + UserState) |
|
|
78
81
|
| **Testes de Integração** | ⚠️ | Requer variáveis de ambiente configuradas |
|
|
79
|
-
| **Documentação** | ⚠️ | README completo
|
|
82
|
+
| **Documentação** | ⚠️ | README completo + SKILL.md §14 (agentes); falta docs/ detalhada |
|
|
80
83
|
|
|
81
84
|
**Legenda:**
|
|
82
85
|
- ✅ Completo
|
|
@@ -158,15 +161,25 @@ poetry run ruff check . && ruff format .
|
|
|
158
161
|
| gRPC | protobuf | Contratos tipados e performance |
|
|
159
162
|
| History Store | Protocol (não ABC) | Plugável, sem acoplamento a classe base |
|
|
160
163
|
| Log Publisher | load_dotenv() + opcional | Retrocompatível — se `LOG_RABBIT_QUEUE` não estiver definido, o publisher é `None` e os erros não são publicados |
|
|
164
|
+
| Agentes de IA | Port fiel do Go, sem pydantic-ai/SDK openai | Paridade de comportamento entre os frameworks; cliente OpenRouter próprio via httpx; pydantic>=2 isolado ao módulo `agent/` (extra opcional `chatgraph[agent]`, exports lazy PEP 562) |
|
|
165
|
+
| transfer_menu do agente | Seletor por `name` do menu | O client Python só transfere por nome (`Menu.from_name`); queue/menu_id do Go não mapeiam — reintroduzir se o RouterHTTPClient ganhar transfer por queue |
|
|
166
|
+
| Redirects | `MAX_REDIRECT_DEPTH=5` global | Recursão de `RedirectResponse` era ilimitada (risco de loop, agravado por agentes); a reentrada por redirect também não regrava MESSAGE_IN no histórico |
|
|
167
|
+
| end_session do agente | Seletor por `name` (depois `id`) de `EndActionInfo`, mesmo critério do `transfer_menu` | `end_session` cobre encerrar E transferir para humano (mesma EndAction no chatbot-router); sem `end_actions` configuradas, `end_action_id` continua livre (retrocompatível) |
|
|
168
|
+
| Ação inválida do agente | Re-prompt corretivo (1 tentativa/turno via `AgentContext.last_action_error`), depois mensagem neutra fixa | Um `transfer_menu`/`end_session` com seletor inexistente perdia o turno inteiro em silêncio (ou, pior, enviava a promessa antes do router rejeitar); "falha antes de side effect" continua valendo — a correção nunca executa ação |
|
|
169
|
+
| TransferToHuman | Aposentado do público (`__init__.py`, SKILL.md); classe mantida em `end_types.py` só por compatibilidade de import | Código morto no pipeline HTTP (sem ramo em `__process_func_response`, sem método no `RouterHTTPClient`) — mesma confusão de "transferir para humano" um nível abaixo do bug de `transfer_menu`/`humano` |
|
|
161
170
|
|
|
162
171
|
## 9. Trabalhos Pendentes
|
|
163
172
|
|
|
164
173
|
| Prioridade | Tarefa | Área | Status |
|
|
165
174
|
|------------|--------|------|--------|
|
|
166
175
|
| Alta | Eliminar duplicação de lógica de registro entre `ChatbotApp.__record_message_in` e `UserCall.__record_history` | chatgraph/bot + types | ⚠️ |
|
|
176
|
+
| Média | Agentes: protocolo two-agent (`DecideRoute`+`Speak`) | chatgraph/agent | ❌ |
|
|
177
|
+
| Média | Agentes: MCP (stdio/SSE/HTTP) como fonte de tools | chatgraph/agent | ❌ |
|
|
178
|
+
| Média | Agentes: memória de longo prazo (`MemoryContext`) e guardrails | chatgraph/agent | ❌ |
|
|
167
179
|
| Média | Documentação técnica detalhada | docs/ | ❌ |
|
|
168
180
|
| Média | Mais testes de integração | tests/integration/ | ⚠️ |
|
|
169
|
-
| Baixa |
|
|
181
|
+
| Baixa | Dispatcher com serialização por chat_id (prefetch > 1 seguro p/ bots com agente) | messages/ | ❌ |
|
|
182
|
+
| Baixa | Exemplos adicionais | examples/ | ⚠️ (`examples/agents_bot.py` criado) |
|
|
170
183
|
| Baixa | Suporte a mais plataformas | bot/ | ❌ |
|
|
171
184
|
|
|
172
185
|
## 10. Notas e Observações
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# ruff: noqa: F822 - os símbolos do agente em __all__ resolvem via
|
|
2
|
+
# __getattr__ (PEP 562, import lazy); o ruff não os enxerga.
|
|
3
|
+
from .auth.credentials import Credential
|
|
4
|
+
from .bot.chatbot_model import ChatbotApp
|
|
5
|
+
from .bot.chatbot_router import ChatbotRouter
|
|
6
|
+
from .bot.default_guard import default_guard
|
|
7
|
+
from .container.container import Container
|
|
8
|
+
from .logger import logger, set_level
|
|
9
|
+
from .messages.log_publisher import LogPublisher
|
|
10
|
+
from .messages.message_consumer import MessageConsumer
|
|
11
|
+
from .models.log_envelope import (
|
|
12
|
+
ErrorLogPayload,
|
|
13
|
+
EventType,
|
|
14
|
+
LogEnvelope,
|
|
15
|
+
)
|
|
16
|
+
from .models.message import Button, File, Message, SendType, TextMessage
|
|
17
|
+
from .models.platform_state import PlatformState, VollStateData
|
|
18
|
+
from .models.userstate import (
|
|
19
|
+
AuthLevel,
|
|
20
|
+
ChatID,
|
|
21
|
+
Menu,
|
|
22
|
+
User,
|
|
23
|
+
UserData,
|
|
24
|
+
UserIdentity,
|
|
25
|
+
UserInternal,
|
|
26
|
+
UserState,
|
|
27
|
+
)
|
|
28
|
+
from .types.background_task import BackgroundTask
|
|
29
|
+
from .types.end_types import (
|
|
30
|
+
EndChatResponse,
|
|
31
|
+
RedirectResponse,
|
|
32
|
+
TransferToMenu,
|
|
33
|
+
)
|
|
34
|
+
from .types.route import Route
|
|
35
|
+
from .types.usercall import UserCall
|
|
36
|
+
|
|
37
|
+
# Símbolos do módulo de agentes de IA (extra opcional `agent`).
|
|
38
|
+
# Import lazy via PEP 562: quem não instalou pydantic não paga o custo
|
|
39
|
+
# nem quebra; o erro amigável vem do guard em chatgraph/agent/__init__.
|
|
40
|
+
_AGENT_EXPORTS = {
|
|
41
|
+
'Agent': 'chatgraph.agent.agent',
|
|
42
|
+
'AgentAction': 'chatgraph.agent.protocol',
|
|
43
|
+
'AgentActionError': 'chatgraph.agent.errors',
|
|
44
|
+
'AgentContext': 'chatgraph.agent.protocol',
|
|
45
|
+
'AgentError': 'chatgraph.agent.errors',
|
|
46
|
+
'AgentResult': 'chatgraph.agent.protocol',
|
|
47
|
+
'ContentResult': 'chatgraph.agent.content',
|
|
48
|
+
'EndActionInfo': 'chatgraph.agent.protocol',
|
|
49
|
+
'LLMClientError': 'chatgraph.agent.errors',
|
|
50
|
+
'MenuInfo': 'chatgraph.agent.protocol',
|
|
51
|
+
'OpenRouterClient': 'chatgraph.agent.openrouter',
|
|
52
|
+
'RouteInfo': 'chatgraph.agent.protocol',
|
|
53
|
+
'Tool': 'chatgraph.agent.types',
|
|
54
|
+
'ToolExecutionError': 'chatgraph.agent.errors',
|
|
55
|
+
'ToolInfo': 'chatgraph.agent.protocol',
|
|
56
|
+
'Usage': 'chatgraph.agent.types',
|
|
57
|
+
'execute_single_agent': 'chatgraph.agent.executor',
|
|
58
|
+
'generate_content': 'chatgraph.agent.content',
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def __getattr__(name: str):
|
|
63
|
+
if name in _AGENT_EXPORTS:
|
|
64
|
+
import importlib # noqa: PLC0415 - import lazy proposital
|
|
65
|
+
|
|
66
|
+
module = importlib.import_module(_AGENT_EXPORTS[name])
|
|
67
|
+
return getattr(module, name)
|
|
68
|
+
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
__all__ = [
|
|
72
|
+
'Agent',
|
|
73
|
+
'AgentAction',
|
|
74
|
+
'AgentActionError',
|
|
75
|
+
'AgentContext',
|
|
76
|
+
'AgentError',
|
|
77
|
+
'AgentResult',
|
|
78
|
+
'ContentResult',
|
|
79
|
+
'EndActionInfo',
|
|
80
|
+
'LLMClientError',
|
|
81
|
+
'MenuInfo',
|
|
82
|
+
'OpenRouterClient',
|
|
83
|
+
'RouteInfo',
|
|
84
|
+
'Tool',
|
|
85
|
+
'ToolExecutionError',
|
|
86
|
+
'ToolInfo',
|
|
87
|
+
'Usage',
|
|
88
|
+
'execute_single_agent',
|
|
89
|
+
'generate_content',
|
|
90
|
+
'set_level',
|
|
91
|
+
'ChatbotApp',
|
|
92
|
+
'Credential',
|
|
93
|
+
'UserCall',
|
|
94
|
+
'ChatbotRouter',
|
|
95
|
+
'RedirectResponse',
|
|
96
|
+
'MessageConsumer',
|
|
97
|
+
'LogPublisher',
|
|
98
|
+
'LogEnvelope',
|
|
99
|
+
'ErrorLogPayload',
|
|
100
|
+
'EventType',
|
|
101
|
+
'Route',
|
|
102
|
+
'EndChatResponse',
|
|
103
|
+
'TransferToMenu',
|
|
104
|
+
'UserState',
|
|
105
|
+
'ChatID',
|
|
106
|
+
'Menu',
|
|
107
|
+
'AuthLevel',
|
|
108
|
+
'User',
|
|
109
|
+
'UserData',
|
|
110
|
+
'UserIdentity',
|
|
111
|
+
'UserInternal',
|
|
112
|
+
'Message',
|
|
113
|
+
'Button',
|
|
114
|
+
'File',
|
|
115
|
+
'TextMessage',
|
|
116
|
+
'SendType',
|
|
117
|
+
'BackgroundTask',
|
|
118
|
+
'Container',
|
|
119
|
+
'logger',
|
|
120
|
+
'default_guard',
|
|
121
|
+
'PlatformState',
|
|
122
|
+
'VollStateData',
|
|
123
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Módulo de agentes de IA do chatgraph.
|
|
2
|
+
|
|
3
|
+
Requer o extra opcional ``agent``::
|
|
4
|
+
|
|
5
|
+
pip install chatgraph[agent]
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import pydantic # noqa: F401
|
|
10
|
+
except ImportError as exc: # pragma: no cover
|
|
11
|
+
raise ModuleNotFoundError(
|
|
12
|
+
'O módulo chatgraph.agent requer o extra "agent". '
|
|
13
|
+
'Instale com: pip install chatgraph[agent]'
|
|
14
|
+
) from exc
|
|
15
|
+
|
|
16
|
+
from .errors import (
|
|
17
|
+
AgentConfigurationError,
|
|
18
|
+
AgentError,
|
|
19
|
+
LLMClientError,
|
|
20
|
+
ToolExecutionError,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
'AgentConfigurationError',
|
|
25
|
+
'AgentError',
|
|
26
|
+
'LLMClientError',
|
|
27
|
+
'ToolExecutionError',
|
|
28
|
+
]
|