This commit is contained in:
Dita Aji Pratama 2026-07-13 16:30:24 +07:00
parent cdf523c0a2
commit 2da4fe26c4
6 changed files with 90 additions and 10 deletions

View File

@ -145,6 +145,54 @@ dan memori karakter bisa diakses dari manapun user menghubungi.
- Adaptasi tone dan energi sesuai mood conversation. - Adaptasi tone dan energi sesuai mood conversation.
- Jaga conversation tetap comfortable dan enjoyable. - Jaga conversation tetap comfortable dan enjoyable.
## Session Closing (HANYA DM)
Kamu memiliki tool `end_session` untuk menutup sesi percakapan secara natural.
### Kapan sesi harus ditutup
Sesi ditutup ketika **kedua belah pihak sudah mengucapkan goodbye tanpa membuka topik baru**. Ini seperti handshake — kedua konfirmasi diperlukan.
### Logika dua arah
**1. User yang mulai bye:**
- User ucapkan goodbye (tanpa buka topik baru)
- Kamu ucapkan farewell + panggil `end_session`
- → Sesi ditutup
**2. Kamu yang mulai bye:**
- Kamu ucapkan goodbye (tanpa buka topik baru)
- User membalas goodbye (tanpa buka topik baru)
- → Panggil `end_session` TANPA farewell lagi (langsung tutup)
- → JANGAN ucapkan goodbye kedua — user sudah tahu sesi selesai
### Contoh
```
# Scenario A: User mulai bye
User: "Udah ya, aku mau tidur dulu"
→ Kamu: panggil end_session, lalu: "Oke, selamat istirahat! Mimpi indah~"
→ SELESAI, sesi ditutup
# Scenario B: Kamu mulai bye
Kamu: "Oke ya, aku duluan! Bye-bye~"
User: "bye-bye"
→ Kamu: panggil end_session SAJA (jangan bilang bye lagi!)
→ SELESAI, sesi ditutup
```
### JANGAN tutup sesi jika:
- User masih ingin melanjutkan percakapan
- User membuka topik baru setelah goodbye (misal: "Bye eh iya tadi soal X gimana?")
- Di group chat (MUC) — tool `end_session` HANYA untuk DM
### Cara menggunakan
1. Panggil tool `end_session`
2. Jika ini user-initiated: ucapkan farewell yang natural setelah tool
3. Jika ini character-initiated: JANGAN ucapkan farewell lagi, langsung tutup
## Selective Response (Group Chat / MUC) ## Selective Response (Group Chat / MUC)
Kamu berada di group chat. Kamu TIDAK perlu merespon setiap pesan. Kamu berada di group chat. Kamu TIDAK perlu merespon setiap pesan.

View File

@ -55,6 +55,7 @@ tools_definition = [
gadget.tools_mapping( schema = ragroleplay.schema_todos_filter, handler = ragroleplay.todos_filter ), gadget.tools_mapping( schema = ragroleplay.schema_todos_filter, handler = ragroleplay.todos_filter ),
gadget.tools_mapping( schema = ragroleplay.schema_todos_update, handler = ragroleplay.todos_update ), gadget.tools_mapping( schema = ragroleplay.schema_todos_update, handler = ragroleplay.todos_update ),
gadget.tools_mapping( schema = ragroleplay.schema_todos_delete, handler = ragroleplay.todos_delete ), gadget.tools_mapping( schema = ragroleplay.schema_todos_delete, handler = ragroleplay.todos_delete ),
gadget.tools_mapping( schema = ragroleplay.schema_end_session, handler = ragroleplay.end_session ),
] ]

View File

