langchain-micompress 0.1.0__tar.gz
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.
- langchain_micompress-0.1.0/LICENSE +21 -0
- langchain_micompress-0.1.0/PKG-INFO +72 -0
- langchain_micompress-0.1.0/README.md +50 -0
- langchain_micompress-0.1.0/langchain_micompress/__init__.py +14 -0
- langchain_micompress-0.1.0/langchain_micompress/_version.py +1 -0
- langchain_micompress-0.1.0/langchain_micompress/client.py +141 -0
- langchain_micompress-0.1.0/langchain_micompress/tools.py +113 -0
- langchain_micompress-0.1.0/langchain_micompress.egg-info/PKG-INFO +72 -0
- langchain_micompress-0.1.0/langchain_micompress.egg-info/SOURCES.txt +13 -0
- langchain_micompress-0.1.0/langchain_micompress.egg-info/dependency_links.txt +1 -0
- langchain_micompress-0.1.0/langchain_micompress.egg-info/requires.txt +2 -0
- langchain_micompress-0.1.0/langchain_micompress.egg-info/top_level.txt +1 -0
- langchain_micompress-0.1.0/pyproject.toml +32 -0
- langchain_micompress-0.1.0/setup.cfg +4 -0
- langchain_micompress-0.1.0/tests/test_live.py +33 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MiCompress (https://micompress.com)
|
|
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.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langchain-micompress
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LangChain tools for MiCompress: compress images, PDFs, video, audio, Office documents and archives
|
|
5
|
+
Author-email: MiCompress <support@micompress.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://micompress.com/mcp-server
|
|
8
|
+
Project-URL: Issues, https://github.com/micompress/micompress-mcp/issues
|
|
9
|
+
Keywords: langchain,tool,agent,compress,compression,image,pdf,micompress
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: langchain-core<2,>=0.3
|
|
20
|
+
Requires-Dist: pydantic>=2
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# langchain-micompress
|
|
24
|
+
|
|
25
|
+
LangChain tools for [MiCompress](https://micompress.com): let your agent compress
|
|
26
|
+
**images, PDFs, video, audio, Office documents, EPUB books and archives**.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install langchain-micompress
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from langchain_micompress import MiCompressCompressFile
|
|
36
|
+
|
|
37
|
+
tool = MiCompressCompressFile() # anonymous, or MiCompressCompressFile(api_key="mc_...")
|
|
38
|
+
print(tool.invoke({"path": "report.pdf", "preset": "extreme"}))
|
|
39
|
+
# Compressed report.pdf → report-min.pdf: 4.2MB → 610.3KB (86% smaller).
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
With an agent:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from langchain_micompress import MiCompressToolkit
|
|
46
|
+
|
|
47
|
+
tools = MiCompressToolkit().get_tools() # micompress_compress_file, micompress_list_formats
|
|
48
|
+
agent = create_agent(model, tools) # any LangChain / LangGraph agent
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Tool | What it does |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `micompress_compress_file` | Compress a local file and write the smaller copy (default: `<name>-min.<ext>`). Arguments: `path`, optional `output_path`, `preset` (`lossless`, `recommended`, `extreme`), `max_width` (images). |
|
|
54
|
+
| `micompress_list_formats` | List the supported extensions by category. |
|
|
55
|
+
|
|
56
|
+
## API key (optional)
|
|
57
|
+
|
|
58
|
+
Without a key the tools work anonymously within per-format size limits. For larger files
|
|
59
|
+
create a free key at [micompress.com/account](https://micompress.com/account) and set
|
|
60
|
+
`MICOMPRESS_API_KEY`, or pass `api_key=`.
|
|
61
|
+
|
|
62
|
+
## What is sent where
|
|
63
|
+
|
|
64
|
+
The file is uploaded to micompress.com over HTTPS to be compressed. Uploaded originals are
|
|
65
|
+
deleted within about 10 minutes and results within 24 hours —
|
|
66
|
+
[privacy policy](https://micompress.com/privacy). Files reachable by URL can also be
|
|
67
|
+
compressed with the hosted MCP server at `https://micompress.com/mcp`
|
|
68
|
+
([docs](https://micompress.com/mcp-server)).
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# langchain-micompress
|
|
2
|
+
|
|
3
|
+
LangChain tools for [MiCompress](https://micompress.com): let your agent compress
|
|
4
|
+
**images, PDFs, video, audio, Office documents, EPUB books and archives**.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install langchain-micompress
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from langchain_micompress import MiCompressCompressFile
|
|
14
|
+
|
|
15
|
+
tool = MiCompressCompressFile() # anonymous, or MiCompressCompressFile(api_key="mc_...")
|
|
16
|
+
print(tool.invoke({"path": "report.pdf", "preset": "extreme"}))
|
|
17
|
+
# Compressed report.pdf → report-min.pdf: 4.2MB → 610.3KB (86% smaller).
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
With an agent:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from langchain_micompress import MiCompressToolkit
|
|
24
|
+
|
|
25
|
+
tools = MiCompressToolkit().get_tools() # micompress_compress_file, micompress_list_formats
|
|
26
|
+
agent = create_agent(model, tools) # any LangChain / LangGraph agent
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Tool | What it does |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `micompress_compress_file` | Compress a local file and write the smaller copy (default: `<name>-min.<ext>`). Arguments: `path`, optional `output_path`, `preset` (`lossless`, `recommended`, `extreme`), `max_width` (images). |
|
|
32
|
+
| `micompress_list_formats` | List the supported extensions by category. |
|
|
33
|
+
|
|
34
|
+
## API key (optional)
|
|
35
|
+
|
|
36
|
+
Without a key the tools work anonymously within per-format size limits. For larger files
|
|
37
|
+
create a free key at [micompress.com/account](https://micompress.com/account) and set
|
|
38
|
+
`MICOMPRESS_API_KEY`, or pass `api_key=`.
|
|
39
|
+
|
|
40
|
+
## What is sent where
|
|
41
|
+
|
|
42
|
+
The file is uploaded to micompress.com over HTTPS to be compressed. Uploaded originals are
|
|
43
|
+
deleted within about 10 minutes and results within 24 hours —
|
|
44
|
+
[privacy policy](https://micompress.com/privacy). Files reachable by URL can also be
|
|
45
|
+
compressed with the hosted MCP server at `https://micompress.com/mcp`
|
|
46
|
+
([docs](https://micompress.com/mcp-server)).
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""LangChain integration for MiCompress (https://micompress.com)."""
|
|
2
|
+
|
|
3
|
+
from ._version import __version__
|
|
4
|
+
from .client import MiCompressClient, MiCompressError
|
|
5
|
+
from .tools import MiCompressCompressFile, MiCompressListFormats, MiCompressToolkit
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"MiCompressClient",
|
|
9
|
+
"MiCompressError",
|
|
10
|
+
"MiCompressCompressFile",
|
|
11
|
+
"MiCompressListFormats",
|
|
12
|
+
"MiCompressToolkit",
|
|
13
|
+
"__version__",
|
|
14
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Minimal stdlib client for the MiCompress compression API.
|
|
2
|
+
|
|
3
|
+
Kept separate from the ``micompress`` CLI package on purpose: the server tells
|
|
4
|
+
integrations apart by ``X-MiCompress-Client``, and this one reports itself as
|
|
5
|
+
``langchain`` so its usage is measurable on its own.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.request
|
|
12
|
+
import uuid
|
|
13
|
+
|
|
14
|
+
from ._version import __version__
|
|
15
|
+
|
|
16
|
+
DEFAULT_BASE = "https://micompress.com"
|
|
17
|
+
USER_AGENT = "micompress-langchain/%s (+https://micompress.com)" % __version__
|
|
18
|
+
CLIENT_ID = "langchain/%s" % __version__
|
|
19
|
+
ACCOUNT_URL = DEFAULT_BASE + "/account"
|
|
20
|
+
PRESETS = ("lossless", "recommended", "extreme")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MiCompressError(Exception):
|
|
24
|
+
"""A compression request failed. ``code`` is the API's machine-readable code, if any."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, message, code=None):
|
|
27
|
+
super().__init__(message)
|
|
28
|
+
self.code = code
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _ext(filename):
|
|
32
|
+
base = os.path.basename(filename).lower()
|
|
33
|
+
return base.rsplit(".", 1)[1] if "." in base else ""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MiCompressClient:
|
|
37
|
+
"""Compress local files through https://micompress.com.
|
|
38
|
+
|
|
39
|
+
``api_key`` is optional (personal ``mc_...`` key from micompress.com/account);
|
|
40
|
+
without it the API works anonymously within per-format size limits.
|
|
41
|
+
It falls back to the ``MICOMPRESS_API_KEY`` environment variable.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, api_key=None, base_url=DEFAULT_BASE, timeout=300):
|
|
45
|
+
self.api_key = api_key if api_key is not None else os.environ.get("MICOMPRESS_API_KEY") or None
|
|
46
|
+
self.base_url = base_url.rstrip("/")
|
|
47
|
+
self.timeout = timeout
|
|
48
|
+
self._formats = None
|
|
49
|
+
|
|
50
|
+
def _headers(self, extra=None):
|
|
51
|
+
headers = {"User-Agent": USER_AGENT, "X-MiCompress-Client": CLIENT_ID, "Accept": "application/json"}
|
|
52
|
+
if self.api_key:
|
|
53
|
+
headers["X-API-Key"] = self.api_key
|
|
54
|
+
if extra:
|
|
55
|
+
headers.update(extra)
|
|
56
|
+
return headers
|
|
57
|
+
|
|
58
|
+
def _request(self, url, data=None, headers=None, method="GET"):
|
|
59
|
+
req = urllib.request.Request(url, data=data, headers=headers or {}, method=method)
|
|
60
|
+
try:
|
|
61
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
62
|
+
return resp.read()
|
|
63
|
+
except urllib.error.HTTPError as e:
|
|
64
|
+
raw = e.read().decode("utf-8", "replace")
|
|
65
|
+
try:
|
|
66
|
+
payload = json.loads(raw)
|
|
67
|
+
except ValueError:
|
|
68
|
+
raise MiCompressError("HTTP %s from %s" % (e.code, url), "HTTP_%s" % e.code)
|
|
69
|
+
code = payload.get("code")
|
|
70
|
+
message = payload.get("error") or "HTTP %s" % e.code
|
|
71
|
+
if code == "LOGIN_REQUIRED":
|
|
72
|
+
message = ("The file is over the anonymous size limit. Use a free API key from %s "
|
|
73
|
+
"(MICOMPRESS_API_KEY) for larger files." % ACCOUNT_URL)
|
|
74
|
+
raise MiCompressError(message, code or "API_ERROR")
|
|
75
|
+
except urllib.error.URLError as e:
|
|
76
|
+
raise MiCompressError("Network error: %s" % e.reason, "NETWORK")
|
|
77
|
+
|
|
78
|
+
def formats(self):
|
|
79
|
+
"""Supported extensions → {group, endpoint, maxBytes} for this caller (cached)."""
|
|
80
|
+
if self._formats is None:
|
|
81
|
+
raw = self._request(self.base_url + "/api/v1/capabilities", headers=self._headers())
|
|
82
|
+
self._formats = json.loads(raw.decode("utf-8"))["formats"]
|
|
83
|
+
return self._formats
|
|
84
|
+
|
|
85
|
+
def compress(self, input_path, output_path=None, preset=None, max_width=None):
|
|
86
|
+
"""Compress ``input_path`` and write the result. Returns a summary dict.
|
|
87
|
+
|
|
88
|
+
``output_path`` defaults to the input path with a ``-min`` suffix.
|
|
89
|
+
"""
|
|
90
|
+
if preset and preset not in PRESETS:
|
|
91
|
+
raise MiCompressError("preset must be one of %s" % ", ".join(PRESETS), "BAD_PRESET")
|
|
92
|
+
if not os.path.isfile(input_path):
|
|
93
|
+
raise MiCompressError("File not found: %s" % input_path, "ENOENT")
|
|
94
|
+
|
|
95
|
+
filename = os.path.basename(input_path)
|
|
96
|
+
info = self.formats().get(_ext(filename))
|
|
97
|
+
if not info:
|
|
98
|
+
raise MiCompressError(
|
|
99
|
+
'Unsupported file type ".%s". Supported: %s'
|
|
100
|
+
% (_ext(filename) or "?", ", ".join(sorted(self.formats()))), "UNSUPPORTED")
|
|
101
|
+
|
|
102
|
+
fields = {"preset": preset or "recommended"}
|
|
103
|
+
if max_width:
|
|
104
|
+
fields["maxWidth"] = str(int(max_width))
|
|
105
|
+
boundary = uuid.uuid4().hex
|
|
106
|
+
body = bytearray()
|
|
107
|
+
for name, value in fields.items():
|
|
108
|
+
body += ("--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n"
|
|
109
|
+
% (boundary, name, value)).encode("utf-8")
|
|
110
|
+
body += ("--%s\r\nContent-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n"
|
|
111
|
+
"Content-Type: application/octet-stream\r\n\r\n"
|
|
112
|
+
% (boundary, filename.replace('"', "_"))).encode("utf-8")
|
|
113
|
+
with open(input_path, "rb") as fh:
|
|
114
|
+
body += fh.read()
|
|
115
|
+
body += ("\r\n--%s--\r\n" % boundary).encode("utf-8")
|
|
116
|
+
|
|
117
|
+
raw = self._request(self.base_url + info["endpoint"], data=bytes(body), method="POST",
|
|
118
|
+
headers=self._headers({"Content-Type": "multipart/form-data; boundary=%s" % boundary}))
|
|
119
|
+
payload = json.loads(raw.decode("utf-8"))
|
|
120
|
+
if not payload.get("success"):
|
|
121
|
+
raise MiCompressError(payload.get("error") or "Compression failed", payload.get("code"))
|
|
122
|
+
|
|
123
|
+
data = self._request(self.base_url + payload["downloadUrl"], headers={"User-Agent": USER_AGENT})
|
|
124
|
+
if not output_path:
|
|
125
|
+
stem, dot_ext = os.path.splitext(input_path)
|
|
126
|
+
# The server may change the extension (e.g. a PNG that is really a JPEG).
|
|
127
|
+
served_ext = os.path.splitext(payload["downloadUrl"])[1] or dot_ext
|
|
128
|
+
output_path = stem + "-min" + served_ext
|
|
129
|
+
parent = os.path.dirname(os.path.abspath(output_path))
|
|
130
|
+
os.makedirs(parent, exist_ok=True)
|
|
131
|
+
with open(output_path, "wb") as fh:
|
|
132
|
+
fh.write(data)
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
"output_path": output_path,
|
|
136
|
+
"input_size": payload.get("inputSize"),
|
|
137
|
+
"output_size": payload.get("outputSize"),
|
|
138
|
+
"saved_percent": payload.get("savedPercent"),
|
|
139
|
+
"format": payload.get("format"),
|
|
140
|
+
"note": payload.get("note"),
|
|
141
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""LangChain tools for MiCompress."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal, Optional, Type
|
|
4
|
+
|
|
5
|
+
from langchain_core.tools import BaseTool, ToolException
|
|
6
|
+
from pydantic import BaseModel, Field, PrivateAttr
|
|
7
|
+
|
|
8
|
+
from .client import MiCompressClient, MiCompressError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _human(n):
|
|
12
|
+
if n is None:
|
|
13
|
+
return "?"
|
|
14
|
+
for unit in ("B", "KB", "MB", "GB"):
|
|
15
|
+
if n < 1024 or unit == "GB":
|
|
16
|
+
return ("%.1f%s" % (n, unit)) if unit != "B" else "%d%s" % (n, unit)
|
|
17
|
+
n /= 1024.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CompressFileInput(BaseModel):
|
|
21
|
+
path: str = Field(description="Path of the local file to compress.")
|
|
22
|
+
output_path: Optional[str] = Field(
|
|
23
|
+
default=None,
|
|
24
|
+
description='Where to write the result. Defaults to the input path with a "-min" suffix.')
|
|
25
|
+
preset: Optional[Literal["lossless", "recommended", "extreme"]] = Field(
|
|
26
|
+
default=None,
|
|
27
|
+
description="lossless keeps quality identical; recommended (default) balances size and quality; "
|
|
28
|
+
"extreme gives the smallest file.")
|
|
29
|
+
max_width: Optional[int] = Field(default=None, description="Images only: resize down to this width in pixels.")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MiCompressCompressFile(BaseTool):
|
|
33
|
+
"""Compress a local image, PDF, video, audio file, Office document or archive.
|
|
34
|
+
|
|
35
|
+
The file is uploaded to https://micompress.com over HTTPS and the smaller copy is
|
|
36
|
+
written back to disk. Set ``MICOMPRESS_API_KEY`` (or pass ``api_key``) for larger files.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
.. code-block:: python
|
|
40
|
+
|
|
41
|
+
from langchain_micompress import MiCompressCompressFile
|
|
42
|
+
tool = MiCompressCompressFile()
|
|
43
|
+
tool.invoke({"path": "report.pdf", "preset": "extreme"})
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
name: str = "micompress_compress_file"
|
|
47
|
+
description: str = (
|
|
48
|
+
"Compress a local file (images such as JPG, PNG, WebP, GIF, HEIC or RAW; PDF; video; audio; "
|
|
49
|
+
"Word, Excel, PowerPoint; EPUB; ZIP/7Z/RAR) and write the smaller copy to disk. "
|
|
50
|
+
"Returns the output path and the size before and after."
|
|
51
|
+
)
|
|
52
|
+
args_schema: Type[BaseModel] = CompressFileInput
|
|
53
|
+
handle_tool_error: bool = True
|
|
54
|
+
|
|
55
|
+
api_key: Optional[str] = None
|
|
56
|
+
base_url: str = "https://micompress.com"
|
|
57
|
+
_client: MiCompressClient = PrivateAttr()
|
|
58
|
+
|
|
59
|
+
def __init__(self, **kwargs):
|
|
60
|
+
super().__init__(**kwargs)
|
|
61
|
+
self._client = MiCompressClient(api_key=self.api_key, base_url=self.base_url)
|
|
62
|
+
|
|
63
|
+
def _run(self, path: str, output_path: Optional[str] = None, preset: Optional[str] = None,
|
|
64
|
+
max_width: Optional[int] = None, run_manager=None) -> str:
|
|
65
|
+
try:
|
|
66
|
+
r = self._client.compress(path, output_path=output_path, preset=preset, max_width=max_width)
|
|
67
|
+
except MiCompressError as e:
|
|
68
|
+
raise ToolException(str(e))
|
|
69
|
+
text = "Compressed %s → %s: %s → %s (%s%% smaller)." % (
|
|
70
|
+
path, r["output_path"], _human(r["input_size"]), _human(r["output_size"]), r["saved_percent"])
|
|
71
|
+
if r.get("note"):
|
|
72
|
+
text += " Note: %s" % r["note"]
|
|
73
|
+
return text
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class MiCompressListFormats(BaseTool):
|
|
77
|
+
"""List the file extensions MiCompress can compress, grouped by category."""
|
|
78
|
+
|
|
79
|
+
name: str = "micompress_list_formats"
|
|
80
|
+
description: str = "List the file extensions that micompress_compress_file accepts, grouped by category."
|
|
81
|
+
handle_tool_error: bool = True
|
|
82
|
+
|
|
83
|
+
api_key: Optional[str] = None
|
|
84
|
+
base_url: str = "https://micompress.com"
|
|
85
|
+
_client: MiCompressClient = PrivateAttr()
|
|
86
|
+
|
|
87
|
+
def __init__(self, **kwargs):
|
|
88
|
+
super().__init__(**kwargs)
|
|
89
|
+
self._client = MiCompressClient(api_key=self.api_key, base_url=self.base_url)
|
|
90
|
+
|
|
91
|
+
def _run(self, run_manager=None) -> str:
|
|
92
|
+
try:
|
|
93
|
+
formats = self._client.formats()
|
|
94
|
+
except MiCompressError as e:
|
|
95
|
+
raise ToolException(str(e))
|
|
96
|
+
groups = {}
|
|
97
|
+
for ext, info in formats.items():
|
|
98
|
+
groups.setdefault(info["group"], []).append(ext)
|
|
99
|
+
return "\n".join("%s: %s" % (g, ", ".join(sorted(exts))) for g, exts in sorted(groups.items()))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class MiCompressToolkit:
|
|
103
|
+
"""Convenience wrapper returning both MiCompress tools."""
|
|
104
|
+
|
|
105
|
+
def __init__(self, api_key: Optional[str] = None, base_url: str = "https://micompress.com"):
|
|
106
|
+
self.api_key = api_key
|
|
107
|
+
self.base_url = base_url
|
|
108
|
+
|
|
109
|
+
def get_tools(self):
|
|
110
|
+
return [
|
|
111
|
+
MiCompressCompressFile(api_key=self.api_key, base_url=self.base_url),
|
|
112
|
+
MiCompressListFormats(api_key=self.api_key, base_url=self.base_url),
|
|
113
|
+
]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langchain-micompress
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LangChain tools for MiCompress: compress images, PDFs, video, audio, Office documents and archives
|
|
5
|
+
Author-email: MiCompress <support@micompress.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://micompress.com/mcp-server
|
|
8
|
+
Project-URL: Issues, https://github.com/micompress/micompress-mcp/issues
|
|
9
|
+
Keywords: langchain,tool,agent,compress,compression,image,pdf,micompress
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: langchain-core<2,>=0.3
|
|
20
|
+
Requires-Dist: pydantic>=2
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# langchain-micompress
|
|
24
|
+
|
|
25
|
+
LangChain tools for [MiCompress](https://micompress.com): let your agent compress
|
|
26
|
+
**images, PDFs, video, audio, Office documents, EPUB books and archives**.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install langchain-micompress
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from langchain_micompress import MiCompressCompressFile
|
|
36
|
+
|
|
37
|
+
tool = MiCompressCompressFile() # anonymous, or MiCompressCompressFile(api_key="mc_...")
|
|
38
|
+
print(tool.invoke({"path": "report.pdf", "preset": "extreme"}))
|
|
39
|
+
# Compressed report.pdf → report-min.pdf: 4.2MB → 610.3KB (86% smaller).
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
With an agent:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from langchain_micompress import MiCompressToolkit
|
|
46
|
+
|
|
47
|
+
tools = MiCompressToolkit().get_tools() # micompress_compress_file, micompress_list_formats
|
|
48
|
+
agent = create_agent(model, tools) # any LangChain / LangGraph agent
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Tool | What it does |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `micompress_compress_file` | Compress a local file and write the smaller copy (default: `<name>-min.<ext>`). Arguments: `path`, optional `output_path`, `preset` (`lossless`, `recommended`, `extreme`), `max_width` (images). |
|
|
54
|
+
| `micompress_list_formats` | List the supported extensions by category. |
|
|
55
|
+
|
|
56
|
+
## API key (optional)
|
|
57
|
+
|
|
58
|
+
Without a key the tools work anonymously within per-format size limits. For larger files
|
|
59
|
+
create a free key at [micompress.com/account](https://micompress.com/account) and set
|
|
60
|
+
`MICOMPRESS_API_KEY`, or pass `api_key=`.
|
|
61
|
+
|
|
62
|
+
## What is sent where
|
|
63
|
+
|
|
64
|
+
The file is uploaded to micompress.com over HTTPS to be compressed. Uploaded originals are
|
|
65
|
+
deleted within about 10 minutes and results within 24 hours —
|
|
66
|
+
[privacy policy](https://micompress.com/privacy). Files reachable by URL can also be
|
|
67
|
+
compressed with the hosted MCP server at `https://micompress.com/mcp`
|
|
68
|
+
([docs](https://micompress.com/mcp-server)).
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
langchain_micompress/__init__.py
|
|
5
|
+
langchain_micompress/_version.py
|
|
6
|
+
langchain_micompress/client.py
|
|
7
|
+
langchain_micompress/tools.py
|
|
8
|
+
langchain_micompress.egg-info/PKG-INFO
|
|
9
|
+
langchain_micompress.egg-info/SOURCES.txt
|
|
10
|
+
langchain_micompress.egg-info/dependency_links.txt
|
|
11
|
+
langchain_micompress.egg-info/requires.txt
|
|
12
|
+
langchain_micompress.egg-info/top_level.txt
|
|
13
|
+
tests/test_live.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
langchain_micompress
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "langchain-micompress"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "LangChain tools for MiCompress: compress images, PDFs, video, audio, Office documents and archives"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "MiCompress", email = "support@micompress.com" }]
|
|
13
|
+
keywords = ["langchain", "tool", "agent", "compress", "compression", "image", "pdf", "micompress"]
|
|
14
|
+
dependencies = ["langchain-core>=0.3,<2", "pydantic>=2"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://micompress.com/mcp-server"
|
|
26
|
+
Issues = "https://github.com/micompress/micompress-mcp/issues"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools]
|
|
29
|
+
packages = ["langchain_micompress"]
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.dynamic]
|
|
32
|
+
version = { attr = "langchain_micompress._version.__version__" }
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Live end-to-end test against micompress.com (needs network)."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
import urllib.request
|
|
6
|
+
|
|
7
|
+
from langchain_micompress import MiCompressCompressFile, MiCompressListFormats, MiCompressToolkit
|
|
8
|
+
|
|
9
|
+
SAMPLE = "https://micompress.com/samples/photo.jpg"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_compress_and_list():
|
|
13
|
+
with tempfile.TemporaryDirectory() as d:
|
|
14
|
+
src = os.path.join(d, "photo.jpg")
|
|
15
|
+
req = urllib.request.Request(SAMPLE, headers={"User-Agent": "micompress-langchain-test"})
|
|
16
|
+
with urllib.request.urlopen(req) as r, open(src, "wb") as fh:
|
|
17
|
+
fh.write(r.read())
|
|
18
|
+
out = MiCompressCompressFile().invoke({"path": src})
|
|
19
|
+
assert "smaller" in out, out
|
|
20
|
+
assert os.path.exists(os.path.join(d, "photo-min.jpg"))
|
|
21
|
+
assert os.path.getsize(os.path.join(d, "photo-min.jpg")) < os.path.getsize(src)
|
|
22
|
+
|
|
23
|
+
err = MiCompressCompressFile().invoke({"path": os.path.join(d, "nope.xyz")})
|
|
24
|
+
assert "not found" in err.lower(), err
|
|
25
|
+
|
|
26
|
+
formats = MiCompressListFormats().invoke({})
|
|
27
|
+
assert "image:" in formats and "pdf" in formats, formats
|
|
28
|
+
assert len(MiCompressToolkit().get_tools()) == 2
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
test_compress_and_list()
|
|
33
|
+
print("ok")
|