ThinkThen

Tutorial

From one question and its exit code to a saved question and a stream of messages.

1. Install it

The download script
curl -fsSL https://thinkthen.dev/install.sh | sh
Homebrew, an option on a Mac
brew install botassembly/thinkthen/thinkthen

The install page covers the key and every language and database.

2. Ask one question

Name the question, pipe the message in, and pass the question as an argument. decide prints a bare true, false, or null, and it sets the exit code to match.

The ticket asks for a refund. decide prints true and exits 0.
question="Does the customer ask for a refund?"

cat <<'EOF' |
I renewed once this morning, but my card shows two charges.
Please refund the duplicate.
EOF
thinkthen decide "$question"
Output
true
exit 0: yes

The exit code is the answer. A script branches on it the way it branches on test.

3. Set a band

--threshold is your bar on the probability. One number is a cut, and the default is 0.5. Two numbers make a band: under the low bar is no, at or over the high bar is yes, and anything between is not sure.

"I want to send this back." could mean an exchange or money back. It lands inside the band 0.2:0.8. decide prints null and exits 3.
question="Does the customer ask for a refund?"

printf '%s\n' "I want to send this back." |
thinkthen decide "$question" \
  --threshold 0.2:0.8
Output
null
exit 3: not sure

The threshold never changes the probability. It changes what you call a yes.

4. Branch on the answer

A script reads the exit code and hands a person the calls it should not make alone.

The send-back line lands in the band. decide exits 3, and the case sends it to a person. The script then exits 0.
question="Does the customer ask for a refund?"

printf '%s\n' "I want to send this back." |
thinkthen decide "$question" \
  --quiet \
  --threshold 0.2:0.8
refund_code=$?

case $refund_code in
  0) echo "refund it" ;;
  1) echo "answer it as usual" ;;
  3) echo "send it to a person" ;;
esac

test "$refund_code" -eq 3
Output
send it to a person
exit 0

5. Save the question to a file

A question file holds one question: the words, what a yes and a no mean, and the threshold.

refund.json, the saved question
{
  "decide": "Does the customer ask for a refund?",
  "true": "The customer asks for money back.",
  "false": "Anything else, such as a cancellation or thanks.",
  "threshold": "0.2:0.8"
}

Write @refund.json in place of the question words.

The saved question answers true for a plain request for money back.
cat <<'EOF' |
I would like to return this and get my money back.
EOF
thinkthen decide @refund.json
Output
true
exit 0: yes
Sending it back could mean an exchange or money back. The band in the file calls it not sure. decide prints null and exits 3.
printf '%s\n' "I want to send this back." |
thinkthen decide @refund.json
Output
null
exit 3: not sure

6. Judge a stream

--lines tells decide to judge each line as its own message. Each answer then prints as one JSON line with its input beside it. The input shows which answer belongs to which message. jq . spreads each line out to read.

Each answer sits beside its message. The send-back line is the null from step 3.
question="Does the customer ask for a refund?"

cat <<'EOF' |
Please refund my order. It arrived broken.
Thanks for the quick help yesterday!
I want to send this back.
EOF
thinkthen decide "$question" \
  --batch 1 \
  --lines \
  --threshold 0.2:0.8 |
jq .
Output
{
  "input": "Please refund my order. It arrived broken.",
  "value": true
}
{
  "input": "Thanks for the quick help yesterday!",
  "value": false
}
{
  "input": "I want to send this back.",
  "value": null
}
exit 0

filter reads one record per line with no flag. It keeps the records where the answer is yes, byte for byte and in order.

filter keeps the two complaints: the broken zipper and the snapped strap.
question="Is this a complaint?"

cat <<'EOF' |
Arrived a day early. Thank you!
The zipper broke the first time I used it.
Does this come in blue?
The strap snapped on day two.
EOF
thinkthen filter "$question" --batch 1
Output
The zipper broke the first time I used it.
The strap snapped on day two.
exit 0: the run finished

rank sorts the records by how likely the answer is yes.

The outage comes first and the quote due today second. The bill due in a month comes third, and the newsletter that needs no reply comes last.
question="Is this urgent?"

cat <<'EOF' |
Newsletter: our autumn catalog is here. No reply needed.
Our checkout page is down and customers cannot pay
Reminder: your invoice is due in 30 days
Please send the signed quote by 5 pm today
EOF
thinkthen rank "$question" --batch 1
Output
Our checkout page is down and customers cannot pay
Please send the signed quote by 5 pm today
Reminder: your invoice is due in 30 days
Newsletter: our autumn catalog is here. No reply needed.
exit 0: the run finished

Put them in a pipe and the second question reads only what the first kept. See the leads how-to for filter, rank, and choose in one pipeline.

7. Ask many questions at once

annotate answers a question set for every record. A question set is any mix of decide, choose, score, and tag questions in one file.

inbox-form.json, the question set
{
  "version": 1,
  "questions": {
    "kind": {
      "choose": "What kind of message is this?",
      "options": [
        "billing",
        "delivery",
        "question"
      ]
    },
    "urgency": {
      "score": "How urgent is this?",
      "levels": [
        "Routine.",
        "Soon.",
        "Immediate."
      ]
    }
  }
}
Three messages need a reply, each beside its kind and its urgency from 0 to 2. The order needed tonight sits near 2, Immediate. The thank-you note drops out.
question="Does this need a reply?"

cat <<'EOF' |
Please fix the wrong amount on my invoice.
Just saying thanks, no reply needed.
Order never arrived and the party is tonight.
Do you ship to Canada?
EOF
thinkthen filter "$question" --batch 1 |
thinkthen annotate inbox-form.json \
  --batch 1 --lines |
jq .
Output
{
  "input": "Please fix the wrong amount on my invoice.",
  "value": {
    "kind": "billing",
    "urgency": 0.58
  }
}
{
  "input": "Order never arrived and the party is tonight.",
  "value": {
    "kind": "delivery",
    "urgency": 1.96
  }
}
{
  "input": "Do you ship to Canada?",
  "value": {
    "kind": "question",
    "urgency": 0.01
  }
}
exit 0

filter drops the thank-you note. Each message left comes back with one field per question.

ThinkThen answers. Your code acts.