daniel8919's picture
Add app.py
df7e007 verified
"""
Limbic-Modulated Reasoning Agent β€” ZeroGPU Space
==================================================
A psychology-aware LLM that adjusts its reasoning behavior in real-time
based on a simulated neuro-behavioral state engine.
Architecture:
User message β†’ LimbicEngine (arousal/valence) β†’ modulate generation params
β†’ inject behavioral directive
β†’ active instincts from memory
β†’ LLM generates with limbic context
β†’ self-debug if needed
Sources:
- Limbic formulas: https://github.com/Xover-Official/LIMBIC-system-PACKGE
- Agentic patterns: https://github.com/affaan-m/everything-claude-code
- ZeroGPU: Runs free on Hugging Face Spaces, no credit card needed
Usage:
Set Space hardware to ZeroGPU in the Settings panel.
The @spaces.GPU decorator handles dynamic GPU allocation.
"""
import spaces
import gradio as gr
import torch
import json
import time
from threading import Thread
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
# ─── Local imports ───
from limbic_engine import LimbicEngine, LimbicState
from memory import SessionMemory, ObservationLog, InstinctStore, SelfDebugger
# ══════════════════════════════════════════════════════════════════════
# MODEL LOADING β€” Must happen at module level for ZeroGPU optimization
# ══════════════════════════════════════════════════════════════════════
MODEL_ID = "Qwen/Qwen3-1.7B" # Fits comfortably in ZeroGPU's H200 VRAM
print(f"Loading model: {MODEL_ID}")
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
device_map="auto",
torch_dtype=torch.bfloat16,
)
model.eval()
print(f"Model loaded: {MODEL_ID}")
# ══════════════════════════════════════════════════════════════════════
# SYSTEM PROMPT β€” Psychology-informed reasoning protocol
# ══════════════════════════════════════════════════════════════════════
BASE_SYSTEM_PROMPT = """You are a Psychology-Aware Reasoning Agent. Your cognitive process is modulated by a \
simulated Limbic System that mirrors human neuro-behavioral patterns.
Your reasoning loop works as follows:
1. You receive the user's message along with a LIMBIC STATE readout
2. The limbic state tells you the user's simulated emotional arousal, valence, and which \
affective engine is dominant (FEAR, SEEKING, CARE, or PANIC)
3. You MUST adjust your response style based on the BEHAVIORAL DIRECTIVE provided
4. You have ACTIVE INSTINCTS β€” learned behavioral patterns that should guide your response
Core principles:
- When FEAR is dominant: Be calm, structured, reassuring. Short clear sentences.
- When SEEKING is dominant: Be expansive, creative, offer novel perspectives.
- When CARE is dominant: Match empathy, validate, support prosocial impulses.
- When PANIC is dominant: Acknowledge pain first. Warmth before solutions. Never dismiss.
- Always check for cognitive biases in both the user's statements and your own reasoning.
- For any mention of self-harm or crisis, include 988 Lifeline and Crisis Text Line resources.
You think deeply before responding. Show your reasoning when appropriate."""
# ══════════════════════════════════════════════════════════════════════
# GLOBAL STATE β€” Initialized once, persisted across calls via gr.State
# ══════════════════════════════════════════════════════════════════════
def create_fresh_state():
"""Create a fresh state dict for a new session."""
engine = LimbicEngine()
session = SessionMemory(session_id=str(int(time.time())))
obs_log = ObservationLog()
instincts = InstinctStore()
debugger = SelfDebugger(obs_log)
return {
"engine": engine,
"session": session,
"obs_log": obs_log,
"instincts": instincts,
"debugger": debugger,
}
# ══════════════════════════════════════════════════════════════════════
# GPU INFERENCE β€” The @spaces.GPU decorated function
# ══════════════════════════════════════════════════════════════════════
@spaces.GPU(duration=90)
def generate_on_gpu(
input_ids: torch.Tensor,
temperature: float,
top_p: float,
max_new_tokens: int,
repetition_penalty: float,
) -> str:
"""
Run model inference on GPU. This function gets a dynamically
allocated GPU from ZeroGPU and releases it when done.
Input tensors are moved to device INSIDE this function
(required by ZeroGPU β€” real CUDA only exists inside @spaces.GPU).
"""
input_ids = input_ids.to(model.device)
streamer = TextIteratorStreamer(
tokenizer,
timeout=30.0,
skip_prompt=True,
skip_special_tokens=True,
)
generation_kwargs = {
"input_ids": input_ids,
"streamer": streamer,
"max_new_tokens": max_new_tokens,
"do_sample": True,
"temperature": max(0.01, temperature),
"top_p": top_p,
"repetition_penalty": repetition_penalty,
}
thread = Thread(target=lambda: model.generate(**generation_kwargs))
thread.start()
output_chunks = []
for text in streamer:
output_chunks.append(text)
thread.join()
return "".join(output_chunks)
# ══════════════════════════════════════════════════════════════════════
# MAIN CHAT FUNCTION β€” Orchestrates the full limbic reasoning loop
# ══════════════════════════════════════════════════════════════════════
def chat(
message: str,
history: list,
state: dict,
show_limbic: bool,
enable_thinking: bool,
):
"""
The full Limbic-Modulated Reasoning Loop:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ User │────▢│ LimbicEngine │────▢│ Build System β”‚
β”‚ Message β”‚ β”‚ (arousal, β”‚ β”‚ Prompt with: β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ valence, β”‚ β”‚ β€’ Limbic state β”‚
β”‚ engines) β”‚ β”‚ β€’ Directive β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β€’ Instincts β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Self-Debug │◀────│ LLM Generate β”‚
β”‚ (if needed) β”‚ β”‚ (temp/top_p β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ from limbic) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
"""
if state is None:
state = create_fresh_state()
engine: LimbicEngine = state["engine"]
session: SessionMemory = state["session"]
instincts: InstinctStore = state["instincts"]
obs_log: ObservationLog = state["obs_log"]
# ── Step 1: Process stimulus through Limbic Engine ──
limbic_state = engine.process_stimulus(message)
gen_params = engine.get_generation_params()
# Record in session memory
session.add_turn("user", message, limbic_state.to_dict())
# ── Step 2: Build the full system prompt ──
behavioral_directive = engine.get_behavioral_directive()
instinct_block = instincts.to_prompt_block(limbic_state.to_dict())
trajectory = session.get_emotional_trajectory()
system_prompt_parts = [BASE_SYSTEM_PROMPT]
system_prompt_parts.append(f"\n{limbic_state.to_system_prompt_block()}")
system_prompt_parts.append(f"\n[BEHAVIORAL DIRECTIVE]\n{behavioral_directive}\n[/BEHAVIORAL DIRECTIVE]")
if instinct_block:
system_prompt_parts.append(f"\n{instinct_block}")
if session.turn_count > 1:
system_prompt_parts.append(f"\n{trajectory}")
system_prompt = "\n".join(system_prompt_parts)
# ── Step 3: Build conversation for the model ──
messages = [{"role": "system", "content": system_prompt}]
# Add conversation history (last 10 turns for context management)
for msg in history[-10:]:
messages.append({"role": msg["role"], "content": msg["content"]})
messages.append({"role": "user", "content": message})
# ── Step 4: Tokenize ──
chat_text = tokenizer.apply_chat_template(
messages,
add_generation_prompt=True,
tokenize=False,
enable_thinking=enable_thinking,
)
input_ids = tokenizer(chat_text, return_tensors="pt").input_ids
# ── Step 5: Generate with limbic-modulated parameters ──
max_tokens = int(512 * gen_params.get("max_new_tokens_scale", 1.0))
max_tokens = max(128, min(1024, max_tokens))
response = generate_on_gpu(
input_ids=input_ids,
temperature=limbic_state.temperature,
top_p=limbic_state.top_p,
max_new_tokens=max_tokens,
repetition_penalty=gen_params.get("repetition_penalty", 1.0),
)
# ── Step 6: Record and return ──
session.add_turn("assistant", response, limbic_state.to_dict())
obs_log.record(
task=f"respond to: {message[:50]}",
outcome="success",
limbic_state=limbic_state.to_dict(),
)
# Build the display output
if show_limbic:
limbic_display = format_limbic_dashboard(limbic_state, gen_params, instincts)
else:
limbic_display = ""
return response, state, limbic_display
# ══════════════════════════════════════════════════════════════════════
# LIMBIC DASHBOARD β€” Visual display of the state engine
# ══════════════════════════════════════════════════════════════════════
def format_limbic_dashboard(
state: LimbicState,
gen_params: dict,
instincts: InstinctStore,
) -> str:
"""Format the limbic state as a readable dashboard."""
def bar(value: float, width: int = 20, label: str = "") -> str:
filled = int(value * width)
empty = width - filled
return f"{label:>18s} {'β–ˆ' * filled}{'β–‘' * empty} {value:.2f}"
def valence_bar(value: float, width: int = 20) -> str:
center = width // 2
pos = int((value + 1) / 2 * width)
chars = list("β–‘" * width)
chars[center] = "β”‚"
chars[min(pos, width - 1)] = "β–ˆ"
return f"{'Valence':>18s} {''.join(chars)} {value:+.2f}"
lines = [
"╔══════════════════════════════════════════╗",
"β•‘ 🧠 LIMBIC STATE DASHBOARD β•‘",
"╠══════════════════════════════════════════╣",
"β•‘ CORE AFFECT β•‘",
f"β•‘ {valence_bar(state.valence):40s} β•‘",
f"β•‘ {bar(state.arousal, label='Arousal'):40s} β•‘",
"β•‘ β•‘",
"β•‘ AFFECTIVE ENGINES (Panksepp) β•‘",
f"β•‘ {bar(state.fear, label='πŸ”΄ FEAR'):40s} β•‘",
f"β•‘ {bar(state.seeking, label='🟒 SEEKING'):40s} β•‘",
f"β•‘ {bar(state.care, label='πŸ”΅ CARE'):40s} β•‘",
f"β•‘ {bar(state.panic, label='🟑 PANIC'):40s} β•‘",
f"β•‘ {'Dominant':>18s}: {state.dominant_engine:<21s} β•‘",
"β•‘ β•‘",
"β•‘ HORMONAL STATE β•‘",
f"β•‘ {bar(state.cortisol, label='Cortisol'):40s} β•‘",
f"β•‘ {bar(state.dopamine, label='Dopamine'):40s} β•‘",
f"β•‘ {bar(state.oxytocin, label='Oxytocin'):40s} β•‘",
f"β•‘ {bar(state.serotonin, label='Serotonin'):40s} β•‘",
f"β•‘ {bar(state.adrenaline, label='Adrenaline'):40s} β•‘",
"β•‘ β•‘",
"β•‘ AUTONOMIC / PSYCHOLOGICAL β•‘",
f"β•‘ {bar(state.vagal_tone, label='Vagal Tone'):40s} β•‘",
f"β•‘ {bar(state.ego_coherence, label='Ego Coherence'):40s} β•‘",
f"β•‘ {bar(state.shadow_reservoir, label='Shadow'):40s} β•‘",
"β•‘ β•‘",
"β•‘ LLM GENERATION PARAMS β•‘",
f"β•‘ {'Temperature':>18s}: {state.temperature:<21.3f} β•‘",
f"β•‘ {'Top-p':>18s}: {state.top_p:<21.3f} β•‘",
f"β•‘ {'Rep. Penalty':>18s}: {gen_params.get('repetition_penalty', 1.0):<21.3f} β•‘",
f"β•‘ {'Token Scale':>18s}: {gen_params.get('max_new_tokens_scale', 1.0):<21.3f} β•‘",
"β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•",
]
return "\n".join(lines)
def reset_state():
"""Reset all state for a new conversation."""
return create_fresh_state(), [], ""
# ══════════════════════════════════════════════════════════════════════
# GRADIO INTERFACE
# ══════════════════════════════════════════════════════════════════════
DESCRIPTION = """# 🧠 Limbic-Modulated Reasoning Agent
An LLM whose **reasoning behavior adapts in real-time** based on a simulated neuro-behavioral state engine.
**How it works:**
1. Your message is processed through a **Limbic Engine** (ported from [LIMBIC-system-PACKGE](https://github.com/Xover-Official/LIMBIC-system-PACKGE))
2. The engine computes **arousal, valence**, and activates **affective engines** (Fear, Seeking, Care, Panic)
3. These modulate the LLM's **temperature, top-p**, and inject **behavioral directives** into the system prompt
4. The agent uses **learned instincts** and a **self-debugging protocol** (from [everything-claude-code](https://github.com/affaan-m/everything-claude-code))
**Try it:** Type something emotional ("I'm terrified of failing") vs curious ("Tell me something fascinating about the brain") and watch the Limbic Dashboard change!
πŸ†“ **Runs free on ZeroGPU** β€” no credit card needed.
"""
with gr.Blocks(
title="Limbic Reasoning Agent",
theme=gr.themes.Soft(),
) as demo:
gr.Markdown(DESCRIPTION)
state = gr.State(value=create_fresh_state)
with gr.Row():
with gr.Column(scale=3):
chatbot = gr.Chatbot(
label="πŸ’¬ Conversation",
type="messages",
height=500,
show_copy_button=True,
)
with gr.Row():
msg = gr.Textbox(
placeholder="Type a message... Try expressing different emotions!",
label="Your message",
scale=4,
lines=2,
)
send_btn = gr.Button("Send", variant="primary", scale=1)
with gr.Row():
show_limbic = gr.Checkbox(value=True, label="🧠 Show Limbic Dashboard")
enable_thinking = gr.Checkbox(value=True, label="πŸ’­ Enable Thinking Mode")
clear_btn = gr.Button("πŸ”„ Reset Conversation", variant="secondary")
with gr.Column(scale=2):
limbic_display = gr.Code(
label="🧠 Limbic State Dashboard",
language=None,
lines=35,
interactive=False,
)
# ── Event handlers ──
def user_message(message, history, state, show_limbic, enable_thinking):
"""Process user message through the limbic reasoning loop."""
if not message.strip():
return "", history, state, ""
# Add user message to history
history = history + [{"role": "user", "content": message}]
# Run the limbic reasoning loop
response, state, limbic_info = chat(
message, history, state, show_limbic, enable_thinking,
)
# Add assistant response
history = history + [{"role": "assistant", "content": response}]
return "", history, state, limbic_info
def clear_all():
new_state = create_fresh_state()
return new_state, [], ""
send_btn.click(
fn=user_message,
inputs=[msg, chatbot, state, show_limbic, enable_thinking],
outputs=[msg, chatbot, state, limbic_display],
)
msg.submit(
fn=user_message,
inputs=[msg, chatbot, state, show_limbic, enable_thinking],
outputs=[msg, chatbot, state, limbic_display],
)
clear_btn.click(
fn=clear_all,
inputs=[],
outputs=[state, chatbot, limbic_display],
)
# ── Example prompts ──
gr.Examples(
examples=[
["I'm terrified of losing my job and I can't sleep at night."],
["Tell me something fascinating about how the brain processes emotions!"],
["My best friend is moving away and I feel completely lost."],
["I just got promoted! I'm so excited about what comes next!"],
["I want to help my sister who's going through depression. What should I do?"],
["Everyone keeps telling me I should 'just be positive' and it makes me furious."],
],
inputs=msg,
)
if __name__ == "__main__":
demo.launch()