AutoFix commited on
Commit
9d9a46e
·
1 Parent(s): da7768b

fix: auto-resolve relative media paths before sending (ROOT CAUSE)

Browse files

The 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

Files changed (2) hide show
  1. Dockerfile +6 -3
  2. 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)