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.
This project uses Poetry for packaging and dependency management, so install that first.
Ensure you're also running at least python 3.11, python --version.
Then you can install the dependencies:
poetry installThis project ships with a local async stack:
api: Flask + Gunicorn on port5000worker: Celery workerrabbitmq: internal broker (no host ports exposed)
- Create your local env file:
cp .env.docker.example .env.docker- Fill in required values in
.env.docker:
TURN_HMAC_SECRETTURN_API_BASE_URLTURN_API_TOKEN
- Spin up the stack:
docker compose --env-file .env.docker up --build- Verify the API is up:
curl http://localhost:5000/metrics- Expose your local API with ngrok (host machine):
ngrok http 5000Then point your webhook sender at:
https://<your-ngrok-subdomain>.ngrok-free.app/nlu/
- Quick local endpoint check (without signature, should return
401):
curl -i -X POST http://localhost:5000/nlu/ \
-H "Content-Type: application/json" \
-d '{"messages":[]}'To run Flask directly:
poetry run flask --app src.application runTo run Celery directly (requires RabbitMQ or another broker):
poetry run celery -A src.celery_app worker --loglevel=info --concurrency=4For synchronous local testing (no broker):
export CELERY_TASK_ALWAYS_EAGER=trueTo run the autoformatting and linting:
poetry run ruff format && poetry run ruff check && poetry run mypy --install-typesFor the test runner, we use pytest:
poetry run pytest- Delete the JSON embeddings file in
src/data/. - Update
nlu.yamlwith your changes. - Run the Flask app. This should regenerate the embeddings file:
poetry run flask --app src.application runIf you'd like your editor to handle linting and/or formatting for you, here's how to set it up.
- Install the Python and Ruff extensions.
- In settings, check the "Python > Linting: Mypy Enabled" box.
- In settings, set the "Python > Formatting: Provider" to "black" (apparently
ruff formatis not supported by the Python extension yet, andblackis probably close enough). - If you want formatting to apply automatically, in settings, check the "Editor: Format On Save" checkbox.
Alternatively, add the following to your settings.json:
{
"python.linting.mypyEnabled": true,
"python.formatting.provider": "black",
"editor.formatOnSave": true
}To release a new version, follow these steps:
- Make sure all relevant PRs are merged and that all necessary QA testing is complete.
- Make sure release notes are up to date and accurate.
- In one commit on the
mainbranch:- Update the version number in
pyproject.tomlto the release version - Replace the UNRELEASED header in
CHANGELOG.mdwith the release version and date
- Update the version number in
- Tag the release commit with the release version (for example,
v0.2.1for version0.2.1). - Push the release commit and tag.
- In one commit on the
mainbranch:- Update the version number in
pyproject.tomlto the next pre-release version - Add a new UNRELEASED header in
CHANGELOG.md
- Update the version number in
- Push the post-release commit.
There is a docker image that can be used to run this service. It uses the following environment variables for configuration:
| Variable | Description | Required |
|---|---|---|
| TURN_HMAC_SECRET | Shared secret used to validate the X-Turn-Hook-Signature header on /nlu/ requests |
Yes |
| SENTRY_DSN | Where to send exceptions | No |
| CELERY_BROKER_URL | RabbitMQ connection URL (for example, amqp://user:pass@host:5672/) |
Yes (for async processing) |
| CELERY_TASK_ALWAYS_EAGER | Set to true for synchronous task execution (testing only) |
No |
| TURN_API_BASE_URL | Turn API base URL (for example, https://whatsapp.turn.io) |
Yes (for Turn integration) |
| TURN_API_TOKEN | Turn API authentication token (Bearer token) | Yes (for Turn integration) |
Note: You need to run both the Flask app (webhook receiver) and Celery worker (task processor) containers for full functionality.