vlmparse 0.1.8__py3-none-any.whl → 0.1.10__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.
@@ -14,25 +14,39 @@ def start_server(
14
14
  model: str,
15
15
  gpus: str,
16
16
  port: None | int = None,
17
- with_vllm_server: bool = True,
17
+ provider: 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, default=with_vllm_server)
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
- logger.warning(
33
- f"No Docker configuration found for model: {model}, using default configuration"
34
- )
35
- return "", container, None, docker_config
36
+ if provider == "registry":
37
+ print(f"DEBUG: Registry lookup failed for {model} (strict mode)")
38
+ raise ValueError(
39
+ f"Model '{model}' not found in registry and provider='registry'. Use provider='hf' to serve arbitrary HuggingFace models."
40
+ )
41
+ elif provider == "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,22 +56,23 @@ 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.update_command_args(
46
- vllm_args,
47
- forget_predefined_vllm_args=forget_predefined_vllm_args,
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 VLLM server for {docker_config.model_name} on port {port}..."
66
+ f"Deploying server for {docker_config.model_name} on port {port}..."
52
67
  )
53
- server = docker_config.get_server(auto_stop=auto_stop)
54
- if server is None:
68
+ provider = docker_config.get_server(auto_stop=auto_stop)
69
+ if provider is None:
55
70
  logger.error(f"Model server not found for model: {model}")
56
71
  return "", container, None, docker_config
57
72
 
58
- base_url, container = server.start()
73
+ base_url, container = provider.start()
59
74
 
60
- return base_url, container, server, docker_config
75
+ return base_url, container, provider, docker_config
61
76
 
62
77
 
63
78
  class ConverterWithServer:
@@ -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
- with_vllm_server: bool = False,
85
+ provider: 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.with_vllm_server = with_vllm_server
101
+ self.provider = provider
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.registries import converter_config_registry
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.provider == "hf":
122
+ start_local_server = True
123
+ elif self.provider == "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.provider == "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
- with_vllm_server=self.with_vllm_server,
133
+ provider=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.provider == "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 DeepSeekOCRDockerServerConfig
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.docker_server import DockerServerConfig, docker_config_registry
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(self, model_name: str, uri: str | None = None) -> ConverterConfig:
108
- """Get config for a model name (thread-safe). Returns default if not registered."""
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
- # Fallback to OpenAIConverterConfig for unregistered models
115
- if uri is not None:
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