Pular para conteúdo

Estrutura do projeto

Estrutura canônica de pastas e arquivos. Use isto como template ao criar um novo chatbot OTIMUS.

NOVOPROJETO/
├── .env.template
├── .gitignore
├── Dockerfile
├── docker-compose.yml
├── init.sql                    # schema do Postgres
├── requirements.txt
├── README.md
├── CLAUDE.md                   # guia específico para agentes Claude
├── config.py                   # env vars + logger
├── server.py                   # FastAPI — webhooks
├── workflow.py                 # LangGraph Functional API (entrypoint principal)
├── agent.py                    # ReAct agent + tools + system prompt
├── scheduling.py               # cliente OTIMUS + regras de negócio
├── wts_client.py               # cliente FlowChat/WTS Chat
├── processors.py               # Gemini (imagem/PDF) + Whisper (áudio)
├── db.py                       # fila de mensagens em Postgres
└── tracking.py                 # telemetria dashboard

Por arquivo

config.py

Único lugar onde se lê os.getenv. Zero lógica.

import os, logging

# --- Obrigatórias ---
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
WTS_API_TOKEN  = os.getenv("WTS_API_TOKEN", "")
DATABASE_URL   = os.getenv("DATABASE_URL", "")

# --- OTIMUS ---
OTIMUS_API_URL = os.getenv("OTIMUS_API_URL", "")
OTIMUS_TOKEN   = os.getenv("OTIMUS_TOKEN", "")

# --- WTS ---
WTS_BASE_URL            = os.getenv("WTS_BASE_URL", "https://api.wts.chat")
WTS_CHANNEL_ID          = os.getenv("WTS_CHANNEL_ID", "")
WTS_DEPARTMENT_HUMAN_ID = os.getenv("WTS_DEPARTMENT_HUMAN_ID", "")
WTS_DEPARTMENT_AI_ID    = os.getenv("WTS_DEPARTMENT_AI_ID", "")

# --- Workflow ---
ALLOWED_INSTANCES = [i.strip() for i in os.getenv("ALLOWED_INSTANCE", "").split(",") if i.strip()]
ALLOWED_TEAM      = os.getenv("ALLOWED_TEAM", "")
MESSAGE_DEBOUNCE_SECONDS = int(os.getenv("MESSAGE_DEBOUNCE_SECONDS", "5"))
CHAT_HISTORY_TABLE  = os.getenv("CHAT_HISTORY_TABLE", "dados_cliente_projeto")
MESSAGE_QUEUE_TABLE = os.getenv("MESSAGE_QUEUE_TABLE", "n8n_fila_mensagens_projeto")
CONTEXT_WINDOW_LENGTH = int(os.getenv("CONTEXT_WINDOW_LENGTH", "50"))

# --- Mídia ---
GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-2.5-flash")

# --- Logging ---
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger("<projeto>")

server.py

Thin layer sobre FastAPI. Apenas routing, validação grosseira de payload e thread spawn. Nenhuma lógica de negócio aqui.

workflow.py

@entrypoint do LangGraph Functional API. Orquestra:

  1. Fetch session do WTS (get_session_by_id).
  2. Filtros (platform, instance, team, status).
  3. Processamento de mídia (via processors.py).
  4. Debounce + anti-overlap (via db.py).
  5. agent.invoke().
  6. Split e envio da resposta (via wts_client.py).

agent.py

  • Instancia llm = ChatOpenAI(model, api_key, temperature).
  • Conecta PostgresSaver (fallback MemorySaver).
  • Define tools com @tool.
  • Define SYSTEM_PROMPT (string gigante).
  • Constrói agent = create_react_agent(llm, tools, prompt, checkpointer).
  • Expõe run_agent(message, session_id, phone_number, contact_name).

scheduling.py (ou agendamento.py)

Núcleo da integração OTIMUS. Contém:

  • PROCEDIMENTOS / CONVENIOS / _DOCTOR_KEYWORDS — dados da clínica.
  • _otimus_headers() — headers autenticados.
  • Funções públicas: consultar_horarios(), agendar_consulta().
  • Funções privadas: _chamar_api_horarios(), _verificar_disponibilidade(), _buscar_paciente_cpf(), _cadastrar_paciente(), _confirmar_agendamento().
  • Helpers: _normalize(), _only_digits(), _yyyymmdd_to_ddmmyyyy(), _validar_agenda_medico(), _filter_and_select_slots().

wts_client.py

Cliente FlowChat/WTS Chat. Funções públicas:

  • get_session_by_id(session_id)
  • send_message(phone, text) — via telefone
  • send_message_in_session(session_id, text) — via sessão
  • transfer_session(session_id)
  • add_tags(phone, tags)

processors.py

  • process_image(url, mime_type) → texto (via Gemini)
  • process_audio(url) → texto (via Whisper)
  • process_document(url, mime_type) → texto (via Gemini, PDFs)

db.py

  • enqueue_message(phone, message_id, message)
  • fetch_and_clear_queue(phone) → lista de textos
  • is_message_newer(phone, session_id) → anti-overlap

tracking.py

  • log_session(session_id, phone, contact_name, content_type)
  • log_token_usage(session_id, provider, model, prompt_tokens, completion_tokens)
  • log_error(error_type, error_message, session_id, phone, stack_trace)
  • log_tag(session_id, phone, tag_name)
  • log_transfer(session_id)

init.sql

Schema do Postgres. Ver Database.

Dockerfile

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8000"]

docker-compose.yml (minimal)

services:
  chatbot:
    build: .
    container_name: <projeto>-chatbot
    restart: unless-stopped
    env_file: .env
    ports:
      - "8000:8000"
    depends_on:
      - postgres

  postgres:
    image: postgres:16
    container_name: <projeto>-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro

volumes:
  pgdata:

CLAUDE.md (por projeto)

Cada projeto deve ter um CLAUDE.md com as informações específicas dele — tudo que é padrão OTIMUS deve apontar para este OTIDOC. Ver exemplo em CLINFETO (/home/DOCKER/CLINFETO/CLAUDE.md).