vlmparse 0.1.7__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/build_doc.py +20 -19
- vlmparse/cli.py +439 -270
- vlmparse/clients/chandra.py +176 -60
- vlmparse/clients/deepseekocr.py +193 -12
- vlmparse/clients/docling.py +0 -1
- vlmparse/clients/dotsocr.py +34 -31
- vlmparse/clients/glmocr.py +243 -0
- vlmparse/clients/granite_docling.py +9 -36
- vlmparse/clients/hunyuanocr.py +5 -1
- vlmparse/clients/lightonocr.py +23 -1
- vlmparse/clients/mineru.py +0 -1
- vlmparse/clients/mistral_converter.py +85 -0
- vlmparse/clients/nanonetocr.py +5 -1
- vlmparse/clients/olmocr.py +6 -2
- vlmparse/clients/openai_converter.py +95 -60
- vlmparse/clients/paddleocrvl.py +195 -40
- vlmparse/converter.py +51 -11
- vlmparse/converter_with_server.py +92 -19
- vlmparse/registries.py +107 -89
- 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 +17 -109
- vlmparse/servers/model_identity.py +48 -0
- vlmparse/servers/server_registry.py +42 -0
- vlmparse/servers/utils.py +83 -219
- vlmparse/st_viewer/st_viewer.py +1 -1
- vlmparse/utils.py +15 -2
- {vlmparse-0.1.7.dist-info → vlmparse-0.1.9.dist-info}/METADATA +13 -3
- vlmparse-0.1.9.dist-info/RECORD +44 -0
- {vlmparse-0.1.7.dist-info → vlmparse-0.1.9.dist-info}/WHEEL +1 -1
- vlmparse-0.1.7.dist-info/RECORD +0 -36
- {vlmparse-0.1.7.dist-info → vlmparse-0.1.9.dist-info}/entry_points.txt +0 -0
- {vlmparse-0.1.7.dist-info → vlmparse-0.1.9.dist-info}/licenses/LICENSE +0 -0
- {vlmparse-0.1.7.dist-info → vlmparse-0.1.9.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import getpass
|
|
2
|
+
import time
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import docker
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .base_server import BaseServerConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _ensure_image_exists(
|
|
15
|
+
client: docker.DockerClient,
|
|
16
|
+
image: str,
|
|
17
|
+
dockerfile_path: Path,
|
|
18
|
+
):
|
|
19
|
+
"""Check if image exists, build it if not."""
|
|
20
|
+
try:
|
|
21
|
+
client.images.get(image)
|
|
22
|
+
logger.info(f"Docker image {image} found")
|
|
23
|
+
return
|
|
24
|
+
except docker.errors.ImageNotFound:
|
|
25
|
+
logger.info(f"Docker image {image} not found, building...")
|
|
26
|
+
|
|
27
|
+
if not dockerfile_path.exists():
|
|
28
|
+
raise FileNotFoundError(
|
|
29
|
+
f"Dockerfile directory not found at {dockerfile_path}"
|
|
30
|
+
) from None
|
|
31
|
+
|
|
32
|
+
logger.info(f"Building image from {dockerfile_path}")
|
|
33
|
+
|
|
34
|
+
# Use low-level API for real-time streaming
|
|
35
|
+
api_client = docker.APIClient(base_url="unix://var/run/docker.sock")
|
|
36
|
+
|
|
37
|
+
# Build the image with streaming
|
|
38
|
+
build_stream = api_client.build(
|
|
39
|
+
path=str(dockerfile_path),
|
|
40
|
+
tag=image,
|
|
41
|
+
rm=True,
|
|
42
|
+
decode=True, # Automatically decode JSON responses to dict
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Stream build logs in real-time
|
|
46
|
+
for chunk in build_stream:
|
|
47
|
+
if "stream" in chunk:
|
|
48
|
+
for line in chunk["stream"].splitlines():
|
|
49
|
+
logger.info(line)
|
|
50
|
+
elif "error" in chunk:
|
|
51
|
+
logger.error(chunk["error"])
|
|
52
|
+
raise docker.errors.BuildError(chunk["error"], build_stream) from None
|
|
53
|
+
elif "status" in chunk:
|
|
54
|
+
# Handle status updates (e.g., downloading layers)
|
|
55
|
+
logger.debug(chunk["status"])
|
|
56
|
+
|
|
57
|
+
logger.info(f"Successfully built image {image}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@contextmanager
|
|
61
|
+
def docker_server(
|
|
62
|
+
config: "BaseServerConfig",
|
|
63
|
+
timeout: int = 1000,
|
|
64
|
+
cleanup: bool = True,
|
|
65
|
+
):
|
|
66
|
+
"""Generic context manager for Docker server deployment.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
config: BaseServerConfig (can be any subclass like DockerServerConfig or VLLMDockerServerConfig)
|
|
70
|
+
timeout: Timeout in seconds to wait for server to be ready
|
|
71
|
+
cleanup: If True, stop and remove container on exit. If False, leave container running
|
|
72
|
+
|
|
73
|
+
Yields:
|
|
74
|
+
tuple: (base_url, container) - The base URL of the server and the Docker container object
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
client = docker.from_env()
|
|
78
|
+
container = None
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
# Ensure image exists
|
|
82
|
+
logger.info(f"Checking for Docker image {config.docker_image}...")
|
|
83
|
+
|
|
84
|
+
if config.dockerfile_dir is not None:
|
|
85
|
+
_ensure_image_exists(
|
|
86
|
+
client, config.docker_image, Path(config.dockerfile_dir)
|
|
87
|
+
)
|
|
88
|
+
else:
|
|
89
|
+
# Pull pre-built image
|
|
90
|
+
try:
|
|
91
|
+
client.images.get(config.docker_image)
|
|
92
|
+
logger.info(f"Docker image {config.docker_image} found locally")
|
|
93
|
+
except docker.errors.ImageNotFound:
|
|
94
|
+
logger.info(
|
|
95
|
+
f"Docker image {config.docker_image} not found locally, pulling..."
|
|
96
|
+
)
|
|
97
|
+
client.images.pull(config.docker_image)
|
|
98
|
+
logger.info(f"Successfully pulled {config.docker_image}")
|
|
99
|
+
|
|
100
|
+
logger.info(
|
|
101
|
+
f"Starting Docker container for {config.model_name} on port {config.docker_port}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Configure GPU access
|
|
105
|
+
device_requests = None
|
|
106
|
+
|
|
107
|
+
if config.gpu_device_ids is None:
|
|
108
|
+
# Default: Try to use all GPUs if available
|
|
109
|
+
device_requests = [
|
|
110
|
+
docker.types.DeviceRequest(count=-1, capabilities=[["gpu"]])
|
|
111
|
+
]
|
|
112
|
+
elif len(config.gpu_device_ids) > 0 and config.gpu_device_ids[0] != "":
|
|
113
|
+
# Use specific GPU devices
|
|
114
|
+
device_requests = [
|
|
115
|
+
docker.types.DeviceRequest(
|
|
116
|
+
device_ids=config.gpu_device_ids, capabilities=[["gpu"]]
|
|
117
|
+
)
|
|
118
|
+
]
|
|
119
|
+
else:
|
|
120
|
+
# Empty list means CPU-only, no GPU
|
|
121
|
+
device_requests = None
|
|
122
|
+
|
|
123
|
+
# Use generic methods from config
|
|
124
|
+
command = config.get_command()
|
|
125
|
+
volumes = config.get_volumes()
|
|
126
|
+
environment = config.get_environment()
|
|
127
|
+
container_port = config.container_port
|
|
128
|
+
log_prefix = config.model_name
|
|
129
|
+
|
|
130
|
+
# Construct URI for label
|
|
131
|
+
uri = f"http://localhost:{config.docker_port}{config.get_base_url_suffix()}"
|
|
132
|
+
|
|
133
|
+
# Determine GPU label
|
|
134
|
+
if config.gpu_device_ids is None:
|
|
135
|
+
gpu_label = "0"
|
|
136
|
+
elif len(config.gpu_device_ids) == 0 or (
|
|
137
|
+
len(config.gpu_device_ids) == 1 and config.gpu_device_ids[0] == ""
|
|
138
|
+
):
|
|
139
|
+
gpu_label = "cpu"
|
|
140
|
+
else:
|
|
141
|
+
gpu_label = ",".join(config.gpu_device_ids)
|
|
142
|
+
|
|
143
|
+
# Start container
|
|
144
|
+
container_kwargs = {
|
|
145
|
+
"image": config.docker_image,
|
|
146
|
+
"ports": {f"{container_port}/tcp": config.docker_port},
|
|
147
|
+
"detach": True,
|
|
148
|
+
"remove": True,
|
|
149
|
+
"name": f"vlmparse-{config.model_name.replace('/', '-')}-{getpass.getuser()}",
|
|
150
|
+
"labels": {
|
|
151
|
+
"vlmparse_model_name": config.model_name,
|
|
152
|
+
"vlmparse_uri": uri,
|
|
153
|
+
"vlmparse_gpus": gpu_label,
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if device_requests is not None:
|
|
158
|
+
container_kwargs["device_requests"] = device_requests
|
|
159
|
+
if command:
|
|
160
|
+
container_kwargs["command"] = command
|
|
161
|
+
if environment:
|
|
162
|
+
container_kwargs["environment"] = environment
|
|
163
|
+
if volumes:
|
|
164
|
+
container_kwargs["volumes"] = volumes
|
|
165
|
+
if config.entrypoint:
|
|
166
|
+
container_kwargs["entrypoint"] = config.entrypoint
|
|
167
|
+
|
|
168
|
+
container = client.containers.run(**container_kwargs)
|
|
169
|
+
|
|
170
|
+
logger.info(
|
|
171
|
+
f"Container {container.short_id} started, waiting for server to be ready..."
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Wait for server to be ready
|
|
175
|
+
start_time = time.time()
|
|
176
|
+
server_ready = False
|
|
177
|
+
last_log_position = 0
|
|
178
|
+
|
|
179
|
+
while time.time() - start_time < timeout:
|
|
180
|
+
try:
|
|
181
|
+
container.reload()
|
|
182
|
+
except docker.errors.NotFound as e:
|
|
183
|
+
logger.error("Container stopped unexpectedly during startup")
|
|
184
|
+
raise RuntimeError(
|
|
185
|
+
"Container crashed during initialization. Check Docker logs for details."
|
|
186
|
+
) from e
|
|
187
|
+
|
|
188
|
+
if container.status == "running":
|
|
189
|
+
# Get all logs and display new ones
|
|
190
|
+
all_logs = container.logs().decode("utf-8")
|
|
191
|
+
|
|
192
|
+
# Display new log lines
|
|
193
|
+
if len(all_logs) > last_log_position:
|
|
194
|
+
new_logs = all_logs[last_log_position:]
|
|
195
|
+
for line in new_logs.splitlines():
|
|
196
|
+
if line.strip(): # Only print non-empty lines
|
|
197
|
+
logger.info(f"[{log_prefix}] {line}")
|
|
198
|
+
last_log_position = len(all_logs)
|
|
199
|
+
|
|
200
|
+
# Check if server is ready
|
|
201
|
+
for indicator in config.server_ready_indicators:
|
|
202
|
+
if indicator in all_logs:
|
|
203
|
+
server_ready = True
|
|
204
|
+
if server_ready:
|
|
205
|
+
logger.info(f"Server ready indicator '{indicator}' found in logs")
|
|
206
|
+
break
|
|
207
|
+
|
|
208
|
+
time.sleep(2)
|
|
209
|
+
|
|
210
|
+
if not server_ready:
|
|
211
|
+
raise TimeoutError(f"Server did not become ready within {timeout} seconds")
|
|
212
|
+
|
|
213
|
+
# Build base URL using config's suffix method
|
|
214
|
+
base_url = (
|
|
215
|
+
f"http://localhost:{config.docker_port}{config.get_base_url_suffix()}"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
logger.info(f"{log_prefix} server ready at {base_url}")
|
|
219
|
+
|
|
220
|
+
yield base_url, container
|
|
221
|
+
|
|
222
|
+
finally:
|
|
223
|
+
if cleanup and container:
|
|
224
|
+
logger.info(f"Stopping container {container.short_id}")
|
|
225
|
+
container.stop(timeout=10)
|
|
226
|
+
logger.info("Container stopped")
|
|
@@ -1,45 +1,28 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from typing import Callable
|
|
3
2
|
|
|
4
|
-
from
|
|
5
|
-
from pydantic import BaseModel, Field
|
|
3
|
+
from pydantic import Field
|
|
6
4
|
|
|
7
|
-
from .
|
|
5
|
+
from .base_server import BaseServer, BaseServerConfig
|
|
6
|
+
from .docker_run_deployment import docker_server
|
|
8
7
|
|
|
9
8
|
|
|
10
|
-
class DockerServerConfig(
|
|
11
|
-
"""
|
|
9
|
+
class DockerServerConfig(BaseServerConfig):
|
|
10
|
+
"""Configuration for deploying a Docker server.
|
|
11
|
+
|
|
12
|
+
Inherits from BaseServerConfig which provides common server configuration.
|
|
13
|
+
"""
|
|
12
14
|
|
|
13
|
-
model_name: str
|
|
14
15
|
docker_image: str
|
|
15
16
|
dockerfile_dir: str | None = None
|
|
16
17
|
command_args: list[str] = Field(default_factory=list)
|
|
17
|
-
server_ready_indicators: list[str] = Field(
|
|
18
|
-
default_factory=lambda: [
|
|
19
|
-
"Application startup complete",
|
|
20
|
-
"Uvicorn running",
|
|
21
|
-
"Starting vLLM API server",
|
|
22
|
-
]
|
|
23
|
-
)
|
|
24
|
-
docker_port: int = 8056
|
|
25
|
-
gpu_device_ids: list[str] | None = None
|
|
26
|
-
container_port: int = 8000
|
|
27
|
-
environment: dict[str, str] = Field(default_factory=dict)
|
|
28
18
|
volumes: dict[str, dict] | None = None
|
|
29
19
|
entrypoint: str | None = None
|
|
30
|
-
aliases: list[str] = Field(default_factory=list)
|
|
31
|
-
|
|
32
|
-
class Config:
|
|
33
|
-
extra = "allow"
|
|
34
20
|
|
|
35
21
|
@property
|
|
36
22
|
def client_config(self):
|
|
37
23
|
"""Override in subclasses to return appropriate client config."""
|
|
38
24
|
raise NotImplementedError
|
|
39
25
|
|
|
40
|
-
def get_client(self, **kwargs):
|
|
41
|
-
return self.client_config.get_client(**kwargs)
|
|
42
|
-
|
|
43
26
|
def get_server(self, auto_stop: bool = True):
|
|
44
27
|
return ConverterServer(config=self, auto_stop=auto_stop)
|
|
45
28
|
|
|
@@ -64,14 +47,6 @@ class DockerServerConfig(BaseModel):
|
|
|
64
47
|
"""Setup volumes for container. Override in subclasses for specific logic."""
|
|
65
48
|
return self.volumes
|
|
66
49
|
|
|
67
|
-
def get_environment(self) -> dict | None:
|
|
68
|
-
"""Setup environment variables. Override in subclasses for specific logic."""
|
|
69
|
-
return self.environment if self.environment else None
|
|
70
|
-
|
|
71
|
-
def get_base_url_suffix(self) -> str:
|
|
72
|
-
"""Return URL suffix (e.g., '/v1' for OpenAI-compatible APIs). Override in subclasses."""
|
|
73
|
-
return ""
|
|
74
|
-
|
|
75
50
|
|
|
76
51
|
DEFAULT_MODEL_NAME = "vllm-model"
|
|
77
52
|
|
|
@@ -84,22 +59,16 @@ class VLLMDockerServerConfig(DockerServerConfig):
|
|
|
84
59
|
hf_home_folder: str | None = os.getenv("HF_HOME", None)
|
|
85
60
|
add_model_key_to_server: bool = False
|
|
86
61
|
container_port: int = 8000
|
|
87
|
-
aliases: list[str] = Field(default_factory=list)
|
|
88
|
-
|
|
89
|
-
@property
|
|
90
|
-
def llm_params(self):
|
|
91
|
-
from vlmparse.clients.openai_converter import LLMParams
|
|
92
|
-
|
|
93
|
-
return LLMParams(
|
|
94
|
-
base_url=f"http://localhost:{self.docker_port}{self.get_base_url_suffix()}",
|
|
95
|
-
model_name=self.default_model_name,
|
|
96
|
-
)
|
|
97
62
|
|
|
98
63
|
@property
|
|
99
64
|
def client_config(self):
|
|
100
65
|
from vlmparse.clients.openai_converter import OpenAIConverterConfig
|
|
101
66
|
|
|
102
|
-
return OpenAIConverterConfig(
|
|
67
|
+
return OpenAIConverterConfig(
|
|
68
|
+
**self._create_client_kwargs(
|
|
69
|
+
f"http://localhost:{self.docker_port}{self.get_base_url_suffix()}"
|
|
70
|
+
)
|
|
71
|
+
)
|
|
103
72
|
|
|
104
73
|
def get_command(self) -> list[str]:
|
|
105
74
|
"""Build VLLM-specific command."""
|
|
@@ -143,70 +112,9 @@ class VLLMDockerServerConfig(DockerServerConfig):
|
|
|
143
112
|
return "/v1"
|
|
144
113
|
|
|
145
114
|
|
|
146
|
-
class ConverterServer:
|
|
115
|
+
class ConverterServer(BaseServer):
|
|
147
116
|
"""Manages Docker server lifecycle with start/stop methods."""
|
|
148
117
|
|
|
149
|
-
def
|
|
150
|
-
|
|
151
|
-
self.
|
|
152
|
-
self._server_context = None
|
|
153
|
-
self._container = None
|
|
154
|
-
self.base_url = None
|
|
155
|
-
|
|
156
|
-
def start(self):
|
|
157
|
-
"""Start the Docker server."""
|
|
158
|
-
if self._server_context is not None:
|
|
159
|
-
logger.warning("Server already started")
|
|
160
|
-
return self.base_url, self._container
|
|
161
|
-
|
|
162
|
-
# Use the generic docker_server for all server types
|
|
163
|
-
self._server_context = docker_server(config=self.config, cleanup=self.auto_stop)
|
|
164
|
-
|
|
165
|
-
self.base_url, self._container = self._server_context.__enter__()
|
|
166
|
-
logger.info(f"Server started at {self.base_url}")
|
|
167
|
-
logger.info(f"Container ID: {self._container.id}")
|
|
168
|
-
logger.info(f"Container name: {self._container.name}")
|
|
169
|
-
return self.base_url, self._container
|
|
170
|
-
|
|
171
|
-
def stop(self):
|
|
172
|
-
"""Stop the Docker server."""
|
|
173
|
-
if self._server_context is not None:
|
|
174
|
-
self._server_context.__exit__(None, None, None)
|
|
175
|
-
self._server_context = None
|
|
176
|
-
self._container = None
|
|
177
|
-
self.base_url = None
|
|
178
|
-
logger.info("Server stopped")
|
|
179
|
-
|
|
180
|
-
def __del__(self):
|
|
181
|
-
"""Automatically stop server when object is destroyed if auto_stop is True."""
|
|
182
|
-
if self.auto_stop and self._server_context is not None:
|
|
183
|
-
self.stop()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
class DockerConfigRegistry:
|
|
187
|
-
"""Registry for mapping model names to their Docker configurations."""
|
|
188
|
-
|
|
189
|
-
def __init__(self):
|
|
190
|
-
self._registry = dict()
|
|
191
|
-
|
|
192
|
-
def register(
|
|
193
|
-
self, model_name: str, config_factory: Callable[[], DockerServerConfig | None]
|
|
194
|
-
):
|
|
195
|
-
"""Register a config factory for a model name."""
|
|
196
|
-
self._registry[model_name] = config_factory
|
|
197
|
-
|
|
198
|
-
def get(self, model_name: str, default=False) -> DockerServerConfig | None:
|
|
199
|
-
"""Get config for a model name. Returns default if not registered."""
|
|
200
|
-
if model_name not in self._registry:
|
|
201
|
-
if default:
|
|
202
|
-
return VLLMDockerServerConfig(model_name=model_name)
|
|
203
|
-
return None
|
|
204
|
-
return self._registry[model_name]()
|
|
205
|
-
|
|
206
|
-
def list_models(self) -> list[str]:
|
|
207
|
-
"""List all registered model names."""
|
|
208
|
-
return list(self._registry.keys())
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
# Global registry instance
|
|
212
|
-
docker_config_registry = DockerConfigRegistry()
|
|
118
|
+
def _create_server_context(self):
|
|
119
|
+
"""Create the Docker server context."""
|
|
120
|
+
return docker_server(config=self.config, cleanup=self.auto_stop)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Model identity mixin for consistent model name handling between server and client configs."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ModelIdentityMixin(BaseModel):
|
|
7
|
+
"""Mixin providing model identity fields with validation.
|
|
8
|
+
|
|
9
|
+
This mixin ensures that model_name and default_model_name are consistently
|
|
10
|
+
passed from server configs to client configs.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
model_name: str
|
|
14
|
+
default_model_name: str | None = None
|
|
15
|
+
aliases: list[str] = Field(default_factory=list)
|
|
16
|
+
|
|
17
|
+
def get_effective_model_name(self) -> str:
|
|
18
|
+
"""Returns the model name to use for API calls."""
|
|
19
|
+
return self.default_model_name if self.default_model_name else self.model_name
|
|
20
|
+
|
|
21
|
+
def _create_client_kwargs(self, base_url: str) -> dict:
|
|
22
|
+
"""Generate kwargs for client config with model identity.
|
|
23
|
+
|
|
24
|
+
Use this method in server configs to ensure consistent passing
|
|
25
|
+
of model_name and default_model_name to client configs.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
base_url: The base URL for the client to connect to.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dictionary with base_url, model_name, and default_model_name.
|
|
32
|
+
"""
|
|
33
|
+
return {
|
|
34
|
+
"base_url": base_url,
|
|
35
|
+
"model_name": self.model_name,
|
|
36
|
+
"default_model_name": self.get_effective_model_name(),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def get_all_names(self) -> list[str]:
|
|
40
|
+
"""Get all names this model can be referenced by.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
List containing model_name, aliases, and short name (after last /).
|
|
44
|
+
"""
|
|
45
|
+
names = [self.model_name] + self.aliases
|
|
46
|
+
if "/" in self.model_name:
|
|
47
|
+
names.append(self.model_name.split("/")[-1])
|
|
48
|
+
return [n for n in names if isinstance(n, str)]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
from vlmparse.servers.base_server import BaseServerConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DockerConfigRegistry:
|
|
7
|
+
"""Registry for mapping model names to their Docker configurations.
|
|
8
|
+
|
|
9
|
+
Thread-safe registry that maps model names to their Docker configuration factories.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
import threading
|
|
14
|
+
|
|
15
|
+
self._registry: dict[str, Callable[[], BaseServerConfig | None]] = {}
|
|
16
|
+
self._lock = threading.RLock()
|
|
17
|
+
|
|
18
|
+
def register(
|
|
19
|
+
self,
|
|
20
|
+
model_name: str,
|
|
21
|
+
config_factory: Callable[[], BaseServerConfig | None],
|
|
22
|
+
):
|
|
23
|
+
"""Register a config factory for a model name (thread-safe)."""
|
|
24
|
+
with self._lock:
|
|
25
|
+
self._registry[model_name] = config_factory
|
|
26
|
+
|
|
27
|
+
def get(self, model_name: str) -> BaseServerConfig | None:
|
|
28
|
+
"""Get config for a model name (thread-safe). Returns None if not registered."""
|
|
29
|
+
with self._lock:
|
|
30
|
+
if model_name not in self._registry:
|
|
31
|
+
return None
|
|
32
|
+
factory = self._registry[model_name]
|
|
33
|
+
return factory()
|
|
34
|
+
|
|
35
|
+
def list_models(self) -> list[str]:
|
|
36
|
+
"""List all registered model names (thread-safe)."""
|
|
37
|
+
with self._lock:
|
|
38
|
+
return list(self._registry.keys())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Global registry instance
|
|
42
|
+
docker_config_registry = DockerConfigRegistry()
|