Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion pyrit/converter/word_level_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,19 @@ def join_words(self, words: list[str]) -> str:
"""
Provide a way for subclasses to override the default behavior of joining words.

Words are rejoined with the same separator they were split on, so a custom
``word_split_separator`` survives the round trip. A ``None`` separator splits on
arbitrary whitespace, which has no single representation to restore, so those
words are joined with a space.

Args:
words (list[str]): List of words to join.

Returns:
str: The joined string.
"""
return " ".join(words)
separator = " " if self._word_split_separator is None else self._word_split_separator
return separator.join(words)

async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult:
"""
Expand Down
40 changes: 40 additions & 0 deletions tests/unit/converter/test_word_level_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ async def convert_word_async(self, word: str) -> str:
return word.upper()


class SeparatorWordLevelConverter(WordLevelConverter):
"""Exposes word_split_separator, mirroring converters like EmojiConverter."""

def __init__(self, *, word_split_separator=" ", word_selection_strategy=None):
super().__init__(
word_selection_strategy=word_selection_strategy,
word_split_separator=word_split_separator,
)

async def convert_word_async(self, word: str) -> str:
return word.upper()


class TestWordLevelConverter:
async def test_convert_async_all_mode(self):
converter = SimpleWordLevelConverter()
Expand Down Expand Up @@ -120,3 +133,30 @@ async def test_default_is_all_words(self):
assert isinstance(converter._word_selection_strategy, AllWordsSelectionStrategy)
result = await converter.convert_async(prompt="test prompt")
assert result.output_text == "TEST PROMPT"


class TestWordLevelConverterSeparator:
@pytest.mark.parametrize("separator", [",", "|", "-", "::"])
async def test_custom_separator_is_preserved(self, separator):
converter = SeparatorWordLevelConverter(word_split_separator=separator)
prompt = separator.join(["alpha", "beta", "gamma"])
result = await converter.convert_async(prompt=prompt)
assert result.output_text == separator.join(["ALPHA", "BETA", "GAMMA"])

async def test_default_space_separator_unchanged(self):
converter = SeparatorWordLevelConverter()
result = await converter.convert_async(prompt="alpha beta gamma")
assert result.output_text == "ALPHA BETA GAMMA"

async def test_none_separator_joins_with_space(self):
converter = SeparatorWordLevelConverter(word_split_separator=None)
result = await converter.convert_async(prompt="alpha\tbeta\ngamma")
assert result.output_text == "ALPHA BETA GAMMA"

async def test_custom_separator_preserved_with_partial_selection(self):
converter = SeparatorWordLevelConverter(
word_split_separator=",",
word_selection_strategy=WordIndexSelectionStrategy(indices=[0, 2]),
)
result = await converter.convert_async(prompt="alpha,beta,gamma")
assert result.output_text == "ALPHA,beta,GAMMA"