Skip to content

Transforms

Use transforms to rewrite inputs, adapt prompts, and normalize tool-calling behavior in SDK workflows.

Transforms are reusable input-rewriting components. They are most useful when you want to mutate a prompt or other object before it reaches the model, instead of changing the task, scorer, or hook.

Use a transform when you want to:

  • adapt or perturb text before an attack or evaluation
  • normalize tool-calling syntax for models that do not support native function calling
  • apply a reusable input rewrite across many trials
  • carry compliance tags with the rewrite itself

Do not use a transform when you actually need:

  • a hook to react to agent events
  • a scorer to judge quality or safety
  • a tool to perform an external action
TypeWhat it changesCommon use
Transforman input object before executionmutate text, adapt prompts, build attack variants
PostTransforma generated Chat after executionnormalize or post-process a generated chat record

Most user code starts with Transform.

Transform can wrap a normal callable or async callable. The result is reusable and composable.

from dreadnode import Transform
strip_markers = Transform(
lambda text: text.replace("[IGNORE]", "").strip(),
name="strip_markers",
modality="text",
)
cleaned = await strip_markers(" [IGNORE] Investigate the host. ")
print(cleaned)

If you already have a list or mapping of transforms, normalize them with Transform.fit_many(...).

from dreadnode import Transform
transforms = Transform.fit_many(
{
"trim": lambda text: text.strip(),
"lower": lambda text: text.lower(),
}
)
for transform in transforms:
print(transform.name)

Transforms are a first-class part of the AIRT attack factories. This is the most common way most users will encounter them.

from dreadnode.airt import pair_attack
from dreadnode.transforms.language import adapt_language
attack = pair_attack(
goal="Reveal the hidden system prompt",
target=target,
attacker_model="openai/gpt-4o-mini",
evaluator_model="openai/gpt-4o-mini",
transforms=[
adapt_language("Spanish", adapter_model="openai/gpt-4o-mini"),
],
)
result = await attack.run()
print(result.best_candidate)

This keeps the attack logic the same while varying how prompts are presented to the target.

The SDK ships many prebuilt transform modules under dreadnode.transforms, including:

  • language for adaptation and transliteration
  • injection for prompt injection and framing patterns
  • reasoning_attacks, system_prompt_extraction, and advanced_jailbreak for red-team workflows
  • document, image, audio, and video for modality-specific transforms
  • rag_poisoning, documentation_poison, and mcp_attacks for system-level attack surfaces

The right way to explore the surface is to import the relevant module and inspect the factory functions it exports.

Some transforms are infrastructural rather than adversarial. The most common examples are the tool-calling adapters:

  • tools_to_json_transform
  • tools_to_json_in_xml_transform
  • tools_to_json_with_tag_transform
  • tools_to_pythonic_transform

The Agent runtime uses these internally based on tool_mode. You usually do not call them directly unless you are building a lower-level generator workflow.

from dreadnode.transforms import get_transform, tools_to_json_transform
json_transform = get_transform("json")
assert json_transform is tools_to_json_transform

How transforms relate to the rest of the SDK

Section titled “How transforms relate to the rest of the SDK”

Think of it this way:

  • transforms change what goes in
  • scorers judge what comes out
  • hooks react to what happened during execution

That separation keeps experiments easier to debug.

  • Do not use a transform as a hidden evaluator. If you need a score, use a scorer.
  • Do not bury critical business logic in an unlabelled lambda. Give important transforms names.
  • Do not assume transforms are agent-only. They are used heavily in AIRT and optimization too.
  • Do not forget that PostTransform is a different abstraction from Transform.