Studio: r23 fixes - inline fence prose guard, I need to / visit / gather verbs
- _has_unclosed_code_fence() splits inline fence handling from column-0 handling. Inline fences (text before the delimiters on the same line) now require a clean info-string trailing (no internal whitespace, no leading space) to count. Prose mentions like "Use \`\`\` to start" or "Use \`\`\`python to open a block." no longer falsely flag the response as mid-stream. Column-0 fences keep their permissive info-string parsing. - _INTENT_SIGNAL + _DIRECT_NUMBERED_PLAN_FRAMING + _BARE_INTENT_NUMBERED_PLAN + _STRONG_INTENT_BEFORE_LIST all add "i need to" as a direct first-person intent phrase. - _DIRECT_NUMBERED_PLAN_FRAMING and _BARE_INTENT_NUMBERED_PLAN add visit / access / navigate / gather / collect / identify / update / edit so browser-navigation and data-gathering plans still re-prompt. - _LOCAL_ACTION_VERBS adds gather / collect / identify so numbered lists whose item verbs match these still trigger the intent-+-action-item cross-check.
This commit is contained in:
parent
9f3c49a733
commit
5564cfa1f4
2 changed files with 84 additions and 16 deletions
|
|
@ -88,7 +88,7 @@ _INTENT_SIGNAL = re.compile(
|
|||
# Handles both straight and curly apostrophes.
|
||||
# Excludes "I can", "I should", "I want to", "let's" which
|
||||
# appear frequently in direct answers / explanations.
|
||||
r"\b(i['\u2019](ll|m going to|m gonna)|i am (going to|gonna)|i will|i shall|let me|allow me)\b"
|
||||
r"\b(i['\u2019](ll|m going to|m gonna)|i am (going to|gonna)|i will|i shall|i need to|let me|allow me)\b"
|
||||
r"|"
|
||||
# Step/plan framing: "First ...", "Step 1:", "Here's my plan".
|
||||
r"\b(?:first\b|step \d+:?|here['\u2019]?s (?:my |the |a )?(?:plan|approach))"
|
||||
|
|
@ -194,10 +194,11 @@ _EXPLICIT_PLAN_HEADER = re.compile(
|
|||
_DIRECT_NUMBERED_PLAN_FRAMING = re.compile(
|
||||
r"(?:"
|
||||
r"\b(?:i['’](?:ll|m going to|m gonna)|i am (?:going to|gonna)|"
|
||||
r"i will|i shall|let me|allow me|now i|next i)\b"
|
||||
r"i will|i shall|i need to|let me|allow me|now i|next i)\b"
|
||||
r"[^\r\n]{0,160}"
|
||||
r"\b(?:open|read|search|look (?:this |that |it |them )?up|browse|"
|
||||
r"google|find|check|verify|compare|review|inspect|examine|"
|
||||
r"visit|access|navigate|gather|collect|"
|
||||
r"do (?:this|these|the following|it)|"
|
||||
r"take (?:these|the following) steps|"
|
||||
r"follow (?:these|the following) steps|"
|
||||
|
|
@ -228,12 +229,13 @@ _FENCE_RUN_RE = re.compile(
|
|||
def _has_unclosed_code_fence(text: str) -> bool:
|
||||
"""True if ``text`` contains a code fence whose closer is missing.
|
||||
|
||||
Each line is scanned so inline openers like ``First. \\`\\`\\`python``
|
||||
are tracked. Prose mentions such as ``Use \\`\\`\\` to start a
|
||||
fence.`` are filtered out by requiring the trailing characters
|
||||
after the fence run to look like a CommonMark info string: empty,
|
||||
or starting with a non-space character (so prose with a leading
|
||||
space disqualifies the run).
|
||||
Each line is scanned with ``search`` so inline openers like
|
||||
``First. \\`\\`\\`python`` are tracked. To avoid reading prose
|
||||
mentions of triple backticks as openers, an INLINE fence (fence
|
||||
not at line start) is only accepted when its trailing characters
|
||||
look like a clean CommonMark info-string token with no internal
|
||||
whitespace. Column-0 fences always count, so multi-token info
|
||||
strings like ``\\`\\`\\`python linenums=1`` still work.
|
||||
"""
|
||||
active_char: Optional[str] = None
|
||||
active_len = 0
|
||||
|
|
@ -245,10 +247,14 @@ def _has_unclosed_code_fence(text: str) -> bool:
|
|||
raw_trailing = line[m.end() :]
|
||||
trailing = raw_trailing.strip()
|
||||
ch = fence[0]
|
||||
# Prose mention guard: a real fence line never has a space
|
||||
# immediately after the delimiters followed by sentence text.
|
||||
if raw_trailing and raw_trailing[0] == " " and trailing:
|
||||
continue
|
||||
is_inline = bool(line[: m.start()].strip())
|
||||
# Inline + multi-word trailing or leading-space trailing both
|
||||
# read as prose ("Use ``` to start", "Use ```python to open").
|
||||
if is_inline:
|
||||
if raw_trailing and raw_trailing[0] == " " and trailing:
|
||||
continue
|
||||
if trailing and (" " in trailing or "\t" in trailing):
|
||||
continue
|
||||
if active_char is None:
|
||||
active_char = ch
|
||||
active_len = len(fence)
|
||||
|
|
@ -308,7 +314,8 @@ def _is_empty_markup_skeleton(matched: str) -> bool:
|
|||
_LOCAL_ACTION_VERBS = (
|
||||
r"load|inspect|parse|"
|
||||
r"calculate|compute|analy[sz]e|extract|"
|
||||
r"run|execute|fetch|download|query"
|
||||
r"run|execute|fetch|download|query|"
|
||||
r"gather|collect|identify"
|
||||
)
|
||||
_NUMBERED_ACTION_ITEM = re.compile(
|
||||
rf"(?:^|\r?\n)[ \t]*\d+\.[ \t]+(?:{_LOCAL_ACTION_VERBS})\b",
|
||||
|
|
@ -320,7 +327,7 @@ _NUMBERED_ACTION_ITEM = re.compile(
|
|||
# numbered-list cross-check.
|
||||
_STRONG_INTENT_BEFORE_LIST = re.compile(
|
||||
r"\b(?:i['’](?:ll|m going to|m gonna)|i am (?:going to|gonna)|"
|
||||
r"i will|i shall|let me|allow me|now i|next i)\b",
|
||||
r"i will|i shall|i need to|let me|allow me|now i|next i)\b",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
|
@ -333,11 +340,12 @@ _STRONG_INTENT_BEFORE_LIST = re.compile(
|
|||
# signal that this is a tool stall.
|
||||
_BARE_INTENT_NUMBERED_PLAN = re.compile(
|
||||
r"\b(?:i['’](?:ll|m going to|m gonna)|i am (?:going to|gonna)|"
|
||||
r"i will|i shall|let me|allow me|now i|next i)\s*:[ \t]*"
|
||||
r"i will|i shall|i need to|let me|allow me|now i|next i)\s*:[ \t]*"
|
||||
r"(?:\r?\n)[ \t]*\d+\.[ \t]+"
|
||||
r"(?:open|read|search|look up|check|verify|create|build|add|set up|"
|
||||
r"load|inspect|parse|calculate|compute|analy[sz]e|extract|run|execute|"
|
||||
r"fetch|download|query|summari[sz]e|implement|generate|draft|write)\b",
|
||||
r"fetch|download|query|summari[sz]e|implement|generate|draft|write|"
|
||||
r"visit|access|navigate|gather|collect|identify|update|edit)\b",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -985,6 +985,66 @@ def test_no_reprompt_on_code_fence_containing_markup_literal():
|
|||
assert not _would_reprompt(content), content
|
||||
|
||||
|
||||
def test_reprompts_on_i_need_to_numbered_plan():
|
||||
"""``First, I need to:`` / ``I need to ...`` numbered plans are
|
||||
tool stalls and still re-prompt."""
|
||||
samples = [
|
||||
(
|
||||
"First, I need to:\n"
|
||||
"1. Read the uploaded file.\n"
|
||||
"2. Summarize it."
|
||||
),
|
||||
(
|
||||
"I need to fetch the latest data:\n"
|
||||
"1. Query the price.\n"
|
||||
"2. Format the answer."
|
||||
),
|
||||
(
|
||||
"I'll:\n"
|
||||
"1. Gather the relevant files.\n"
|
||||
"2. Identify the issue."
|
||||
),
|
||||
]
|
||||
for content in samples:
|
||||
assert not _has_answer_artifact(content), content
|
||||
assert _would_reprompt(content), content
|
||||
|
||||
|
||||
def test_reprompts_on_visit_or_access_numbered_plan():
|
||||
"""First-person + browser/navigation verbs (visit/access/navigate)
|
||||
+ numbered list is a tool stall."""
|
||||
samples = [
|
||||
(
|
||||
"I'll visit the official site:\n"
|
||||
"1. Open the homepage.\n"
|
||||
"2. Read the release notes.\n"
|
||||
"3. Summarize."
|
||||
),
|
||||
(
|
||||
"Let me access the GitHub repo:\n"
|
||||
"1. Open the README.\n"
|
||||
"2. Identify the install instructions."
|
||||
),
|
||||
]
|
||||
for content in samples:
|
||||
assert not _has_answer_artifact(content), content
|
||||
assert _would_reprompt(content), content
|
||||
|
||||
|
||||
def test_no_reprompt_on_inline_backtick_python_prose_after_code():
|
||||
"""``Use ```python to start a Python fence.`` is prose after a
|
||||
completed answer; it must NOT be treated as an unclosed fence."""
|
||||
content = (
|
||||
"Here is the snippet:\n"
|
||||
"```python\n"
|
||||
"print(1)\n"
|
||||
"```\n"
|
||||
"Use ```python to start a Python block in your reply."
|
||||
)
|
||||
assert _has_answer_artifact(content)
|
||||
assert not _would_reprompt(content)
|
||||
|
||||
|
||||
def test_reprompts_on_take_or_follow_steps_numbered_plan():
|
||||
"""``I'll take these steps:`` / ``I will follow these steps:`` +
|
||||
numbered list of work items is a plan stall."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue