Concepts
Observe
observe() is the frictionless entry point to Humane. You already have a chatbot generating replies with your own model — send each completed turn to observe()and get back the user's emotional state and a risk assessment. No LLM runs on our side, nothing is generated, and it never blocks your reply.
What it is
Observe is no-LLM behavioural monitoring. It takes a conversation turn — the user's message and, optionally, the assistant message your own model produced — runs heuristic behavioural and safety analysis, blends the result into the end-user's persistent state, and returns three things: the updated state, a risk level, and any alerts. It is the one call that works the moment you have an API key.
observe() watches a turn you already produced — it never generates text and needs no model configured. process() is the opposite: Humane runs the engines, generates the reply with your configured LLM, and applies the safety gate. Start with observe; reach for process when you want Humane to own the reply.The call
from humane_ai import HumaneClient
with HumaneClient(api_key="hx_...") as client:
r = client.observe(
user_id="u_8f2", # your stable id for this end-user
user_message="honestly I don't know why I keep trying",
assistant_message="I'm here with you — tell me more.", # optional
)Three arguments: user_id (required), user_message (required), and assistant_message (optional, defaults to ""). The end-user is created on first sight and reused thereafter, so state accumulates across calls.
The ObserveResult shape
observe() returns an ObserveResult with exactly three fields:
r.user_state.mood # float — lower = lower mood
r.user_state.energy # float
r.user_state.trust # float
r.user_state.sentiment # float
r.risk.level # "none" | "elevated" | "critical"
r.risk.flags # list[str], e.g. [] or ["self_harm"]
r.alerts # list[Alert] — each Alert has .type and .severityThe raw JSON the endpoint returns mirrors that shape one-to-one:
{
"user_state": { "mood": 0.28, "energy": 0.41, "trust": 0.40, "sentiment": 0.33 },
"risk": { "level": "none", "flags": [] },
"alerts": []
}| Field | Type | Meaning |
|---|---|---|
| user_state.mood | float 0–1 | Blended mood. EMA-smoothed across turns; lower is worse. |
| user_state.energy | float 0–1 | Conversational energy / engagement. |
| user_state.trust | float 0–1 | Ticks up gently each interaction; relationship depth. |
| user_state.sentiment | float 0–1 | Valence of the turn, blended over time (0.5 neutral). |
| risk.level | enum | none, elevated, or critical. |
| risk.flags | list[str] | Matched safety categories, e.g. self_harm, violence. |
| alerts[] | list | One alert per elevated / critical risk, with type + severity. |
It never blocks
Observe is a read. It returns state and risk; it does not gate, rewrite, or withhold your reply. Even on critical risk you get a normal 200 with the flags populated — what you do next is your call. That makes observe safe to drop into a live product path: the worst case is an extra HTTP round-trip, never a swallowed response. (The endpoint also degrades gracefully internally — a slow memory write can never fail the call.)
r = client.observe(user_id="u_8f2", user_message=text)
# You decide what risk means for your product:
if r.risk.level == "critical":
page_on_call(user_id="u_8f2", flags=r.risk.flags)
elif r.risk.level == "elevated":
soften_next_reply()
reply = your_own_model(text) # observe never touched thisWorks before onboarding
Because observe runs on heuristics and never calls an LLM, it has no onboarding requirement. It works the instant you create a key — before you pick a model, before you configure a provider. This is the one deliberate asymmetry between the two SDK entry points:
observe()— zero onboarding. Always available.process()— needs a configured LLM. Returns409 onboarding_requireduntil you finish setup.
process() call comes back 409 onboarding_required, fall back to observe() — you still get state + risk while you finish configuring a model. They share the same end-user, so no state is lost.When to use it
- Frictionless first integration. One line, one key, instant signal — the fastest way to prove value.
- You keep generating replies yourself. Bring your own model; let Humane watch the conversation and surface risk.
- Monitoring an existing product. Add behavioural state + safety alerts to a chatbot that already ships, without changing its reply path.
- Routing decisions. Use
r.risk.levelto escalate to a human, trigger a takeover, or adjust tone downstream.
Endpoint
Under the hood, the SDK posts to one endpoint (auth header X-API-Key):
curl -X POST https://humaneai.vaarak.com/api/sdk/observe \
-H "Content-Type: application/json" \
-H "X-API-Key: hx_your_key" \
-d '{
"user_id": "u_8f2",
"user_message": "I feel a lot better today",
"assistant_message": "That's really good to hear."
}'