c2c-demo / app.py
raqibcodes's picture
Initial C2C Gradio app (app.py, requirements, README)
e16e78a verified
Raw
History Blame Contribute Delete
4.71 kB
"""
Gradio Space: Chaos-to-Clarity (C2C) — messy text → YAML via Gemma 4 E4B + LoRA from the Hub.
Env (optional):
C2C_BASE_MODEL default google/gemma-4-E4B-it
C2C_ADAPTER_REPO default raqibcodes/c2c-checkpoints
C2C_ADAPTER_SUBFOLDER e.g. last-checkpoint (empty = repo root)
HF_TOKEN Space secret if adapter or base is private / gated
"""
from __future__ import annotations
import os
import threading
import gradio as gr
import torch
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
if os.environ.get("HF_TOKEN"):
from huggingface_hub import login
login(token=os.environ["HF_TOKEN"], add_to_git_credential=False)
MODEL_NAME = os.environ.get("C2C_BASE_MODEL", "google/gemma-4-E4B-it").strip()
HUB_REPO = os.environ.get("C2C_ADAPTER_REPO", "raqibcodes/c2c-checkpoints").strip()
_sub = os.environ.get("C2C_ADAPTER_SUBFOLDER", "").strip()
HUB_SUBFOLDER = _sub or None
C2C_INSTRUCTION = """You are a structured extractor for the Chaos-to-Clarity (C2C) task.
Output rules (mandatory):
- Respond with YAML only. No markdown fences, no prose, no bullet options, no explanations.
- Keys: is_act (0 or 1), intent (remind|schedule|log|notify), tasks (list).
- Each task must have: act, who, due, pri (H|M|L).
- If is_act is 0, tasks must be an empty list.
The user message after the '---' separator is messy human text to extract from."""
_tokenizer = None
_model = None
_load_lock = threading.Lock()
def c2c_user_content(raw_user_text: str) -> str:
return f"{C2C_INSTRUCTION}\n\n---\n\n{raw_user_text.strip()}"
def strip_thinking(raw: str) -> str:
raw = raw.strip()
if "<|channel|>thought" in raw:
end = raw.rfind("<|channel|>")
if end != -1:
raw = raw[end + len("<|channel|>") :].strip()
if raw.startswith("```"):
raw = raw[3:]
if raw.lower().startswith("yaml"):
raw = raw[4:].lstrip()
fence = raw.rfind("```")
if fence != -1:
raw = raw[:fence].strip()
return raw
def _load_model():
global _tokenizer, _model
if _model is not None:
return
if not torch.cuda.is_available():
raise RuntimeError(
"CUDA is not available. In Space Settings → Hardware, enable a GPU (e.g. T4)."
)
_tok_kw = {}
if HUB_SUBFOLDER:
_tok_kw["subfolder"] = HUB_SUBFOLDER
tokenizer = AutoTokenizer.from_pretrained(HUB_REPO, **_tok_kw)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
base = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
quantization_config=bnb_config,
device_map={"": 0},
attn_implementation="eager",
low_cpu_mem_usage=True,
)
if HUB_SUBFOLDER:
model = PeftModel.from_pretrained(base, HUB_REPO, subfolder=HUB_SUBFOLDER)
else:
model = PeftModel.from_pretrained(base, HUB_REPO)
model.eval()
_tokenizer, _model = tokenizer, model
@torch.inference_mode()
def extract(messy_text: str, max_new_tokens: int) -> str:
text = (messy_text or "").strip()
if not text:
return "Paste some messy text first."
with _load_lock:
_load_model()
messages = [{"role": "user", "content": c2c_user_content(text)}]
prompt = _tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
inputs = _tokenizer(prompt, return_tensors="pt").to(_model.device)
out = _model.generate(
**inputs,
max_new_tokens=int(max_new_tokens),
do_sample=False,
pad_token_id=_tokenizer.pad_token_id,
)
gen = _tokenizer.decode(
out[0][inputs["input_ids"].shape[1] :], skip_special_tokens=False
)
return strip_thinking(gen)
with gr.Blocks(title="C2C Extractor") as demo:
gr.Markdown(
"### Chaos-to-Clarity (C2C)\n"
"Fine-tuned **Gemma 4 E4B-it** + LoRA: messy message → **YAML** tasks.\n\n"
"_First click may take a minute while the model loads._"
)
messy = gr.Textbox(
label="Messy message",
lines=5,
placeholder="e.g. hey can u remind me to email sarah about the invoice by friday and grab oat milk",
)
max_tok = gr.Slider(64, 512, value=256, step=32, label="Max new tokens")
out = gr.Textbox(label="C2C YAML", lines=14)
go = gr.Button("Extract", variant="primary")
go.click(fn=extract, inputs=[messy, max_tok], outputs=out)