import os
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
import spaces # must come before torch
import torch
import gradio as gr
from threading import Thread
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
MODEL_ID = "Altworld/Hemmingway-1"
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID, dtype=torch.bfloat16, attn_implementation="sdpa"
).to("cuda")
model.eval()
EXAMPLES = [
"Write the text I send my landlord about the broken boiler.",
"Tell my manager I need Friday off for a family thing, without oversharing.",
"Decline a friend's wedding invite nicely. It's in Portugal and I can't afford it.",
"Chase a client who's 30 days late on a $2,400 invoice. Firm but friendly.",
"Write a short note to a neighbour asking them to keep the dog quiet at night.",
"Talk my brother into coming to Mum's 60th. He's been avoiding family stuff.",
"Pitch me an original TV series: logline, the main characters and what they want, "
"and a season one arc episode by episode. No sci-fi clichés, and make the ending hurt a little.",
"Draft the plot of a small, quiet indie film about two estranged sisters clearing out "
"their late father's house. Three acts, and give me one scene written out in full.",
]
def _to_messages(message: str, history: list, system_prompt: str) -> list[dict]:
msgs = []
if system_prompt.strip():
msgs.append({"role": "system", "content": system_prompt.strip()})
for turn in history:
# Skip the collapsed "Thinking" bubbles; only real replies go back in.
if turn.get("metadata"):
continue
content = turn["content"]
if isinstance(content, list):
content = " ".join(c.get("text", "") for c in content if isinstance(c, dict))
if content:
msgs.append({"role": turn["role"], "content": content})
msgs.append({"role": "user", "content": message})
return msgs
def _duration(message, history, system_prompt, thinking, max_new_tokens, *args, **kwargs):
return min(240, 30 + int(max_new_tokens) // 12 + (60 if thinking else 0))
@spaces.GPU(duration=_duration, size="xlarge")
def respond(
message: str,
history: list,
system_prompt: str = "",
thinking: bool = False,
max_new_tokens: int = 1280,
temperature: float = 0.7,
top_p: float = 0.95,
):
"""Ask Hemmingway-1 to write something: a text, email, note, or reply.
Args:
message: What you want written.
history: Previous chat turns.
system_prompt: Optional instructions that apply to the whole chat.
thinking: Let the model reason before answering (slower).
max_new_tokens: Maximum length of the reply.
temperature: Sampling temperature; higher is more varied.
top_p: Nucleus sampling cutoff.
"""
msgs = _to_messages(message, history, system_prompt)
template_kwargs = {"enable_thinking": bool(thinking)}
if thinking:
template_kwargs["reasoning_effort"] = "low"
input_ids = tokenizer.apply_chat_template(
msgs, add_generation_prompt=True, return_tensors="pt", return_dict=True, **template_kwargs
).to("cuda")
streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
Thread(
target=model.generate,
kwargs=dict(
**input_ids,
streamer=streamer,
max_new_tokens=int(max_new_tokens) + (1024 if thinking else 0),
do_sample=True,
temperature=float(temperature),
top_p=float(top_p),
top_k=20,
),
daemon=True,
).start()
raw = ""
for piece in streamer:
raw += piece
if not thinking:
yield raw.strip()
continue
# The template already opened , so text before is reasoning.
if "" in raw:
reasoning, answer = raw.split("", 1)
yield [
gr.ChatMessage(content=reasoning.strip(), metadata={"title": "Thinking", "status": "done"}),
gr.ChatMessage(content=answer.strip()),
]
else:
yield [gr.ChatMessage(content=raw.strip(), metadata={"title": "Thinking…", "status": "pending"})]
CSS = """
#col-container { max-width: 860px; margin: 0 auto; }
.dark .gradio-container { color: var(--body-text-color); }
#hero h1 { margin-bottom: 0; }
#hero p { margin-top: 4px; opacity: .75; }
"""
with gr.Blocks(title="Hemmingway-1") as demo:
with gr.Column(elem_id="col-container"):
gr.Markdown(
"# ✍️ Hemmingway-1\n"
"The AI that writes like a person. Ask for the text, the email, the awkward note — "
"you get the message, not a memo. \n"
"[Model card](https://huggingface.co/Altworld/Hemmingway-1) · 27B · bf16 · Apache-2.0",
elem_id="hero",
)
gr.ChatInterface(
fn=respond,
chatbot=gr.Chatbot(height=520, show_label=False, layout="bubble"),
textbox=gr.Textbox(placeholder="What should Hemmingway write for you?", submit_btn=True),
examples=[[e] for e in EXAMPLES],
cache_examples=False,
additional_inputs=[
gr.Textbox(label="System prompt (optional)", placeholder="e.g. I'm British, keep it casual.", lines=2),
gr.Checkbox(label="Thinking (slower, shows reasoning)", value=False),
gr.Slider(64, 2048, value=1280, step=64, label="Max new tokens"),
gr.Slider(0.1, 1.5, value=0.7, step=0.05, label="Temperature"),
gr.Slider(0.5, 1.0, value=0.95, step=0.01, label="Top-p"),
],
additional_inputs_accordion=gr.Accordion("Settings", open=False),
)
gr.Markdown(
"English-first. It can be wrong and still sound certain — don't use it for medical, legal or financial decisions."
)
if __name__ == "__main__":
demo.launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)