vlmparse 0.1.8__py3-none-any.whl → 0.1.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vlmparse/cli.py +439 -286
- vlmparse/clients/deepseekocr.py +170 -0
- vlmparse/clients/glmocr.py +243 -0
- vlmparse/clients/paddleocrvl.py +191 -43
- vlmparse/converter_with_server.py +53 -16
- vlmparse/registries.py +20 -10
- vlmparse/servers/base_server.py +127 -0
- vlmparse/servers/docker_compose_deployment.py +489 -0
- vlmparse/servers/docker_compose_server.py +39 -0
- vlmparse/servers/docker_run_deployment.py +226 -0
- vlmparse/servers/docker_server.py +9 -125
- vlmparse/servers/server_registry.py +42 -0
- vlmparse/servers/utils.py +83 -219
- vlmparse/st_viewer/st_viewer.py +1 -1
- {vlmparse-0.1.8.dist-info → vlmparse-0.1.9.dist-info}/METADATA +3 -3
- {vlmparse-0.1.8.dist-info → vlmparse-0.1.9.dist-info}/RECORD +20 -14
- {vlmparse-0.1.8.dist-info → vlmparse-0.1.9.dist-info}/WHEEL +0 -0
- {vlmparse-0.1.8.dist-info → vlmparse-0.1.9.dist-info}/entry_points.txt +0 -0
- {vlmparse-0.1.8.dist-info → vlmparse-0.1.9.dist-info}/licenses/LICENSE +0 -0
- {vlmparse-0.1.8.dist-info → vlmparse-0.1.9.dist-info}/top_level.txt +0 -0
|
@@ -14,25 +14,39 @@ def start_server(
|
|
|
14
14
|
model: str,
|
|
15
15
|
gpus: str,
|
|
16
16
|
port: None | int = None,
|
|
17
|
-
|
|
17
|
+
server: Literal["registry", "hf"] = "registry",
|
|
18
18
|
vllm_args: list[str] = {},
|
|
19
19
|
forget_predefined_vllm_args: bool = False,
|
|
20
20
|
auto_stop: bool = False,
|
|
21
21
|
):
|
|
22
22
|
from vlmparse.registries import docker_config_registry
|
|
23
|
+
from vlmparse.servers.docker_server import (
|
|
24
|
+
DEFAULT_MODEL_NAME,
|
|
25
|
+
VLLMDockerServerConfig,
|
|
26
|
+
)
|
|
23
27
|
|
|
24
28
|
base_url = ""
|
|
25
29
|
container = None
|
|
26
|
-
docker_config = docker_config_registry.get(model
|
|
30
|
+
docker_config = docker_config_registry.get(model)
|
|
27
31
|
|
|
28
32
|
if port is None:
|
|
29
33
|
port = DEFAULT_SERVER_PORT
|
|
30
34
|
|
|
31
35
|
if docker_config is None:
|
|
32
|
-
|
|
33
|
-
f"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
if server == "registry":
|
|
37
|
+
print(f"DEBUG: Registry lookup failed for {model} (strict mode)")
|
|
38
|
+
raise ValueError(
|
|
39
|
+
f"Model '{model}' not found in registry and server='registry'. Use server='hf' to serve arbitrary HuggingFace models."
|
|
40
|
+
)
|
|
41
|
+
elif server == "hf":
|
|
42
|
+
docker_config = VLLMDockerServerConfig(
|
|
43
|
+
model_name=model, default_model_name=DEFAULT_MODEL_NAME
|
|
44
|
+
)
|
|
45
|
+
else:
|
|
46
|
+
logger.warning(
|
|
47
|
+
f"No Docker configuration found for model: {model} and server type is undetermined."
|
|
48
|
+
)
|
|
49
|
+
return "", container, None, docker_config
|
|
36
50
|
|
|
37
51
|
gpu_device_ids = None
|
|
38
52
|
if gpus is not None:
|
|
@@ -42,13 +56,14 @@ def start_server(
|
|
|
42
56
|
if port is not None:
|
|
43
57
|
docker_config.docker_port = port
|
|
44
58
|
docker_config.gpu_device_ids = gpu_device_ids
|
|
45
|
-
docker_config
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
if hasattr(docker_config, "update_command_args"):
|
|
60
|
+
docker_config.update_command_args(
|
|
61
|
+
vllm_args,
|
|
62
|
+
forget_predefined_vllm_args=forget_predefined_vllm_args,
|
|
63
|
+
)
|
|
49
64
|
|
|
50
65
|
logger.info(
|
|
51
|
-
f"Deploying
|
|
66
|
+
f"Deploying server for {docker_config.model_name} on port {port}..."
|
|
52
67
|
)
|
|
53
68
|
server = docker_config.get_server(auto_stop=auto_stop)
|
|
54
69
|
if server is None:
|
|
@@ -67,7 +82,7 @@ class ConverterWithServer:
|
|
|
67
82
|
uri: str | None = None,
|
|
68
83
|
gpus: str | None = None,
|
|
69
84
|
port: int | None = None,
|
|
70
|
-
|
|
85
|
+
server: Literal["registry", "hf", "google", "openai"] = "registry",
|
|
71
86
|
concurrency: int = 10,
|
|
72
87
|
vllm_args: dict | None = None,
|
|
73
88
|
forget_predefined_vllm_args: bool = False,
|
|
@@ -83,7 +98,7 @@ class ConverterWithServer:
|
|
|
83
98
|
self.uri = uri
|
|
84
99
|
self.port = port
|
|
85
100
|
self.gpus = gpus
|
|
86
|
-
self.
|
|
101
|
+
self.server_type = server
|
|
87
102
|
self.concurrency = concurrency
|
|
88
103
|
self.vllm_args = vllm_args
|
|
89
104
|
self.forget_predefined_vllm_args = forget_predefined_vllm_args
|
|
@@ -91,18 +106,31 @@ class ConverterWithServer:
|
|
|
91
106
|
self.server = None
|
|
92
107
|
self.client = None
|
|
93
108
|
|
|
94
|
-
if self.uri is not None:
|
|
109
|
+
if self.uri is not None and self.model is None:
|
|
95
110
|
self.model = get_model_from_uri(self.uri)
|
|
96
111
|
|
|
97
112
|
def start_server_and_client(self):
|
|
98
|
-
from vlmparse.
|
|
113
|
+
from vlmparse.clients.openai_converter import OpenAIConverterConfig
|
|
114
|
+
from vlmparse.registries import (
|
|
115
|
+
converter_config_registry,
|
|
116
|
+
docker_config_registry,
|
|
117
|
+
)
|
|
99
118
|
|
|
119
|
+
start_local_server = False
|
|
100
120
|
if self.uri is None:
|
|
121
|
+
if self.server_type == "hf":
|
|
122
|
+
start_local_server = True
|
|
123
|
+
elif self.server_type == "registry":
|
|
124
|
+
if self.model in docker_config_registry.list_models():
|
|
125
|
+
start_local_server = True
|
|
126
|
+
|
|
127
|
+
if start_local_server:
|
|
128
|
+
server_arg = "hf" if self.server_type == "hf" else "registry"
|
|
101
129
|
_, _, self.server, docker_config = start_server(
|
|
102
130
|
model=self.model,
|
|
103
131
|
gpus=self.gpus,
|
|
104
132
|
port=self.port,
|
|
105
|
-
|
|
133
|
+
server=server_arg,
|
|
106
134
|
vllm_args=self.vllm_args,
|
|
107
135
|
forget_predefined_vllm_args=self.forget_predefined_vllm_args,
|
|
108
136
|
auto_stop=True,
|
|
@@ -113,10 +141,19 @@ class ConverterWithServer:
|
|
|
113
141
|
return_documents_in_batch_mode=self.return_documents
|
|
114
142
|
)
|
|
115
143
|
else:
|
|
144
|
+
# Should not happen if start_server works as expected
|
|
116
145
|
self.client = converter_config_registry.get(self.model).get_client(
|
|
117
146
|
return_documents_in_batch_mode=self.return_documents
|
|
118
147
|
)
|
|
119
148
|
|
|
149
|
+
elif self.server_type == "hf":
|
|
150
|
+
client_config = OpenAIConverterConfig(
|
|
151
|
+
model_name=self.model, base_url=self.uri
|
|
152
|
+
)
|
|
153
|
+
self.client = client_config.get_client(
|
|
154
|
+
return_documents_in_batch_mode=self.return_documents
|
|
155
|
+
)
|
|
156
|
+
|
|
120
157
|
else:
|
|
121
158
|
client_config = converter_config_registry.get(self.model, uri=self.uri)
|
|
122
159
|
|
vlmparse/registries.py
CHANGED
|
@@ -2,9 +2,13 @@ import os
|
|
|
2
2
|
from collections.abc import Callable
|
|
3
3
|
|
|
4
4
|
from vlmparse.clients.chandra import ChandraDockerServerConfig
|
|
5
|
-
from vlmparse.clients.deepseekocr import
|
|
5
|
+
from vlmparse.clients.deepseekocr import (
|
|
6
|
+
DeepSeekOCR2DockerServerConfig,
|
|
7
|
+
DeepSeekOCRDockerServerConfig,
|
|
8
|
+
)
|
|
6
9
|
from vlmparse.clients.docling import DoclingDockerServerConfig
|
|
7
10
|
from vlmparse.clients.dotsocr import DotsOCRDockerServerConfig
|
|
11
|
+
from vlmparse.clients.glmocr import GLMOCRDockerServerConfig
|
|
8
12
|
from vlmparse.clients.granite_docling import GraniteDoclingDockerServerConfig
|
|
9
13
|
from vlmparse.clients.hunyuanocr import HunyuanOCRDockerServerConfig
|
|
10
14
|
from vlmparse.clients.lightonocr import (
|
|
@@ -18,7 +22,9 @@ from vlmparse.clients.olmocr import OlmOCRDockerServerConfig
|
|
|
18
22
|
from vlmparse.clients.openai_converter import OpenAIConverterConfig
|
|
19
23
|
from vlmparse.clients.paddleocrvl import PaddleOCRVLDockerServerConfig
|
|
20
24
|
from vlmparse.converter import ConverterConfig
|
|
21
|
-
from vlmparse.servers.
|
|
25
|
+
from vlmparse.servers.docker_compose_server import DockerComposeServerConfig
|
|
26
|
+
from vlmparse.servers.docker_server import DockerServerConfig
|
|
27
|
+
from vlmparse.servers.server_registry import docker_config_registry
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
def get_default(cls, field_name):
|
|
@@ -31,17 +37,19 @@ def get_default(cls, field_name):
|
|
|
31
37
|
|
|
32
38
|
|
|
33
39
|
# All server configs - single source of truth
|
|
34
|
-
SERVER_CONFIGS: list[type[DockerServerConfig]] = [
|
|
40
|
+
SERVER_CONFIGS: list[type[DockerServerConfig | DockerComposeServerConfig]] = [
|
|
35
41
|
ChandraDockerServerConfig,
|
|
36
42
|
LightOnOCRDockerServerConfig,
|
|
37
43
|
DotsOCRDockerServerConfig,
|
|
38
44
|
PaddleOCRVLDockerServerConfig,
|
|
45
|
+
GLMOCRDockerServerConfig,
|
|
39
46
|
NanonetOCR2DockerServerConfig,
|
|
40
47
|
HunyuanOCRDockerServerConfig,
|
|
41
48
|
DoclingDockerServerConfig,
|
|
42
49
|
OlmOCRDockerServerConfig,
|
|
43
50
|
MinerUDockerServerConfig,
|
|
44
51
|
DeepSeekOCRDockerServerConfig,
|
|
52
|
+
DeepSeekOCR2DockerServerConfig,
|
|
45
53
|
GraniteDoclingDockerServerConfig,
|
|
46
54
|
LightonOCR21BServerConfig,
|
|
47
55
|
]
|
|
@@ -78,7 +86,7 @@ class ConverterConfigRegistry:
|
|
|
78
86
|
|
|
79
87
|
def register_from_server(
|
|
80
88
|
self,
|
|
81
|
-
server_config_cls: type[DockerServerConfig],
|
|
89
|
+
server_config_cls: type[DockerServerConfig | DockerComposeServerConfig],
|
|
82
90
|
):
|
|
83
91
|
"""Register converter config derived from a server config class.
|
|
84
92
|
|
|
@@ -104,17 +112,19 @@ class ConverterConfigRegistry:
|
|
|
104
112
|
for name in names:
|
|
105
113
|
self._registry[name] = factory
|
|
106
114
|
|
|
107
|
-
def get(
|
|
108
|
-
|
|
115
|
+
def get(
|
|
116
|
+
self,
|
|
117
|
+
model_name: str,
|
|
118
|
+
uri: str | None = None,
|
|
119
|
+
) -> ConverterConfig:
|
|
120
|
+
"""Get config for a model name (thread-safe). Raises ValueError if not registered."""
|
|
109
121
|
with self._lock:
|
|
110
122
|
factory = self._registry.get(model_name)
|
|
111
123
|
|
|
112
124
|
if factory is not None:
|
|
113
125
|
return factory(uri)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return OpenAIConverterConfig(base_url=uri)
|
|
117
|
-
return OpenAIConverterConfig(model_name=model_name)
|
|
126
|
+
|
|
127
|
+
raise ValueError(f"Model '{model_name}' not found in registry.")
|
|
118
128
|
|
|
119
129
|
def list_models(self) -> list[str]:
|
|
120
130
|
"""List all registered model names (thread-safe)."""
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Base classes for server configurations and server lifecycle management."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
from loguru import logger
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from .model_identity import ModelIdentityMixin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseServerConfig(ModelIdentityMixin, ABC):
|
|
12
|
+
"""Base configuration for deploying a server.
|
|
13
|
+
|
|
14
|
+
Inherits from ModelIdentityMixin which provides:
|
|
15
|
+
- model_name: str
|
|
16
|
+
- default_model_name: str | None
|
|
17
|
+
- aliases: list[str]
|
|
18
|
+
- _create_client_kwargs(base_url): Helper for creating client configs
|
|
19
|
+
- get_all_names(): All names this model can be referenced by
|
|
20
|
+
|
|
21
|
+
All server configs should inherit from this base class.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
docker_port: int = 8056
|
|
25
|
+
container_port: int = 8000
|
|
26
|
+
gpu_device_ids: list[str] | None = None
|
|
27
|
+
environment: dict[str, str] = Field(default_factory=dict)
|
|
28
|
+
server_ready_indicators: list[str] = Field(
|
|
29
|
+
default_factory=lambda: [
|
|
30
|
+
"Application startup complete",
|
|
31
|
+
"Uvicorn running",
|
|
32
|
+
"Starting vLLM API server",
|
|
33
|
+
]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
class Config:
|
|
37
|
+
extra = "allow"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def client_config(self):
|
|
42
|
+
"""Override in subclasses to return appropriate client config."""
|
|
43
|
+
raise NotImplementedError
|
|
44
|
+
|
|
45
|
+
def get_client(self, **kwargs):
|
|
46
|
+
"""Get a client instance configured for this server."""
|
|
47
|
+
return self.client_config.get_client(**kwargs)
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def get_server(self, auto_stop: bool = True):
|
|
51
|
+
"""Get a server instance for this configuration."""
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
54
|
+
def get_environment(self) -> dict | None:
|
|
55
|
+
"""Setup environment variables. Override in subclasses for specific logic."""
|
|
56
|
+
return self.environment if self.environment else None
|
|
57
|
+
|
|
58
|
+
def get_base_url_suffix(self) -> str:
|
|
59
|
+
"""Return URL suffix (e.g., '/v1' for OpenAI-compatible APIs). Override in subclasses."""
|
|
60
|
+
return ""
|
|
61
|
+
|
|
62
|
+
def update_command_args(
|
|
63
|
+
self,
|
|
64
|
+
vllm_args: dict | None = None,
|
|
65
|
+
forget_predefined_vllm_args: bool = False,
|
|
66
|
+
) -> list[str]:
|
|
67
|
+
"""Update command arguments. Override in subclasses that support this."""
|
|
68
|
+
_ = vllm_args, forget_predefined_vllm_args
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class BaseServer(ABC):
|
|
73
|
+
"""Base class for managing server lifecycle with start/stop methods.
|
|
74
|
+
|
|
75
|
+
All server implementations should inherit from this class.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, config: BaseServerConfig, auto_stop: bool = True):
|
|
79
|
+
self.config = config
|
|
80
|
+
self.auto_stop = auto_stop
|
|
81
|
+
self._server_context = None
|
|
82
|
+
self._container = None
|
|
83
|
+
self.base_url = None
|
|
84
|
+
|
|
85
|
+
@abstractmethod
|
|
86
|
+
def _create_server_context(self):
|
|
87
|
+
"""Create the appropriate server context. Override in subclasses."""
|
|
88
|
+
raise NotImplementedError
|
|
89
|
+
|
|
90
|
+
def start(self):
|
|
91
|
+
"""Start the server."""
|
|
92
|
+
if self._server_context is not None:
|
|
93
|
+
logger.warning("Server already started")
|
|
94
|
+
return self.base_url, self._container
|
|
95
|
+
|
|
96
|
+
self._server_context = self._create_server_context()
|
|
97
|
+
self.base_url, self._container = self._server_context.__enter__()
|
|
98
|
+
logger.info(f"Server started at {self.base_url}")
|
|
99
|
+
if self._container is not None:
|
|
100
|
+
logger.info(f"Container ID: {self._container.id}")
|
|
101
|
+
logger.info(f"Container name: {self._container.name}")
|
|
102
|
+
return self.base_url, self._container
|
|
103
|
+
|
|
104
|
+
def stop(self):
|
|
105
|
+
"""Stop the server."""
|
|
106
|
+
if self._server_context is not None:
|
|
107
|
+
try:
|
|
108
|
+
self._server_context.__exit__(None, None, None)
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.warning(f"Error during server cleanup: {e}")
|
|
111
|
+
finally:
|
|
112
|
+
self._server_context = None
|
|
113
|
+
self._container = None
|
|
114
|
+
self.base_url = None
|
|
115
|
+
logger.info("Server stopped")
|
|
116
|
+
|
|
117
|
+
def __del__(self):
|
|
118
|
+
"""Automatically stop server when object is destroyed if auto_stop is True.
|
|
119
|
+
|
|
120
|
+
Note: This is a fallback mechanism. Prefer using the context manager
|
|
121
|
+
or explicitly calling stop() for reliable cleanup.
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
if self.auto_stop and self._server_context is not None:
|
|
125
|
+
self.stop()
|
|
126
|
+
except Exception:
|
|
127
|
+
pass # Suppress errors during garbage collection
|