Stream, validate, persist, and render constrained UI parts from tools, including the current-time component. Preserve markdown following think blocks as normal answer content.
151 lines
5.0 KiB
Python
151 lines
5.0 KiB
Python
from decimal import Decimal
|
|
|
|
from core.tools.entities.ui_entities import A2UI_CATALOG_ID, MessageUIPart, ToolUIMessage
|
|
from fields.message_fields import ExploreMessageListItem, MessageListItem, WebMessageListItem
|
|
|
|
|
|
def _base_kwargs():
|
|
return {
|
|
"id": "m1",
|
|
"conversation_id": "c1",
|
|
"inputs": {},
|
|
"query": "hi",
|
|
"answer": "answer",
|
|
"retriever_resources": [],
|
|
"agent_thoughts": [],
|
|
"message_files": [],
|
|
"status": "normal",
|
|
"extra_contents": [],
|
|
}
|
|
|
|
|
|
def _ui_part_payload(index: int, *, large: bool = False) -> dict:
|
|
surface_id = f"surface-{index}"
|
|
messages = [
|
|
{
|
|
"version": "v0.9.1",
|
|
"createSurface": {
|
|
"surfaceId": surface_id,
|
|
"catalogId": A2UI_CATALOG_ID,
|
|
},
|
|
},
|
|
]
|
|
if large:
|
|
messages.append(
|
|
{
|
|
"version": "v0.9.1",
|
|
"updateDataModel": {
|
|
"surfaceId": surface_id,
|
|
"value": ["x" * 4096] * 20,
|
|
},
|
|
}
|
|
)
|
|
messages.append(
|
|
{
|
|
"version": "v0.9.1",
|
|
"updateComponents": {
|
|
"surfaceId": surface_id,
|
|
"components": [{"id": "root", "component": "Text", "text": "Weather"}],
|
|
},
|
|
}
|
|
)
|
|
ui_message = ToolUIMessage(messages=messages)
|
|
return MessageUIPart.from_tool_ui_message(
|
|
part_id=f"call-{index}:{surface_id}",
|
|
sequence=1,
|
|
ui_message=ui_message,
|
|
).model_dump(mode="json")
|
|
|
|
|
|
class TestExploreMessageListItem:
|
|
def test_exposes_metadata_for_history_rehydration(self):
|
|
# The Explore/installed-app surface must surface message_metadata (incl. reasoning)
|
|
# so the chat-with-history client can rehydrate the thinking panel on reload.
|
|
item = ExploreMessageListItem(**_base_kwargs(), metadata={"reasoning": {"llm": "thinking..."}})
|
|
|
|
payload = item.model_dump(mode="json")
|
|
|
|
assert payload["metadata"] == {"reasoning": {"llm": "thinking..."}}
|
|
|
|
def test_metadata_defaults_to_none(self):
|
|
item = ExploreMessageListItem(**_base_kwargs())
|
|
assert item.model_dump(mode="json")["metadata"] is None
|
|
|
|
def test_base_message_list_item_has_no_metadata(self):
|
|
# Guard the public service-API contract: the base item must not leak metadata.
|
|
payload = MessageListItem(**_base_kwargs()).model_dump(mode="json")
|
|
assert "metadata" not in payload
|
|
|
|
def test_message_list_item_exposes_usage_fields(self):
|
|
payload = MessageListItem(
|
|
**_base_kwargs(),
|
|
message_tokens=7,
|
|
answer_tokens=11,
|
|
provider_response_latency=1.25,
|
|
total_price=Decimal("0.0001234"),
|
|
currency="USD",
|
|
).model_dump(mode="json")
|
|
|
|
assert payload["message_tokens"] == 7
|
|
assert payload["answer_tokens"] == 11
|
|
assert payload["total_tokens"] == 18
|
|
assert payload["provider_response_latency"] == 1.25
|
|
assert payload["total_price"] == "0.0001234"
|
|
assert payload["currency"] == "USD"
|
|
|
|
def test_web_message_list_item_exposes_usage_and_metadata(self):
|
|
payload = WebMessageListItem(
|
|
**_base_kwargs(),
|
|
metadata={"usage": {"total_tokens": 18}},
|
|
message_tokens=7,
|
|
answer_tokens=11,
|
|
).model_dump(mode="json")
|
|
|
|
assert payload["metadata"] == {"usage": {"total_tokens": 18}}
|
|
assert payload["message_tokens"] == 7
|
|
assert payload["answer_tokens"] == 11
|
|
assert payload["total_tokens"] == 18
|
|
|
|
def test_service_message_exposes_only_valid_bounded_ui_parts(self):
|
|
metadata = {
|
|
"usage": {"total_tokens": 18},
|
|
"ui_parts": [
|
|
_ui_part_payload(0),
|
|
{"part_id": "broken"},
|
|
*[_ui_part_payload(index) for index in range(1, 18)],
|
|
],
|
|
}
|
|
|
|
payload = MessageListItem(
|
|
**_base_kwargs(),
|
|
message_metadata_dict=metadata,
|
|
).model_dump(mode="json")
|
|
|
|
assert "metadata" not in payload
|
|
assert len(payload["ui_parts"]) == 16
|
|
assert payload["ui_parts"][0]["part_id"] == "call-0:surface-0"
|
|
|
|
def test_web_metadata_stays_raw_while_top_level_ui_parts_are_safe(self):
|
|
metadata = {
|
|
"reasoning": {"llm": "thinking..."},
|
|
"ui_parts": [_ui_part_payload(0), {"part_id": "broken"}],
|
|
}
|
|
|
|
payload = WebMessageListItem(
|
|
**_base_kwargs(),
|
|
message_metadata_dict=metadata,
|
|
).model_dump(mode="json")
|
|
|
|
assert payload["metadata"] == metadata
|
|
assert len(payload["ui_parts"]) == 1
|
|
|
|
def test_service_message_ui_parts_respect_cumulative_payload_budget(self):
|
|
metadata = {"ui_parts": [_ui_part_payload(index, large=True) for index in range(7)]}
|
|
|
|
payload = MessageListItem(
|
|
**_base_kwargs(),
|
|
message_metadata_dict=metadata,
|
|
).model_dump(mode="json")
|
|
|
|
assert 0 < len(payload["ui_parts"]) < 7
|