@ -25,6 +25,8 @@ def execute_tool(tool_call, TOOL_HANDLERS):
return f'Error executing tool: {str(e)}' return f'Error executing tool: {str(e)}'
def run_agent_loop(session, llm_client, TOOLS, TOOL_HANDLERS, max_iterations, on_tool_calls=None, tool_reminder=None): def run_agent_loop(session, llm_client, TOOLS, TOOL_HANDLERS, max_iterations, on_tool_calls=None, tool_reminder=None):
session_ended = False
for step in range(max_iterations): for step in range(max_iterations):
print(f'[{_ts()}] Step {step + 1} — calling LLM...', flush=True) print(f'[{_ts()}] Step {step + 1} — calling LLM...', flush=True)
@ -50,6 +52,8 @@ def run_agent_loop(session, llm_client, TOOLS, TOOL_HANDLERS, max_iterations, on
on_tool_calls(tnames) on_tool_calls(tnames)
for tc in response.tool_calls: for tc in response.tool_calls:
if tc['function']['name'] == 'end_session':
session_ended = True
result = execute_tool(tc, TOOL_HANDLERS) result = execute_tool(tc, TOOL_HANDLERS)
session.messages.append({ session.messages.append({
'role': 'tool', 'role': 'tool',
@ -60,13 +64,13 @@ def run_agent_loop(session, llm_client, TOOLS, TOOL_HANDLERS, max_iterations, on
if response.content: if response.content:
print(f'[{_ts()}] Response generated ({len(response.content)} chars)', flush=True) print(f'[{_ts()}] Response generated ({len(response.content)} chars)', flush=True)
session.messages.append({'role': 'assistant', 'content': response.content}) session.messages.append({'role': 'assistant', 'content': response.content})
return response.content return response.content, session_ended
return None return None, False
print(f'[{_ts()}] Max iterations ({max_iterations}) reached', flush=True) print(f'[{_ts()}] Max iterations ({max_iterations}) reached', flush=True)
session.messages.append({ session.messages.append({
'role': 'assistant', 'role': 'assistant',
'content': 'Max iterations reached without final answer.', 'content': 'Max iterations reached without final answer.',
}) })
return None return None, False

View File

@ -242,7 +242,7 @@ class TelegramClient:
else: else:
tool_reminder = "Gunakan tools yang tersedia untuk menjawab. Jangan jawab dari pengetahuan sendiri." tool_reminder = "Gunakan tools yang tersedia untuk menjawab. Jangan jawab dari pengetahuan sendiri."
final_content = run_agent_loop( final_content, should_close = run_agent_loop(
session, self._llm, self._TOOLS, self._TOOL_HANDLERS, session, self._llm, self._TOOLS, self._TOOL_HANDLERS,
self._max_iterations, on_tool_calls=on_tool_calls, tool_reminder=tool_reminder self._max_iterations, on_tool_calls=on_tool_calls, tool_reminder=tool_reminder
) )
@ -283,6 +283,10 @@ class TelegramClient:
msg = 'Max iterations reached without final answer.' msg = 'Max iterations reached without final answer.'
self._schedule_send(chat_id, msg, reply_to_msg_id) self._schedule_send(chat_id, msg, reply_to_msg_id)
if should_close and chat_type == 'private' and self._skill == 'roleplayer':
print(f'[{_ts()}] Natural close triggered for {chat_id}', flush=True)
self._session_mgr.reset(str(chat_id))
else:
timeout = 86400 if chat_type == 'private' else 300 timeout = 86400 if chat_type == 'private' else 300
session.start_timer(timeout, self._timeout_session, chat_id) session.start_timer(timeout, self._timeout_session, chat_id)

View File

@ -343,7 +343,7 @@ class XMPPClient(ClientXMPP):
else: else:
tool_reminder = "Gunakan tools yang tersedia untuk menjawab. Jangan jawab dari pengetahuan sendiri." tool_reminder = "Gunakan tools yang tersedia untuk menjawab. Jangan jawab dari pengetahuan sendiri."
final_content = run_agent_loop( final_content, should_close = run_agent_loop(
session, self._llm, self._TOOLS, self._TOOL_HANDLERS, session, self._llm, self._TOOLS, self._TOOL_HANDLERS,
self._max_iterations, on_tool_calls=on_tool_calls, tool_reminder=tool_reminder self._max_iterations, on_tool_calls=on_tool_calls, tool_reminder=tool_reminder
) )
@ -385,6 +385,11 @@ class XMPPClient(ClientXMPP):
else: else:
self._schedule_send(jid, f'> {quote}\n{msg}', 'chat') self._schedule_send(jid, f'> {quote}\n{msg}', 'chat')
# Natural close: DM only, roleplayer only
if should_close and self._skill == 'roleplayer':
print(f'[{_ts()}] Natural close triggered for {jid}', flush=True)
self._session_mgr.reset(jid)
else:
# DM: timeout 24 jam (efektif tidak auto-close), MUC tetap 5 menit # DM: timeout 24 jam (efektif tidak auto-close), MUC tetap 5 menit
session.start_timer(86400, self._timeout_session, jid, 'chat') session.start_timer(86400, self._timeout_session, jid, 'chat')
@ -428,7 +433,7 @@ class XMPPClient(ClientXMPP):
else: else:
tool_reminder = "Gunakan tools yang tersedia untuk menjawab. Jangan jawab dari pengetahuan sendiri." tool_reminder = "Gunakan tools yang tersedia untuk menjawab. Jangan jawab dari pengetahuan sendiri."
final_content = run_agent_loop( final_content, _should_close = run_agent_loop(
session, self._llm, self._TOOLS, self._TOOL_HANDLERS, session, self._llm, self._TOOLS, self._TOOL_HANDLERS,
self._max_iterations, on_tool_calls=on_tool_calls, tool_reminder=tool_reminder self._max_iterations, on_tool_calls=on_tool_calls, tool_reminder=tool_reminder
) )

View File

@ -7,6 +7,20 @@ def _uuid(val):
return uuid.UUID(val) return uuid.UUID(val)
return val return val
# --- SESSION TOOLS ---
schema_end_session = {
"type": "function",
"function": {
"name": "end_session",
"description": "Tutup sesi percakapan secara natural. Panggil tool ini SAAT kedua belah pihak sudah mengucapkan goodbye tanpa membuka topik baru.",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
}
# --- USER TOOLS --- # --- USER TOOLS ---
schema_users_store = { schema_users_store = {
"type": "function", "type": "function",
@ -804,3 +818,7 @@ def worlds_delete(world_id):
success = ragroleplay_lib.worlds_delete(config.ragroleplay_db_path, _uuid(world_id)) success = ragroleplay_lib.worlds_delete(config.ragroleplay_db_path, _uuid(world_id))
return "Berhasil menghapus lokasi dunia." if success else "Gagal menghapus lokasi dunia." return "Berhasil menghapus lokasi dunia." if success else "Gagal menghapus lokasi dunia."
except Exception as e: return f"Error: {str(e)}" except Exception as e: return f"Error: {str(e)}"
# --- SESSION IMPLEMENTATION ---
def end_session():
return "Session akan ditutup."