mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-26 17:38:36 +00:00
fix: also close orphaned single-backtick inline-code spans
ensure_closed_code_fences previously only handled triple-backtick (```) code-block fences. Single backtick (`) inline-code spans have the same problem: an orphaned opening backtick causes the remainder of the message to render as inline code on Discord and other platforms. After balancing triple-backtick fences, strip complete ```...``` regions and count remaining standalone backtick markers. If odd, append a closing backtick. Same trade-off as the triple-backtick fix: a stray closing backtick may create a brief empty inline-code span, which is far less harmful than the rest of the message being inline code.
This commit is contained in:
parent
3e2fe3fcab
commit
b932997f44
2 changed files with 102 additions and 23 deletions
|
|
@ -74,15 +74,18 @@ def escape_code_fences_for_display(text: str) -> str:
|
|||
|
||||
|
||||
def ensure_closed_code_fences(text: str) -> str:
|
||||
"""Append a closing `` ``` `` fence if the text has an odd number of
|
||||
triple-backtick markers.
|
||||
"""Append a closing `` ``` `` fence and/or `` ` `` if the text has
|
||||
orphaned code-block or inline-code markers.
|
||||
|
||||
When model output is truncated mid-code-block (e.g. by token limits
|
||||
or a finish_reason="length"), the resulting message has an unclosed
|
||||
code fence. On Discord, Slack, and other platforms this causes
|
||||
everything after the orphaned fence to render as a single code block.
|
||||
The same problem applies to inline-code spans closed by a single
|
||||
backtick: an orphaned `` ` `` makes the remainder of the message
|
||||
render as inline code.
|
||||
|
||||
The fix is trivial: count `` ``` `` occurrences. If odd, append a
|
||||
Triple-backtick: count `` ``` `` occurrences. If odd, append a
|
||||
closing fence on its own line. This is safe because nested
|
||||
triple-backtick fences (e.g. a literal `` ``` `` inside a code block)
|
||||
are exceedingly rare in model output and, when they do appear, the
|
||||
|
|
@ -90,14 +93,35 @@ def ensure_closed_code_fences(text: str) -> str:
|
|||
of the message — far less harmful than the entire message being one
|
||||
giant code block.
|
||||
|
||||
Single backtick: after balancing triple-backtick fences, strip all
|
||||
complete `` ```…``` `` regions and count remaining standalone `` ` ``.
|
||||
If odd, append a closing inline-code backtick. Same trade-off: a
|
||||
stray closing backtick may produce a brief empty inline-code span,
|
||||
which is far less harmful than the rest of the message being rendered
|
||||
as inline code.
|
||||
|
||||
Returns:
|
||||
The input text with a closing fence appended if needed, or the
|
||||
The input text with closing markers appended if needed, or the
|
||||
input text unchanged.
|
||||
"""
|
||||
if not isinstance(text, str) or not text:
|
||||
return text
|
||||
|
||||
# Step 1: fix triple-backtick code-block fences (existing logic)
|
||||
if text.count("```") % 2 == 1:
|
||||
return text.rstrip("\n") + "\n```"
|
||||
text = text.rstrip("\n") + "\n```"
|
||||
|
||||
# Step 2: fix single-backtick inline-code spans
|
||||
# Remove complete ```…``` regions so their internal backticks don't
|
||||
# pollute the standalone count. Also remove any trailing unclosed
|
||||
# ``` that leaks through (defence in depth).
|
||||
import re
|
||||
without_fences = re.sub(r"```.*?```", "", text, flags=re.DOTALL)
|
||||
without_fences = re.sub(r"```[^`]*$", "", without_fences)
|
||||
|
||||
if without_fences.count("`") % 2 == 1:
|
||||
text = text + "`"
|
||||
|
||||
return text
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import pytest
|
|||
from unittest.mock import AsyncMock, MagicMock, patch, ANY
|
||||
|
||||
from gateway.platforms.base import BasePlatformAdapter
|
||||
from gateway.stream_consumer import GatewayStreamConsumer, StreamConsumerConfig
|
||||
from gateway.stream_consumer import GatewayStreamConsumer, StreamConsumerConfig, ensure_closed_code_fences
|
||||
|
||||
|
||||
# ── helpers ───────────────────────────────────────────────────────────────
|
||||
|
|
@ -340,37 +340,92 @@ class TestStreamConsumerFinalSendGap:
|
|||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# I. What the fix should do
|
||||
# I. ensure_closed_code_fences — triple-backtick fence balancing
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
class TestFixStub:
|
||||
"""If a _ensure_closed_code_fences() helper were added, these tests
|
||||
should pass."""
|
||||
class TestEnsureClosedCodeFences:
|
||||
"""Unit tests for the standalone ensure_closed_code_fences helper."""
|
||||
|
||||
@staticmethod
|
||||
def _ensure_closed_fences(text: str) -> str:
|
||||
"""Stub: append closing ``` if odd count and text doesn't already
|
||||
end with a closing fence on its own line."""
|
||||
if _odd_fences(text):
|
||||
return text.rstrip() + "\n```"
|
||||
return text
|
||||
# ── triple backtick ──────────────────────────────────────────────
|
||||
|
||||
def test_closes_unclosed(self):
|
||||
assert not _odd_fences(self._ensure_closed_fences(
|
||||
def test_closes_unclosed_triple(self):
|
||||
"""Unclosed ``` gets a closing fence appended."""
|
||||
assert not _odd_fences(ensure_closed_code_fences(
|
||||
"💭 **Reasoning:**\n```\ncut off"
|
||||
))
|
||||
|
||||
def test_noop_balanced(self):
|
||||
def test_noop_balanced_triple(self):
|
||||
"""Already-balanced ``` blocks are unchanged."""
|
||||
t = "```\nblock\n```\ncontent"
|
||||
assert self._ensure_closed_fences(t) == t
|
||||
assert ensure_closed_code_fences(t) == t
|
||||
|
||||
def test_noop_no_fence(self):
|
||||
"""Plain text without fences passes through."""
|
||||
t = "plain text"
|
||||
assert self._ensure_closed_fences(t) == t
|
||||
assert ensure_closed_code_fences(t) == t
|
||||
|
||||
def test_noop_already_ends_with_close(self):
|
||||
"""Text ending with a balanced ``` is unchanged."""
|
||||
t = "```\nblock\n```"
|
||||
assert self._ensure_closed_fences(t) == t
|
||||
assert ensure_closed_code_fences(t) == t
|
||||
|
||||
def test_closes_unclosed_mid_message(self):
|
||||
"""``` in the middle (not at end) still gets closed when odd."""
|
||||
t = "before\n```\nunclosed block\nmore text here"
|
||||
result = ensure_closed_code_fences(t)
|
||||
assert not _odd_fences(result)
|
||||
assert result.endswith("\n```")
|
||||
|
||||
# ── single backtick ──────────────────────────────────────────────
|
||||
|
||||
def test_closes_unclosed_single(self):
|
||||
"""Orphaned single backtick gets a closing backtick appended."""
|
||||
result = ensure_closed_code_fences("Here is `inline code")
|
||||
assert result == "Here is `inline code`"
|
||||
|
||||
def test_noop_balanced_single(self):
|
||||
"""Paired single backticks are unchanged."""
|
||||
t = "Here is `inline code` and more text."
|
||||
assert ensure_closed_code_fences(t) == t
|
||||
|
||||
def test_single_inside_triple_ignored(self):
|
||||
"""Backticks inside ``` regions are NOT counted for single-bt parity."""
|
||||
t = "```\n`code` inside\n```\noutside `text`"
|
||||
# outside has paired `text` → balanced, triple-blocks are balanced → no change
|
||||
assert ensure_closed_code_fences(t) == t
|
||||
|
||||
def test_single_outside_unclosed_after_triple(self):
|
||||
"""Unclosed single backtick outside ``` blocks gets fixed."""
|
||||
t = "```\nblock\n```\noutside `text"
|
||||
result = ensure_closed_code_fences(t)
|
||||
assert result == "outside `text`" or result.endswith("`")
|
||||
|
||||
def test_both_triple_and_single_unclosed(self):
|
||||
"""Both ``` and ` unclosed → both get closed."""
|
||||
result = ensure_closed_code_fences("```\ncode\nstill open `inline")
|
||||
assert result.endswith("`")
|
||||
assert "```\ncode\nstill open `inline`\n```" in result or result.count("```") % 2 == 0
|
||||
|
||||
def test_noop_empty_or_none(self):
|
||||
"""Empty/None returns unchanged."""
|
||||
assert ensure_closed_code_fences("") == ""
|
||||
assert ensure_closed_code_fences(None) is None
|
||||
|
||||
def test_single_inline_code_in_prose(self):
|
||||
"""Realistic prose with `handle: \"...\"` inline code."""
|
||||
# `handle: "abc"` is open – unbalanced single backtick
|
||||
t = (
|
||||
'LLM 看到 `_headroom.retrieval.handle` 的值,就是它要傳給'
|
||||
' `headroom_retrieve(hash="125f4ae286e24ad8c0816907"` 的那個字串。'
|
||||
)
|
||||
result = ensure_closed_code_fences(t)
|
||||
# After fix: the last unclosed ` gets closed at the end
|
||||
assert result.count("`") % 2 == 0
|
||||
|
||||
def test_multiple_single_backtick_pairs(self):
|
||||
"""Multiple correctly-paired single backtick spans are unchanged."""
|
||||
t = "Use `cmd1` for X and `cmd2` for Y."
|
||||
assert ensure_closed_code_fences(t) == t
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue