Unified highlighter snippet fragmenter issues

Hello,
We are knowing some issues with how snippets are splitted with unified highlighter.
We use Elasticsearch in 7.16
Some snippets are very shorts, sometimes the snippet is inconsistent or is only a repetition of user query terms.
I understood that for unified highlighter, sentence boundary_scanner is used by default for breaking fragments. I understood that if the sentence has more chars than fragment_size number, it will be break into shorter slice.
But I don't understand why it breaks so short snippets when I ask for 150 or 300 chars and have only 16 chars returned. Is there a way to prevent from always being the same size ?

Here is my mapping :

 "mappings" : {
      "properties" : {
        "content" : {
          "type" : "text",
          "store" : true,
          "term_vector" : "with_positions_offsets",
          "analyzer" : "text_minimal_fr",
          "search_analyzer" : "search_text_minimal_fr"
        }
      }
    }

Here is a query :

{
    "query": {
        "bool": {
            "should": [
                {
                    "multi_match": {
                        "query": "contrat de travail",
                        "fields": [
                            "content"
                        ],
                        "operator": "AND",
                        "auto_generate_synonyms_phrase_query": false,
                        "type": "phrase",
                        "slop": 10
                    }
                }
            ]
        }
    },
    "_source": [
        "id"
    ],
    "size": 10,
    "from": 0,
    "sort": {
        "_score": "desc"
    },
    "highlight": {
        "fields": {
            "content": {
                "number_of_fragments": 1,
                "type": "unified",
                "pre_tags": [
                    "<hit>"
                ],
                "post_tags": [
                    "</hit>"
                ],
                "fragment_size": 150,
                "no_match_size": 150,
                "order": "score"
            }
        }
    }
}

And the result :

