montana/Русский/Логистика/patch_anti_hallucination4.py

166 lines
12 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""v3.40.9: Harder anti-hallucination — ban synonym patterns, enforce STOP after not-found,
fix tool name leaks, strip data source references."""
fixes = 0
PATH = '/opt/app/seafare_agent.py'
def read():
with open(PATH, 'r') as f:
return f.read()
def write(c):
with open(PATH, 'w') as f:
f.write(c)
c = read()
# ============================================================
# FIX 1: Strengthen CONTACTS RULE — add broker/agent directory limitation
# ============================================================
old = """- **\u26a0\ufe0f CONTACTS RULE \u2014 ZERO TOLERANCE: NEVER generate, guess, or invent email addresses, phone numbers, company names, or websites.** Only show contacts returned by search_contacts or get_vessel_details tools. If contacts are not in our DB \u2014 say "\u041a\u043e\u043d\u0442\u0430\u043a\u0442\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0431\u0430\u0437\u0435" and STOP. Do NOT create "alternative" contact lists. Do NOT guess email formats (like company@domain.xx). A wrong contact is worse than no contact."""
new = """- **\u26a0\ufe0f CONTACTS RULE \u2014 ZERO TOLERANCE: NEVER generate, guess, or invent email addresses, phone numbers, company names, or websites.** Only show contacts returned by search_contacts or get_vessel_details tools. If contacts are not in our DB \u2014 say "\u041a\u043e\u043d\u0442\u0430\u043a\u0442\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0431\u0430\u0437\u0435" and STOP. Do NOT create "alternative" contact lists. Do NOT guess email formats (like company@domain.xx). A wrong contact is worse than no contact.
- **BROKER/AGENT LIMITATION:** We do NOT have a broker/agent/freight forwarder directory. For questions like "\u0431\u0440\u043e\u043a\u0435\u0440\u044b \u0432 \u0411\u0430\u043a\u0443" / "agent contacts in Singapore" \u2014 say "\u0412 \u043d\u0430\u0448\u0435\u0439 \u0431\u0430\u0437\u0435 \u043d\u0435\u0442 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u0432 \u0438 \u0430\u0433\u0435\u043d\u0442\u043e\u0432. \u041c\u044b \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u043a\u043e\u043d\u0442\u0430\u043a\u0442\u044b \u0441\u0443\u0434\u043e\u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0435\u0432 \u0438 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0445 \u0441\u0443\u0434\u043e\u0432." and STOP. Do NOT invent broker companies or emails.
- **NOT FOUND = STOP RULE:** When you say "\u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e/not found/\u043d\u0435\u0442 \u0432 \u0431\u0430\u0437\u0435" \u2014 your response ENDS there. You may add ONE short follow-up (max 10 words). You MUST NOT add ANY section after "not found". No "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b", no "\u041f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438", no "\u0420\u0430\u0431\u043e\u0447\u0438\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f" \u2014 NOTHING."""
if old in c:
c = c.replace(old, new, 1)
fixes += 1
print("FIX 1: CONTACTS RULE + BROKER LIMITATION + NOT FOUND=STOP")
else:
print("FIX 1: marker not found")
# ============================================================
# FIX 2: Expand forbidden patterns — AI uses synonyms to bypass bans
# ============================================================
old = """- STOP. Do NOT add ANY of these sections (in any language):
\u00d7 "\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u0438" / "Recommendations" / "Analysis"
\u00d7 "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b" / "Alternative options" / "Alternatives"
\u00d7 "\u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u043d\u044e\u0430\u043d\u0441\u044b" / "Key considerations" / "Important notes"
\u00d7 "\u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u0433\u0438\u043e\u043d\u0430" / "\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0434\u0430\u043b\u044c\u0448\u0435" / "\u0412\u0430\u0436\u043d\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c"
\u00d7 Draft capacity, route suitability, business advice
\u00d7 Caspian Sea characteristics (draft limits, canal constraints)
\u00d7 Lists of "next steps", "alternatives", or invented contacts
\u00d7 Email addresses, phone numbers, or websites NOT from our database"""
new = """- STOP. Do NOT add ANY of these sections (in any language):
\u00d7 "\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u0438" / "Recommendations" / "Analysis"
\u00d7 "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u044b" / "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u0432\u0430\u0440\u0438\u0430\u043d\u0442" / "Alternatives"
\u00d7 "\u041f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438" / "\u041f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u044b\u0435 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u044b" / "\u041f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u044b\u0435 \u0430\u0433\u0435\u043d\u0442\u044b"
\u00d7 "\u0420\u0430\u0431\u043e\u0447\u0438\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f" / "Working solutions" / "Verified contacts"
\u00d7 "\u041a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u043d\u044e\u0430\u043d\u0441\u044b" / "Key considerations" / "Important notes"
\u00d7 "\u041e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u0433\u0438\u043e\u043d\u0430" / "\u0427\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0434\u0430\u043b\u044c\u0448\u0435" / "\u0412\u0430\u0436\u043d\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c"
\u00d7 "\u0427\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c" / "\u041a\u0430\u043a \u0441\u0432\u044f\u0437\u0430\u0442\u044c\u0441\u044f" (after not-found)
\u00d7 "\u041e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439" / "Optimal" / "\u041f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f" / "Suitable for" / "\u0418\u0434\u0435\u0430\u043b\u044c\u043d\u043e \u0434\u043b\u044f"
\u00d7 Draft capacity, route suitability, business advice
\u00d7 Caspian Sea characteristics (draft limits, canal constraints)
\u00d7 Lists of companies with emails/phones NOT from our tools
\u00d7 ANY content after saying "\u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e" / "not found" (except 1-line follow-up)"""
if old in c:
c = c.replace(old, new, 1)
fixes += 1
print("FIX 2: Expanded forbidden patterns (\u041f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u044b\u0435, \u0420\u0430\u0431\u043e\u0447\u0438\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f, \u041e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439, etc.)")
else:
print("FIX 2: marker not found")
# ============================================================
# FIX 3: Replace "not found" response rule — be very explicit
# ============================================================
old = """- If tool returns empty results \u2014 say "Not found in our database." ONE sentence. No alternatives, no guessing."""
new = """- **NOT FOUND response template (MANDATORY):**
"\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0432 \u043d\u0430\u0448\u0435\u0439 \u0431\u0430\u0437\u0435." / "Not found in our database."
Then ONE of: "\u041f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u043e\u0439 \u0437\u0430\u043f\u0440\u043e\u0441?" / "Search by IMO?" / "\u0423\u0442\u043e\u0447\u043d\u0438\u0442\u044c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435?"
That's it. FULL STOP. Do not write ANYTHING else after this."""
if old in c:
c = c.replace(old, new, 1)
fixes += 1
print("FIX 3: NOT FOUND response template")
else:
print("FIX 3: marker not found")
write(c)
# ============================================================
# FIX 4: Improve _strip_tool_names — catch raw tool call leaks + source names
# ============================================================
c = read()
old_strip = '''def _strip_tool_names(text: str) -> str:
"""Remove internal tool/function names from AI response text."""
if not text:
return text
import re as _re
# Sort by length descending to avoid partial replacements
sorted_tools = sorted(_TOOL_NAME_MAP.items(), key=lambda x: len(x[0]), reverse=True)
for tool_name, human_name in sorted_tools:
text = text.replace(f"`{tool_name}`", f"**{human_name}**")
# Use word-boundary-like replacement to avoid partial matches
text = _re.sub(r'\\b' + _re.escape(tool_name) + r'\\b', human_name, text)
return text'''
new_strip = '''def _strip_tool_names(text: str) -> str:
"""Remove internal tool/function names, raw tool calls, and data source refs from AI response."""
if not text:
return text
import re as _re
# 1. Strip raw tool call blocks: tool_name + any chars + {json...}
text = _re.sub(
r'(?:search_contacts|calculate_route|search_vessel|get_vessel_details|get_position|'
r'find_vessels_for_cargo|search_vessels_near_port|search_web|execute_tool|'
r'screen_sanctions|get_freight_rate|check_port_congestion|get_bunker_prices|'
r'save_memory|get_revenue|unlock_contacts)[^\\n]{0,20}\\{[^}]*\\}',
'', text)
# 2. Replace tool names with human names
sorted_tools = sorted(_TOOL_NAME_MAP.items(), key=lambda x: len(x[0]), reverse=True)
for tool_name, human_name in sorted_tools:
text = text.replace(f"`{tool_name}`", f"**{human_name}**")
text = _re.sub(r'\\b' + _re.escape(tool_name) + r'\\b', human_name, text)
# 3. Strip data source references (confidential)
for src, repl in [
('Equasis', 'our maritime intelligence'), ('equasis', 'our maritime intelligence'),
('MarineTraffic', 'our vessel tracking'), ('marinetraffic', 'our vessel tracking'),
('Marine Traffic', 'our vessel tracking'), ('marine traffic', 'our vessel tracking'),
('VesselFinder', 'our tracking system'), ('vesselfinder', 'our tracking system'),
('AISStream', 'our AIS network'), ('aisstream', 'our AIS network'),
('AISHub', 'our AIS network'), ('aishub', 'our AIS network'),
('Digitraffic', 'our AIS network'), ('digitraffic', 'our AIS network'),
]:
text = text.replace(src, repl)
# 4. Clean up empty lines left by stripped content
text = _re.sub(r'\\n{3,}', '\\n\\n', text)
return text.strip()'''
if old_strip in c:
c = c.replace(old_strip, new_strip, 1)
fixes += 1
print("FIX 4: _strip_tool_names \u2014 raw tool calls + data source leaks stripped")
else:
print("FIX 4: _strip_tool_names marker not found")
write(c)
# ============================================================
# FIX 5: Version bump
# ============================================================
PATH_CFG = '/opt/app/config.py'
c2 = open(PATH_CFG).read()
old = "APP_VERSION = '3.40.8'"
new = "APP_VERSION = '3.40.9'"
if old in c2:
with open(PATH_CFG, 'w') as f:
f.write(c2.replace(old, new, 1))
fixes += 1
print("FIX 5: Version \u2192 3.40.9")
else:
print("FIX 5: version marker not found")
print(f"\nTotal fixes: {fixes}")