Spaces:
Sleeping
Sleeping
Commit ·
d641c27
1
Parent(s): 2cd43d3
fix: hide inert provider toggle on public demo; report real model name in telemetry
Browse filesOn the public Space (PUBLIC_DEMO=1) the provider is forced to Anthropic, so the
Anthropic/Ollama toggle was inert — now hidden on the public demo, kept for local
dev. Also fixes a telemetry label bug: the run footer always showed the Claude
model id even on Ollama runs; triage() now reads the real model's .model_name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- api.py +3 -0
- frontend/src/components/Header.tsx +20 -16
- frontend/src/components/TriagePanel.tsx +4 -2
- frontend/src/types.ts +3 -0
- scripts/test_api.py +1 -0
- src/agent.py +10 -3
api.py
CHANGED
|
@@ -95,6 +95,9 @@ def stats() -> dict:
|
|
| 95 |
"precedent_cases": count(CONFIG.cases_collection),
|
| 96 |
"provider": CONFIG.llm_provider,
|
| 97 |
"providers": list(_PROVIDERS),
|
|
|
|
|
|
|
|
|
|
| 98 |
}
|
| 99 |
|
| 100 |
|
|
|
|
| 95 |
"precedent_cases": count(CONFIG.cases_collection),
|
| 96 |
"provider": CONFIG.llm_provider,
|
| 97 |
"providers": list(_PROVIDERS),
|
| 98 |
+
# On the public demo the provider is forced to Anthropic (see run_triage), so the UI hides
|
| 99 |
+
# the otherwise-inert provider toggle.
|
| 100 |
+
"public_demo": CONFIG.public_demo,
|
| 101 |
}
|
| 102 |
|
| 103 |
|
frontend/src/components/Header.tsx
CHANGED
|
@@ -53,22 +53,26 @@ export function Header({
|
|
| 53 |
</div>
|
| 54 |
)}
|
| 55 |
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
<button
|
| 74 |
onClick={onHelp}
|
|
|
|
| 53 |
</div>
|
| 54 |
)}
|
| 55 |
|
| 56 |
+
{/* Provider toggle — hidden on the public demo, where the provider is forced to Anthropic and
|
| 57 |
+
the toggle would be inert/misleading. It stays for local dev (Anthropic vs free Ollama). */}
|
| 58 |
+
{!stats?.public_demo && (
|
| 59 |
+
<div
|
| 60 |
+
className="flex items-center rounded-lg border border-line p-0.5 bg-panel"
|
| 61 |
+
title="Anthropic = Claude (demo). Ollama = free local model — watch the policy gate hold it to the same floor."
|
| 62 |
+
>
|
| 63 |
+
{["anthropic", "ollama"].map((p) => (
|
| 64 |
+
<button
|
| 65 |
+
key={p}
|
| 66 |
+
onClick={() => onProvider(p)}
|
| 67 |
+
className={`px-3 py-1 text-xs rounded-md capitalize transition ${
|
| 68 |
+
provider === p ? "bg-surface shadow-sm text-accent font-semibold" : "text-muted hover:text-heading"
|
| 69 |
+
}`}
|
| 70 |
+
>
|
| 71 |
+
{p}
|
| 72 |
+
</button>
|
| 73 |
+
))}
|
| 74 |
+
</div>
|
| 75 |
+
)}
|
| 76 |
|
| 77 |
<button
|
| 78 |
onClick={onHelp}
|
frontend/src/components/TriagePanel.tsx
CHANGED
|
@@ -69,8 +69,10 @@ export function TriagePanel({
|
|
| 69 |
<Scale size={14} className="mt-0.5 shrink-0 text-gold-deep" />
|
| 70 |
<span>
|
| 71 |
The model only recommends. A deterministic <b>policy gate</b> then enforces the rules in
|
| 72 |
-
code and can only route a case to a human — never auto-approve/reject.
|
| 73 |
-
|
|
|
|
|
|
|
| 74 |
</span>
|
| 75 |
</div>
|
| 76 |
</div>
|
|
|
|
| 69 |
<Scale size={14} className="mt-0.5 shrink-0 text-gold-deep" />
|
| 70 |
<span>
|
| 71 |
The model only recommends. A deterministic <b>policy gate</b> then enforces the rules in
|
| 72 |
+
code and can only route a case to a human — never auto-approve/reject.
|
| 73 |
+
{provider === "ollama" && (
|
| 74 |
+
<> On <b>ollama</b> (a weak local model) you'll see the gate fire more often.</>
|
| 75 |
+
)}
|
| 76 |
</span>
|
| 77 |
</div>
|
| 78 |
</div>
|
frontend/src/types.ts
CHANGED
|
@@ -80,6 +80,9 @@ export interface Stats {
|
|
| 80 |
precedent_cases: number;
|
| 81 |
provider: string;
|
| 82 |
providers: string[];
|
|
|
|
|
|
|
|
|
|
| 83 |
}
|
| 84 |
|
| 85 |
export interface PolicyRuleRef {
|
|
|
|
| 80 |
precedent_cases: number;
|
| 81 |
provider: string;
|
| 82 |
providers: string[];
|
| 83 |
+
// True on the deployed public demo, where the provider is forced to Anthropic — the UI hides the
|
| 84 |
+
// (then-inert) provider toggle.
|
| 85 |
+
public_demo: boolean;
|
| 86 |
}
|
| 87 |
|
| 88 |
export interface PolicyRuleRef {
|
scripts/test_api.py
CHANGED
|
@@ -44,6 +44,7 @@ def t_stats():
|
|
| 44 |
body = r.json()
|
| 45 |
assert "policy_rules" in body and "precedent_cases" in body
|
| 46 |
assert body["provider"] in body["providers"]
|
|
|
|
| 47 |
|
| 48 |
|
| 49 |
def t_list_campaigns():
|
|
|
|
| 44 |
body = r.json()
|
| 45 |
assert "policy_rules" in body and "precedent_cases" in body
|
| 46 |
assert body["provider"] in body["providers"]
|
| 47 |
+
assert isinstance(body["public_demo"], bool) # drives the UI's provider-toggle visibility
|
| 48 |
|
| 49 |
|
| 50 |
def t_list_campaigns():
|
src/agent.py
CHANGED
|
@@ -166,9 +166,16 @@ def triage(campaign: Campaign, model: object | None = None) -> GatedDecision:
|
|
| 166 |
gated = apply_policy_gate(campaign, result.output)
|
| 167 |
gated.tokens_in, gated.tokens_out = _usage_tokens(result)
|
| 168 |
gated.latency_ms = latency_ms
|
| 169 |
-
#
|
| 170 |
-
#
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
gated.model_name = name.split(":", 1)[1] if isinstance(name, str) and ":" in name else name
|
| 173 |
return gated
|
| 174 |
|
|
|
|
| 166 |
gated = apply_policy_gate(campaign, result.output)
|
| 167 |
gated.tokens_in, gated.tokens_out = _usage_tokens(result)
|
| 168 |
gated.latency_ms = latency_ms
|
| 169 |
+
# Report the model that actually ran. A string model carries its own name (e.g.
|
| 170 |
+
# "anthropic:claude-..."); None is the default Anthropic path; a model *object* is the Ollama path
|
| 171 |
+
# (OllamaModel.model_name, e.g. "qwen2.5:7b-instruct") — so the footer reflects the real provider,
|
| 172 |
+
# not always Anthropic. Strip any "provider:" prefix for display.
|
| 173 |
+
if isinstance(model, str):
|
| 174 |
+
name = model
|
| 175 |
+
elif model is None:
|
| 176 |
+
name = CONFIG.anthropic_model
|
| 177 |
+
else:
|
| 178 |
+
name = getattr(model, "model_name", None) or CONFIG.ollama_model
|
| 179 |
gated.model_name = name.split(":", 1)[1] if isinstance(name, str) and ":" in name else name
|
| 180 |
return gated
|
| 181 |
|