Files
DemonGatanjieu-Anomalous_Mo…/api/mcp_look.py
T
DemonGatanjieuandClaude Opus 5.5 f7d8a69e75 Shorten the daily tools' descriptions and begin_task's rules
The descriptions of the daily set are rewritten without the repeats and
roundabout phrasing. Their rules and don'ts stay: no cropping or editing
pictures with code, no reading ComfyUI's folders, ask the user in the
workbench. So do the shared property texts: the query words, node ids, the
task id. tools/list for the daily set drops from about 27,300 to 22,200
characters, and the full one from 46,000 to 40,700. begin_task's note, read at
the start of every task, drops from about 1,900 to 900 characters. The
replacements were made with ast, so each description's adjacent literals
changed as one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 15:54:42 +08:00

87 lines
4.6 KiB
Python

"""zoom_image, the AI's closer look: part of a picture (in ComfyUI's output, input or temp folder)
cut out and shown larger, and the same part of a second picture beside it to compare a before
and an after, attached to the answer. It reads only: the pictures stay as they are. With it an AI
need not crop, enlarge or compare pictures with code of its own (each such command asks the user
to allow it). The region is in fractions of the picture, as the user's marks give theirs."""
import base64
import io
from PIL import Image
from .mcp_tools import ToolError, _resolve_folder_image, _tool
MIN_SIZE, MAX_SIZE, DEFAULT_SIZE = 256, 1024, 768
MAX_ENLARGE = 4 # a tiny part is shown at most this many times its size
MIN_PIXELS = 8
def _box(region, width, height):
"""Region {x, y, width, height} (fractions 0-1, default the whole picture) as a pixel box."""
region = region if isinstance(region, dict) else {}
try:
x, y = (min(1.0, max(0.0, float(region.get(key, 0)))) for key in ("x", "y"))
w, h = (min(1.0, max(0.0, float(region.get(key, 1)))) for key in ("width", "height"))
except (TypeError, ValueError):
raise ToolError("region is {x, y, width, height}, each a fraction 0-1 of the picture.")
left, top = round(x * width), round(y * height)
right, bottom = min(width, round((x + w) * width)), min(height, round((y + h) * height))
if right - left < MIN_PIXELS or bottom - top < MIN_PIXELS:
raise ToolError(f"The region is under {MIN_PIXELS} pixels across; give a larger one.")
return left, top, right, bottom
def _part(path, region, size):
"""(facts, MCP image item) of `region` of the picture at `path`, its longer side `size` at most."""
try:
with Image.open(path) as picture:
box = _box(region, picture.width, picture.height)
part = picture.crop(box).convert("RGB")
whole = f"{picture.width}x{picture.height}"
except (OSError, ValueError, Image.DecompressionBombError):
raise ToolError("The picture cannot be read.")
scale = min(size / max(part.size), MAX_ENLARGE)
shown = part.resize((max(1, round(part.width * scale)), max(1, round(part.height * scale))), Image.LANCZOS)
data = io.BytesIO()
shown.save(data, "JPEG", quality=88)
facts = {"picture": whole, "region": f"x {box[0]}-{box[2]}, y {box[1]}-{box[3]} px", "shown": f"{shown.width}x{shown.height}"}
return facts, {"type": "image", "data": base64.b64encode(data.getvalue()).decode("ascii"), "mimeType": "image/jpeg"}
def zoom_image(image, region=None, folder="output", compare_with="", compare_folder="", size=DEFAULT_SIZE):
try:
size = min(MAX_SIZE, max(MIN_SIZE, int(size)))
except (TypeError, ValueError):
raise ToolError(f"size is {MIN_SIZE}-{MAX_SIZE} pixels.")
rel, path = _resolve_folder_image(image, folder)
facts, item = _part(path, region, size)
answer = {"image": rel, "folder": folder, **facts}
extra = [item]
if compare_with:
other_folder = compare_folder or folder
other_rel, other_path = _resolve_folder_image(compare_with, other_folder)
other_facts, other_item = _part(other_path, region, size)
answer["compare_with"] = {"image": other_rel, "folder": other_folder, **other_facts}
answer["note"] = "Two pictures are attached: first image, then compare_with, the same region of each."
extra.append(other_item)
return answer, extra
_REGION = {"type": "object", "description": "Fractions 0-1, like the user's marks; default all.",
"properties": {name: {"type": "number", "minimum": 0, "maximum": 1} for name in ("x", "y", "width", "height")}}
_FOLDER = {"type": "string", "enum": ["output", "input", "temp"], "default": "output"}
LOOK_TOOLS = dict([
_tool("zoom_image", "Look closer at a picture",
"Part of a picture larger, at full detail (a hand, a face, a seam). folder: output, input or temp. "
"compare_with: a second picture, the same region beside it. Use it instead of cropping with code; "
"change pictures only by running a workflow, never by editing their files.",
zoom_image, {"image": {"type": "string", "description": "As the other tools name it."},
"region": _REGION, "folder": _FOLDER,
"compare_with": {"type": "string", "description": "A second picture."},
"compare_folder": {"type": "string", "enum": ["output", "input", "temp"],
"description": "Default: folder."},
"size": {"type": "integer", "minimum": MIN_SIZE, "maximum": MAX_SIZE, "default": DEFAULT_SIZE}},
["image"]),
])