Rust SDK
The Rust SDK gives your Rust code the ten functions as methods on one engine; each call gives back a plain Rust value, such as true or false from decide, or an Err when the answer is not sure or ThinkThen can't judge the text.
use thinkthen::{Decide, Engine};
fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = Engine::from_env()?; let refund = Decide::new("Does the customer request a refund?")?; let is_refund: bool = engine .run(&refund, "Please refund my order.")? .into_answer()?; println!("{is_refund}"); Ok(())}Output
true
true means yes: the message asks for a refund.
Add the crate (Rust 1.95.0 or later):
cargo add thinkthen@0.2.0 --no-default-featuresThe engine is inside the crate, so cargo run is all it needs. Engine::from_env() uses the key from the Quickstart.
Handle not sure and failures
Section titled “Handle not sure and failures”Set a low bar and a high bar with Threshold::band. Between them, decide is not sure, and into_answer() returns Err(NoAnswer::Unsure) with the probability. When ThinkThen can't judge the message, it returns Err(NoAnswer::Failed) with the error instead. Neither one can pass for false.
use thinkthen::{Decide, Engine, NoAnswer, Threshold};
fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = Engine::from_env()?; let refund = Decide::new("Does the customer request a refund?")? .threshold(Threshold::band(0.2, 0.8)?); let message = "I was charged twice for one invoice."; match engine.run(&refund, message)?.into_answer() { Ok(is_refund) => println!("{is_refund}"), Err(NoAnswer::Unsure { probability }) => println!( "Not sure ({}): ask a person.", probability.get() ), Err(NoAnswer::Failed(error)) => { println!("{}: {error}", error.code()) } } Ok(())}Output
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.
probability.get() is the chance of yes. error.code() names the error, such as record.missing; Troubleshooting lists each one. With ?, not sure and a failure both stop the function. answer() borrows the answer instead of taking it.
run itself returns Err only when the call can't go ahead, such as blank text (record.empty) or a cancelled call.
The ten functions
Section titled “The ten functions”Each function reads the same support-inbox messages as the function pages.
decide
Section titled “decide”Yes or no about one message
use thinkthen::{Decide, Engine};
fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = Engine::from_env()?; let refund = Decide::new("Does the customer request a refund?")?; // into_answer() fails on not sure or a failed record, so neither // reads as false. let is_refund: bool = engine .run(&refund, "Please refund my order.")? .into_answer()?; println!("{is_refund}"); Ok(())}Output
true
Yes: the message asks for a refund. Decide takes .threshold(...) (one bar with Threshold::cut, or a low and a high bar with Threshold::band), .yes_means(...) and .no_means(...). decide shows every option.
choose
Section titled “choose”The one choice that fits
use thinkthen::{Choose, Engine};
fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = Engine::from_env()?; let about = Choose::new( "What is the message about?", ["billing", "shipping", "other"], )?; let topic: String = engine .run(&about, "Please refund my order.")? .into_answer()?; println!("{topic}"); Ok(())}Output
billing
A refund is about billing. Choose takes .descriptions(...), each choice paired with what it means, and .threshold(...), which can make choose not sure. choose shows every option.
filter
Section titled “filter”Only the messages that answer yes
use thinkthen::{Engine, Filter};
fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = Engine::from_env()?; let messages = vec![ "Please refund my order.", "Where is my parcel?", "I was charged twice for one invoice.", ]; let refund = Filter::new("Does the customer request a refund?")?; // Only the messages that answer yes. // A failed message arrives as an Err in its place. for refund_request in engine.filter(&refund, messages, |message| *message)? { println!("{}", refund_request?); } Ok(())}Output
Please refund my order.
I was charged twice for one invoice.
The parcel question is left out. engine.filter gives back your own items, and leaves out the ones it is not sure about. The closure picks the text to judge from each item. A message that fails arrives as an Err in its place, holding the item, and reading goes on. engine.filter_items gives every item with its outcome instead. filter shows every option.
Every label that fits
use thinkthen::{Engine, Tag};
fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = Engine::from_env()?; let appear = Tag::new("Which topics appear?", ["billing", "shipping"])?; let topics: Vec<String> = engine .run(&appear, "I was charged twice and my parcel is late again.")? .into_answer()?; println!("{topics:?}"); Ok(())}Output
["billing", "shipping"]
The message is about both billing and shipping. The answer is a Vec<String>, empty when no label fits. tag shows every option.
A number from 0 to 1 on your scale
use thinkthen::{Engine, Score};
fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = Engine::from_env()?; let how_urgent = Score::new("How urgent is the request?", ["routine", "urgent"])?; // 0 is routine, 1 is urgent. let urgency: f64 = engine .run(&how_urgent, "Please refund my order.")? .into_answer()?; println!("{urgency}"); Ok(())}Output
0.05
The levels run lowest first, so a plain refund request scores near routine. score shows every option.
Every message, highest first
use thinkthen::{Engine, Rank};
fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = Engine::from_env()?; let messages = vec![ "Please refund my order.", "Where is my parcel?", "I was charged twice for one invoice.", ]; let by_urgency = Rank::new("How urgent is the request?", ["routine", "urgent"])?; // Rank 1 is the most urgent. for ranked in engine.rows(&by_urgency, messages)? { let ranked = ranked?; let rank = ranked.answer()?; let (_, message) = ranked .record() .expect("a rank row keeps its record") .original(); println!("{rank} {}", String::from_utf8_lossy(message)); } Ok(())}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. Each answer is a NonZeroUsize rank, starting at 1, and record() holds the message it belongs to. Rank takes .top(...) to keep only the first few. rank shows every option.
The line that answers the question
use thinkthen::{Engine, Find};
fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = Engine::from_env()?; let message = "Morgan works for Example Group.\nPlease refund my order."; let requested = Find::new("Which line states the requested action?")?; // None when no line answers the question. match engine.run(&requested, message)?.into_answer()? { Some(action) => println!("{}", action.text()), None => println!("no line answers"), } Ok(())}Output
Please refund my order.
The second line states the action. The answer is Some(Found), with text(), start() and end(), or None when no line answers. Offsets count UTF-8 bytes, as Rust strings do, so &message[action.start()..action.end()] is the same text. find shows every option.
recognize
Section titled “recognize”Each name in the text and its kind
use thinkthen::{Engine, Recognize};
fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = Engine::from_env()?; let people_and_groups = Recognize::new(["person", "organization"])?; let names = engine .run(&people_and_groups, "Morgan works for Example Group.")? .into_answer()?; for name in &names { println!("{}: {}", name.kind(), name.text()); } Ok(())}Output
person: Morgan
organization: Example Group
Each Name has text(), kind(), start(), end() and probability(). recognize shows every option.
relate
Section titled “relate”Names linked by the relations you allow
use thinkthen::{Engine, Relate, RelationRule};
fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = Engine::from_env()?; let message = "Morgan works for Example Group.\nPlease refund my order."; let works_for = Relate::new([RelationRule::new( "works_for", "person", "organization", )?])?; let relations = engine.run(&works_for, message)?.into_answer()?; for relation in &relations { println!( "{} --{}--> {}", relation.source().text(), relation.relation(), relation.target().text() ); } Ok(())}Output
Morgan --works_for--> Example Group
RelationRule::new takes the relation, then the kind of name at each end. .reads(...) words the relation, and .either(true) makes it work in both directions. relate shows every option.
annotate
Section titled “annotate”Several questions for each message
use thinkthen::{AnnotatableQuestion, Annotate, Choose, Decide, Engine, Score};
fn main() -> Result<(), Box<dyn std::error::Error>> { let engine = Engine::from_env()?; let messages = vec![ "Please refund my order.", "Where is my parcel?", "I was charged twice for one invoice.", ]; let triage = Annotate::new([ ( "refund", AnnotatableQuestion::from(Decide::new( "Does the customer request a refund?", )?), ), ( "topic", Choose::new( "What is the message about?", ["billing", "shipping", "other"], )? .into(), ), ( "urgency", Score::new("How urgent is the request?", ["routine", "urgent"])? .into(), ), ])?; for annotated in engine.rows(&triage, messages)? { // Each answer prints bare, or as unsure or failed with its code. let answers = annotated?.into_answer()?; println!( "{} {} {}", answers["refund"], answers["topic"], answers["urgency"] ); } Ok(())}Output
true billing 0.05
false shipping 0.14
true billing 0.45
Each answer is read by name. It prints as its answer, or as unsure or failed: with the error code; answers.answer::<Decide>("refund") takes it as a Result, as decide does. annotate shows every option.
The full API
Section titled “The full API”engine.rows(&question, messages) takes any owned iterator and gives one result per message, in order. With the stream feature, rows.into_stream() is a Stream for async code; the crate brings no async runtime. Each question takes .field("/text".parse()?) to judge one field of a JSON message. Dropping the results, or calling cancel(), stops the call. The Rust API reference lists every type and method.