|
1 | 1 | # MomConnect Intent Classifier |
2 | 2 |
|
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. |
4 | 4 |
|
5 | 5 | ## 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. |
7 | 8 |
|
8 | 9 | Ensure you're also running at least python 3.11, `python --version`. |
9 | 10 |
|
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 | + |
11 | 45 | ```bash |
12 | | -~ poetry install |
| 46 | +curl http://localhost:5000/metrics |
13 | 47 | ``` |
14 | 48 |
|
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 | + |
16 | 51 | ```bash |
17 | | -~ poetry run flask --app src.application run |
| 52 | +ngrok http 5000 |
18 | 53 | ``` |
19 | 54 |
|
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 | +``` |
21 | 60 |
|
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`): |
23 | 62 |
|
24 | | -To start the Celery worker: |
25 | 63 | ```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":[]}' |
27 | 67 | ``` |
28 | 68 |
|
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 |
34 | 75 | ``` |
35 | 76 |
|
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 | + |
37 | 85 | ```bash |
38 | 86 | export CELERY_TASK_ALWAYS_EAGER=true |
39 | 87 | ``` |
40 | 88 |
|
41 | | -To run the autoformatting and linting, run |
| 89 | +To run the autoformatting and linting: |
| 90 | + |
42 | 91 | ```bash |
43 | | -~ ruff format && ruff check && mypy --install-types |
| 92 | +poetry run ruff format && poetry run ruff check && poetry run mypy --install-types |
44 | 93 | ``` |
45 | 94 |
|
46 | 95 | For the test runner, we use [pytest](https://docs.pytest.org/): |
| 96 | + |
47 | 97 | ```bash |
48 | | -~ pytest |
| 98 | +poetry run pytest |
49 | 99 | ``` |
50 | 100 |
|
51 | | -## Regenerating the embeddings json file |
| 101 | +## Regenerating the embeddings JSON file |
52 | 102 |
|
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 | +``` |
56 | 110 |
|
57 | 111 | ## Editor configuration |
58 | 112 |
|
59 | 113 | If you'd like your editor to handle linting and/or formatting for you, here's how to set it up. |
60 | 114 |
|
61 | 115 | ### Visual Studio Code |
62 | 116 |
|
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. |
67 | 121 |
|
68 | 122 | Alternatively, add the following to your `settings.json`: |
| 123 | + |
69 | 124 | ```json |
70 | 125 | { |
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 |
74 | 129 | } |
75 | 130 | ``` |
76 | 131 |
|
77 | 132 | ## Release process |
78 | 133 |
|
79 | 134 | To release a new version, follow these steps: |
80 | 135 |
|
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. |
83 | 138 | 1. In one commit on the `main` branch: |
84 | 139 | - Update the version number in `pyproject.toml` to the release version |
85 | 140 | - 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. |
88 | 143 | 1. In one commit on the `main` branch: |
89 | 144 | - Update the version number in `pyproject.toml` to the next pre-release version |
90 | 145 | - Add a new UNRELEASED header in `CHANGELOG.md` |
91 | | -1. Push the post-release commit |
| 146 | +1. Push the post-release commit. |
92 | 147 |
|
93 | 148 | ## 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) | |
105 | 159 | | TURN_API_TOKEN | Turn API authentication token (Bearer token) | Yes (for Turn integration) | |
106 | 160 |
|
107 | 161 | **Note:** You need to run both the Flask app (webhook receiver) and Celery worker (task processor) containers for full functionality. |
0 commit comments