pakai agent loop yang modular
This commit is contained in:
parent
3b5ef010df
commit
07dd99ded4
@ -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()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user