Files
wehub-resource-sync e904b667c6
Build/Publish Develop Docs / deploy (push) Failing after 1s
PaddleOCR Code Style Check / check-code-style (push) Failing after 1s
PaddleOCR PR Tests GPU / detect-changes (push) Failing after 1s
PaddleOCR PR Tests / detect-changes (push) Failing after 1s
PaddleOCR PR Tests GPU / test-pr-gpu (push) Has been cancelled
PaddleOCR PR Tests / test-pr (push) Has been cancelled
PaddleOCR PR Tests GPU / test-pr-gpu-impl (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.13) (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.8) (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.9) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:59:26 +08:00

183 lines
5.7 KiB
Python

# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Provider-specific input adapters for the unified MCP input contract."""
from __future__ import annotations
import abc
import base64
import io
from contextlib import contextmanager
from typing import Any, Generator, Union
from .input_contract import (
COMMON_INPUT_DESCRIPTION,
InputKind,
classify_input,
decode_input_bytes,
extract_base64_payload,
infer_file_type_from_bytes,
materialize_bytes_as_temp_file,
normalize,
resolve_absolute_path,
validate_external_contract,
)
from ...providers import (
InferenceProvider,
get_provider_spec,
is_http_provider,
normalize_provider,
)
class InputAdapter(abc.ABC):
@property
@abc.abstractmethod
def provider(self) -> InferenceProvider:
pass
@property
def description(self) -> str:
spec = get_provider_spec(self.provider)
return (
f"Inference provider: {spec.provider.value} ({spec.display_name}). "
f"{COMMON_INPUT_DESCRIPTION}"
)
def normalize(self, input_data: str) -> str:
return normalize(input_data)
def validate(self, input_data: str) -> None:
validate_external_contract(input_data)
@abc.abstractmethod
@contextmanager
def prepare(self, input_data: str) -> Generator[Any, None, None]:
"""Convert user input into the inference provider's native form."""
raise NotImplementedError
class LocalInputAdapter(InputAdapter):
@property
def provider(self) -> InferenceProvider:
return InferenceProvider.LOCAL
@contextmanager
def prepare(self, input_data: str) -> Generator[Union[str, Any], None, None]:
value = normalize(input_data)
kind = classify_input(value)
if kind is InputKind.URL:
yield value
return
if kind is InputKind.ABSOLUTE_PATH:
yield str(resolve_absolute_path(value))
return
data = decode_input_bytes(value, kind)
file_type = infer_file_type_from_bytes(data)
if file_type == "image":
import numpy as np
from PIL import Image as PILImage
try:
image_pil = PILImage.open(io.BytesIO(data))
image_arr = np.array(image_pil.convert("RGB"))
yield np.ascontiguousarray(image_arr[..., ::-1])
except Exception as e:
raise ValueError(f"Failed to decode Base64 image: {e}") from e
return
if file_type == "pdf":
with materialize_bytes_as_temp_file(data, file_type="pdf") as temp_path:
yield str(temp_path)
return
raise ValueError(
"Unsupported Base64 content type for local inference. "
"Only image and PDF inputs are supported."
)
class AIStudioInputAdapter(InputAdapter):
@property
def provider(self) -> InferenceProvider:
return InferenceProvider.AISTUDIO
@contextmanager
def prepare(self, input_data: str) -> Generator[dict[str, str], None, None]:
value = normalize(input_data)
kind = classify_input(value)
if kind is InputKind.URL:
yield {"file_url": value}
return
if kind is InputKind.ABSOLUTE_PATH:
yield {"file_path": str(resolve_absolute_path(value))}
return
data = decode_input_bytes(value, kind)
file_type = infer_file_type_from_bytes(data)
if file_type not in ("image", "pdf"):
raise ValueError(
"Unsupported Base64 content type for AI Studio. "
"Only image and PDF inputs are supported."
)
with materialize_bytes_as_temp_file(data, file_type=file_type) as temp_path:
yield {"file_path": str(temp_path)}
class HTTPInputAdapter(InputAdapter):
def __init__(self, provider: InferenceProvider | str) -> None:
normalized_provider = normalize_provider(provider)
if not is_http_provider(normalized_provider):
raise ValueError(
f"HTTPInputAdapter requires an HTTP transport provider, "
f"got {normalized_provider.value!r}."
)
self._provider = normalized_provider
@property
def provider(self) -> InferenceProvider:
return self._provider
@contextmanager
def prepare(self, input_data: str) -> Generator[str, None, None]:
yield self.prepare_http_file_field(input_data)
def prepare_http_file_field(self, input_data: str) -> str:
value = normalize(input_data)
kind = classify_input(value)
if kind is InputKind.URL:
return value
if kind is InputKind.ABSOLUTE_PATH:
path = resolve_absolute_path(value)
return base64.b64encode(path.read_bytes()).decode("ascii")
payload = extract_base64_payload(value)
decode_input_bytes(value, kind)
return payload
LOCAL_INPUT_ADAPTER = LocalInputAdapter()
AISTUDIO_INPUT_ADAPTER = AIStudioInputAdapter()
SELF_HOSTED_INPUT_ADAPTER = HTTPInputAdapter(InferenceProvider.SELF_HOSTED)
QIANFAN_INPUT_ADAPTER = HTTPInputAdapter(InferenceProvider.QIANFAN)