Implement Robust Fallback for Unknown Queries#70
Implement Robust Fallback for Unknown Queries#70vaibhav45sktech wants to merge 4 commits intodbpedia:masterfrom
Conversation
📝 WalkthroughWalkthroughImplements 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
Sequence DiagramsequenceDiagram
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)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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:fallbacktextandnoresultstextnow 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: allmay 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
:focusstate 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 wherebox-shadowis dropped. Consider addingoutlineor using:focus-visiblewith 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 astatic finalfield instead.
extractKeywordallocates a newHashSetand array each time it's called. Since the stop-word list is fixed, promote it to aprivate static final Set<String>class constant. Also, prefer the already-importedjava.utiltypes 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== truecomparison.
Utility.isJSONObject(reply)already returns aboolean; the== trueis unnecessary.♻️ Suggested change
- if (Utility.isJSONObject(reply) == true) { + if (Utility.isJSONObject(reply)) {
39-39: PreferisEmpty()oversize() > 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()) {
|
@CodeRabbit please review |
|
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
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.
appendFallbackSuggestionsalways 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 whenfallbackTriggered == 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
elsebranch 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== truecomparison.
Utility.isJSONObject(reply)already returns a boolean.- if (Utility.isJSONObject(reply) == true) { + if (Utility.isJSONObject(reply)) {
153-176: Hoist stop-word set to astatic finalfield; use imports instead of fully-qualified types.The
stopSetis rebuilt on every call with the same contents. Promoting it to a class-level constant avoids repeated allocation and makes the intent clearer. The inlinejava.util.Set/java.util.HashSetreferences 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; }
| 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)); |
There was a problem hiding this comment.
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.
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.mdSince BetterCodeHub image link was no longer accessible
Summary by CodeRabbit
New Features
Improvements
Documentation