I have been asked by a client to reduce the amount of false positives that Apache Solr was providing in the 'Did you mean?'. Some words in the index were perfectly valid, but Solr insisted on trying to suggest more popular terms.

E.g. a search for 'woods' (in 11 documents) would return 'did you mean? foods' (in 69 documents)

This feature patch aims to provide the ability to ask Solr for:

$params['spellcheck.extendedResults'] = 'true';

These extended spell check results include the frequency to which the word appears in the index, and also whether or not Solr thinks the word is spelled correctly.

I made a new addition to the Solr configuration page to include:

  • A checkbox to show only if the word is misspelled
  • A textfield to set the minimum original frequency to suppress the 'did you mean?'

This way an admin can easily suppress the 'did you mean?' to words that appear in fewer than 5 documents for instance, or alternatively only show for misspelled words (appear in 0 documents in the index).

What are your thoughts on this patch, is it likely this can be accepted upstream into the module?

Thanks
Sean

Comments

nick_vh’s picture

Status: Needs review » Needs work

Very interesting of course.

+++ b/apachesolr_search.moduleundefined
@@ -852,15 +856,27 @@ function apachesolr_search_get_search_suggestions() {
+        unset($suggestions['correctlySpelled']);

Can this be commented? Can we prevent this?

+++ b/apachesolr_search.moduleundefined
@@ -841,6 +843,8 @@ function apachesolr_search_process_response($response, DrupalSolrQueryInterface
+  $search_page = apachesolr_search_page_load('core_search');

This is also not wanted, since we do allow searches from more then 1 environment

I'll try to test it out now :-)

nick_vh’s picture

StatusFileSize
new30.91 KB
new7.27 KB

Rerolled the patch so it applies on the latest dev version.

There is however still one problem it seems. The following code did not validate on my system.

if ($min_freq >= $value->suggestion[0]->origFreq) {
  $replacements[$word] = $value->suggestion[0]->word;
}

I only got

0 (Object) stdClass
word (String, 5 characters ) testt
freq (Integer) 15

Maybe there is a difference between Solr 1.4 and 3.x?

wiifm’s picture

Hi Nick_vh,

Yes, I forgot to mention that we are running on Solr version 3.1.0, and I don't really have access to Solr 1.4 to test this with.

This is the raw Solr response from the query

http://127.0.0.1:8983/solr/[core-name]/select/?q=Cancer%20trends&spellcheck=true&spellcheck.build=true&version=2.2&spellcheck.extendedResults=true

...
<lst name="spellcheck">
	<lst name="suggestions">
		<lst name="trends">
			<int name="numFound">1</int>
			<int name="startOffset">7</int>
			<int name="endOffset">13</int>
			<int name="origFreq">50</int>
			<arr name="suggestion">
				<lst>
					<str name="word">friends</str>
					<int name="freq">66</int>
				</lst>
			</arr>
		</lst>
		<bool name="correctlySpelled">true</bool>
	</lst>
</lst>
</response>

My response above seems to marry up with the documentation at http://wiki.apache.org/solr/SpellCheckComponent#Extended_Results where it says:

The spellcheck.extendedResults=true parameter provides frequency of each original term in the index (origFreq) as well as the frequency of each suggestion in the index (frequency).

Can you post the last part of your response from Solr?

S

acbramley’s picture

StatusFileSize
new7.26 KB

The line if ($min_freq >= $value->suggestion[0]->origFreq) had the incorrect logic. Altered the patch in #2 to use correct logic.

nick_vh’s picture

Ok, I gave it another try with my standard test and it was not giving me the same result. We should have a way to enable or disable the advanced spelchecker so the original behavior does not get lost.

nick_vh’s picture

I'm a bit doubtful for the final implementation for this. It seems that the purpose is very specific and the UI to configure it will not be very clear for most of the people?

Are you able to do this without hacking the module? In other words, by using the hooks provided? You could easily make a new contrib module that extends this module and if I was you I would go for that solution?.

wiifm’s picture

Hi @Nick_vh,

Is there a hook that can alter the spell checker results before they are returned? The only reason why I patched the module was because I saw no other way to achieve this

nick_vh’s picture

StatusFileSize
new1.04 KB

Would this be sufficient?

nick_vh’s picture

StatusFileSize
new1.01 KB

And now with a newline

nick_vh’s picture

Version: 7.x-1.0-beta11 » 7.x-1.x-dev
Status: Needs work » Needs review
wiifm’s picture

Yes - that would be perfect, and would mean I can place my customisations into our modules and leave apachesolr clean ;)

Will apply the patch at work, and make sure it is all good

acbramley’s picture

@Nick_vh this hook doesn't allow us to add $params['spellcheck.extendedResults'] = 'true'; which is needed to get those extra parameters.

nick_vh’s picture

hook_query_alter? That should be sufficient to add that parameter?

/**
 * Alter the query after it's prepared and cached.
 *
 * Any module performing a search should call
 * drupal_alter('apachesolr_query', $query). That function then invokes this
 * hook. It allows modules to modify the query object and its parameters.
 *
 * A module implementing HOOK_apachesolr_query_alter() may set
 * $query->abort_search to TRUE to flag the query to be aborted.
 *
 * @param object $query
 *   An object implementing DrupalSolrQueryInterface. No need for &.
 */
function hook_apachesolr_query_alter($query) {
  // I only want to see articles by the admin!
  $query->addFilter("is_uid", 1);

  // Only search titles.
  $query->replaceParam('qf', 'label');
}
nick_vh’s picture

Status: Needs review » Needs work
acbramley’s picture

Ah yes, that does work :)

