Compare commits

...

7 Commits

9 changed files with 59 additions and 85 deletions

View File

@ -2,7 +2,7 @@ skill : programmer
name : Hendrik
age : 35
gender : male
tone : casual
tone : formal
verbosity : concise
humor : none
language : id

View File

@ -2,8 +2,8 @@
agent:
character: hendrik # Directory name in agent/characters/<character>/
max_iterations: 40 # step
max_tool_output: 40000
max_iterations: 30 # step
max_tool_output: 4000
llm:
timeout: 3000 # second

View File

@ -1,14 +1,17 @@
import os, sys, threading, time
import signal
import os, sys, threading, time, signal
import config
from services.xmpp_client import XMPPClient
from scripts.llm_client import LLMClient
from tools import coder, rag, carrack
from scripts import gadget
from services.xmpp_client import XMPPClient
from services.telegram_client import TelegramClient
from services.llm_client import LLMClient
from scripts.personality import build_system_prompt
from scripts import gadget
from tui import HendrikTUI
tools_definition = [
@ -46,7 +49,6 @@ def main():
i += 2
else:
i += 1
if workspace:
resolved = os.path.abspath(workspace)
if not os.path.isdir(resolved):
@ -74,8 +76,6 @@ def main():
services.append(client)
if config.TELEGRAM_ENABLED:
from services.telegram_client import TelegramClient
allowed_ids = []
if config.TELEGRAM_ALLOWED_GROUP_IDS.strip():
allowed_ids = [r.strip() for r in config.TELEGRAM_ALLOWED_GROUP_IDS.split(',') if r.strip()]
@ -108,8 +108,8 @@ def main():
svc.stop()
_shutdown = True
signal.signal(signal.SIGTERM, _handle_sig)
signal.signal(signal.SIGINT, _handle_sig)
signal.signal(signal.SIGTERM, _handle_sig) # Handling interupt
signal.signal(signal.SIGINT, _handle_sig) # Handling terminate
try:
while not _shutdown:
@ -117,8 +117,8 @@ def main():
except KeyboardInterrupt:
_shutdown = True
print("Exiting.", flush=True)
else:
from tui import HendrikTUI
HendrikTUI(
llm_client = llm_client,
tools_definition = tools_definition,

View File

@ -1,11 +1,9 @@
import json
from datetime import datetime
def _ts():
return datetime.now().strftime('%H:%M:%S')
def execute_tool(tool_call, TOOL_HANDLERS):
tname = tool_call['function']['name']
targs = json.loads(tool_call['function']['arguments'])
@ -26,7 +24,6 @@ def execute_tool(tool_call, TOOL_HANDLERS):
except Exception as e:
return f'Error executing tool: {str(e)}'
def run_agent_loop(session, llm_client, TOOLS, TOOL_HANDLERS, max_iterations, on_tool_calls=None):
for step in range(max_iterations):
print(f'[{_ts()}] Step {step + 1} — calling LLM...', flush=True)
@ -66,3 +63,4 @@ def run_agent_loop(session, llm_client, TOOLS, TOOL_HANDLERS, max_iterations, on
'content': 'Max iterations reached without final answer.',
})
return None

View File

@ -1,4 +1,4 @@
from .personality import build_system_prompt
import re
def tools_mapping(schema, handler, name=None):
tool_name = name or schema["function"]["name"]
@ -10,3 +10,25 @@ def tool_schemas(tools_definition):
def tool_handlers(tools_definition):
return {t["name"]: t["handler"] for t in tools_definition}
def strip_thinking(text: str) -> str:
if not text:
return text
# Strip XML-style thinking blocks (case-insensitive, DOTALL for multiline)
text = re.sub(r'<think[^>]*>.*?</think>', '', text, flags=re.DOTALL | re.IGNORECASE)
text = re.sub(r'<reasoning[^>]*>.*?</reasoning>', '', text, flags=re.DOTALL | re.IGNORECASE)
# Strip lines starting with Thinking: / Reasoning: / Let me think...
lines = text.splitlines()
cleaned = []
skip_block = False
for line in lines:
stripped = line.strip().lower()
if stripped.startswith(('thinking:', 'reasoning:', 'let me thought', 'let me think')):
skip_block = True
continue
if skip_block and not stripped:
skip_block = False
continue
if not skip_block:
cleaned.append(line)
result = '\n'.join(cleaned).strip()
return result

View File

@ -1,49 +1,14 @@
import json
import re
from scripts import gadget
import urllib.request
import urllib.error
def _strip_thinking(text: str) -> str:
"""
Hapus semua bentuk thinking/reasoning dari response text.
Handles:
- <think>...</think> blocks (any case)
- <reasoning>...</reasoning> blocks
- "Thinking:" / "Reasoning:" inline prefixes
"""
if not text:
return text
# Strip XML-style thinking blocks (case-insensitive, DOTALL for multiline)
text = re.sub(r'<think[^>]*>.*?</think>', '', text, flags=re.DOTALL | re.IGNORECASE)
text = re.sub(r'<reasoning[^>]*>.*?</reasoning>', '', text, flags=re.DOTALL | re.IGNORECASE)
# Strip lines starting with Thinking: / Reasoning: / Let me think...
lines = text.splitlines()
cleaned = []
skip_block = False
for line in lines:
stripped = line.strip().lower()
if stripped.startswith(('thinking:', 'reasoning:', 'let me thought', 'let me think')):
skip_block = True
continue
if skip_block and not stripped:
skip_block = False
continue
if not skip_block:
cleaned.append(line)
result = '\n'.join(cleaned).strip()
return result
class LLMClient:
class Message:
def __init__(self, msg):
raw_content = msg.get('content', '')
# Auto-strip thinking dari content
self.content = _strip_thinking(raw_content) if isinstance(raw_content, str) else raw_content
self.content = gadget.strip_thinking(raw_content) if isinstance(raw_content, str) else raw_content
self.tool_calls = msg.get('tool_calls', None)
self.warning = None

View File

@ -7,7 +7,7 @@ from datetime import datetime
import config
from services.session_manager import SessionManager
from services.agent_loop import run_agent_loop
from scripts.agent_loop import run_agent_loop
from scripts.personality import PERSONALITY
from tools.roleplayer import _name_mentioned

View File

@ -6,7 +6,7 @@ from datetime import datetime
from slixmpp import ClientXMPP
from services.session_manager import SessionManager
from services.agent_loop import run_agent_loop
from scripts.agent_loop import run_agent_loop
import config
from tools.roleplayer import should_respond
@ -436,7 +436,7 @@ class XMPPClient(ClientXMPP):
session.start_timer(300, self._timeout_session, room, 'groupchat')
def _execute_tool(self, tool_call):
from services.agent_loop import execute_tool
from scripts.agent_loop import execute_tool
return execute_tool(tool_call, self._TOOL_HANDLERS)
def _schedule_send(self, to, body, mtype='chat'):

View File

@ -2,7 +2,7 @@ import json
import threading
from datetime import datetime
import config
from scripts import ntro
from scripts import ntro, agent_loop
def _add_msg(app, role, content, **kwargs):
@ -15,6 +15,14 @@ def _add_msg(app, role, content, **kwargs):
)
WELCOME_ART = """
,--. ,--.,------.,--. ,--.,------. ,------. ,--.,--. ,--.
| '--' || .---'| ,'.| || .-. \\ | .--. '| || .' /
| .--. || `--, | |' ' || | \\ :| '--'.'| || . '
| | | || `---.| | ` || '--' /| |\\ \\ | || |\\ \\
`--' `--'`------'`--' `--'`-------' `--' '--'`--'`--' '--'
"""
"""
__ __ _______ __ _ ______ ______ ___ ___ _
| | | || || | | || | | _ | | | | | | |
| |_| || ___|| |_| || _ || | || | | | |_| |
@ -35,7 +43,6 @@ WELCOME_ART = """
"""
def log(app, role, text):
app.log.append({
"role": role,
@ -144,7 +151,9 @@ def _agent_loop(app):
"arguments": targs,
}))
app.scroll = 999999
execute_tool(app, tc)
result = agent_loop.execute_tool(tc, app.TOOL_HANDLERS)
_add_msg(app, "tool", str(result), tool_call_id=tc["id"])
# Log content AI setelah tools (jika ada)
if response.content and response.content.strip():
@ -165,26 +174,6 @@ def _agent_loop(app):
app.agent_done.set()
def execute_tool(app, tool_call):
tname = tool_call["function"]["name"]
targs = json.loads(tool_call["function"]["arguments"])
handler = app.TOOL_HANDLERS.get(tname)
app.agent_done.set()
ntro.end(stamp)
if not handler:
result = f"Tool {tname} not found"
else:
try:
if tname == "search_code":
result = handler(
pattern=targs["pattern"],
search_type=targs["search_type"],
path=targs.get("path", "."),
)
elif tname == "git_operation":
result = handler(args=targs["args"])
else:
result = handler(**targs)
except Exception as e:
result = f"Error executing tool: {str(e)}"
_add_msg(app, "tool", str(result), tool_call_id=tool_call["id"])