---
title: Ruby SDK
description: "Ruby SDK: ask the ten ThinkThen questions from Ruby and get yes, no or not sure back as plain Ruby values."
goal: The reader runs a first decide in Ruby, installs the gem, rescues not sure and failures, and finds the Ruby call for each of the ten functions.
reader: A Ruby developer who wants to ask questions about text from code
kind: reference
verified: 2026-11-03
sidebar:
  label: "Ruby"
---

# Ruby SDK

The Ruby SDK gives your Ruby code the ten functions as methods on one client; `decide?` gives back `true` or `false`, and raises when the answer is not sure.

```ruby
require "thinkthen"

ThinkThen::Client.open do |client|
  is_refund = client.decide?("Does the customer request a refund?",
                             "Please refund my order.")
  puts is_refund
end
```

Output

```text
true
```


`true` means yes: the message asks for a refund.

Install the gem (CRuby 3.3 or later and RubyGems). It includes the native engine.

```sh
gem install thinkthen --version 0.2.0
```

With Bundler, run `bundle add thinkthen --version 0.2.0`. The client uses the key from the [Quickstart](/quickstart/).

## Rescue not sure and failures

Give `threshold:` a range, such as `0.2..0.8`, to set a low bar and a high bar. Between them, decide is not sure, and `decide?` raises `ThinkThen::UnsureError`. When ThinkThen can't judge the message, it raises an error that includes `ThinkThen::Error`, such as `ThinkThen::RecordError`. Neither one can pass for `false`.

```ruby
require "thinkthen"

message = "I was charged twice for one invoice."
ThinkThen::Client.open do |client|
  is_refund = client.decide?("Does the customer request a refund?",
                             message, threshold: 0.2..0.8)
  puts is_refund
rescue ThinkThen::UnsureError => unsure
  puts "Not sure (#{unsure.probability}): ask a person."
rescue ThinkThen::Error => failure
  puts "#{failure.code}: #{failure.message}"
end
```

Output

```text
Not sure (0.7): ask a person.
```


A double charge might be a refund request, or might not, so the answer is not sure. Send it to a person.

`UnsureError#probability` is the chance of yes. `UnsureError` does not include `ThinkThen::Error`, so `rescue ThinkThen::Error` never catches a not sure by accident. The error's `code` names what went wrong, such as `:"record.empty"`; [Troubleshooting](/reference/troubleshooting/) lists each one.

To check the outcome without raising, call `client.decide` without the question mark. What it gives back is a `ThinkThen::Answered`, `ThinkThen::Unsure` or `ThinkThen::Failed`, so you can test `unsure?` and `failed?`, or match it with `case ... in ThinkThen::Unsure`. Reading its `answer` raises just as `decide?` does.

## The ten functions

Each function asks the same support-inbox questions as the function pages.

### decide

<details class="example-disclosure">
<summary>Yes or no about one message</summary>

```ruby
require "thinkthen"

ThinkThen::Client.open do |client|
  # decide? raises ThinkThen::UnsureError when not sure,
  # ThinkThen::RecordError on failure.
  is_refund = client.decide?("Does the customer request a refund?",
                             "Please refund my order.")
  puts is_refund
end
```

Output

```text
true
```


Yes: the message asks for a refund. `decide?` takes `threshold:` (one bar, or a range for a low and a high bar), `yes_means:` and `no_means:`. [decide](/functions/decide/) shows every option.

`decide_each` takes many messages and yields one result for each, in order. A not sure or a failure is a result like any other, so the rest keep going:

```ruby
require "thinkthen"

messages = [
  {"id" => "a1", "text" => "Please refund my order."},
  {"id" => "a2", "text" => "Where is my parcel?"},
  {"id" => "a3", "text" => "I was charged twice for one invoice."},
]
question = "Does the customer request a refund?"

ThinkThen::Client.open do |client|
  # One message: true or false, raising when not sure or failed.
  is_refund = client.decide?(question, messages.first["text"])
  puts "first message: #{is_refund}"

  # Many messages in one call: one row each, in input order.
  client.decide_each(question, messages, field: "/text") do |refund_check|
    case refund_check
    in ThinkThen::Answered then puts "#{refund_check.id} #{refund_check.yes?}"
    in ThinkThen::Unsure then puts "#{refund_check.id} not sure: ask a person"
    in ThinkThen::Failed then
      puts "#{refund_check.id} failed: #{refund_check.error.code}"
    end
  end
end
```

Output

```text
first message: true
a1 true
a2 false
a3 true
```


`field: "/text"` judges the `text` field of each Hash, and `id` is the message's own `id`. Each one-message method has an `_each` twin that works the same way.

</details>

### choose

<details class="example-disclosure">
<summary>The one choice that fits</summary>

```ruby
require "thinkthen"

ThinkThen::Client.open do |client|
  topic = client.choose("What is the message about?",
                        "Please refund my order.",
                        choices: ["billing", "shipping", "other"]).answer
  puts topic
end
```

Output

```text
billing
```


A refund is about billing. `choose` takes `descriptions:`, a Hash from each choice to what it means, and `threshold:`, which can make choose not sure. `answer` raises `UnsureError` then. [choose](/functions/choose/) shows every option.

</details>

### filter

<details class="example-disclosure">
<summary>Only the messages that answer yes</summary>

```ruby
require "thinkthen"

messages = [
  "Please refund my order.",
  "Where is my parcel?",
  "I was charged twice for one invoice.",
]
ThinkThen::Client.open do |client|
  # Only the messages that answer yes.
  # A failed message raises ThinkThen::RecordError after the kept ones.
  refund_requests =
    client.filter("Does the customer request a refund?", messages)
  refund_requests.each { |refund_request| puts refund_request }
end
```

Output

```text
Please refund my order.
I was charged twice for one invoice.
```


The parcel question is left out. `filter` gives back your own messages, and leaves out the ones it is not sure about. If any message fails, it raises one `RecordError` after the kept ones, and its `rows` lists every failure. `failures: :row` hands the failures over after the kept messages instead, and `filter_each` gives every message with its outcome. [filter](/functions/filter/) shows every option.

</details>

### tag

<details class="example-disclosure">
<summary>Every label that fits</summary>

```ruby
require "thinkthen"

ThinkThen::Client.open do |client|
  topics = client.tag("Which topics appear?",
                      "I was charged twice and my parcel is late again.",
                      labels: ["billing", "shipping"]).answer
  p topics
end
```

Output

```text
["billing", "shipping"]
```


The message is about both billing and shipping. The answer is an Array of the labels that fit. [tag](/functions/tag/) shows every option.

</details>

### score

<details class="example-disclosure">
<summary>A number from 0 to 1 on your scale</summary>

```ruby
require "thinkthen"

ThinkThen::Client.open do |client|
  urgency = client.score("How urgent is the request?",
                         "Please refund my order.",
                         levels: ["routine", "urgent"]).answer
  puts urgency
end
```

Output

```text
0.05
```


The levels run lowest first, so a plain refund request scores near routine. [score](/functions/score/) shows every option.

</details>

### rank

<details class="example-disclosure">
<summary>Every message, highest first</summary>

```ruby
require "thinkthen"

messages = [
  "Please refund my order.",
  "Where is my parcel?",
  "I was charged twice for one invoice.",
]
ThinkThen::Client.open do |client|
  # Every message, most urgent first. rank starts at 1.
  by_urgency = client.rank("How urgent is the request?", messages,
                           levels: ["routine", "urgent"])
  by_urgency.each { |ranked| puts "#{ranked.rank} #{ranked.item}" }
end
```

Output

```text
1 I was charged twice for one invoice.
2 Where is my parcel?
3 Please refund my order.
```


The double charge is the most urgent. Each result holds a copy of your message as `item` and its place as `rank`, starting at 1. Reading `rank` raises `UnsureError` for a message it is not sure about; that message keeps its place in the order. `top:` keeps only the first few. [rank](/functions/rank/) shows every option.

