Skip to content

Conversation

@justiandevs
Copy link

@justiandevs justiandevs commented Nov 25, 2025

Samenvatting

Deze PR integreert het werk uit de PoC: https://github.com/delta10/signal-classification-poc in Signalen. In de PoC is aangetoond dat het mogelijk is om met een (eventueel self-hosted) open-source LLM de categorie van een melding te voorspellen. De voorspelling wordt gedaan op basis van de beschikbare subcategorieën en hun omschrijvingen. Hierdoor is geen apart trainingsproces meer nodig, in tegenstelling tot het huidige TF-IDF-model. Dit maakt de oplossing flexibeler en onderhoudsvriendelijker.

Functionaliteit

Het LLM-model voorspelt de meest waarschijnlijke subcategorie op basis van:

  • De tekst van de melding
  • De omschrijvingen van de beschikbare subcategorieën
  • Alleen subcategorieën met een omschrijving worden opgenomen in de prompt en kunnen dus worden voorspeld.
  • Wanneer het model wel zeker is van een hoofdcategorie maar niet van een subcategorie, wordt teruggevallen op de subcategorie: Overig {{ hoofdcategorienaam }}
  • Wanneer het model niet zeker is van zowel de hoofdcategorie als de subcategorie, wordt teruggevallen op: Overig / Overig

Feature flags

LLM_BACKGROUND_PREDICTION_ENABLED

Zet LLM-voorspellingen op de achtergrond aan. De daadwerkelijke classificatie gebeurt nog steeds via het TF-IDF-model. Op de achtergrond wordt óók een voorspelling uitgevoerd met het LLM-model. Het resultaat hiervan wordt opgeslagen in de database tabel llm_prediction_prediction met de kolommen:
llm_predicted_category
tfidf_predicted_category
signal

LLM_FOREGROUND_PREDICTION_ENABLED

Zet LLM-voorspellingen op de voorgrond aan. De daadwerkelijke classificatie (bijvoorbeeld bij een request vanuit de frontend van Signalen) gebeurt dan met het LLM-model.

Configuratie

De gebruikte LLM wordt geconfigureerd met de volgende environment-variabelen:

LLM_API_URL – URL van het model
LLM_API_KEY – API key voor het model

Voor het testen zijn de hosted generatieve AI-modellen van Scaleway gebruikt. Getest met: mistral-small-3.2-24b-instruct-2506.

Notities

  • De LLM-voorspelling is op dit moment merkbaar trager dan de oude TF-IDF-voorspelling.
    Al uitgevoerde optimalisaties:
    max_tokens ingesteld om de response te beperken.
    Caching van de get_system_prompt() functie.
    Gekozen voor een relatief snel model.

  • De kosten van het gebruik van de Scaleway-modellen zijn nog niet volledig inzichtelijk (ik had geen toegang tot het kostenoverzicht). Dit moeten we nog goed in de gaten houden.

Testen

  • Zet één of beide feature flags aan:
    LLM_BACKGROUND_PREDICTION_ENABLED
    LLM_FOREGROUND_PREDICTION_ENABLED
  • Configureer het model via:
    LLM_API_URL
    LLM_API_KEY
  • Maak nieuwe meldingen
  • Bij background prediction:
    Controleer de resultaten in de tabel llm_prediction_prediction.
  • Verifieer de fallback-logica:
    Alleen subcategorieën met omschrijving worden voorspeld.
    Onzekere subcategorie → Overig {{ hoofdcategorienaam }}
    Onzekere hoofd- en subcategorie → Overig / Overig

@justiandevs justiandevs marked this pull request as ready for review November 26, 2025 14:29
@bartjkdp bartjkdp self-requested a review December 2, 2025 08:39
@@ -1,5 +1,5 @@
#
# This file is autogenerated by pip-compile with Python 3.13
# This file is autogenerated by pip-compile with Python 3.12
Copy link
Member

@bartjkdp bartjkdp Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misschien mooi om dit even te genereren met Python 3.13. Ik stel voor om verder een aparte requirements.txt te maken voor deze module, zoals we dat ook voor classification doen, om conflicten te voorkomen.

Zie bijvoorbeeld:

COPY app/signals/apps/classification/requirements.txt /app/signals/apps/classification/requirements.txt


def post(self, request, *args, **kwargs):
try:
if settings.LLM_FOREGROUND_PREDICTION_ENABLED:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deze kan boven het try blok, toch?

"content": text,
},
],
model='mistral-small-3.2-24b-instruct-2506',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Het is mooi om hier een instelling van te maken via settings en een environment variabele, dan kunnen we dat makkelijk instellen. Dit mag wel de default zijn.

def get_categories_with_description() -> QuerySet[tuple[str, str, str]]:
categories = (Category.objects
.filter(parent__isnull=False)
.filter(~Q(description=""), description__isnull=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misschien kunnen we deze filter op description eraf halen, dat voorkomt dat we impliciet categorieën niet meenemen.

def format_categories(categories: QuerySet[tuple[str, str, str]]) -> str:
result = []
for parent_name, name, description in categories:
result.append(f" - {parent_name} -> {name}: {description}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
result.append(f" - {parent_name} -> {name}: {description}")
result.append(f" - {parent_name} -> {name}: {description or ''}")

Copy link
Member

@bartjkdp bartjkdp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ziet er heel goed uit, nog een paar kleine wijzigingen voorgesteld.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants