Skip to content

Commit adb09c2

Browse files
committed
Add dockerfile and update readme
1 parent f6ce695 commit adb09c2

4 files changed

Lines changed: 174 additions & 46 deletions

File tree

.env.docker.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copy this file to .env.docker and set real values before running compose.
2+
3+
# Shared app + worker configuration
4+
TURN_HMAC_SECRET=replace-with-turn-webhook-secret
5+
TURN_API_BASE_URL=https://whatsapp.turn.io
6+
TURN_API_TOKEN=replace-with-turn-api-token
7+
8+
# Optional
9+
SENTRY_DSN=

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ pip-selfcheck.json
3838
# Environments
3939
.env/
4040
.venv/
41+
.env.docker
42+
.env
4143

4244
# Installer logs
4345
pip-log.txt

README.md

Lines changed: 100 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,161 @@
11
# MomConnect Intent Classifier
22

3-
Model that classifies the intent of inbound messages. It is not intended to be exposed to the outside world, we only have it accisble inside the cluster.
3+
Model that classifies the intent of inbound messages. It is not intended to be exposed to the outside world; we only have it accessible inside the cluster.
44

55
## Development
6-
This project uses [poetry](https://python-poetry.org/docs/#installation) for packaging and dependancy management, so install that first.
6+
7+
This project uses [Poetry](https://python-poetry.org/docs/#installation) for packaging and dependency management, so install that first.
78

89
Ensure you're also running at least python 3.11, `python --version`.
910

10-
Then you can install the dependencies
11+
Then you can install the dependencies:
12+
13+
```bash
14+
poetry install
15+
```
16+
17+
### Local Stack with Docker Compose
18+
19+
This project ships with a local async stack:
20+
21+
- `api`: Flask + Gunicorn on port `5000`
22+
- `worker`: Celery worker
23+
- `rabbitmq`: internal broker (no host ports exposed)
24+
25+
1. Create your local env file:
26+
27+
```bash
28+
cp .env.docker.example .env.docker
29+
```
30+
31+
1. Fill in required values in `.env.docker`:
32+
33+
- `TURN_HMAC_SECRET`
34+
- `TURN_API_BASE_URL`
35+
- `TURN_API_TOKEN`
36+
37+
1. Spin up the stack:
38+
39+
```bash
40+
docker compose --env-file .env.docker up --build
41+
```
42+
43+
1. Verify the API is up:
44+
1145
```bash
12-
~ poetry install
46+
curl http://localhost:5000/metrics
1347
```
1448

15-
To run a local worker, set NLU_USERNAME and NLU_PASSWORD environment variables, then start up the flask worker
49+
1. Expose your local API with ngrok (host machine):
50+
1651
```bash
17-
~ poetry run flask --app src.application run
52+
ngrok http 5000
1853
```
1954

20-
### Running the Celery Worker
55+
Then point your webhook sender at:
56+
57+
```text
58+
https://<your-ngrok-subdomain>.ngrok-free.app/nlu/
59+
```
2160

22-
For async task processing, you need to run a Celery worker. First, ensure you have RabbitMQ running (or another message broker configured via `CELERY_BROKER_URL`).
61+
1. Quick local endpoint check (without signature, should return `401`):
2362

24-
To start the Celery worker:
2563
```bash
26-
~ poetry run celery -A src.celery_app worker --loglevel=info --concurrency=4
64+
curl -i -X POST http://localhost:5000/nlu/ \
65+
-H "Content-Type: application/json" \
66+
-d '{"messages":[]}'
2767
```
2868

29-
To test the Celery connection, you can run the hello world task:
30-
```python
31-
from src.tasks import hello_world
32-
result = hello_world.delay("Test")
33-
print(result.get()) # Should print: {'status': 'success', 'message': 'Hello, Test!'}
69+
### Local Python-Only Run (No Docker)
70+
71+
To run Flask directly:
72+
73+
```bash
74+
poetry run flask --app src.application run
3475
```
3576

36-
For local development with synchronous execution (no RabbitMQ needed), set:
77+
To run Celery directly (requires RabbitMQ or another broker):
78+
79+
```bash
80+
poetry run celery -A src.celery_app worker --loglevel=info --concurrency=4
81+
```
82+
83+
For synchronous local testing (no broker):
84+
3785
```bash
3886
export CELERY_TASK_ALWAYS_EAGER=true
3987
```
4088

41-
To run the autoformatting and linting, run
89+
To run the autoformatting and linting:
90+
4291
```bash
43-
~ ruff format && ruff check && mypy --install-types
92+
poetry run ruff format && poetry run ruff check && poetry run mypy --install-types
4493
```
4594

4695
For the test runner, we use [pytest](https://docs.pytest.org/):
96+
4797
```bash
48-
~ pytest
98+
poetry run pytest
4999
```
50100

51-
## Regenerating the embeddings json file
101+
## Regenerating the embeddings JSON file
52102

53-
1. Delete the json embeddings file in src/data/
54-
1. Update the nlu.yaml with your changes
55-
1. Run the flask app, this should regenerate the embeddings file. `poetry run flask --app src.application run`
103+
1. Delete the JSON embeddings file in `src/data/`.
104+
1. Update `nlu.yaml` with your changes.
105+
1. Run the Flask app. This should regenerate the embeddings file:
106+
107+
```bash
108+
poetry run flask --app src.application run
109+
```
56110

57111
## Editor configuration
58112

59113
If you'd like your editor to handle linting and/or formatting for you, here's how to set it up.
60114

61115
### Visual Studio Code
62116

63-
1. Install the Python and Ruff extensions
64-
1. In settings, check the "Python > Linting: Mypy Enabled" box
65-
1. In settings, set the "Python > Formatting: Provider" to "black" (apparently "ruff format" isn't supported by the Python extension yet and "black" is probably close enough)
66-
1. If you want to have formatting automatically apply, in settings, check the "Editor: Format On Save" checkbox
117+
1. Install the Python and Ruff extensions.
118+
1. In settings, check the "Python > Linting: Mypy Enabled" box.
119+
1. In settings, set the "Python > Formatting: Provider" to "black" (apparently `ruff format` is not supported by the Python extension yet, and `black` is probably close enough).
120+
1. If you want formatting to apply automatically, in settings, check the "Editor: Format On Save" checkbox.
67121

68122
Alternatively, add the following to your `settings.json`:
123+
69124
```json
70125
{
71-
"python.linting.mypyEnabled": true,
72-
"python.formatting.provider": "black",
73-
"editor.formatOnSave": true,
126+
"python.linting.mypyEnabled": true,
127+
"python.formatting.provider": "black",
128+
"editor.formatOnSave": true
74129
}
75130
```
76131

77132
## Release process
78133

79134
To release a new version, follow these steps:
80135

81-
1. Make sure all relevant PRs are merged and that all necessary QA testing is complete
82-
1. Make sure release notes are up to date and accurate
136+
1. Make sure all relevant PRs are merged and that all necessary QA testing is complete.
137+
1. Make sure release notes are up to date and accurate.
83138
1. In one commit on the `main` branch:
84139
- Update the version number in `pyproject.toml` to the release version
85140
- Replace the UNRELEASED header in `CHANGELOG.md` with the release version and date
86-
1. Tag the release commit with the release version (for example, `v0.2.1` for version `0.2.1`)
87-
1. Push the release commit and tag
141+
1. Tag the release commit with the release version (for example, `v0.2.1` for version `0.2.1`).
142+
1. Push the release commit and tag.
88143
1. In one commit on the `main` branch:
89144
- Update the version number in `pyproject.toml` to the next pre-release version
90145
- Add a new UNRELEASED header in `CHANGELOG.md`
91-
1. Push the post-release commit
146+
1. Push the post-release commit.
92147

93148
## Running in Production
94-
There is a [docker image](https://github.com/praekeltfoundation/mc-intent-classifier/pkgs/container/mc-intent-classifier) that can be used to easily run this service. It uses the following environment variables for configuration:
95-
96-
| Variable | Description | Required |
97-
| ---------- | ----------- | -------- |
98-
| NLU_USERNAME | The username used for API requests | Yes (for /nlu/ endpoint) |
99-
| NLU_PASSWORD | The password used for API requests | Yes (for /nlu/ endpoint) |
100-
| SENTRY_DSN | Where to send exceptions to | No |
101-
| CELERY_BROKER_URL | RabbitMQ connection URL (e.g., amqp://user:pass@host:5672/) | Yes (for async processing) |
102-
| CELERY_RESULT_BACKEND | Result backend URL (e.g., rpc://) | No (defaults to rpc://) |
103-
| CELERY_TASK_ALWAYS_EAGER | Set to "true" for synchronous task execution (testing only) | No |
104-
| TURN_API_BASE_URL | Turn API base URL (e.g., https://whatsapp.turn.io) | Yes (for Turn integration) |
149+
150+
There is a [docker image](https://github.com/praekeltfoundation/mc-intent-classifier/pkgs/container/mc-intent-classifier) that can be used to run this service. It uses the following environment variables for configuration:
151+
152+
| Variable | Description | Required |
153+
| --- | --- | --- |
154+
| TURN_HMAC_SECRET | Shared secret used to validate the `X-Turn-Hook-Signature` header on `/nlu/` requests | Yes |
155+
| SENTRY_DSN | Where to send exceptions | No |
156+
| CELERY_BROKER_URL | RabbitMQ connection URL (for example, `amqp://user:pass@host:5672/`) | Yes (for async processing) |
157+
| CELERY_TASK_ALWAYS_EAGER | Set to `true` for synchronous task execution (testing only) | No |
158+
| TURN_API_BASE_URL | Turn API base URL (for example, `https://whatsapp.turn.io`) | Yes (for Turn integration) |
105159
| TURN_API_TOKEN | Turn API authentication token (Bearer token) | Yes (for Turn integration) |
106160

107161
**Note:** You need to run both the Flask app (webhook receiver) and Celery worker (task processor) containers for full functionality.

docker-compose.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
services:
2+
rabbitmq:
3+
image: rabbitmq:3-management
4+
healthcheck:
5+
test: ["CMD", "rabbitmq-diagnostics", "ping"]
6+
interval: 10s
7+
timeout: 5s
8+
retries: 5
9+
volumes:
10+
- rabbitmq_data:/var/lib/rabbitmq
11+
12+
api:
13+
build:
14+
context: .
15+
command:
16+
[
17+
"/.venv/bin/gunicorn",
18+
"application:app",
19+
"-b",
20+
"0.0.0.0:5000",
21+
"-w",
22+
"4",
23+
]
24+
working_dir: /src
25+
depends_on:
26+
rabbitmq:
27+
condition: service_healthy
28+
ports:
29+
- "5000:5000"
30+
environment:
31+
TURN_HMAC_SECRET: ${TURN_HMAC_SECRET}
32+
TURN_API_BASE_URL: ${TURN_API_BASE_URL}
33+
TURN_API_TOKEN: ${TURN_API_TOKEN}
34+
SENTRY_DSN: ${SENTRY_DSN:-}
35+
CELERY_BROKER_URL: amqp://guest:guest@rabbitmq:5672//
36+
CELERY_TASK_ALWAYS_EAGER: "false"
37+
38+
worker:
39+
build:
40+
context: .
41+
command:
42+
[
43+
"/.venv/bin/celery",
44+
"-A",
45+
"src.celery_app",
46+
"worker",
47+
"--loglevel=info",
48+
"--concurrency=4",
49+
]
50+
working_dir: /src
51+
depends_on:
52+
rabbitmq:
53+
condition: service_healthy
54+
environment:
55+
TURN_HMAC_SECRET: ${TURN_HMAC_SECRET}
56+
TURN_API_BASE_URL: ${TURN_API_BASE_URL}
57+
TURN_API_TOKEN: ${TURN_API_TOKEN}
58+
SENTRY_DSN: ${SENTRY_DSN:-}
59+
CELERY_BROKER_URL: amqp://guest:guest@rabbitmq:5672//
60+
CELERY_TASK_ALWAYS_EAGER: "false"
61+
62+
volumes:
63+
rabbitmq_data:

0 commit comments

Comments
 (0)