cmdop-llm 0.1.0__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.
- cmdop_llm/__init__.py +89 -0
- cmdop_llm/_client.py +217 -0
- cmdop_llm/_constants.py +12 -0
- cmdop_llm/_version.py +6 -0
- cmdop_llm/py.typed +1 -0
- cmdop_llm/resources/__init__.py +11 -0
- cmdop_llm/resources/ocr.py +180 -0
- cmdop_llm/resources/vision.py +186 -0
- cmdop_llm/types/__init__.py +11 -0
- cmdop_llm/types/ocr.py +50 -0
- cmdop_llm/types/vision.py +59 -0
- cmdop_llm-0.1.0.dist-info/METADATA +198 -0
- cmdop_llm-0.1.0.dist-info/RECORD +15 -0
- cmdop_llm-0.1.0.dist-info/WHEEL +4 -0
- cmdop_llm-0.1.0.dist-info/licenses/LICENSE +21 -0
cmdop_llm/__init__.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""CMDOP LLM Python SDK.
|
|
2
|
+
|
|
3
|
+
A drop-in replacement for OpenAI Python SDK that connects to llm.cmdop.com.
|
|
4
|
+
Provides access to 200+ AI models via OpenRouter, plus custom vision/OCR endpoints.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
```python
|
|
8
|
+
from cmdop_llm import CmdopLLM
|
|
9
|
+
|
|
10
|
+
client = CmdopLLM(api_key="your-api-key")
|
|
11
|
+
|
|
12
|
+
# Chat completion (same as OpenAI)
|
|
13
|
+
response = client.chat.completions.create(
|
|
14
|
+
model="anthropic/claude-3.5-sonnet",
|
|
15
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Vision analysis (CMDOP-specific)
|
|
19
|
+
result = client.vision.analyze(image_url="https://...")
|
|
20
|
+
|
|
21
|
+
# OCR (CMDOP-specific)
|
|
22
|
+
result = client.ocr.extract(image_url="https://...")
|
|
23
|
+
```
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from openai import (
|
|
27
|
+
APIConnectionError,
|
|
28
|
+
APIError,
|
|
29
|
+
APIStatusError,
|
|
30
|
+
APITimeoutError,
|
|
31
|
+
AsyncStream,
|
|
32
|
+
AuthenticationError,
|
|
33
|
+
BadRequestError,
|
|
34
|
+
ConflictError,
|
|
35
|
+
InternalServerError,
|
|
36
|
+
NotFoundError,
|
|
37
|
+
OpenAIError,
|
|
38
|
+
PermissionDeniedError,
|
|
39
|
+
RateLimitError,
|
|
40
|
+
Stream,
|
|
41
|
+
UnprocessableEntityError,
|
|
42
|
+
)
|
|
43
|
+
from openai._types import NOT_GIVEN, NotGiven
|
|
44
|
+
|
|
45
|
+
from ._client import AsyncClient, AsyncCmdopLLM, Client, CmdopLLM
|
|
46
|
+
from ._version import __author__, __email__, __title__, __version__
|
|
47
|
+
from .resources import AsyncOCR, AsyncVision, OCR, Vision
|
|
48
|
+
from .types import OCRRequest, OCRResponse, VisionAnalyzeRequest, VisionAnalyzeResponse
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
# Version info
|
|
52
|
+
"__version__",
|
|
53
|
+
"__title__",
|
|
54
|
+
"__author__",
|
|
55
|
+
"__email__",
|
|
56
|
+
# Main clients
|
|
57
|
+
"CmdopLLM",
|
|
58
|
+
"AsyncCmdopLLM",
|
|
59
|
+
"Client",
|
|
60
|
+
"AsyncClient",
|
|
61
|
+
# Custom resources
|
|
62
|
+
"Vision",
|
|
63
|
+
"AsyncVision",
|
|
64
|
+
"OCR",
|
|
65
|
+
"AsyncOCR",
|
|
66
|
+
# Custom types
|
|
67
|
+
"VisionAnalyzeRequest",
|
|
68
|
+
"VisionAnalyzeResponse",
|
|
69
|
+
"OCRRequest",
|
|
70
|
+
"OCRResponse",
|
|
71
|
+
# Re-exported from OpenAI
|
|
72
|
+
"OpenAIError",
|
|
73
|
+
"APIError",
|
|
74
|
+
"APIStatusError",
|
|
75
|
+
"APIConnectionError",
|
|
76
|
+
"APITimeoutError",
|
|
77
|
+
"AuthenticationError",
|
|
78
|
+
"BadRequestError",
|
|
79
|
+
"ConflictError",
|
|
80
|
+
"NotFoundError",
|
|
81
|
+
"PermissionDeniedError",
|
|
82
|
+
"RateLimitError",
|
|
83
|
+
"UnprocessableEntityError",
|
|
84
|
+
"InternalServerError",
|
|
85
|
+
"Stream",
|
|
86
|
+
"AsyncStream",
|
|
87
|
+
"NOT_GIVEN",
|
|
88
|
+
"NotGiven",
|
|
89
|
+
]
|
cmdop_llm/_client.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""Main client classes for CMDOP LLM service."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Mapping, Optional, Union
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from openai import AsyncOpenAI, OpenAI
|
|
10
|
+
from openai._compat import cached_property
|
|
11
|
+
from openai._types import NOT_GIVEN, NotGiven, Timeout
|
|
12
|
+
|
|
13
|
+
from ._constants import DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, ENV_API_KEY, ENV_BASE_URL
|
|
14
|
+
from .resources.ocr import AsyncOCR, OCR
|
|
15
|
+
from .resources.vision import AsyncVision, Vision
|
|
16
|
+
|
|
17
|
+
__all__ = ["CmdopLLM", "AsyncCmdopLLM"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CmdopLLM(OpenAI):
|
|
21
|
+
"""Synchronous client for CMDOP LLM service.
|
|
22
|
+
|
|
23
|
+
A drop-in replacement for OpenAI client that connects to llm.cmdop.com.
|
|
24
|
+
Provides access to 200+ AI models via OpenRouter, plus custom vision/OCR endpoints.
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
```python
|
|
28
|
+
from cmdop_llm import CmdopLLM
|
|
29
|
+
|
|
30
|
+
# Initialize with API key
|
|
31
|
+
client = CmdopLLM(api_key="your-cmdop-api-key")
|
|
32
|
+
|
|
33
|
+
# Or use CMDOP_API_KEY environment variable
|
|
34
|
+
client = CmdopLLM()
|
|
35
|
+
|
|
36
|
+
# Chat completion (same as OpenAI)
|
|
37
|
+
response = client.chat.completions.create(
|
|
38
|
+
model="anthropic/claude-3.5-sonnet",
|
|
39
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
40
|
+
)
|
|
41
|
+
print(response.choices[0].message.content)
|
|
42
|
+
|
|
43
|
+
# Vision analysis (CMDOP-specific)
|
|
44
|
+
result = client.vision.analyze(
|
|
45
|
+
image_url="https://example.com/image.jpg"
|
|
46
|
+
)
|
|
47
|
+
print(result.description)
|
|
48
|
+
|
|
49
|
+
# OCR text extraction (CMDOP-specific)
|
|
50
|
+
result = client.ocr.extract(
|
|
51
|
+
image_url="https://example.com/document.png"
|
|
52
|
+
)
|
|
53
|
+
print(result.text)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
api_key: CMDOP API key. If not provided, reads from CMDOP_API_KEY env var.
|
|
58
|
+
base_url: Override the default base URL (https://llm.cmdop.com).
|
|
59
|
+
timeout: Request timeout in seconds or httpx.Timeout object.
|
|
60
|
+
max_retries: Maximum number of retries for failed requests.
|
|
61
|
+
default_headers: Headers to include in all requests.
|
|
62
|
+
default_query: Query parameters to include in all requests.
|
|
63
|
+
http_client: Custom httpx.Client instance.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
*,
|
|
69
|
+
api_key: Optional[str] = None,
|
|
70
|
+
base_url: Union[str, httpx.URL, None] = None,
|
|
71
|
+
timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
|
|
72
|
+
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
73
|
+
default_headers: Optional[Mapping[str, str]] = None,
|
|
74
|
+
default_query: Optional[Mapping[str, object]] = None,
|
|
75
|
+
http_client: Optional[httpx.Client] = None,
|
|
76
|
+
) -> None:
|
|
77
|
+
# Get API key from environment if not provided
|
|
78
|
+
if api_key is None:
|
|
79
|
+
api_key = os.environ.get(ENV_API_KEY)
|
|
80
|
+
if api_key is None:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"API key is required. Set {ENV_API_KEY} environment variable "
|
|
83
|
+
"or pass api_key parameter."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Get base URL from environment or use default
|
|
87
|
+
if base_url is None:
|
|
88
|
+
base_url = os.environ.get(ENV_BASE_URL, DEFAULT_BASE_URL)
|
|
89
|
+
|
|
90
|
+
super().__init__(
|
|
91
|
+
api_key=api_key,
|
|
92
|
+
base_url=base_url,
|
|
93
|
+
timeout=timeout,
|
|
94
|
+
max_retries=max_retries,
|
|
95
|
+
default_headers=default_headers,
|
|
96
|
+
default_query=default_query,
|
|
97
|
+
http_client=http_client,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@cached_property
|
|
101
|
+
def vision(self) -> Vision:
|
|
102
|
+
"""Access vision analysis endpoints.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Vision resource for image analysis.
|
|
106
|
+
"""
|
|
107
|
+
return Vision(self)
|
|
108
|
+
|
|
109
|
+
@cached_property
|
|
110
|
+
def ocr(self) -> OCR:
|
|
111
|
+
"""Access OCR text extraction endpoints.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
OCR resource for text extraction from images.
|
|
115
|
+
"""
|
|
116
|
+
return OCR(self)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class AsyncCmdopLLM(AsyncOpenAI):
|
|
120
|
+
"""Asynchronous client for CMDOP LLM service.
|
|
121
|
+
|
|
122
|
+
Async version of CmdopLLM for use with asyncio.
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
```python
|
|
126
|
+
import asyncio
|
|
127
|
+
from cmdop_llm import AsyncCmdopLLM
|
|
128
|
+
|
|
129
|
+
async def main():
|
|
130
|
+
client = AsyncCmdopLLM()
|
|
131
|
+
|
|
132
|
+
# Chat completion
|
|
133
|
+
response = await client.chat.completions.create(
|
|
134
|
+
model="openai/gpt-4o",
|
|
135
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
136
|
+
)
|
|
137
|
+
print(response.choices[0].message.content)
|
|
138
|
+
|
|
139
|
+
# Streaming
|
|
140
|
+
stream = await client.chat.completions.create(
|
|
141
|
+
model="openai/gpt-4o",
|
|
142
|
+
messages=[{"role": "user", "content": "Write a poem"}],
|
|
143
|
+
stream=True
|
|
144
|
+
)
|
|
145
|
+
async for chunk in stream:
|
|
146
|
+
if chunk.choices[0].delta.content:
|
|
147
|
+
print(chunk.choices[0].delta.content, end="")
|
|
148
|
+
|
|
149
|
+
asyncio.run(main())
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
api_key: CMDOP API key. If not provided, reads from CMDOP_API_KEY env var.
|
|
154
|
+
base_url: Override the default base URL (https://llm.cmdop.com).
|
|
155
|
+
timeout: Request timeout in seconds or httpx.Timeout object.
|
|
156
|
+
max_retries: Maximum number of retries for failed requests.
|
|
157
|
+
default_headers: Headers to include in all requests.
|
|
158
|
+
default_query: Query parameters to include in all requests.
|
|
159
|
+
http_client: Custom httpx.AsyncClient instance.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
*,
|
|
165
|
+
api_key: Optional[str] = None,
|
|
166
|
+
base_url: Union[str, httpx.URL, None] = None,
|
|
167
|
+
timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN,
|
|
168
|
+
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
169
|
+
default_headers: Optional[Mapping[str, str]] = None,
|
|
170
|
+
default_query: Optional[Mapping[str, object]] = None,
|
|
171
|
+
http_client: Optional[httpx.AsyncClient] = None,
|
|
172
|
+
) -> None:
|
|
173
|
+
# Get API key from environment if not provided
|
|
174
|
+
if api_key is None:
|
|
175
|
+
api_key = os.environ.get(ENV_API_KEY)
|
|
176
|
+
if api_key is None:
|
|
177
|
+
raise ValueError(
|
|
178
|
+
f"API key is required. Set {ENV_API_KEY} environment variable "
|
|
179
|
+
"or pass api_key parameter."
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Get base URL from environment or use default
|
|
183
|
+
if base_url is None:
|
|
184
|
+
base_url = os.environ.get(ENV_BASE_URL, DEFAULT_BASE_URL)
|
|
185
|
+
|
|
186
|
+
super().__init__(
|
|
187
|
+
api_key=api_key,
|
|
188
|
+
base_url=base_url,
|
|
189
|
+
timeout=timeout,
|
|
190
|
+
max_retries=max_retries,
|
|
191
|
+
default_headers=default_headers,
|
|
192
|
+
default_query=default_query,
|
|
193
|
+
http_client=http_client,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
@cached_property
|
|
197
|
+
def vision(self) -> AsyncVision:
|
|
198
|
+
"""Access vision analysis endpoints.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
AsyncVision resource for image analysis.
|
|
202
|
+
"""
|
|
203
|
+
return AsyncVision(self)
|
|
204
|
+
|
|
205
|
+
@cached_property
|
|
206
|
+
def ocr(self) -> AsyncOCR:
|
|
207
|
+
"""Access OCR text extraction endpoints.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
AsyncOCR resource for text extraction from images.
|
|
211
|
+
"""
|
|
212
|
+
return AsyncOCR(self)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# Aliases for convenience
|
|
216
|
+
Client = CmdopLLM
|
|
217
|
+
AsyncClient = AsyncCmdopLLM
|
cmdop_llm/_constants.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Constants for cmdop-llm package."""
|
|
2
|
+
|
|
3
|
+
# Default base URL for CMDOP LLM service
|
|
4
|
+
DEFAULT_BASE_URL = "https://llm.cmdop.com"
|
|
5
|
+
|
|
6
|
+
# Environment variable names
|
|
7
|
+
ENV_API_KEY = "CMDOP_API_KEY"
|
|
8
|
+
ENV_BASE_URL = "CMDOP_BASE_URL"
|
|
9
|
+
|
|
10
|
+
# Default timeout settings (seconds)
|
|
11
|
+
DEFAULT_TIMEOUT = 60.0
|
|
12
|
+
DEFAULT_MAX_RETRIES = 2
|
cmdop_llm/_version.py
ADDED
cmdop_llm/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# PEP 561 marker file - this package supports type checking
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""OCR extraction resource for CMDOP LLM service."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Literal, Optional, Union
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from openai._compat import cached_property
|
|
9
|
+
from openai._resource import AsyncAPIResource, SyncAPIResource
|
|
10
|
+
from openai._types import NOT_GIVEN, Body, Headers, NotGiven, Query
|
|
11
|
+
|
|
12
|
+
from ..types.ocr import OCRResponse
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from .._client import AsyncCmdopLLM, CmdopLLM
|
|
16
|
+
|
|
17
|
+
__all__ = ["OCR", "AsyncOCR"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class OCR(SyncAPIResource):
|
|
21
|
+
"""OCR text extraction resource.
|
|
22
|
+
|
|
23
|
+
Provides text extraction from images using vision models.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
```python
|
|
27
|
+
from cmdop_llm import CmdopLLM
|
|
28
|
+
|
|
29
|
+
client = CmdopLLM()
|
|
30
|
+
result = client.ocr.extract(
|
|
31
|
+
image_url="https://example.com/document.png"
|
|
32
|
+
)
|
|
33
|
+
print(result.text)
|
|
34
|
+
```
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
_client: CmdopLLM
|
|
38
|
+
|
|
39
|
+
@cached_property
|
|
40
|
+
def with_raw_response(self) -> OCRWithRawResponse:
|
|
41
|
+
"""Access raw HTTP response data."""
|
|
42
|
+
return OCRWithRawResponse(self)
|
|
43
|
+
|
|
44
|
+
def extract(
|
|
45
|
+
self,
|
|
46
|
+
*,
|
|
47
|
+
image: Optional[str] = None,
|
|
48
|
+
image_url: Optional[str] = None,
|
|
49
|
+
model: Optional[str] = None,
|
|
50
|
+
mode: Literal["tiny", "small", "base", "gundam"] = "base",
|
|
51
|
+
fetch_image: bool = True,
|
|
52
|
+
extra_headers: Headers | None = None,
|
|
53
|
+
extra_query: Query | None = None,
|
|
54
|
+
extra_body: Body | None = None,
|
|
55
|
+
timeout: Union[float, httpx.Timeout, None, NotGiven] = NOT_GIVEN,
|
|
56
|
+
) -> OCRResponse:
|
|
57
|
+
"""Extract text from an image using OCR.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
image: Base64-encoded image data.
|
|
61
|
+
image_url: URL of the image to extract text from.
|
|
62
|
+
model: Model to use. Defaults to server default.
|
|
63
|
+
mode: OCR quality mode ("tiny", "small", "base", "gundam").
|
|
64
|
+
fetch_image: Whether server should fetch image from URL.
|
|
65
|
+
extra_headers: Additional headers to send.
|
|
66
|
+
extra_query: Additional query parameters.
|
|
67
|
+
extra_body: Additional body parameters.
|
|
68
|
+
timeout: Request timeout.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
OCRResponse with extracted text.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ValueError: If neither image nor image_url is provided.
|
|
75
|
+
openai.APIError: If the request fails.
|
|
76
|
+
"""
|
|
77
|
+
if not image and not image_url:
|
|
78
|
+
raise ValueError("Either 'image' or 'image_url' must be provided")
|
|
79
|
+
|
|
80
|
+
body = {
|
|
81
|
+
"image": image,
|
|
82
|
+
"image_url": image_url,
|
|
83
|
+
"model": model,
|
|
84
|
+
"mode": mode,
|
|
85
|
+
"fetch_image": fetch_image,
|
|
86
|
+
}
|
|
87
|
+
# Remove None values
|
|
88
|
+
body = {k: v for k, v in body.items() if v is not None}
|
|
89
|
+
|
|
90
|
+
return self._post(
|
|
91
|
+
"/vision/ocr",
|
|
92
|
+
body=body,
|
|
93
|
+
options={
|
|
94
|
+
"extra_headers": extra_headers,
|
|
95
|
+
"extra_query": extra_query,
|
|
96
|
+
"extra_body": extra_body,
|
|
97
|
+
"timeout": timeout,
|
|
98
|
+
},
|
|
99
|
+
cast_to=OCRResponse,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class AsyncOCR(AsyncAPIResource):
|
|
104
|
+
"""Async OCR text extraction resource."""
|
|
105
|
+
|
|
106
|
+
_client: AsyncCmdopLLM
|
|
107
|
+
|
|
108
|
+
@cached_property
|
|
109
|
+
def with_raw_response(self) -> AsyncOCRWithRawResponse:
|
|
110
|
+
"""Access raw HTTP response data."""
|
|
111
|
+
return AsyncOCRWithRawResponse(self)
|
|
112
|
+
|
|
113
|
+
async def extract(
|
|
114
|
+
self,
|
|
115
|
+
*,
|
|
116
|
+
image: Optional[str] = None,
|
|
117
|
+
image_url: Optional[str] = None,
|
|
118
|
+
model: Optional[str] = None,
|
|
119
|
+
mode: Literal["tiny", "small", "base", "gundam"] = "base",
|
|
120
|
+
fetch_image: bool = True,
|
|
121
|
+
extra_headers: Headers | None = None,
|
|
122
|
+
extra_query: Query | None = None,
|
|
123
|
+
extra_body: Body | None = None,
|
|
124
|
+
timeout: Union[float, httpx.Timeout, None, NotGiven] = NOT_GIVEN,
|
|
125
|
+
) -> OCRResponse:
|
|
126
|
+
"""Extract text from an image using OCR (async).
|
|
127
|
+
|
|
128
|
+
See OCR.extract for full documentation.
|
|
129
|
+
"""
|
|
130
|
+
if not image and not image_url:
|
|
131
|
+
raise ValueError("Either 'image' or 'image_url' must be provided")
|
|
132
|
+
|
|
133
|
+
body = {
|
|
134
|
+
"image": image,
|
|
135
|
+
"image_url": image_url,
|
|
136
|
+
"model": model,
|
|
137
|
+
"mode": mode,
|
|
138
|
+
"fetch_image": fetch_image,
|
|
139
|
+
}
|
|
140
|
+
body = {k: v for k, v in body.items() if v is not None}
|
|
141
|
+
|
|
142
|
+
return await self._post(
|
|
143
|
+
"/vision/ocr",
|
|
144
|
+
body=body,
|
|
145
|
+
options={
|
|
146
|
+
"extra_headers": extra_headers,
|
|
147
|
+
"extra_query": extra_query,
|
|
148
|
+
"extra_body": extra_body,
|
|
149
|
+
"timeout": timeout,
|
|
150
|
+
},
|
|
151
|
+
cast_to=OCRResponse,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class OCRWithRawResponse:
|
|
156
|
+
"""Raw response wrapper for OCR resource."""
|
|
157
|
+
|
|
158
|
+
def __init__(self, ocr: OCR) -> None:
|
|
159
|
+
self._ocr = ocr
|
|
160
|
+
|
|
161
|
+
def extract(self, **kwargs) -> httpx.Response: # type: ignore
|
|
162
|
+
"""Get raw response from extract."""
|
|
163
|
+
return self._ocr._client.with_raw_response._post( # type: ignore
|
|
164
|
+
"/vision/ocr",
|
|
165
|
+
body=kwargs,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class AsyncOCRWithRawResponse:
|
|
170
|
+
"""Raw response wrapper for AsyncOCR resource."""
|
|
171
|
+
|
|
172
|
+
def __init__(self, ocr: AsyncOCR) -> None:
|
|
173
|
+
self._ocr = ocr
|
|
174
|
+
|
|
175
|
+
async def extract(self, **kwargs) -> httpx.Response: # type: ignore
|
|
176
|
+
"""Get raw response from extract."""
|
|
177
|
+
return await self._ocr._client.with_raw_response._post( # type: ignore
|
|
178
|
+
"/vision/ocr",
|
|
179
|
+
body=kwargs,
|
|
180
|
+
)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Vision analysis resource for CMDOP LLM service."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Literal, Optional, Union
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from openai._compat import cached_property
|
|
9
|
+
from openai._resource import AsyncAPIResource, SyncAPIResource
|
|
10
|
+
from openai._types import NOT_GIVEN, Body, Headers, NotGiven, Query
|
|
11
|
+
|
|
12
|
+
from ..types.vision import VisionAnalyzeResponse
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from .._client import AsyncCmdopLLM, CmdopLLM
|
|
16
|
+
|
|
17
|
+
__all__ = ["Vision", "AsyncVision"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Vision(SyncAPIResource):
|
|
21
|
+
"""Vision analysis resource.
|
|
22
|
+
|
|
23
|
+
Provides image analysis capabilities using vision models.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
```python
|
|
27
|
+
from cmdop_llm import CmdopLLM
|
|
28
|
+
|
|
29
|
+
client = CmdopLLM()
|
|
30
|
+
result = client.vision.analyze(
|
|
31
|
+
image_url="https://example.com/image.jpg",
|
|
32
|
+
prompt="Describe this image"
|
|
33
|
+
)
|
|
34
|
+
print(result.description)
|
|
35
|
+
```
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
_client: CmdopLLM
|
|
39
|
+
|
|
40
|
+
@cached_property
|
|
41
|
+
def with_raw_response(self) -> VisionWithRawResponse:
|
|
42
|
+
"""Access raw HTTP response data."""
|
|
43
|
+
return VisionWithRawResponse(self)
|
|
44
|
+
|
|
45
|
+
def analyze(
|
|
46
|
+
self,
|
|
47
|
+
*,
|
|
48
|
+
image: Optional[str] = None,
|
|
49
|
+
image_url: Optional[str] = None,
|
|
50
|
+
prompt: Optional[str] = None,
|
|
51
|
+
model: Optional[str] = None,
|
|
52
|
+
ocr_mode: Literal["tiny", "small", "base", "gundam"] = "base",
|
|
53
|
+
fetch_image: bool = True,
|
|
54
|
+
extra_headers: Headers | None = None,
|
|
55
|
+
extra_query: Query | None = None,
|
|
56
|
+
extra_body: Body | None = None,
|
|
57
|
+
timeout: Union[float, httpx.Timeout, None, NotGiven] = NOT_GIVEN,
|
|
58
|
+
) -> VisionAnalyzeResponse:
|
|
59
|
+
"""Analyze an image using a vision model.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
image: Base64-encoded image data.
|
|
63
|
+
image_url: URL of the image to analyze.
|
|
64
|
+
prompt: Custom prompt for the analysis.
|
|
65
|
+
model: Model to use. Defaults to server default.
|
|
66
|
+
ocr_mode: OCR quality mode ("tiny", "small", "base", "gundam").
|
|
67
|
+
fetch_image: Whether server should fetch image from URL.
|
|
68
|
+
extra_headers: Additional headers to send.
|
|
69
|
+
extra_query: Additional query parameters.
|
|
70
|
+
extra_body: Additional body parameters.
|
|
71
|
+
timeout: Request timeout.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
VisionAnalyzeResponse with extracted text and description.
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
ValueError: If neither image nor image_url is provided.
|
|
78
|
+
openai.APIError: If the request fails.
|
|
79
|
+
"""
|
|
80
|
+
if not image and not image_url:
|
|
81
|
+
raise ValueError("Either 'image' or 'image_url' must be provided")
|
|
82
|
+
|
|
83
|
+
body = {
|
|
84
|
+
"image": image,
|
|
85
|
+
"image_url": image_url,
|
|
86
|
+
"prompt": prompt,
|
|
87
|
+
"model": model,
|
|
88
|
+
"ocr_mode": ocr_mode,
|
|
89
|
+
"fetch_image": fetch_image,
|
|
90
|
+
}
|
|
91
|
+
# Remove None values
|
|
92
|
+
body = {k: v for k, v in body.items() if v is not None}
|
|
93
|
+
|
|
94
|
+
return self._post(
|
|
95
|
+
"/vision/analyze",
|
|
96
|
+
body=body,
|
|
97
|
+
options={
|
|
98
|
+
"extra_headers": extra_headers,
|
|
99
|
+
"extra_query": extra_query,
|
|
100
|
+
"extra_body": extra_body,
|
|
101
|
+
"timeout": timeout,
|
|
102
|
+
},
|
|
103
|
+
cast_to=VisionAnalyzeResponse,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class AsyncVision(AsyncAPIResource):
|
|
108
|
+
"""Async vision analysis resource."""
|
|
109
|
+
|
|
110
|
+
_client: AsyncCmdopLLM
|
|
111
|
+
|
|
112
|
+
@cached_property
|
|
113
|
+
def with_raw_response(self) -> AsyncVisionWithRawResponse:
|
|
114
|
+
"""Access raw HTTP response data."""
|
|
115
|
+
return AsyncVisionWithRawResponse(self)
|
|
116
|
+
|
|
117
|
+
async def analyze(
|
|
118
|
+
self,
|
|
119
|
+
*,
|
|
120
|
+
image: Optional[str] = None,
|
|
121
|
+
image_url: Optional[str] = None,
|
|
122
|
+
prompt: Optional[str] = None,
|
|
123
|
+
model: Optional[str] = None,
|
|
124
|
+
ocr_mode: Literal["tiny", "small", "base", "gundam"] = "base",
|
|
125
|
+
fetch_image: bool = True,
|
|
126
|
+
extra_headers: Headers | None = None,
|
|
127
|
+
extra_query: Query | None = None,
|
|
128
|
+
extra_body: Body | None = None,
|
|
129
|
+
timeout: Union[float, httpx.Timeout, None, NotGiven] = NOT_GIVEN,
|
|
130
|
+
) -> VisionAnalyzeResponse:
|
|
131
|
+
"""Analyze an image using a vision model (async).
|
|
132
|
+
|
|
133
|
+
See Vision.analyze for full documentation.
|
|
134
|
+
"""
|
|
135
|
+
if not image and not image_url:
|
|
136
|
+
raise ValueError("Either 'image' or 'image_url' must be provided")
|
|
137
|
+
|
|
138
|
+
body = {
|
|
139
|
+
"image": image,
|
|
140
|
+
"image_url": image_url,
|
|
141
|
+
"prompt": prompt,
|
|
142
|
+
"model": model,
|
|
143
|
+
"ocr_mode": ocr_mode,
|
|
144
|
+
"fetch_image": fetch_image,
|
|
145
|
+
}
|
|
146
|
+
body = {k: v for k, v in body.items() if v is not None}
|
|
147
|
+
|
|
148
|
+
return await self._post(
|
|
149
|
+
"/vision/analyze",
|
|
150
|
+
body=body,
|
|
151
|
+
options={
|
|
152
|
+
"extra_headers": extra_headers,
|
|
153
|
+
"extra_query": extra_query,
|
|
154
|
+
"extra_body": extra_body,
|
|
155
|
+
"timeout": timeout,
|
|
156
|
+
},
|
|
157
|
+
cast_to=VisionAnalyzeResponse,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class VisionWithRawResponse:
|
|
162
|
+
"""Raw response wrapper for Vision resource."""
|
|
163
|
+
|
|
164
|
+
def __init__(self, vision: Vision) -> None:
|
|
165
|
+
self._vision = vision
|
|
166
|
+
|
|
167
|
+
def analyze(self, **kwargs) -> httpx.Response: # type: ignore
|
|
168
|
+
"""Get raw response from analyze."""
|
|
169
|
+
return self._vision._client.with_raw_response._post( # type: ignore
|
|
170
|
+
"/vision/analyze",
|
|
171
|
+
body=kwargs,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class AsyncVisionWithRawResponse:
|
|
176
|
+
"""Raw response wrapper for AsyncVision resource."""
|
|
177
|
+
|
|
178
|
+
def __init__(self, vision: AsyncVision) -> None:
|
|
179
|
+
self._vision = vision
|
|
180
|
+
|
|
181
|
+
async def analyze(self, **kwargs) -> httpx.Response: # type: ignore
|
|
182
|
+
"""Get raw response from analyze."""
|
|
183
|
+
return await self._vision._client.with_raw_response._post( # type: ignore
|
|
184
|
+
"/vision/analyze",
|
|
185
|
+
body=kwargs,
|
|
186
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Type definitions for cmdop-llm custom endpoints."""
|
|
2
|
+
|
|
3
|
+
from .ocr import OCRRequest, OCRResponse
|
|
4
|
+
from .vision import VisionAnalyzeRequest, VisionAnalyzeResponse
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"VisionAnalyzeRequest",
|
|
8
|
+
"VisionAnalyzeResponse",
|
|
9
|
+
"OCRRequest",
|
|
10
|
+
"OCRResponse",
|
|
11
|
+
]
|
cmdop_llm/types/ocr.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Type definitions for OCR endpoint."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, model_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OCRRequest(BaseModel):
|
|
9
|
+
"""Request for OCR text extraction."""
|
|
10
|
+
|
|
11
|
+
image: Optional[str] = None
|
|
12
|
+
"""Base64-encoded image data."""
|
|
13
|
+
|
|
14
|
+
image_url: Optional[str] = None
|
|
15
|
+
"""URL of the image to extract text from."""
|
|
16
|
+
|
|
17
|
+
model: Optional[str] = None
|
|
18
|
+
"""Model to use for OCR. Defaults to server default."""
|
|
19
|
+
|
|
20
|
+
mode: Literal["tiny", "small", "base", "gundam"] = "base"
|
|
21
|
+
"""OCR mode affecting quality and speed."""
|
|
22
|
+
|
|
23
|
+
fetch_image: bool = True
|
|
24
|
+
"""Whether to fetch image from URL on server side."""
|
|
25
|
+
|
|
26
|
+
@model_validator(mode="after")
|
|
27
|
+
def validate_image_source(self) -> "OCRRequest":
|
|
28
|
+
"""Ensure at least one image source is provided."""
|
|
29
|
+
if not self.image and not self.image_url:
|
|
30
|
+
raise ValueError("Either 'image' or 'image_url' must be provided")
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class OCRResponse(BaseModel):
|
|
35
|
+
"""Response from OCR extraction."""
|
|
36
|
+
|
|
37
|
+
text: str
|
|
38
|
+
"""Extracted text content."""
|
|
39
|
+
|
|
40
|
+
model: str
|
|
41
|
+
"""Model used for extraction."""
|
|
42
|
+
|
|
43
|
+
cost_usd: float
|
|
44
|
+
"""Cost of the request in USD."""
|
|
45
|
+
|
|
46
|
+
tokens_input: int
|
|
47
|
+
"""Number of input tokens used."""
|
|
48
|
+
|
|
49
|
+
tokens_output: int
|
|
50
|
+
"""Number of output tokens generated."""
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Type definitions for vision analysis endpoint."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, model_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class VisionAnalyzeRequest(BaseModel):
|
|
9
|
+
"""Request for vision analysis."""
|
|
10
|
+
|
|
11
|
+
image: Optional[str] = None
|
|
12
|
+
"""Base64-encoded image data."""
|
|
13
|
+
|
|
14
|
+
image_url: Optional[str] = None
|
|
15
|
+
"""URL of the image to analyze."""
|
|
16
|
+
|
|
17
|
+
prompt: Optional[str] = None
|
|
18
|
+
"""Custom prompt for the analysis."""
|
|
19
|
+
|
|
20
|
+
model: Optional[str] = None
|
|
21
|
+
"""Model to use for analysis. Defaults to server default."""
|
|
22
|
+
|
|
23
|
+
ocr_mode: Literal["tiny", "small", "base", "gundam"] = "base"
|
|
24
|
+
"""OCR mode for text extraction quality."""
|
|
25
|
+
|
|
26
|
+
fetch_image: bool = True
|
|
27
|
+
"""Whether to fetch image from URL on server side."""
|
|
28
|
+
|
|
29
|
+
@model_validator(mode="after")
|
|
30
|
+
def validate_image_source(self) -> "VisionAnalyzeRequest":
|
|
31
|
+
"""Ensure at least one image source is provided."""
|
|
32
|
+
if not self.image and not self.image_url:
|
|
33
|
+
raise ValueError("Either 'image' or 'image_url' must be provided")
|
|
34
|
+
return self
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class VisionAnalyzeResponse(BaseModel):
|
|
38
|
+
"""Response from vision analysis."""
|
|
39
|
+
|
|
40
|
+
extracted_text: str
|
|
41
|
+
"""Text extracted from the image."""
|
|
42
|
+
|
|
43
|
+
description: str
|
|
44
|
+
"""Description of the image content."""
|
|
45
|
+
|
|
46
|
+
language: Optional[str] = None
|
|
47
|
+
"""Detected language of the text."""
|
|
48
|
+
|
|
49
|
+
model: str
|
|
50
|
+
"""Model used for analysis."""
|
|
51
|
+
|
|
52
|
+
cost_usd: float
|
|
53
|
+
"""Cost of the request in USD."""
|
|
54
|
+
|
|
55
|
+
tokens_input: int
|
|
56
|
+
"""Number of input tokens used."""
|
|
57
|
+
|
|
58
|
+
tokens_output: int
|
|
59
|
+
"""Number of output tokens generated."""
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cmdop-llm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for CMDOP LLM Service - OpenAI-compatible API for 200+ AI models
|
|
5
|
+
Project-URL: Homepage, https://cmdop.com
|
|
6
|
+
Project-URL: Documentation, https://sdk.cmdop.com
|
|
7
|
+
Project-URL: Repository, https://github.com/markolofsen/cmdop-client
|
|
8
|
+
Project-URL: Issues, https://github.com/markolofsen/cmdop-client/issues
|
|
9
|
+
Author-email: CMDOP Team <support@cmdop.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ai,anthropic,api,chat,claude,cmdop,gpt,llm,openai,openrouter
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: httpx<1,>=0.23.0
|
|
26
|
+
Requires-Dist: openai<3.0.0,>=1.0.0
|
|
27
|
+
Requires-Dist: pydantic<3,>=2.0.0
|
|
28
|
+
Requires-Dist: typing-extensions>=4.5.0
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pyright>=1.1.300; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: respx>=0.20.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# CMDOP LLM Python SDK
|
|
39
|
+
|
|
40
|
+
Python SDK for CMDOP LLM Service - OpenAI-compatible API for 200+ AI models.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install cmdop-llm
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from cmdop_llm import CmdopLLM
|
|
52
|
+
|
|
53
|
+
client = CmdopLLM(api_key="your-api-key")
|
|
54
|
+
|
|
55
|
+
response = client.chat.completions.create(
|
|
56
|
+
model="anthropic/claude-3.5-sonnet",
|
|
57
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
58
|
+
)
|
|
59
|
+
print(response.choices[0].message.content)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
- **Drop-in OpenAI replacement** - Same API, different models
|
|
65
|
+
- **200+ Models** - GPT-4, Claude, Llama, Mistral, Gemini via single endpoint
|
|
66
|
+
- **Streaming** - Real-time token streaming
|
|
67
|
+
- **Vision & OCR** - Image analysis and text extraction
|
|
68
|
+
- **Image Generation** - FLUX, DALL-E and other models
|
|
69
|
+
- **Async Support** - Full async/await support
|
|
70
|
+
|
|
71
|
+
## Environment Variables
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
export CMDOP_API_KEY="your-api-key"
|
|
75
|
+
export CMDOP_BASE_URL="https://llm.cmdop.com" # Optional, default
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Usage Examples
|
|
79
|
+
|
|
80
|
+
### Chat Completion
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from cmdop_llm import CmdopLLM
|
|
84
|
+
|
|
85
|
+
client = CmdopLLM()
|
|
86
|
+
|
|
87
|
+
response = client.chat.completions.create(
|
|
88
|
+
model="openai/gpt-4o",
|
|
89
|
+
messages=[
|
|
90
|
+
{"role": "system", "content": "You are a helpful assistant."},
|
|
91
|
+
{"role": "user", "content": "Explain quantum computing."}
|
|
92
|
+
],
|
|
93
|
+
temperature=0.7,
|
|
94
|
+
max_tokens=1000,
|
|
95
|
+
)
|
|
96
|
+
print(response.choices[0].message.content)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Streaming
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
stream = client.chat.completions.create(
|
|
103
|
+
model="anthropic/claude-3.5-sonnet",
|
|
104
|
+
messages=[{"role": "user", "content": "Write a poem."}],
|
|
105
|
+
stream=True,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
for chunk in stream:
|
|
109
|
+
if chunk.choices[0].delta.content:
|
|
110
|
+
print(chunk.choices[0].delta.content, end="")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Vision Analysis
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
result = client.vision.analyze(
|
|
117
|
+
image_url="https://example.com/image.jpg",
|
|
118
|
+
prompt="Describe this image"
|
|
119
|
+
)
|
|
120
|
+
print(result.description)
|
|
121
|
+
print(result.extracted_text)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### OCR Text Extraction
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
result = client.ocr.extract(
|
|
128
|
+
image_url="https://example.com/document.png"
|
|
129
|
+
)
|
|
130
|
+
print(result.text)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Image Generation
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
response = client.images.generate(
|
|
137
|
+
model="black-forest-labs/FLUX.1-schnell",
|
|
138
|
+
prompt="A futuristic cityscape",
|
|
139
|
+
size="1024x1024",
|
|
140
|
+
)
|
|
141
|
+
print(response.data[0].url)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Async Usage
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import asyncio
|
|
148
|
+
from cmdop_llm import AsyncCmdopLLM
|
|
149
|
+
|
|
150
|
+
async def main():
|
|
151
|
+
client = AsyncCmdopLLM()
|
|
152
|
+
|
|
153
|
+
response = await client.chat.completions.create(
|
|
154
|
+
model="openai/gpt-4o",
|
|
155
|
+
messages=[{"role": "user", "content": "Hello!"}]
|
|
156
|
+
)
|
|
157
|
+
print(response.choices[0].message.content)
|
|
158
|
+
|
|
159
|
+
asyncio.run(main())
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Available Models
|
|
163
|
+
|
|
164
|
+
Access 200+ models including:
|
|
165
|
+
|
|
166
|
+
- **OpenAI**: gpt-4o, gpt-4o-mini, gpt-4-turbo
|
|
167
|
+
- **Anthropic**: claude-3.5-sonnet, claude-3-opus, claude-3-haiku
|
|
168
|
+
- **Google**: gemini-pro, gemini-1.5-pro
|
|
169
|
+
- **Meta**: llama-3.1-405b, llama-3.1-70b
|
|
170
|
+
- **Mistral**: mistral-large, mixtral-8x22b
|
|
171
|
+
- **Image**: FLUX.1-schnell, FLUX.1-pro, stable-diffusion-xl
|
|
172
|
+
|
|
173
|
+
Use model format: `provider/model-name` (e.g., `openai/gpt-4o`)
|
|
174
|
+
|
|
175
|
+
## API Reference
|
|
176
|
+
|
|
177
|
+
### CmdopLLM
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
CmdopLLM(
|
|
181
|
+
api_key: str = None, # From CMDOP_API_KEY env if not set
|
|
182
|
+
base_url: str = None, # Default: https://llm.cmdop.com
|
|
183
|
+
timeout: float = None, # Request timeout
|
|
184
|
+
max_retries: int = 2, # Retry count
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Resources
|
|
189
|
+
|
|
190
|
+
- `client.chat.completions` - Chat completions (OpenAI compatible)
|
|
191
|
+
- `client.images` - Image generation (OpenAI compatible)
|
|
192
|
+
- `client.models` - List available models
|
|
193
|
+
- `client.vision` - Vision analysis (CMDOP specific)
|
|
194
|
+
- `client.ocr` - OCR extraction (CMDOP specific)
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
cmdop_llm/__init__.py,sha256=LaW6PRIW-GO8ipcsvfLtA2_lNI4MvJsUiL4GwvdiKVc,2140
|
|
2
|
+
cmdop_llm/_client.py,sha256=Y_ycGrvkuBIzgxx2HWg95W3piJ1Ml5CRSMUwyh4rYFY,7032
|
|
3
|
+
cmdop_llm/_constants.py,sha256=bxyJOaffvTUPRn12GveQYeFRof8iLpw8Xgi_z_usgJQ,301
|
|
4
|
+
cmdop_llm/_version.py,sha256=-XsuXf8DEZujLc8xXXaLl82JH7PEwJ1nbZJqHaYy3FI,154
|
|
5
|
+
cmdop_llm/py.typed,sha256=qww23I-O7yv_Kf5Wu2UW3r8K1Vd12UDESBl5tmzRSI0,60
|
|
6
|
+
cmdop_llm/resources/__init__.py,sha256=Jqx7K-ijPxSahuYBJQHT9xngSWgrAeB0WF4TEiOsHEw,193
|
|
7
|
+
cmdop_llm/resources/ocr.py,sha256=I44rXAWgqDhQFBF6ewqBw9SQoWZc-3ENwe-_iIk0MzY,5438
|
|
8
|
+
cmdop_llm/resources/vision.py,sha256=xRr4eQEALoxyPw3VUxWibCMNelT4_EeQyHIGxFvQ9nU,5852
|
|
9
|
+
cmdop_llm/types/__init__.py,sha256=uuB068ElAXawfftqneg9Cb9CZxq3TZuOCkwZm8l6-hQ,270
|
|
10
|
+
cmdop_llm/types/ocr.py,sha256=WOWV1OYGKoZjmghrOnff4274rrnI4qQEKv9ppDNlXa0,1312
|
|
11
|
+
cmdop_llm/types/vision.py,sha256=nsvG58ggFRb6cRAClcbsaesHA1nu4BYbH5NleZR8UAg,1584
|
|
12
|
+
cmdop_llm-0.1.0.dist-info/METADATA,sha256=SDznx7azTjqwelhjT6SsITtJ3SiYeAYbSz202lEyUwk,5147
|
|
13
|
+
cmdop_llm-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
+
cmdop_llm-0.1.0.dist-info/licenses/LICENSE,sha256=8flEwqCzbjvfOljG-NWHrUXYYyNVOpWMLXNyvEeuiDA,1062
|
|
15
|
+
cmdop_llm-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CMDOP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|