What it does
Reads a short dialogue turn (assistant + user history) and emits an
AffectDelta-shaped JSON:
{ "valence": -0.31, "arousal": 0.52, "labels": ["frustrated", "curious"], "confidence": 0.78, "monologue": "想搞懂但卡住了,又想再试一次", "mixed_blend": { "primary": "frustrated", "secondary": "curious", "weights": [0.6, 0.4] }}
This is the LLMSource slot in feltstate's pipeline — measurement,
not generation. The reply model reads the resulting felt-state block
and decides for itself how to act.
Why it exists
feltstate.PHILOSOPHY §1 says: "the real signal comes from an LLMSource
(a separate model call whose only job is to measure) or a classifier you
fine-tune."
This is the second half of that sentence — an actual fine-tuned classifier.
Training data — not released
~750 SFT samples, hand-curated Chinese-first dialogue snippets with
labeled affect outcomes. Data not released to protect contributor
privacy. The schema and label vocabulary are documented so others can
build their own corpus.
Label vocab — 24 labels (v8)
Anchor table (valence, arousal) drawn from internal rater calibration:
Table with columns: label, v, a, notes| label | v | a | notes |
|---|
| focused | +0.58 | 0.45 | mid-arousal — quiet focus, not hype |
| frustrated | -0.48 | 0.37 | low arousal — stuck, not exploding |
| satisfied | +0.76 | 0.59 | |
| tired | -0.67 | 0.38 | low-v low-a — depleted |
| exploring | +0.30 | 0.53 | curious-leaning, slightly positive |
| ... | |
Inter-rater agreement on these labels: r ≈ 0.74 on calibration set.
(See v9 for the 50-label expansion.)
Usage
from peft import PeftModelfrom transformers import AutoModelForCausalLM, AutoTokenizer base = "Qwen/Qwen2.5-1.5B-Instruct"tok = AutoTokenizer.from_pretrained(base)mdl = AutoModelForCausalLM.from_pretrained(base, device_map="auto")mdl = PeftModel.from_pretrained(mdl, "kaishuiji/vheart-affect-v8")mdl.eval() prompt = "..." # see prompt template belowout = mdl.generate(tok(prompt, return_tensors="pt").input_ids.to(mdl.device), max_new_tokens=256, do_sample=False)print(tok.decode(out[0], skip_special_tokens=True))
With feltstate
from feltstate import Enginefrom feltstate.sources import VheartSource # see feltstate/sources/vheart.py eng = Engine(source=VheartSource("kaishuiji/vheart-affect-v8"))eng.observe("今晚跑通了三周的实验。")print(eng.state.mood)
Prompt template
Input is a chat-style turn; assistant response is the JSON above.
System prompt (translated; production uses Chinese):
You measure the affect of a character in this dialogue. You do not
generate replies; you output JSON only. Schema fields: valence (-1..1),
arousal (0..1), labels (subset of vocab), confidence (0..1), monologue
(short Chinese sentence — what the character feels internally, not
says aloud), mixed_blend (primary + secondary label with weights).
Same label, different context → different point. Do not collapse to
templates.
Full system prompt available in the prompt_template.txt file in this repo.
Limitations
- Chinese-first. English inputs work but trained mostly on Chinese.
- Single-character measurement. Designed for a felt state of one
ongoing companion, not third-party emotion analysis at scale.
- Schema bound to feltstate. Labels are the v8 24-vocab. If you
need a different vocab, fine-tune your own — this is a reference,
not a hammer.
- 750 samples is small. v9 expanded data; if you have GPU budget,
use v9.
Citation
@software{feltstate_vheart, author = {morephine}, title = {feltstate / vheart-affect-v8}, url = {https://huggingface.co/kaishuiji/vheart-affect-v8}, year = {2026}}
License
Apache 2.0 (matches Qwen2.5 base).
Acknowledgments
- Qwen team for the base model
- feltstate philosophy authors (same hands)