pakai agent loop yang modular

This commit is contained in:
Dita Aji Pratama 2026-06-16 22:49:00 +07:00
parent 3b5ef010df
commit 07dd99ded4

View File

@ -1,5 +1,4 @@
import asyncio import asyncio
import json
import random import random
import signal import signal
import threading import threading
@ -7,6 +6,7 @@ from datetime import datetime
from slixmpp import ClientXMPP from slixmpp import ClientXMPP
from services.session_manager import SessionManager from services.session_manager import SessionManager
from services.agent_loop import run_agent_loop
import config import config
from tools.roleplayer import should_respond from tools.roleplayer import should_respond
@ -306,7 +306,54 @@ class XMPPClient(ClientXMPP):
if self._loop and not self._loop.is_closed(): if self._loop and not self._loop.is_closed():
asyncio.run_coroutine_threadsafe(_read_delay(), self._loop) asyncio.run_coroutine_threadsafe(_read_delay(), self._loop)
self._agent_loop(session, jid, body, 'chat', sender_nickname=jid) my_name = PERSONALITY.name
quote = body
def on_tool_calls(tnames):
if not is_roleplay:
self._schedule_send(jid, f'> {quote}\nUsing: {", ".join(tnames)}', 'chat')
final_content = run_agent_loop(
session, self._llm, self._TOOLS, self._TOOL_HANDLERS,
self._max_iterations, on_tool_calls=on_tool_calls
)
if final_content is not None:
if is_roleplay:
if config.XMPP_SELECTIVE_RESPONSE:
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)
if should_respond(
message=quote,
sender_nickname=jid,
recent_history=recent_history,
my_name=my_name,
):
print(f'[{_ts()}] need_response=True → sending response')
self._schedule_send(jid, final_content, 'chat')
else:
print(f'[{_ts()}] need_response=False → staying silent')
else:
from tools.roleplayer import _name_mentioned
if _name_mentioned(my_name, quote):
print(f'[{_ts()}] Name mentioned → sending response')
self._schedule_send(jid, final_content, 'chat')
else:
print(f'[{_ts()}] Name not mentioned → staying silent')
else:
self._schedule_send(jid, f'> {quote}\n{final_content}', 'chat')
else:
msg = 'Max iterations reached without final answer.'
if is_roleplay:
self._schedule_send(jid, msg, 'chat')
else:
self._schedule_send(jid, f'> {quote}\n{msg}', 'chat')
# 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')
@ -337,110 +384,60 @@ class XMPPClient(ClientXMPP):
if self._loop and not self._loop.is_closed(): if self._loop and not self._loop.is_closed():
asyncio.run_coroutine_threadsafe(_read_delay(), self._loop) asyncio.run_coroutine_threadsafe(_read_delay(), self._loop)
self._agent_loop(session, room, f'[{nick}] {body}', 'groupchat', sender_nickname=nick) my_name = PERSONALITY.name
quote = f'[{nick}] {body}'
def on_tool_calls(tnames):
if not is_roleplay:
self._schedule_send(room, f'> {quote}\nUsing: {", ".join(tnames)}', 'groupchat')
final_content = run_agent_loop(
session, self._llm, self._TOOLS, self._TOOL_HANDLERS,
self._max_iterations, on_tool_calls=on_tool_calls
)
if final_content is not None:
if is_roleplay:
if config.XMPP_SELECTIVE_RESPONSE:
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)
if should_respond(
message=quote,
sender_nickname=nick,
recent_history=recent_history,
my_name=my_name,
):
print(f'[{_ts()}] need_response=True → sending response')
self._schedule_send(room, final_content, 'groupchat')
else:
print(f'[{_ts()}] need_response=False → staying silent')
else:
from tools.roleplayer import _name_mentioned
if _name_mentioned(my_name, quote):
print(f'[{_ts()}] Name mentioned → sending response')
self._schedule_send(room, final_content, 'groupchat')
else:
print(f'[{_ts()}] Name not mentioned → staying silent')
else:
self._schedule_send(room, f'> {quote}\n{final_content}', 'groupchat')
else:
msg = 'Max iterations reached without final answer.'
if is_roleplay:
self._schedule_send(room, msg, 'groupchat')
else:
self._schedule_send(room, f'> {quote}\n{msg}', 'groupchat')
session.start_timer(300, self._timeout_session, room, 'groupchat') session.start_timer(300, self._timeout_session, room, 'groupchat')
def _agent_loop(self, session, to, quote, mtype, sender_nickname=""):
is_roleplayer = self._skill == 'roleplayer'
my_name = PERSONALITY.name
for step in range(self._max_iterations):
print(f'[{_ts()}] Step {step + 1} — calling LLM...')
response = self._llm.chat(session.messages, tools=self._TOOLS)
if response.tool_calls:
amsg = {
'role': 'assistant',
'content': response.content,
'tool_calls': response.tool_calls,
}
session.messages.append(amsg)
tnames = [tc['function']['name'] for tc in response.tool_calls]
print(f'[{_ts()}] Using tools: {", ".join(tnames)}')
# Roleplayer tidak perlu kirim status "Using: ..."
if not is_roleplayer:
self._schedule_send(to, f'> {quote}\nUsing: {", ".join(tnames)}', mtype)
for tc in response.tool_calls:
result = self._execute_tool(tc)
session.messages.append({
'role': 'tool',
'tool_call_id': tc['id'],
'content': str(result),
})
else:
if response.content:
print(f'[{_ts()}] Response generated ({len(response.content)} chars)')
session.messages.append({'role': 'assistant', 'content': response.content})
# ── Roleplayer: cek need_response sebelum kirim ──
if is_roleplayer:
if config.XMPP_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
print(f'[{_ts()}] Max iterations ({self._max_iterations}) reached')
session.messages.append({
'role': 'assistant',
'content': 'Max iterations reached without final answer.',
})
if is_roleplayer:
self._schedule_send(to, 'Max iterations reached without final answer.', mtype)
else:
self._schedule_send(to, f'> {quote}\nMax iterations reached without final answer.', mtype)
def _execute_tool(self, tool_call): def _execute_tool(self, tool_call):
tname = tool_call['function']['name'] from services.agent_loop import execute_tool
targs = json.loads(tool_call['function']['arguments']) return execute_tool(tool_call, self._TOOL_HANDLERS)
handler = self._TOOL_HANDLERS.get(tname)
if not handler:
return f'Tool {tname} not found'
try:
if tname == 'search_code':
return handler(
pattern=targs['pattern'],
search_type=targs['search_type'],
path=targs.get('path', '.'),
)
elif tname == 'git_operation':
return handler(args=targs['args'])
else:
return handler(**targs)
except Exception as e:
return f'Error executing tool: {str(e)}'
def _schedule_send(self, to, body, mtype='chat'): def _schedule_send(self, to, body, mtype='chat'):
if self._loop and not self._loop.is_closed(): if self._loop and not self._loop.is_closed():
@ -480,7 +477,7 @@ class XMPPClient(ClientXMPP):
# dan kita tidak mau proses mati karena itu. # dan kita tidak mau proses mati karena itu.
try: try:
self._loop.add_signal_handler(signal.SIGTERM, self._stopped.set) self._loop.add_signal_handler(signal.SIGTERM, self._stopped.set)
except NotImplementedError: except (NotImplementedError, RuntimeError):
pass pass
await self.connect() await self.connect()