fix: deterministic browser navigate for 'go back to <site>' (구글로 돌아가)
The 7B narrated '구글 메인으로 돌아갑니다' without acting, so the screen stayed on Naver. Split site intent into SEARCH vs NAV: nav words (돌아가/이동/열어/메인/go back) now drive controlBrowser.navigate to the site homepage directly, search words run controlBrowser.search — both deterministically, no LLM.
This commit is contained in:
@@ -833,25 +833,30 @@ _SITE_TOKEN_MAP = {
|
||||
"다음": "daum", "daum": "daum",
|
||||
"빙": "bing", "bing": "bing",
|
||||
}
|
||||
# Search / open intent words (Korean deployment + English). Kept explicit because
|
||||
# this is a DETERMINISTIC fast-path — the small chat model can't be trusted to
|
||||
# emit the controlBrowser call reliably, so when the user names a site AND
|
||||
# expresses a search/open intent we run it directly, no LLM judgement.
|
||||
_SEARCH_INTENT_WORDS = (
|
||||
"검색해줘", "검색해", "검색", "찾아줘", "찾아봐", "찾아", "열어줘", "열어",
|
||||
"들어가줘", "들어가", "띄워줘", "띄워", "보여줘",
|
||||
"search for", "search", "look up", "find", "open", "go to", "navigate",
|
||||
# Site homepages for the navigate (go-to / go-back) intent.
|
||||
_SITE_HOME = {
|
||||
"naver": "naver.com", "google": "google.com", "daum": "daum.net",
|
||||
"youtube": "youtube.com", "bing": "bing.com",
|
||||
}
|
||||
# SEARCH intent (run a query on the site) vs NAV intent (just open / go back to
|
||||
# the site). Explicit word lists because this is a DETERMINISTIC fast-path — the
|
||||
# chat model narrates ("돌아갑니다") without emitting the controlBrowser call, so
|
||||
# we act directly. "돌아가" (go back) is NAV, "검색" is SEARCH.
|
||||
_SEARCH_WORDS = ("검색", "찾아", "search", "look up", "find")
|
||||
_NAV_WORDS = (
|
||||
"돌아가", "돌아와", "이동", "가줘", "가자", "열어", "들어가", "띄워", "보여",
|
||||
"메인", "홈페이지", "홈으로", "back to", "go back", "go to", "open", "navigate",
|
||||
)
|
||||
_ALL_INTENT_WORDS = _SEARCH_WORDS + _NAV_WORDS + (
|
||||
"검색해줘", "검색해", "찾아줘", "찾아봐", "열어줘", "들어가줘", "띄워줘", "보여줘",
|
||||
)
|
||||
|
||||
|
||||
def _maybe_deterministic_site_search(text: str, db, cfg, language) -> Optional[str]:
|
||||
"""When broadcasting AND the user names a site AND asks to search/open it,
|
||||
run controlBrowser.search directly so the result actually appears on screen.
|
||||
|
||||
The 3B chat model reliably narrates ("검색하겠습니다") instead of emitting the
|
||||
controlBrowser tool call, so site-specified search is executed
|
||||
deterministically here rather than left to the model. Fail-open: any problem
|
||||
returns None and the normal reply flow continues.
|
||||
"""When broadcasting AND the user names a site AND asks to search or open/go
|
||||
to it, drive the on-screen browser directly (search or navigate) so it
|
||||
actually happens — the chat model only narrates ("돌아갑니다") without acting.
|
||||
Fail-open: any problem returns None and the normal reply flow continues.
|
||||
"""
|
||||
try:
|
||||
from . import turn_state
|
||||
@@ -865,27 +870,34 @@ def _maybe_deterministic_site_search(text: str, db, cfg, language) -> Optional[s
|
||||
if _t in low:
|
||||
site, tok = _key, _t
|
||||
break
|
||||
if not site or not any(w in low for w in _SEARCH_INTENT_WORDS):
|
||||
has_search = any(w in low for w in _SEARCH_WORDS)
|
||||
has_nav = any(w in low for w in _NAV_WORDS)
|
||||
if not site or not (has_search or has_nav):
|
||||
return None
|
||||
import re
|
||||
q = re.sub(re.escape(tok) + r"(에서|에다가|에다|에|로|를|을)?", " ", text, flags=re.IGNORECASE)
|
||||
for w in sorted(_SEARCH_INTENT_WORDS, key=len, reverse=True):
|
||||
q = re.sub(re.escape(tok) + r"(에서|에다가|에다|에|로|를|을|으로)?", " ", text, flags=re.IGNORECASE)
|
||||
for w in sorted(_ALL_INTENT_WORDS, key=len, reverse=True):
|
||||
q = re.sub(re.escape(w), " ", q, flags=re.IGNORECASE)
|
||||
q = re.sub(r"\s+", " ", q).strip(" .,!?。")
|
||||
if not q:
|
||||
q = text
|
||||
|
||||
from ..tools.registry import run_tool_with_retries
|
||||
if has_search and len(q) >= 2:
|
||||
args = {"action": "search", "site": site, "query": q}
|
||||
else:
|
||||
# NAV (go back / open) — go to the site's homepage.
|
||||
args = {"action": "navigate", "url": _SITE_HOME.get(site, site)}
|
||||
res = run_tool_with_retries(
|
||||
db=db, cfg=cfg, tool_name="controlBrowser",
|
||||
tool_args={"action": "search", "site": site, "query": q},
|
||||
db=db, cfg=cfg, tool_name="controlBrowser", tool_args=args,
|
||||
system_prompt="", original_prompt="", redacted_text=redact(text),
|
||||
max_retries=1, language=language,
|
||||
)
|
||||
if res and getattr(res, "success", False):
|
||||
debug_log(f"deterministic site search executed: {site} '{q}'", "tools")
|
||||
debug_log(f"deterministic browser: {args}", "tools")
|
||||
if args["action"] == "navigate":
|
||||
return res.reply_text or f"{site} 메인 페이지로 이동했습니다."
|
||||
return res.reply_text or f"{site}에서 '{q}'를 검색해 화면에 띄웠습니다."
|
||||
except Exception as e: # noqa: BLE001
|
||||
debug_log(f"deterministic site search failed (fail-open): {e}", "tools")
|
||||
debug_log(f"deterministic browser failed (fail-open): {e}", "tools")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user