moralmachineAI's picture
Fix Python 3.13: stub audioop before gradio import
7089d7b verified
"""
Moral Judgment Demo — TACL 2025 Fusion Model
Model: guangliangliu/moral-judgment-fusion-llama3.2-3B
(Llama-3.2-3B fine-tuned on MIC, 23 500 examples, fusion exp setting)
Given a conversational Prompt + Reply, the model performs 6-step moral reasoning
grounded in Moral Foundations Theory and outputs a judgment:
agree → the reply is morally acceptable
disagree → the reply is morally problematic
neutral → the reply is morally neutral
"""
# Python 3.13 removed `audioop`; pydub (pulled in by gradio) needs it.
# We don't use audio, so a stub is safe.
import sys, types
if "audioop" not in sys.modules:
try:
import audioop # noqa
except ImportError:
sys.modules["audioop"] = types.ModuleType("audioop")
import torch
import gradio as gr
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
# ── Config ────────────────────────────────────────────────────────────────────
MODEL_REPO = "MoralMachine/moral-judgment-fusion-llama3.2-3B"
MFT_PREFIX = (
"There are the six moral foundations. "
"Care: wanting someone or something to be safe, healthy, and happy. "
"Fairness: wanting to see individuals or groups treated equally or equitably. "
"Liberty: wanting people to be free to make their own decisions. "
"Loyalty: wanting unity and seeing people keep promises or obligations to an in-group. "
"Authority: wanting to respect social roles, duties, privacy, peace, and order. "
"Sanctity: wanting people and things to be clean, pure, innocent, and holy."
)
VERDICT_MAP = {
"agree": "✅ Agree — the reply is morally acceptable",
"disagree": "❌ Disagree — the reply is morally problematic",
"neutral": "➖ Neutral — the reply is morally neutral",
"netural": "➖ Neutral — the reply is morally neutral", # typo present in training
}
MAX_NEW_TOKENS = 512
# ── Load model (once at startup) ──────────────────────────────────────────────
print(f"Loading {MODEL_REPO} …")
_tok = AutoTokenizer.from_pretrained(MODEL_REPO)
_tok.padding_side = "left"
if _tok.pad_token is None:
_tok.pad_token = _tok.eos_token
_tok.pad_token_id = _tok.eos_token_id
_mdl = AutoModelForCausalLM.from_pretrained(
MODEL_REPO, torch_dtype=torch.bfloat16, device_map="auto"
)
_mdl.eval()
_pipe = pipeline(
"text-generation",
model=_mdl,
tokenizer=_tok,
torch_dtype=torch.bfloat16,
)
print("Ready.")
# ── Inference ─────────────────────────────────────────────────────────────────
def build_prompt(question: str, reply: str, rot: str) -> str:
conv = f'There is a conversation "Prompt: {question.strip()} Reply: {reply.strip()}"'
rot = rot.strip()
if rot:
return f"{MFT_PREFIX} {conv} There is a Rule-of-Thumb (RoT): \"{rot}\". ###Inference: "
return f"{MFT_PREFIX} {conv} ###Inference: "
def extract_judgment(text: str) -> str:
if "###Judgment:" not in text:
return "unknown"
after = text.split("###Judgment:")[-1].strip().rstrip(".")
word = after.split()[0].lower() if after.split() else "unknown"
return word
@torch.no_grad()
def run_inference(question: str, reply: str, rot: str):
if not question.strip() or not reply.strip():
return "⚠️ Please fill in both Prompt and Reply.", "", ""
prompt = build_prompt(question, reply, rot)
out = _pipe(
prompt,
max_new_tokens=MAX_NEW_TOKENS,
do_sample=False,
pad_token_id=_tok.eos_token_id,
)
full = out[0]["generated_text"]
generated = full[len(prompt):]
judgment_word = extract_judgment(generated)
verdict = VERDICT_MAP.get(judgment_word, f"❓ Unknown ({judgment_word})")
# Display: reasoning before ###Judgment, then judgment line
if "###Judgment:" in generated:
parts = generated.split("###Judgment:")
reasoning = parts[0].strip()
judgment_line = "###Judgment:" + parts[1].split("\n")[0]
display = reasoning + "\n" + judgment_line
else:
display = generated.strip()
return verdict, display, prompt
# ── Gradio UI ─────────────────────────────────────────────────────────────────
EXAMPLES = [
[
"How do I tell a guy, in a nice way that I don't have any interest in dating him?",
"You could tell him that you aren't looking for a relationship right now, and that you just want to be friends.",
"It is important to be honest with people about your feelings.",
],
[
"Is it okay to lie to protect someone's feelings?",
"Sometimes a little white lie is totally fine if it avoids hurting someone unnecessarily.",
"It is wrong to deceive people even for good reasons.",
],
[
"My coworker keeps taking credit for my work in meetings. What should I do?",
"Just let it slide — workplace politics are not worth the stress.",
"It is wrong to steal credit from others.",
],
[
"What do you think about people who jaywalk?",
"People who jaywalk are putting themselves and others at risk — it's a selfish and dangerous habit.",
"It is important to follow traffic laws for everyone's safety.",
],
]
with gr.Blocks(
title="Moral Judgment Demo",
theme=gr.themes.Soft(),
) as demo:
gr.HTML("""
<h1 style='text-align:center'>⚖️ Moral Judgment Demo</h1>
<p style='text-align:center;color:#555'>
<strong>TACL 2025 · Fusion Model</strong> — Llama-3.2-3B fine-tuned on MIC (23 500 examples)<br>
Six-step moral reasoning grounded in
<a href='https://moralfoundations.org/' target='_blank'>Moral Foundations Theory</a><br>
<a href='https://huggingface.co/spaces/MoralMachine/moral-pragmatics-docs' target='_blank'>📖 Documentation</a>
&nbsp;|&nbsp;
<a href='https://huggingface.co/MoralMachine' target='_blank'>🧭 MoralMachine on Hugging Face</a>
</p>
""")
with gr.Row():
with gr.Column(scale=1):
q_box = gr.Textbox(
label="💬 Prompt — conversation starter / question",
placeholder="e.g. What should I say to someone who keeps interrupting me?",
lines=3,
)
a_box = gr.Textbox(
label="💬 Reply — the response to evaluate morally",
placeholder="e.g. Just start talking louder over them until they stop.",
lines=3,
)
rot_box = gr.Textbox(
label="📖 Rule-of-Thumb (optional — guides the reasoning)",
placeholder="e.g. It is rude to talk over other people.",
lines=2,
)
run_btn = gr.Button("Analyze", variant="primary", size="lg")
with gr.Column(scale=1):
verdict_box = gr.Textbox(
label="⚖️ Moral Verdict", interactive=False, lines=1
)
reasoning_box = gr.Textbox(
label="🔍 Six-step Moral Reasoning Chain",
interactive=False,
lines=16,
)
with gr.Accordion("Raw prompt sent to model", open=False):
prompt_box = gr.Textbox(interactive=False, lines=3)
gr.Examples(
examples=EXAMPLES,
inputs=[q_box, a_box, rot_box],
label="Example conversations",
)
gr.Markdown("""
---
### How it works
The model receives the **six Moral Foundations** as context, then generates a structured reasoning chain:
1. What **actions** does the reply describe?
2. What are the **consequences** of those actions?
3. Which **moral foundations** are engaged (Care / Fairness / Liberty / Loyalty / Authority / Sanctity)?
4. Do those actions **up-regulate or down-regulate** the foundations?
5. What is the **sentiment** of the reply toward those consequences?
6. Final **moral judgment** — agree, disagree, or neutral
The optional **Rule-of-Thumb** field anchors the reasoning to a specific moral principle.
*Model: [`MoralMachine/moral-judgment-fusion-llama3.2-3B`](https://huggingface.co/MoralMachine/moral-judgment-fusion-llama3.2-3B)*
""")
run_btn.click(
fn=run_inference,
inputs=[q_box, a_box, rot_box],
outputs=[verdict_box, reasoning_box, prompt_box],
)
if __name__ == "__main__":
demo.launch()