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",
|
"다음": "daum", "daum": "daum",
|
||||||
"빙": "bing", "bing": "bing",
|
"빙": "bing", "bing": "bing",
|
||||||
}
|
}
|
||||||
# Search / open intent words (Korean deployment + English). Kept explicit because
|
# Site homepages for the navigate (go-to / go-back) intent.
|
||||||
# this is a DETERMINISTIC fast-path — the small chat model can't be trusted to
|
_SITE_HOME = {
|
||||||
# emit the controlBrowser call reliably, so when the user names a site AND
|
"naver": "naver.com", "google": "google.com", "daum": "daum.net",
|
||||||
# expresses a search/open intent we run it directly, no LLM judgement.
|
"youtube": "youtube.com", "bing": "bing.com",
|
||||||
_SEARCH_INTENT_WORDS = (
|
}
|
||||||
"검색해줘", "검색해", "검색", "찾아줘", "찾아봐", "찾아", "열어줘", "열어",
|
# 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
|
||||||
"search for", "search", "look up", "find", "open", "go to", "navigate",
|
# 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]:
|
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,
|
"""When broadcasting AND the user names a site AND asks to search or open/go
|
||||||
run controlBrowser.search directly so the result actually appears on screen.
|
to it, drive the on-screen browser directly (search or navigate) so it
|
||||||
|
actually happens — the chat model only narrates ("돌아갑니다") without acting.
|
||||||
The 3B chat model reliably narrates ("검색하겠습니다") instead of emitting the
|
Fail-open: any problem returns None and the normal reply flow continues.
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from . import turn_state
|
from . import turn_state
|
||||||
@@ -865,27 +870,34 @@ def _maybe_deterministic_site_search(text: str, db, cfg, language) -> Optional[s
|
|||||||
if _t in low:
|
if _t in low:
|
||||||
site, tok = _key, _t
|
site, tok = _key, _t
|
||||||
break
|
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
|
return None
|
||||||
import re
|
import re
|
||||||
q = re.sub(re.escape(tok) + r"(에서|에다가|에다|에|로|를|을)?", " ", text, flags=re.IGNORECASE)
|
q = re.sub(re.escape(tok) + r"(에서|에다가|에다|에|로|를|을|으로)?", " ", text, flags=re.IGNORECASE)
|
||||||
for w in sorted(_SEARCH_INTENT_WORDS, key=len, reverse=True):
|
for w in sorted(_ALL_INTENT_WORDS, key=len, reverse=True):
|
||||||
q = re.sub(re.escape(w), " ", q, flags=re.IGNORECASE)
|
q = re.sub(re.escape(w), " ", q, flags=re.IGNORECASE)
|
||||||
q = re.sub(r"\s+", " ", q).strip(" .,!?。")
|
q = re.sub(r"\s+", " ", q).strip(" .,!?。")
|
||||||
if not q:
|
|
||||||
q = text
|
|
||||||
from ..tools.registry import run_tool_with_retries
|
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(
|
res = run_tool_with_retries(
|
||||||
db=db, cfg=cfg, tool_name="controlBrowser",
|
db=db, cfg=cfg, tool_name="controlBrowser", tool_args=args,
|
||||||
tool_args={"action": "search", "site": site, "query": q},
|
|
||||||
system_prompt="", original_prompt="", redacted_text=redact(text),
|
system_prompt="", original_prompt="", redacted_text=redact(text),
|
||||||
max_retries=1, language=language,
|
max_retries=1, language=language,
|
||||||
)
|
)
|
||||||
if res and getattr(res, "success", False):
|
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}'를 검색해 화면에 띄웠습니다."
|
return res.reply_text or f"{site}에서 '{q}'를 검색해 화면에 띄웠습니다."
|
||||||
except Exception as e: # noqa: BLE001
|
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
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user