Prompt Targets are endpoints for where to send prompts. For example, a target could be a GPT-4 or Llama endpoint. Targets are typically used with other components like attacks, scorers, and converters.
An attack’s main job is to change prompts to a given format, apply any converters, and then send them off to prompt targets (sometimes using various strategies). Within an attack, prompt targets are (mostly) swappable, meaning you can use the same logic with different target endpoints.
A scorer’s main job is to score a prompt. Often, these use LLMs, in which case, a given scorer can often use different configured targets.
A converter’s job is to transform a prompt. Often, these use LLMs, in which case, a given converter can use different configured targets.
Prompt targets are found here in code.
send_prompt_async¶
The main entry method has the following signature:
async def send_prompt_async(
self,
*,
message: Message,
send_context: TargetSendContext | None = None,
) -> list[Message]:A Message object contains the current request and the identifiers needed to load its conversation
history. This is discussed in more depth here.
send_context is an internal protocol that lets caller-owned execution state select persisted history
and observe when target-specific execution begins. Attacks with prepended history own the concrete
PrependedHistorySendContext; targets do not construct it, clone it, or decide whether its seed should
be replayed. Before target-specific execution, PromptTarget loads memory history, asks the protocol for the
caller-approved target view, and then runs the target’s capability-normalization pipeline. Request
converters have already run by this point, so role-specific converter choices remain intact even when
the target must receive one flattened request. The context is ephemeral and does not replace the
structured messages stored in memory.
Prepended request converters apply only to user messages by default. Every other role, including
system, developer, tool, and assistant / simulated_assistant, requires explicit opt-in.
For a stateful target without editable history, the initial bootstrap is flattened once. Later sends
retain replayable memory history in the normalized view so a target such as WebsocketTarget can
restore a replaced provider session, while the existing provider session still receives only the
current request. A stateful TAP clone bootstraps its new provider session with the complete replayable
duplicated branch, even when the attack started without an explicit prepended seed. Stateless targets
continue to receive only the explicit prepended seed plus each current request, never prior live branch
turns. A stateful TAP target without editable history can flatten only text converter output when
branching; non-text converter output requires an editable-history target, a stateless target, or
branching_factor=1 so copied media is never replayed under a different role.
PromptTarget records one target invocation immediately before calling
_send_prompt_to_target_async. This framework-owned boundary is not a target capability or a
subclass-managed flag. A stateful target consumes one-time bootstrap history only after that call
returns successfully; normalization failures, target errors, and cancellation retain it for retry.
Target-side rate limiting remains independent and continues to use limit_requests_per_minute.
send_prompt_async is the final public orchestration method. Custom target subclasses implement
_send_prompt_to_target_async(*, normalized_conversation: list[Message]) -> list[Message] instead of
overriding send_prompt_async.
Chat-style targets vs general targets¶
A PromptTarget is a generic place to send a prompt. With PyRIT, the idea is that it will eventually be consumed by an AI application, but that doesn’t have to be immediate. For example, you could have a SharePoint target. Everything you send a prompt to is a PromptTarget. Many attacks work generically with any PromptTarget including RedTeamingAttack and PromptSendingAttack.
With some algorithms, you want to send a prompt, set a system prompt, and modify conversation history (including PAIR Chao et al., 2023, TAP Mehrotra et al., 2023, and flip attack Liu et al., 2024). These algorithms require a target whose TargetCapabilities declare both supports_multi_turn=True and supports_editable_history=True — i.e. you can modify a conversation history. Consumers express this requirement via CHAT_TARGET_REQUIREMENTS and validate it against target.configuration at construction time. See Target Capabilities below for the full list of capabilities and how they compose into a TargetConfiguration.
Note: The previous PromptChatTarget class has been removed. Use PromptTarget directly with a TargetConfiguration declaring supports_multi_turn=True and supports_editable_history=True. See Target Capabilities for details.
Here are some examples:
| Example | Chat-style target? | Notes |
|---|---|---|
| OpenAIChatTarget (e.g., GPT-4) | Yes (multi-turn + editable history) | Designed for conversational prompts (system messages, conversation history, etc.). |
| OpenAIImageTarget | No | Used for image generation; does not manage conversation history. |
| HTTPTarget | No | Generic HTTP target. Some apps might allow conversation history, but this target doesn’t handle it. |
| A2ATarget | No (multi-turn, but no editable history) | Text-only Agent-to-Agent endpoints through the official a2a-sdk (v0.3 / v1.0). |
| AzureBlobStorageTarget | No | Used primarily for storage; not for conversation-based AI. |
OpenAI SDK compatibility¶
PyRIT supports OpenAI Python SDK 2.20 and later 2.x releases, and SDK 3.x. The upper bound excludes SDK 4 until it has been evaluated. The compatibility tests exercise both SDK lines with fabricated provider responses, including custom HTTP clients, authentication, errors, and binary responses.
SDK 2 uses httpx by default; SDK 3 uses httpx2. The
httpx_client_kwargs parameter keeps its existing name and forwards options to
AsyncOpenAI, not to an HTTP client constructor. Numeric timeouts keep working:
target = OpenAIChatTarget(
model_name="my-deployment",
endpoint="https://my-resource.openai.azure.com/openai/v1",
api_key="...",
httpx_client_kwargs={"timeout": 180},
)For a custom transport, proxy, TLS context, or event hook, construct an async
HTTP client and pass it as httpx_client_kwargs={"http_client": client}.
An httpx.AsyncClient works on both SDK lines using SDK 3’s legacy-client
compatibility layer. On SDK 3, httpx2.AsyncClient and
openai.DefaultAsyncHttpxClient use the native HTTPX2 transport. Keep custom
transport classes, request/response objects, and hooks in the same HTTP family
as the client, and close caller-created clients when finished.
The SDK 3 default transport uses the operating-system TLS trust store rather
than HTTPX’s certifi bundle. Install corporate or private CA certificates in
that trust store, set SSL_CERT_FILE / SSL_CERT_DIR, or pass a client with
an explicit TLS context. Catch SDK exceptions such as openai.APITimeoutError
and openai.APIConnectionError, rather than assuming their underlying cause
is always an HTTPX exception. See the upstream
HTTPX2 migration guide.
Use base URLs without the API operation path, for example /openai/v1 rather
than /openai/deployments/.../chat/completions. Pass deployment names through
model_name and additional query parameters through
httpx_client_kwargs={"default_query": {"api-version": "..."}}.
The SDK owns trailing-slash normalization and appending the operation path.
OpenAIVideoTarget retains the deprecated videos API for compatible endpoints.
This does not restore availability of OpenAI’s retired Sora service. SDK 3’s
deprecation warnings remain visible; type-check exceptions are limited to the
specific SDK calls in that adapter.
The default installation does not require LiteLLM and can resolve to SDK 3.
The optional litellm and all extras retain stable LiteLLM releases. A stable
LiteLLM release that requires openai<3 selects SDK 2, which PyRIT also supports;
there is no need to install a development build to use these extras.
The shared uv.lock therefore retains SDK 2 to satisfy all extras. CI tests
SDK 2 and 3 without LiteLLM, and stable LiteLLM with SDK 2. Ordinary package
installations resolve the selected extras independently of this development lock.
A2A agents¶
Install the optional client with pip install "pyrit[a2a]" (also included in
pyrit[all]). A2ATarget uses the official a2a-sdk 1.x client for JSON-RPC.
Set protocol_version="0.3" (the default) or "1.0" for a known endpoint.
Set "auto" to discover the agent card. Discovery errors are not hidden by
a fallback to another protocol. Use agent_card_path for a non-standard
relative card path.
The adapter sends one text piece per turn. The agent owns its context and
history. The target cannot edit or replay that history, send system-role
messages, enforce native JSON output, or send audio, images, or files.
Custom capabilities cannot enable these unsupported features or disable
multi-turn support. Disabling it would squash local history while the agent
retains the same history. To restore an
existing upstream context on a new target instance, use
set_conversation_context. A local history without an upstream context ID
is rejected. reset_conversation_async forgets the local mapping; it does
not delete remote state. Sends and resets for the same conversation are
serialized. Different conversations can run concurrently.
set_conversation_context raises an error if that conversation is in use.
The target retries submission only after an explicit HTTP 429. Retries use
the same A2A message ID. Rate-limit errors during polling retry only the poll.
Polling honors Retry-After (seconds or HTTP date), with exponential backoff
when that header is absent or invalid. The polling deadline bounds these waits.
max_requests_per_minute applies to submission attempts and task polls, not
agent-card discovery.
Empty results, task failures, and agent errors that mention a downstream
rate limit do not cause automatic resubmission. A polling timeout does not
cancel the remote task. Do not blindly resubmit an action after a timeout.
request_timeout_seconds sets the HTTPX connect, read, write, and pool
timeouts and defaults to task_timeout_seconds. These are per-phase
timeouts, not a total turn deadline. Instead, pass HTTPX timeout for
per-phase settings or timeout=None to disable HTTP timeouts. Do not pass
both timeout options. Timeouts must be finite and positive.
task_timeout_seconds is a separate deadline that starts after submission
returns a pending task. It bounds polling, rate-limit waits, and in-flight
poll requests, even with timeout=None. poll_interval_seconds must be
finite and non-negative.
Identifiers include the endpoint, requested protocol, card path, and optional
routing_identifier. Set this non-secret deployment label when HTTP headers
select a different agent at the same endpoint. Do not put tokens in the URL,
card path, or routing label. Credentials, HTTP headers, and timeout settings
are not copied into identifier parameters.
auth_token accepts a static token or an async callable that returns a token.
The callable runs before each HTTP request, including discovery, submission
retries, and polls. Use a provider that caches tokens and refreshes them
before expiry. Do not combine auth_token with HTTPX auth or an
Authorization header. Custom HTTPX authentication is supported when
auth_token is not set.
For Foundry, keep the credential open for the full target operation:
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
from pyrit.prompt_target import A2ATarget
async with DefaultAzureCredential() as credential:
token_provider = get_bearer_token_provider(credential, "https://ai.azure.com/.default")
target = A2ATarget(
endpoint=agent_endpoint,
protocol_version="auto",
agent_card_path="agentCard/v1.0",
auth_token=token_provider,
)
responses = await target.send_prompt_async(message=message)A2A integration tests¶
The local tests run an official SDK server on an ephemeral loopback port.
They cover 0.3 and 1.0, discovery, persisted multi-turn history, task
continuation, failure, and prevention of duplicate submissions. From a
development environment with the all extra, set RUN_ALL_TESTS=true and run:
uv run pytest tests/integration/targets/test_a2a_target_integration.py -k "not foundry"The separate Foundry test also requires RUN_ALL_TESTS=true and
A2A_FOUNDRY_ENDPOINT. It uses A2A_FOUNDRY_AUTH_TOKEN if supplied; otherwise,
it uses a refreshable Entra token provider through DefaultAzureCredential for
https://ai.azure.com/.default. The identity must have access to the agent
(for example, the Agent Consumer role). A2A_FOUNDRY_CARD_PATH
defaults to agentCard/v1.0; set it to
agentCard/v0.3 for a preview endpoint. The test requires a successful text
response, not just a non-empty error. It does not create Azure resources.
Target Capabilities¶
Every PromptTarget exposes a TargetConfiguration (via target.configuration) that declares what the target natively supports. This lets attacks, converters, and scorers reason about whether a given target is suitable for a given workflow — and, where possible, adapt automatically when a capability is missing.
A TargetConfiguration composes three concerns:
TargetCapabilities— an immutable, declarative description of what the target natively supports.CapabilityHandlingPolicy— for each capability that can be adapted, whether toADAPT(apply a normalization step to work around the gap) orRAISE(fail immediately).ConversationNormalizationPipeline— the ordered set of normalizers derived from the gap between the declared capabilities and the policy.
Capabilities¶
TargetCapabilities declares the following flags:
| Capability | Meaning |
|---|---|
supports_multi_turn | The target accepts and uses conversation history (or maintains state externally, e.g. via a WebSocket). |
supports_multi_message_pieces | The target accepts more than one MessagePiece in a single request (e.g. text + image in one message). |
supports_editable_history | The conversation history can be modified after the fact. Implies supports_multi_turn. Required for attacks that rewrite prior turns. |
supports_system_prompt | The target natively supports a system-role message. |
supports_json_output | The target supports a “json” response format that guarantees valid JSON output. |
supports_json_schema | The target supports constraining output to a caller-provided JSON schema. |
input_modalities | The set of input modality combinations the target accepts (e.g. {text}, {text, image_path}). |
output_modalities | The set of output modality combinations the target produces. |
Each target class defines defaults; instances can override individual capabilities when they depend on deployment configuration (e.g. HTTPTarget, PlaywrightTarget).
For well-known underlying models, you can look up a profile with get_known_capabilities(underlying_model="gpt-4o") from pyrit.prompt_target.
Tool-call history uses the existing function_call and function_call_output input
modalities. These describe acceptance of prior calls and results, not tool execution.
See Target Capabilities for examples.
How consumers use capabilities¶
Components that need a particular capability declare it as a TargetRequirements and validate at construction time:
from pyrit.prompt_target import CHAT_TARGET_REQUIREMENTS
CHAT_TARGET_REQUIREMENTS.validate(target=target)TargetRequirements.validate collects every missing capability and raises a single ValueError. For one-off checks against a single capability you can also call target.configuration.ensure_can_handle(capability=...) directly.
TargetRequirements can also enforce modality constraints via required_input_modalities and required_output_modalities. Each entry is a set of PromptDataType values the consumer needs the target to accept (or produce). At least one of the target’s modality combos must be a superset of each required combo:
from pyrit.prompt_target import TargetRequirements
# A consumer that requires image input and text output
VISION_REQUIREMENTS = TargetRequirements(
required_input_modalities=frozenset({frozenset({"image_path"})}),
required_output_modalities=frozenset({frozenset({"text"})}),
)
VISION_REQUIREMENTS.validate(target=target)Adapting vs raising¶
Some capability gaps can be papered over by PyRIT itself. For example, a single-turn target can be made to appear multi-turn by flattening the conversation history into a single prompt before sending. The CapabilityHandlingPolicy controls this on a per-capability basis:
UnsupportedCapabilityBehavior.RAISE— fail at construction time. This is the safe default.UnsupportedCapabilityBehavior.ADAPT— run the corresponding normalizer in the conversation pipeline before the target sees the messages.
Non-adaptable capabilities (e.g. supports_editable_history) are not represented in the policy at all; requesting them on a target that lacks them always raises.
Overriding capabilities per instance¶
For targets whose capabilities depend on deployment (HTTP endpoints, Playwright-driven UIs, custom backends), pass a TargetConfiguration to the constructor:
from pyrit.prompt_target.common.target_capabilities import TargetCapabilities
from pyrit.prompt_target.common.target_configuration import TargetConfiguration
config = TargetConfiguration(
capabilities=TargetCapabilities(
supports_multi_turn=True,
supports_editable_history=True,
supports_system_prompt=True,
),
)
target = MyHTTPTarget(custom_configuration=config, ...)The full implementation lives in pyrit/prompt_target/common/target_capabilities.py and pyrit/prompt_target/common/target_configuration.py. For runnable examples — inspecting capabilities on a real target, comparing known model profiles, and ADAPT vs RAISE in action — see Target Capabilities.
Discovering live target capabilities¶
Declared capabilities describe what a target should support. For deployments where actual behavior is uncertain — custom OpenAI-compatible endpoints, gateways that strip features, models whose support drifts — you can probe what the target actually accepts at runtime:
from pyrit.prompt_target import discover_target_capabilities_async
# Probe boolean capabilities and input modalities, returning a
# best-effort TargetCapabilities:
queried = await discover_target_capabilities_async(target=target)Each probe sends a minimal request (bounded by per_probe_timeout_s, default 30s, with one retry on transient errors) and only marks a capability or modality as supported if the call returns cleanly. discover_target_capabilities_async returns a merged view: probed where possible, declared where probing is unavailable or out of scope. “Supported” here means the request was accepted — a target that silently ignores a system prompt or response_format directive is still reported as supporting it, so validate response content out of band when the distinction matters. This function is not safe to call concurrently with other operations on the same target instance: it temporarily mutates target._configuration and writes probe rows to memory (rows are tagged with prompt_metadata["capability_probe"] == "1" for filtering). See Target Capabilities for runnable examples.
Multi-Modal Targets¶
Like most of PyRIT, targets can be multi-modal.
OpenAI Chat Target (text + image --> text)
OpenAI Image Target (text --> image or text + image --> image)
OpenAI Video Target (text --> video)
OpenAI TTS Target (text --> audio)
- Chao, P., Robey, A., Dobriban, E., Hassani, H., Pappas, G. J., & Wong, E. (2023). Jailbreaking Black Box Large Language Models in Twenty Queries. arXiv Preprint arXiv:2310.08419. https://arxiv.org/abs/2310.08419
- Mehrotra, A., Zampetakis, M., Kassianik, P., Nelson, B., Anderson, H., Singer, Y., & Karbasi, A. (2023). Tree of Attacks: Jailbreaking Black-Box LLMs Automatically. arXiv Preprint arXiv:2312.02119. https://arxiv.org/abs/2312.02119
- Liu, Y., He, X., Xiong, M., Fu, J., Deng, S., Ma, Y., Zhang, J., & Hooi, B. (2024). FlipAttack: Jailbreak LLMs via Flipping. arXiv Preprint arXiv:2410.02832. https://arxiv.org/abs/2410.02832