Local model: no chat history by default; rules check for look/text edits (marked in the chat header)
This commit is contained in:
parent
549cd46778
commit
e4552f5649
@ -703,6 +703,31 @@ def _focus_results(store, message, actions):
|
|||||||
return 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=""):
|
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
|
"""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)."""
|
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})
|
errors.append({"model": local["model"], "error": "local_busy", "ms": 0})
|
||||||
else:
|
else:
|
||||||
try:
|
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:
|
except Exception:
|
||||||
_local_slots.release()
|
_local_slots.release()
|
||||||
raise
|
raise
|
||||||
@ -767,6 +793,8 @@ def llm_agent(store, message, history, cfg, look=""):
|
|||||||
except concurrent.futures.TimeoutError:
|
except concurrent.futures.TimeoutError:
|
||||||
raise LLMError("timeout", f">{cap:.0f}s")
|
raise LLMError("timeout", f">{cap:.0f}s")
|
||||||
out["actions"] = _focus_results(store, message, out["actions"])
|
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"],
|
out.update(agent="llm", provider="local", local=True, model=local["model"],
|
||||||
latency_ms=int((time.time() - t0) * 1000), fallbacks=errors)
|
latency_ms=int((time.time() - t0) * 1000), fallbacks=errors)
|
||||||
LAST.update(model=local["model"], at=time.time(), error=None, local=True)
|
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
|
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")'
|
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":"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 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 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 short = (model || "").replace(/:free$/, "");
|
||||||
const secs = ms ? " · " + (ms / 1000).toFixed(1) + "s" : "";
|
const secs = ms ? " · " + (ms / 1000).toFixed(1) + "s" : "";
|
||||||
if (local) {
|
if (local) {
|
||||||
// the local backup model running on this server (used when the free hosted models are unavailable)
|
// 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)` });
|
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;
|
return;
|
||||||
}
|
}
|
||||||
setBadge({ text: "AI: " + short, title: (model || "") + " via OpenRouter (free tier)" });
|
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) });
|
if (event) history.current.push({ role: "user", content: "UI_EVENT " + JSON.stringify(event) });
|
||||||
history.current.push({ role: "assistant", content: out.reply || "" });
|
history.current.push({ role: "assistant", content: out.reply || "" });
|
||||||
say("bot", out.reply || "(no 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") {
|
else if (out.agent === "fallback") {
|
||||||
setChatMode("built-in agent answered (AI model unavailable)");
|
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" });
|
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 UIAction { type: string; payload?: any }
|
||||||
export interface ChatReply {
|
export interface ChatReply {
|
||||||
reply?: string; actions?: UIAction[]; agent?: "llm" | "fallback" | "ui-event" | "rate-limit"; model?: string; latency_ms?: number;
|
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 ChatEvent { type: string; [k: string]: unknown }
|
||||||
export interface Status {
|
export interface Status {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user