Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Prompt Targets

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.

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:

ExampleChat-style target?Notes
OpenAIChatTarget (e.g., GPT-4)Yes (multi-turn + editable history)Designed for conversational prompts (system messages, conversation history, etc.).
OpenAIImageTargetNoUsed for image generation; does not manage conversation history.
HTTPTargetNoGeneric HTTP target. Some apps might allow conversation history, but this target doesn’t handle it.
A2ATargetNo (multi-turn, but no editable history)Text-only Agent-to-Agent endpoints through the official a2a-sdk (v0.3 / v1.0).
AzureBlobStorageTargetNoUsed 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:

Capabilities

TargetCapabilities declares the following flags:

CapabilityMeaning
supports_multi_turnThe target accepts and uses conversation history (or maintains state externally, e.g. via a WebSocket).
supports_multi_message_piecesThe target accepts more than one MessagePiece in a single request (e.g. text + image in one message).
supports_editable_historyThe conversation history can be modified after the fact. Implies supports_multi_turn. Required for attacks that rewrite prior turns.
supports_system_promptThe target natively supports a system-role message.
supports_json_outputThe target supports a “json” response format that guarantees valid JSON output.
supports_json_schemaThe target supports constraining output to a caller-provided JSON schema.
input_modalitiesThe set of input modality combinations the target accepts (e.g. {text}, {text, image_path}).
output_modalitiesThe 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:

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.

References
  1. 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
  2. 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
  3. 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