pakai agent loop yang modular
This commit is contained in:
parent
3b5ef010df
commit
07dd99ded4
@ -1,5 +1,4 @@
|
||||
import asyncio
|
||||
import json
|
||||
import random
|
||||
import signal
|
||||
import threading
|
||||
@ -7,6 +6,7 @@ from datetime import datetime
|
||||
|
||||
from slixmpp import ClientXMPP
|
||||
from services.session_manager import SessionManager
|
||||
from services.agent_loop import run_agent_loop
|
||||
|
||||
import config
|
||||
from tools.roleplayer import should_respond
|
||||
@ -306,7 +306,54 @@ 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', 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
|
||||
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():
|
||||
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')
|
||||
|
||||
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):
|
||||
tname = tool_call['function']['name']
|
||||
targs = json.loads(tool_call['function']['arguments'])
|
||||
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)}'
|
||||
from services.agent_loop import execute_tool
|
||||
return execute_tool(tool_call, self._TOOL_HANDLERS)
|
||||
|
||||
def _schedule_send(self, to, body, mtype='chat'):
|
||||
if self._loop and not self._loop.is_closed():
|
||||
@ -480,7 +477,7 @@ class XMPPClient(ClientXMPP):
|
||||
# dan kita tidak mau proses mati karena itu.
|
||||
try:
|
||||
self._loop.add_signal_handler(signal.SIGTERM, self._stopped.set)
|
||||
except NotImplementedError:
|
||||
except (NotImplementedError, RuntimeError):
|
||||
pass
|
||||
|
||||
await self.connect()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user