This issue has been created
 
 
LLM AI Integration / cid:jira-generated-image-avatar-86e8f338-bc07-4856-a023-1893efbd9b87 LLMAI-172 Open

Keyword search turns a natural-language question into an unweighted OR bag of words with no stopword filtering

 
View issue   ยท   Add comment
 

Issue created

 
cid:jira-generated-image-avatar-88f4eb1f-de88-4cc7-a217-35c9041954fc Sebastian Elsner created this issue on 17/Aug/26 16:01
 
Summary: Keyword search turns a natural-language question into an unweighted OR bag of words with no stopword filtering
Issue Type: cid:jira-generated-image-avatar-86e8f338-bc07-4856-a023-1893efbd9b87 Bug
Affects Versions: 0.9
Assignee: Unassigned
Created: 17/Aug/26 16:01
Environment: XWiki 18.6.0, ai-llm 0.9, bundled Solr 9.4.1.
Priority: cid:jira-generated-image-static-major-7c545e2e-a777-4b44-bd34-577f728af414 Major
Reporter: Sebastian Elsner
Description:

What happens

SolrConnector.keywordSearch builds its query as:

query.setQuery("%s:%s".formatted(AiLLMSolrCoreInitializer.FIELD_CONTENT_INDEX,
    this.solrUtils.toCompleteFilterQueryString(textQuery)));

SolrUtils.toCompleteFilterQueryString runs ClientUtils.escapeQueryChars, which escapes whitespace. The whole question therefore arrives at the classic query parser as a single escaped term, is handed to the field analyzer, and is split into one token per word. With autoGeneratePhraseQueries unset (default false for luceneMatchVersion >= 6) and no q.op configured, those tokens are combined with the default OR operator into a BooleanQuery of SHOULD clauses ranked by BM25.

Two things make this worse than it sounds:

  • XWiki's text_general is created programmatically in AbstractSolrCoreInitializer.addTextGeneralFieldType() as StandardTokenizerFactory + LowerCaseFilterFactory and nothing else โ€“ there is no stopword filter. So "how", "do", "i", "and", "my", "at" are all real query terms.
  • there is no mm / minimum-should-match, so a chunk matching only function words is a valid hit.

Why it matters

The chat filter feeds the user's raw message into this path, and chat messages are natural-language questions. The result is that the keyword half reliably spends its whole budget on chunks that match only common words. Keyword search is meant to complement vector search on rare, exact terms; here it mostly injects noise.

Observed

Query "How do I install zsh and oh my zsh at rise?", keyword-only, limit 5 โ€“ 4 of the 5 returned chunks contain no occurrence of "zsh" anywhere; they match on install, at, my, rise. Scores 5.70 / 5.39 / 5.36 / 5.33 against 21.63 for the one genuinely relevant chunk.

By contrast the same path with the single rare term zsh returns exactly one hit, the correct one โ€“ so the field and analyzer are fine. It is specifically multi-word input that breaks.

Suggested fix

Parse the query as a query rather than escaping it into one term: use edismax with qf=content_index and a sensible mm (e.g. 2<70%), optionally with pf so phrase proximity is boosted. A stopword filter on the field would help too, but mm is the part that stops function-word-only matches from consuming the result budget.