|
2 | 2 |
|
3 | 3 | # pylint: disable=too-many-lines |
4 | 4 |
|
| 5 | +import base64 |
5 | 6 | import sqlite3 |
6 | 7 | from typing import Any |
7 | 8 |
|
8 | 9 | import psycopg2 |
9 | 10 | import pytest |
10 | 11 | from fastapi import HTTPException |
11 | 12 | from llama_stack_client.types import ModelListResponse |
| 13 | +from pydantic_ai.messages import ImageUrl |
12 | 14 | from pytest_mock import MockerFixture |
13 | 15 | from sqlalchemy.exc import SQLAlchemyError |
14 | 16 |
|
|
27 | 29 | from models.database.conversations import UserConversation, UserTurn |
28 | 30 | from tests.unit import config_dict |
29 | 31 | from utils.query import ( |
| 32 | + build_multimodal_input, |
30 | 33 | consume_query_tokens, |
31 | 34 | extract_provider_and_model_from_model_id, |
32 | 35 | handle_known_apistatus_errors, |
@@ -255,6 +258,96 @@ def test_prepare_input_with_attachments(self) -> None: |
255 | 258 | assert "[Attachment: text]" in result |
256 | 259 | assert "attachment content" in result |
257 | 260 |
|
| 261 | + def test_prepare_input_skips_image_attachments(self) -> None: |
| 262 | + """Test that image attachments are excluded from text input.""" |
| 263 | + text_attachment = Attachment( |
| 264 | + attachment_type="log", |
| 265 | + content="log output", |
| 266 | + content_type="text/plain", |
| 267 | + ) |
| 268 | + image_attachment = Attachment( |
| 269 | + attachment_type="image", |
| 270 | + content=base64.b64encode(b"\xff\xd8\xff\xe0" + b"\x00" * 10).decode(), |
| 271 | + content_type="image/jpeg", |
| 272 | + ) |
| 273 | + query_request = QueryRequest( |
| 274 | + query="describe this image", |
| 275 | + attachments=[text_attachment, image_attachment], |
| 276 | + ) # pyright: ignore[reportCallIssue] |
| 277 | + result = prepare_input(query_request) |
| 278 | + assert "describe this image" in result |
| 279 | + assert "[Attachment: log]" in result |
| 280 | + assert "log output" in result |
| 281 | + assert result.count("[Attachment:") == 1 |
| 282 | + |
| 283 | + def test_prepare_input_only_image_attachments(self) -> None: |
| 284 | + """Test prepare_input with only image attachments returns just the query.""" |
| 285 | + image_attachment = Attachment( |
| 286 | + attachment_type="image", |
| 287 | + content=base64.b64encode(b"\x89PNG" + b"\x00" * 10).decode(), |
| 288 | + content_type="image/png", |
| 289 | + ) |
| 290 | + query_request = QueryRequest( |
| 291 | + query="what is in this image", |
| 292 | + attachments=[image_attachment], |
| 293 | + ) # pyright: ignore[reportCallIssue] |
| 294 | + result = prepare_input(query_request) |
| 295 | + assert result == "what is in this image" |
| 296 | + |
| 297 | + |
| 298 | +class TestBuildMultimodalInput: |
| 299 | + """Tests for build_multimodal_input function.""" |
| 300 | + |
| 301 | + def test_single_image(self) -> None: |
| 302 | + """Test building multimodal input with one image.""" |
| 303 | + image_data = base64.b64encode(b"\xff\xd8\xff\xe0" + b"\x00" * 10).decode() |
| 304 | + image_attachment = Attachment( |
| 305 | + attachment_type="image", |
| 306 | + content=image_data, |
| 307 | + content_type="image/jpeg", |
| 308 | + ) |
| 309 | + result = build_multimodal_input("describe this", [image_attachment]) |
| 310 | + assert len(result) == 2 |
| 311 | + assert result[0] == "describe this" |
| 312 | + assert isinstance(result[1], ImageUrl) |
| 313 | + assert result[1].url == f"data:image/jpeg;base64,{image_data}" |
| 314 | + |
| 315 | + def test_multiple_images(self) -> None: |
| 316 | + """Test building multimodal input with multiple images.""" |
| 317 | + jpeg_data = base64.b64encode(b"\xff\xd8\xff\xe0" + b"\x00" * 10).decode() |
| 318 | + png_data = base64.b64encode(b"\x89PNG" + b"\x00" * 10).decode() |
| 319 | + attachments = [ |
| 320 | + Attachment( |
| 321 | + attachment_type="image", |
| 322 | + content=jpeg_data, |
| 323 | + content_type="image/jpeg", |
| 324 | + ), |
| 325 | + Attachment( |
| 326 | + attachment_type="image", |
| 327 | + content=png_data, |
| 328 | + content_type="image/png", |
| 329 | + ), |
| 330 | + ] |
| 331 | + result = build_multimodal_input("compare these", attachments) |
| 332 | + assert len(result) == 3 |
| 333 | + assert result[0] == "compare these" |
| 334 | + assert isinstance(result[1], ImageUrl) |
| 335 | + assert result[1].url == f"data:image/jpeg;base64,{jpeg_data}" |
| 336 | + assert isinstance(result[2], ImageUrl) |
| 337 | + assert result[2].url == f"data:image/png;base64,{png_data}" |
| 338 | + |
| 339 | + def test_image_url_media_type(self) -> None: |
| 340 | + """Test that ImageUrl has correct media_type set.""" |
| 341 | + image_data = base64.b64encode(b"\x89PNG" + b"\x00" * 10).decode() |
| 342 | + attachment = Attachment( |
| 343 | + attachment_type="image", |
| 344 | + content=image_data, |
| 345 | + content_type="image/png", |
| 346 | + ) |
| 347 | + result = build_multimodal_input("test", [attachment]) |
| 348 | + assert isinstance(result[1], ImageUrl) |
| 349 | + assert result[1].media_type == "image/png" |
| 350 | + |
258 | 351 |
|
259 | 352 | class TestExtractProviderAndModelFromModelId: |
260 | 353 | """Tests for extract_provider_and_model_from_model_id function.""" |
|
0 commit comments