Turn natural-language statements like "I had a rough morning" into structured mood data — a mood label, an intent, an energy level, and a confidence score — using whatever language model you prefer.
"I had a rough morning, ugh"
│
▼
emoody
│
▼
{
"mood": "frustrated",
"intent": "shift_mood", ← they want relief, not validation
"energy_level": 3, ← suggest something low-key, not hype
"confidence": 0.85
}
Any Python app can use this. You bring your own LLM key.
A lot of apps want to do something like: figure out how the user feels, then respond well. Wellness apps. Journaling apps. Voice assistants. Music recommenders. Smart homes. They all need the same primitive — a small, reliable function that turns a sentence into a structured guess about the speaker's emotional state and what they probably want.
Until now, every team has built that primitive themselves. emoody is that primitive, packaged once, given away free.
You compose it into your app however you like. emoody doesn't pick the music, store the journal entries, or run the chat — it just classifies. Your app does the rest.
- You're building an app where the user types or speaks how they're feeling, and you want to take a sensible action in response.
- You want structured output — typed enums and validated numbers, not a free-form blob of text from an LLM that your code has to defend against.
- You want to swap LLM providers (Anthropic, OpenAI, a local model) without changing your app code.
- You don't want to ship credentials in your library — you (or the developer integrating your app) bring your own.
from emoody import classify_mood, MockProvider
result = classify_mood("I'm feeling pretty down today", provider=MockProvider())
print(result.mood) # Mood.SAD
print(result.intent) # Intent.SHIFT_MOOD (they want to feel better)
print(result.energy_level) # 2 (low energy — calm, not hype)
print(result.confidence) # 0.5MockProvider uses simple keyword rules. It's not smart, but it lets you build, test, and demo your app without spending a cent or signing up for anything.
import os
os.environ["ANTHROPIC_API_KEY"] = "sk-ant-..." # your key, not ours
from emoody import classify_mood
result = classify_mood("I had a rough morning, ugh")
# MoodIntent(mood=Mood.FRUSTRATED, intent=Intent.SHIFT_MOOD,
# energy_level=3, confidence=0.85)When you don't pass a provider, classify_mood defaults to Anthropic Claude (Haiku 4.5). It uses Claude's structured-output feature so the result is always a valid MoodIntent — never a malformed JSON blob.
You only need to set ANTHROPIC_API_KEY once, in your shell environment.
A MoodIntent is just a Pydantic model. Four fields:
| Field | What it is | Possible values |
|---|---|---|
mood |
How the user is feeling | happy, sad, frustrated, anxious, calm, energetic, neutral |
intent |
What kind of response would help | match_mood (lean in), shift_mood (cheer me up / calm me down), focus, sleep, party |
energy_level |
Suggested response energy | 0 (quiet/calm) → 10 (high-energy) |
confidence |
Model's confidence in its guess | 0.0 → 1.0 |
The schema is opinionated on purpose. v0.1 has a small fixed vocabulary so downstream apps can reason about it. You can pass a custom system prompt to bias the model, but you can't redefine the enums yet — that's planned for v0.2.
your app emoody provider (Anthropic / OpenAI / yours)
│ │ │
│ classify_mood() │ │
├──────────────────▶│ │
│ │ structured-output call │
│ ├───────────────────────────▶│
│ │ │
│ │ valid MoodIntent │
│ │◀───────────────────────────│
│ │ │
│ MoodIntent │ │
│◀──────────────────│ │
emoody is a thin orchestration layer. The real work is the provider's. Swap providers freely.
The Anthropic provider is just one implementation of a tiny interface:
class Provider(Protocol):
def classify(self, text: str, *, system_prompt: str) -> MoodIntent: ...Anything matching that shape works. Here's a stub showing how you'd plug in your own — OpenAI, a local model via Ollama, a deterministic fake for tests, whatever:
from emoody import classify_mood
from emoody.schema import Intent, Mood, MoodIntent
class MyProvider:
def classify(self, text, *, system_prompt):
# call your LLM, parse the response, return a MoodIntent
return MoodIntent(
mood=Mood.HAPPY, intent=Intent.MATCH_MOOD,
energy_level=7, confidence=0.9,
)
result = classify_mood("hello world", provider=MyProvider())The protocol is runtime_checkable, so isinstance(MyProvider(), Provider) works for tests.
emoody classify "I'm exhausted but want to push through" --provider mock{
"mood": "neutral",
"intent": "sleep",
"energy_level": 1,
"confidence": 0.5
}Drop --provider mock to use the default Anthropic provider (needs ANTHROPIC_API_KEY).
# Core + mock provider, nothing else
pip install git+https://github.com/aniruddhgoteti/eMoody
# Add the Anthropic provider
pip install "emoody[anthropic] @ git+https://github.com/aniruddhgoteti/eMoody"
# Everything: Anthropic + Streamlit demo + dev tools
pip install "emoody[dev] @ git+https://github.com/aniruddhgoteti/eMoody"PyPI release coming once the schema settles. Until then, install from this repo directly.
Requires Python 3.10+.
pip install "emoody[demo,anthropic] @ git+https://github.com/aniruddhgoteti/eMoody"
streamlit run examples/streamlit_demo.pyOpen the URL Streamlit prints. Type a sentence. Watch it get classified. Toggle to the Anthropic provider if you want to use a real LLM (you'll need your own key).
emoody began as the winning entry to the 2018 Sony Audio Control Hackathon. The original was a single Python script that listened to your voice through a microphone, classified your mood with Wit.ai, and made a Sony smart speaker play music to match. Cute hackathon prototype — and badly out of date by 2026 (Wit.ai is being sunset by Meta, and the script had hardcoded paths and credentials all over).
The original lives at archive/eMoody-2018.py for posterity.
This rewrite throws out the speaker integration, the voice capture, and the cloud NLP, and keeps just the idea — turn a feeling expressed in plain English into structured data — packaged as a small library other developers can build on.
Does this run a model on my machine? No. emoody itself is just glue code. The classification happens in whatever LLM provider you pick. The mock provider is the only "local" option, and it's not a real model — just keyword rules useful for tests and offline demos.
Can I use this without any API key?
Yes — use MockProvider. It's fully deterministic and good enough to develop your app's UI and logic before you pay for any LLM.
Does this store anything? No. emoody has no database, no logs, no telemetry. Each call is stateless.
Can I add a custom mood like nostalgic or bittersweet?
Not in v0.1 — the enum is fixed. v0.2 plans to support custom vocabularies via a MoodSchema(...) constructor. Until then, you can build a richer enum on top.
What happens if the LLM returns garbage?
The Anthropic provider uses Claude's tool-use feature, which structurally enforces the JSON shape — Claude can't return a malformed MoodIntent. If you write your own provider, you need to enforce the shape yourself, or the call will raise a Pydantic validation error.
How much does the Anthropic provider cost? Roughly $0.0005 per classification at list price (Claude Haiku 4.5, ~300 input tokens + 80 output tokens). With prompt caching that drops to ~$0.0002. A user doing 50 classifications a month costs you about two cents.
How do I verify it really works with the Anthropic API?
export ANTHROPIC_API_KEY=...
pip install -e ".[dev]"
pytest -m smokeThat's an opt-in test (skipped by default and never run in CI) that pushes five varied phrases through Claude and checks the moods are sensible.
v0.1 — alpha. The schema may shift in v0.2 once real downstream apps integrate. Pin the version if that scares you.
Roadmap lives in BACKLOG.md. Headlines: OpenAI + local-LLM providers next, then voice helpers, then async.
MIT.
That's it. Build something.