feat(ai): enforce direct image edit contract

This commit is contained in:
chengma
2026-07-20 17:52:59 +08:00
parent 61db0f4ed6
commit 2a2cbad7bd
11 changed files with 382 additions and 94 deletions
+47 -5
View File
@@ -69,10 +69,10 @@ class AITests(TempDirMixin, unittest.TestCase):
"name": "Image",
"category": "image",
"enabled": True,
"url": "https://example.invalid/v1/chat/completions",
"url": "https://example.invalid/v1",
"model": "image-model",
"api_key": "sk-image-secret",
"api_type": "auto",
"api_type": "images_edits",
"connect_timeout_seconds": 1,
"timeout_seconds": 1,
"extra_body": {},
@@ -378,9 +378,13 @@ class AITests(TempDirMixin, unittest.TestCase):
b64_image = base64.b64encode(generated.getvalue()).decode("ascii")
def fake_urlopen(request, timeout=None):
body = json.loads(request.data.decode("utf-8"))
self.assertEqual("image-model", body["model"])
self.assertIn("目标分辨率:512", body["messages"][0]["content"][0]["text"])
body = request.data
self.assertIn(b'name="model"', body)
self.assertIn(b"image-model", body)
self.assertIn(b'name="image[]"', body)
self.assertIn(b'name="n"', body)
self.assertIn(b"\r\n1\r\n", body)
self.assertIn("multipart/form-data", request.headers["Content-type"])
return _Response({"data": [{"b64_json": b64_image}]})
with mock.patch("app.ai.urllib.request.urlopen", side_effect=fake_urlopen):
@@ -399,6 +403,44 @@ class AITests(TempDirMixin, unittest.TestCase):
self.assert_removed(temp_dir)
def test_image_edit_body_keeps_repeated_image_fields_in_order(self):
with self.make_temp_dir() as temp_dir:
first = os.path.join(temp_dir, "primary.jpg")
second = os.path.join(temp_dir, "reference.png")
with open(first, "wb") as fh:
fh.write(b"first-image")
with open(second, "wb") as fh:
fh.write(b"second-image")
body, content_type = ai._image_edit_body(
{"model": "image-model", "extra_body": {"quality": "high"}},
"生成商品图",
[first, second],
"1k",
)
self.assertIn("multipart/form-data", content_type)
self.assertEqual(2, body.count(b'name="image[]"'))
self.assertLess(body.index(b"primary.jpg"), body.index(b"reference.png"))
self.assertIn(b'name="n"', body)
self.assertIn(b"\r\n1\r\n", body)
self.assert_removed(temp_dir)
def test_direct_image_response_only_accepts_openai_data_fields(self):
encoded = base64.b64encode(b"image-bytes").decode("ascii")
self.assertEqual(encoded, ai._find_image_ref({"data": [{"b64_json": encoded}]}))
self.assertEqual(
"https://images.example.com/new.png",
ai._find_image_ref({"data": [{"url": "https://images.example.com/new.png"}]}),
)
self.assertIsNone(ai._find_image_ref({"choices": [{"image_url": "x"}]}))
with self.assertRaisesRegex(ai.AIError, "OpenAI 图片编辑接口"):
ai._extract_image_bytes({"message": {"url": "https://bad.example/x"}}, {}, {})
with self.assertRaisesRegex(ai.AIError, "只允许 http/https"):
ai._extract_image_bytes({"data": [{"url": "file:///tmp/image.png"}]}, {}, {})
def test_cmhub_gen_title_uses_alias_and_emits_metadata(self):
with self.make_temp_dir() as temp_dir:
cfg, key_path = self._cmhub_config(temp_dir)