Spaces:
Running
Running
AutoFix commited on
Commit ·
9d9a46e
1
Parent(s): da7768b
fix: auto-resolve relative media paths before sending (ROOT CAUSE)
Browse filesThe LLM often uses bare filenames in MEDIA: tags like 'report.md' instead of
absolute paths like '/tmp/report.md'. The gateway's extract_media() captures
the bare name, but send_document() fails with 'File not found' because it
can't resolve relative paths.
New patch_resolve_media_paths.py:
- After extract_media(), scans each media path
- If relative, searches /tmp/, /data/hermes/uploads/, cwd, HERMES_HOME
- Replaces with resolved absolute path if found
- Logs resolution for debugging
This fixes the actual root cause visible in gateway logs:
WARNING: Failed to send media (.md): File not found: 广东天气预报.md
- Dockerfile +6 -3
- scripts/patch_resolve_media_paths.py +101 -0
Dockerfile
CHANGED
|
@@ -18,7 +18,6 @@ RUN pip install --quiet --upgrade pip && \
|
|
| 18 |
pip install --quiet -e "/app/hermes-agent[feishu,mcp,cron,pty]" 2>&1 | tail -10
|
| 19 |
|
| 20 |
# Patch: add document file extensions to auto-detection for native delivery
|
| 21 |
-
# This enables Hermes to send .md, .pdf, .docx, .xlsx etc. as native attachments
|
| 22 |
COPY scripts/patch_file_delivery.py /tmp/patch_file_delivery.py
|
| 23 |
RUN python3 /tmp/patch_file_delivery.py; rm -f /tmp/patch_file_delivery.py
|
| 24 |
|
|
@@ -27,11 +26,15 @@ COPY patches/hermes-agent/agent/prompt_builder.py /app/hermes-agent/agent/prompt
|
|
| 27 |
COPY patches/hermes-agent/tools/send_message_tool.py /app/hermes-agent/tools/send_message_tool.py
|
| 28 |
|
| 29 |
# Patch: Auto-inject MEDIA: tags from write_file tool calls
|
| 30 |
-
# Ensures files created by write_file are delivered as native attachments
|
| 31 |
-
# even when the LLM forgets to include MEDIA: tags in its response
|
| 32 |
COPY scripts/patch_auto_media.py /tmp/patch_auto_media.py
|
| 33 |
RUN python3 /tmp/patch_auto_media.py; rm -f /tmp/patch_auto_media.py
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
# Install Node.js 23
|
| 36 |
RUN ARCH=$(dpkg --print-architecture) \
|
| 37 |
&& if [ "$ARCH" = "amd64" ]; then NODE_ARCH="x64"; else NODE_ARCH="$ARCH"; fi \
|
|
|
|
| 18 |
pip install --quiet -e "/app/hermes-agent[feishu,mcp,cron,pty]" 2>&1 | tail -10
|
| 19 |
|
| 20 |
# Patch: add document file extensions to auto-detection for native delivery
|
|
|
|
| 21 |
COPY scripts/patch_file_delivery.py /tmp/patch_file_delivery.py
|
| 22 |
RUN python3 /tmp/patch_file_delivery.py; rm -f /tmp/patch_file_delivery.py
|
| 23 |
|
|
|
|
| 26 |
COPY patches/hermes-agent/tools/send_message_tool.py /app/hermes-agent/tools/send_message_tool.py
|
| 27 |
|
| 28 |
# Patch: Auto-inject MEDIA: tags from write_file tool calls
|
|
|
|
|
|
|
| 29 |
COPY scripts/patch_auto_media.py /tmp/patch_auto_media.py
|
| 30 |
RUN python3 /tmp/patch_auto_media.py; rm -f /tmp/patch_auto_media.py
|
| 31 |
|
| 32 |
+
# Patch: Auto-resolve relative media paths to absolute paths
|
| 33 |
+
# ROOT CAUSE FIX: LLM often uses bare filenames in MEDIA: tags (e.g. "report.md")
|
| 34 |
+
# which causes send_document() to fail with "File not found"
|
| 35 |
+
COPY scripts/patch_resolve_media_paths.py /tmp/patch_resolve_media_paths.py
|
| 36 |
+
RUN python3 /tmp/patch_resolve_media_paths.py; rm -f /tmp/patch_resolve_media_paths.py
|
| 37 |
+
|
| 38 |
# Install Node.js 23
|
| 39 |
RUN ARCH=$(dpkg --print-architecture) \
|
| 40 |
&& if [ "$ARCH" = "amd64" ]; then NODE_ARCH="x64"; else NODE_ARCH="$ARCH"; fi \
|
scripts/patch_resolve_media_paths.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Patch hermes-agent gateway to auto-resolve relative media paths.
|
| 3 |
+
|
| 4 |
+
ROOT CAUSE: When the LLM uses write_file, it often saves files with relative
|
| 5 |
+
paths like "广东天气预报.md" or "/tmp/广东天气预报.md", then puts
|
| 6 |
+
"MEDIA:广东天气预报.md" in the response. The gateway's extract_media()
|
| 7 |
+
parses this bare filename, but the subsequent send_document() call fails
|
| 8 |
+
because the path is relative and os.path.isfile() returns False.
|
| 9 |
+
|
| 10 |
+
FIX: After extract_media() returns the media_files list, scan each path:
|
| 11 |
+
1. If already absolute and exists → keep as-is
|
| 12 |
+
2. If relative → search well-known directories (/tmp/, /data/hermes/uploads/,
|
| 13 |
+
current working directory, HERMES_HOME) for a file with that name
|
| 14 |
+
3. Replace the path with the resolved absolute path
|
| 15 |
+
4. If still not found → keep the original (will fail with clear error)
|
| 16 |
+
|
| 17 |
+
This is a surgical patch to the _deliver_response() method in base.py,
|
| 18 |
+
inserted right after the "media_files, response = self.extract_media(response)"
|
| 19 |
+
line.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import re
|
| 23 |
+
import sys
|
| 24 |
+
import os
|
| 25 |
+
import glob
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def patch_file(filepath: str):
|
| 29 |
+
with open(filepath, 'r') as f:
|
| 30 |
+
content = f.read()
|
| 31 |
+
|
| 32 |
+
# Find the insertion point: right after extract_media() call
|
| 33 |
+
old = """\
|
| 34 |
+
# Extract MEDIA:<path> tags (from TTS tool) before other processing
|
| 35 |
+
media_files, response = self.extract_media(response)"""
|
| 36 |
+
|
| 37 |
+
new = """\
|
| 38 |
+
# Extract MEDIA:<path> tags (from TTS tool) before other processing
|
| 39 |
+
media_files, response = self.extract_media(response)
|
| 40 |
+
|
| 41 |
+
# Auto-resolve relative media paths to absolute paths.
|
| 42 |
+
# The LLM often uses bare filenames in MEDIA: tags (e.g. "report.md")
|
| 43 |
+
# which fail because send_document() can't find them.
|
| 44 |
+
_media_search_dirs = ['/tmp', '/data/hermes/uploads', os.getcwd()]
|
| 45 |
+
_hermes_home = os.path.expanduser('~/.hermes')
|
| 46 |
+
if _hermes_home not in _media_search_dirs:
|
| 47 |
+
_media_search_dirs.append(_hermes_home)
|
| 48 |
+
_resolved_media = []
|
| 49 |
+
for _mpath, _mvoice in media_files:
|
| 50 |
+
if os.path.isabs(_mpath) and os.path.isfile(_mpath):
|
| 51 |
+
_resolved_media.append((_mpath, _mvoice))
|
| 52 |
+
continue
|
| 53 |
+
# Try to find the file in well-known directories
|
| 54 |
+
_basename = os.path.basename(_mpath)
|
| 55 |
+
_found = None
|
| 56 |
+
for _search_dir in _media_search_dirs:
|
| 57 |
+
_candidate = os.path.join(_search_dir, _basename)
|
| 58 |
+
if os.path.isfile(_candidate):
|
| 59 |
+
_found = _candidate
|
| 60 |
+
break
|
| 61 |
+
if _found:
|
| 62 |
+
logger.info("[%s] Resolved relative media path '%s' -> '%s'", self.name, _mpath, _found)
|
| 63 |
+
_resolved_media.append((_found, _mvoice))
|
| 64 |
+
else:
|
| 65 |
+
# Keep original — will fail with a clear error message
|
| 66 |
+
_resolved_media.append((_mpath, _mvoice))
|
| 67 |
+
logger.warning("[%s] Could not resolve media path '%s' — searched %s", self.name, _mpath, _media_search_dirs)
|
| 68 |
+
media_files = _resolved_media"""
|
| 69 |
+
|
| 70 |
+
if old not in content:
|
| 71 |
+
print(f"WARNING: Could not find insertion point in {filepath}", file=sys.stderr)
|
| 72 |
+
print("The upstream code may have changed. Skipping this patch.", file=sys.stderr)
|
| 73 |
+
sys.exit(0)
|
| 74 |
+
|
| 75 |
+
content = content.replace(old, new, 1)
|
| 76 |
+
|
| 77 |
+
with open(filepath, 'w') as f:
|
| 78 |
+
f.write(content)
|
| 79 |
+
|
| 80 |
+
print(f"Patched {filepath}: auto-resolve relative media paths before sending")
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
if __name__ == "__main__":
|
| 84 |
+
candidates = [
|
| 85 |
+
"/app/hermes-agent/gateway/platforms/base.py",
|
| 86 |
+
]
|
| 87 |
+
candidates.extend(glob.glob("/app/venv/lib/**/gateway/platforms/base.py", recursive=True))
|
| 88 |
+
|
| 89 |
+
filepath = None
|
| 90 |
+
for c in candidates:
|
| 91 |
+
if os.path.isfile(c):
|
| 92 |
+
filepath = c
|
| 93 |
+
break
|
| 94 |
+
|
| 95 |
+
if not filepath:
|
| 96 |
+
print("WARNING: base.py not found in any candidate location", file=sys.stderr)
|
| 97 |
+
print(f"Checked: {candidates}", file=sys.stderr)
|
| 98 |
+
print("Skipping patch_resolve_media_paths.", file=sys.stderr)
|
| 99 |
+
sys.exit(0)
|
| 100 |
+
|
| 101 |
+
patch_file(filepath)
|