nick_vh’s picture

Status: Needs work » Needs review
acbramley’s picture

However, adding this parameter changes the structure of the suggestions array, making (apahcesolr_search.module +856):

foreach ($suggestions as $word => $value) {
  $replacements[$word] = $value->suggestion[0];
}

incorrect as the word is now under $value->suggestion[0]->word. Any ideas of how to get around this?

acbramley’s picture

StatusFileSize
new706 bytes

This patch fixes the above problem for both cases.

nick_vh’s picture

Status: Needs review » Needs work

Without documentation this patch is as fuzzy as a patch could be? Would there really not be any other way?

acbramley’s picture

Nope, because when you add the query parameter to return extended results it changes the suggestions from an array of strings to an array of objects.

acbramley’s picture

Looking at the drupal_alter call on the suggestions as well, this doesn't provide what we need as that will only pass in the string that it will suggest. This means that none of the properties (origFreq etc) are available in the alter hook. It would be much better to do it like this:

function apachesolr_search_get_search_suggestions() {
  $suggestions_output = array();
  if (apachesolr_has_searched()) {
    $query = apachesolr_current_query();
    $keyword = $query->getParam('q');
    $searcher = $query->getSearcher();
    $response = apachesolr_static_response_cache($searcher);
    // Get spellchecker suggestions into an array.
    if (!empty($response->spellcheck->suggestions)) {
      $suggestions = get_object_vars($response->spellcheck->suggestions);
      if ($suggestions) {
        drupal_alter('apachesolr_suggestions', $suggestions, $env_id);
        $replacements = array();
        // Get the original query and retrieve all words with suggestions.
        foreach ($suggestions as $word => $value) {
          $replacements[$word] = $value->suggestion[0];
        }
        // Replace the keyword with the suggested keyword.
        $suggested_keyword = strtr($keyword, $replacements);
        // Show only if suggestion is different than current query.
        if ($keyword != $suggested_keyword) {
         $suggestions_output[] = $suggested_keyword;
        }
      }
    }
  }

  return $suggestions_output;
}
acbramley’s picture

StatusFileSize
new1.77 KB

Sorry, wrong again. This patch includes the correct placing and variables to use in the alter hook so we have something useful, and the change in the foreach loop with documentation as to what it's for :)

nick_vh’s picture

I don't like this approach. It would be better if you actually remake the array so it fits in to the function properly.

halcyonCorsair’s picture

@Nick_vh:
Could you please exand on that comment? We're flying in the dark here about what you want, and why you're rejecting our suggestions.

nick_vh’s picture

sorry if I sounded rude :-)

I would prefer that the logic of this $value->suggestion[0]->word happens in the alter.
We are adding query specific syntaxes here and that is not necessary.

I'd prefer if we reworked it to something similar like :

 // Get spellchecker suggestions into an array.
     if (!empty($response->spellcheck->suggestions)) {
       $suggestions = get_object_vars($response->spellcheck->suggestions);
       // pre processing could happen here so we have a flat suggestion array?
       drupal_alter('apachesolr_suggestions', $suggestions, $env_id);
       if ($suggestions) {
         $replacements = array();
         // Get the original query and retrieve all words with suggestions.
         foreach ($suggestions as $word => $suggestion) {
           $replacements[$word] = $suggestion;
         }
         // Replace the keyword with the suggested keyword.
         $suggested_keyword = strtr($keyword, $replacements);

This way we do everything in the alter and we can add more suggestion query weirdness if one would want?

milesw’s picture

I think you can address some of these problems through changes to Solr config.

Regarding the original issue:

E.g. a search for 'woods' (in 11 documents) would return 'did you mean? foods' (in 69 documents)

Try settings spellcheck.onlyMorePopular to "false". Solr keeps it off by default, but the config for apachesolr.module turns it on. It's a misleading option, which sounds helpful, but I find it leads to worse suggestions overall.

And regarding the other part:

This way an admin can easily suppress the 'did you mean?' to words that appear in fewer than 5 documents for instance, or alternatively only show for misspelled words (appear in 0 documents in the index).

Another possibility is to use a spellchecker config option called "thresholdTokenFrequency". Seems to be undocumented, but here is a stack overflow example). This tells the spellcheck component to ignore terms found in less than a certain percentage of documents.

petednz’s picture

This thread looks like it ran out of steam - did it get picked up somewhere else. In the meantime - can someone clarify if the suggestion to "Try settings spellcheck.onlyMorePopular to "false"" is a UI setting or actually in the module? I am assuming the latter but happy to get a nice surprise (though I can't find it)

milesw’s picture

@petednz: It's a Solr parameter that can be set in solrconfig.xml or overridden with URL parameters. The solrconfig.xml bundled with apachesolr.module sets it to true. You can use hook_apachesolr_query_alter() or hook_apachesolr_query_prepare() to override that setting.

petednz’s picture

sweet. really appreciate your prompt and useful response.

daniel.nitsche’s picture

@milesw, I'm assuming you're running Solr 1.4? That feature doesn't seem to be available in Solr 3, or at least it's never worked for me.

milesw’s picture

@daniel.nitsche: I was using 3.x. Are you referring to the onlyMorePopular feature? If you changed it in solrconfig.xml don't forget you have to reload the config. :)

The only change I'm aware of is in Solr 4.x where you can optionally use the DirectSolrSpellChecker instead of the default IndexBasedSpellChecker. As I understand it, the onlyMorePopular param irrelevant with that new spell checker.

acbramley’s picture

Status: Needs work » Needs review

I completely lost track of this issue as I moved off the project that was using the patch. While there's valid suggestions as to what other things you can do with solrconfig and parameters to achieve similar functionality, an alter hook gives you far more power and flexibility. We've been using this extensively since the patch #22 was posted. This is the example of how we use it:

/**
 * Implements hook_apachesolr_suggestions_alter().
 */ 
function mymodule_apachesolr_suggestions_alter(&$suggestions, $env_id) {
  $incorrect_spelling_only = variable_get('mymodule_spellcheck_correctly_spelled', 0);
  $min_freq = variable_get('mymodule_spellcheck_min_orig_freq', '0');

  if ($suggestions['correctlySpelled'] == TRUE && $incorrect_spelling_only) {
    // If we've set it so we only want incorrectly spelt words to be suggested and the suggestion
    // is spelt correctly then we don't want to do anything.
    return;
  }
  unset($suggestions['correctlySpelled']);
  
  // If min_freq is 0, retain normal functionality
  if ($min_freq != 0) {
    foreach ($suggestions as $word => $value) {
      // If the word occurs more (or the same as) the minimum frequency, don't use it.
      if ($value->origFreq >= $min_freq) {
        unset($suggestions[$word]);
      }
    }
  } 
}

Setting to needs review to try kick this off again

Status: Needs review » Needs work

The last submitted patch, 1361854-10.patch, failed testing.

acbramley’s picture

Status: Needs work » Needs review
StatusFileSize
new1.83 KB

Woops, needed a reroll.

nick_vh’s picture

Version: 7.x-1.x-dev » 6.x-3.x-dev
Status: Needs review » Patch (to be ported)

I'll give in and commit this. But we should actually get a cleaner solution but let's take this as part of the solution. Committed to 7.x-1.x and needs backport now

acbramley’s picture

Thanks @Nick_vh, feel free to add a comment above the line you are not happy with as a TODO to fix :)