diff --git a/pyrit/converter/template_segment_converter.py b/pyrit/converter/template_segment_converter.py index 7140c51e09..77d48470d4 100644 --- a/pyrit/converter/template_segment_converter.py +++ b/pyrit/converter/template_segment_converter.py @@ -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",) @@ -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. @@ -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, } diff --git a/tests/unit/converter/test_template_segment_converter.py b/tests/unit/converter/test_template_segment_converter.py index 73b85b5b9b..1add19c45e 100644 --- a/tests/unit/converter/test_template_segment_converter.py +++ b/tests/unit/converter/test_template_segment_converter.py @@ -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(): @@ -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