Skip to content

Implement Robust Fallback for Unknown Queries#70

Open
vaibhav45sktech wants to merge 6 commits intodbpedia:masterfrom
vaibhav45sktech:fallback
Open

Implement Robust Fallback for Unknown Queries#70
vaibhav45sktech wants to merge 6 commits intodbpedia:masterfrom
vaibhav45sktech:fallback

Conversation

@vaibhav45sktech
Copy link
Contributor

@vaibhav45sktech vaibhav45sktech commented Feb 6, 2026

  • Limit fallback smart replies to DBpedia topics for faster, more relevant suggestions.

  • Update smart-reply pill layout for better tap targets and accessibility.

  • Improve fallback messaging and keyword extraction logic.

  • No breaking changes.

Resolves #56

  • Removed the BetterCodeHub from README.md
  • Since BetterCodeHub image link was no longer accessible

Summary by CodeRabbit

  • New Features

    • Contextual smart-reply suggestions now appear more reliably when fallback responses are triggered.
  • Improvements

    • Smart-reply layout is more responsive with refined spacing, reduced border and radius, subtle shadow, and smooth transitions.
    • Interactive states (hover/active/focus) strengthened for clearer feedback.
    • Fallback prompts updated with clearer guidance and example questions to better assist users.
  • Documentation

    • Removed a compliance badge from the README.

@coderabbitai
Copy link

coderabbitai bot commented Feb 6, 2026

📝 Walkthrough

Walkthrough

Implements responsive flex styling for smart-reply UI, augments TextHandler with a fallback suggestion flow that appends DBpedia-focused smart replies (using keyword extraction and Eliza as needed), and updates RiveScript fallback messages; README badge removed.

Changes

Cohort / File(s) Summary
Smart-reply styling
app/src/less/modules/smart-reply.less
Replaced fixed height/overflow with responsive flex layout (padding-top 15→12px, display:flex, flex-wrap, gap:10px); adjusted .smart-reply border/padding/radius; added color, shadow, and transition; expanded hover/active/focus states.
Fallback suggestion system
src/main/java/chatbot/lib/handlers/TextHandler.java
Added constructor overload; introduced fallbackTriggered and elizaResponded flags; added private helpers appendFallbackSuggestions and extractKeyword; set fallback conditions (punctuation-triggered Eliza or no NL replies) and append DBpedia-focused smart replies; improved sanitizeText logging.
RiveScript fallback text
src/main/resources/rivescript/scenario-text.rive
Replaced generic apology lines in fallbacktext and noresultstext with more specific guidance and example question suggestions.
Docs
README.md
Removed BCH compliance badge line.

Sequence Diagram

sequenceDiagram
    participant User
    participant TextHandler
    participant NLHandler
    participant Eliza
    participant DBpedia
    participant ResponseBuilder

    User->>TextHandler: submitText(text)
    TextHandler->>NLHandler: requestReplies(text)
    alt NL replies returned
        NLHandler-->>TextHandler: replies
    else no NL replies or punctuation-triggered fallback
        TextHandler->>Eliza: (optional) requestElizaReply(text)
        Eliza-->>TextHandler: elizaReply
        TextHandler->>TextHandler: extractKeyword(text)
        TextHandler->>DBpedia: querySuggestions(keyword)
        DBpedia-->>TextHandler: suggestions
    end
    TextHandler->>ResponseBuilder: buildResponse(replies, suggestions)
    ResponseBuilder-->>User: sendResponse(response)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Implement Robust Fallback for Unknown Queries' directly aligns with the main objective of the pull request to implement a robust fallback mechanism for handling unknown queries. It accurately reflects the core changes across the codebase.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/main/java/chatbot/lib/handlers/TextHandler.java`:
- Around line 119-122: The current assignment to fallbackPrompt in TextHandler
uses helper.getRiveScriptBot().answer(request.getUserId(),
RiveScriptReplyType.FALLBACK_TEXT)[0] which can throw
ArrayIndexOutOfBoundsException if the returned String[] is empty; update the
code to capture the String[] result from helper.getRiveScriptBot().answer(...),
check if the array length is > 0 and use result[0], otherwise assign a safe
hardcoded default fallback (e.g., "Sorry, I don't understand.") to
fallbackPrompt; reference the helper.getRiveScriptBot().answer call,
RiveScriptReplyType.FALLBACK_TEXT, and fallbackPrompt when making this change.
- Around line 81-101: The code sets fallbackTriggered true before calling
NLHandler.answer() which causes appendFallbackSuggestions to run even when
NLHandler returned a valid response; change the logic in TextHandler so that you
do not unconditionally set fallbackTriggered=true before invoking new
NLHandler(request, textMessage, helper).answer(), instead call
NLHandler.answer(), then inspect the returned responseGenerator (or its response
list via responseGenerator.getResponse().size()) and only set fallbackTriggered
(or invoke appendFallbackSuggestions) when that response is empty; update any
references to fallbackTriggered and the FALLBACK_SCENARIO branch to use the
actual result from NLHandler.answer() to decide whether to
appendFallbackSuggestions.
🧹 Nitpick comments (6)
src/main/resources/rivescript/scenario-text.rive (1)

43-47: fallbacktext and noresultstext now have identical responses — intentional?

Both triggers return the exact same string. This means the user sees identical messaging whether the bot hit its general fallback or specifically found no results, making it harder to distinguish scenarios during debugging and providing no differentiated guidance to the user. If this is intentional, consider at least adding a second - alternative to each so RiveScript can vary responses.

app/src/less/modules/smart-reply.less (2)

15-17: transition: all may cause unintended animation side-effects.

Transitioning every property (including layout-related ones like padding, border-width) can produce visual jank and hurt performance. Scope it to the properties you actually intend to animate.

♻️ Suggested change
-    transition: all 120ms ease-in-out;
+    transition: background 120ms ease-in-out, color 120ms ease-in-out, box-shadow 120ms ease-in-out;

20-27: Focus indicator may not meet WCAG 2.1 requirements.

The :focus state relies solely on a box-shadow glow with no distinct outline. Users navigating by keyboard may have difficulty identifying which pill is focused, especially on high-contrast or forced-color modes where box-shadow is dropped. Consider adding outline or using :focus-visible with a clear ring.

♿ Suggested addition
 .smart-reply:hover,
 .smart-reply:active,
 .smart-reply:focus {
     background: `@brand-primary`;
     color: `@white`;
     box-shadow: 0 4px 10px fade(`@brand-primary`, 35%);
     text-decoration: none;
 }
+
+.smart-reply:focus-visible {
+    outline: 2px solid `@brand-primary`;
+    outline-offset: 2px;
+}
src/main/java/chatbot/lib/handlers/TextHandler.java (3)

144-167: Stop-word set is re-created on every invocation; use a static final field instead.

extractKeyword allocates a new HashSet and array each time it's called. Since the stop-word list is fixed, promote it to a private static final Set<String> class constant. Also, prefer the already-imported java.util types rather than inline fully-qualified names.

♻️ Proposed refactor

Add a class-level constant:

private static final java.util.Set<String> STOP_WORDS = java.util.Set.of(
        "what", "who", "where", "why", "how", "is", "are", "the", "a", "an",
        "of", "in", "on", "for", "with", "to", "tell", "me", "about", "please");

