Production-ready framework for building AI-powered citizen service chatbots connected to WhatsApp or Chatwoot. Used in Brazilian municipal government deployments to answer citizen questions about taxes, permits, social services, and urban infrastructure — automating thousands of monthly interactions with zero code changes between municipalities.
Brazilian municipal governments receive high volumes of repetitive citizen inquiries through WhatsApp. This framework provides a hardened base that handles the operational complexity (webhook dedup, error alerting, PII-safe logging, Docker Swarm compatibility) so each new deployment only needs to supply a FAQ file and a system prompt.
| Decision | Why |
|---|---|
| BaseAgent pattern | Swap OpenAI for any LLM, or add a retrieval-augmented agent, without touching the webhook layer |
| Redis dedup | Idempotent webhook processing — safe under Chatwoot retries and multi-replica Swarm deployments |
| PII-safe structured logging | CPF, phone, email values are replaced with [REDACTED] before any log output |
| Admin notifications on errors | On-call engineers receive WhatsApp alerts when an agent raises an unhandled exception; flood guard prevents storms |
| Docker Swarm compatible | deploy: block in compose, multi-stage Dockerfile, non-root container user |
Citizen
|
| WhatsApp message
v
Evolution API (or Chatwoot inbox)
|
| POST /webhook/chatwoot
v
┌─────────────────────────────────────────────┐
│ FastAPI Webhook Router │
│ 1. HMAC signature verify (optional) │
│ 2. parse_webhook() → session_id + message │
│ 3. Redis dedup (SET NX / TTL 60s) │
│ 4. send_typing() indicator │
└────────────────┬────────────────────────────┘
│
v
┌────────────────┐
│ BaseAgent.run │ ← catches exceptions → notify_admin()
└───────┬────────┘
│
v
┌────────────────┐
│ agent.process │
│ (FAQAgent) │
│ 1. Keyword │
│ search FAQ │
│ 2. OpenAI │
│ gpt-4.1 │
└───────┬────────┘
│ reply text
v
chatwoot.send_message()
|
v
Citizen sees reply
govtech-citizen-chatbot-template/
├── app/
│ ├── agent/
│ │ ├── base.py # BaseAgent abstract class
│ │ └── prompts.py # System prompt templates + safety rules
│ ├── agents/
│ │ ├── faq_agent.py # FAQ + OpenAI fallback (production)
│ │ └── echo_agent.py # Echo agent (testing/demo)
│ ├── core/
│ │ ├── config.py # Pydantic Settings (all env vars)
│ │ ├── dedup.py # Redis SET NX deduplication
│ │ ├── logging.py # structlog + PII redaction processor
│ │ └── notify.py # Admin WhatsApp error notifications
│ ├── integrations/
│ │ ├── chatwoot.py # Chatwoot API + webhook parser
│ │ └── whatsapp.py # Evolution API + webhook parser
│ ├── webhook/
│ │ └── chatwoot.py # FastAPI router POST /webhook/chatwoot
│ └── main.py # App entry point, /health endpoint
├── agents/
│ └── faq/
│ └── faq_data.yaml # Example FAQ data (edit per municipality)
├── docker-compose.yml
├── Dockerfile
├── requirements.txt
└── .env.example
Three steps to add a custom agent:
Step 1 — Extend BaseAgent
# app/agents/my_agent.py
from app.agent.base import BaseAgent
class MyAgent(BaseAgent):
name = "Assistente da Prefeitura de Campinas"
async def process(self, session_id: str, message: str, metadata: dict) -> str:
# Your logic here: call OpenAI, query a database, hit an API...
return f"Olá! Você disse: {message}"Step 2 — Register in the webhook router
# app/webhook/chatwoot.py (inside _load_agent())
if agent_type == "my":
from app.agents.my_agent import MyAgent
return MyAgent()Step 3 — Set in environment
AGENT_TYPE=myThat's it. The webhook router, dedup, logging, and error notifications all work automatically.
# 1. Clone
git clone https://github.com/LufeDigitalWave/govtech-citizen-chatbot-template.git
cd govtech-citizen-chatbot-template
# 2. Configure
cp .env.example .env
# Edit .env — at minimum set AGENT_TYPE=echo for a smoke test (no OpenAI needed)
# 3. Start services
docker compose up -d
# 4. Expose locally with ngrok (for Chatwoot webhook)
ngrok http 8000
# Copy the HTTPS URL: https://xxxx.ngrok.io
# 5. Register webhook in Chatwoot
# Settings > Integrations > Webhooks > New Webhook
# URL: https://xxxx.ngrok.io/webhook/chatwoot
# Events: message_created
# 6. Test with curl
curl -X POST http://localhost:8000/webhook/chatwoot \
-H "Content-Type: application/json" \
-d '{
"event": "message_created",
"message": {"content": "Qual o horário de atendimento?", "message_type": 0, "id": "test-001"},
"conversation": {"id": 1, "inbox_id": 1},
"contact": {"name": "Cidadão Teste"},
"account": {"id": 1}
}'
# 7. Check health
curl http://localhost:8000/healthTo switch to the full FAQ agent with OpenAI:
# In .env
AGENT_TYPE=faq
OPENAI_API_KEY=sk-proj-...
AGENT_CITY=Campinas
# Restart
docker compose up -d apiEdit agents/faq/faq_data.yaml to add your municipality's real FAQ content.
# Build and push image
docker build -t your-registry.io/govtech-chatbot:latest .
docker push your-registry.io/govtech-chatbot:latest
# Deploy stack
docker stack deploy -c docker-compose.yml chatbot
# Rolling update with zero downtime (update_config: start-first is set)
docker service update \
--image your-registry.io/govtech-chatbot:v1.1.0 \
chatbot_api
# Inject secrets via Docker secrets (recommended over env vars for keys)
echo "sk-proj-..." | docker secret create openai_api_key -
docker service update \
--secret-add openai_api_key \
chatbot_apiScale the API service independently of Redis (Redis stays on the manager):
docker service scale chatbot_api=4| Variable | Default | Description |
|---|---|---|
AGENT_TYPE |
faq |
Agent to load: faq or echo |
AGENT_CITY |
Município |
City name in system prompt |
FAQ_PATH |
agents/faq/faq_data.yaml |
Path to FAQ YAML |
OPENAI_API_KEY |
— | Required for FAQAgent |
OPENAI_MODEL |
gpt-4.1 |
OpenAI chat model |
CHATWOOT_URL |
— | Chatwoot base URL |
CHATWOOT_API_TOKEN |
— | Chatwoot user token |
CHATWOOT_ACCOUNT_ID |
1 |
Chatwoot account ID |
EVOLUTION_URL |
— | Evolution API base URL |
EVOLUTION_API_KEY |
— | Evolution API key |
EVOLUTION_INSTANCE |
default |
Evolution instance name |
ADMIN_PHONE |
— | WhatsApp for error alerts |
REDIS_URL |
redis://localhost:6379/0 |
Redis connection |
WEBHOOK_SECRET |
— | HMAC secret for webhook sig |
LOG_LEVEL |
INFO |
Root log level |
JSON_LOGS |
false |
JSON logs for aggregators |
pip install -r requirements.txt
pytest tests/ -vMIT — see LICENSE.
Built with FastAPI, structlog, Redis, and OpenAI. Deployed in production for Brazilian municipal government citizen services.