Recipe catalog / sentiment-shift
Compare sentiment across messages
How does the sentiment expressed in laterMessage compare with earlierMessage from the same person?
You need to know whether a customer's expressed sentiment moved during a conversation, for example after an agent reply or a handoff.
Explore this recipe interactively ยท Source and implementation guide
Use sentiment-shift in TypeScript
Install with npm install jev-recipes. Requires Node.js 22.9 or newer and ES modules. Set TYPESAFE_API_KEY in your server environment for live calls, which send input to TypeSafe and use API quota. See the installation guide.
import { sentimentShift } from 'jev-recipes/sentiment-shift';
const result = await sentimentShift({
"earlierMessage": "I have been waiting three days for a reply and my order still has not shipped. This is ridiculous.",
"laterMessage": "Thanks for sorting that out so quickly, the tracking number just arrived. Really appreciate it."
});
console.log(result);
Input contract
| Field | Type | Needed |
|---|---|---|
| earlierMessage | string | Required |
| laterMessage | string | Required |
| minConfidence | number | Optional |
Full input and result schemas
{
"input": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"earlierMessage": {
"type": "string"
},
"laterMessage": {
"type": "string"
},
"minConfidence": {
"type": "number",
"minimum": 0,
"maximum": 1
}
},
"required": [
"earlierMessage",
"laterMessage"
]
},
"result": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"model": {
"type": "string"
},
"usage": {
"type": "object",
"properties": {
"input_tokens": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"output_tokens": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"input_tokens",
"output_tokens"
],
"additionalProperties": false
},
"status": {
"type": "string",
"enum": [
"ready",
"review"
]
},
"verdict": {
"type": "string",
"enum": [
"improved",
"unchanged",
"worsened",
"unclear"
]
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"probabilities": {
"type": "object",
"propertyNames": {
"type": "string",
"enum": [
"improved",
"unchanged",
"worsened",
"unclear"
]
},
"additionalProperties": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"required": [
"improved",
"unchanged",
"worsened",
"unclear"
]
}
},
"required": [
"model",
"usage",
"status",
"verdict",
"confidence",
"probabilities"
],
"additionalProperties": false
}
}Saved example result
This hand-authored response demonstrates the contract. It is not a model accuracy measurement. Run it without an API key: npx jev-recipes demo sentiment-shift.
{
"model": "demo-fixture",
"usage": {
"input_tokens": 0,
"output_tokens": 0
},
"status": "ready",
"verdict": "improved",
"confidence": 0.94,
"probabilities": {
"improved": 0.94,
"unchanged": 0.02,
"worsened": 0.01,
"unclear": 0.03
}
}
Evaluation evidence
No verified live accuracy measurement is available. Evaluate representative cases before using this decision in your workflow.
Use the evaluation guide to measure this decision on your own labeled cases.
Limitations
- Compares expressed wording in two messages, not the person's actual feelings or the state of their issue.
- A shift does not establish its cause. Pair the verdict with the intervening turns in application code.
Related recipes
- frustration-signal: Use frustration-signal for a categorical read on frustration in a single message.
- resolution-check: Use resolution-check to decide whether the customer reported the issue resolved.