Typed chat: free hosted models -> local model (llama-server), no keyword rules; busy reply + Retry #7
@ -703,6 +703,31 @@ def _focus_results(store, message, actions):
|
||||
return actions
|
||||
|
||||
|
||||
LOCAL_HISTORY = int(os.environ.get("AGENT_MARKET_LOCAL_LLM_HISTORY", "0")) # small models copy earlier turns; 0 = no history
|
||||
|
||||
|
||||
def _rules_check(message, look, out):
|
||||
"""Look/copy commands are where a ~4B model is least reliable (it copies presets, misplaces keys, or replies without
|
||||
the action). When the deterministic parser recognises the message as a look or text edit, its validated patch
|
||||
wins and the answer is marked rules_checked (the chat header says so)."""
|
||||
try:
|
||||
cur = json.loads(look) if look else {}
|
||||
except ValueError:
|
||||
cur = {}
|
||||
from .theme import parse_look_command
|
||||
r = parse_look_command(message, cur if isinstance(cur, dict) else {})
|
||||
if not r:
|
||||
return out
|
||||
reply, payload = r
|
||||
if payload is None: # a text edit it couldn't place: keep the model's answer if it did something
|
||||
if not out["actions"]:
|
||||
out["reply"], out["rules_checked"] = reply, True
|
||||
return out
|
||||
out["actions"] = [{"type": "set_theme", "payload": payload}]
|
||||
out["reply"], out["rules_checked"] = reply, True
|
||||
return out
|
||||
|
||||
|
||||
def llm_agent(store, message, history, cfg, look=""):
|
||||
"""Hosted models in order (skipping ones cooling down after a 429, and all of them while the daily free quota is
|
||||
used up), then the local backup model with its own timeout. Raises if everything fails (keyword rules answer then)."""
|
||||
@ -756,7 +781,8 @@ def llm_agent(store, message, history, cfg, look=""):
|
||||
errors.append({"model": local["model"], "error": "local_busy", "ms": 0})
|
||||
else:
|
||||
try:
|
||||
fut = _pool.submit(_llm_once, store, local, local["model"], message, history, look, cap)
|
||||
hist = (history or [])[-LOCAL_HISTORY:] if LOCAL_HISTORY > 0 else []
|
||||
fut = _pool.submit(_llm_once, store, local, local["model"], message, hist, look, cap)
|
||||
except Exception:
|
||||
_local_slots.release()
|
||||
raise
|
||||
@ -767,6 +793,8 @@ def llm_agent(store, message, history, cfg, look=""):
|
||||
except concurrent.futures.TimeoutError:
|
||||
raise LLMError("timeout", f">{cap:.0f}s")
|
||||
out["actions"] = _focus_results(store, message, out["actions"])
|
||||
if not message.startswith("UI_EVENT "):
|
||||
out = _rules_check(message, look, out)
|
||||
out.update(agent="llm", provider="local", local=True, model=local["model"],
|
||||
latency_ms=int((time.time() - t0) * 1000), fallbacks=errors)
|
||||
LAST.update(model=local["model"], at=time.time(), error=None, local=True)
|
||||
|
||||
@ -85,3 +85,7 @@ chat 18810 | python3 -c '
|
||||
import json,sys; d=json.load(sys.stdin); assert d["agent"]=="llm" and d.get("local") and d["fallbacks"]==[], d
|
||||
print("PASS local model works with no hosted key configured")'
|
||||
curl -s localhost:18810/api/chat -H 'Content-Type: application/json' -d '{"message":"show me bikes"}' | grep -q test-dummy-key && { echo "FAIL key"; exit 1; } || true
|
||||
curl -s localhost:18810/api/chat -H 'Content-Type: application/json' -d '{"message":"hide the bullet points","theme":{}}' | python3 -c '
|
||||
import json,sys; d=json.load(sys.stdin); a=d["actions"]
|
||||
assert d["agent"]=="llm" and d.get("local") and d.get("rules_checked") and [x["type"] for x in a]==["set_theme"] and a[0]["payload"]["patch"]["sections"]["hidden"]==["bullets"], d
|
||||
print("PASS local model look command checked by the rules (validated patch wins, marked rules_checked)")'
|
||||
|
||||
@ -50,13 +50,13 @@ export default function App() {
|
||||
}, []);
|
||||
const say = useCallback((role: Msg["role"], text: string) => { const id = ++seq; setMsgs((m) => [...m, { id, role, text }]); return id; }, []);
|
||||
const unsay = useCallback((id: number) => setMsgs((m) => m.filter((x) => x.id !== id)), []);
|
||||
const setModel = useCallback((model?: string, ms?: number, local = false) => {
|
||||
const setModel = useCallback((model?: string, ms?: number, local = false, checked = false) => {
|
||||
const short = (model || "").replace(/:free$/, "");
|
||||
const secs = ms ? " · " + (ms / 1000).toFixed(1) + "s" : "";
|
||||
if (local) {
|
||||
// the local backup model running on this server (used when the free hosted models are unavailable)
|
||||
setBadge({ text: `Local model (${short})`, title: `${model} running on this server (local backup; slower than the hosted models)` });
|
||||
setChatMode(`Local model · ${short}${secs}`);
|
||||
setChatMode(`Local model · ${short}${secs}${checked ? " · look checked by rules" : ""}`);
|
||||
return;
|
||||
}
|
||||
setBadge({ text: "AI: " + short, title: (model || "") + " via OpenRouter (free tier)" });
|
||||
@ -98,7 +98,7 @@ export default function App() {
|
||||
if (event) history.current.push({ role: "user", content: "UI_EVENT " + JSON.stringify(event) });
|
||||
history.current.push({ role: "assistant", content: out.reply || "" });
|
||||
say("bot", out.reply || "(no reply)");
|
||||
if (out.agent === "llm") setModel(out.model, out.latency_ms, !!out.local);
|
||||
if (out.agent === "llm") setModel(out.model, out.latency_ms, !!out.local, !!out.rules_checked);
|
||||
else if (out.agent === "fallback") {
|
||||
setChatMode("built-in agent answered (AI model unavailable)");
|
||||
setBadge({ text: "agent: built-in (AI unavailable)", title: "The hosted model is rate-limited or out of free quota" });
|
||||
|
||||
@ -16,7 +16,7 @@ export interface Order {
|
||||
export interface UIAction { type: string; payload?: any }
|
||||
export interface ChatReply {
|
||||
reply?: string; actions?: UIAction[]; agent?: "llm" | "fallback" | "ui-event" | "rate-limit"; model?: string; latency_ms?: number;
|
||||
provider?: string; local?: boolean;
|
||||
provider?: string; local?: boolean; rules_checked?: boolean;
|
||||
}
|
||||
export interface ChatEvent { type: string; [k: string]: unknown }
|
||||
export interface Status {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user