Files
wehub-resource-sync c56bef871b
Sync docs with Docusaurus / sync (push) Waiting to run
Tests / Check if changed (push) Waiting to run
Tests / format (push) Blocked by required conditions
Tests / check-imports (push) Blocked by required conditions
Tests / Unit / macos-latest (push) Blocked by required conditions
Tests / Unit / ubuntu-latest (push) Blocked by required conditions
Tests / Unit / windows-latest (push) Blocked by required conditions
Tests / mypy (push) Blocked by required conditions
Tests / Integration / ubuntu-latest (push) Blocked by required conditions
Tests / Integration / macos-latest (push) Blocked by required conditions
Tests / Integration / windows-latest (push) Blocked by required conditions
Tests / notify-slack-on-failure (push) Blocked by required conditions
Tests / Mark tests as completed (push) Blocked by required conditions
Docker image release / Build base image (push) Waiting to run
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:28 +08:00

147 lines
5.5 KiB
Python

# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
import re
from typing import Any
from haystack import component, logging
from haystack.core.serialization import default_from_dict, default_to_dict
from haystack.dataclasses import ChatMessage
logger = logging.getLogger(__name__)
@component
class RegexTextExtractor:
"""
Extracts text from chat message or string input using a regex pattern.
RegexTextExtractor parses input text or ChatMessages using a provided regular expression pattern.
It can be configured to search through all messages or only the last message in a list of ChatMessages.
### Usage example
```python
from haystack.components.extractors import RegexTextExtractor
from haystack.dataclasses import ChatMessage
# Using with a string
parser = RegexTextExtractor(regex_pattern='<issue url=\"(.+)\">')
result = parser.run(text_or_messages='<issue url="github.com/hahahaha">hahahah</issue>')
# result: {"captured_text": "github.com/hahahaha"}
# Using with ChatMessages
messages = [ChatMessage.from_user('<issue url="github.com/hahahaha">hahahah</issue>')]
result = parser.run(text_or_messages=messages)
# result: {"captured_text": "github.com/hahahaha"}
```
"""
def __init__(self, regex_pattern: str) -> None:
"""
Creates an instance of the RegexTextExtractor component.
:param regex_pattern:
The regular expression pattern used to extract text.
The pattern should include a capture group to extract the desired text.
Example: `'<issue url="(.+)">'` captures `'github.com/hahahaha'` from `'<issue url="github.com/hahahaha">'`.
"""
self.regex_pattern = regex_pattern
# Check if the pattern has at least one capture group
num_groups = re.compile(regex_pattern).groups
if num_groups < 1:
logger.warning(
"The provided regex pattern {regex_pattern} doesn't contain any capture groups. "
"The entire match will be returned instead.",
regex_pattern=regex_pattern,
)
def to_dict(self) -> dict[str, Any]:
"""
Serializes the component to a dictionary.
:returns:
Dictionary with serialized data.
"""
return default_to_dict(self, regex_pattern=self.regex_pattern)
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "RegexTextExtractor":
"""
Deserializes the component from a dictionary.
:param data:
The dictionary to deserialize from.
:returns:
The deserialized component.
"""
# return_empty_on_no_match is an old parameter. We'd like to avoid that pipelines break if it's still present.
if "return_empty_on_no_match" in data["init_parameters"]:
logger.warning("The `return_empty_on_no_match` init parameter has been removed and will be ignored.")
data["init_parameters"].pop("return_empty_on_no_match")
return default_from_dict(cls, data)
@component.output_types(captured_text=str)
def run(self, text_or_messages: str | list[ChatMessage]) -> dict[str, str]:
"""
Extracts text from input using the configured regex pattern.
:param text_or_messages:
Either a string or a list of ChatMessage objects to search through.
:returns:
- `{"captured_text": "matched text"}` if a match is found
- `{"captured_text": ""}` if no match is found
:raises TypeError: if receiving a list the last element is not a ChatMessage instance.
"""
if isinstance(text_or_messages, str):
return self._build_result(self._extract_from_text(text_or_messages))
if not text_or_messages:
logger.warning("Received empty list of messages")
return {"captured_text": ""}
return self._process_last_message(text_or_messages)
def _build_result(self, result: str | list[str]) -> dict:
"""Helper method to build the return dictionary based on configuration."""
if (isinstance(result, str) and result == "") or (isinstance(result, list) and not result):
return {"captured_text": ""}
return {"captured_text": result}
def _process_last_message(self, messages: list[ChatMessage]) -> dict:
"""
Process only the last message and build the result.
:raises TypeError: If the last element of the list is not a ChatMessage instance.
"""
last_message = messages[-1]
if not isinstance(last_message, ChatMessage):
raise TypeError(f"Expected ChatMessage object, got {type(last_message)}")
if last_message.text is None:
logger.warning("Last message has no text content")
return {"captured_text": ""}
result = self._extract_from_text(last_message.text)
return self._build_result(result)
def _extract_from_text(self, text: str) -> str | list[str]:
"""
Extract text using the regex pattern.
:param text:
The text to search through.
:returns:
The text captured by the first capturing group in the regex pattern.
If the pattern has no capture groups, returns the entire match.
If no match is found, returns an empty string.
"""
match = re.search(self.regex_pattern, text)
if not match:
return ""
if match.groups():
return match.group(1)
return match.group(0)