Compare commits
7 Commits
6725b7bf0c
...
28bd27f79c
| Author | SHA1 | Date | |
|---|---|---|---|
| 28bd27f79c | |||
| fb52623d3e | |||
| 7aa1f56124 | |||
| e26a026e0c | |||
| 76d68c0fea | |||
| 941157a672 | |||
| bedb38a45e |
@ -2,7 +2,7 @@ skill : programmer
|
|||||||
name : Hendrik
|
name : Hendrik
|
||||||
age : 35
|
age : 35
|
||||||
gender : male
|
gender : male
|
||||||
tone : casual
|
tone : formal
|
||||||
verbosity : concise
|
verbosity : concise
|
||||||
humor : none
|
humor : none
|
||||||
language : id
|
language : id
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
agent:
|
agent:
|
||||||
character: hendrik # Directory name in agent/characters/<character>/
|
character: hendrik # Directory name in agent/characters/<character>/
|
||||||
max_iterations: 40 # step
|
max_iterations: 30 # step
|
||||||
max_tool_output: 40000
|
max_tool_output: 4000
|
||||||
|
|
||||||
llm:
|
llm:
|
||||||
timeout: 3000 # second
|
timeout: 3000 # second
|
||||||
|
|||||||
24
hendrik.py
24
hendrik.py
@ -1,14 +1,17 @@
|
|||||||
import os, sys, threading, time
|
import os, sys, threading, time, signal
|
||||||
import signal
|
|
||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
from services.xmpp_client import XMPPClient
|
|
||||||
|
|
||||||
from scripts.llm_client import LLMClient
|
|
||||||
from tools import coder, rag, carrack
|
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.personality import build_system_prompt
|
||||||
|
from scripts import gadget
|
||||||
|
|
||||||
|
from tui import HendrikTUI
|
||||||
|
|
||||||
tools_definition = [
|
tools_definition = [
|
||||||
|
|
||||||
@ -46,7 +49,6 @@ def main():
|
|||||||
i += 2
|
i += 2
|
||||||
else:
|
else:
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if workspace:
|
if workspace:
|
||||||
resolved = os.path.abspath(workspace)
|
resolved = os.path.abspath(workspace)
|
||||||
if not os.path.isdir(resolved):
|
if not os.path.isdir(resolved):
|
||||||
@ -74,8 +76,6 @@ def main():
|
|||||||
services.append(client)
|
services.append(client)
|
||||||
|
|
||||||
if config.TELEGRAM_ENABLED:
|
if config.TELEGRAM_ENABLED:
|
||||||
from services.telegram_client import TelegramClient
|
|
||||||
|
|
||||||
allowed_ids = []
|
allowed_ids = []
|
||||||
if config.TELEGRAM_ALLOWED_GROUP_IDS.strip():
|
if config.TELEGRAM_ALLOWED_GROUP_IDS.strip():
|
||||||
allowed_ids = [r.strip() for r in config.TELEGRAM_ALLOWED_GROUP_IDS.split(',') if r.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()
|
svc.stop()
|
||||||
_shutdown = True
|
_shutdown = True
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, _handle_sig)
|
signal.signal(signal.SIGTERM, _handle_sig) # Handling interupt
|
||||||
signal.signal(signal.SIGINT, _handle_sig)
|
signal.signal(signal.SIGINT, _handle_sig) # Handling terminate
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while not _shutdown:
|
while not _shutdown:
|
||||||
@ -117,8 +117,8 @@ def main():
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
_shutdown = True
|
_shutdown = True
|
||||||
print("Exiting.", flush=True)
|
print("Exiting.", flush=True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
from tui import HendrikTUI
|
|
||||||
HendrikTUI(
|
HendrikTUI(
|
||||||
llm_client = llm_client,
|
llm_client = llm_client,
|
||||||
tools_definition = tools_definition,
|
tools_definition = tools_definition,
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
def _ts():
|
def _ts():
|
||||||
return datetime.now().strftime('%H:%M:%S')
|
return datetime.now().strftime('%H:%M:%S')
|
||||||
|
|
||||||
|
|
||||||
def execute_tool(tool_call, TOOL_HANDLERS):
|
def execute_tool(tool_call, TOOL_HANDLERS):
|
||||||
tname = tool_call['function']['name']
|
tname = tool_call['function']['name']
|
||||||
targs = json.loads(tool_call['function']['arguments'])
|
targs = json.loads(tool_call['function']['arguments'])
|
||||||
@ -26,7 +24,6 @@ def execute_tool(tool_call, TOOL_HANDLERS):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f'Error executing tool: {str(e)}'
|
return f'Error executing tool: {str(e)}'
|
||||||
|
|
||||||
|
|
||||||
def run_agent_loop(session, llm_client, TOOLS, TOOL_HANDLERS, max_iterations, on_tool_calls=None):
|
def run_agent_loop(session, llm_client, TOOLS, TOOL_HANDLERS, max_iterations, on_tool_calls=None):
|
||||||
for step in range(max_iterations):
|
for step in range(max_iterations):
|
||||||
print(f'[{_ts()}] Step {step + 1} — calling LLM...', flush=True)
|
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.',
|
'content': 'Max iterations reached without final answer.',
|
||||||
})
|
})
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
from .personality import build_system_prompt
|
import re
|
||||||
|
|
||||||
def tools_mapping(schema, handler, name=None):
|
def tools_mapping(schema, handler, name=None):
|
||||||
tool_name = name or schema["function"]["name"]
|
tool_name = name or schema["function"]["name"]
|
||||||
@ -10,3 +10,25 @@ def tool_schemas(tools_definition):
|
|||||||
def tool_handlers(tools_definition):
|
def tool_handlers(tools_definition):
|
||||||
return {t["name"]: t["handler"] for t in 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
|
||||||
|
|||||||
@ -1,49 +1,14 @@
|
|||||||
import json
|
import json
|
||||||
import re
|
from scripts import gadget
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.error
|
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 LLMClient:
|
||||||
class Message:
|
class Message:
|
||||||
def __init__(self, msg):
|
def __init__(self, msg):
|
||||||
raw_content = msg.get('content', '')
|
raw_content = msg.get('content', '')
|
||||||
# Auto-strip thinking dari 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.tool_calls = msg.get('tool_calls', None)
|
||||||
self.warning = None
|
self.warning = None
|
||||||
|
|
||||||
@ -7,7 +7,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
import config
|
import config
|
||||||
from services.session_manager import SessionManager
|
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 scripts.personality import PERSONALITY
|
||||||
from tools.roleplayer import _name_mentioned
|
from tools.roleplayer import _name_mentioned
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +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
|
from scripts.agent_loop import run_agent_loop
|
||||||
|
|
||||||
import config
|
import config
|
||||||
from tools.roleplayer import should_respond
|
from tools.roleplayer import should_respond
|
||||||
@ -436,7 +436,7 @@ class XMPPClient(ClientXMPP):
|
|||||||
session.start_timer(300, self._timeout_session, room, 'groupchat')
|
session.start_timer(300, self._timeout_session, room, 'groupchat')
|
||||||
|
|
||||||
def _execute_tool(self, tool_call):
|
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)
|
return execute_tool(tool_call, self._TOOL_HANDLERS)
|
||||||
|
|
||||||
def _schedule_send(self, to, body, mtype='chat'):
|
def _schedule_send(self, to, body, mtype='chat'):
|
||||||
|
|||||||
39
tui/agent.py
39
tui/agent.py
@ -2,7 +2,7 @@ import json
|
|||||||
import threading
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import config
|
import config
|
||||||
from scripts import ntro
|
from scripts import ntro, agent_loop
|
||||||
|
|
||||||
|
|
||||||
def _add_msg(app, role, content, **kwargs):
|
def _add_msg(app, role, content, **kwargs):
|
||||||
@ -15,6 +15,14 @@ def _add_msg(app, role, content, **kwargs):
|
|||||||
)
|
)
|
||||||
|
|
||||||
WELCOME_ART = """
|
WELCOME_ART = """
|
||||||
|
,--. ,--.,------.,--. ,--.,------. ,------. ,--.,--. ,--.
|
||||||
|
| '--' || .---'| ,'.| || .-. \\ | .--. '| || .' /
|
||||||
|
| .--. || `--, | |' ' || | \\ :| '--'.'| || . '
|
||||||
|
| | | || `---.| | ` || '--' /| |\\ \\ | || |\\ \\
|
||||||
|
`--' `--'`------'`--' `--'`-------' `--' '--'`--'`--' '--'
|
||||||
|
|
||||||
|
"""
|
||||||
|
"""
|
||||||
__ __ _______ __ _ ______ ______ ___ ___ _
|
__ __ _______ __ _ ______ ______ ___ ___ _
|
||||||
| | | || || | | || | | _ | | | | | | |
|
| | | || || | | || | | _ | | | | | | |
|
||||||
| |_| || ___|| |_| || _ || | || | | | |_| |
|
| |_| || ___|| |_| || _ || | || | | | |_| |
|
||||||
@ -35,7 +43,6 @@ WELCOME_ART = """
|
|||||||
╚══════════════════════════════════════════╝
|
╚══════════════════════════════════════════╝
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def log(app, role, text):
|
def log(app, role, text):
|
||||||
app.log.append({
|
app.log.append({
|
||||||
"role": role,
|
"role": role,
|
||||||
@ -144,7 +151,9 @@ def _agent_loop(app):
|
|||||||
"arguments": targs,
|
"arguments": targs,
|
||||||
}))
|
}))
|
||||||
app.scroll = 999999
|
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)
|
# Log content AI setelah tools (jika ada)
|
||||||
if response.content and response.content.strip():
|
if response.content and response.content.strip():
|
||||||
@ -165,26 +174,6 @@ def _agent_loop(app):
|
|||||||
app.agent_done.set()
|
app.agent_done.set()
|
||||||
|
|
||||||
|
|
||||||
def execute_tool(app, tool_call):
|
app.agent_done.set()
|
||||||
tname = tool_call["function"]["name"]
|
ntro.end(stamp)
|
||||||
targs = json.loads(tool_call["function"]["arguments"])
|
|
||||||
handler = app.TOOL_HANDLERS.get(tname)
|
|
||||||
|
|
||||||
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"])
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user