{
  "took" : 118,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : 1244.9432,
    "hits" : [
      {
        "_source" : {
          "id" : "JP_QUALIF_3545013"
        },
        "highlight" : {
          "texte" : [
            "Z... saisissait le Conseil des prud'hommes de Narbonne pour que ses <hit>contrats</hit> de <hit>travail</hit> à durée déterminée soient requalifiés en un <hit>contrat</hit> de <hit>travail</hit>"
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3515022"
        },
        "highlight" : {
          "texte" : [
            "du <hit>contrat</hit> de <hit>travail</hit>, - constater que la SA Orpea a rompu le <hit>contrat</hit> de <hit>travail</hit> de Mme X... ; - dire que la rupture du <hit>contrat</hit> de <hit>travail</hit> est un licenciement"
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3583842"
        },
        "highlight" : {
          "texte" : [
            "Le <hit>contrat</hit> de <hit>travail</hit> à durée indéterminée constitue le droit commun du <hit>contrat</hit> de <hit>travail</hit> et le recours au <hit>contrat</hit> de <hit>travail</hit> à durée déterminée est soumis"
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3576000"
        },
        "highlight" : {
          "texte" : [
            "rupture du <hit>contrat</hit> de <hit>travail</hit>."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3533606"
        },
        "highlight" : {
          "texte" : [
            "du <hit>contrat</hit> de <hit>travail</hit> initial liant M."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3533501"
        },
        "highlight" : {
          "texte" : [
            "Selon ce texte, le <hit>contrat</hit> de <hit>travail</hit> à temps partiel est un <hit>contrat</hit> écrit."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3580835"
        },
        "highlight" : {
          "texte" : [
            "<hit>contrat</hit> de <hit>travail</hit> avec Mme X..."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3529057"
        },
        "highlight" : {
          "texte" : [
            "le <hit>contrat</hit> de <hit>travail</hit> à aucun des cessionnaires."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3557091"
        },
        "highlight" : {
          "texte" : [
            "Le <hit>contrat</hit> de <hit>travail</hit> à durée déterminée de M."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3531192"
        },
        "highlight" : {
          "texte" : [
            "Or en l'espèce, le <hit>contrat</hit> de <hit>travail</hit> de Mme G..."
          ]
        }
      }
    ]
  }
}

I tried whith bigger fragment_size 300 or more:

{
  "took" : 163,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : 1244.9432,
    "hits" : [
      {
        "_source" : {
          "id" : "JP_QUALIF_3545013"
        },
        "highlight" : {
          "texte" : [
            "Z... saisissait le Conseil des prud'hommes de Narbonne pour que ses <hit>contrats</hit> de <hit>travail</hit> à durée déterminée soient requalifiés en un <hit>contrat</hit> de <hit>travail</hit> à durée indéterminée et obtenir le paiement de sommes relatives à la rupture de son <hit>contrat</hit> de <hit>travail</hit>."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3515022"
        },
        "highlight" : {
          "texte" : [
            "Elle précise que si le <hit>contrat</hit> de <hit>travail</hit> du 10 juin 2015 est un <hit>contrat</hit> de <hit>travail</hit> à temps plein, celui du 1er juillet 2015 porte sur un travail à temps partiel."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3583842"
        },
        "highlight" : {
          "texte" : [
            "de <hit>travail</hit> à durée déterminée, elle est réputée avoir occupé un <hit>contrat</hit> de <hit>travail</hit> à durée indéterminée depuis le premier <hit>contrat</hit> de <hit>travail</hit> à durée déterminée irrégulier."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3576000"
        },
        "highlight" : {
          "texte" : [
            "la rupture du <hit>contrat</hit> de <hit>travail</hit>."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3517911"
        },
        "highlight" : {
          "texte" : [
            "d'exécution du <hit>contrat</hit> de <hit>travail</hit>. 8."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3559341"
        },
        "highlight" : {
          "texte" : [
            "2014 ''ne pouvait avoir pour effet d'entraîner la requalification du <hit>contrat</hit> de <hit>travail</hit> à temps partiel en <hit>contrat</hit> de <hit>travail</hit> à temps complet'', quand cette nullité emportait la requalification du <hit>contrat</hit> de <hit>travail</hit> à temps partiel en <hit>contrat</hit> de <hit>travail</hit> à temps complet et permettait à M."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3529057"
        },
        "highlight" : {
          "texte" : [
            "étant suffisamment grave pour empêcher la poursuite du <hit>contrat</hit> de <hit>travail</hit> ; que dès lors que la prise d'acte de la rupture coïncide avec la fin de son arrêt de <hit>travail</hit> qui a suspendu l'exécution du <hit>contrat</hit> de <hit>travail</hit>, le délai entre la date de notification du transfert du <hit>contrat</hit> de <hit>travail</hit> d'une part"
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3596694"
        },
        "highlight" : {
          "texte" : [
            "Par requête du 19 janvier 2018, Mme A... a saisi le conseil de prud'hommes de Marseille afin de voir juger que la relation de <hit>travail</hit> s'est poursuivie postérieurement au dernier <hit>contrat</hit> de <hit>travail</hit> à durée déterminée, de solliciter la requalification du <hit>contrat</hit> de <hit>travail</hit> en <hit>contrat</hit> de <hit>travail</hit> à durée"
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3557091"
        },
        "highlight" : {
          "texte" : [
            "rompu abusivement le <hit>contrat</hit> de <hit>travail</hit> de M."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3531192"
        },
        "highlight" : {
          "texte" : [
            "Sur l'exécution déloyale du <hit>contrat</hit> de <hit>travail</hit> : Mme G..."
          ]
        }
      }
    ]
  }
}

I tried to not cut sentences with fragment_size set to 0 but it is not good for our needs because we really needs that snippets size remains the same size.
Here is an example of response with frament_size set to 0 :

{
  "took" : 88,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : 1244.9432,
    "hits" : [
      {
        "_source" : {
          "id" : "JP_QUALIF_3545013"
        },
        "highlight" : {
          "texte" : [
            "Ainsi, les <hit>contrats</hit> de <hit>travail</hit> de Mme X..."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3515022"
        },
        "highlight" : {
          "texte" : [
            "Elle précise que si le <hit>contrat</hit> de <hit>travail</hit> du 10 juin 2015 est un <hit>contrat</hit> de <hit>travail</hit> à temps plein, celui du 1er juillet 2015 porte sur un travail à temps partiel."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3583842"
        },
        "highlight" : {
          "texte" : [
            "Elle ajoute que le délai de l'action en requalification d'un <hit>contrat</hit> de <hit>travail</hit> à durée déterminée en <hit>contrat</hit> de <hit>travail</hit> à durée indéterminée a pour point de départ, en cas de succession des <hit>contrats</hit> de <hit>travail</hit> à durée déterminée, le terme du dernier d'entre eux, qu'en cas de requalification des <hit>contrats</hit> de <hit>travail</hit> à durée déterminée, elle est réputée avoir occupé un <hit>contrat</hit> de <hit>travail</hit> à durée indéterminée depuis le premier <hit>contrat</hit> de <hit>travail</hit> à durée déterminée irrégulier."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3576000"
        },
        "highlight" : {
          "texte" : [
            "B... est fondée ; que cette résiliation du <hit>contrat</hit> de <hit>travail</hit> entraîne la rupture du <hit>contrat</hit> de <hit>travail</hit> de Madame X..."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3517911"
        },
        "highlight" : {
          "texte" : [
            "C'est donc bien le 1er juin 2014 que le <hit>contrat</hit> de <hit>travail</hit> a été rompu."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3559341"
        },
        "highlight" : {
          "texte" : [
            "Le salarié fait grief à l'arrêt de le débouter de ses demandes tendant à voir requalifier le <hit>contrat</hit> de <hit>travail</hit> à durée déterminée à temps partiel du 1 juin 2014 en <hit>contrat</hit> de <hit>travail</hit> à temps complet et fixer sa créance au passif de la liquidation judiciaire de la société Agi sécurité à diverses sommes à titre de rappel de salaire, d’indemnité compensatrice de congés payés afférents et d’indemnité de précarité, alors « que la nullité de la clause d'un <hit>contrat</hit> de <hit>travail</hit> par laquelle un salarié s'engage à travailler pour un employeur à titre exclusif et à temps partiel lui permet d'obtenir la requalification de ce <hit>contrat</hit> de <hit>travail</hit> en <hit>contrat</hit> de <hit>travail</hit> à temps complet et par partant, de bénéficier des conséquences financières d'une telle requalification ; qu'en statuant comme elle l'a fait, motif pris que la nullité de la clause d'exclusivité insérée dans le <hit>contrat</hit> de <hit>travail</hit> à temps partiel du 1 juin 2014 ''ne pouvait avoir pour effet d'entraîner la requalification du <hit>contrat</hit> de <hit>travail</hit> à temps partiel en <hit>contrat</hit> de <hit>travail</hit> à temps complet'', quand cette nullité emportait la requalification du <hit>contrat</hit> de <hit>travail</hit> à temps partiel en <hit>contrat</hit> de <hit>travail</hit> à temps complet et permettait à M."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3597689"
        },
        "highlight" : {
          "texte" : [
            "Elle a saisi la juridiction prud'homale de demandes relatives à l'exécution et à la rupture de son <hit>contrat</hit> de <hit>travail</hit>."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3533606"
        },
        "highlight" : {
          "texte" : [
            "D... à la date du 31 décembre 2011 ; que le prononcé de la rupture du <hit>contrat</hit> de <hit>travail</hit> ne marque pas la date de la fin du <hit>contrat</hit> ; qu'en l'espèce, l'exécution du <hit>contrat</hit> de <hit>travail</hit> initial liant M."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3533501"
        },
        "highlight" : {
          "texte" : [
            "Selon ce texte, le <hit>contrat</hit> de <hit>travail</hit> à temps partiel est un <hit>contrat</hit> écrit."
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3580835"
        },
        "highlight" : {
          "texte" : [
            "En l'espèce, le <hit>contrat</hit> de <hit>travail</hit> du 12 juin 2009 de M."
          ]
        }
      }
    ]
  }
}

I don't understand why it breaks so short snippets when I ask for 150 or 300 chars and have only 16 chars returned.

My assumption would be that short fragments were the last fragments in a field's text. Given a field text, a highlighter breaks it into several passages based on fragment_size, but the last fragment what was left over. It also so happened in your case, that the last fragment was scored the best among other fragments and returned in the results.

I wonder if in your case it would make more sense to use boundary_scanner of type word instead of default type of sentence.

Hi Mayva,

Thank you for your answer.
I have another example with a larger fragment size .

{
    "query": {
        "bool": {
            "should": [
                {
                    "multi_match": {
                        "query": "dommage ouvrage",
                        "fields": [
                            "content"
                        ],
                        "operator": "AND",
                        "auto_generate_synonyms_phrase_query": false,
                        "type": "phrase",
                        "slop": 10
                    }
                }
            ]
        }
    },
    "_source": [
        "id"
    ],
    "size": 10,
    "from": 0,
    "sort": {
        "_score": "desc"
    },
    "highlight": {
        "fields": {
            "content": {
                "number_of_fragments": 1,
                "type": "unified",
                "pre_tags": [
                    "<hit>"
                ],
                "post_tags": [
                    "</hit>"
                ],
                "fragment_size": 950,
                "no_match_size": 950,
                "order": "score"
            }
        }
    }
}

Here is a result list in the left and extract of the document in the right. Maybe it is not so clear but you can see that there are others extracts more relevant and bigger that what is returned by Elasticsearch.
Moreover you can see that the size fragment are heterogeneous.
Is there a way to see an explain for the highlighting ?

I already tried the WORD fragmenter but I don't understand the use case of this, it only returned the query user terms. Maybe i'm not using it well.

-{
  "took" : 119,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 936,
      "relation" : "eq"
    },
    "max_score" : 2364.882,
    "hits" : [
      {
        "_source" : {
          "id" : "JP_QUALIF_3551306"
        },
        "highlight" : {
          "texte" : [
            "<hit>dommages</hit>-<hit>ouvrage</hit>"
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3525083"
        },
        "highlight" : {
          "texte" : [
            "<hit>dommages</hit>-<hit>ouvrage</hit>"
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3595329"
        },
        "highlight" : {
          "texte" : [
            "<hit>ouvrage</hit>"
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3596222"
        },
        "highlight" : {
          "texte" : [
            "<hit>ouvrage</hit>"
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3514139"
        },
        "highlight" : {
          "texte" : [
            "<hit>dommages</hit>"
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3577868"
        },
        "highlight" : {
          "texte" : [
            "<hit>dommage</hit>"
          ]
        }
      },
      {
        "_source" : {
          "id" : "JP_QUALIF_3565680"
        },
        "highlight" : {
          "texte" : [
            "<hit>dommage</hit>"
          ]
        }
      }
    ]
  }
}

Reading NOTE of boundary_scanner > sentence:

NOTE: When used with the unified highlighter, the sentence scanner splits sentences bigger than fragment_size at the first word boundary next to fragment_size . You can set fragment_size to 0 to never split any sentence.

it seems that fragment_size parameter only works against sentences longer than the fragment_size.

It could be a reason for heterogeneous size of fragments.

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.