feat(adapters): GovernedVLMClient — VLMProvider 最小可用实现
- 包装 GovernedLLMClient,注入 base64 图片到 OpenAI Vision API 格式 - 复用 LLM 治理栈全部能力(熔断、缓存、重试、遥测) - 8 项单元测试覆盖协议满足、图片编码、注入逻辑、不可变性
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"""VLM 适配器单元测试。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from adapters.vlm import GovernedVLMClient
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class TestGovernedVLMClientProtocol:
|
||||
def test_has_chat_with_images(self):
|
||||
assert hasattr(GovernedVLMClient, "chat_with_images")
|
||||
|
||||
def test_satisfies_vlm_protocol(self):
|
||||
"""GovernedVLMClient 应满足 VLMProvider Protocol。"""
|
||||
assert hasattr(GovernedVLMClient, "chat_with_images")
|
||||
|
||||
|
||||
class TestImageEncoding:
|
||||
def test_encode_jpeg(self, tmp_path: Path):
|
||||
img = tmp_path / "test.jpg"
|
||||
img.write_bytes(b"\xff\xd8\xff\xe0fake_jpeg_data")
|
||||
result = GovernedVLMClient._encode_image(img)
|
||||
assert result.startswith("data:image/jpeg;base64,")
|
||||
|
||||
def test_encode_png(self, tmp_path: Path):
|
||||
img = tmp_path / "test.png"
|
||||
img.write_bytes(b"\x89PNG\r\n\x1a\nfake_png_data")
|
||||
result = GovernedVLMClient._encode_image(img)
|
||||
assert result.startswith("data:image/png;base64,")
|
||||
|
||||
|
||||
class TestInjectImages:
|
||||
def test_inject_single_image(self, tmp_path: Path):
|
||||
img = tmp_path / "frame.jpg"
|
||||
img.write_bytes(b"\xff\xd8\xff\xe0data")
|
||||
messages = [{"role": "user", "content": "描述这帧画面"}]
|
||||
result = GovernedVLMClient._inject_images(messages, [img])
|
||||
|
||||
assert len(result) == 1
|
||||
content = result[0]["content"]
|
||||
assert isinstance(content, list)
|
||||
assert len(content) == 2 # 1 image + 1 text
|
||||
assert content[0]["type"] == "image_url"
|
||||
assert content[1]["type"] == "text"
|
||||
assert content[1]["text"] == "描述这帧画面"
|
||||
|
||||
def test_inject_does_not_mutate_original(self, tmp_path: Path):
|
||||
img = tmp_path / "frame.jpg"
|
||||
img.write_bytes(b"\xff\xd8\xff\xe0data")
|
||||
messages = [{"role": "user", "content": "text"}]
|
||||
original_content = messages[0]["content"]
|
||||
GovernedVLMClient._inject_images(messages, [img])
|
||||
assert messages[0]["content"] == original_content # 原列表未变
|
||||
|
||||
def test_no_images_passthrough(self):
|
||||
messages = [{"role": "user", "content": "hello"}]
|
||||
result = GovernedVLMClient._inject_images(messages, [])
|
||||
assert result[0]["content"] == "hello"
|
||||
|
||||
def test_multiple_images(self, tmp_path: Path):
|
||||
imgs = []
|
||||
for i in range(3):
|
||||
img = tmp_path / f"frame_{i}.jpg"
|
||||
img.write_bytes(b"\xff\xd8\xff\xe0data")
|
||||
imgs.append(img)
|
||||
messages = [{"role": "user", "content": "描述"}]
|
||||
result = GovernedVLMClient._inject_images(messages, imgs)
|
||||
content = result[0]["content"]
|
||||
assert len(content) == 4 # 3 images + 1 text
|
||||
Reference in New Issue
Block a user