Classifier
AI-Powered Content Classification System¶
Overview¶
Kitsune's AI-powered classification system automatically analyzes user-submitted content (support questions and Zendesk tickets) to detect spam, validate product assignment, and classify content into appropriate topics. The system uses Large Language Models (LLMs) to provide three-stage classification that helps route content correctly and reduce moderation burden.
Classification Stages¶
The classifier performs up to three sequential analyses:
- Spam Detection - Identifies spam, scams, and inappropriate content
- Product Validation - Checks if content belongs to the correct product (with potential reassignment)
- Topic Classification - Categorizes content into the product's taxonomy
Architecture¶
Core Components¶
1. Spam Classifier (kitsune/llm/spam/classifier.py)¶
The spam classifier analyzes content and returns a confidence score (0-100) along with reasoning.
Confidence Thresholds: - ≥75%: High confidence spam → Automatic spam action - 60-74%: Medium confidence → Flag for manual review - <60%: Low confidence → Not spam, continue to product/topic classification
Special Flag - maybe_misclassified:
When the LLM detects spam but suspects it might be legitimate content posted to the wrong product, it sets this flag. This triggers a product reassignment check before marking as spam.
2. Product Classifier (kitsune/llm/categorization/classifiers.py)¶
Analyzes whether content belongs to a different product than initially assigned. This handles cases where users: - Select the wrong product in the form - Post content about multiple products - Submit generic questions that better fit another product
The classifier can be configured to only consider products with forum support (only_with_forums=True).
3. Topic Classifier (kitsune/llm/categorization/classifiers.py)¶
Classifies content into the product's topic taxonomy. Topics are hierarchical (e.g., "Sync and Save > Sync Your Profile") and used for: - Routing to appropriate support channels - Generating analytics - Creating searchable tags (Zendesk)
Entry Points¶
The classifier has two main entry points with different behaviors:
classify_question()- For support questions (AAQ flow)classify_zendesk_submission()- For Zendesk support tickets
Classification Flows¶
Questions (AAQ) Flow¶
graph TD
A[New Question Submitted] --> B[Spam Detection]
B --> C{Is Spam?}
C -->|High Confidence ≥75%| D{maybe_misclassified?}
C -->|Medium 60-74%| E[Flag for Review]
C -->|Low <60%| F[Topic Classification]
D -->|Yes| G[Product Reassignment Check]
D -->|No| H[Mark as Spam]
G --> I{Different Product<br/>with Forums?}
I -->|Yes| J[Reassign Product]
I -->|No| H
J --> F
E --> END1[Manual Review Queue]
F --> K[Update Question]
K --> L[Flag for Content Moderation]
H --> M[Flag as Spam]
END1:::pending
L:::success
M:::spam
classDef spam fill:#f96,stroke:#333,stroke-width:2px
classDef success fill:#9f6,stroke:#333,stroke-width:2px
classDef pending fill:#fc9,stroke:#333,stroke-width:2px
Key Characteristics: - Only considers products with forums for reassignment (Questions require forum support) - Updates question's product and topic fields if reassigned/classified - Creates automatic tags based on topic - All questions get flagged for content moderation with classification results
Zendesk Flow¶
graph TD
A[New Support Ticket] --> B{Skip Spam<br/>Moderation?}
B -->|Yes - Config Set| C[Product Reassignment Check]
B -->|No - Normal Flow| D[Spam Detection]
D --> E{Is Spam?}
E -->|High Confidence ≥75%| F{maybe_misclassified?}
E -->|Medium 60-74%| G[Flag for Review]
E -->|Low <60%| C
F -->|Yes| H[Product Reassignment Check]
F -->|No| I{Waffle Switch<br/>zendesk-spam-classifier}
I -->|Active| J[Flag for Moderation]
I -->|Inactive| K[Auto-Reject]
H --> L{Different Product?}
C --> L
L -->|Yes| M{Original Product<br/>has Ticketing?}
L -->|No| N[Topic Classification]
M -->|Yes| O[Send to Zendesk with 'other' tag]
M -->|No| P[Flag for Review]
N --> Q[Generate Tier Tags]
Q --> R[Send to Zendesk]
G --> END1[Manual Review Queue]
J --> END1
P --> END1
K --> END2[Rejected]
O --> END3[Sent to Zendesk]
R --> END3
END1:::pending
END2:::rejected
END3:::success
classDef success fill:#9f6,stroke:#333,stroke-width:2px
classDef pending fill:#fc9,stroke:#333,stroke-width:2px
classDef rejected fill:#f96,stroke:#333,stroke-width:2px
Key Characteristics: - Considers all products for reassignment (not just those with forums) - Can skip spam detection entirely via product configuration - Special handling for product reassignment based on ticketing support - Generates hierarchical tier tags (t1-, t2-, t3-) for Zendesk routing - Waffle switch controls spam moderation vs auto-rejection
Product Reassignment Logic¶
Questions (AAQ)¶
When spam is flagged with maybe_misclassified=True:
- Run product classifier with
only_with_forums=True - If different product found with forum support:
- Change action to
NOT_SPAM - Reassign question to new product
- Run topic classification for new product
- If no suitable product found:
- Keep spam classification
Why only products with forums? Questions require a forum for discussion, so reassignment only makes sense to products that support this channel.
Zendesk¶
Product reassignment can happen in two scenarios:
Scenario 1: Spam with maybe_misclassified=True
- Check all products (not just those with forums)
- Decision based on original product's ticketing support:
- Original has ticketing: Send to Zendesk with "other" tag (let Zendesk handle routing)
- Original lacks ticketing: Flag for manual review (might need forum-based support)
Scenario 2: Not spam, routine check - Check all products for better match - Decision logic same as Scenario 1
Why check ticketing support? If the original product has Zendesk integration, a misclassified product can still be routed within Zendesk using the "other" tag. If the original product lacks ticketing, the user might need forum-based support instead, requiring human review.
Zendesk Tag Generation¶
When a Zendesk ticket is classified as NOT_SPAM, the system generates tags for routing:
Tier Tags¶
Topics are hierarchical. Example: Sync and Save > Sync Your Profile > Sync Your Passwords
This generates tier tags:
- t1-sync-and-save
- t2-sync-your-profile
- t3-sync-your-passwords
Additional Tags¶
- Legacy tags: Backward-compatible tags for existing Zendesk workflows (e.g., "general", "bookmarks")
- Automation tags: Trigger specific Zendesk automations based on topic
- "other" tag: Added when product reassignment occurred
- "undefined" tag: Used when topic classification fails or returns "Undefined"
- System tags: Preserved tags like "loginless_ticket", "stage"
Tag Conversion Rules¶
Topics → Tags transformation:
- Convert to lowercase
- Remove punctuation (, .)
- Replace spaces with hyphens
- Example: "Sync and Save" → "sync-and-save"
Configuration¶
Feature Flags (Waffle Switches)¶
auto-question-classifier (Questions)
- Active: Run full classification pipeline
- Inactive: Flag all questions for manual review (with flagit-spam-autoflag as fallback)
flagit-spam-autoflag (Questions)
- Active: Auto-flag questions when auto-question-classifier is disabled
- Only used as fallback mechanism
zendesk-spam-classifier (Zendesk)
- Active: Spam tickets go to moderation queue for human review
- Inactive: Spam tickets are automatically rejected (fully automated)
Product-Level Configuration¶
Skip Spam Moderation¶
Products can bypass spam detection entirely via ProductSupportConfig.zendesk_config.skip_spam_moderation:
config = product.support_configs.get(is_active=True)
if config.zendesk_config.skip_spam_moderation:
# Skip spam detection, go straight to product/topic classification
Use case: High-trust products or products with external spam filtering.
Ticketing Support¶
Products declare Zendesk integration via Product.has_ticketing_support:
@property
def has_ticketing_support(self):
return self.support_configs.filter(
is_active=True,
zendesk_config__isnull=False,
).exists()
This property influences product reassignment behavior in Zendesk flow.
LLM Configuration¶
The classifier uses the default LLM configured in kitsune/llm/utils.py via get_llm(). Model selection happens at runtime and can be configured via Django settings.
Retry Logic: All classifier chains use automatic retry with exponential backoff (via build_chain_with_retry()).
Processing Pipeline¶
Celery Tasks¶
question_classifier (kitsune/llm/tasks.py)
- Triggered when new question is created
- Checks auto-question-classifier waffle switch
- Calls classify_question() and process_classification_result()
- Retries on failure (max 3 retries with exponential backoff)
zendesk_submission_classifier (kitsune/customercare/tasks.py)
- Triggered when new support ticket is created
- Always runs (waffle switch only affects spam handling)
- Calls classify_zendesk_submission() and process_zendesk_classification_result()
- On failure after retries: marks ticket as STATUS_PROCESSING_FAILED
process_moderation_queue (kitsune/llm/tasks.py)
- Periodic task to reprocess flagged questions
- Only runs if auto-question-classifier is active
- Handles stale flags from previous classification attempts
process_failed_zendesk_tickets (kitsune/customercare/tasks.py)
- Safety net for tickets where classification failed
- Sends to Zendesk with whatever tags are currently set
auto_reject_old_zendesk_spam (kitsune/customercare/tasks.py)
- Auto-rejects spam tickets older than 14 days
- Prevents spam from accumulating in moderation queue
Moderation Actions¶
The classifier returns one of three actions:
ModerationAction.NOT_SPAM¶
- Content is legitimate
- Product/topic fields updated if needed
- Question: Flagged for content moderation with classification notes
- Zendesk: Sent to Zendesk with generated tags
ModerationAction.SPAM¶
- High confidence spam (≥75%)
- Question:
mark_as_spam()called, flagged as spam - Zendesk: Behavior depends on
zendesk-spam-classifierswitch - Active: Flag for manual review
- Inactive: Auto-reject (
STATUS_REJECTED)
ModerationAction.FLAG_REVIEW¶
- Medium confidence spam (60-74%)
- Both: Flagged with
FLAG_PENDINGstatus for manual review - Includes LLM reasoning in flag notes
Implementation Details¶
Spam Detection with Confidence¶
The spam classifier returns structured output:
{
"is_spam": bool,
"confidence": int, # 0-100
"reason": str,
"maybe_misclassified": bool # Optional flag
}
Confidence determines action:
def determine_action_from_spam_result(spam_result: dict):
confidence = spam_result.get("confidence", 0)
if confidence >= 75:
return ModerationAction.SPAM
elif confidence > 60:
return ModerationAction.FLAG_REVIEW
else:
return ModerationAction.NOT_SPAM
Product Reassignment Helper¶
Both flows use a shared helper _handle_product_reassignment():
def _handle_product_reassignment(
payload: dict[str, Any],
product: Product,
only_with_forums: bool,
on_reassignment: Callable[[dict[str, Any], Product], dict[str, Any]],
) -> dict[str, Any] | None
This encapsulates: 1. Running product classifier 2. Checking if product actually changed 3. Calling the appropriate reassignment handler 4. Returning results or None if no reassignment
Topic Hierarchy Handling¶
Topic classification may return hierarchical paths like:
- "Sync and Save > Sync Your Profile"
- "Bookmarks.History.Cookies"
- "Privacy / Security :: Passwords"
The system extracts the most specific topic using get_most_specific():
# Recognizes separators: ">", ";", " / ", " - ", ".", "::", ":", "|"
get_most_specific("Sync and Save > Sync Your Profile")
# Returns: "Sync Your Profile"
Exception Handling¶
Classification Failures¶
Questions: - Task retries up to 3 times with exponential backoff - If all retries fail: question remains unflagged (may be picked up by periodic queue processor)
Zendesk:
- Task retries up to 3 times
- After all retries: ticket marked STATUS_PROCESSING_FAILED
- Periodic task process_failed_zendesk_tickets sends these to Zendesk with current tags
Missing Topics¶
If topic classification returns "Undefined" or topic isn't found in database: - Question: Uses product's default topic (if any) - Zendesk: Uses "undefined" + "general" tags
Product Not Found¶
If product reassignment suggests a product that doesn't exist in the database: - Reassignment is skipped - Original product is retained - Classification continues with original product's taxonomy
Common Scenarios¶
Scenario 1: Clean Question Submission¶
- User submits Firefox question about bookmarks
- Spam classifier: 15% confidence →
NOT_SPAM - Product classifier: Confirms Firefox (no reassignment)
- Topic classifier: "Bookmarks, History and Tabs > Bookmarks"
- Question updated with topic, flagged for content moderation
Scenario 2: Wrong Product Selection¶
- User submits about "Pocket" but selected "Firefox"
- Spam classifier: 20% confidence →
NOT_SPAM - Product classifier: Detects "Pocket" product
- Question reassigned to Pocket
- Topic classifier runs with Pocket's taxonomy
- Question updated, flagged for content moderation
Scenario 3: Misclassified Spam (Question)¶
- User posts legitimate Mozilla Account question
- Spam classifier: 80% confidence,
maybe_misclassified=True - Product reassignment check: Finds "Mozilla Account" product (has forums)
- Action changed to
NOT_SPAM, question reassigned - Topic classification runs for Mozilla Account
- Question updated, flagged for content moderation
Scenario 4: Misclassified Spam (Zendesk, Original Product Has Ticketing)¶
- User submits VPN question to wrong product
- Spam classifier: 85% confidence,
maybe_misclassified=True - Product reassignment check: Finds "Mozilla VPN" product
- Original product has ticketing support
- Send to Zendesk with "other" tag (Zendesk will route)
- No topic classification needed
Scenario 5: Misclassified Spam (Zendesk, Original Product Lacks Ticketing)¶
- User submits question to product without Zendesk integration
- Spam classifier: 85% confidence,
maybe_misclassified=True - Product reassignment check: Finds different product
- Original product lacks ticketing support
- Action:
FLAG_REVIEW(might need forum support) - Human review required
Scenario 6: Skip Spam for Trusted Product¶
- Product configured with
skip_spam_moderation=True - Spam detection bypassed entirely
- Product reassignment check runs
- Topic classification runs
- Ticket sent to Zendesk with tags
Scenario 7: High Confidence Spam (Zendesk, Auto-reject)¶
- Obvious spam submitted
- Spam classifier: 95% confidence
zendesk-spam-classifierswitch inactive- Ticket auto-rejected (
STATUS_REJECTED) - No flagged object created
Scenario 8: Medium Confidence Spam¶
- Suspicious content submitted
- Spam classifier: 65% confidence
- Action:
FLAG_REVIEW - Question/ticket flagged with
FLAG_PENDING - Waits in moderation queue for human review
Summary of Key Differences¶
| Aspect | Questions (AAQ) | Zendesk |
|---|---|---|
| Product Reassignment Scope | Only products with forums | All products |
| Reassignment Decision Factor | N/A (forums required) | Original product's ticketing support |
| Spam Moderation Bypass | Not available | Via skip_spam_moderation config |
| Spam Handling Switch | auto-question-classifier |
zendesk-spam-classifier |
| Tag Generation | Auto-tags based on topic | Hierarchical tier tags (t1-, t2-, t3-) |
| Failed Classification | Silent failure (retry later) | Marked failed, sent without full classification |
| Output | Updated Question record + FlaggedObject | Zendesk ticket with tags |
Monitoring and Observability¶
FlaggedObject Records¶
All classification results create FlaggedObject records with:
- Reason: REASON_SPAM or REASON_CONTENT_MODERATION
- Status: FLAG_PENDING or FLAG_ACCEPTED
- Notes: LLM reasoning and classification details
- Creator: SUMO Bot user
Status Tracking (Zendesk)¶
Support tickets progress through statuses:
- STATUS_NEW → Created
- STATUS_PROCESSING_FAILED → Classification failed
- STATUS_FLAGGED → Awaiting moderation
- STATUS_SENT → Sent to Zendesk
- STATUS_REJECTED → Rejected as spam
Logging¶
Classification tasks log to k.task logger with:
- Classification failures and retry attempts
- Failed ticket processing counts
- Auto-rejection counts for old spam