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.
Files changed (111) hide show
  1. {chatgraph-1.3.1 → chatgraph-1.4.0}/.env.example +24 -0
  2. {chatgraph-1.3.1 → chatgraph-1.4.0}/.github/skills/chatgraph-framework/SKILL.md +201 -7
  3. chatgraph-1.4.0/CLAUDE.md +130 -0
  4. {chatgraph-1.3.1 → chatgraph-1.4.0}/PKG-INFO +3 -1
  5. {chatgraph-1.3.1 → chatgraph-1.4.0}/PROGRESS.md +17 -4
  6. chatgraph-1.4.0/chatgraph/__init__.py +123 -0
  7. chatgraph-1.4.0/chatgraph/agent/__init__.py +28 -0
  8. chatgraph-1.4.0/chatgraph/agent/agent.py +539 -0
  9. chatgraph-1.4.0/chatgraph/agent/content.py +138 -0
  10. chatgraph-1.4.0/chatgraph/agent/context.py +212 -0
  11. chatgraph-1.4.0/chatgraph/agent/errors.py +53 -0
  12. chatgraph-1.4.0/chatgraph/agent/executor.py +653 -0
  13. chatgraph-1.4.0/chatgraph/agent/history_bridge.py +206 -0
  14. chatgraph-1.4.0/chatgraph/agent/openrouter.py +383 -0
  15. chatgraph-1.4.0/chatgraph/agent/prompts.py +306 -0
  16. chatgraph-1.4.0/chatgraph/agent/protocol.py +459 -0
  17. chatgraph-1.4.0/chatgraph/agent/schema.py +259 -0
  18. chatgraph-1.4.0/chatgraph/agent/types.py +108 -0
  19. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/bot/chatbot_model.py +81 -29
  20. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/bot/chatbot_router.py +19 -21
  21. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/bot/default_functions.py +0 -1
  22. chatgraph-1.4.0/chatgraph/bot/route_registry.py +64 -0
  23. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/history/__init__.py +0 -0
  24. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/history/entry.py +15 -1
  25. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/history/keys.py +0 -0
  26. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/history/store.py +0 -0
  27. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/messages/__init__.py +0 -0
  28. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/messages/log_publisher.py +0 -0
  29. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/messages/message_consumer.py +0 -0
  30. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/__init__.py +0 -0
  31. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/log_envelope.py +0 -0
  32. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/message.py +8 -2
  33. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/platform_state.py +0 -0
  34. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/types/end_types.py +6 -1
  35. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/types/route.py +8 -3
  36. {chatgraph-1.3.1 → chatgraph-1.4.0}/docs/gaps-go-to-python.md +47 -13
  37. chatgraph-1.4.0/docs/gaps-python-to-go.md +69 -0
  38. chatgraph-1.4.0/examples/agent_smoke.py +259 -0
  39. chatgraph-1.4.0/examples/agents_bot.py +127 -0
  40. chatgraph-1.4.0/examples/content_insight.py +63 -0
  41. {chatgraph-1.3.1 → chatgraph-1.4.0}/pyproject.toml +6 -1
  42. chatgraph-1.4.0/tests/unit/test_agent_content.py +168 -0
  43. chatgraph-1.4.0/tests/unit/test_agent_context.py +241 -0
  44. chatgraph-1.4.0/tests/unit/test_agent_core.py +352 -0
  45. chatgraph-1.4.0/tests/unit/test_agent_executor.py +764 -0
  46. chatgraph-1.4.0/tests/unit/test_agent_history_bridge.py +208 -0
  47. chatgraph-1.4.0/tests/unit/test_agent_openrouter.py +401 -0
  48. chatgraph-1.4.0/tests/unit/test_agent_prompts.py +190 -0
  49. chatgraph-1.4.0/tests/unit/test_agent_protocol.py +265 -0
  50. chatgraph-1.4.0/tests/unit/test_agent_schema.py +323 -0
  51. chatgraph-1.4.0/tests/unit/test_agent_validate_config.py +146 -0
  52. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_chatbot_app_log_publisher.py +0 -0
  53. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_guard.py +1 -1
  54. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_history_integration.py +0 -0
  55. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_history_store.py +0 -0
  56. chatgraph-1.4.0/tests/unit/test_history_tool_entries.py +88 -0
  57. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_log_envelope.py +0 -0
  58. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_log_publisher.py +0 -0
  59. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_message_consumer_log_publisher.py +0 -0
  60. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_models_message.py +30 -0
  61. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_models_platform_state.py +0 -0
  62. chatgraph-1.4.0/tests/unit/test_route_registry_integration.py +236 -0
  63. {chatgraph-1.3.1 → chatgraph-1.4.0}/uv.lock +0 -0
  64. chatgraph-1.3.1/chatgraph/__init__.py +0 -71
  65. chatgraph-1.3.1/docs/gaps-python-to-go.md +0 -65
  66. {chatgraph-1.3.1 → chatgraph-1.4.0}/.github/agents/SkillManager.agent.md +0 -0
  67. {chatgraph-1.3.1 → chatgraph-1.4.0}/.github/agents/architect.agent.md +0 -0
  68. {chatgraph-1.3.1 → chatgraph-1.4.0}/.github/agents/code-reviewer.agent.md +0 -0
  69. {chatgraph-1.3.1 → chatgraph-1.4.0}/.github/agents/developer.agent.md +0 -0
  70. {chatgraph-1.3.1 → chatgraph-1.4.0}/.github/copilot-instructions.md +0 -0
  71. {chatgraph-1.3.1 → chatgraph-1.4.0}/.gitignore +0 -0
  72. {chatgraph-1.3.1 → chatgraph-1.4.0}/.python-version +0 -0
  73. {chatgraph-1.3.1 → chatgraph-1.4.0}/LICENSE +0 -0
  74. {chatgraph-1.3.1 → chatgraph-1.4.0}/README.md +0 -0
  75. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/auth/credentials.py +0 -0
  76. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/bot/default_guard.py +0 -0
  77. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/cli/__init__.py +0 -0
  78. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/container/container.py +0 -0
  79. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/error/chatbot_error.py +0 -0
  80. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/error/route_error.py +0 -0
  81. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/gRPC/gRPCCall.py +0 -0
  82. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/logger/__init__.py +0 -0
  83. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/logger/logger.py +0 -0
  84. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/logger/user_logger.py +0 -0
  85. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/actions.py +0 -0
  86. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/http_responses.py +0 -0
  87. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/models/userstate.py +0 -0
  88. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/pb/router.proto +0 -0
  89. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/pb/router_pb2.py +0 -0
  90. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/pb/router_pb2_grpc.py +0 -0
  91. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/services/__init__.py +0 -0
  92. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/services/router_http_client.py +0 -0
  93. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/types/background_task.py +0 -0
  94. {chatgraph-1.3.1 → chatgraph-1.4.0}/chatgraph/types/usercall.py +0 -0
  95. {chatgraph-1.3.1 → chatgraph-1.4.0}/example.py +0 -0
  96. {chatgraph-1.3.1 → chatgraph-1.4.0}/example2.py +0 -0
  97. {chatgraph-1.3.1 → chatgraph-1.4.0}/jsons/voll_return.json +0 -0
  98. {chatgraph-1.3.1 → chatgraph-1.4.0}/poetry.lock +0 -0
  99. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/__init__.py +0 -0
  100. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/integration/__init__.py +0 -0
  101. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/integration/conftest.py +0 -0
  102. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/integration/test_router_client_integration.py +0 -0
  103. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/__init__.py +0 -0
  104. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/conftest.py +0 -0
  105. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_default_guard.py +0 -0
  106. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_message_consumer.py +0 -0
  107. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_models_actions.py +0 -0
  108. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_models_userstate.py +0 -0
  109. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_router_http_client.py +0 -0
  110. {chatgraph-1.3.1 → chatgraph-1.4.0}/tests/unit/test_user_logger.py +0 -0
  111. {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, TransferToHuman, BackgroundTask, setting up ChatbotApp, ChatbotRouter, include_router, configuring RabbitMQ consumer, adding per-user file logging with UserLoggerManager, integrating chatgraph in a new Python project."
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 outro menu
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
- ### `TransferToHuman(campaign_id?, campaign_name?, observations?)` — Transfere para humano
216
- ```python
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.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-05
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
- | **Testes Unitários** | ✅ | 283 testes passando (histórico + log publisher + UserState) |
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, falta docs/ detalhada |
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 | Exemplos adicionais | examples/ | ❌ |
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
+ ]