-
Notifications
You must be signed in to change notification settings - Fork 0
feat: native multimodal image support (configurable, opt-in) #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
567306b
fd6c506
48af5fb
045d480
1d21a91
df54e58
c9da2f9
4a4424e
f7aea2e
84e700d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,3 +58,4 @@ mini-swe-agent/ | |
| # Nix | ||
| .direnv/ | ||
| result | ||
| hermes_state.db | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 estimate_request_tokens_rough not updated for multimodal, causes massive token overcount triggering false compression
This inflated estimate feeds into the preflight compression check at (Refers to lines 943-944) Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -904,8 +904,24 @@ def estimate_tokens_rough(text: str) -> int: | |
|
|
||
| def estimate_messages_tokens_rough(messages: List[Dict[str, Any]]) -> int: | ||
| """Rough token estimate for a message list (pre-flight only).""" | ||
| total_chars = sum(len(str(msg)) for msg in messages) | ||
| return total_chars // 4 | ||
| total = 0 | ||
| for msg in messages: | ||
| content = msg.get("content", "") if isinstance(msg, dict) else str(msg) | ||
| if isinstance(content, list): | ||
| # Multimodal content array — count text blocks normally, | ||
| # use fixed estimate for image blocks (base64 is huge but | ||
| # actual token cost is ~1600 per image for most providers). | ||
| for block in content: | ||
| btype = block.get("type", "") if isinstance(block, dict) else "" | ||
| if btype == "text": | ||
| total += len(block.get("text", "")) // 4 | ||
| elif btype in ("image_url", "image"): | ||
| total += 1600 # approximate per-image token cost | ||
| else: | ||
| total += len(str(block)) // 4 | ||
| else: | ||
| total += len(str(msg)) // 4 | ||
|
Comment on lines
+922
to
+923
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: estimate_messages_tokens_rough non-list path uses str(msg) instead of str(content) In the new multimodal-aware Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| return total | ||
|
|
||
|
|
||
| def estimate_request_tokens_rough( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -300,7 +300,14 @@ def __init__(self, config: PlatformConfig): | |
| self._runner: Optional["web.AppRunner"] = None | ||
| self._site: Optional["web.TCPSite"] = None | ||
| self._response_store = ResponseStore() | ||
| self._session_db: Optional[Any] = None # Lazy-init SessionDB for session continuity | ||
| # Shared SessionDB singleton — avoids creating (and leaking) a new | ||
| # SQLite connection on every /v1/chat/completions request. | ||
| self._session_db = None | ||
| try: | ||
| from hermes_state import SessionDB | ||
| self._session_db = SessionDB() | ||
| except Exception: | ||
| pass | ||
|
Comment on lines
+305
to
+310
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: API server SessionDB now created eagerly in init instead of lazily At Was this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
| @staticmethod | ||
| def _parse_cors_origins(value: Any) -> tuple[str, ...]: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 _merge_summary_into_tail permanently destroys multimodal image blocks in protected tail message
When compression triggers the
_merge_summary_into_tailpath (role alternation conflict),_flatten_multimodal_content()is called on the first tail message's content atagent/context_compressor.py:677. This converts a multimodal content array (with image blocks) into a plain text string, permanently replacing the structured content withsummary + "\n\n" + flattened_text. The image blocks (base64 references) are replaced with"[Attached image]"placeholders and lost. After compression, when these messages are sent to the LLM, the model can no longer see the images that were in the protected tail — defeating the purpose of tail protection for multimodal messages.Prompt for agents
Was this helpful? React with 👍 or 👎 to provide feedback.