Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyrit/output/conversation/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ async def _build_piece_async(

data: dict[str, Any] = {
"data_type": piece.original_value_data_type,
"original_value_data_type": piece.original_value_data_type,
"converted_value_data_type": piece.converted_value_data_type,
"original_value": piece.original_value,
"converted_value": piece.converted_value,
"response_error": piece.response_error,
Expand Down
11 changes: 8 additions & 3 deletions pyrit/output/scenario_result/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,17 @@
<div class="msg">
<div class="role">{{ m.role }}{% if m.is_simulated %} (simulated){% endif %}</div>
{% for p in m.pieces %}
{% if p.data_type == "reasoning" %}
{% set display_type = (
p.converted_value_data_type
if p.converted_value and p.converted_value_data_type
else (p.original_value_data_type or p.data_type)
) %}
{% if display_type == "reasoning" or p.data_type == "reasoning" %}
<div class="piece reasoning">{{ p.reasoning_summary }}</div>
{% elif p.data_type == "text" %}
{% elif display_type == "text" %}
<div class="piece">{{ p.converted_value or p.original_value }}</div>
{% else %}
<div class="piece media">[{{ p.data_type }}] {{ p.converted_value or p.original_value }}</div>
<div class="piece media">[{{ display_type }}] {{ p.converted_value or p.original_value }}</div>
{% endif %}
{% if p.response_error and p.response_error != "none" %}
<div class="piece err">error: {{ p.response_error }}</div>
Expand Down
22 changes: 22 additions & 0 deletions tests/unit/output/conversation/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,32 @@ async def test_build_async_maps_message_and_piece_fields():
assert structured[0]["role"] == "user"
assert structured[0]["is_simulated"] is False
assert structured[0]["pieces"][0]["data_type"] == "text"
assert structured[0]["pieces"][0]["original_value_data_type"] == "text"
assert structured[0]["pieces"][0]["converted_value_data_type"] == "text"
assert structured[0]["pieces"][0]["original_value"] == "hi there"
assert structured[0]["pieces"][0]["response_error"] == "none"


async def test_build_async_includes_distinct_converted_data_type():
piece = MessagePiece(
role="user",
original_value="create an image of a cat",
original_value_data_type="text",
converted_value="media/cat.png",
converted_value_data_type="image_path",
conversation_id="c1",
sequence=0,
)
structured = await JsonConversationPrinter(source=_StubSource()).build_async([_message(piece)])

p = structured[0]["pieces"][0]
assert p["data_type"] == "text"
assert p["original_value_data_type"] == "text"
assert p["converted_value_data_type"] == "image_path"
assert p["original_value"] == "create an image of a cat"
assert p["converted_value"] == "media/cat.png"


async def test_build_async_omits_scores_when_not_requested():
piece = _piece()
source = _StubSource({str(piece.id): [_score(piece_id=str(piece.id))]})
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/output/scenario_result/test_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,39 @@ async def test_html_report_renders_media_piece_as_placeholder():
assert "[image_path] media/img.png" in html


async def test_html_report_renders_text_to_image_converted_media_with_label():
payload = _payload()
payload["conversations"][0]["messages"][0]["pieces"][0] = {
"data_type": "text",
"original_value_data_type": "text",
"converted_value_data_type": "image_path",
"original_value": "draw a cat",
"converted_value": "media/cat.png",
"response_error": None,
}

html = await HtmlScenarioReportPrinter().render_async(payload)

assert "[image_path] media/cat.png" in html


async def test_html_report_renders_image_to_text_converted_piece_as_plain_text():
payload = _payload()
payload["conversations"][0]["messages"][0]["pieces"][0] = {
"data_type": "image_path",
"original_value_data_type": "image_path",
"converted_value_data_type": "text",
"original_value": "media/receipt.png",
"converted_value": "Total: $50",
"response_error": None,
}

html = await HtmlScenarioReportPrinter().render_async(payload)

assert "Total: $50" in html
assert "[image_path]" not in html


async def test_html_report_renders_every_template_field():
# Kitchen-sink payload: exercises every branch the template reads — reasoning
# pieces, inline scores, response errors, groups — so a renamed/removed field
Expand Down