refactor(tests): encapsulate OCR fixture providers

This commit is contained in:
Yujong Lee 2026-09-01 13:40:08 -07:00
parent e50a3a1859
commit 4aa2c73541
45 changed files with 781 additions and 624 deletions

View file

@ -2,10 +2,10 @@ from __future__ import annotations
import argparse
import logging
from collections.abc import Callable, Sequence
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import Final, Generic, TypeVar, cast
from typing import Final, Generic, Protocol, TypeVar, cast
from hypothesis.strategies import SearchStrategy
from pydantic import BaseModel
@ -34,6 +34,26 @@ class FixtureTarget(Generic[InputT]):
required_inputs: tuple[InputT, ...] = ()
class FixtureSdkCall(Protocol):
def __call__(self, **kwargs: object) -> object: ...
class FixtureProvider(Protocol[InputT]):
def targets(
self,
environ: Mapping[str, str],
sdk_call: FixtureSdkCall,
) -> tuple[FixtureTarget[InputT], ...]: ...
def discover_fixture_targets(
providers: tuple[FixtureProvider[InputT], ...],
environ: Mapping[str, str],
sdk_call: FixtureSdkCall,
) -> tuple[FixtureTarget[InputT], ...]:
return tuple(target for provider in providers for target in provider.targets(environ, sdk_call))
def generate_target_fixtures(
target: FixtureTarget[InputT],
root: Path,

View file

@ -0,0 +1,72 @@
from __future__ import annotations
import queue
from collections.abc import Mapping
from dataclasses import dataclass
from typing import Final
from hypothesis import strategies as st
from tests.route_parity.fixture_generator import FixtureSdkCall, FixtureTarget, discover_fixture_targets
from tests.route_parity.fixture_models import SdkInputBase
from tests.route_parity.fixture_recorder import ProviderSpec
class ExampleSdkInput(SdkInputBase):
model: str
@dataclass(frozen=True, slots=True)
class ExampleProvider:
name: str
key_name: str
def targets(
self,
environ: Mapping[str, str],
sdk_call: FixtureSdkCall,
) -> tuple[FixtureTarget[ExampleSdkInput], ...]:
api_key: Final = environ.get(self.key_name)
if not api_key:
return ()
def invoke(api_base: str, case_input: ExampleSdkInput) -> object:
return sdk_call(api_base=api_base, api_key=api_key, **case_input.as_sdk_kwargs())
case_input: Final = ExampleSdkInput(model=f"{self.name}/model")
return (
FixtureTarget(
name=self.name,
provider_spec=ProviderSpec(upstream_base=f"https://{self.name}.example"),
strategy=st.just(case_input),
invoke=invoke,
required_inputs=(case_input,),
),
)
def test_discover_fixture_targets_flattens_configured_providers_and_injects_sdk_call() -> None:
calls: Final[queue.SimpleQueue[dict[str, object]]] = queue.SimpleQueue()
def sdk_call(**kwargs: object) -> object:
calls.put(kwargs)
return "response"
providers: Final = (
ExampleProvider(name="first", key_name="FIRST_KEY"),
ExampleProvider(name="skipped", key_name="SKIPPED_KEY"),
ExampleProvider(name="second", key_name="SECOND_KEY"),
)
targets: Final = discover_fixture_targets(
providers,
{"FIRST_KEY": "first-secret", "SECOND_KEY": "second-secret"},
sdk_call,
)
assert tuple(target.name for target in targets) == ("first", "second")
assert targets[1].invoke("http://127.0.0.1:1234", targets[1].required_inputs[0]) == "response"
assert calls.get_nowait() == {
"api_base": "http://127.0.0.1:1234",
"api_key": "second-secret",
"model": "second/model",
}

View file

@ -6,7 +6,7 @@ from typing import Final
import pytest
from tests.route_parity.fixture_recorder import fixture_id, parametrize_recorded_fixtures
from tests.test_litellm.ocr.fixture_models import OcrParityCase
from tests.test_litellm.ocr.fixtures.models import OcrParityCase
FIXTURE_DIR_ENV: Final = "LITELLM_OCR_FIXTURE_DIR"
@ -19,7 +19,7 @@ def _fixture_id(fixture: OcrParityCase) -> str:
def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
default_directory: Final = Path(__file__).with_name("fixtures")
default_directory: Final = Path(__file__).with_name("fixtures") / "data"
parametrize_recorded_fixtures(
metafunc,
fixture_name="ocr_fixture",
@ -27,7 +27,7 @@ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
env_var=FIXTURE_DIR_ENV,
default_directory=default_directory,
regeneration_command=(
f"uv run python -m tests.test_litellm.ocr.generate_fixtures --fixture-dir {default_directory}"
f"uv run python -m tests.test_litellm.ocr.fixtures.generate --fixture-dir {default_directory}"
),
id_builder=_fixture_id,
)

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,115 @@
from __future__ import annotations
from collections.abc import Mapping
from typing import Final, cast
from hypothesis import strategies as st
from hypothesis.strategies import DrawFn, SearchStrategy
from tests.route_parity.fixture_generator import FixtureSdkCall
from tests.route_parity.fixture_recorder import ProviderSpec
from tests.test_litellm.ocr.fixtures.common import (
OcrFixtureTarget,
invoke_with_api_key,
pdf_document,
public_document_strategy,
)
from tests.test_litellm.ocr.fixtures.mistral import (
MISTRAL_MODEL,
mistral_input_strategy,
required_mistral_inputs,
)
from tests.test_litellm.ocr.fixtures.models import (
AzureDocumentIntelligenceOcrSdkInput,
AzureMistralOcrSdkInput,
MistralOcrSdkInput,
OcrSdkInputBase,
)
def _as_azure_mistral(case_input: MistralOcrSdkInput, model: str) -> AzureMistralOcrSdkInput:
values: Final = case_input.model_dump(mode="python", exclude={"boundary", "model", "custom_llm_provider"})
return AzureMistralOcrSdkInput.model_validate({**values, "model": model})
def _required_document_intelligence_inputs() -> tuple[AzureDocumentIntelligenceOcrSdkInput, ...]:
document: Final = pdf_document()
model: Final = "azure_ai/doc-intelligence/prebuilt-layout"
return (
AzureDocumentIntelligenceOcrSdkInput(model=model, document=document),
AzureDocumentIntelligenceOcrSdkInput(model=model, document=document, pages=[0, 1]),
AzureDocumentIntelligenceOcrSdkInput(model=model, document=document, features=["languages"]),
AzureDocumentIntelligenceOcrSdkInput(model=model, document=document, req_format="litellm"),
)
@st.composite
def azure_document_intelligence_input_strategy(draw: DrawFn) -> AzureDocumentIntelligenceOcrSdkInput:
optional_params: Final = draw(
st.fixed_dictionaries(
{},
optional={
"pages": st.sampled_from(([0], [0, 1], "1-2")),
"features": st.sampled_from((["languages"], ["keyValuePairs"], "languages,keyValuePairs")),
},
)
)
return AzureDocumentIntelligenceOcrSdkInput.model_validate(
{
"model": draw(
st.sampled_from(
(
"azure_ai/doc-intelligence/prebuilt-read",
"azure_ai/doc-intelligence/prebuilt-layout",
"azure_ai/doc-intelligence/prebuilt-document",
)
)
),
"document": draw(public_document_strategy()),
**optional_params,
}
)
class AzureMistralFixtureProvider:
def targets(self, environ: Mapping[str, str], sdk_call: FixtureSdkCall) -> tuple[OcrFixtureTarget, ...]:
api_key: Final = environ.get("AZURE_AI_API_KEY")
upstream_base: Final = environ.get("AZURE_AI_API_BASE")
configured_model: Final = environ.get("AZURE_AI_OCR_MODEL")
if not api_key or not upstream_base or not configured_model:
return ()
model: Final = configured_model if configured_model.startswith("azure_ai/") else f"azure_ai/{configured_model}"
return (
OcrFixtureTarget(
name="azure-mistral",
provider_spec=ProviderSpec(upstream_base=upstream_base.rstrip("/")),
strategy=cast(
SearchStrategy[OcrSdkInputBase],
mistral_input_strategy(MISTRAL_MODEL).map(lambda case_input: _as_azure_mistral(case_input, model)),
),
invoke=invoke_with_api_key(sdk_call, api_key),
required_inputs=cast(
tuple[OcrSdkInputBase, ...],
tuple(
_as_azure_mistral(case_input, model) for case_input in required_mistral_inputs(MISTRAL_MODEL)
),
),
),
)
class AzureDocumentIntelligenceFixtureProvider:
def targets(self, environ: Mapping[str, str], sdk_call: FixtureSdkCall) -> tuple[OcrFixtureTarget, ...]:
api_key: Final = environ.get("AZURE_DOCUMENT_INTELLIGENCE_API_KEY")
upstream_base: Final = environ.get("AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT")
if not api_key or not upstream_base:
return ()
return (
OcrFixtureTarget(
name="azure-document-intelligence",
provider_spec=ProviderSpec(upstream_base=upstream_base.rstrip("/")),
strategy=cast(SearchStrategy[OcrSdkInputBase], azure_document_intelligence_input_strategy()),
invoke=invoke_with_api_key(sdk_call, api_key),
required_inputs=cast(tuple[OcrSdkInputBase, ...], _required_document_intelligence_inputs()),
),
)

View file

@ -0,0 +1,64 @@
from __future__ import annotations
import base64
from collections.abc import Callable
from pathlib import Path
from typing import Final
from urllib.parse import quote
from hypothesis import strategies as st
from hypothesis.strategies import SearchStrategy
from tests.route_parity.fixture_generator import FixtureSdkCall, FixtureTarget
from tests.test_litellm.ocr.fixtures.models import (
JsonSchemaDefinition,
JsonSchemaResponseFormat,
MistralDocumentUrlDocument,
MistralImageUrlDocument,
OcrSdkInputBase,
)
OcrFixtureTarget = FixtureTarget[OcrSdkInputBase]
def image_document(text: str, font_size: int) -> MistralImageUrlDocument:
url: Final = f"https://dummyjson.com/image/800x300/ffffff/000000?text={quote(text)}&fontSize={font_size}"
return MistralImageUrlDocument(type="image_url", image_url=url)
def fixture_pdf_data_uri() -> str:
fixture: Final = Path(__file__).resolve().parents[3] / "llm_translation" / "fixtures" / "dummy.pdf"
encoded: Final = base64.b64encode(fixture.read_bytes()).decode("ascii")
return f"data:application/pdf;base64,{encoded}"
def pdf_document() -> MistralDocumentUrlDocument:
return MistralDocumentUrlDocument(type="document_url", document_url=fixture_pdf_data_uri())
def public_document_strategy() -> SearchStrategy[MistralImageUrlDocument | MistralDocumentUrlDocument]:
return st.sampled_from((image_document("invoice 123", 24), pdf_document()))
def annotation_format(name: str) -> JsonSchemaResponseFormat:
return JsonSchemaResponseFormat(
type="json_schema",
json_schema=JsonSchemaDefinition(
name=name,
description="Extract the visible document fields",
schema={
"type": "object",
"properties": {"title": {"type": "string"}},
"required": ["title"],
"additionalProperties": False,
},
strict=True,
),
)
def invoke_with_api_key(sdk_call: FixtureSdkCall, api_key: str) -> Callable[[str, OcrSdkInputBase], object]:
def invoke(api_base: str, case_input: OcrSdkInputBase) -> object:
return sdk_call(api_base=api_base, api_key=api_key, **case_input.as_sdk_kwargs())
return invoke

View file

@ -0,0 +1,92 @@
from __future__ import annotations
import logging
import os
from collections.abc import Mapping
from pathlib import Path
from typing import Final, cast
from dotenv import load_dotenv
import litellm
from litellm.rust_bridge.ocr import use_litellm_rust
from tests.route_parity.fixture_generator import (
FixtureProvider,
FixtureSdkCall,
discover_fixture_targets,
generate_target_fixtures,
parse_generator_args,
)
from tests.route_parity.fixture_generator import require_targets as require_fixture_targets
from tests.route_parity.fixture_recorder import fixture_directory
from tests.test_litellm.ocr.fixtures.azure import (
AzureDocumentIntelligenceFixtureProvider,
AzureMistralFixtureProvider,
azure_document_intelligence_input_strategy,
)
from tests.test_litellm.ocr.fixtures.common import OcrFixtureTarget
from tests.test_litellm.ocr.fixtures.mistral import (
MistralFixtureProvider,
mistral_input_strategy,
)
from tests.test_litellm.ocr.fixtures.models import OcrParityCase, OcrSdkInputBase
from tests.test_litellm.ocr.fixtures.reducto import (
ReductoFixtureProvider,
reducto_legacy_input_strategy,
reducto_v3_input_strategy,
)
from tests.test_litellm.ocr.fixtures.vertex import (
VertexFixtureProvider,
vertex_deepseek_input_strategy,
)
__all__ = (
"azure_document_intelligence_input_strategy",
"mistral_input_strategy",
"reducto_legacy_input_strategy",
"reducto_v3_input_strategy",
"vertex_deepseek_input_strategy",
)
FIXTURE_DIR_ENV: Final = "LITELLM_OCR_FIXTURE_DIR"
OCR_FIXTURE_PROVIDERS: Final[tuple[FixtureProvider[OcrSdkInputBase], ...]] = (
MistralFixtureProvider(),
AzureMistralFixtureProvider(),
AzureDocumentIntelligenceFixtureProvider(),
VertexFixtureProvider(),
ReductoFixtureProvider(),
)
def discover_targets(
environ: Mapping[str, str],
sdk_call: FixtureSdkCall,
) -> tuple[OcrFixtureTarget, ...]:
return discover_fixture_targets(OCR_FIXTURE_PROVIDERS, environ, sdk_call)
def require_targets(targets: tuple[OcrFixtureTarget, ...]) -> tuple[OcrFixtureTarget, ...]:
return require_fixture_targets(
targets,
"No OCR fixture providers are configured. Set a supported provider API key and endpoint",
)
def main() -> None:
logging.basicConfig(level=logging.INFO, format="%(message)s")
load_dotenv()
args: Final = parse_generator_args()
sdk_call: Final = cast(FixtureSdkCall, litellm.ocr)
targets: Final = require_targets(discover_targets(os.environ, sdk_call))
root: Final = fixture_directory(
args.fixture_dir,
os.environ.get(FIXTURE_DIR_ENV),
Path(__file__).with_name("data"),
)
use_litellm_rust(False, ocr=None, aocr=None)
for target in targets:
generate_target_fixtures(target, root, args.examples, args.concurrency, OcrParityCase)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,98 @@
from __future__ import annotations
from collections.abc import Mapping
from typing import Final, cast
from hypothesis import strategies as st
from hypothesis.strategies import DrawFn, SearchStrategy
from tests.route_parity.fixture_generator import FixtureSdkCall
from tests.route_parity.fixture_recorder import ProviderSpec
from tests.test_litellm.ocr.fixtures.common import (
OcrFixtureTarget,
annotation_format,
image_document,
invoke_with_api_key,
public_document_strategy,
)
from tests.test_litellm.ocr.fixtures.models import MistralOcrSdkInput, OcrSdkInputBase
MISTRAL_MODEL: Final = "mistral/mistral-ocr-latest"
_VALUE_TEXT: Final = st.just("case-1")
@st.composite
def mistral_input_strategy(draw: DrawFn, model: str) -> MistralOcrSdkInput:
document: Final = draw(public_document_strategy())
annotation: Final = draw(
st.sampled_from(
(
{},
{"document_annotation_format": annotation_format("document_title")},
{
"document_annotation_format": annotation_format("prompted_document_title"),
"document_annotation_prompt": "Extract the visible title",
},
)
)
)
optional_params: Final = draw(
st.fixed_dictionaries(
{},
optional={
"pages": st.just([0]),
"include_image_base64": st.booleans(),
"image_limit": st.just(1),
"image_min_size": st.just(300),
"bbox_annotation_format": st.just(annotation_format("bounding_boxes")),
"extract_header": st.booleans(),
"extract_footer": st.booleans(),
"table_format": st.just("markdown"),
"confidence_scores_granularity": st.just("page"),
"include_blocks": st.booleans(),
"id": _VALUE_TEXT,
},
)
)
return MistralOcrSdkInput.model_validate({"model": model, "document": document, **annotation, **optional_params})
def required_mistral_inputs(model: str) -> tuple[MistralOcrSdkInput, ...]:
document: Final = image_document("invoice 123", 24)
annotation: Final = annotation_format("document_title")
bbox_annotation: Final = annotation_format("bounding_boxes")
cases: Final[tuple[dict[str, object], ...]] = (
{},
{"pages": [0]},
{"include_image_base64": True},
{"image_limit": 1},
{"image_min_size": 300},
{"bbox_annotation_format": bbox_annotation},
{"document_annotation_format": annotation},
{"document_annotation_format": annotation, "document_annotation_prompt": "Extract the visible title"},
{"extract_header": True},
{"extract_footer": True},
{"table_format": "markdown"},
{"confidence_scores_granularity": "page"},
{"include_blocks": False},
{"id": "case-1"},
)
return tuple(MistralOcrSdkInput.model_validate({"model": model, "document": document, **case}) for case in cases)
class MistralFixtureProvider:
def targets(self, environ: Mapping[str, str], sdk_call: FixtureSdkCall) -> tuple[OcrFixtureTarget, ...]:
api_key: Final = environ.get("MISTRAL_API_KEY")
if not api_key:
return ()
configured: Final = environ.get("MISTRAL_API_BASE", "https://api.mistral.ai").rstrip("/")
upstream_base: Final = configured.removesuffix("/v1")
return (
OcrFixtureTarget(
name="mistral-ocr",
provider_spec=ProviderSpec(upstream_base=upstream_base),
strategy=cast(SearchStrategy[OcrSdkInputBase], mistral_input_strategy(MISTRAL_MODEL)),
invoke=invoke_with_api_key(sdk_call, api_key),
required_inputs=cast(tuple[OcrSdkInputBase, ...], required_mistral_inputs(MISTRAL_MODEL)),
),
)

View file

@ -0,0 +1,187 @@
from __future__ import annotations
from collections.abc import Mapping
from typing import Final, cast
from hypothesis import strategies as st
from hypothesis.strategies import DrawFn, SearchStrategy
from tests.route_parity.fixture_generator import FixtureSdkCall
from tests.route_parity.fixture_recorder import ProviderSpec
from tests.test_litellm.ocr.fixtures.common import (
OcrFixtureTarget,
fixture_pdf_data_uri,
invoke_with_api_key,
)
from tests.test_litellm.ocr.fixtures.models import (
OcrSdkInputBase,
ReductoChunking,
ReductoDocumentUrlDocument,
ReductoFormatting,
ReductoParseLegacySdkInput,
ReductoParseV3SdkInput,
ReductoRetrieval,
ReductoSettings,
)
_REDUCTO_API_BASE: Final = "https://platform.reducto.ai"
def _formatting_strategy() -> SearchStrategy[ReductoFormatting]:
return st.builds(
ReductoFormatting,
add_page_markers=st.booleans(),
table_output_format=st.sampled_from(("html", "json", "md", "jsonbbox", "dynamic", "csv")),
merge_tables=st.booleans(),
include=st.sampled_from(
(
[],
["hyperlinks"],
["change_tracking", "highlight", "comments"],
["signatures", "ignore_watermarks"],
)
),
)
def _chunking_strategy() -> SearchStrategy[ReductoChunking]:
return st.one_of(
st.builds(
ReductoChunking,
chunk_mode=st.sampled_from(("section", "page", "disabled", "block", "page_sections")),
chunk_size=st.just(None),
chunk_overlap=st.just(0),
),
st.builds(
ReductoChunking,
chunk_mode=st.just("variable"),
chunk_size=st.sampled_from((250, 1000, 1500)),
chunk_overlap=st.sampled_from((0, 32, 128)),
),
)
def _retrieval_strategy() -> SearchStrategy[ReductoRetrieval]:
return st.builds(
ReductoRetrieval,
chunking=_chunking_strategy(),
filter_blocks=st.sampled_from(
(
[],
["Header"],
["Header", "Footer", "Page Number"],
["Figure", "Table", "Key Value"],
)
),
embedding_optimized=st.booleans(),
)
def _settings_strategy() -> SearchStrategy[ReductoSettings]:
return st.builds(
ReductoSettings,
ocr_system=st.sampled_from(("standard", "legacy")),
extraction_mode=st.sampled_from(("ocr", "hybrid")),
force_url_result=st.just(False),
return_ocr_data=st.booleans(),
return_images=st.sampled_from(([], ["figure"], ["table"], ["page"], ["figure", "table", "page"])),
embed_pdf_metadata=st.booleans(),
embed_pdf_metadata_dpi=st.sampled_from((50, 100, 250)),
persist_results=st.just(False),
timeout=st.sampled_from((None, 300.0, 900.0)),
page_range=st.sampled_from((None, [1], [1, 2], ["Sheet1"])),
)
@st.composite
def reducto_v3_input_strategy(
draw: DrawFn, document: ReductoDocumentUrlDocument | None = None
) -> ReductoParseV3SdkInput:
model, custom_llm_provider = draw(st.sampled_from((("reducto/parse-v3", None), ("parse-v3", "reducto"))))
options: Final = draw(
st.fixed_dictionaries(
{},
optional={
"formatting": _formatting_strategy(),
"retrieval": _retrieval_strategy(),
"settings": _settings_strategy(),
},
)
)
return ReductoParseV3SdkInput.model_validate(
{
"model": model,
"custom_llm_provider": custom_llm_provider,
"document": document
or ReductoDocumentUrlDocument(type="document_url", document_url="reducto://fixture-document.pdf"),
**options,
}
)
def reducto_legacy_input_strategy(
document: ReductoDocumentUrlDocument | None = None,
) -> SearchStrategy[ReductoParseLegacySdkInput]:
selected_document: Final = document or ReductoDocumentUrlDocument(
type="document_url", document_url="reducto://fixture-document.pdf"
)
return st.sampled_from(
(
ReductoParseLegacySdkInput(model="reducto/parse-legacy", document=selected_document),
ReductoParseLegacySdkInput(model="parse-legacy", custom_llm_provider="reducto", document=selected_document),
ReductoParseLegacySdkInput(model="reducto/parse-legacy", document=selected_document, enhance={}),
)
)
def _required_v3_inputs(document: ReductoDocumentUrlDocument) -> tuple[ReductoParseV3SdkInput, ...]:
return (
ReductoParseV3SdkInput(model="reducto/parse-v3", document=document),
ReductoParseV3SdkInput(
model="reducto/parse-v3",
document=document,
formatting=ReductoFormatting(table_output_format="md"),
),
ReductoParseV3SdkInput(
model="reducto/parse-v3",
document=document,
retrieval=ReductoRetrieval(chunking=ReductoChunking(chunk_mode="page")),
),
ReductoParseV3SdkInput(
model="reducto/parse-v3",
document=document,
settings=ReductoSettings(return_ocr_data=True),
),
)
class ReductoFixtureProvider:
def targets(self, environ: Mapping[str, str], sdk_call: FixtureSdkCall) -> tuple[OcrFixtureTarget, ...]:
api_key: Final = environ.get("REDUCTO_API_KEY")
if not api_key:
return ()
upstream_base: Final = environ.get("REDUCTO_API_BASE", _REDUCTO_API_BASE).rstrip("/")
document: Final = ReductoDocumentUrlDocument(type="document_url", document_url=fixture_pdf_data_uri())
invoke: Final = invoke_with_api_key(sdk_call, api_key)
return (
OcrFixtureTarget(
name="reducto-v3",
provider_spec=ProviderSpec(upstream_base=upstream_base),
strategy=cast(SearchStrategy[OcrSdkInputBase], reducto_v3_input_strategy(document)),
invoke=invoke,
required_inputs=cast(tuple[OcrSdkInputBase, ...], _required_v3_inputs(document)),
),
OcrFixtureTarget(
name="reducto-legacy",
provider_spec=ProviderSpec(upstream_base=upstream_base),
strategy=cast(SearchStrategy[OcrSdkInputBase], reducto_legacy_input_strategy(document)),
invoke=invoke,
required_inputs=cast(
tuple[OcrSdkInputBase, ...],
(
ReductoParseLegacySdkInput(model="reducto/parse-legacy", document=document),
ReductoParseLegacySdkInput(model="reducto/parse-legacy", document=document, enhance={}),
),
),
),
)

View file

@ -0,0 +1,113 @@
from __future__ import annotations
from collections.abc import Mapping
from typing import Final, cast
from hypothesis import strategies as st
from hypothesis.strategies import DrawFn, SearchStrategy
from tests.route_parity.fixture_generator import FixtureSdkCall
from tests.route_parity.fixture_recorder import ProviderSpec
from tests.test_litellm.ocr.fixtures.common import (
OcrFixtureTarget,
image_document,
invoke_with_api_key,
public_document_strategy,
)
from tests.test_litellm.ocr.fixtures.mistral import (
MISTRAL_MODEL,
mistral_input_strategy,
required_mistral_inputs,
)
from tests.test_litellm.ocr.fixtures.models import (
MistralOcrSdkInput,
OcrSdkInputBase,
VertexDeepSeekOcrSdkInput,
VertexMistralOcrSdkInput,
)
def _as_vertex_mistral(case_input: MistralOcrSdkInput, project: str, location: str) -> VertexMistralOcrSdkInput:
values: Final = case_input.model_dump(mode="python", exclude={"boundary", "model", "custom_llm_provider"})
return VertexMistralOcrSdkInput.model_validate({**values, "vertex_project": project, "vertex_location": location})
def _required_deepseek_inputs(project: str, location: str) -> tuple[VertexDeepSeekOcrSdkInput, ...]:
document: Final = image_document("invoice 123", 24)
common: Final = {"document": document, "vertex_project": project, "vertex_location": location}
cases: Final[tuple[dict[str, object], ...]] = (
{},
{"stream": False},
{"temperature": 0.5},
{"max_tokens": 256},
{"top_p": 0.9},
{"n": 1},
{"stop": ["END", "STOP"]},
)
return tuple(VertexDeepSeekOcrSdkInput.model_validate({**common, **case}) for case in cases)
@st.composite
def vertex_deepseek_input_strategy(draw: DrawFn, project: str, location: str) -> VertexDeepSeekOcrSdkInput:
optional_params: Final = draw(
st.fixed_dictionaries(
{},
optional={
"stream": st.just(False),
"temperature": st.sampled_from((0.0, 0.5, 1.0)),
"max_tokens": st.sampled_from((1, 256, 1024)),
"top_p": st.sampled_from((0.1, 0.9, 1.0)),
"n": st.just(1),
"stop": st.sampled_from(("END", ["END", "STOP"])),
},
)
)
return VertexDeepSeekOcrSdkInput.model_validate(
{
"document": draw(public_document_strategy()),
"vertex_project": project,
"vertex_location": location,
**optional_params,
}
)
class VertexFixtureProvider:
def targets(self, environ: Mapping[str, str], sdk_call: FixtureSdkCall) -> tuple[OcrFixtureTarget, ...]:
api_key: Final = environ.get("VERTEX_AI_API_KEY")
project: Final = environ.get("VERTEXAI_PROJECT") or environ.get("VERTEX_PROJECT")
location: Final = environ.get("VERTEXAI_LOCATION") or environ.get("VERTEX_LOCATION") or "us-central1"
if not api_key or not project:
return ()
upstream_base: Final = environ.get("VERTEX_AI_API_BASE") or f"https://{location}-aiplatform.googleapis.com"
invoke: Final = invoke_with_api_key(sdk_call, api_key)
return (
OcrFixtureTarget(
name="vertex-mistral",
provider_spec=ProviderSpec(upstream_base=upstream_base.rstrip("/")),
strategy=cast(
SearchStrategy[OcrSdkInputBase],
st.builds(
_as_vertex_mistral,
case_input=mistral_input_strategy(MISTRAL_MODEL),
project=st.just(project),
location=st.just(location),
),
),
invoke=invoke,
required_inputs=cast(
tuple[OcrSdkInputBase, ...],
tuple(
_as_vertex_mistral(case_input, project, location)
for case_input in required_mistral_inputs(MISTRAL_MODEL)
),
),
),
OcrFixtureTarget(
name="vertex-deepseek",
provider_spec=ProviderSpec(upstream_base=upstream_base.rstrip("/")),
strategy=cast(SearchStrategy[OcrSdkInputBase], vertex_deepseek_input_strategy(project, location)),
invoke=invoke,
required_inputs=cast(tuple[OcrSdkInputBase, ...], _required_deepseek_inputs(project, location)),
),
)

View file

@ -1,605 +0,0 @@
from __future__ import annotations
import base64
import logging
import os
from collections.abc import Callable, Mapping
from pathlib import Path
from typing import Final, cast
from urllib.parse import quote
from dotenv import load_dotenv
from hypothesis import strategies as st
from hypothesis.strategies import DrawFn, SearchStrategy
import litellm
from litellm.rust_bridge.ocr import use_litellm_rust
from tests.route_parity.fixture_generator import (
FixtureTarget,
generate_target_fixtures,
parse_generator_args,
)
from tests.route_parity.fixture_generator import (
require_targets as require_fixture_targets,
)
from tests.route_parity.fixture_recorder import (
ProviderSpec,
fixture_directory,
)
from tests.test_litellm.ocr.fixture_models import (
AzureDocumentIntelligenceOcrSdkInput,
AzureMistralOcrSdkInput,
JsonSchemaDefinition,
JsonSchemaResponseFormat,
MistralDocumentUrlDocument,
MistralImageUrlDocument,
MistralOcrSdkInput,
OcrParityCase,
OcrSdkInputBase,
ReductoChunking,
ReductoDocumentUrlDocument,
ReductoFormatting,
ReductoParseLegacySdkInput,
ReductoParseV3SdkInput,
ReductoRetrieval,
ReductoSettings,
VertexDeepSeekOcrSdkInput,
VertexMistralOcrSdkInput,
)
FIXTURE_DIR_ENV: Final = "LITELLM_OCR_FIXTURE_DIR"
_VALUE_TEXT: Final = st.just("case-1")
_MISTRAL_MODEL: Final = "mistral/mistral-ocr-latest"
_REDUCTO_API_BASE: Final = "https://platform.reducto.ai"
OcrFixtureTarget = FixtureTarget[OcrSdkInputBase]
def _image_document(text: str, font_size: int) -> MistralImageUrlDocument:
url: Final = f"https://dummyjson.com/image/800x300/ffffff/000000?text={quote(text)}&fontSize={font_size}"
return MistralImageUrlDocument(type="image_url", image_url=url)
def _fixture_pdf_data_uri() -> str:
fixture: Final = Path(__file__).resolve().parents[2] / "llm_translation" / "fixtures" / "dummy.pdf"
encoded: Final = base64.b64encode(fixture.read_bytes()).decode("ascii")
return f"data:application/pdf;base64,{encoded}"
def _pdf_document() -> MistralDocumentUrlDocument:
return MistralDocumentUrlDocument(type="document_url", document_url=_fixture_pdf_data_uri())
def _public_document_strategy() -> SearchStrategy[MistralImageUrlDocument | MistralDocumentUrlDocument]:
return st.sampled_from((_image_document("invoice 123", 24), _pdf_document()))
def _annotation_format(name: str) -> JsonSchemaResponseFormat:
return JsonSchemaResponseFormat(
type="json_schema",
json_schema=JsonSchemaDefinition(
name=name,
description="Extract the visible document fields",
schema={
"type": "object",
"properties": {"title": {"type": "string"}},
"required": ["title"],
"additionalProperties": False,
},
strict=True,
),
)
def _mistral_confidence_strategy() -> SearchStrategy[str]:
return st.just("page")
@st.composite
def mistral_input_strategy(draw: DrawFn, model: str) -> MistralOcrSdkInput:
document: Final = draw(_public_document_strategy())
annotation: Final = draw(
st.sampled_from(
(
{},
{"document_annotation_format": _annotation_format("document_title")},
{
"document_annotation_format": _annotation_format("prompted_document_title"),
"document_annotation_prompt": "Extract the visible title",
},
)
)
)
optional_params: Final = draw(
st.fixed_dictionaries(
{},
optional={
"pages": st.just([0]),
"include_image_base64": st.booleans(),
"image_limit": st.just(1),
"image_min_size": st.just(300),
"bbox_annotation_format": st.just(_annotation_format("bounding_boxes")),
"extract_header": st.booleans(),
"extract_footer": st.booleans(),
"table_format": st.just("markdown"),
"confidence_scores_granularity": _mistral_confidence_strategy(),
"include_blocks": st.booleans(),
"id": _VALUE_TEXT,
},
)
)
return MistralOcrSdkInput.model_validate(
{
"model": model,
"document": document,
**annotation,
**optional_params,
}
)
def _as_azure_mistral(case_input: MistralOcrSdkInput, model: str) -> AzureMistralOcrSdkInput:
values: Final = case_input.model_dump(mode="python", exclude={"boundary", "model", "custom_llm_provider"})
return AzureMistralOcrSdkInput.model_validate({**values, "model": model})
def _as_vertex_mistral(case_input: MistralOcrSdkInput, project: str, location: str) -> VertexMistralOcrSdkInput:
values: Final = case_input.model_dump(mode="python", exclude={"boundary", "model", "custom_llm_provider"})
return VertexMistralOcrSdkInput.model_validate(
{**values, "vertex_project": project, "vertex_location": location}
)
def _required_mistral_inputs(model: str) -> tuple[MistralOcrSdkInput, ...]:
document: Final = _image_document("invoice 123", 24)
annotation: Final = _annotation_format("document_title")
bbox_annotation: Final = _annotation_format("bounding_boxes")
cases: Final[tuple[dict[str, object], ...]] = (
{},
{"pages": [0]},
{"include_image_base64": True},
{"image_limit": 1},
{"image_min_size": 300},
{"bbox_annotation_format": bbox_annotation},
{"document_annotation_format": annotation},
{"document_annotation_format": annotation, "document_annotation_prompt": "Extract the visible title"},
{"extract_header": True},
{"extract_footer": True},
{"table_format": "markdown"},
{"confidence_scores_granularity": "page"},
{"include_blocks": False},
{"id": "case-1"},
)
return tuple(
MistralOcrSdkInput.model_validate({"model": model, "document": document, **case}) for case in cases
)
def _required_azure_document_intelligence_inputs() -> tuple[AzureDocumentIntelligenceOcrSdkInput, ...]:
document: Final = _pdf_document()
model: Final = "azure_ai/doc-intelligence/prebuilt-layout"
return (
AzureDocumentIntelligenceOcrSdkInput(model=model, document=document),
AzureDocumentIntelligenceOcrSdkInput(model=model, document=document, pages=[0, 1]),
AzureDocumentIntelligenceOcrSdkInput(model=model, document=document, features=["languages"]),
AzureDocumentIntelligenceOcrSdkInput(model=model, document=document, req_format="litellm"),
)
def _required_vertex_deepseek_inputs(project: str, location: str) -> tuple[VertexDeepSeekOcrSdkInput, ...]:
document: Final = _image_document("invoice 123", 24)
common: Final = {"document": document, "vertex_project": project, "vertex_location": location}
cases: Final[tuple[dict[str, object], ...]] = (
{},
{"stream": False},
{"temperature": 0.5},
{"max_tokens": 256},
{"top_p": 0.9},
{"n": 1},
{"stop": ["END", "STOP"]},
)
return tuple(VertexDeepSeekOcrSdkInput.model_validate({**common, **case}) for case in cases)
def _required_reducto_v3_inputs(
document: ReductoDocumentUrlDocument,
) -> tuple[ReductoParseV3SdkInput, ...]:
return (
ReductoParseV3SdkInput(model="reducto/parse-v3", document=document),
ReductoParseV3SdkInput(
model="reducto/parse-v3",
document=document,
formatting=ReductoFormatting(table_output_format="md"),
),
ReductoParseV3SdkInput(
model="reducto/parse-v3",
document=document,
retrieval=ReductoRetrieval(chunking=ReductoChunking(chunk_mode="page")),
),
ReductoParseV3SdkInput(
model="reducto/parse-v3",
document=document,
settings=ReductoSettings(return_ocr_data=True),
),
)
@st.composite
def azure_document_intelligence_input_strategy(draw: DrawFn) -> AzureDocumentIntelligenceOcrSdkInput:
optional_params: Final = draw(
st.fixed_dictionaries(
{},
optional={
"pages": st.sampled_from(([0], [0, 1], "1-2")),
"features": st.sampled_from((["languages"], ["keyValuePairs"], "languages,keyValuePairs")),
},
)
)
return AzureDocumentIntelligenceOcrSdkInput.model_validate(
{
"model": draw(
st.sampled_from(
(
"azure_ai/doc-intelligence/prebuilt-read",
"azure_ai/doc-intelligence/prebuilt-layout",
"azure_ai/doc-intelligence/prebuilt-document",
)
)
),
"document": draw(_public_document_strategy()),
**optional_params,
}
)
@st.composite
def vertex_deepseek_input_strategy(draw: DrawFn, project: str, location: str) -> VertexDeepSeekOcrSdkInput:
optional_params: Final = draw(
st.fixed_dictionaries(
{},
optional={
"stream": st.just(False),
"temperature": st.sampled_from((0.0, 0.5, 1.0)),
"max_tokens": st.sampled_from((1, 256, 1024)),
"top_p": st.sampled_from((0.1, 0.9, 1.0)),
"n": st.just(1),
"stop": st.sampled_from(("END", ["END", "STOP"])),
},
)
)
return VertexDeepSeekOcrSdkInput.model_validate(
{
"document": draw(_public_document_strategy()),
"vertex_project": project,
"vertex_location": location,
**optional_params,
}
)
def _reducto_formatting_strategy() -> SearchStrategy[ReductoFormatting]:
return st.builds(
ReductoFormatting,
add_page_markers=st.booleans(),
table_output_format=st.sampled_from(("html", "json", "md", "jsonbbox", "dynamic", "csv")),
merge_tables=st.booleans(),
include=st.sampled_from(
(
[],
["hyperlinks"],
["change_tracking", "highlight", "comments"],
["signatures", "ignore_watermarks"],
)
),
)
def _reducto_chunking_strategy() -> SearchStrategy[ReductoChunking]:
return st.one_of(
st.builds(
ReductoChunking,
chunk_mode=st.sampled_from(("section", "page", "disabled", "block", "page_sections")),
chunk_size=st.just(None),
chunk_overlap=st.just(0),
),
st.builds(
ReductoChunking,
chunk_mode=st.just("variable"),
chunk_size=st.sampled_from((250, 1000, 1500)),
chunk_overlap=st.sampled_from((0, 32, 128)),
),
)
def _reducto_retrieval_strategy() -> SearchStrategy[ReductoRetrieval]:
return st.builds(
ReductoRetrieval,
chunking=_reducto_chunking_strategy(),
filter_blocks=st.sampled_from(
(
[],
["Header"],
["Header", "Footer", "Page Number"],
["Figure", "Table", "Key Value"],
)
),
embedding_optimized=st.booleans(),
)
def _reducto_settings_strategy() -> SearchStrategy[ReductoSettings]:
return st.builds(
ReductoSettings,
ocr_system=st.sampled_from(("standard", "legacy")),
extraction_mode=st.sampled_from(("ocr", "hybrid")),
force_url_result=st.just(False),
return_ocr_data=st.booleans(),
return_images=st.sampled_from(([], ["figure"], ["table"], ["page"], ["figure", "table", "page"])),
embed_pdf_metadata=st.booleans(),
embed_pdf_metadata_dpi=st.sampled_from((50, 100, 250)),
persist_results=st.just(False),
timeout=st.sampled_from((None, 300.0, 900.0)),
page_range=st.sampled_from((None, [1], [1, 2], ["Sheet1"])),
)
@st.composite
def reducto_v3_input_strategy(
draw: DrawFn, document: ReductoDocumentUrlDocument | None = None
) -> ReductoParseV3SdkInput:
model, custom_llm_provider = draw(st.sampled_from((("reducto/parse-v3", None), ("parse-v3", "reducto"))))
options: Final = draw(
st.fixed_dictionaries(
{},
optional={
"formatting": _reducto_formatting_strategy(),
"retrieval": _reducto_retrieval_strategy(),
"settings": _reducto_settings_strategy(),
},
)
)
return ReductoParseV3SdkInput.model_validate(
{
"model": model,
"custom_llm_provider": custom_llm_provider,
"document": document
or ReductoDocumentUrlDocument(type="document_url", document_url="reducto://fixture-document.pdf"),
**options,
}
)
def reducto_legacy_input_strategy(
document: ReductoDocumentUrlDocument | None = None,
) -> SearchStrategy[ReductoParseLegacySdkInput]:
selected_document: Final = document or ReductoDocumentUrlDocument(
type="document_url", document_url="reducto://fixture-document.pdf"
)
return st.sampled_from(
(
ReductoParseLegacySdkInput(
model="reducto/parse-legacy",
document=selected_document,
),
ReductoParseLegacySdkInput(
model="parse-legacy",
custom_llm_provider="reducto",
document=selected_document,
),
ReductoParseLegacySdkInput(
model="reducto/parse-legacy",
document=selected_document,
enhance={},
),
)
)
def _generate_examples(
target: OcrFixtureTarget,
root: Path,
examples: int,
concurrency: int,
) -> None:
generate_target_fixtures(target, root, examples, concurrency, OcrParityCase)
def _mistral_upstream_base(environ: Mapping[str, str]) -> str:
configured: Final = environ.get("MISTRAL_API_BASE", "https://api.mistral.ai").rstrip("/")
return configured.removesuffix("/v1")
def _mistral_target(
environ: Mapping[str, str],
sdk_call: Callable[..., object],
) -> OcrFixtureTarget | None:
api_key: Final = environ.get("MISTRAL_API_KEY")
if not api_key:
return None
def invoke(api_base: str, case_input: OcrSdkInputBase) -> object:
return sdk_call(api_base=api_base, api_key=api_key, **case_input.as_sdk_kwargs())
return OcrFixtureTarget(
name="mistral-ocr",
provider_spec=ProviderSpec(upstream_base=_mistral_upstream_base(environ)),
strategy=cast(SearchStrategy[OcrSdkInputBase], mistral_input_strategy(_MISTRAL_MODEL)),
invoke=invoke,
required_inputs=cast(tuple[OcrSdkInputBase, ...], _required_mistral_inputs(_MISTRAL_MODEL)),
)
def _azure_mistral_target(
environ: Mapping[str, str], sdk_call: Callable[..., object]
) -> OcrFixtureTarget | None:
api_key: Final = environ.get("AZURE_AI_API_KEY")
upstream_base: Final = environ.get("AZURE_AI_API_BASE")
configured_model: Final = environ.get("AZURE_AI_OCR_MODEL")
if not api_key or not upstream_base or not configured_model:
return None
model: Final = configured_model if configured_model.startswith("azure_ai/") else f"azure_ai/{configured_model}"
def invoke(api_base: str, case_input: OcrSdkInputBase) -> object:
return sdk_call(api_base=api_base, api_key=api_key, **case_input.as_sdk_kwargs())
return OcrFixtureTarget(
name="azure-mistral",
provider_spec=ProviderSpec(upstream_base=upstream_base.rstrip("/")),
strategy=cast(
SearchStrategy[OcrSdkInputBase],
mistral_input_strategy(_MISTRAL_MODEL).map(lambda case_input: _as_azure_mistral(case_input, model)),
),
invoke=invoke,
required_inputs=cast(
tuple[OcrSdkInputBase, ...],
tuple(_as_azure_mistral(case_input, model) for case_input in _required_mistral_inputs(_MISTRAL_MODEL)),
),
)
def _azure_document_intelligence_target(
environ: Mapping[str, str], sdk_call: Callable[..., object]
) -> OcrFixtureTarget | None:
api_key: Final = environ.get("AZURE_DOCUMENT_INTELLIGENCE_API_KEY")
upstream_base: Final = environ.get("AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT")
if not api_key or not upstream_base:
return None
def invoke(api_base: str, case_input: OcrSdkInputBase) -> object:
return sdk_call(api_base=api_base, api_key=api_key, **case_input.as_sdk_kwargs())
return OcrFixtureTarget(
name="azure-document-intelligence",
provider_spec=ProviderSpec(upstream_base=upstream_base.rstrip("/")),
strategy=cast(SearchStrategy[OcrSdkInputBase], azure_document_intelligence_input_strategy()),
invoke=invoke,
required_inputs=cast(
tuple[OcrSdkInputBase, ...], _required_azure_document_intelligence_inputs()
),
)
def _vertex_targets(
environ: Mapping[str, str], sdk_call: Callable[..., object]
) -> tuple[OcrFixtureTarget, ...]:
api_key: Final = environ.get("VERTEX_AI_API_KEY")
project: Final = environ.get("VERTEXAI_PROJECT") or environ.get("VERTEX_PROJECT")
location: Final = environ.get("VERTEXAI_LOCATION") or environ.get("VERTEX_LOCATION") or "us-central1"
if not api_key or not project:
return ()
upstream_base: Final = environ.get("VERTEX_AI_API_BASE") or f"https://{location}-aiplatform.googleapis.com"
def invoke(api_base: str, case_input: OcrSdkInputBase) -> object:
return sdk_call(api_base=api_base, api_key=api_key, **case_input.as_sdk_kwargs())
return (
OcrFixtureTarget(
name="vertex-mistral",
provider_spec=ProviderSpec(upstream_base=upstream_base.rstrip("/")),
strategy=cast(
SearchStrategy[OcrSdkInputBase],
st.builds(
_as_vertex_mistral,
case_input=mistral_input_strategy(_MISTRAL_MODEL),
project=st.just(project),
location=st.just(location),
),
),
invoke=invoke,
required_inputs=cast(
tuple[OcrSdkInputBase, ...],
tuple(
_as_vertex_mistral(case_input, project, location)
for case_input in _required_mistral_inputs(_MISTRAL_MODEL)
),
),
),
OcrFixtureTarget(
name="vertex-deepseek",
provider_spec=ProviderSpec(upstream_base=upstream_base.rstrip("/")),
strategy=cast(
SearchStrategy[OcrSdkInputBase], vertex_deepseek_input_strategy(project, location)
),
invoke=invoke,
required_inputs=cast(
tuple[OcrSdkInputBase, ...], _required_vertex_deepseek_inputs(project, location)
),
),
)
def _reducto_targets(
environ: Mapping[str, str], sdk_call: Callable[..., object]
) -> tuple[OcrFixtureTarget, ...]:
api_key: Final = environ.get("REDUCTO_API_KEY")
if not api_key:
return ()
upstream_base: Final = environ.get("REDUCTO_API_BASE", _REDUCTO_API_BASE).rstrip("/")
document: Final = ReductoDocumentUrlDocument(type="document_url", document_url=_fixture_pdf_data_uri())
def invoke(api_base: str, case_input: OcrSdkInputBase) -> object:
return sdk_call(api_base=api_base, api_key=api_key, **case_input.as_sdk_kwargs())
return (
OcrFixtureTarget(
name="reducto-v3",
provider_spec=ProviderSpec(upstream_base=upstream_base),
strategy=cast(SearchStrategy[OcrSdkInputBase], reducto_v3_input_strategy(document)),
invoke=invoke,
required_inputs=cast(tuple[OcrSdkInputBase, ...], _required_reducto_v3_inputs(document)),
),
OcrFixtureTarget(
name="reducto-legacy",
provider_spec=ProviderSpec(upstream_base=upstream_base),
strategy=cast(SearchStrategy[OcrSdkInputBase], reducto_legacy_input_strategy(document)),
invoke=invoke,
required_inputs=cast(
tuple[OcrSdkInputBase, ...],
(
ReductoParseLegacySdkInput(model="reducto/parse-legacy", document=document),
ReductoParseLegacySdkInput(model="reducto/parse-legacy", document=document, enhance={}),
),
),
),
)
def discover_targets(
environ: Mapping[str, str],
sdk_call: Callable[..., object],
) -> tuple[OcrFixtureTarget, ...]:
optional_targets: Final = (
_mistral_target(environ, sdk_call),
_azure_mistral_target(environ, sdk_call),
_azure_document_intelligence_target(environ, sdk_call),
)
direct_targets: Final = tuple(target for target in optional_targets if target is not None)
return (*direct_targets, *_vertex_targets(environ, sdk_call), *_reducto_targets(environ, sdk_call))
def require_targets(targets: tuple[OcrFixtureTarget, ...]) -> tuple[OcrFixtureTarget, ...]:
return require_fixture_targets(
targets,
"No OCR fixture providers are configured. Set a supported provider API key and endpoint",
)
def main() -> None:
logging.basicConfig(level=logging.INFO, format="%(message)s")
load_dotenv()
args: Final = parse_generator_args()
sdk_call: Final = cast(Callable[..., object], litellm.ocr)
targets: Final = require_targets(discover_targets(os.environ, sdk_call))
root: Final = fixture_directory(
args.fixture_dir,
os.environ.get(FIXTURE_DIR_ENV),
Path(__file__).with_name("fixtures"),
)
use_litellm_rust(False, ocr=None, aocr=None)
for target in targets:
_generate_examples(target, root, args.examples, args.concurrency)
if __name__ == "__main__":
main()

View file

@ -11,7 +11,14 @@ from litellm.llms.base_llm.ocr.transformation import BaseOCRConfig
from litellm.llms.mistral.ocr.transformation import MistralOCRConfig
from litellm.llms.reducto.ocr.transformation import ReductoParseLegacyConfig, ReductoParseV3Config
from litellm.llms.vertex_ai.ocr.deepseek_transformation import VertexAIDeepSeekOCRConfig
from tests.test_litellm.ocr.fixture_models import (
from tests.test_litellm.ocr.fixtures.generate import (
azure_document_intelligence_input_strategy,
mistral_input_strategy,
reducto_legacy_input_strategy,
reducto_v3_input_strategy,
vertex_deepseek_input_strategy,
)
from tests.test_litellm.ocr.fixtures.models import (
AzureDocumentIntelligenceOcrSdkInput,
AzureMistralOcrSdkInput,
JsonSchemaDefinition,
@ -32,13 +39,6 @@ from tests.test_litellm.ocr.fixture_models import (
VertexDeepSeekOcrSdkInput,
VertexMistralOcrSdkInput,
)
from tests.test_litellm.ocr.generate_fixtures import (
azure_document_intelligence_input_strategy,
mistral_input_strategy,
reducto_legacy_input_strategy,
reducto_v3_input_strategy,
vertex_deepseek_input_strategy,
)
COMMON_FIELDS: Final = frozenset(
{"boundary", "model", "document", "custom_llm_provider", "vertex_project", "vertex_location"}

View file

@ -7,7 +7,7 @@ from typing import Final
import pytest
from tests.route_parity.fixture_recorder import generate_case_inputs
from tests.test_litellm.ocr.generate_fixtures import (
from tests.test_litellm.ocr.fixtures.generate import (
discover_targets,
parse_generator_args,
require_targets,
@ -80,9 +80,9 @@ def test_azure_mistral_discovery_requires_and_normalizes_deployment_model() -> N
}
assert discover_targets(incomplete, _unused_sdk_call) == ()
target: Final = discover_targets(
{**incomplete, "AZURE_AI_OCR_MODEL": "mistral-ocr-deployment"}, _unused_sdk_call
)[0]
target: Final = discover_targets({**incomplete, "AZURE_AI_OCR_MODEL": "mistral-ocr-deployment"}, _unused_sdk_call)[
0
]
assert target.required_inputs[0].model == "azure_ai/mistral-ocr-deployment"

View file

@ -38,7 +38,7 @@ from tests.route_parity.runner import (
parity_worker_main,
run_execution,
)
from tests.test_litellm.ocr.fixture_models import OcrParityCase, OcrSdkInput
from tests.test_litellm.ocr.fixtures.models import OcrParityCase, OcrSdkInput
API_KEY: Final = "test-key"
PYTHON_HTTP_SENTINEL: Final = "python-ocr-parity-fallback"
@ -318,7 +318,7 @@ def sdk_workers() -> Generator[tuple[PythonScriptWorker, PythonScriptWorker]]:
@pytest.fixture(scope="module")
def startup_ocr_fixture() -> OcrParityCase:
default_directory: Final = Path(__file__).with_name("fixtures")
default_directory: Final = Path(__file__).with_name("fixtures") / "data"
configured: Final = os.environ.get(FIXTURE_DIR_ENV)
directory: Final = Path(configured).expanduser() if configured is not None else default_directory
fixtures: Final = recorded_fixtures(directory, OcrParityCase)