feat: Modell pro Chat waehlen — und sehen, welches gerade laeuft
Nach einem Sicherheitsfilter-Treffer fuhr ein Projekt still auf dem Ausweich-Modell weiter, und nirgends war zu sehen, auf welchem. Zurueck ging nur ueber "Liste leeren" im Filter-Panel, und das gleich fuer alle Chats. Jetzt drei Ebenen (aria-brain/context_model.py): Einstellung (brainModel) < pro Chat < Ausweich (Sicherheitsfilter) - Pro Chat: Hauptchat und jedes Projekt koennen ein eigenes Modell haben, in /shared/config/context_models.json. Bewusst kein Feld im Projekt-Manifest — der Hauptchat ist kein Projekt und waere sonst aussen vor. - "↩ zurueck" pro Chat: loescht nur die Filter-Vermerke DIESES Chats. Blockt der Filter erneut, weicht das Brain eben wieder aus. - Angezeigt wird, was wirklich laeuft: chat() gibt das Modell aus modelUsage der CLI zurueck (aufgeloeste Version statt Alias) samt Grund; Brain → Bridge → chat_backup + Live-Payload → App und Diagnostic. Als Grundeinstellung zaehlt das Modell, mit dem das Brain laeuft, nicht der Dateiwert — so faellt ein ausstehender Neustart nach brainModel-Aenderung auf. Diagnostic: 🧠-Auswahl neben "+ Projekt" fuer den fokussierten Chat, Modell im Untertitel jeder Kontext-Karte, Modell statt "Claude" am Bubble-Badge (orange mit ↪ bei Ausweich). App: hinter der Quell-Anzeige (Einstellungen, Default aus — Kopfzeile bleibt fuer Mama unveraendert) ein Modell-Chip in der Kopfzeile; Tippen oeffnet die Auswahl fuer diesen Chat inkl. "Zurueck". Laeuft ueber den vorhandenen brain_request-Kanal, also keine neuen RVS-Typen. Tests: 47/47 (12 neue fuer die Aufloesungsreihenfolge gegen agent.chat()), tsc ohne Fehler. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+33
-6
@@ -39,9 +39,13 @@ import watcher as watcher_mod
|
||||
import oauth as oauth_mod
|
||||
import projects as projects_mod
|
||||
import safeguard
|
||||
import context_model
|
||||
import project_vms as project_vms_mod
|
||||
|
||||
BRIDGE_URL = os.environ.get("BRIDGE_URL", "http://aria-bridge:8090")
|
||||
# Modell-Info fuer Antworten, an denen kein Claude-Modell beteiligt war
|
||||
# (Fast-Path, Wake-Befehle, lokales LLM). Leer statt geraten.
|
||||
_NO_MODEL = {"model": "", "requested": "", "reason": ""}
|
||||
# SearXNG (self-hosted Meta-Suche) — Backend fuers web_search-Tool (B1b).
|
||||
SEARXNG_URL = os.environ.get("SEARXNG_URL", "http://searxng:8080").rstrip("/")
|
||||
# FLUX-Render kann bis ~90s dauern, beim ersten Render nach Container-Start
|
||||
@@ -2529,7 +2533,7 @@ class Agent:
|
||||
project_id=active_project_id)
|
||||
self.conversation.add("assistant", reply, project_id=active_project_id)
|
||||
logger.info("[wake-off] Sprachbefehl erkannt — App stoppt Listener")
|
||||
return reply, "wake-off", False, False, False
|
||||
return reply, "wake-off", False, False, False, _NO_MODEL
|
||||
|
||||
# Wake-Word AN per Befehl (Text/Aufnahme-Button): App startet den Listener.
|
||||
if _user_wants_wake_on(user_message):
|
||||
@@ -2538,7 +2542,7 @@ class Agent:
|
||||
project_id=active_project_id)
|
||||
self.conversation.add("assistant", reply, project_id=active_project_id)
|
||||
logger.info("[wake-on] Befehl erkannt — App startet Listener")
|
||||
return reply, "wake-on", False, False, False
|
||||
return reply, "wake-on", False, False, False, _NO_MODEL
|
||||
|
||||
# Fast-Path: einfache "reines Steuern"-Commands ueberspringen Claude komplett.
|
||||
# Jeder Skill kann in seinem Manifest fast_patterns deklarieren — das Brain
|
||||
@@ -2565,7 +2569,7 @@ class Agent:
|
||||
elif _wants_continue:
|
||||
converse = True
|
||||
# Fast-Path = reiner Steuerbefehl, nie eine Rueckfrage → awaiting=False.
|
||||
return fast_reply, "fast-path", speak, converse, False
|
||||
return fast_reply, "fast-path", speak, converse, False, _NO_MODEL
|
||||
|
||||
# 1. User-Turn an die Konversation. Den Zeitstempel behalten wir: er
|
||||
# identifiziert diesen Turn, wenn Anthropics Sicherheitsfilter
|
||||
@@ -2591,7 +2595,7 @@ class Agent:
|
||||
elif _wants_continue:
|
||||
converse = True
|
||||
# Local ist tool-loses Reden; blockierende Rueckfragen macht Claude.
|
||||
return local_reply, "local", speak, converse, False
|
||||
return local_reply, "local", speak, converse, False, _NO_MODEL
|
||||
|
||||
# 2. Hot Memory (alle pinned Punkte)
|
||||
hot = self.store.list_pinned()
|
||||
@@ -2733,14 +2737,20 @@ class Agent:
|
||||
# Filter gescheitert, nehmen wir direkt das Ausweich-Modell — sonst
|
||||
# laeuft jeder Turn erst gegen dieselbe Wand. Rollt der Turn aus dem
|
||||
# Fenster, gilt wieder das Hauptmodell; das verfaellt von selbst.
|
||||
primary_model = self.proxy.model
|
||||
# Reihenfolge: Einstellung (brainModel) < pro Chat < Ausweich.
|
||||
primary_model, model_reason = context_model.resolve(
|
||||
active_project_id, self.proxy.model)
|
||||
turn_model = primary_model
|
||||
_blocked = safeguard.blocked_models(active_project_id,
|
||||
[t.ts for t in window])
|
||||
if safeguard.norm_model(primary_model) in _blocked:
|
||||
turn_model = safeguard.fallback_for(primary_model)
|
||||
model_reason = context_model.REASON_FALLBACK
|
||||
logger.warning("[safeguard] %s ist in diesem Fenster blockiert — "
|
||||
"Turn laeuft direkt auf %s", primary_model, turn_model)
|
||||
if model_reason == context_model.REASON_PER_CHAT:
|
||||
logger.info("[context-model] %s nutzt %s (pro Chat gesetzt)",
|
||||
active_project_id or "(Hauptchat)", turn_model)
|
||||
|
||||
logger.info("chat: pinned=%d cold=%d skills=%d/%d window=%d project=%r prompt_chars=%d",
|
||||
len(hot), len(cold), len(active_skills), len(all_skills),
|
||||
@@ -2753,6 +2763,9 @@ class Agent:
|
||||
# zwei Turns, was OpenAI/Anthropic verwirrt und bei strict tools-Aufrufen
|
||||
# zu 400-Errors fuehren kann.
|
||||
final_reply = ""
|
||||
# Welches Modell hat am Ende geantwortet — fuer die Anzeige in App und
|
||||
# Diagnostic. Default: das angefragte; die CLI darf es praezisieren.
|
||||
answered_model = turn_model
|
||||
# Vorlesen ja/nein fuer diesen Claude-Turn. Default True (Gespraech).
|
||||
# Fuehrt Claude einen Steuerbefehl-Skill (run_*, speak=false) erfolgreich
|
||||
# aus, wird die Antwort NICHT vorgelesen — der Text landet aber normal in
|
||||
@@ -2763,8 +2776,16 @@ class Agent:
|
||||
self._ear_control = None # ear_control-Tool: 'off'|'on' oder None
|
||||
try:
|
||||
for iteration in range(self.MAX_TOOL_ITERATIONS):
|
||||
_model_before = turn_model
|
||||
result, turn_model = self._llm_call(
|
||||
messages, tools, active_project_id, turn_model, user_turn_ts)
|
||||
if turn_model != _model_before:
|
||||
# _llm_call ist auf das Ausweich-Modell gewechselt.
|
||||
model_reason = context_model.REASON_FALLBACK
|
||||
# modelUsage der CLI ist die Wahrheit: bei einem Tier-Alias
|
||||
# steht hier die aufgeloeste Version ("sonnet" → "claude-...").
|
||||
if result.model_used:
|
||||
answered_model = result.model_used
|
||||
if result.tool_calls:
|
||||
# Assistant-Turn mit tool_calls in messages anhaengen (nicht in Conversation!)
|
||||
messages.append(ProxyMessage(
|
||||
@@ -2915,7 +2936,13 @@ class Agent:
|
||||
elif self._ear_control == "on":
|
||||
answered_by, speak, converse = "wake-on", False, False
|
||||
# awaiting_reply = ARIA stellt eine blockierende Rueckfrage (Queue pausiert).
|
||||
return (final_reply, answered_by, speak, converse, awaiting_reply)
|
||||
# Modell-Info: was hat geantwortet und warum genau dieses. Die
|
||||
# Oberflaechen zeigen das an — sonst merkt niemand, dass ein Projekt
|
||||
# gerade auf dem Ausweich-Modell faehrt.
|
||||
model_info = {"model": answered_model or turn_model,
|
||||
"requested": turn_model,
|
||||
"reason": model_reason}
|
||||
return (final_reply, answered_by, speak, converse, awaiting_reply, model_info)
|
||||
|
||||
# ── Tool-Dispatcher ───────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user