feat: store and display backend + host metadata on chat messages
Each assistant message in the session JSON now carries: backend, backend_label, host (platform.node()) These fields are shown as model tags in the UI — on live responses and when loading session history. Session log entries (sessions/YYYY-MM-DD.md) include the backend label and host in the turn header. The local (OpenAI-compat) backend strips non-standard fields before sending messages to the API so extra fields don't leak upstream. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -183,7 +183,8 @@ async def _local(system_prompt: str, messages: list[dict], model_cfg: dict | Non
|
|||||||
msgs: list[dict] = []
|
msgs: list[dict] = []
|
||||||
if system_prompt:
|
if system_prompt:
|
||||||
msgs.append({"role": "system", "content": system_prompt})
|
msgs.append({"role": "system", "content": system_prompt})
|
||||||
msgs.extend(messages)
|
# Strip any non-standard metadata fields before sending to the API
|
||||||
|
msgs.extend({"role": m["role"], "content": m["content"]} for m in messages)
|
||||||
|
|
||||||
url = api_url.rstrip("/") + chat_path
|
url = api_url.rstrip("/") + chat_path
|
||||||
headers: dict[str, str] = {}
|
headers: dict[str, str] = {}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
import platform
|
||||||
import jwt
|
import jwt
|
||||||
from fastapi import APIRouter, HTTPException, Query, Request
|
from fastapi import APIRouter, HTTPException, Query, Request
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
@@ -108,10 +109,18 @@ async def _stream_chat(req: ChatRequest):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
response_text, actual_backend = task.result()
|
response_text, actual_backend = task.result()
|
||||||
history.append({"role": "assistant", "content": response_text})
|
backend_label = _backend_label(actual_backend, user, role="chat")
|
||||||
|
host = platform.node()
|
||||||
|
history.append({
|
||||||
|
"role": "assistant",
|
||||||
|
"content": response_text,
|
||||||
|
"backend": actual_backend,
|
||||||
|
"backend_label": backend_label,
|
||||||
|
"host": host,
|
||||||
|
})
|
||||||
save_session(session_id, history)
|
save_session(session_id, history)
|
||||||
if not req.off_record:
|
if not req.off_record:
|
||||||
log_turn(session_id, req.message, response_text)
|
log_turn(session_id, req.message, response_text, backend_label, host)
|
||||||
|
|
||||||
# fallback_used only makes sense for explicit backend selections.
|
# fallback_used only makes sense for explicit backend selections.
|
||||||
# In auto mode (req.model is None), just report what responded.
|
# In auto mode (req.model is None), just report what responded.
|
||||||
@@ -121,7 +130,8 @@ async def _stream_chat(req: ChatRequest):
|
|||||||
"response": response_text,
|
"response": response_text,
|
||||||
"session_id": session_id,
|
"session_id": session_id,
|
||||||
"backend": actual_backend,
|
"backend": actual_backend,
|
||||||
"backend_label": _backend_label(actual_backend, user, role="chat"),
|
"backend_label": backend_label,
|
||||||
|
"host": host,
|
||||||
"fallback_used": fallback_used,
|
"fallback_used": fallback_used,
|
||||||
}
|
}
|
||||||
yield f"data: {json.dumps(payload)}\n\n"
|
yield f"data: {json.dumps(payload)}\n\n"
|
||||||
|
|||||||
@@ -3,7 +3,13 @@ from config import settings
|
|||||||
from persona import persona_path
|
from persona import persona_path
|
||||||
|
|
||||||
|
|
||||||
def log_turn(session_id: str, user_msg: str, assistant_msg: str) -> None:
|
def log_turn(
|
||||||
|
session_id: str,
|
||||||
|
user_msg: str,
|
||||||
|
assistant_msg: str,
|
||||||
|
backend_label: str = "",
|
||||||
|
host: str = "",
|
||||||
|
) -> None:
|
||||||
today = datetime.now().strftime("%Y-%m-%d")
|
today = datetime.now().strftime("%Y-%m-%d")
|
||||||
sessions_dir = persona_path() / "sessions"
|
sessions_dir = persona_path() / "sessions"
|
||||||
sessions_dir.mkdir(exist_ok=True)
|
sessions_dir.mkdir(exist_ok=True)
|
||||||
@@ -12,11 +18,14 @@ def log_turn(session_id: str, user_msg: str, assistant_msg: str) -> None:
|
|||||||
timestamp = datetime.now().strftime("%H:%M")
|
timestamp = datetime.now().strftime("%H:%M")
|
||||||
is_new = not log_file.exists()
|
is_new = not log_file.exists()
|
||||||
|
|
||||||
|
meta_parts = [p for p in [backend_label, host] if p]
|
||||||
|
meta = f" · {' / '.join(meta_parts)}" if meta_parts else ""
|
||||||
|
|
||||||
with open(log_file, "a") as f:
|
with open(log_file, "a") as f:
|
||||||
if is_new:
|
if is_new:
|
||||||
f.write(f"# Session Log — {today}\n")
|
f.write(f"# Session Log — {today}\n")
|
||||||
f.write(
|
f.write(
|
||||||
f"\n### [{timestamp}] `{session_id}`\n"
|
f"\n### [{timestamp}] `{session_id}`{meta}\n"
|
||||||
f"**{settings.user_name}:** {user_msg}\n\n"
|
f"**{settings.user_name}:** {user_msg}\n\n"
|
||||||
f"**{settings.agent_name}:** {assistant_msg}\n"
|
f"**{settings.agent_name}:** {assistant_msg}\n"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -566,6 +566,13 @@
|
|||||||
currentHistory.push({ role, content: msg.content });
|
currentHistory.push({ role, content: msg.content });
|
||||||
const msgDiv = addMessage(role, msg.content);
|
const msgDiv = addMessage(role, msg.content);
|
||||||
attachHistoryControls(msgDiv, i);
|
attachHistoryControls(msgDiv, i);
|
||||||
|
if (role === 'assistant' && (msg.backend_label || msg.backend)) {
|
||||||
|
const modelTag = document.createElement('div');
|
||||||
|
modelTag.className = 'model-tag';
|
||||||
|
const label = msg.backend_label || msg.backend;
|
||||||
|
modelTag.textContent = msg.host ? `${label} · ${msg.host}` : label;
|
||||||
|
msgDiv.appendChild(modelTag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!silent) addMessage('system', `Resumed session ${id}`);
|
if (!silent) addMessage('system', `Resumed session ${id}`);
|
||||||
@@ -979,9 +986,10 @@
|
|||||||
const modelTag = document.createElement('div');
|
const modelTag = document.createElement('div');
|
||||||
modelTag.className = 'model-tag' + (data.fallback_used ? ' fallback' : '');
|
modelTag.className = 'model-tag' + (data.fallback_used ? ' fallback' : '');
|
||||||
const label = data.backend_label || data.backend || '';
|
const label = data.backend_label || data.backend || '';
|
||||||
|
const hostSuffix = data.host ? ` · ${data.host}` : '';
|
||||||
modelTag.textContent = data.fallback_used
|
modelTag.textContent = data.fallback_used
|
||||||
? `⚡ fallback → ${label}`
|
? `⚡ fallback → ${label}${hostSuffix}`
|
||||||
: label;
|
: `${label}${hostSuffix}`;
|
||||||
thinkingDiv.appendChild(modelTag);
|
thinkingDiv.appendChild(modelTag);
|
||||||
} else if (data.type === 'error') {
|
} else if (data.type === 'error') {
|
||||||
throw new Error(data.message);
|
throw new Error(data.message);
|
||||||
|
|||||||
Reference in New Issue
Block a user