hendrik/lib/agent_loop.py

95 lines
3.9 KiB
Python
Raw Permalink Normal View History

2026-06-16 22:44:10 +07:00
import json
from datetime import datetime
2026-07-24 19:07:09 +07:00
from tools.vision import ImagePayload
2026-06-16 22:44:10 +07:00
def _ts():
return datetime.now().strftime('%H:%M:%S')
2026-06-22 11:54:17 +07:00
def execute_tool(tool_call, TOOL_HANDLERS):
2026-06-16 22:44:10 +07:00
tname = tool_call['function']['name']
targs = json.loads(tool_call['function']['arguments'])
handler = TOOL_HANDLERS.get(tname)
if not handler:
2026-06-22 11:54:17 +07:00
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)}'
2026-06-16 22:44:10 +07:00
2026-07-03 10:45:00 +07:00
def run_agent_loop(session, llm_client, TOOLS, TOOL_HANDLERS, max_iterations, on_tool_calls=None, tool_reminder=None):
2026-07-13 16:30:24 +07:00
session_ended = False
2026-06-16 22:44:10 +07:00
for step in range(max_iterations):
2026-06-18 08:12:59 +07:00
print(f'[{_ts()}] Step {step + 1} — calling LLM...', flush=True)
2026-06-26 10:00:24 +07:00
# Ambil konfigurasi disable_reasoning dari personality karakter
# Default ke False jika tidak didefinisikan
personality = getattr(session, 'personality', {})
disable_reasoning = personality.get('disable_reasoning', False)
2026-07-03 10:45:00 +07:00
response = llm_client.chat(session.messages, tools=TOOLS, disable_reasoning=disable_reasoning, tool_reminder=tool_reminder)
2026-06-16 22:44:10 +07:00
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]
2026-06-18 08:12:59 +07:00
print(f'[{_ts()}] Using tools: {", ".join(tnames)}', flush=True)
2026-06-16 22:44:10 +07:00
if on_tool_calls:
2026-07-18 02:14:07 +07:00
on_tool_calls(response.content or "")
2026-06-16 22:44:10 +07:00
for tc in response.tool_calls:
2026-07-13 16:30:24 +07:00
if tc['function']['name'] == 'end_session':
session_ended = True
2026-06-16 22:44:10 +07:00
result = execute_tool(tc, TOOL_HANDLERS)
2026-07-24 19:07:09 +07:00
if isinstance(result, ImagePayload):
session.messages.append({
'role': 'tool',
'tool_call_id': tc['id'],
'content': [
{"type": "text", "text": f"[Image loaded: {result.path}]"},
{"type": "image_url", "image_url": {"url": f"data:{result.mime};base64,{result.data}"}}
]
})
else:
session.messages.append({
'role': 'tool',
'tool_call_id': tc['id'],
'content': str(result),
})
2026-06-16 22:44:10 +07:00
else:
2026-07-26 07:18:33 +07:00
has_content = bool(response.content) or (isinstance(response.content, list) and len(response.content) > 0)
if has_content:
2026-06-16 22:44:10 +07:00
session.messages.append({'role': 'assistant', 'content': response.content})
2026-07-26 07:18:33 +07:00
if isinstance(response.content, list):
text_parts = [b.get('text', '') for b in response.content
if isinstance(b, dict) and b.get('type') == 'text']
text = '\n'.join(text_parts) if text_parts else "[Image generated]"
print(f'[{_ts()}] Response generated (multimodal)', flush=True)
return text, session_ended
print(f'[{_ts()}] Response generated ({len(response.content)} chars)', flush=True)
2026-07-13 16:30:24 +07:00
return response.content, session_ended
return None, False
2026-06-16 22:44:10 +07:00
2026-06-18 08:12:59 +07:00
print(f'[{_ts()}] Max iterations ({max_iterations}) reached', flush=True)
2026-06-16 22:44:10 +07:00
session.messages.append({
'role': 'assistant',
'content': 'Max iterations reached without final answer.',
})
2026-07-13 16:30:24 +07:00
return None, False
2026-06-22 11:39:11 +07:00