Initial commit: Hermes Agent Skills collection

This commit is contained in:
Debian
2026-07-12 19:02:59 +00:00
commit e9cc106625
789 changed files with 233126 additions and 0 deletions
@@ -0,0 +1,165 @@
#!/usr/bin/env python3
"""Batch visual QA of rendered document pages/slides via direct vLLM API calls.
Bypasses the vision_analyze tool — useful when:
- vision_analyze fails due to config caching / model resolution issues
- You need to QA many images efficiently in one pass
- The active chat model is text-only but a multimodal model is on the same endpoint
Usage:
python batch_vision_qa.py /path/to/images/ <num_images> [options]
Environment variables:
NORIS_API_KEY - API key for the vLLM endpoint (required)
VISION_BASE_URL - Full chat completions URL (default: https://ai.noris.de/v1/chat/completions)
VISION_MODEL - Model name (default: vllm/release/gemma-4-31b-it)
Example:
NORIS_API_KEY=sk-bf-xxx python batch_vision_qa.py /home/debian/ai101/qa/ 17
Expects files named slide-01.jpg, slide-02.jpg, ..., slide-NN.jpg.
Outputs per-slide status and a summary of any issues found.
Before using: verify the model accepts images (see references/model-capabilities.md).
"""
import sys
import os
import base64
import json
import time
import urllib.request
API_KEY = os.environ.get("NORIS_API_KEY", "")
BASE_URL = os.environ.get(
"VISION_BASE_URL", "https://ai.noris.de/v1/chat/completions"
)
MODEL = os.environ.get("VISION_MODEL", "vllm/release/gemma-4-31b-it")
# Expanded 6-category prompt: overlap, overflow, contrast, layout, content, symbols
QA_PROMPT_TEMPLATE = """You are a visual QA reviewer for a professional presentation slide.
Analyze this slide carefully for ALL of these issues:
1. TEXT OVERLAP: Any text overlapping other text or graphics?
2. TEXT OVERFLOW: Any text extending beyond edges or getting cut off?
3. CONTRAST: Any low contrast between text and background making text hard to read?
4. LAYOUT: Misalignment, elements too close, insufficient margins, awkward spacing?
5. CONTENT: Unclear messaging, missing information, placeholder text, spelling errors, logical inconsistencies?
6. SYMBOLS: Any broken/missing Unicode characters showing as boxes or empty circles?
Respond in this exact JSON format:
{{"slide": {slide_num}, "overlap": "none|<description>", "overflow": "none|<description>", "contrast": "ok|<description>", "layout": "ok|<description>", "content": "ok|<description>", "symbols": "ok|<description>", "overall": "clean|issues found"}}
Be precise and thorough. If no issues, say "clean". If issues exist, describe them specifically."""
def qa_single_image(image_path: str, slide_num: int) -> dict:
"""Send one image to the vision model and return parsed QA result."""
with open(image_path, "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
payload = {
"model": MODEL,
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": QA_PROMPT_TEMPLATE.format(slide_num=slide_num),
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{img_b64}"
},
},
],
}
],
"max_tokens": 500,
}
req = urllib.request.Request(
BASE_URL,
data=json.dumps(payload).encode(),
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
)
resp = urllib.request.urlopen(req, timeout=60)
data = json.loads(resp.read())
content = data["choices"][0]["message"].get("content", "")
# Strip markdown code fences if present
content = content.strip()
if content.startswith("```"):
content = content.split("\n", 1)[-1].rsplit("```", 1)[0].strip()
try:
return json.loads(content)
except json.JSONDecodeError:
return {
"slide": slide_num,
"overlap": "parse error",
"overflow": "parse error",
"contrast": "parse error",
"layout": "parse error",
"content": "parse error",
"symbols": "parse error",
"overall": content[:200],
}
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <images_dir> <num_images>")
print(f"Example: {sys.argv[0]} /home/debian/ai101/qa/ 17")
sys.exit(1)
images_dir = sys.argv[1]
num_slides = int(sys.argv[2])
if not API_KEY:
print("ERROR: Set NORIS_API_KEY environment variable.")
sys.exit(1)
results = []
issues_found = []
for i in range(1, num_slides + 1):
slide_path = os.path.join(images_dir, f"slide-{i:02d}.jpg")
if not os.path.exists(slide_path):
print(f"Slide {i}: file not found — {slide_path}")
continue
try:
result = qa_single_image(slide_path, i)
results.append(result)
status = result.get("overall", "?")
marker = "⚠️" if "issue" in status.lower() else ""
print(f"Slide {i}/{num_slides} {marker} {status}")
if "issue" in status.lower():
issues_found.append(result)
except Exception as e:
print(f"Slide {i} FAILED: {e}")
results.append({"slide": i, "error": str(e)})
time.sleep(0.5)
print("\n" + "=" * 60)
if issues_found:
print(f"ISSUES ON {len(issues_found)} SLIDE(S):")
for r in issues_found:
print(f" Slide {r['slide']}:")
for key in ["overlap", "overflow", "contrast", "layout", "content", "symbols"]:
val = r.get(key, "")
if val and val not in ("none", "ok", ""):
print(f" {key}: {val}")
else:
print("ALL SLIDES CLEAN ✅")
if __name__ == "__main__":
main()
@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""
Validate a .pptx file for PowerPoint compatibility.
Checks for issues that LibreOffice tolerates but PowerPoint rejects:
- Negative shape dimensions (cx, cy ≤ 0) in <a:ext> elements
- Negative shape positions (x, y < 0) in <a:off> elements
- Missing required OOXML parts ([Content_Types].xml, presentation.xml, etc.)
- Malformed XML in any part
- Broken relationship references (rId in slide XML not found in .rels)
- Missing Content_Type overrides for slides
Usage:
python3 validate_pptx_compat.py <file.pptx>
Exit codes:
0 = all checks passed
1 = issues found (printed to stderr)
"""
import sys
import zipfile
import xml.etree.ElementTree as ET
NS_A = "http://schemas.openxmlformats.org/drawingml/2006/main"
NS_P = "http://schemas.openxmlformats.org/presentationml/2006/main"
NS_R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
NS_REL = "{http://schemas.openxmlformats.org/package/2006/relationships}"
def validate(pptx_path: str) -> list[str]:
"""Return a list of issue strings; empty list means clean."""
issues = []
with zipfile.ZipFile(pptx_path, "r") as z:
# 1. Zip integrity
bad = z.testzip()
if bad:
issues.append(f"BAD ZIP ENTRY: {bad}")
# 2. XML parse check on all XML/rels parts
for name in z.namelist():
if name.endswith(".xml") or name.endswith(".rels"):
try:
ET.parse(z.open(name))
except ET.ParseError as e:
issues.append(f"XML PARSE ERROR in {name}: {e}")
# 3. Required parts
required = [
"[Content_Types].xml",
"_rels/.rels",
"ppt/presentation.xml",
"ppt/_rels/presentation.xml.rels",
"ppt/theme/theme1.xml",
]
for req in required:
if req not in z.namelist():
issues.append(f"MISSING REQUIRED PART: {req}")
# 4. Slide rels existence
slides = [
f for f in z.namelist()
if f.startswith("ppt/slides/slide") and f.endswith(".xml")
]
for s in sorted(slides):
rels = s.replace("ppt/slides/", "ppt/slides/_rels/") + ".rels"
if rels not in z.namelist():
issues.append(f"MISSING RELS for {s}")
# 5. Content_Type overrides for slides
ct = ET.parse(z.open("[Content_Types].xml"))
ct_ns = {"ct": "http://schemas.openxmlformats.org/package/2006/content-types"}
overrides = [
o.get("PartName") for o in ct.findall(".//ct:Override", ct_ns)
]
for s in sorted(slides):
part_name = "/" + s
if part_name not in overrides:
issues.append(f"Missing Content_Type override for {s}")
# 6. Negative dimensions and positions (THE key check)
for i in range(1, len(slides) + 1):
slide_path = f"ppt/slides/slide{i}.xml"
if slide_path not in z.namelist():
continue
root = ET.parse(z.open(slide_path)).getroot()
for sp in root.iter(f"{{{NS_P}}}sp"):
spPr = sp.find(f".//{{{NS_P}}}spPr")
if spPr is None:
continue
# Get shape name for reporting
cNvPr = sp.find(f".//{{{NS_P}}}cNvPr")
name = cNvPr.get("name", "?") if cNvPr is not None else "?"
xfrm = spPr.find(f"{{{NS_A}}}xfrm")
if xfrm is None:
continue
off = xfrm.find(f"{{{NS_A}}}off")
ext = xfrm.find(f"{{{NS_A}}}ext")
if ext is not None:
cx = int(ext.get("cx", "0"))
cy = int(ext.get("cy", "0"))
if cx <= 0 or cy <= 0:
issues.append(
f"slide{i}: shape \"{name}\" has zero/negative extent: "
f"cx={cx} cy={cy}"
)
if off is not None:
x = int(off.get("x", "0"))
y = int(off.get("y", "0"))
if x < 0 or y < 0:
issues.append(
f"slide{i}: shape \"{name}\" has negative position: "
f"x={x} y={y}"
)
# 7. Broken image embed references
for i in range(1, len(slides) + 1):
slide_path = f"ppt/slides/slide{i}.xml"
if slide_path not in z.namelist():
continue
root = ET.parse(z.open(slide_path)).getroot()
rels_path = f"ppt/slides/_rels/slide{i}.xml.rels"
if rels_path not in z.namelist():
continue
rels_tree = ET.parse(z.open(rels_path))
rels_root = rels_tree.getroot()
valid_rids = {
rel.get("Id") for rel in rels_root.findall(f"{NS_REL}Relationship")
}
for blip in root.iter(f"{{{NS_A}}}blip"):
embed = blip.get(f"{{{NS_R}}}embed")
if embed is not None and embed not in valid_rids:
issues.append(
f"slide{i}: blipFill embed '{embed}' not found in rels"
)
return issues
def main():
if len(sys.argv) != 2:
print("Usage: validate_pptx_compat.py <file.pptx>", file=sys.stderr)
sys.exit(2)
pptx_path = sys.argv[1]
try:
issues = validate(pptx_path)
except FileNotFoundError:
print(f"File not found: {pptx_path}", file=sys.stderr)
sys.exit(2)
except zipfile.BadZipFile:
print(f"Not a valid ZIP/PPTX file: {pptx_path}", file=sys.stderr)
sys.exit(2)
if issues:
print(f"{len(issues)} issue(s) found:", file=sys.stderr)
for iss in issues:
print(f"{iss}", file=sys.stderr)
sys.exit(1)
else:
print(f"{pptx_path}: all checks passed")
sys.exit(0)
if __name__ == "__main__":
main()