Spaces:
Sleeping
Sleeping
File size: 6,442 Bytes
756cdab | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | """Sprint S4.8 โ couverture des 4 adapters VLM.
Avant S4 : ``adapters/vlm/{anthropic,mistral,ollama,openai}_vlm.py``
ร 0% direct (testรฉs transitivement).
Cible : 80%+ โ vรฉrifie le contrat MRO + ``input_types`` /
``output_types`` + ``name`` propre ร chaque adapter, sans appeler
les SDK rรฉels (qui exigeraient des clรฉs API et du rรฉseau).
"""
from __future__ import annotations
import pytest
from picarones.domain.artifacts import ArtifactType
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Liste des adapters ร tester avec leur identifiant attendu
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
_VLM_CASES = [
("anthropic_vlm", "picarones.adapters.vlm.anthropic_vlm",
"AnthropicVLMAdapter"),
("mistral_vlm", "picarones.adapters.vlm.mistral_vlm",
"MistralVLMAdapter"),
("ollama_vlm", "picarones.adapters.vlm.ollama_vlm",
"OllamaVLMAdapter"),
("openai_vlm", "picarones.adapters.vlm.openai_vlm",
"OpenAIVLMAdapter"),
]
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 1. Contrat de base : input/output types, name, MRO
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
@pytest.mark.parametrize(
"expected_name,module_path,class_name", _VLM_CASES,
)
class TestVLMAdapterContract:
def test_input_types_is_image(
self, expected_name: str, module_path: str, class_name: str,
) -> None:
import importlib
module = importlib.import_module(module_path)
adapter_cls = getattr(module, class_name)
adapter = adapter_cls(model="any-model", config={})
assert ArtifactType.IMAGE in adapter.input_types
def test_output_types_is_raw_text(
self, expected_name: str, module_path: str, class_name: str,
) -> None:
import importlib
module = importlib.import_module(module_path)
adapter_cls = getattr(module, class_name)
adapter = adapter_cls(model="any-model", config={})
assert ArtifactType.RAW_TEXT in adapter.output_types
def test_name_is_distinct_per_adapter(
self, expected_name: str, module_path: str, class_name: str,
) -> None:
import importlib
module = importlib.import_module(module_path)
adapter_cls = getattr(module, class_name)
adapter = adapter_cls(model="any-model", config={})
assert adapter.name == expected_name
def test_mro_baseVLMAdapter_first(
self, expected_name: str, module_path: str, class_name: str,
) -> None:
"""Le garde-fou ``__init_subclass__`` exige
``BaseVLMAdapter`` AVANT le LLM sibling dans le MRO. On
vรฉrifie qu'une instance correctement dรฉfinie a bien
``BaseVLMAdapter`` parmi ses ancรชtres et que ``input_types``
vient bien de lui (et pas du LLM)."""
import importlib
from picarones.adapters.vlm.base import BaseVLMAdapter
module = importlib.import_module(module_path)
adapter_cls = getattr(module, class_name)
assert issubclass(adapter_cls, BaseVLMAdapter)
# MRO : BaseVLMAdapter doit venir avant BaseLLMAdapter
# (ร travers la chaรฎne d'hรฉritage, on vรฉrifie indirectement
# que ``input_types`` est l'IMAGE ; dรฉjร testรฉ plus haut).
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 2. Transcription prompt configurable
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestTranscriptionPromptConfigurable:
def test_custom_prompt_via_config(self) -> None:
from picarones.adapters.vlm.openai_vlm import OpenAIVLMAdapter
adapter = OpenAIVLMAdapter(
model="gpt-4o",
config={"transcription_prompt": "Custom prompt for testing."},
)
# Doit pouvoir instancier sans erreur ; le prompt est consommรฉ
# par ``execute``.
assert adapter.name == "openai_vlm"
def test_default_prompt_used_when_none_provided(self) -> None:
from picarones.adapters.vlm.openai_vlm import OpenAIVLMAdapter
adapter = OpenAIVLMAdapter(model="gpt-4o", config={})
# Pas de plantage ร l'init โ le dรฉfaut est utilisรฉ.
assert adapter is not None
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# 3. MRO guard โ ordre incorrect โ TypeError
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TestMROGuardRaisesOnSwap:
"""Le garde-fou ``__init_subclass__`` doit lever ``TypeError``
quand on dรฉclare le LLM sibling AVANT ``BaseVLMAdapter``.
Reproduction du bug que le garde protรจge : si l'ordre est
inversรฉ, ``input_types`` viendrait du LLM (= RAW_TEXT) au
lieu de IMAGE, et le pipeline silencieusement passerait du
texte au VLM."""
def test_swapped_parents_raises_typeerror(self) -> None:
from picarones.adapters.llm.openai_adapter import OpenAIAdapter
from picarones.adapters.vlm.base import BaseVLMAdapter
with pytest.raises(TypeError):
# Ordre INVERSE โ BaseVLMAdapter en deuxiรจme.
class _BadVLM(OpenAIAdapter, BaseVLMAdapter): # type: ignore[misc]
@property
def name(self) -> str:
return "bad"
|