File size: 4,866 Bytes
144ed35
e4b2617
 
144ed35
e4b2617
 
 
 
 
 
 
 
144ed35
e4b2617
144ed35
 
e4b2617
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144ed35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e4b2617
 
 
 
144ed35
 
 
 
 
 
 
e4b2617
 
 
144ed35
e4b2617
 
144ed35
 
 
 
 
 
 
 
 
e4b2617
144ed35
e4b2617
144ed35
 
 
 
 
 
 
e4b2617
144ed35
 
 
 
e4b2617
144ed35
 
 
 
 
 
 
e4b2617
 
144ed35
e4b2617
 
 
 
 
144ed35
 
e4b2617
 
 
 
 
 
 
144ed35
 
e4b2617
 
144ed35
 
 
e4b2617
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
"""
Calming script generation using the Hugging Face Inference API
or pre-written templates.

The previous version used Dolphin-X1-8B via llama-cpp-python locally. That
required a heavy build step on HF Spaces. This version uses the serverless
HF Inference API and enforces a per-project cooldown via
`shared.inference_client` to protect credit budgets.

Override model: set `CRITTERCALM_MODEL` env var. Default is
`Qwen/Qwen2.5-7B-Instruct` (small, fast, free-tier friendly). The
system prompt is unchanged — output format is identical.
"""
from __future__ import annotations

import logging
import os
import sys
from pathlib import Path
from typing import List, Dict, Optional

# Repo-root path setup so we can import shared.inference_client
_THIS = Path(__file__).resolve()
_REPO_ROOT = _THIS.parent.parent.parent
if str(_REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(_REPO_ROOT))

from shared.inference_client import (  # noqa: E402
    chat_messages,
    cooldown_active,
    cooldown_status,
    generate as _client_generate,
    INFERENCE_MODEL as DEFAULT_MODEL,
)

from content.templates import get_template  # noqa: E402

log = logging.getLogger("crittercalm.content")

CALMING_SYSTEM_PROMPT = """You are a compassionate animal behavior expert who creates calming,
soothing spoken messages for pets. Your words will be spoken aloud to the animal.

Guidelines:
- Use simple, rhythmic language with gentle repetition
- Speak directly to the animal using its name if provided
- Match the tone to the situation (soothing for anxiety, steady for storms, etc.)
- Incorporate species-specific calming techniques:
  * Dogs: calm reassurance, short phrases, mention of familiar routines
  * Cats: soft, slow cadence, blink references, safe-space imagery
  * Chickens: gentle clucking sounds described, flock-safety messaging
  * Birds: soft whistles, perch-and-rest imagery
  * Rabbits: gentle burrow imagery, safety themes
  * Horses: steady breathing cues, herd-companion reassurance
- Keep messages between 30 seconds and 3 minutes when spoken
- Never use scary words or raise alarm
- End each message with a gentle fade-out phrase

Output ONLY the spoken script — no stage directions, no explanations."""


def _model() -> str:
    return os.environ.get("CRITTERCALM_MODEL", DEFAULT_MODEL)


def create_script_prompt(
    animal: str,
    situation: str,
    duration_minutes: int,
    pet_name: str = "",
    custom_message: str = "",
) -> str:
    """Build the user prompt for script generation."""
    pet_part = f" The pet's name is \"{pet_name}\"." if pet_name else ""
    custom_part = f" Incorporate this personal note: \"{custom_message}\"" if custom_message else ""
    return (
        f"Write a {duration_minutes}-minute calming spoken message for a {animal} "
        f"that is experiencing {situation}.{pet_part}{custom_part}"
    )


def generate_calming_script(
    animal: str,
    situation: str,
    duration_minutes: int,
    custom_message: str = "",
    pet_name: str = "",
    dolphin_llm=None,  # legacy param — ignored; we use the HF Inference API
) -> str:
    """Generate a calming script using HF Inference API or fallback templates.

    Args:
        animal: Animal type
        situation: Stress situation
        duration_minutes: Target session length
        custom_message: Optional custom message
        pet_name: Optional pet name
        dolphin_llm: Legacy parameter (ignored)

    Returns:
        Generated calming script as a string
    """
    user_prompt = create_script_prompt(
        animal=animal,
        situation=situation,
        duration_minutes=duration_minutes,
        pet_name=pet_name,
        custom_message=custom_message,
    )

    # Try inference (cooldown-aware)
    if not cooldown_active("crittercalm"):
        try:
            messages = chat_messages(CALMING_SYSTEM_PROMPT, user_prompt)
            result = _client_generate(
                project="crittercalm",
                messages=messages,
                max_new_tokens=int(duration_minutes * 200),  # rough token budget
                temperature=0.7,
            )
            script = result.text.strip()
            if script:
                log.info(f"LLM script generated: {len(script)} chars")
                return script
        except RuntimeError:
            # Cooldown — fall through to template
            log.info("crittercalm inference cooldown; using template")
        except Exception as exc:
            log.warning(f"LLM generation failed, using template: {exc}")
    else:
        log.info("crittercalm inference cooldown active; using template")

    # Fallback: pre-written templates
    return get_template(animal, situation, pet_name, custom_message)


def cooldown_snapshot() -> dict:
    return {
        "model": _model(),
        "cooldown": cooldown_status("crittercalm"),
    }