161 lines
5.8 KiB
Python
161 lines
5.8 KiB
Python
import re
|
|
|
|
schema_need_response = {
|
|
"type": "function",
|
|
"function": {
|
|
"name": "need_response",
|
|
"description": (
|
|
"Decide whether you should respond to the current message. "
|
|
"Use this tool when you are unsure whether to reply or stay silent. "
|
|
"Returns 'true' if you should respond, 'false' if you should stay silent. "
|
|
"Rules: "
|
|
"- Return 'true' if your name is mentioned or someone talks about you. "
|
|
"- Return 'true' if the message is related to your previous conversation. "
|
|
"- Return 'false' if the message is between other people, unclear, "
|
|
"unrelated to you, or you have nothing to add."
|
|
),
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"message": {
|
|
"type": "string",
|
|
"description": "The latest message to evaluate.",
|
|
},
|
|
"sender_nickname": {
|
|
"type": "string",
|
|
"description": "The nickname of the person who sent the message.",
|
|
},
|
|
"recent_history": {
|
|
"type": "string",
|
|
"description": "Recent conversation history for context (last few messages).",
|
|
},
|
|
"my_name": {
|
|
"type": "string",
|
|
"description": "Your (the AI's) name.",
|
|
},
|
|
},
|
|
"required": ["message", "sender_nickname", "recent_history", "my_name"],
|
|
},
|
|
},
|
|
}
|
|
|
|
def _name_mentioned(name: str, text: str) -> bool:
|
|
text_lower = text.lower()
|
|
name_lower = name.lower()
|
|
pattern = r'\b' + re.escape(name_lower) + r'\b'
|
|
return bool(re.search(pattern, text_lower))
|
|
|
|
|
|
def _bot_nick_mentioned(name: str, text: str) -> bool:
|
|
"""Mention nama bot, termasuk varian suffix angka dari logika anti-ban
|
|
MUC (mis. 'lily' juga cocok untuk 'lily_1')."""
|
|
text_lower = text.lower()
|
|
pattern = r'\b' + re.escape(name.lower()) + r'(?:_\d+)?\b'
|
|
return bool(re.search(pattern, text_lower))
|
|
|
|
def need_response(message: str, sender_nickname: str, recent_history: str, my_name: str) -> str:
|
|
"""
|
|
Decide whether the AI should respond to a message.
|
|
|
|
Args:
|
|
message: The latest message to evaluate.
|
|
sender_nickname: Nickname of the sender.
|
|
recent_history: Recent conversation history for context.
|
|
my_name: The AI's name.
|
|
|
|
Returns:
|
|
"true" → should respond
|
|
"false" → should stay silent
|
|
|
|
Rules:
|
|
1. STRONG — Direct call/mention of AI's name → always True
|
|
2. BRIEF — Talks about AI in third person → True
|
|
3. CONTEXTUAL — Related to AI's previous conversation → True
|
|
4. NO REPLY — Unrelated, confusing, between other people → False
|
|
"""
|
|
msg = message.strip()
|
|
|
|
# --- Rule 4: Empty message ---
|
|
if not msg:
|
|
return "false"
|
|
|
|
# --- Rule 1: Direct mention/name call ---
|
|
if _name_mentioned(my_name, msg):
|
|
return "true"
|
|
|
|
# --- Rule 2: Talks about AI (third person) ---
|
|
name_parts = my_name.lower().split()
|
|
text_lower = msg.lower()
|
|
if any(part in text_lower for part in name_parts if len(part) > 1):
|
|
return "true"
|
|
|
|
# --- Rule 3: Contextual ---
|
|
# Logic updated: We no longer return "true" just because history exists.
|
|
# The actual decision for context should be handled by the LLM via the tool call
|
|
# since determining 'relatedness' requires semantic understanding.
|
|
# However, as a Python helper, we return "false" to avoid false-positives,
|
|
# forcing the system to rely on the LLM's reasoning if called as a tool.
|
|
return "false"
|
|
|
|
|
|
# Pola sapaan XMPP MUC: pesan diawali "Nama: " (mis. "Tinny: O iya, ...").
|
|
# Guard (?!//) agar URL seperti "https://..." tidak dianggap sapaan.
|
|
_ADDRESS_PREFIX_RE = re.compile(r'^\s*([A-Za-z0-9_\-]+)\s*:(?!//)\s+\S')
|
|
|
|
|
|
def _collect_bot_names(my_name: str, bot_aliases=None) -> set:
|
|
"""Semua variasi nama bot: codename, kata penyusunnya, plus alias tambahan
|
|
(mis. nick MUC beserta varian suffix-nya seperti lily_1)."""
|
|
names = set()
|
|
|
|
def _add(value: str):
|
|
v = (value or '').strip().lower()
|
|
if not v:
|
|
return
|
|
names.add(v)
|
|
for part in v.split():
|
|
if len(part) > 1:
|
|
names.add(part)
|
|
|
|
_add(my_name)
|
|
for alias in (bot_aliases or []):
|
|
_add(alias)
|
|
return names
|
|
|
|
|
|
def should_respond(message: str, sender_nickname: str, recent_history: str, my_name: str,
|
|
bot_aliases=None) -> bool:
|
|
"""
|
|
Gate deterministik (tanpa LLM): apakah bot harus membalas pesan group/MUC.
|
|
|
|
Urutan aturan:
|
|
0. Pesan kosong → False
|
|
1. Pesan diawali "Nama: " (format sapaan XMPP MUC):
|
|
- Nama = nama bot → True (disapa langsung)
|
|
- Nama lain → False (jelas sedang bicara ke orang lain)
|
|
2. Nama bot muncul di mana pun dalam pesan (word-boundary) → True
|
|
3. Selain itu → False (default diam)
|
|
|
|
Catatan: `recent_history` dipertahankan demi kompatibilitas pemanggil
|
|
lama, tapi sengaja TIDAK dipakai — riwayat bukan alasan untuk membalas.
|
|
"""
|
|
msg = (message or '').strip()
|
|
if not msg:
|
|
return False
|
|
|
|
names = _collect_bot_names(my_name, bot_aliases)
|
|
|
|
# --- Aturan 1: format sapaan "Nama: pesan" ---
|
|
match = _ADDRESS_PREFIX_RE.match(msg)
|
|
if match:
|
|
target = match.group(1).lower()
|
|
# Cocokkan persis, atau varian suffix dari alias (mis. lily_1 vs lily)
|
|
for n in names:
|
|
if target == n or target.startswith(n + '_'):
|
|
return True
|
|
return False
|
|
|
|
# --- Aturan 2: mention nama bot (kata utuh, bukan substring) ---
|
|
return any(_bot_nick_mentioned(n, msg) for n in names)
|
|
|