Skip to content

Implement Robust Fallback for Unknown Queries#70

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

Implement Robust Fallback for Unknown Queries#70
vaibhav45sktech wants to merge 4 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.

  • Removed the BetterCodeHub from README.md

  • Since BetterCodeHub image link was no longer accessible

Summary by CodeRabbit

  • New Features

    • Contextual smart-reply suggestions are shown when fallback responses are triggered.
  • Improvements

    • Smart-reply styling updated for a more responsive layout, refined spacing, subtle shadow, and smooth transitions.
    • Fallback prompts now include clearer guidance and example questions to better guide users.
    • Fallback behavior made more consistent so helpful suggestions appear reliably when no direct reply is available.
  • 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, adds programmatic fallback suggestion flow in TextHandler to append DBpedia-focused smart replies when no replies occur, and updates RiveScript fallback messages with more detailed guidance.

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 fallbackTriggered flag, private helpers appendFallbackSuggestions and extractKeyword, set flag on FALLBACK_SCENARIO and NL-path misses, appended DBpedia-focused smart replies when no responses; improved sanitizeText logging.
RiveScript fallback text
src/main/resources/rivescript/scenario-text.rive
Replaced generic apology lines in fallbacktext and noresultstext with more detailed guidance and example question suggestions.
Docs
README.md
Removed BCH compliance badge line.

Sequence Diagram

sequenceDiagram
    actor User
    participant TextHandler
    participant ReplyEngine
    participant FallbackEngine
    participant ResponseBuilder

    User->>TextHandler: submitText(text)
    TextHandler->>ReplyEngine: generateReplies(text)
    alt FALLBACK_SCENARIO or no replies
        TextHandler->>FallbackEngine: extractKeyword(text)
        FallbackEngine-->>TextHandler: keyword or default prompt
        TextHandler->>ResponseBuilder: appendFallbackSuggestions(suggestions)
    end
    ResponseBuilder-->>User: returnResponse(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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implement Robust Fallback for Unknown Queries' directly and clearly describes the main objective of the PR—enhancing fallback handling for cases where the chatbot cannot generate relevant responses.

✏️ 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;
     }

Comment on lines +114 to +132
private ResponseGenerator appendFallbackSuggestions(ResponseGenerator responseGenerator, String originalText) {
// Extract keyword for contextual fallback message
String keyword = extractKeyword(originalText);

String fallbackPrompt;
if (keyword != null && !keyword.isEmpty()) {
fallbackPrompt = "I didn't quite understand, but you might be asking about '" + keyword
+ "'. Try one of these suggestions:";
} else {
// Use default RiveScript fallback when no keyword could be extracted
String[] fallbackResult = helper.getRiveScriptBot().answer(request.getUserId(),
RiveScriptReplyType.FALLBACK_TEXT);
if (fallbackResult != null && fallbackResult.length > 0) {
fallbackPrompt = fallbackResult[0];
} else {
fallbackPrompt = "Sorry, I don't understand.";
}
}
responseGenerator.addTextResponse(new ResponseData(fallbackPrompt));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fallback prompt appended unconditionally on top of existing Eliza response.

When the Eliza path fires (line 83-86), fallbackTriggered = true and a text response from Eliza is already added. Then at line 103, appendFallbackSuggestions adds another text response (the "I didn't quite understand…" prompt at line 132). The user will see both the Eliza reply and a contradictory "I didn't understand" message in sequence.

If this double-message is intentional (Eliza answer + guided suggestions), consider changing the fallback prompt to something that doesn't contradict the Eliza reply — e.g., "Here are some things I can help with:" — instead of "I didn't quite understand…".

🤖 Prompt for AI Agents
In `@src/main/java/chatbot/lib/handlers/TextHandler.java` around lines 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.

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.

1 participant