</details>

### find

<details class="example-disclosure">
<summary>The line that answers the question</summary>

```ruby
require "thinkthen"

message = "Morgan works for Example Group.\nPlease refund my order."
ThinkThen::Client.open do |client|
  # nil when no line answers the question.
  action = client.find("Which line states the requested action?",
                       message).answer
  puts action ? action.text : "no line answers"
end
```

Output

```text
Please refund my order.
```


The second line states the action. The answer is a `ThinkThen::Found` with `text`, `start` and `end`, or `nil` when no line answers. [find](/functions/find/) shows every option.

</details>

### recognize

<details class="example-disclosure">
<summary>Each name in the text and its kind</summary>

```ruby
require "thinkthen"

ThinkThen::Client.open do |client|
  names = client.recognize("Morgan works for Example Group.",
                           kinds: ["person", "organization"]).answer
  names.each { |name| puts "#{name.kind}: #{name.text}" }
end
```

Output

```text
person: Morgan
organization: Example Group
```


Each `ThinkThen::Name` holds `text`, `kind`, `start`, `end` and `probability`. [recognize](/functions/recognize/) shows every option.

</details>

### relate

<details class="example-disclosure">
<summary>Names linked by the relations you allow</summary>

```ruby
require "thinkthen"

message = "Morgan works for Example Group.\nPlease refund my order."
works_for = ThinkThen::RelationRule.new(source: "person",
                                        target: "organization",
                                        relation: "works_for")
ThinkThen::Client.open do |client|
  relations = client.relate(message, rules: [works_for]).answer
  relations.each do |relation|
    puts "#{relation.source.text} --#{relation.relation}--> " \
         "#{relation.target.text}"
  end
end
```

Output

```text
Morgan --works_for--> Example Group
```


A `ThinkThen::RelationRule` names the kind of name at each end and the relation between them. It also takes `phrase:` and `symmetric: true`. [relate](/functions/relate/) shows every option.

</details>

### annotate

<details class="example-disclosure">
<summary>Several questions for each message</summary>

```ruby
require "thinkthen"

messages = [
  "Please refund my order.",
  "Where is my parcel?",
  "I was charged twice for one invoice.",
]
questions = {
  "refund" => ThinkThen::DecideQuestion.new(
    "Does the customer request a refund?"
  ),
  "topic" => ThinkThen::ChooseQuestion.new(
    "What is the message about?",
    choices: ["billing", "shipping", "other"]
  ),
  "urgency" => ThinkThen::ScoreQuestion.new(
    "How urgent is the request?",
    levels: ["routine", "urgent"]
  ),
}
ThinkThen::Client.open do |client|
  client.annotate_each(questions, messages) do |form|
    is_refund = form[:refund].answer
    topic = form[:topic].answer
    urgency = form[:urgency].answer
    puts "#{is_refund} #{topic} #{urgency}"
  end
end
```

Output

```text
true billing 0.05
false shipping 0.14
true billing 0.45
```


`form[:refund]` takes a String or Symbol name and raises `KeyError` for a name you never added. Each `answer` raises `UnsureError` when not sure, as decide does. For one message, call `client.annotate(questions, message)`. [annotate](/functions/annotate/) shows every option.

</details>

## The full API

A message is a String, or a Hash with String or Symbol keys; `field: "/text"` judges one field. Every call also takes `name:` and `cancel:`. Pass `cancel: ThinkThen::CancelToken.new`, and `cancel` on the token stops the call; Ctrl-C does the same, and both raise `ThinkThen::CancelledError`. `ThinkThen.configure` sets up one shared client for the module calls, such as `ThinkThen.decide?`.

The [Ruby signatures](https://github.com/botassembly/thinkthen/blob/v0.2.0/sdk/ruby/sig/thinkthen.rbs) list every public name, and the [Ruby API reference](https://github.com/botassembly/thinkthen/blob/v0.2.0/sdk/ruby/API.md) explains each one.