Then simplify extractKeyword:

     private String extractKeyword(String text) {
         if (text == null || text.trim().length() == 0) {
             return null;
         }
         String cleaned = text.replaceAll("[^A-Za-z0-9 ]", " ").toLowerCase();
         String[] tokens = cleaned.split(" +");
-        String[] stop = new String[] { "what", "who", "where", "why", "how", "is", "are", "the", "a", "an", "of", "in",
-                "on", "for", "with", "to", "tell", "me", "about", "please" };
-        java.util.Set<String> stopSet = new java.util.HashSet<>();
-        for (String s : stop)
-            stopSet.add(s);
 
         String best = null;
         for (String t : tokens) {
             if (t.length() < 3)
                 continue;
-            if (stopSet.contains(t))
+            if (STOP_WORDS.contains(t))
                 continue;
             if (best == null || t.length() > best.length()) {
                 best = t;
             }
         }
         return best;
     }

59-60: Nit: redundant == true comparison.

Utility.isJSONObject(reply) already returns a boolean; the == true is unnecessary.

♻️ Suggested change
-            if (Utility.isJSONObject(reply) == true) {
+            if (Utility.isJSONObject(reply)) {

39-39: Prefer isEmpty() over size() > 0.

Minor style nit — !replacements.isEmpty() is the idiomatic Java way to check for a non-empty collection.

♻️ Suggested change
-                if (replacements.size() > 0) {
+                if (!replacements.isEmpty()) {

@vaibhav45sktech
Copy link
Contributor Author

@CodeRabbit please review

@coderabbitai
Copy link

coderabbitai bot commented Feb 6, 2026

@vaibhav45sktech, I'll review the changes in this PR right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/main/java/chatbot/lib/handlers/TextHandler.java`:
- Around line 114-132: appendFallbackSuggestions currently always adds a “I
didn't quite understand…” prompt which conflicts when the Eliza path already
added a meaningful reply (controlled by fallbackTriggered); modify
appendFallbackSuggestions (or its caller) to check the fallbackTriggered flag
(set when Eliza responds) and either skip adding the fallback prompt when
fallbackTriggered==true or switch to a non-contradictory message (e.g., "Here
are some things I can help with:") before calling
responseGenerator.addTextResponse; locate the logic in appendFallbackSuggestions
and where fallbackTriggered is set/checked to implement the conditional behavior
and adjust the prompt selection accordingly.
🧹 Nitpick comments (3)
src/main/java/chatbot/lib/handlers/TextHandler.java (3)

102-110: Eliza final-fallback (lines 107-110) is now unreachable dead code.

appendFallbackSuggestions always adds at least one text response (line 132), so after the block at lines 103-105 runs, responseGenerator.getResponse().size() is guaranteed to be > 0. The only path that skips lines 103-105 is when fallbackTriggered == false && size > 0 — which also means line 108's check fails. No execution path reaches line 109.

This dead block may mask future bugs if the fallback flow is modified. Consider removing it or restructuring so the Eliza fallback is an explicit else branch within the suggestion guard.

Suggested restructuring
         // Add guided fallback suggestions when we could not understand
         if (fallbackTriggered || responseGenerator.getResponse().size() == 0) {
             responseGenerator = appendFallbackSuggestions(responseGenerator, textMessage);
         }
-
-        // Fallback when everything else fails Eliza will answer
-        if (responseGenerator.getResponse().size() == 0) {
-            responseGenerator.addTextResponse(new ResponseData(helper.getEliza().processInput(textMessage)));
-        }
         return responseGenerator;

If you want to keep Eliza as a last-resort safety net (defensive coding), at least add a comment explaining it's intentionally unreachable under current logic.


60-60: Nit: redundant == true comparison.

Utility.isJSONObject(reply) already returns a boolean.

-            if (Utility.isJSONObject(reply) == true) {
+            if (Utility.isJSONObject(reply)) {

153-176: Hoist stop-word set to a static final field; use imports instead of fully-qualified types.

The stopSet is rebuilt on every call with the same contents. Promoting it to a class-level constant avoids repeated allocation and makes the intent clearer. The inline java.util.Set / java.util.HashSet references should also use the existing import section.

Suggested refactor

Add imports at the top of the file:

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

Add a static field in the class:

private static final Set<String> STOP_WORDS = new HashSet<>(Arrays.asList(
    "what", "who", "where", "why", "how", "is", "are", "the", "a", "an",
    "of", "in", "on", "for", "with", "to", "tell", "me", "about", "please"
));

Then simplify extractKeyword:

     private String extractKeyword(String text) {
         if (text == null || text.trim().length() == 0) {
             return null;
         }
         String cleaned = text.replaceAll("[^A-Za-z0-9 ]", " ").toLowerCase();
         String[] tokens = cleaned.split(" +");
-        String[] stop = new String[] { "what", "who", "where", "why", "how", "is", "are", "the", "a", "an", "of", "in",
-                "on", "for", "with", "to", "tell", "me", "about", "please" };
-        java.util.Set<String> stopSet = new java.util.HashSet<>();
-        for (String s : stop)
-            stopSet.add(s);
-
         String best = null;
         for (String t : tokens) {
             if (t.length() < 3)
                 continue;
-            if (stopSet.contains(t))
+            if (STOP_WORDS.contains(t))
                 continue;
             if (best == null || t.length() > best.length()) {
                 best = t;
             }
         }
         return best;
     }

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/main/java/chatbot/lib/handlers/TextHandler.java`:
- Around line 81-105: Introduce a dedicated boolean elizaResponded (initialize
alongside fallbackTriggered) and set elizaResponded = true only in the Eliza
branch where you call helper.getEliza().processInput(...); keep
fallbackTriggered true for any fallback condition (both Eliza and
NLHandler-empty) but do NOT set elizaResponded when NLHandler returns zero
results; finally call appendFallbackSuggestions(responseGenerator, textMessage,
elizaResponded) so appendFallbackSuggestions can distinguish Eliza vs keyword
fallback; update references in TextHandler (the Eliza branch, the NLHandler
empty-result branch, and the final appendFallbackSuggestions call) accordingly.
🧹 Nitpick comments (2)
src/main/java/chatbot/lib/handlers/TextHandler.java (2)

59-60: Nit: redundant == true comparison.

Utility.isJSONObject(reply) already returns boolean.

Suggested diff
-            if (Utility.isJSONObject(reply) == true) {
+            if (Utility.isJSONObject(reply)) {

159-182: Stop-word set is rebuilt on every call; prefer a static constant and use imports.

extractKeyword allocates a new HashSet from an array literal on each invocation. Since the stop list is fixed, promote it to a private static final Set<String>. Also, the inline fully-qualified java.util.Set / java.util.HashSet references should be proper imports at the top of the file.

♻️ Suggested refactor

Add to imports:

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

Add a class-level constant:

private static final Set<String> STOP_WORDS = new HashSet<>(Arrays.asList(
    "what", "who", "where", "why", "how", "is", "are", "the", "a", "an",
    "of", "in", "on", "for", "with", "to", "tell", "me", "about", "please"
));

Then simplify extractKeyword:

     private String extractKeyword(String text) {
         if (text == null || text.trim().length() == 0) {
             return null;
         }
         String cleaned = text.replaceAll("[^A-Za-z0-9 ]", " ").toLowerCase();
         String[] tokens = cleaned.split(" +");
-        String[] stop = new String[] { "what", "who", "where", ... };
-        java.util.Set<String> stopSet = new java.util.HashSet<>();
-        for (String s : stop)
-            stopSet.add(s);
 
         String best = null;
         for (String t : tokens) {
             if (t.length() < 3)
                 continue;
-            if (stopSet.contains(t))
+            if (STOP_WORDS.contains(t))
                 continue;
             if (best == null || t.length() > best.length()) {
                 best = t;
             }
         }
         return best;
     }

@vaibhav45sktech
Copy link
Contributor Author

Greetings @RicardoUsbeck @ram-g-athreya , kindly review my pr whenever available , Thankyou.

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.

Handling Incorrect or Unknown Queries

1 participant