Persona, Selective Response, Mention Response
This commit is contained in:
parent
8a0363b985
commit
b2240d304e
@ -27,3 +27,7 @@ PERSONA_LANGUAGE=id # id | en | (kosong = auto)
|
||||
PERSONA_MOOD=cheerful # cheerful | calm | energetic | sarcastic
|
||||
# PERSONA_CATCHPHRASES=Siap bro!, Haha~, Wkwkwk
|
||||
|
||||
# Selective response (roleplayer mode): true = hanya respon kalau ada mention/relevansi.
|
||||
# false = semua pesan direspon (tanpa filter).
|
||||
PERSONA_SELECTIVE_RESPONSE=true
|
||||
|
||||
|
||||
@ -48,6 +48,10 @@ PERSONA_MOOD = os.getenv("PERSONA_MOOD", default="cheerful").strip().lower() or
|
||||
# Contoh: "Siap bro!, Haha~, Wkwkwk"
|
||||
PERSONA_CATCHPHRASES = os.getenv("PERSONA_CATCHPHRASES", default="").strip()
|
||||
|
||||
# Selective response: true = roleplayer hanya respon kalau ada mention/relevansi (default).
|
||||
# false = roleplayer semua pesan ikut respon (seperti biasa, tanpa filter).
|
||||
PERSONA_SELECTIVE_RESPONSE = os.getenv("PERSONA_SELECTIVE_RESPONSE", default="true").strip().lower() in ("true", "1", "yes")
|
||||
|
||||
# Humanize Delay Configuration (anti-bot detection)
|
||||
READ_DELAY_MIN = float( os.getenv("READ_DELAY_MIN", default="1.0" ) ) # min reading delay (second)
|
||||
READ_DELAY_MAX = float( os.getenv("READ_DELAY_MAX", default="2.0" ) ) # max reading delay (second)
|
||||
|
||||
@ -204,6 +204,32 @@ def _build_roleplayer_prompt(cfg: PersonalityConfig) -> str:
|
||||
"- Adapt your tone and energy to match the mood of the conversation.",
|
||||
"- Keep the conversation comfortable and enjoyable.",
|
||||
"",
|
||||
"⚠️ SELECTIVE RESPONSE — IMPORTANT:",
|
||||
"You are in a group chat / MUC room. You do NOT need to respond to every message. "
|
||||
"Use the following rules to decide whether to reply:",
|
||||
"",
|
||||
"1. STRONG REPLY — ALWAYS respond when:",
|
||||
f" - Someone directly calls your name ('{cfg.name}' or mentions you).",
|
||||
" - Someone asks you a direct question.",
|
||||
"",
|
||||
"2. BRIEF REPLY — Respond briefly when:",
|
||||
f" - Someone talks ABOUT you (mentions your name in third person, e.g. '{cfg.name} is cool').",
|
||||
" - You can add something relevant or funny to the ongoing topic.",
|
||||
"",
|
||||
"3. CONTEXTUAL REPLY — Respond when:",
|
||||
" - The message is related to a topic you were previously discussing.",
|
||||
" - You have something meaningful to contribute.",
|
||||
"",
|
||||
"4. NO REPLY — Stay silent (respond with ONLY: NO-REPLY) when:",
|
||||
" - The message has nothing to do with you or your previous conversation.",
|
||||
" - Someone already answered the question or resolved the topic.",
|
||||
" - The message is between other people and doesn't need your input.",
|
||||
" - The message is confusing, unclear, or you cannot understand it.",
|
||||
" - Adding a response would interrupt the flow of conversation.",
|
||||
"",
|
||||
"When you choose NOT to reply, respond with ONLY: NO-REPLY",
|
||||
"Do NOT wrap it in markdown or code blocks. Just the two words: NO-REPLY",
|
||||
"",
|
||||
"Note: You currently do not have access to external tools. "
|
||||
"Focus on being a great conversationalist!",
|
||||
]
|
||||
|
||||
@ -10,6 +10,8 @@ from services.session_manager import SessionManager
|
||||
|
||||
import config
|
||||
from scripts.persona import MODE as PERSONA_MODE
|
||||
from tools.roleplayer import should_respond
|
||||
from scripts.persona import PERSONALITY
|
||||
|
||||
# Anti-ban: delay constants for MUC rejoin behavior
|
||||
MUC_REJOIN_INITIAL_DELAY = 5.0 # detik, delay awal sebelum rejoin
|
||||
@ -300,7 +302,7 @@ class XMPPClient(ClientXMPP):
|
||||
if self._loop and not self._loop.is_closed():
|
||||
asyncio.run_coroutine_threadsafe(_read_delay(), self._loop)
|
||||
|
||||
self._agent_loop(session, jid, body, 'chat')
|
||||
self._agent_loop(session, jid, body, 'chat', sender_nickname=jid)
|
||||
|
||||
session.start_timer(300, self._timeout_session, jid, 'chat')
|
||||
|
||||
@ -326,12 +328,13 @@ class XMPPClient(ClientXMPP):
|
||||
if self._loop and not self._loop.is_closed():
|
||||
asyncio.run_coroutine_threadsafe(_read_delay(), self._loop)
|
||||
|
||||
self._agent_loop(session, room, f'[{nick}] {body}', 'groupchat')
|
||||
self._agent_loop(session, room, f'[{nick}] {body}', 'groupchat', sender_nickname=nick)
|
||||
|
||||
session.start_timer(300, self._timeout_session, room, 'groupchat')
|
||||
|
||||
def _agent_loop(self, session, to, quote, mtype):
|
||||
def _agent_loop(self, session, to, quote, mtype, sender_nickname=""):
|
||||
is_roleplayer = self._mode == 'roleplayer'
|
||||
my_name = PERSONALITY.name
|
||||
|
||||
for step in range(self._max_iterations):
|
||||
print(f'[{_ts()}] Step {step + 1} — calling LLM...')
|
||||
@ -361,12 +364,40 @@ class XMPPClient(ClientXMPP):
|
||||
})
|
||||
else:
|
||||
if response.content:
|
||||
print(f'[{_ts()}] Response sent ({len(response.content)} chars)')
|
||||
print(f'[{_ts()}] Response generated ({len(response.content)} chars)')
|
||||
session.messages.append({'role': 'assistant', 'content': response.content})
|
||||
|
||||
# Roleplayer: kirim langsung isinya, tanpa prefix
|
||||
# ── Roleplayer: cek need_response sebelum kirim ──
|
||||
if is_roleplayer:
|
||||
if config.PERSONA_SELECTIVE_RESPONSE:
|
||||
# Build recent history dari session messages (tanpa system prompt)
|
||||
recent_msgs = []
|
||||
for msg in session.messages[-6:]:
|
||||
if msg.get('role') == 'user':
|
||||
recent_msgs.append(f"User: {msg.get('content', '')}")
|
||||
elif msg.get('role') == 'assistant' and msg.get('content'):
|
||||
recent_msgs.append(f"{my_name}: {msg.get('content', '')}")
|
||||
recent_history = "\n".join(recent_msgs)
|
||||
|
||||
original_message = quote
|
||||
if should_respond(
|
||||
message=original_message,
|
||||
sender_nickname=sender_nickname,
|
||||
recent_history=recent_history,
|
||||
my_name=my_name,
|
||||
):
|
||||
print(f'[{_ts()}] need_response=True → sending response')
|
||||
self._schedule_send(to, response.content, mtype)
|
||||
else:
|
||||
print(f'[{_ts()}] need_response=False → staying silent')
|
||||
else:
|
||||
# Selective response OFF: cuma respon kalau nama AI disebut di pesan
|
||||
from tools.roleplayer import _name_mentioned
|
||||
if _name_mentioned(my_name, quote):
|
||||
print(f'[{_ts()}] Name mentioned → sending response')
|
||||
self._schedule_send(to, response.content, mtype)
|
||||
else:
|
||||
print(f'[{_ts()}] Name not mentioned → staying silent')
|
||||
else:
|
||||
self._schedule_send(to, f'> {quote}\n{response.content}', mtype)
|
||||
return
|
||||
|
||||
102
tools/roleplayer.py
Normal file
102
tools/roleplayer.py
Normal file
@ -0,0 +1,102 @@
|
||||
import re
|
||||
|
||||
|
||||
def _name_mentioned(name: str, text: str) -> bool:
|
||||
"""Cek apakah nama AI disebut dalam pesan (case-insensitive, word-boundary)."""
|
||||
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 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 — check recent history ---
|
||||
if recent_history:
|
||||
return "true"
|
||||
|
||||
# --- Rule 4: Default — no strong signal, stay silent ---
|
||||
return "false"
|
||||
|
||||
|
||||
def should_respond(message: str, sender_nickname: str, recent_history: str, my_name: str) -> bool:
|
||||
"""
|
||||
Python-side helper: return boolean directly.
|
||||
Used by XMPP client to gate whether to send the LLM's response.
|
||||
"""
|
||||
result = need_response(message, sender_nickname, recent_history, my_name)
|
||||
return result.strip().lower() == "true"
|
||||
|
||||
|
||||
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"],
|
||||
},
|
||||
},
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user