Skip to content
Closed
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/template_segment_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class TemplateSegmentConverter(Converter):

This converter is a generalized version of this:
https://adversa.ai/blog/universal-llm-jailbreak-chatgpt-gpt-4-bard-bing-anthropic-and-beyond/

The converter identifier captures the ordered template parameter names alongside
the template hash, parameter count, and seed. Changing parameter ordering alters
segment binding, so differing parameter orders produce distinct identifier hashes
and registry names.
"""

SUPPORTED_INPUT_TYPES = ("text",)
Expand Down Expand Up @@ -73,7 +78,7 @@ def __init__(

def _build_identifier(self) -> ComponentIdentifier:
"""
Build identifier with template parameters.
Build identifier with template hash, ordered parameter names, parameter count, and seed.

Returns:
ComponentIdentifier: The identifier for this converter.
Expand All @@ -82,6 +87,7 @@ def _build_identifier(self) -> ComponentIdentifier:
return self._create_identifier(
params={
"template_hash": template_hash,
"parameters": list(self.prompt_template.parameters or []),
"number_parameters": self._number_parameters,
"seed": self._seed,
}
Expand Down
58 changes: 58 additions & 0 deletions tests/unit/converter/test_template_segment_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from pyrit.converter import TemplateSegmentConverter
from pyrit.models import SeedPrompt
from pyrit.registry import ConverterRegistry


def test_template_segment_converter_init_default():
Expand Down Expand Up @@ -168,3 +169,60 @@ async def test_template_segment_converter_invalid_input_type():
converter = TemplateSegmentConverter()
with pytest.raises(ValueError, match="Input type not supported"):
await converter.convert_async(prompt="test", input_type="image_path")


def test_template_segment_converter_identifier_includes_ordered_parameters() -> None:
"""Test that the converter identifier includes ordered parameter names."""
template = SeedPrompt(value="{{ left }} and {{ right }}", parameters=["left", "right"])
converter = TemplateSegmentConverter(prompt_template=template, seed=42)
identifier = converter.get_identifier()

assert identifier.params["parameters"] == ["left", "right"]
assert identifier.params["number_parameters"] == 2
assert identifier.params["seed"] == 42
assert "template_hash" in identifier.params


def test_template_segment_converter_identifier_differs_when_parameters_reordered() -> None:
"""Reordering the template parameters changes binding behavior and must yield distinct hashes."""
template = "LEFT[{{left}}] RIGHT[{{right}}]"
first = TemplateSegmentConverter(
prompt_template=SeedPrompt(value=template, parameters=["left", "right"]),
seed=7,
)
second = TemplateSegmentConverter(
prompt_template=SeedPrompt(value=template, parameters=["right", "left"]),
seed=7,
)

assert first.get_identifier().hash != second.get_identifier().hash
assert first.get_identifier().params["parameters"] == ["left", "right"]
assert second.get_identifier().params["parameters"] == ["right", "left"]


async def test_template_segment_converter_reordered_parameters_coexist_in_registry_async() -> None:
"""Distinct parameter bindings have distinct default registry names and register without conflict."""
registry = ConverterRegistry()
template = "LEFT[{{left}}] RIGHT[{{right}}]"
first = registry.create_named_instance(
name="source",
type_name="TemplateSegmentConverter",
params={
"prompt_template": SeedPrompt(value=template, parameters=["left", "right"]),
"seed": 7,
},
)
second = registry.recreate_instance(
source=first,
params={"prompt_template": SeedPrompt(value=template, parameters=["right", "left"])},
)

res_first = await first.convert_async(prompt="alpha beta")
res_second = await second.convert_async(prompt="alpha beta")
assert res_first.output_text == "LEFT[alpha] RIGHT[beta]"
assert res_second.output_text == "LEFT[beta] RIGHT[alpha]"

automatic_names = ConverterRegistry()
automatic_names.instances.register(first)
automatic_names.instances.register(second)
assert len(automatic_names.instances) == 2
Loading