fix: skip empty untrusted context blocks to prevent model hallucination
untrusted_context_message() now returns None when content is empty or whitespace-only. Previously an empty document would produce a blank UNTRUSTED_SOURCE_DATA wrapper that confused Gemma into thinking the user pasted a placeholder template. Callers already use truthiness guards (if _doc_message:) for insertion, so None propagates cleanly. Two _protected setter sites needed explicit guards added (doc + email paths in agent_loop.py).
This commit is contained in:
parent
6c9e04fa69
commit
60452712b2
2 changed files with 12 additions and 4 deletions
|
|
@ -1504,7 +1504,8 @@ def _build_system_prompt(
|
||||||
"knowing the user's personal style."
|
"knowing the user's personal style."
|
||||||
)
|
)
|
||||||
_doc_message = untrusted_context_message("active editor document", doc_ctx)
|
_doc_message = untrusted_context_message("active editor document", doc_ctx)
|
||||||
_doc_message["_protected"] = True
|
if _doc_message:
|
||||||
|
_doc_message["_protected"] = True
|
||||||
|
|
||||||
# Auto-detect suggestion mode
|
# Auto-detect suggestion mode
|
||||||
_last_user_msg = ""
|
_last_user_msg = ""
|
||||||
|
|
@ -1584,7 +1585,8 @@ def _build_system_prompt(
|
||||||
f"open email's sender.\n"
|
f"open email's sender.\n"
|
||||||
)
|
)
|
||||||
_email_message = untrusted_context_message("active email reader", email_ctx)
|
_email_message = untrusted_context_message("active email reader", email_ctx)
|
||||||
_email_message["_protected"] = True
|
if _email_message:
|
||||||
|
_email_message["_protected"] = True
|
||||||
|
|
||||||
# Inject writing style for any email writing path. This is deliberately
|
# Inject writing style for any email writing path. This is deliberately
|
||||||
# broader than read/list: models may compose via send_email, reply_to_email,
|
# broader than read/list: models may compose via send_email, reply_to_email,
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ def _sanitize_label(label: str) -> str:
|
||||||
return label
|
return label
|
||||||
|
|
||||||
|
|
||||||
def untrusted_context_message(label: str, content: Any) -> Dict[str, Any]:
|
def untrusted_context_message(label: str, content: Any) -> Dict[str, Any] | None:
|
||||||
"""Return an LLM message that keeps retrieved/source text out of system role.
|
"""Return an LLM message that keeps retrieved/source text out of system role.
|
||||||
|
|
||||||
The template is structured so that *only* the hardcoded
|
The template is structured so that *only* the hardcoded
|
||||||
|
|
@ -69,9 +69,15 @@ def untrusted_context_message(label: str, content: Any) -> Dict[str, Any]:
|
||||||
caller-derived text is placed in the pre-guard trusted framing zone.
|
caller-derived text is placed in the pre-guard trusted framing zone.
|
||||||
The source label and the body content are both placed *inside* the
|
The source label and the body content are both placed *inside* the
|
||||||
guarded block where the LLM treats them as untrusted data.
|
guarded block where the LLM treats them as untrusted data.
|
||||||
|
|
||||||
|
Returns None when content is empty or whitespace-only — avoids
|
||||||
|
injecting blank wrapper blocks that confuse the model into thinking
|
||||||
|
the user pasted a placeholder template.
|
||||||
"""
|
"""
|
||||||
|
text = "" if content is None else str(content).strip()
|
||||||
|
if not text:
|
||||||
|
return None
|
||||||
safe_label = _sanitize_label(label)
|
safe_label = _sanitize_label(label)
|
||||||
text = "" if content is None else str(content)
|
|
||||||
text = _escape_guard_markers(text)
|
text = _escape_guard_markers(text)
|
||||||
return {
|
return {
|
||||||
"role": "user",
|
"role": "user",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue