Skip to content

FEAT normalize messages before sending#1613

Draft
hannahwestra25 wants to merge 8 commits intomicrosoft:mainfrom
hannahwestra25:hawestra/normalize_send_prompt
Draft

FEAT normalize messages before sending#1613
hannahwestra25 wants to merge 8 commits intomicrosoft:mainfrom
hannahwestra25:hawestra/normalize_send_prompt

Conversation

@hannahwestra25
Copy link
Copy Markdown
Contributor

Description

Utilize the Normalization Pipeline in the Target Send Path

PR 4 of the TargetConfiguration roadmap

Problem

The TargetConfiguration.normalize_async pipeline (system-squash, history-squash, etc.) was fully built in this PR but never called. Every target independently fetched conversation history, appended the current message, and sent it to the API — some with ad-hoc normalization (AzureMLChatTarget), most with none at all. This meant the centralized normalization pipeline was dead code, and normalization behavior was inconsistent across targets.

Solution

Wire the normalization pipeline into the send path so that every prompt passes through configuration.normalize_async() before reaching the target's API call. This is done by making send_prompt_async a concrete template method on PromptTarget that validates, fetches conversation from memory, runs the normalization pipeline, and delegates to a new _send_prompt_target_async abstract method for wire-format-specific logic.

Changes

  • PromptTarget.send_prompt_async: Now a concrete method that calls self.configuration.normalize_async(messages=...) and passes the result to _send_prompt_target_async
  • All 20 target subclasses: Renamed send_prompt_async_send_prompt_target_async, removed duplicated validation/memory-fetch boilerplate, now receive the pre-normalized conversation directly
  • AzureMLChatTarget: message_normalizer parameter deprecated with auto-translation to TargetConfiguration(policy={SYSTEM_PROMPT: ADAPT}); will be removed in v0.14.0

Breaking Changes

  • Target authors must override _send_prompt_target_async instead of send_prompt_async

Tests and Documentation

  • Tests: Updated all mocks/stubs to new signature; added test_normalize_async_integration.py (395 lines) covering normalize-is-called, normalized-conversation-is-used, memory-not-mutated, and legacy deprecation paths

wip: running integration tests

Comment thread pyrit/prompt_target/azure_ml_chat_target.py
Copy link
Copy Markdown
Contributor

@romanlutz romanlutz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need tests for this?

Nvm didn't render first time I looked!

Comment thread pyrit/prompt_target/azure_blob_storage_target.py Outdated
Comment thread pyrit/prompt_target/http_target/http_target.py Outdated

@limit_requests_per_minute
async def send_prompt_async(self, *, message: Message) -> list[Message]:
async def _send_prompt_target_async(self, *, normalized_conversation: list[Message]) -> list[Message]:
Copy link
Copy Markdown
Contributor

@jsong468 jsong468 Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about making normalized_conversation a data object (maybe in message.py) in order to call clearly-named properties like normalized_conversation.current_message and normalized_conversation.current_request instead of normalized_conversation[-1] and normalized_conversation[-1].message_pieces[0]? I'm thinking it might not be super clear what those indices are referring to when creating/editing target subclasses.

Comment thread pyrit/prompt_target/openai/openai_response_target.py
Comment thread pyrit/prompt_target/common/prompt_target.py
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably use a normalized_conversation param instead of conversation_id to ensure consistency

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/ anywhere we use conversation_id to grab conversation history, we should probably use the normalized_conversation list instead since it includes history now.

message = normalized_conversation[-1]
message_piece: MessagePiece = message.message_pieces[0]
json_config = _JsonResponseConfig(enabled=False)
if message.message_pieces:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: we can get rid of this check?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants