115 lines
7.3 KiB
Python
115 lines
7.3 KiB
Python
"""Narrow execution requirements grounded in the latest human instruction."""
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
|
||
from backend.config import settings
|
||
from backend.prospecting.contracts import IntentPlan, ProspectingBrief, TARGET_QUESTION
|
||
from backend.browser.preflight_policy import PreflightPolicy, compile_preflight_policy
|
||
|
||
|
||
def _clauses(message: str):
|
||
text = re.sub(r"```[\s\S]*?```", "", message)
|
||
# A tool name formatted inline may still be an instruction. Whole quoted
|
||
# requests, code examples and block quotations remain untrusted material.
|
||
text = re.sub(r"`(resolve_reply_target(?:\([^`\n]*\))?)`", r"\1", text)
|
||
text = re.sub(r'''“[^”]*”|‘[^’]*’|「[^」]*」|『[^』]*』|"[^"\n]*"|'[^'\n]*'|`[^`\n]*`''', "", text)
|
||
text = "\n".join(line for line in text.splitlines() if not line.lstrip().startswith(">"))
|
||
for clause in re.split(r"[,,。;;!?!?\n]|(?:但是|但要|但请|不过|而是)", text):
|
||
if clause.strip() and not re.search(r"例如|比如|示例|参考案例|只是.{0,8}(?:引用|日志|描述)|(?:日志|文档|网页|截图).{0,8}(?:写着|提示|显示|内容)|(?:解释|介绍|讨论|了解).{0,15}(?:resolve_reply_target|网页回查|返回原笔记)|resolve_reply_target.{0,6}(?:是什么|什么意思|怎么用)", clause):
|
||
yield clause.strip()
|
||
|
||
|
||
_BROWSER_RECHECK = re.compile(
|
||
r"resolve_reply_target(?:\([^)]*\))?"
|
||
r"|(?:原生历史|浏览历史|历史记录)(?:返回|回退|后退)"
|
||
r"|(?:原生)?(?:历史)?(?:返回|回到|回退|退回)(?:到)?(?:原|刚才|之前|该|这篇|同一篇|实际|真实|当前|已保存的)?(?:笔记|详情页|页面|网页|浏览器)"
|
||
r"|(?:网页|页面|浏览器|原页|原笔记|详情页)(?:上|中|里)?(?:实际|重新|再次|现场|直接|进行|去|上)?(?:回查|核对|复核|检查|确认|查证|验证)"
|
||
r"|(?:回查|核对|复核|检查|确认|查证|验证)(?:一下|当前|实际|真实)?(?:网页|页面|原页|原笔记|浏览器)"
|
||
r"|(?:点击|打开|进入).{0,20}(?:回复前缀|回复对象|目标主页|作者主页|发言人主页|实际主页|真实主页).{0,12}(?:核|查|确认|验证)"
|
||
)
|
||
_NEGATIVE_PREFIX = re.compile(r"(?:不要|不用|无需|不必|不需要|不能|别|不再|停止|取消|放弃|暂不|先不|不是|不使用|不调用|不执行)[^,,。;;]{0,10}$")
|
||
_NEGATIVE_SUFFIX = re.compile(r"\s*(?:先|暂时)?(?:不用|无需|不必|不要|取消|停止|不执行|不需要)(?:做|执行|操作)?")
|
||
_BROWSER_DISABLED = re.compile(r"(?:不|不需要|无需|不用|不必|不要|别)(?:再|实际)?(?:操作|打开|访问|使用|进入)(?:任何|实际)?(?:网页|页面|浏览器)")
|
||
_BROWSER_CANCELLED = re.compile(r"(?:取消|停止|放弃|不再|不要|不用|不需要|无需|不必)(?:(?:本次|本轮|当前|实际)的?)?(?:网页|页面|浏览器)?回查")
|
||
|
||
|
||
def browser_recheck_requested(message: str) -> bool:
|
||
"""Stored sources do not imply an offline job when a web action is explicit."""
|
||
page_required = bool(settings.reply_relation_preflight_enabled and compile_preflight_policy(message).active and re.search(
|
||
r"(?:当前|这篇|原)(?:笔记|页面).{0,16}预检|预检.{0,16}(?:当前|这篇|原)(?:笔记|页面)", message))
|
||
actions_required = set()
|
||
for clause in _clauses(message):
|
||
if _BROWSER_DISABLED.search(clause) or _BROWSER_CANCELLED.search(clause):
|
||
page_required = False
|
||
actions_required.clear()
|
||
continue
|
||
for match in _BROWSER_RECHECK.finditer(clause):
|
||
value = match.group()
|
||
is_tool = value.startswith("resolve_reply_target")
|
||
if is_tool and not (
|
||
re.search(r"(?:用|使用|调用|执行|运行)(?:工具)?\s*$", clause[:match.start()])
|
||
or re.match(r"\s*(?:来|去)?(?:核对|核实|核|回查|检查)", clause[match.end():])
|
||
or _NEGATIVE_PREFIX.search(clause[:match.start()]) or _NEGATIVE_SUFFIX.match(clause[match.end():])
|
||
or clause == match.group()):
|
||
continue
|
||
negative = bool(_NEGATIVE_PREFIX.search(clause[:match.start()])
|
||
or _NEGATIVE_SUFFIX.match(clause[match.end():]))
|
||
action = ('resolve_reply_target' if is_tool else 'return' if re.match(
|
||
r"(?:原生历史|浏览历史|历史记录|(?:原生)?(?:历史)?(?:返回|回到|回退|退回))", value)
|
||
else 'profile' if re.match(r"点击|打开|进入", value) else None)
|
||
if action:
|
||
# Cancelling one browser operation does not cancel an
|
||
# independent instruction to inspect the actual page.
|
||
if negative:
|
||
actions_required.discard(action)
|
||
else:
|
||
actions_required.add(action)
|
||
elif negative:
|
||
page_required = False
|
||
actions_required.clear()
|
||
else:
|
||
page_required = True
|
||
return page_required or bool(actions_required)
|
||
|
||
|
||
def browser_recheck_brief(brief: ProspectingBrief, message: str) -> ProspectingBrief:
|
||
# A focused read-only recheck has an existing object and must not be
|
||
# short-circuited by a discovery headcount or an export-only route.
|
||
if browser_recheck_requested(message) and brief.intent in {"discover", "export"}:
|
||
return brief.model_copy(update={"intent": "inspect"})
|
||
return brief
|
||
|
||
|
||
def ground_execution_plan(plan: IntentPlan, message: str) -> IntentPlan:
|
||
policy = (compile_preflight_policy(message, plan.brief.max_notes)
|
||
if settings.reply_relation_preflight_enabled else PreflightPolicy())
|
||
if policy.active:
|
||
brief = plan.brief.model_copy(update={"intent": "inspect", "max_notes": policy.max_notes})
|
||
return plan.model_copy(update={"brief": brief, "reuse_existing_sources": False,
|
||
"missing": [item for item in plan.missing if item != TARGET_QUESTION],
|
||
"summary": "只读预检当前可见回复关系,按实际记录交付范围与未读部分,本轮不保存来源或生成名单。",
|
||
"stages": ["核对访问范围", "只读关系预检", "交付预检结果"]})
|
||
if not browser_recheck_requested(message):
|
||
return plan
|
||
brief = browser_recheck_brief(plan.brief, message)
|
||
missing = [item for item in plan.missing if item != TARGET_QUESTION or TARGET_QUESTION in brief.missing()]
|
||
return plan.model_copy(update={
|
||
"brief": brief, "reuse_existing_sources": False, "missing": missing,
|
||
"summary": "按你的要求回到实际页面核对既有来源,保留采集范围和未知项。",
|
||
"stages": list(dict.fromkeys(["实际页面回查", *plan.stages])),
|
||
})
|
||
|
||
|
||
def recheck_without_analysis(message: str) -> bool:
|
||
"""Only an explicit analysis prohibition in an explicit recheck can skip it."""
|
||
if not browser_recheck_requested(message):
|
||
return False
|
||
forbidden = False
|
||
for clause in _clauses(message):
|
||
if re.search(r"(?:不|不想|无需|不用|不必|不要|别|禁止|停止|取消)(?:再|重新|重复)?(?:搜索(?:或|和|及))?(?:再|重新|重复)?(?:分析(?:名单|候选|需求)?|做(?:名单|候选|需求)分析)", clause):
|
||
forbidden = True
|
||
elif re.search(r"(?:重新|再次|再|仍要|继续|需要|只需|仍需|请|要)分析(?:名单|候选|需求)?", clause):
|
||
forbidden = False
|
||
return forbidden
|