feat: persist photos and enrich conversation history

This commit is contained in:
Xin Wang
2026-08-05 16:14:03 +08:00
parent 545536bdf3
commit 317a0600bc
19 changed files with 1146 additions and 119 deletions

View File

@@ -105,6 +105,54 @@ class ConversationRecorderTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual(saved["sessionId"], "conv_test")
self.assertEqual(saved["sequence"], 1)
async def test_image_is_persisted_as_message_with_artifact(self):
conversation = SimpleNamespace(message_count=0)
class FakeSession:
def __init__(self):
self.added = []
self.committed = False
async def __aenter__(self):
return self
async def __aexit__(self, *_args):
return None
def add(self, value):
self.added.append(value)
async def get(self, _model, _session_id):
return conversation
async def commit(self):
self.committed = True
session = FakeSession()
recorder = ConversationRecorder("conv_test")
with (
patch("services.conversation_history.SessionLocal", return_value=session),
patch("services.conversation_history.put_object") as put_object,
):
await recorder._record_image(
b"jpeg-data",
input_id="input_photo",
timestamp="2026-08-05T10:00:00+08:00",
mime_type="image/jpeg",
)
self.assertTrue(session.committed)
self.assertEqual(len(session.added), 2)
message, artifact = session.added
self.assertEqual(message.content_type, "image")
self.assertEqual(message.role, "user")
self.assertEqual(message.extra["input_id"], "input_photo")
self.assertEqual(artifact.message_id, message.id)
self.assertEqual(artifact.kind, "image")
self.assertEqual(artifact.size_bytes, len(b"jpeg-data"))
self.assertEqual(conversation.message_count, 1)
put_object.assert_called_once()
if __name__ == "__main__":
unittest.main()

View File

@@ -32,7 +32,7 @@ class UserInputParserTests(unittest.TestCase):
self.assertIsNotNone(value)
self.assertEqual(value.text, "帮我看看")
self.assertTrue(value.has_camera_frame)
self.assertEqual(value.transcript_text, "帮我看看\n已发送一张图片")
self.assertEqual(value.transcript_text, "帮我看看")
def test_rejects_legacy_and_unsupported_image_sources(self):
self.assertIsNone(parse_user_input({"type": "user-text", "text": "旧协议"}))
@@ -54,6 +54,25 @@ class UserInputParserTests(unittest.TestCase):
}
)
def test_image_only_input_has_no_synthetic_chat_text(self):
value = parse_user_input(
{
"type": "user-input",
"schema_version": 1,
"input_id": "input_photo",
"parts": [
{
"type": "input_image",
"source": {"type": "camera_frame", "frame": "current"},
}
],
}
)
self.assertIsNotNone(value)
self.assertEqual(value.transcript_text, "")
self.assertEqual(value.prompt_text, "请根据用户刚提交的图片进行回复。")
def test_native_image_uses_the_standard_multimodal_user_turn_path(self):
image = UserImageRawFrame(
image=bytes([220, 40, 40] * 16 * 16),