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
147 lines
5.5 KiB
Python
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)
|