Java SDK
The Java SDK gives your code the ten functions as methods on a client; each returns a result whose answer() you can use directly, such as true or false from decide.
import dev.thinkthen.*;
public class Example { public static void main(String[] args) { try (var client = ThinkThen.builder().build()) { boolean isRefund = client.decide( "Does the customer request a refund?", "Please refund my order.").answer(); System.out.println(isRefund); } }}Output
true
Add it to Maven or Gradle (Java 22 or later). It includes the native engine:
io.github.botassembly:thinkthen-java:0.2.0With the JAR and its dependencies in a lib folder, Java runs the file directly:
java --enable-native-access=ALL-UNNAMED \ -cp 'lib/*' Example.javaOn the module path, use --enable-native-access=dev.thinkthen. The client uses the key you set in the Quickstart.
When decide is not sure
Section titled “When decide is not sure”Give decide a low and a high bar, and a text that falls between them is not sure. Reading a not-sure answer with answer() throws UnsureException, so it can't pass for a no. This message lands between the bars:
import dev.thinkthen.*;
public class Unsure { public static void main(String[] args) { var bars = DecideOptions.builder() .threshold(Threshold.band(0.2, 0.8)).build(); try (var client = ThinkThen.builder().build()) { var isRefund = client.decide( "Does the customer request a refund?", "I was charged twice for one invoice.", bars); try { if (isRefund.answer()) { System.out.println("Send to refunds."); } else { System.out.println("Leave in the inbox."); } } catch (UnsureException notSure) { System.out.println("Not sure. Ask a person."); } } }}Output
Not sure. Ask a person.
Send not sure to a person. To branch without the exception, check isUnsure() first. UnsureException is a NoSuchElementException, and its probability() gives the chance of yes.
When a call fails
Section titled “When a call fails”Blank text throws ThinkThenException at once, and nothing is sent:
import dev.thinkthen.*;
public class Failure { public static void main(String[] args) { try (var client = ThinkThen.builder().build()) { client.decide( "Does the customer request a refund?", ""); } catch (ThinkThenException failure) { System.out.println(failure.code().wireName()); } }}Output
record.empty
Any other failure, such as the service being down, comes back as a result whose isFailed() is true. Its answer() throws ThinkThenException, whose code() names the problem. throwIfFailed() throws it sooner. A list passed to a one-message call throws IllegalArgumentException; use the …Stream call instead. Troubleshooting lists each code and its fix.
The ten functions
Section titled “The ten functions”Each example asks about the same support-inbox messages as the function pages.
decide
Section titled “decide”decide: yes, no or not sure
import dev.thinkthen.*;
public class Example { public static void main(String[] args) { try (var client = ThinkThen.builder().build()) { boolean isRefund = client.decide( "Does the customer request a refund?", "Please refund my order.").answer(); System.out.println(isRefund); } }}Output
true
The message asks for a refund, so decide says true. DecideOptions.builder().threshold(Threshold.cut(0.8)) moves the bar, and Threshold.band(0.2, 0.8) sets a low and a high bar, as above. field(Pointer.of("/text")) picks the field to judge when a message is a Map or JSON. client.decideStream(question, messages) judges a whole list and returns a Stream of results; close it with try-with-resources. decide shows every answer.
choose
Section titled “choose”choose: one answer from your list
import dev.thinkthen.*;import java.util.List;
public class Choose { public static void main(String[] args) { try (var client = ThinkThen.builder().build()) { String topic = client.choose( "What is the message about?", "Please refund my order.", List.of("billing", "shipping", "other")) .answer(); System.out.println(topic); } }}Output
billing
A refund request is about billing. With ChooseOptions.builder().threshold(...), choose is not sure when no choice reaches the bar, and answer() then throws UnsureException. choose shows every answer.
filter
Section titled “filter”filter: keep the messages that answer yes
import dev.thinkthen.*;import java.util.List;import java.util.stream.Stream;
public class Filter { public static void main(String[] args) { List<String> messages = List.of( "Please refund my order.", "Where is my parcel?", "I was charged twice for one invoice."); try (var client = ThinkThen.builder().build(); Stream<String> refunds = client.filter( "Does the customer request a refund?", messages)) { refunds.forEach(System.out::println); } }}Output
Please refund my order.
I was charged twice for one invoice.
filter gives a Stream of your own messages that answer yes: the refund request and the double charge. Not-sure messages are left out. If a message fails, the stream first gives every kept message, then throws one ThinkThenException whose failures() lists each failed result. client.filterRows(...) gives a Filtered for every message instead, with item(), kept() and isUnsure():
import dev.thinkthen.*;import java.util.List;import java.util.Map;
public class Example { public static void main(String[] args) { List<Map<String, String>> messages = List.of( Map.of("id", "a1", "text", "Please refund my order."), Map.of("id", "a2", "text", "Where is my parcel?"), Map.of("id", "a3", "text", "I was charged twice for one invoice.")); var question = "Does the customer request a refund?"; var options = FilterOptions.builder() .field(Pointer.of("/text")).build(); try (var client = ThinkThen.builder().build(); var refundChecks = client.filterRows(question, messages, options)) { refundChecks.forEach(each -> { boolean kept = each.row() instanceof Answered<Boolean, NoDetail> a && a.answer(); System.out.println(each.item().get("id") + " " + each.row().status() + " kept=" + kept); }); } }}Output
a1 ANSWERED kept=true
a2 ANSWERED kept=false
a3 ANSWERED kept=true
filter shows every answer.
tag: every label that fits
import dev.thinkthen.*;import java.util.List;
public class Tag { public static void main(String[] args) { try (var client = ThinkThen.builder().build()) { List<String> topics = client.tag( "Which topics appear?", "I was charged twice and my parcel is late again.", List.of("billing", "shipping")).answer(); System.out.println(topics); } }}Output
[billing, shipping]
The message names a double charge and a late parcel, so both labels fit. The list is empty when none fits. tag shows every answer.
score: a number from 0 to 1
import dev.thinkthen.*;import java.util.List;
public class Score { public static void main(String[] args) { try (var client = ThinkThen.builder().build()) { double urgency = client.score( "How urgent is the request?", "Please refund my order.", List.of("routine", "urgent")).answer(); System.out.println(urgency); } }}Output
0.05
A refund request is routine, so it scores near 0. Levels run lowest first. score shows every answer.
rank: put messages in order
import dev.thinkthen.*;import java.util.List;import java.util.stream.Stream;
public class Rank { public static void main(String[] args) { List<String> messages = List.of( "Please refund my order.", "Where is my parcel?", "I was charged twice for one invoice."); try (var client = ThinkThen.builder().build(); Stream<Ranked<JsonValue>> byUrgency = client.rank( "How urgent is the request?", messages, List.of("routine", "urgent"))) { byUrgency.forEach(ranked -> { long rank = ranked.answer(); var text = ((JsonString) ranked.item()).value(); System.out.println(rank + " " + text); }); } }}Output
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, so it ranks 1. answer() is the rank, a Long. item() is a JsonValue copy of the message, so text comes back as a JsonString. position() is the message's place in your list, counted from 1. RankOptions.builder().top(2) ranks only the first two. rank shows every answer.
find: the line that answers
import dev.thinkthen.*;import java.util.Optional;
public class Find { public static void main(String[] args) { String message = "Morgan works for Example Group.\n" + "Please refund my order."; try (var client = ThinkThen.builder().build()) { Optional<Span> action = client.find( "Which line states the requested action?", message).answer(); System.out.println( action.map(Span::text).orElse("no line answers")); } }}Output
Please refund my order.
The second line holds the request. The answer is an Optional<Span>, empty when no line answers; a Span has text(), start() and end(), counted in characters. find shows every answer.
recognize
Section titled “recognize”recognize: names and their kinds
import dev.thinkthen.*;import java.util.List;
public class Recognize { public static void main(String[] args) { try (var client = ThinkThen.builder().build()) { List<Entity> names = client.recognize( "Morgan works for Example Group.", List.of("person", "organization")).answer(); for (Entity name : names) { System.out.println(name.mention().kind() + " " + name.mention().span().text()); } } }}Output
person Morgan
organization Example Group
Morgan is a person and Example Group an organization. Each Entity has a mention(), with its kind() and span(), and a probability(). recognize shows every answer.
relate
Section titled “relate”relate: how names connect
import dev.thinkthen.*;import java.util.List;
public class Relate { public static void main(String[] args) { String message = "Morgan works for Example Group.\n" + "Please refund my order."; var worksFor = RelationRule.of( "person", "organization", "works_for"); try (var client = ThinkThen.builder().build()) { List<Relation> relations = client.relate( message, List.of(worksFor)).answer(); for (Relation relation : relations) { System.out.println( relation.source().span().text() + " " + relation.relation() + " " + relation.target().span().text()); } } }}Output
Morgan works_for Example Group
Morgan works for Example Group, so relate links the two by the rule. relate finds the names itself, so it needs no recognize call first. RelationRule.builder(source, target, relation) adds reads(...) to word the relation and either(true) to make it work both ways. relate shows every answer.
annotate
Section titled “annotate”annotate: several questions at once
import dev.thinkthen.*;import java.util.List;
public class Annotate { public static void main(String[] args) { List<String> messages = List.of( "Please refund my order.", "Where is my parcel?", "I was charged twice for one invoice."); var questions = AnnotationQuestions.builder() .put("refund", DecideQuestion.of( "Does the customer request a refund?")) .put("topic", ChooseQuestion.of( "What is the message about?", List.of("billing", "shipping", "other"))) .put("urgency", ScoreQuestion.of( "How urgent is the request?", List.of("routine", "urgent"))) .build(); try (var client = ThinkThen.builder().build()) { for (String message : messages) { Annotations form = client.annotate(questions, message).answer(); System.out.println( form.decide("refund").answer() + " " + form.choose("topic").answer() + " " + form.score("urgency").answer()); } } }}Output
true billing 0.05
false shipping 0.14
true billing 0.45
Each message gets all three answers: refund or not, the topic, and its urgency. Each question's answer() throws on not sure or failure, as decide's does. form.decide("refund") throws ThinkThenException with the code usage.argument for a name you never added or a question of another function. annotate shows every answer.
The full API
Section titled “The full API”client.async() gives every one-message call as a CompletableFuture. The Java API reference lists every name, and its Javadoc ships in the sources JAR.