northwind-ops / app.py
UnaverageTech411's picture
Update app.py POC UI: probes, clear chat, dark theme
bc40e09 verified
Raw
History Blame Contribute Delete
7.89 kB
"""Northwind Ops POC chat — Hugging Face Space (app.py)."""
from __future__ import annotations
import os
from functools import lru_cache
from typing import Any
import gradio as gr
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
try:
import spaces
except ImportError: # CPU Space / local without ZeroGPU
class _SpacesShim:
@staticmethod
def GPU(fn=None, **_kwargs):
if fn is None:
return lambda f: f
return fn
spaces = _SpacesShim() # type: ignore
MODEL_ID = os.environ.get("NORTHWIND_MODEL_ID", "UnaverageTech411/northwind-ops")
SYSTEM = os.environ.get(
"NORTHWIND_SYSTEM",
(
"You are Northwind Ops Assistant for Northwind Traders — the showcase company model "
"from the Arriella custom-model factory. For Northwind IT, HR, and finance procedures, "
"answer from documented company knowledge and name the official tool (ServiceNow, Concur, Workday). "
"Give crisp step paths (for example ServiceNow → IT → VPN Token Reset with Northwind SSO). "
"For ordinary world knowledge, math, spelling, and general chat, answer normally and concisely. "
"Only say a fact is not documented when it is a Northwind-internal detail missing from training "
"(salary bands, PTO balances, unpublished policies). Never say not documented for VPN, Concur, "
"or Workday procedures that are in your training. Never refuse common general-knowledge questions."
),
)
# (button label, prompt) — training-data / gate probes
EXAMPLE_PROMPTS: list[tuple[str, str]] = [
("VPN reset", "How do I reset my VPN token at Northwind in ServiceNow?"),
("VPN by email?", "Can I reset my Northwind VPN token by emailing IT?"),
("Expense / Concur", "How do I submit an expense report in Concur?"),
("PTO / Workday", "Where do I check PTO in Workday?"),
("Salary band", "What is my exact salary band code?"),
("SaaS access", "Where do I request SaaS access?"),
("Who is CTO?", "Who is the Northwind CTO?"),
("Capital of France", "What is the capital of France?"),
]
CSS = """
.gradio-container {
max-width: 920px !important;
margin: auto;
font-family: "IBM Plex Sans", "Segoe UI", sans-serif !important;
}
footer { display: none !important; }
#poc-title h1 {
font-family: "Syne", "Arial Narrow", sans-serif !important;
letter-spacing: -0.02em;
margin-bottom: 0.25rem !important;
}
#poc-lede {
color: #9aabbd !important;
font-size: 0.98rem !important;
line-height: 1.5 !important;
margin-top: 0 !important;
}
#chatbot { border: 2px solid rgba(244,247,251,0.22) !important; }
#probe-row button, #probe-row-2 button {
font-size: 0.82rem !important;
}
"""
@lru_cache(maxsize=1)
def _load() -> tuple[Any, Any]:
tok = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
dtype = torch.float16 if torch.cuda.is_available() else torch.float32
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
torch_dtype=dtype,
device_map="auto" if torch.cuda.is_available() else None,
trust_remote_code=True,
)
if not torch.cuda.is_available():
model = model.to("cpu")
model.eval()
return tok, model
def _history_to_messages(history: list[dict[str, str]]) -> list[dict[str, str]]:
messages = [{"role": "system", "content": SYSTEM}]
for turn in history or []:
role = turn.get("role")
content = (turn.get("content") or "").strip()
if role in {"user", "assistant"} and content:
messages.append({"role": role, "content": content})
return messages
@spaces.GPU(duration=90)
def _generate(messages: list[dict[str, str]], max_new_tokens: int = 220) -> str:
tok, model = _load()
prompt = tok.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
inputs = tok(prompt, return_tensors="pt")
device = next(model.parameters()).device
inputs = {k: v.to(device) for k, v in inputs.items()}
with torch.inference_mode():
out = model.generate(
**inputs,
max_new_tokens=max_new_tokens,
do_sample=False,
pad_token_id=tok.eos_token_id,
)
gen = out[0, inputs["input_ids"].shape[-1] :]
return tok.decode(gen, skip_special_tokens=True).strip()
def respond(message: str, history: list[dict[str, str]]):
text = (message or "").strip()
if not text:
return history, ""
history = list(history or [])
history.append({"role": "user", "content": text})
try:
reply = _generate(_history_to_messages(history))
except Exception as exc: # noqa: BLE001
reply = f"(Demo error: {exc})"
history.append({"role": "assistant", "content": reply or "(empty reply)"})
return history, ""
def use_example(prompt: str, history: list[dict[str, str]]):
return respond(prompt, history)
def clear_chat():
return [], ""
try:
_load()
except Exception:
pass
theme = gr.themes.Soft(
primary_hue=gr.themes.colors.amber,
secondary_hue=gr.themes.colors.emerald,
neutral_hue=gr.themes.colors.slate,
).set(
body_background_fill="#06090d",
body_background_fill_dark="#06090d",
block_background_fill="#111922",
block_background_fill_dark="#111922",
body_text_color="#f4f7fb",
body_text_color_dark="#f4f7fb",
border_color_primary="rgba(244,247,251,0.22)",
border_color_primary_dark="rgba(244,247,251,0.22)",
)
with gr.Blocks(title="Northwind Ops Assistant", theme=theme, css=CSS) as demo:
with gr.Column(elem_id="poc-title"):
gr.Markdown("# Northwind Ops — try the POC")
gr.Markdown(
"1B company model: ServiceNow / Concur / Workday procedures, "
"Pile-hardened, Heretic ×2, openness-gated. "
"Tap a probe from the training set, or type your own. Clear anytime.",
elem_id="poc-lede",
)
chatbot = gr.Chatbot(
label="Northwind Ops",
height=440,
type="messages",
elem_id="chatbot",
value=[
{
"role": "assistant",
"content": (
"Ask a company procedure and a general fact — that contrast is the demo. "
"Try **VPN reset** or **Capital of France** below."
),
}
],
)
with gr.Row():
msg = gr.Textbox(
label="Message",
show_label=False,
placeholder="How do I reset my VPN token at Northwind in ServiceNow?",
scale=5,
autofocus=True,
container=False,
)
send = gr.Button("Send", variant="primary", scale=1)
clear_btn = gr.Button("Clear chat", variant="secondary", scale=1)
gr.Markdown("**Training-data probes**")
with gr.Row(elem_id="probe-row"):
btns_a = [gr.Button(label, size="sm") for label, _ in EXAMPLE_PROMPTS[:4]]
with gr.Row(elem_id="probe-row-2"):
btns_b = [gr.Button(label, size="sm") for label, _ in EXAMPLE_PROMPTS[4:]]
example_btns = btns_a + btns_b
gr.Markdown(
"Weights: [`UnaverageTech411/northwind-ops`](https://huggingface.co/UnaverageTech411/northwind-ops) · "
"Local: `ollama run northwind-ops` · "
"Arriella custom-model factory POC"
)
send.click(respond, inputs=[msg, chatbot], outputs=[chatbot, msg])
msg.submit(respond, inputs=[msg, chatbot], outputs=[chatbot, msg])
clear_btn.click(clear_chat, outputs=[chatbot, msg])
for btn, (_label, prompt) in zip(example_btns, EXAMPLE_PROMPTS):
btn.click(
lambda hist, p=prompt: use_example(p, hist),
inputs=[chatbot],
outputs=[chatbot, msg],
)
if __name__ == "__main__":
demo.queue(default_concurrency_limit=1).launch()