Improving tool calling information

This commit is contained in:
Dita Aji Pratama 2026-06-09 11:38:50 +07:00
parent f121c6cbb0
commit 399850eb3b
2 changed files with 89 additions and 27 deletions

View File

@ -74,7 +74,11 @@ def _agent_loop(app):
app.scroll = 999999 app.scroll = 999999
for tc in response.tool_calls: for tc in response.tool_calls:
tname = tc["function"]["name"] tname = tc["function"]["name"]
log(app, "system", f" \u2192 {tname}") targs = tc["function"]["arguments"]
log(app, "tool_call", json.dumps({
"name": tname,
"arguments": targs,
}))
app.scroll = 999999 app.scroll = 999999
execute_tool(app, tc) execute_tool(app, tc)
else: else:

View File

@ -3,6 +3,7 @@
# lalu membaca state dari `app` untuk menggambar di layar. # lalu membaca state dari `app` untuk menggambar di layar.
import curses import curses
import json
import os import os
import textwrap import textwrap
@ -21,6 +22,8 @@ C_INPUT_BORDER = 9 # border input box: biru
C_STATUS_INFO = 12 # status info (workspace/hints): putih C_STATUS_INFO = 12 # status info (workspace/hints): putih
C_HINT_DISABLED = 13 # hint disabled (abu-abu) C_HINT_DISABLED = 13 # hint disabled (abu-abu)
C_WELCOME = 14 # welcome art: light blue C_WELCOME = 14 # welcome art: light blue
C_TOOL_CALL = 15 # tool call: kuning terang
C_TOOL_RESULT = 16 # tool result: magenta muda
def init_colors(): def init_colors():
@ -40,6 +43,8 @@ def init_colors():
curses.init_pair(C_INPUT_BORDER, curses.COLOR_BLUE, -1) curses.init_pair(C_INPUT_BORDER, curses.COLOR_BLUE, -1)
curses.init_pair(C_HINT_DISABLED, 8, -1) # abu-abu di atas bg default curses.init_pair(C_HINT_DISABLED, 8, -1) # abu-abu di atas bg default
curses.init_pair(C_WELCOME, curses.COLOR_BLUE + 8, -1) # light blue curses.init_pair(C_WELCOME, curses.COLOR_BLUE + 8, -1) # light blue
curses.init_pair(C_TOOL_CALL, curses.COLOR_YELLOW + 8, -1) # bright yellow
curses.init_pair(C_TOOL_RESULT, curses.COLOR_MAGENTA + 8, -1) # bright magenta
def draw(app, stdscr): def draw(app, stdscr):
@ -74,67 +79,113 @@ def draw_chat(app, stdscr):
if chat_h <= 0: if chat_h <= 0:
return return
# Render log ke list of (color, text) agar scroll calculation akurat # Render log ke list of rows; setiap row = list of (color, text) segments.
# Setiap baris di-wrap sesuai lebar terminal # Ini memungkinkan satu baris punya multi-warna (misal label tool_call).
rendered = [] # Setiap baris di-wrap sesuai lebar terminal.
rendered = [] # list of list of (color, text)
def _add_row(segments):
# segments: list of (color, text)
rendered.append(segments)
def _add_blank():
rendered.append([(None, "")])
def _wrap_render(text, indent=0, color=C_INPUT): def _wrap_render(text, indent=0, color=C_INPUT):
available = w - indent - 1 # sisakan 1 kolom margin kanan available = w - indent - 1 # sisakan 1 kolom margin kanan
if available <= 0: if available <= 0:
rendered.append((color, " " * indent)) _add_row([(color, " " * indent)])
return return
for line in text.split("\n"): for line in text.split("\n"):
if not line: if not line:
rendered.append((color, " " * indent)) _add_row([(color, " " * indent)])
continue continue
start = 0 start = 0
while start < len(line): while start < len(line):
chunk = line[start:start + available] chunk = line[start:start + available]
rendered.append((color, " " * indent + chunk)) _add_row([(color, " " * indent + chunk)])
start += available start += available
for idx, item in enumerate(app.log): for idx, item in enumerate(app.log):
role, text = item["role"], item["text"] role, text = item["role"], item["text"]
if role == "sep": if role == "sep":
rendered.append((None, "")) _add_blank()
rendered.append((None, "")) _add_blank()
continue continue
# Tambah blank line sebelum system log setelah user/ai response # Tambah blank line sebelum system log setelah user/ai response
if role == "system" and idx > 0 and app.log[idx - 1]["role"] in ("user", "ai"): if role == "system" and idx > 0 and app.log[idx - 1]["role"] in ("user", "ai"):
rendered.append((None, "")) _add_blank()
# Tambah blank line sebelum ai response setelah user (langsung, tanpa tools) # Tambah blank line sebelum ai response setelah user (langsung, tanpa tools)
if role == "ai" and idx > 0 and app.log[idx - 1]["role"] in ("user", "ai"): if role == "ai" and idx > 0 and app.log[idx - 1]["role"] in ("user", "ai"):
rendered.append((None, "")) _add_blank()
# Tambah blank line sebelum ai response setelah system log # Tambah blank line sebelum ai response setelah system log
if role == "ai" and idx > 0 and app.log[idx - 1]["role"] == "system": if role == "ai" and idx > 0 and app.log[idx - 1]["role"] == "system":
rendered.append((None, "")) _add_blank()
if role == "user": if role == "user":
label = f" You ({item['time']}) " label = f" You ({item['time']}) "
rendered.append((C_USER, label)) _add_row([(C_USER, label)])
_wrap_render(text, indent=1, color=C_INPUT) _wrap_render(text, indent=1, color=C_INPUT)
elif role == "ai": elif role == "ai":
label = f" Hendrik ({item['time']}) " label = f" Hendrik ({item['time']}) "
rendered.append((C_AI, label)) _add_row([(C_AI, label)])
_wrap_render(text, indent=1, color=C_INPUT) _wrap_render(text, indent=1, color=C_INPUT)
elif role == "system": elif role == "system":
lines = text.split("\n") lines = text.split("\n")
rendered.append((C_SYSTEM, lines[0])) _add_row([(C_SYSTEM, lines[0])])
for line in lines[1:]: for line in lines[1:]:
rendered.append((C_SYSTEM, " " + line)) _add_row([(C_SYSTEM, " " + line)])
elif role == "welcome": elif role == "welcome":
lines = text.split("\n") lines = text.split("\n")
for line in lines: for line in lines:
rendered.append((C_WELCOME, " " + line)) _add_row([(C_WELCOME, " " + line)])
elif role == "tool_call":
# Format:
# (blank line)
# Hendrik run_bash (HH:MM) ← "Hendrik" hijau, "run_bash (HH:MM)" kuning
# { ← arguments indent 1 spasi, kuning
# "command": "ls -la"
# }
# (blank line)
# Blank line di atas (kecuali sebelumnya sudah blank dari role lain)
if idx > 0 and app.log[idx - 1]["role"] not in ("sep", "welcome"):
_add_blank()
try:
tc = json.loads(text)
tname = tc["name"]
targs_raw = tc["arguments"]
# Pretty-print arguments
try:
targs = json.loads(targs_raw) if isinstance(targs_raw, str) else targs_raw
args_str = json.dumps(targs, indent=2, ensure_ascii=False)
except Exception:
args_str = str(targs_raw)
# Label: "Hendrik" hijau + "tool_name" kuning + "(HH:MM)" hijau
_add_row([
(C_AI, " Hendrik "),
(C_TOOL_CALL, tname),
(C_AI, f" ({item['time']}) "),
])
for aline in args_str.split("\n"):
_add_row([(C_INPUT, " " + aline)])
except Exception:
_add_row([
(C_AI, " Hendrik "),
(C_TOOL_CALL, "unknown"),
(C_AI, f" ({item['time']}) "),
])
# Blank line di bawah
_add_blank()
elif role == "error": elif role == "error":
label = " \u2717 " label = " \u2717 "
lines = text.split("\n") lines = text.split("\n")
rendered.append((C_ERROR, label + (lines[0] if lines else ""))) _add_row([(C_ERROR, label + (lines[0] if lines else ""))])
for line in lines[1:]: for line in lines[1:]:
rendered.append((C_ERROR, " " + line)) _add_row([(C_ERROR, " " + line)])
# Clamp scroll agar tidak melebihi total baris # Clamp scroll agar tidak melebihi total baris
total = len(rendered) total = len(rendered)
@ -153,14 +204,21 @@ def draw_chat(app, stdscr):
y = chat_top y = chat_top
for i in range(app.scroll, min(app.scroll + chat_h, total)): for i in range(app.scroll, min(app.scroll + chat_h, total)):
color, text = rendered[i] segments = rendered[i]
attr = curses.color_pair(color) | curses.A_BOLD if color else curses.A_NORMAL x = 0
if len(text) > w: for color, text in segments:
text = text[:w] if not text:
try: continue
stdscr.addstr(y, 0, text, attr) attr = curses.color_pair(color) | curses.A_BOLD if color else curses.A_NORMAL
except curses.error: remaining = w - x
pass if remaining <= 0:
break
display = text[:remaining]
try:
stdscr.addstr(y, x, display, attr)
except curses.error:
pass
x += len(display)
y += 1 y += 1