Fixing model info
This commit is contained in:
parent
72b50811f1
commit
911e44fdd5
@ -2,7 +2,6 @@ import json
|
|||||||
import threading
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import config
|
import config
|
||||||
from lib import moop
|
|
||||||
from lib import ntro, agent_loop
|
from lib import ntro, agent_loop
|
||||||
from tools.vision import ImagePayload
|
from tools.vision import ImagePayload
|
||||||
|
|
||||||
@ -45,12 +44,39 @@ WELCOME_ART = """
|
|||||||
╚══════════════════════════════════════════╝
|
╚══════════════════════════════════════════╝
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def log(app, role, text):
|
def log(app, role, text, model_info=None):
|
||||||
app.log.append({
|
entry = {
|
||||||
"role": role,
|
"role": role,
|
||||||
"text": text,
|
"text": text,
|
||||||
"time": datetime.now().strftime("%H:%M"),
|
"time": datetime.now().strftime("%H:%M"),
|
||||||
})
|
}
|
||||||
|
if model_info:
|
||||||
|
# Display pair: (provider, model)
|
||||||
|
entry["model_info"] = model_info
|
||||||
|
app.log.append(entry)
|
||||||
|
|
||||||
|
|
||||||
|
def _pair(model_info):
|
||||||
|
"""(provider, model) untuk display; None jika info tidak ada."""
|
||||||
|
if not model_info:
|
||||||
|
return None
|
||||||
|
return (model_info.get("provider"), model_info.get("model"))
|
||||||
|
|
||||||
|
|
||||||
|
def _tag_ai_logs(app, prev_entries, pair):
|
||||||
|
"""Tempelkan info model yang benar-benar dipakai ke entri log AI yang baru
|
||||||
|
dibuat pada langkah ini.
|
||||||
|
|
||||||
|
Berbasis identitas entri, bukan index: entri "Thinking..." di-pop setelah
|
||||||
|
llm.chat() kembali sehingga index entri AI ikut bergeser. `prev_entries`
|
||||||
|
menahan referensi entri lama agar id-nya tidak dipakai ulang oleh Python.
|
||||||
|
"""
|
||||||
|
if not pair:
|
||||||
|
return
|
||||||
|
seen = {id(e) for e in prev_entries}
|
||||||
|
for entry in app.log:
|
||||||
|
if entry.get("role") == "ai" and id(entry) not in seen:
|
||||||
|
entry["model_info"] = pair
|
||||||
|
|
||||||
|
|
||||||
def submit(app, stdscr):
|
def submit(app, stdscr):
|
||||||
@ -59,12 +85,6 @@ def submit(app, stdscr):
|
|||||||
return
|
return
|
||||||
|
|
||||||
log(app, "user", query)
|
log(app, "user", query)
|
||||||
model_info = (
|
|
||||||
moop.resolve_provider(None, app.llm.base_url, app.llm.model),
|
|
||||||
app.llm.model
|
|
||||||
)
|
|
||||||
if app.log:
|
|
||||||
app.log[-1]["model_info"] = model_info
|
|
||||||
app.input_buffer = [""]
|
app.input_buffer = [""]
|
||||||
app.input_line = 0
|
app.input_line = 0
|
||||||
app.input_col = 0
|
app.input_col = 0
|
||||||
@ -83,7 +103,7 @@ def submit(app, stdscr):
|
|||||||
app._model_info(),
|
app._model_info(),
|
||||||
)
|
)
|
||||||
|
|
||||||
_add_msg(app, "user", query, model_info=app._model_info())
|
_add_msg(app, "user", query)
|
||||||
|
|
||||||
app.agent_done.clear()
|
app.agent_done.clear()
|
||||||
app.agent_thread = threading.Thread(
|
app.agent_thread = threading.Thread(
|
||||||
@ -128,8 +148,15 @@ def _agent_loop(app):
|
|||||||
skill = config.AGENT_SKILLS
|
skill = config.AGENT_SKILLS
|
||||||
tool_reminder = None
|
tool_reminder = None
|
||||||
|
|
||||||
|
log_prev = list(app.log)
|
||||||
response = app.llm.chat(app.messages, tools=app.TOOLS, on_stream_chunk=on_stream_chunk, tool_reminder=tool_reminder)
|
response = app.llm.chat(app.messages, tools=app.TOOLS, on_stream_chunk=on_stream_chunk, tool_reminder=tool_reminder)
|
||||||
|
|
||||||
|
# Model final: diambil dari response, bukan dari state llm saat ini,
|
||||||
|
# supaya auto-switch/rotasi key/fallback ikut tercatat dengan benar.
|
||||||
|
used = getattr(response, "model_info", None) or {}
|
||||||
|
pair = _pair(used)
|
||||||
|
akwargs = {"model_info": used} if used else {}
|
||||||
|
|
||||||
# Hapus "Thinking..." log
|
# Hapus "Thinking..." log
|
||||||
for i in range(len(app.log) - 1, -1, -1):
|
for i in range(len(app.log) - 1, -1, -1):
|
||||||
if app.log[i].get('role') == 'system' and 'Thinking' in app.log[i].get('text', ''):
|
if app.log[i].get('role') == 'system' and 'Thinking' in app.log[i].get('text', ''):
|
||||||
@ -177,11 +204,12 @@ def _agent_loop(app):
|
|||||||
if placeholder_marker in text or text == "" or text == "...":
|
if placeholder_marker in text or text == "" or text == "...":
|
||||||
app.log.pop(i)
|
app.log.pop(i)
|
||||||
|
|
||||||
_add_msg(app, "assistant", response.content, tool_calls=response.tool_calls)
|
_add_msg(app, "assistant", response.content, tool_calls=response.tool_calls, **akwargs)
|
||||||
|
|
||||||
# Log content AI dulu (penjelasan sebelum tools)
|
# Log content AI dulu (penjelasan sebelum tools)
|
||||||
if response.content and response.content.strip():
|
if response.content and response.content.strip():
|
||||||
log(app, "ai", response.content)
|
log(app, "ai", response.content, model_info=pair)
|
||||||
|
_tag_ai_logs(app, log_prev, pair)
|
||||||
|
|
||||||
# Log dan execute tool_calls
|
# Log dan execute tool_calls
|
||||||
for tc in response.tool_calls:
|
for tc in response.tool_calls:
|
||||||
@ -190,7 +218,7 @@ def _agent_loop(app):
|
|||||||
log(app, "tool_call", json.dumps({
|
log(app, "tool_call", json.dumps({
|
||||||
"name": tname,
|
"name": tname,
|
||||||
"arguments": targs,
|
"arguments": targs,
|
||||||
}))
|
}), model_info=pair)
|
||||||
if app.scroll_follow:
|
if app.scroll_follow:
|
||||||
app.scroll = 999999
|
app.scroll = 999999
|
||||||
result = agent_loop.execute_tool(tc, app.TOOL_HANDLERS)
|
result = agent_loop.execute_tool(tc, app.TOOL_HANDLERS)
|
||||||
@ -205,8 +233,9 @@ def _agent_loop(app):
|
|||||||
has_content = bool(response.content) or (isinstance(response.content, list) and len(response.content) > 0)
|
has_content = bool(response.content) or (isinstance(response.content, list) and len(response.content) > 0)
|
||||||
if has_content:
|
if has_content:
|
||||||
if isinstance(response.content, list):
|
if isinstance(response.content, list):
|
||||||
log(app, "ai", "[Image generated]")
|
log(app, "ai", "[Image generated]", model_info=pair)
|
||||||
_add_msg(app, "assistant", response.content)
|
_add_msg(app, "assistant", response.content, **akwargs)
|
||||||
|
_tag_ai_logs(app, log_prev, pair)
|
||||||
log(app, "sep", "")
|
log(app, "sep", "")
|
||||||
ntro.end(stamp)
|
ntro.end(stamp)
|
||||||
app.agent_done.set()
|
app.agent_done.set()
|
||||||
|
|||||||
@ -209,22 +209,23 @@ class HendrikTUI:
|
|||||||
if i > 0:
|
if i > 0:
|
||||||
log(self, "sep", "")
|
log(self, "sep", "")
|
||||||
log(self, "user", msg["content"])
|
log(self, "user", msg["content"])
|
||||||
mi = msg.get("model_info")
|
|
||||||
if mi:
|
|
||||||
self.log[-1]["model_info"] = (mi.get("provider"), mi.get("model"))
|
|
||||||
elif msg["role"] == "assistant":
|
elif msg["role"] == "assistant":
|
||||||
|
mi = msg.get("model_info")
|
||||||
|
pair = (mi.get("provider"), mi.get("model")) if mi else None
|
||||||
# Log tool calls jika ada
|
# Log tool calls jika ada
|
||||||
if msg.get("tool_calls"):
|
if msg.get("tool_calls"):
|
||||||
for tc in msg["tool_calls"]:
|
for tc in msg["tool_calls"]:
|
||||||
log(self, "tool_call", json.dumps({
|
log(self, "tool_call", json.dumps({
|
||||||
"name": tc["function"]["name"],
|
"name": tc["function"]["name"],
|
||||||
"arguments": tc["function"]["arguments"],
|
"arguments": tc["function"]["arguments"],
|
||||||
}))
|
}), model_info=pair)
|
||||||
# Log AI content jika ada dan ini adalah response final
|
# Log AI content jika ada dan ini adalah response final
|
||||||
next_role = session.messages[i + 1]["role"] if i + 1 < len(session.messages) else None
|
next_role = session.messages[i + 1]["role"] if i + 1 < len(session.messages) else None
|
||||||
if next_role in ("user", None):
|
if next_role in ("user", None):
|
||||||
if msg.get("content"):
|
if msg.get("content"):
|
||||||
log(self, "ai", msg["content"])
|
log(self, "ai", msg["content"])
|
||||||
|
if pair:
|
||||||
|
self.log[-1]["model_info"] = pair
|
||||||
elif msg["role"] == "tool":
|
elif msg["role"] == "tool":
|
||||||
# Tool result - tidak perlu di-log secara eksplisit
|
# Tool result - tidak perlu di-log secara eksplisit
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -55,6 +55,19 @@ def draw_header(app, stdscr):
|
|||||||
draw_menubar(app, stdscr)
|
draw_menubar(app, stdscr)
|
||||||
|
|
||||||
|
|
||||||
|
def _model_info_line(item):
|
||||||
|
"""Baris " Provider - Model " dari entri log; "" jika info tidak ada.
|
||||||
|
|
||||||
|
Dipakai bersama oleh blok `ai` dan `tool_call` agar tampilan konsisten."""
|
||||||
|
model_info = item.get("model_info")
|
||||||
|
if not model_info:
|
||||||
|
return ""
|
||||||
|
p_name, m_name = model_info
|
||||||
|
if p_name:
|
||||||
|
return f" {p_name} - {m_name} "
|
||||||
|
return f" {m_name} " if m_name else ""
|
||||||
|
|
||||||
|
|
||||||
def draw_chat(app, stdscr):
|
def draw_chat(app, stdscr):
|
||||||
# Area chat — dari baris 2 sampai baris (h - 11).
|
# Area chat — dari baris 2 sampai baris (h - 11).
|
||||||
# Bisa di-scroll dengan Page Up / Page Down.
|
# Bisa di-scroll dengan Page Up / Page Down.
|
||||||
@ -136,20 +149,13 @@ def draw_chat(app, stdscr):
|
|||||||
_add_blank()
|
_add_blank()
|
||||||
|
|
||||||
if role == "user":
|
if role == "user":
|
||||||
model_info = item.get("model_info", None)
|
_add_row([(C_USER, f" You ({item['time']}) ")])
|
||||||
if model_info:
|
|
||||||
p_name, m_name = model_info
|
|
||||||
if p_name:
|
|
||||||
info_line = f" {p_name} - {m_name} "
|
|
||||||
else:
|
|
||||||
info_line = f" {m_name} "
|
|
||||||
else:
|
|
||||||
info_line = " Unknown - Model info not found "
|
|
||||||
_add_row([(C_USER, info_line, False)])
|
|
||||||
label = f" You ({item['time']}) "
|
|
||||||
_add_row([(C_USER, label)])
|
|
||||||
_wrap_render(text, indent=1, color=C_INPUT, bold=False)
|
_wrap_render(text, indent=1, color=C_INPUT, bold=False)
|
||||||
elif role == "ai":
|
elif role == "ai":
|
||||||
|
info_line = _model_info_line(item)
|
||||||
|
if info_line:
|
||||||
|
_add_row([(C_AI, info_line, False)])
|
||||||
|
|
||||||
label = f" {app.character_name} ({item['time']}) "
|
label = f" {app.character_name} ({item['time']}) "
|
||||||
_add_row([(C_AI, label)])
|
_add_row([(C_AI, label)])
|
||||||
_wrap_render(text, indent=1, color=C_INPUT, bold=False)
|
_wrap_render(text, indent=1, color=C_INPUT, bold=False)
|
||||||
@ -165,6 +171,7 @@ def draw_chat(app, stdscr):
|
|||||||
elif role == "tool_call":
|
elif role == "tool_call":
|
||||||
# Format:
|
# Format:
|
||||||
# (blank line)
|
# (blank line)
|
||||||
|
# Ollama Cloud - gemma4:31b-cloud
|
||||||
# Hendrik run_bash (HH:MM) ← "Hendrik" hijau, "run_bash (HH:MM)" kuning
|
# Hendrik run_bash (HH:MM) ← "Hendrik" hijau, "run_bash (HH:MM)" kuning
|
||||||
# { ← arguments indent 1 spasi, kuning
|
# { ← arguments indent 1 spasi, kuning
|
||||||
# "command": "ls -la"
|
# "command": "ls -la"
|
||||||
@ -173,6 +180,9 @@ def draw_chat(app, stdscr):
|
|||||||
# Blank line di atas (kecuali sebelumnya sudah blank dari role lain)
|
# Blank line di atas (kecuali sebelumnya sudah blank dari role lain)
|
||||||
if idx > 0 and app.log[idx - 1]["role"] not in ("sep", "welcome"):
|
if idx > 0 and app.log[idx - 1]["role"] not in ("sep", "welcome"):
|
||||||
_add_blank()
|
_add_blank()
|
||||||
|
info_line = _model_info_line(item)
|
||||||
|
if info_line:
|
||||||
|
_add_row([(C_AI, info_line, False)])
|
||||||
try:
|
try:
|
||||||
tc = json.loads(text)
|
tc = json.loads(text)
|
||||||
tname = tc["name"]
|
tname = tc["name"]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
from lib import gadget
|
from lib import gadget, moop
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.error
|
import urllib.error
|
||||||
|
|
||||||
@ -30,11 +30,14 @@ class LLMClient:
|
|||||||
self._chain_index = 0
|
self._chain_index = 0
|
||||||
|
|
||||||
class Message:
|
class Message:
|
||||||
def __init__(self, msg):
|
def __init__(self, msg, model_info=None):
|
||||||
raw_content = msg.get('content', '') # Ambil konten mentah
|
raw_content = msg.get('content', '') # Ambil konten mentah
|
||||||
self.content = gadget.strip_thinking(raw_content) if isinstance(raw_content, str) else raw_content # Auto-strip <thinking> dari content
|
self.content = gadget.strip_thinking(raw_content) if isinstance(raw_content, str) else raw_content # Auto-strip <thinking> dari content
|
||||||
self.tool_calls = msg.get('tool_calls', None) # Ambil tool calls
|
self.tool_calls = msg.get('tool_calls', None) # Ambil tool calls
|
||||||
self.warning = None
|
self.warning = None
|
||||||
|
# Model yang benar-benar dipakai menghasilkan pesan ini. Bisa berbeda
|
||||||
|
# dengan model awal permintaan karena auto-switch/fallback.
|
||||||
|
self.model_info = model_info or {}
|
||||||
|
|
||||||
# ------------------------------------------------------------ config ---
|
# ------------------------------------------------------------ config ---
|
||||||
|
|
||||||
@ -86,6 +89,23 @@ class LLMClient:
|
|||||||
self.model = cand.get("model", "")
|
self.model = cand.get("model", "")
|
||||||
self.api_key = cand.get("api_key", "")
|
self.api_key = cand.get("api_key", "")
|
||||||
|
|
||||||
|
def _model_info(self, cand=None):
|
||||||
|
"""Info model yang dipakai untuk satu permintaan.
|
||||||
|
|
||||||
|
Dari kandidat chain jika ada (provider sudah dibawa moop), jika tidak
|
||||||
|
(klien non-chain) pakai state aktif dan cari nama provider-nya."""
|
||||||
|
if cand:
|
||||||
|
return {
|
||||||
|
"provider": cand.get("provider"),
|
||||||
|
"base_url": cand.get("base_url", ""),
|
||||||
|
"model": cand.get("model", ""),
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"provider": moop.resolve_provider(None, self.base_url, self.model),
|
||||||
|
"base_url": self.base_url,
|
||||||
|
"model": self.model,
|
||||||
|
}
|
||||||
|
|
||||||
# ----------------------------------------------------- chain selection ---
|
# ----------------------------------------------------- chain selection ---
|
||||||
|
|
||||||
def _pick_chain_type(self, messages):
|
def _pick_chain_type(self, messages):
|
||||||
@ -108,7 +128,7 @@ class LLMClient:
|
|||||||
def chat(self, messages, tools=None, on_stream_chunk=None, disable_reasoning=False, tool_reminder=None):
|
def chat(self, messages, tools=None, on_stream_chunk=None, disable_reasoning=False, tool_reminder=None):
|
||||||
|
|
||||||
if not self._chains: # kalau bukan chain
|
if not self._chains: # kalau bukan chain
|
||||||
return self._chat_once(
|
result = self._chat_once(
|
||||||
self.base_url, self.model, self.api_key,
|
self.base_url, self.model, self.api_key,
|
||||||
messages,
|
messages,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
@ -116,6 +136,8 @@ class LLMClient:
|
|||||||
disable_reasoning=disable_reasoning,
|
disable_reasoning=disable_reasoning,
|
||||||
tool_reminder=tool_reminder,
|
tool_reminder=tool_reminder,
|
||||||
)
|
)
|
||||||
|
result.model_info = self._model_info()
|
||||||
|
return result
|
||||||
|
|
||||||
chain_type = self._pick_chain_type(messages)
|
chain_type = self._pick_chain_type(messages)
|
||||||
if chain_type != self._chain_key:
|
if chain_type != self._chain_key:
|
||||||
@ -176,6 +198,9 @@ class LLMClient:
|
|||||||
disable_reasoning=disable_reasoning, tool_reminder=tool_reminder,
|
disable_reasoning=disable_reasoning, tool_reminder=tool_reminder,
|
||||||
)
|
)
|
||||||
self._chain_index = idx
|
self._chain_index = idx
|
||||||
|
# Catat model yang benar-benar menjawab (bukan model awal
|
||||||
|
# permintaan) supaya pemanggil bisa menampilkan data yang jujur.
|
||||||
|
result.model_info = self._model_info(cand)
|
||||||
return result
|
return result
|
||||||
except _LLMFail as e:
|
except _LLMFail as e:
|
||||||
last = e
|
last = e
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user