Visitar URL original
fix: AnswerBuilder returns whole match when optional capture group does not participate by zuizhuai · Pull Request #13677 · deepset-ai/haystack · GitHub
Skip to content

fix: AnswerBuilder returns whole match when optional capture group does not participate - #13677

Open
zuizhuai wants to merge 2 commits into
deepset-ai:mainfrom
zuizhuai:fix/answer-builder-optional-group
Open

zuizhuai wants to merge 2 commits into
deepset-ai:mainfrom
zuizhuai:fix/answer-builder-optional-group

Conversation

@zuizhuai

Copy link
Copy Markdown
Contributor

Description

AnswerBuilder._extract_answer_string used match.lastindex to decide whether a pattern contained a capture group. lastindex is the index of the last group that actually participated in the match, so it is None when a pattern has a single optional capture group that does not take part (for example Answer: (\d+)? against Answer: abc).

In that case the code wrongly treated the pattern as having no group and returned the whole match as the answer, instead of an empty answer.

Fix

Switch to match.groups(), which reflects whether the pattern declares any capture group, and return match.group(1) or "" for the single-group case. This mirrors the existing behaviour of RegexTextExtractor (haystack/components/extractors/regex_text_extractor.py), which already handles optional groups correctly.

_check_num_groups_in_regex already guarantees at most one capture group, so the match.groups() truthiness check is sufficient to distinguish no group from one group.

Changes

  • haystack/components/builders/answer_builder.py: use match.groups() instead of match.lastindex, and match.group(1) or "".
  • test/components/builders/test_answer_builder.py: add a regression test for an optional group that does not participate.
  • releasenotes/notes/answer-builder-optional-group-*.yaml: add a fix note.

Verification

python -m pytest test/components/builders/test_answer_builder.py → 40 passed.

_extract_answer_string used match.lastindex to decide whether a
pattern had a capture group. When the pattern has a single *optional*
capture group that does not take part in a match (e.g. Answer:
(\d+)? against Answer: abc), lastindex is None and the
code wrongly treated it as a no-group pattern, returning the whole
match as the answer.

Switch to match.groups(), mirroring RegexTextExtractor, so an
optional group that does not participate yields an empty answer instead
of leaking the full match into the answer.
@zuizhuai
zuizhuai requested a review from a team as a code owner November 11, 2026 01:32
@zuizhuai
zuizhuai requested review from davidsbatista and removed request for a team November 11, 2026 01:32
@vercel

vercel Bot commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

@zuizhuai is attempting to deploy a commit to the deepset Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

Copy link
Copy Markdown
Contributor

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  haystack/components/builders
  answer_builder.py 283
  haystack/core/pipeline
  pipeline.py
Project Total  

This report was generated by python-coverage-comment-action

@lxcxjxhx lxcxjxhx left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The distinction this fixes is worth stating plainly, because it is the kind of thing that reads as a typo:

lastindex groups()
means index of the last group that participated tuple of all groups, None for the ones that did not
(\d+)? vs abc None (None,)
no groups at all None ()

So if not match.lastindex was answering "did any group participate", not "does the pattern have a group". The two agree for every pattern with a mandatory group, which is why it survived.

One thing I checked, since or "" on a capture group looks like it could swallow a real answer: it does not. match.group(1) is a str when the group participates, and "0" is truthy — "0" or "" is "0". The or "" only fires on None, which is exactly the case the PR is about. A group that matches the empty string ((\d*) against abc) also returns "", which is the same value either way.

The test covers the reported case (Answer: (\d+)? against Answer: not a number) and the release note names the behaviour it now matches (RegexTextExtractor), which is the right reference — "what does the sibling component do" is more useful than "what did we decide".

No blocking notes.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants