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
183 lines
5.7 KiB
Python
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)
|