diff --git a/doc/code/memory/5_advanced_memory.py b/doc/code/memory/5_advanced_memory.py index 6fed29eb61..6436db6fdb 100644 --- a/doc/code/memory/5_advanced_memory.py +++ b/doc/code/memory/5_advanced_memory.py @@ -263,7 +263,7 @@ ) # Wrap each piece in a Message so we can pass it to score_async -assistant_messages = [Message([piece]) for piece in assistant_pieces] +assistant_messages = [Message(message_pieces=[piece]) for piece in assistant_pieces] # Score every response with both scorers — scores are automatically persisted in memory for msg in assistant_messages: diff --git a/doc/code/memory/6_azure_sql_memory.py b/doc/code/memory/6_azure_sql_memory.py index 693ea2e8c1..3e272aa48d 100644 --- a/doc/code/memory/6_azure_sql_memory.py +++ b/doc/code/memory/6_azure_sql_memory.py @@ -73,9 +73,9 @@ ), ] -memory.add_message_to_memory(request=Message([message_list[0]])) -memory.add_message_to_memory(request=Message([message_list[1]])) -memory.add_message_to_memory(request=Message([message_list[2]])) +memory.add_message_to_memory(request=Message(message_pieces=[message_list[0]])) +memory.add_message_to_memory(request=Message(message_pieces=[message_list[1]])) +memory.add_message_to_memory(request=Message(message_pieces=[message_list[2]])) entries = memory.get_conversation_messages(conversation_id=conversation_id) diff --git a/doc/code/targets/4_openai_video_target.py b/doc/code/targets/4_openai_video_target.py index 811d7bb0f2..465cde5dc0 100644 --- a/doc/code/targets/4_openai_video_target.py +++ b/doc/code/targets/4_openai_video_target.py @@ -148,7 +148,7 @@ original_value="Make it a watercolor painting style", prompt_metadata={"video_id": video_id}, ) -remix_result = await video_target.send_prompt_async(message=Message([remix_piece])) # type: ignore +remix_result = await video_target.send_prompt_async(message=Message(message_pieces=[remix_piece])) # type: ignore print(f"Remixed video: {remix_result[0].message_pieces[0].converted_value}") # %% [markdown] @@ -190,5 +190,5 @@ converted_value_data_type="image_path", conversation_id=conversation_id, ) -result = await i2v_target.send_prompt_async(message=Message([text_piece, image_piece])) # type: ignore +result = await i2v_target.send_prompt_async(message=Message(message_pieces=[text_piece, image_piece])) # type: ignore print(f"Text+Image-to-video result: {result[0].message_pieces[0].converted_value}") diff --git a/doc/code/targets/6_1_target_capabilities.py b/doc/code/targets/6_1_target_capabilities.py index ee2ceec82f..108472c562 100644 --- a/doc/code/targets/6_1_target_capabilities.py +++ b/doc/code/targets/6_1_target_capabilities.py @@ -304,7 +304,7 @@ def _ok_response(): return [ Message( - [ + message_pieces=[ MessagePiece( role="assistant", original_value="ok", diff --git a/pyrit/message_normalizer/json_schema_normalizer.py b/pyrit/message_normalizer/json_schema_normalizer.py index b3a072fd88..f7acc1cfe2 100644 --- a/pyrit/message_normalizer/json_schema_normalizer.py +++ b/pyrit/message_normalizer/json_schema_normalizer.py @@ -126,7 +126,7 @@ def _adapt_message(self, *, message: Message) -> Message: if not changed: return message - return Message(new_pieces) + return Message(message_pieces=new_pieces) def _adapt_piece(self, *, piece: MessagePiece) -> MessagePiece: """ diff --git a/tests/integration/targets/test_entra_auth_targets.py b/tests/integration/targets/test_entra_auth_targets.py index 18c3201432..9ae437e8a4 100644 --- a/tests/integration/targets/test_entra_auth_targets.py +++ b/tests/integration/targets/test_entra_auth_targets.py @@ -332,7 +332,7 @@ async def test_video_target_remix_entra_auth(sqlite_instance): original_value="A bird flying over a lake", converted_value="A bird flying over a lake", ) - result = await target.send_prompt_async(message=Message([text_piece])) + result = await target.send_prompt_async(message=Message(message_pieces=[text_piece])) response_piece = result[0].message_pieces[0] assert response_piece.response_error == "none" video_id = response_piece.prompt_metadata.get("video_id") @@ -345,7 +345,7 @@ async def test_video_target_remix_entra_auth(sqlite_instance): converted_value="Add a sunset", prompt_metadata={"video_id": video_id}, ) - remix_result = await target.send_prompt_async(message=Message([remix_piece])) + remix_result = await target.send_prompt_async(message=Message(message_pieces=[remix_piece])) assert remix_result[0].message_pieces[0].response_error == "none" diff --git a/tests/integration/targets/test_targets_and_secrets.py b/tests/integration/targets/test_targets_and_secrets.py index 7409f7a3aa..a546664161 100644 --- a/tests/integration/targets/test_targets_and_secrets.py +++ b/tests/integration/targets/test_targets_and_secrets.py @@ -571,7 +571,7 @@ async def test_video_remix_chain(sqlite_instance): original_value="A cat sitting on a windowsill", converted_value="A cat sitting on a windowsill", ) - result = await target.send_prompt_async(message=Message([text_piece])) + result = await target.send_prompt_async(message=Message(message_pieces=[text_piece])) assert len(result) == 1 response_piece = result[0].message_pieces[0] assert response_piece.response_error == "none" @@ -586,7 +586,7 @@ async def test_video_remix_chain(sqlite_instance): converted_value="Make it a watercolor painting style", prompt_metadata={"video_id": video_id}, ) - remix_result = await target.send_prompt_async(message=Message([remix_piece])) + remix_result = await target.send_prompt_async(message=Message(message_pieces=[remix_piece])) assert len(remix_result) == 1 remix_response = remix_result[0].message_pieces[0] assert remix_response.response_error == "none" @@ -636,7 +636,7 @@ async def test_video_image_to_video(sqlite_instance): converted_value_data_type="image_path", conversation_id=conversation_id, ) - result = await target.send_prompt_async(message=Message([text_piece, image_piece])) + result = await target.send_prompt_async(message=Message(message_pieces=[text_piece, image_piece])) assert len(result) == 1 response_piece = result[0].message_pieces[0] assert response_piece.response_error == "none", f"Image-to-video failed: {response_piece.converted_value}" diff --git a/tests/unit/message_normalizer/test_json_schema_normalizer.py b/tests/unit/message_normalizer/test_json_schema_normalizer.py index abfc399328..eed3ef7679 100644 --- a/tests/unit/message_normalizer/test_json_schema_normalizer.py +++ b/tests/unit/message_normalizer/test_json_schema_normalizer.py @@ -46,7 +46,7 @@ class TestJsonSchemaNormalizer: async def test_text_piece_gets_schema_appended_to_converted_value(self, normalizer: JsonSchemaNormalizer) -> None: schema = {"type": "object", "properties": {"answer": {"type": "string"}}} piece = _text_piece(value="Answer the question.", metadata={JSON_SCHEMA_METADATA_KEY: schema}) - message = Message([piece]) + message = Message(message_pieces=[piece]) result = await normalizer.normalize_async([message]) out_piece = result[0].message_pieces[0] @@ -66,7 +66,7 @@ async def test_text_piece_preserves_other_metadata(self, normalizer: JsonSchemaN "other": 7, }, ) - result = await normalizer.normalize_async([Message([piece])]) + result = await normalizer.normalize_async([Message(message_pieces=[piece])]) new_metadata = result[0].message_pieces[0].prompt_metadata assert JSON_SCHEMA_METADATA_KEY not in new_metadata assert new_metadata == {"response_format": "json", "other": 7} @@ -76,7 +76,7 @@ async def test_non_text_piece_only_strips_key(self, normalizer: JsonSchemaNormal piece = _image_piece(value="fake.jpg", metadata={JSON_SCHEMA_METADATA_KEY: schema, "extra": "stay"}) original_converted_value = piece.converted_value - result = await normalizer.normalize_async([Message([piece])]) + result = await normalizer.normalize_async([Message(message_pieces=[piece])]) out_piece = result[0].message_pieces[0] assert JSON_SCHEMA_METADATA_KEY not in out_piece.prompt_metadata @@ -87,7 +87,7 @@ async def test_non_text_piece_only_strips_key(self, normalizer: JsonSchemaNormal async def test_no_schema_is_noop(self, normalizer: JsonSchemaNormalizer) -> None: piece = _text_piece(value="just say hi", metadata={"unrelated": True}) - message = Message([piece]) + message = Message(message_pieces=[piece]) result = await normalizer.normalize_async([message]) @@ -98,7 +98,7 @@ async def test_input_pieces_not_mutated(self, normalizer: JsonSchemaNormalizer) schema = {"type": "object"} piece = _text_piece(value="hi", metadata={JSON_SCHEMA_METADATA_KEY: schema}) - await normalizer.normalize_async([Message([piece])]) + await normalizer.normalize_async([Message(message_pieces=[piece])]) # The original piece still carries the schema and its unchanged text. assert piece.prompt_metadata == {JSON_SCHEMA_METADATA_KEY: schema} @@ -117,7 +117,7 @@ async def test_mixed_pieces_in_message_each_handled(self, normalizer: JsonSchema ) no_schema_piece = _text_piece(value="z", metadata={"foo": "bar"}, conversation_id=conversation_id) - result = await normalizer.normalize_async([Message([text_piece, image_piece, no_schema_piece])]) + result = await normalizer.normalize_async([Message(message_pieces=[text_piece, image_piece, no_schema_piece])]) out_pieces = result[0].message_pieces assert JSON_SCHEMA_METADATA_KEY not in out_pieces[0].prompt_metadata @@ -133,8 +133,8 @@ async def test_mixed_pieces_in_message_each_handled(self, normalizer: JsonSchema async def test_multiple_messages(self, normalizer: JsonSchemaNormalizer) -> None: schema = {"type": "object"} - msg_with_schema = Message([_text_piece(value="a", metadata={JSON_SCHEMA_METADATA_KEY: schema})]) - msg_without_schema = Message([_text_piece(value="b", metadata={})]) + msg_with_schema = Message(message_pieces=[_text_piece(value="a", metadata={JSON_SCHEMA_METADATA_KEY: schema})]) + msg_without_schema = Message(message_pieces=[_text_piece(value="b", metadata={})]) result = await normalizer.normalize_async([msg_with_schema, msg_without_schema]) assert "### Response format" in result[0].message_pieces[0].converted_value @@ -155,7 +155,7 @@ async def test_appended_text_lists_schema_keys(self, normalizer: JsonSchemaNorma } piece = _text_piece(value="prompt", metadata={JSON_SCHEMA_METADATA_KEY: schema}) - result = await normalizer.normalize_async([Message([piece])]) + result = await normalizer.normalize_async([Message(message_pieces=[piece])]) appended = result[0].message_pieces[0].converted_value # Sanity-check that the rendered text actually surfaces schema field names. @@ -170,7 +170,7 @@ async def test_custom_template_is_used(self) -> None: schema = {"type": "object"} piece = _text_piece(value="hi", metadata={JSON_SCHEMA_METADATA_KEY: schema}) - result = await normalizer.normalize_async([Message([piece])]) + result = await normalizer.normalize_async([Message(message_pieces=[piece])]) out_value = result[0].message_pieces[0].converted_value assert "<>" in out_value diff --git a/tests/unit/models/test_message.py b/tests/unit/models/test_message.py index 2827dd13b2..2408a19cc6 100644 --- a/tests/unit/models/test_message.py +++ b/tests/unit/models/test_message.py @@ -73,7 +73,7 @@ def test_get_pieces_by_type_returns_matching_pieces() -> None: converted_value_data_type="image_path", conversation_id=conversation_id, ) - msg = Message([text_piece, image_piece]) + msg = Message(message_pieces=[text_piece, image_piece]) result = msg.get_pieces_by_type(data_type="text") assert len(result) == 1 @@ -86,7 +86,7 @@ def test_get_pieces_by_type_returns_matching_pieces() -> None: def test_get_pieces_by_type_returns_empty_for_no_match() -> None: piece = MessagePiece(role="user", original_value="hello", converted_value="hello") - msg = Message([piece]) + msg = Message(message_pieces=[piece]) assert msg.get_pieces_by_type(data_type="image_path") == [] @@ -94,13 +94,13 @@ def test_get_piece_by_type_returns_first_match() -> None: conversation_id = "test-conv" text1 = MessagePiece(role="user", original_value="a", converted_value="a", conversation_id=conversation_id) text2 = MessagePiece(role="user", original_value="b", converted_value="b", conversation_id=conversation_id) - msg = Message([text1, text2]) + msg = Message(message_pieces=[text1, text2]) assert msg.get_piece_by_type(data_type="text") is text1 def test_get_piece_by_type_returns_none_for_no_match() -> None: piece = MessagePiece(role="user", original_value="hello", converted_value="hello") - msg = Message([piece]) + msg = Message(message_pieces=[piece]) assert msg.get_piece_by_type(data_type="image_path") is None diff --git a/tests/unit/prompt_target/target/test_image_target.py b/tests/unit/prompt_target/target/test_image_target.py index 20ad7eb3b1..dad736abdc 100644 --- a/tests/unit/prompt_target/target/test_image_target.py +++ b/tests/unit/prompt_target/target/test_image_target.py @@ -80,7 +80,7 @@ async def test_send_prompt_async_generate( with patch.object(image_target._async_client.images, "generate", new_callable=AsyncMock) as mock_generate: mock_generate.return_value = mock_response - resp = await image_target.send_prompt_async(message=Message([request])) + resp = await image_target.send_prompt_async(message=Message(message_pieces=[request])) assert len(resp) == 1 assert resp path = resp[0].message_pieces[0].original_value @@ -115,7 +115,7 @@ async def test_send_prompt_async_edit( with patch.object(image_target._async_client.images, "edit", new_callable=AsyncMock) as mock_edit: mock_edit.return_value = mock_response - resp = await image_target.send_prompt_async(message=Message([text_piece, image_piece])) + resp = await image_target.send_prompt_async(message=Message(message_pieces=[text_piece, image_piece])) assert len(resp) == 1 assert resp path = resp[0].message_pieces[0].original_value @@ -151,7 +151,7 @@ async def test_send_prompt_async_edit_single_image_passes_tuple_not_list( with patch.object(image_target._async_client.images, "edit", new_callable=AsyncMock) as mock_edit: mock_edit.return_value = mock_response - resp = await image_target.send_prompt_async(message=Message([text_piece, image_piece])) + resp = await image_target.send_prompt_async(message=Message(message_pieces=[text_piece, image_piece])) assert resp call_kwargs = mock_edit.call_args[1] @@ -189,7 +189,9 @@ async def test_send_prompt_async_edit_multiple_images( with patch.object(image_target._async_client.images, "edit", new_callable=AsyncMock) as mock_edit: mock_edit.return_value = mock_response - resp = await image_target.send_prompt_async(message=Message([image_piece, text_piece] + image_pieces)) + resp = await image_target.send_prompt_async( + message=Message(message_pieces=[image_piece, text_piece] + image_pieces) + ) assert len(resp) == 1 assert resp path = resp[0].message_pieces[0].original_value @@ -226,7 +228,7 @@ async def test_send_prompt_async_invalid_image_path( ) with pytest.raises(FileNotFoundError): - await image_target.send_prompt_async(message=Message([text_piece, image_piece])) + await image_target.send_prompt_async(message=Message(message_pieces=[text_piece, image_piece])) async def test_send_prompt_async_empty_response( @@ -248,7 +250,7 @@ async def test_send_prompt_async_empty_response( mock_generate.return_value = mock_response with pytest.raises(EmptyResponseException): - await image_target.send_prompt_async(message=Message([request])) + await image_target.send_prompt_async(message=Message(message_pieces=[request])) async def test_send_prompt_async_rate_limit_exception( @@ -264,7 +266,7 @@ async def test_send_prompt_async_rate_limit_exception( mock_generate.side_effect = RateLimitError("Rate Limit Reached", response=MagicMock(), body={}) with pytest.raises(RateLimitException): - await image_target.send_prompt_async(message=Message([request])) + await image_target.send_prompt_async(message=Message(message_pieces=[request])) async def test_send_prompt_async_bad_request_error( @@ -290,7 +292,7 @@ async def test_send_prompt_async_bad_request_error( # Non-content-filter BadRequestError should be re-raised (same as chat target behavior) with pytest.raises(Exception): - await image_target.send_prompt_async(message=Message([request])) + await image_target.send_prompt_async(message=Message(message_pieces=[request])) async def test_send_prompt_async_empty_response_adds_memory( @@ -316,7 +318,7 @@ async def test_send_prompt_async_empty_response_adds_memory( image_target._memory = mock_memory with pytest.raises(EmptyResponseException): - await image_target.send_prompt_async(message=Message([request])) + await image_target.send_prompt_async(message=Message(message_pieces=[request])) async def test_send_prompt_async_rate_limit_adds_memory( @@ -338,7 +340,7 @@ async def test_send_prompt_async_rate_limit_adds_memory( image_target._memory = mock_memory with pytest.raises(RateLimitException): - await image_target.send_prompt_async(message=Message([request])) + await image_target.send_prompt_async(message=Message(message_pieces=[request])) async def test_send_prompt_async_bad_request_content_filter( @@ -364,7 +366,7 @@ async def test_send_prompt_async_bad_request_content_filter( with patch.object(image_target._async_client.images, "generate", new_callable=AsyncMock) as mock_generate: mock_generate.side_effect = bad_request_error - result = await image_target.send_prompt_async(message=Message([request])) + result = await image_target.send_prompt_async(message=Message(message_pieces=[request])) assert len(result) == 1 assert result[0].message_pieces[0].converted_value_data_type == "error" assert "content_filter" in result[0].message_pieces[0].converted_value @@ -393,7 +395,7 @@ async def test_send_prompt_async_bad_request_content_policy_violation( with patch.object(image_target._async_client.images, "generate", new_callable=AsyncMock) as mock_generate: mock_generate.side_effect = bad_request_error - result = await image_target.send_prompt_async(message=Message([request])) + result = await image_target.send_prompt_async(message=Message(message_pieces=[request])) assert len(result) == 1 assert result[0].message_pieces[0].response_error == "blocked" assert result[0].message_pieces[0].converted_value_data_type == "error" @@ -542,7 +544,7 @@ async def test_generate_request_passes_background( with patch.object(image_target._async_client.images, "generate", new_callable=AsyncMock) as mock_generate: mock_generate.return_value = mock_response - resp = await image_target.send_prompt_async(message=Message([request])) + resp = await image_target.send_prompt_async(message=Message(message_pieces=[request])) assert resp call_kwargs = mock_generate.call_args[1] @@ -569,7 +571,7 @@ async def test_generate_request_omits_background_when_none( with patch.object(image_target._async_client.images, "generate", new_callable=AsyncMock) as mock_generate: mock_generate.return_value = mock_response - resp = await image_target.send_prompt_async(message=Message([request])) + resp = await image_target.send_prompt_async(message=Message(message_pieces=[request])) assert resp call_kwargs = mock_generate.call_args[1] diff --git a/tests/unit/prompt_target/target/test_prompt_shield_target.py b/tests/unit/prompt_target/target/test_prompt_shield_target.py index f57ed741e9..05003dac32 100644 --- a/tests/unit/prompt_target/target/test_prompt_shield_target.py +++ b/tests/unit/prompt_target/target/test_prompt_shield_target.py @@ -72,7 +72,7 @@ async def test_prompt_shield_reject_non_text( promptshield_target: PromptShieldTarget, audio_message_piece: MessagePiece ): with pytest.raises(ValueError): - await promptshield_target.send_prompt_async(message=Message([audio_message_piece])) + await promptshield_target.send_prompt_async(message=Message(message_pieces=[audio_message_piece])) async def test_prompt_shield_document_parsing( diff --git a/tests/unit/prompt_target/target/test_prompt_target_azure_blob_storage.py b/tests/unit/prompt_target/target/test_prompt_target_azure_blob_storage.py index 49b74c66d1..01ca270c76 100644 --- a/tests/unit/prompt_target/target/test_prompt_target_azure_blob_storage.py +++ b/tests/unit/prompt_target/target/test_prompt_target_azure_blob_storage.py @@ -132,7 +132,7 @@ async def test_send_prompt_async( message_piece = sample_entries[0] message_piece.converted_value = "Test content" - request = Message([message_piece]) + request = Message(message_pieces=[message_piece]) response = await azure_blob_storage_target.send_prompt_async(message=request) diff --git a/tests/unit/prompt_target/target/test_video_target.py b/tests/unit/prompt_target/target/test_video_target.py index 666c07a7b1..555063e054 100644 --- a/tests/unit/prompt_target/target/test_video_target.py +++ b/tests/unit/prompt_target/target/test_video_target.py @@ -81,7 +81,7 @@ def test_video_validate_request_multiple_text_pieces(video_target: OpenAIVideoTa msg2 = MessagePiece( role="user", original_value="test2", converted_value="test2", conversation_id=conversation_id ) - video_target._validate_request(normalized_conversation=[Message([msg1, msg2])]) + video_target._validate_request(normalized_conversation=[Message(message_pieces=[msg1, msg2])]) def test_video_validate_prompt_type_image_only(video_target: OpenAIVideoTarget): @@ -90,7 +90,7 @@ def test_video_validate_prompt_type_image_only(video_target: OpenAIVideoTarget): msg = MessagePiece( role="user", original_value="test", converted_value="test", converted_value_data_type="image_path" ) - video_target._validate_request(normalized_conversation=[Message([msg])]) + video_target._validate_request(normalized_conversation=[Message(message_pieces=[msg])]) async def test_video_send_prompt_async_success( @@ -123,7 +123,7 @@ async def test_video_send_prompt_async_success( mock_download.return_value = mock_video_response mock_factory.return_value = mock_serializer - response = await video_target.send_prompt_async(message=Message([request])) + response = await video_target.send_prompt_async(message=Message(message_pieces=[request])) # Verify SDK methods were called correctly mock_create.assert_called_once_with( @@ -166,7 +166,7 @@ async def test_video_send_prompt_async_failed_content_filter( with patch.object(video_target._async_client.videos, "create_and_poll", new_callable=AsyncMock) as mock_create: mock_create.return_value = mock_video - response = await video_target.send_prompt_async(message=Message([request])) + response = await video_target.send_prompt_async(message=Message(message_pieces=[request])) # Verify response is error with blocked status assert len(response) == 1 @@ -195,7 +195,7 @@ async def test_video_send_prompt_async_failed_processing_error( with patch.object(video_target._async_client.videos, "create_and_poll", new_callable=AsyncMock) as mock_create: mock_create.return_value = mock_video - response = await video_target.send_prompt_async(message=Message([request])) + response = await video_target.send_prompt_async(message=Message(message_pieces=[request])) # Verify response is processing error assert len(response) == 1 @@ -225,7 +225,7 @@ async def test_video_send_prompt_async_bad_request_exception( with patch.object(video_target._async_client.videos, "create_and_poll", new_callable=AsyncMock) as mock_create: mock_create.side_effect = bad_request_error - response = await video_target.send_prompt_async(message=Message([request])) + response = await video_target.send_prompt_async(message=Message(message_pieces=[request])) # Verify response is error with blocked status (content filter) assert len(response) == 1 @@ -251,7 +251,7 @@ async def test_video_send_prompt_async_rate_limit_exception( mock_create.side_effect = rate_limit_error with pytest.raises(RateLimitException): - await video_target.send_prompt_async(message=Message([request])) + await video_target.send_prompt_async(message=Message(message_pieces=[request])) async def test_video_send_prompt_async_api_error( @@ -273,7 +273,7 @@ async def test_video_send_prompt_async_api_error( mock_create.side_effect = api_error with pytest.raises(APIStatusError): - await video_target.send_prompt_async(message=Message([request])) + await video_target.send_prompt_async(message=Message(message_pieces=[request])) async def test_video_send_prompt_async_unexpected_status( @@ -291,7 +291,7 @@ async def test_video_send_prompt_async_unexpected_status( with patch.object(video_target._async_client.videos, "create_and_poll", new_callable=AsyncMock) as mock_create: mock_create.return_value = mock_video - response = await video_target.send_prompt_async(message=Message([request])) + response = await video_target.send_prompt_async(message=Message(message_pieces=[request])) # Verify response is error with unknown status assert len(response) == 1 @@ -367,7 +367,7 @@ def test_validate_accepts_text_only(self, video_target: OpenAIVideoTarget): """Test validation accepts single text piece (text-to-video mode).""" msg = MessagePiece(role="user", original_value="test prompt", converted_value="test prompt") # Should not raise - video_target._validate_request(normalized_conversation=[Message([msg])]) + video_target._validate_request(normalized_conversation=[Message(message_pieces=[msg])]) def test_validate_accepts_text_and_image(self, video_target: OpenAIVideoTarget): """Test validation accepts text + image (image-to-video mode).""" @@ -386,7 +386,7 @@ def test_validate_accepts_text_and_image(self, video_target: OpenAIVideoTarget): conversation_id=conversation_id, ) # Should not raise - video_target._validate_request(normalized_conversation=[Message([msg_text, msg_image])]) + video_target._validate_request(normalized_conversation=[Message(message_pieces=[msg_text, msg_image])]) def test_validate_rejects_multiple_images(self, video_target: OpenAIVideoTarget): """Test validation rejects multiple image pieces.""" @@ -412,7 +412,9 @@ def test_validate_rejects_multiple_images(self, video_target: OpenAIVideoTarget) conversation_id=conversation_id, ) with pytest.raises(ValueError, match="at most 1 image piece"): - video_target._validate_request(normalized_conversation=[Message([msg_text, msg_img1, msg_img2])]) + video_target._validate_request( + normalized_conversation=[Message(message_pieces=[msg_text, msg_img1, msg_img2])] + ) def test_validate_rejects_unsupported_types(self, video_target: OpenAIVideoTarget): """Test validation rejects unsupported data types.""" @@ -435,7 +437,7 @@ def test_validate_rejects_unsupported_types(self, video_target: OpenAIVideoTarge match="This target supports only the following data types.*If your target does support this, set the" " custom_configuration parameter accordingly", ): - video_target._validate_request(normalized_conversation=[Message([msg_text, msg_audio])]) + video_target._validate_request(normalized_conversation=[Message(message_pieces=[msg_text, msg_audio])]) def test_validate_rejects_remix_with_image(self, video_target: OpenAIVideoTarget): """Test validation rejects remix mode combined with image input.""" @@ -455,7 +457,7 @@ def test_validate_rejects_remix_with_image(self, video_target: OpenAIVideoTarget conversation_id=conversation_id, ) with pytest.raises(ValueError, match="Cannot use image input in remix mode"): - video_target._validate_request(normalized_conversation=[Message([msg_text, msg_image])]) + video_target._validate_request(normalized_conversation=[Message(message_pieces=[msg_text, msg_image])]) @pytest.mark.usefixtures("patch_central_database") @@ -517,7 +519,7 @@ async def test_image_to_video_calls_create_with_input_reference(self, video_targ mock_download.return_value = mock_video_response mock_mime.return_value = "image/png" - response = await video_target.send_prompt_async(message=Message([msg_text, msg_image])) + response = await video_target.send_prompt_async(message=Message(message_pieces=[msg_text, msg_image])) # Verify create_and_poll was called with input_reference as tuple with MIME type mock_create.assert_called_once() @@ -588,7 +590,7 @@ async def test_remix_calls_remix_and_poll(self, video_target: OpenAIVideoTarget) mock_download.return_value = mock_video_response mock_factory.return_value = mock_serializer - response = await video_target.send_prompt_async(message=Message([msg])) + response = await video_target.send_prompt_async(message=Message(message_pieces=[msg])) # Verify remix was called with correct params mock_remix.assert_called_once_with("existing_video_123", prompt="make it more dramatic") @@ -634,7 +636,7 @@ async def test_remix_skips_poll_if_completed(self, video_target: OpenAIVideoTarg mock_download.return_value = mock_video_response mock_factory.return_value = mock_serializer - await video_target.send_prompt_async(message=Message([msg])) + await video_target.send_prompt_async(message=Message(message_pieces=[msg])) # Verify poll was NOT called since status was already completed mock_poll.assert_not_called() @@ -688,7 +690,7 @@ async def test_remix_with_text_and_video_path_pieces(self, video_target: OpenAIV mock_download.return_value = mock_video_response mock_factory.return_value = mock_serializer - response = await video_target.send_prompt_async(message=Message([msg_text, msg_video])) + response = await video_target.send_prompt_async(message=Message(message_pieces=[msg_text, msg_video])) # Verify remix was called with the video_id from text metadata mock_remix.assert_called_once_with("vid_from_ui_123", prompt="make it more dramatic") @@ -743,7 +745,7 @@ async def test_response_includes_video_id_metadata(self, video_target: OpenAIVid mock_download.return_value = mock_video_response mock_factory.return_value = mock_serializer - response = await video_target.send_prompt_async(message=Message([msg])) + response = await video_target.send_prompt_async(message=Message(message_pieces=[msg])) # Verify response contains video_id in metadata for chaining response_piece = response[0].message_pieces[0] @@ -766,7 +768,7 @@ def video_target(self) -> OpenAIVideoTarget: def test_validate_rejects_empty_message(self, video_target: OpenAIVideoTarget): """Test that empty messages are rejected (by Message constructor).""" with pytest.raises(ValueError, match="at least one message piece"): - Message([]) + Message(message_pieces=[]) def test_validate_rejects_no_text_piece(self, video_target: OpenAIVideoTarget): """Test validation rejects message without text piece.""" @@ -777,7 +779,7 @@ def test_validate_rejects_no_text_piece(self, video_target: OpenAIVideoTarget): converted_value_data_type="image_path", ) with pytest.raises(ValueError, match="Expected exactly 1 text piece"): - video_target._validate_request(normalized_conversation=[Message([msg])]) + video_target._validate_request(normalized_conversation=[Message(message_pieces=[msg])]) async def test_image_to_video_with_jpeg(self, video_target: OpenAIVideoTarget): """Test image-to-video with JPEG image format.""" @@ -825,7 +827,7 @@ async def test_image_to_video_with_jpeg(self, video_target: OpenAIVideoTarget): mock_download.return_value = mock_video_response mock_mime.return_value = "image/jpeg" - response = await video_target.send_prompt_async(message=Message([msg_text, msg_image])) + response = await video_target.send_prompt_async(message=Message(message_pieces=[msg_text, msg_image])) # Verify JPEG MIME type is used call_kwargs = mock_create.call_args.kwargs @@ -882,7 +884,7 @@ async def test_image_to_video_with_webp_uses_guess_type_fallback(self, video_tar mock_download.return_value = mock_video_response mock_mime.return_value = None # strict=True returns None for .webp - response = await video_target.send_prompt_async(message=Message([msg_text, msg_image])) + response = await video_target.send_prompt_async(message=Message(message_pieces=[msg_text, msg_image])) # Verify webp MIME type is correctly resolved via guess_type fallback call_kwargs = mock_create.call_args.kwargs @@ -917,7 +919,7 @@ async def test_image_to_video_with_unknown_mime_raises_error(self, video_target: mock_factory.return_value = mock_image_serializer mock_mime.return_value = None # MIME type cannot be determined - await video_target.send_prompt_async(message=Message([msg_text, msg_image])) + await video_target.send_prompt_async(message=Message(message_pieces=[msg_text, msg_image])) async def test_remix_with_failed_status(self, video_target: OpenAIVideoTarget): """Test remix mode handles failed video generation.""" @@ -943,7 +945,7 @@ async def test_remix_with_failed_status(self, video_target: OpenAIVideoTarget): mock_remix.return_value = mock_video # Don't need poll since status is already "failed" - response = await video_target.send_prompt_async(message=Message([msg])) + response = await video_target.send_prompt_async(message=Message(message_pieces=[msg])) # Verify response is processing error response_piece = response[0].message_pieces[0] @@ -1018,7 +1020,7 @@ def test_validate_accepts_text_and_video_path(self, video_target: OpenAIVideoTar conversation_id=conversation_id, ) # Should not raise - video_target._validate_request(normalized_conversation=[Message([msg_text, msg_video])]) + video_target._validate_request(normalized_conversation=[Message(message_pieces=[msg_text, msg_video])]) def test_validate_rejects_video_path_and_image_path(self, video_target: OpenAIVideoTarget) -> None: """Test validation rejects combining video_path and image_path.""" @@ -1044,7 +1046,9 @@ def test_validate_rejects_video_path_and_image_path(self, video_target: OpenAIVi conversation_id=conversation_id, ) with pytest.raises(ValueError, match="Cannot combine video_path and image_path"): - video_target._validate_request(normalized_conversation=[Message([msg_text, msg_video, msg_image])]) + video_target._validate_request( + normalized_conversation=[Message(message_pieces=[msg_text, msg_video, msg_image])] + ) def test_remix_keeps_video_path_pieces_when_ids_match(self, video_target: OpenAIVideoTarget) -> None: """Test that video_path pieces are preserved after validation so normalizer stores them.""" @@ -1064,7 +1068,7 @@ def test_remix_keeps_video_path_pieces_when_ids_match(self, video_target: OpenAI prompt_metadata={"video_id": "vid_123"}, conversation_id=conversation_id, ) - message = Message([msg_text, msg_video]) + message = Message(message_pieces=[msg_text, msg_video]) OpenAIVideoTarget._validate_video_remix_pieces(message=message) @@ -1090,7 +1094,7 @@ def test_remix_raises_when_video_ids_mismatch(self, video_target: OpenAIVideoTar prompt_metadata={"video_id": "vid_DIFFERENT"}, conversation_id=conversation_id, ) - message = Message([msg_text, msg_video]) + message = Message(message_pieces=[msg_text, msg_video]) with pytest.raises(ValueError, match="video_id mismatch"): OpenAIVideoTarget._validate_video_remix_pieces(message=message) @@ -1111,7 +1115,7 @@ def test_remix_raises_when_text_missing_video_id(self, video_target: OpenAIVideo converted_value_data_type="video_path", conversation_id=conversation_id, ) - message = Message([msg_text, msg_video]) + message = Message(message_pieces=[msg_text, msg_video]) with pytest.raises(ValueError, match="missing.*video_id"): OpenAIVideoTarget._validate_video_remix_pieces(message=message) @@ -1123,7 +1127,7 @@ def test_remix_no_op_without_video_path(self, video_target: OpenAIVideoTarget) - original_value="generate a cat video", converted_value="generate a cat video", ) - message = Message([msg_text]) + message = Message(message_pieces=[msg_text]) OpenAIVideoTarget._validate_video_remix_pieces(message=message) @@ -1146,7 +1150,7 @@ def test_remix_raises_when_video_path_missing_video_id(self, video_target: OpenA converted_value_data_type="video_path", conversation_id=conversation_id, ) - message = Message([msg_text, msg_video]) + message = Message(message_pieces=[msg_text, msg_video]) with pytest.raises(ValueError, match="video_path piece is missing.*video_id"): OpenAIVideoTarget._validate_video_remix_pieces(message=message) @@ -1165,7 +1169,7 @@ async def test_send_prompt_async_raises_when_no_text_piece(patch_central_databas converted_value="/path/image.png", converted_value_data_type="image_path", ) - message = Message([msg]) + message = Message(message_pieces=[msg]) with patch.object(target, "_validate_request"): with pytest.raises(ValueError, match="No text piece found in message"): await target.send_prompt_async(message=message) diff --git a/tests/unit/prompt_target/test_discover_target_capabilities.py b/tests/unit/prompt_target/test_discover_target_capabilities.py index 2da7105999..c62f8537d4 100644 --- a/tests/unit/prompt_target/test_discover_target_capabilities.py +++ b/tests/unit/prompt_target/test_discover_target_capabilities.py @@ -50,7 +50,7 @@ async def _send_prompt_to_target_async(self, *, normalized_conversation: list[Me def _ok_response(*, conversation_id: str = "probe", text: str = "ok") -> list[Message]: return [ Message( - [ + message_pieces=[ MessagePiece( role="assistant", original_value=text, @@ -66,7 +66,7 @@ def _ok_response(*, conversation_id: str = "probe", text: str = "ok") -> list[Me def _error_response(*, conversation_id: str = "probe") -> list[Message]: return [ Message( - [ + message_pieces=[ MessagePiece( role="assistant", original_value="blocked",