Skip to content

C++ SDK

The C++ SDK gives your C++17 program the ten functions as methods on one client; each call gives back a result whose value() is a plain C++ value, such as true or false from decide, and throws when the answer is not sure or failed.

example.cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>
int main() try {
thinkthen::client client;
const thinkthen::decide refund{"Does the customer request a refund?"};
// value() throws when not sure or failed, so neither reads as false.
const bool is_refund =
client.decide(refund, "Please refund my order.").value();
std::cout << std::boolalpha << is_refund << '\n';
} catch (const thinkthen::unsure&) {
std::cerr << "not sure: a person should read it\n";
return 1;
} catch (const thinkthen::error& failure) {
std::cerr << failure.code().message() << ": " << failure.what() << '\n';
return 1;
}

Output

true

true means yes: the message asks for a refund.

It needs a C++17 compiler. Extract the C++ and C archives for your platform from the 0.2.0 release into one thinkthen folder, save the program as example.cpp, then compile it and run ./example:

Terminal window
c++ -std=c++17 -Wall -Wextra -Werror example.cpp -o example \
$(PKG_CONFIG_PATH="$PWD/thinkthen/lib/pkgconfig" \
pkg-config --cflags --libs thinkthen-c) \
-Wl,-rpath,"$PWD/thinkthen/lib"

With CMake, use find_package(ThinkThenCpp CONFIG REQUIRED) and link ThinkThen::Cpp. The client uses the key from the Quickstart.

Set a low bar and a high bar with thinkthen::threshold::band. Between them, decide is not sure, and value() throws thinkthen::unsure. When ThinkThen can't judge the message, value() throws thinkthen::error instead. Neither one can pass for false.

example.cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>
int main() try {
thinkthen::client client;
// No below 0.2, yes at 0.8 or above, not sure between.
const auto refund =
thinkthen::decide{"Does the customer request a refund?"}
.threshold(thinkthen::threshold::band(0.2, 0.8));
const auto answer =
client.decide(refund, "I was charged twice for one invoice.");
// value() throws thinkthen::unsure here, so it never reads as false.
std::cout << std::boolalpha << answer.value() << '\n';
} catch (const thinkthen::unsure& not_sure) {
std::cout << "not sure: ask a person";
if (const auto probability = not_sure.probability())
std::cout << " (probability " << *probability << ')';
std::cout << '\n';
} catch (const thinkthen::error& failure) {
std::cerr << failure.code().message() << ": " << failure.what()
<< '\n';
return 1;
}

Output

not sure: ask a person (probability 0.7)

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

thinkthen::unsure derives from std::exception, not from thinkthen::error, so a handler for errors never catches a not-sure answer. Its probability() is the chance of yes, when the model gave one.

thinkthen::error derives from std::system_error. code().message() names the error, such as record.empty, and what() says what went wrong; Troubleshooting lists each one. Blank text throws thinkthen::error from the call itself, before any request is sent. A refused question throws it when you build the question.

To check the outcome without an exception, test unsure() or failed(), or call answer(), which gives a pointer that is null when there is no answer. A decide or filter result also tests as its answer in an if, and throws the same way.

Each function reads the same support-inbox messages as the function pages. Each program is a whole file that compiles with the line above.

Yes or no about one message
example.cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>
int main() try {
thinkthen::client client;
const thinkthen::decide refund{"Does the customer request a refund?"};
// value() throws when not sure or failed, so neither reads as false.
const bool is_refund =
client.decide(refund, "Please refund my order.").value();
std::cout << std::boolalpha << is_refund << '\n';
} catch (const thinkthen::unsure&) {
std::cerr << "not sure: a person should read it\n";
return 1;
} catch (const thinkthen::error& failure) {
std::cerr << failure.code().message() << ": " << failure.what() << '\n';
return 1;
}

Output

true

Yes: the message asks for a refund. A thinkthen::decide question takes .threshold(0.7) for one bar, .threshold(thinkthen::threshold::band(0.2, 0.8)) for a low and a high bar, and .yes_means(...) and .no_means(...). client.decide_each takes many messages and gives one result each, in order. decide shows every option.

The one choice that fits
example.cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>
int main() try {
thinkthen::client client;
const thinkthen::choose about{"What is the message about?",
{"billing", "shipping", "other"}};
const std::string topic =
client.choose(about, "Please refund my order.").value();
std::cout << topic << '\n';
} catch (const thinkthen::unsure&) {
std::cerr << "not sure: a person should read it\n";
return 1;
} catch (const thinkthen::error& failure) {
std::cerr << failure.code().message() << ": " << failure.what() << '\n';
return 1;
}

Output

billing

A refund is about billing. A thinkthen::choose question takes .descriptions(...), a map from each choice to what it means, and .threshold(...), which can make choose not sure. choose shows every option.

Only the messages that answer yes
example.cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>
int main() try {
thinkthen::client client;
const thinkthen::filter refund{"Does the customer request a refund?"};
const std::vector<std::string> messages{
"Please refund my order.",
"Where is my parcel?",
"I was charged twice for one invoice.",
};
// Only the messages that answer yes. Failed messages are reported
// together after the last kept one.
for (const auto& refund_request : client.filter_each(refund, messages))
std::cout << std::get<thinkthen::owned_text>(refund_request).value
<< '\n';
} catch (const thinkthen::error& failure) {
std::cerr << failure.code().message() << ": " << failure.what() << '\n';
for (const auto& failed : failure.failed_rows())
std::cerr << "message " << failed.position << ": " << failed.message
<< '\n';
return 1;
}

Output

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

The parcel question is left out. filter_each gives back your own messages, each as the thinkthen::owned_record you passed in, and leaves out the ones it is not sure about. A failed message does not stop it: it gives back every kept message, then throws one thinkthen::error whose failed_rows() lists each failed message with its position, code and message. filter_rows_each gives every message with its outcome instead, where kept() is true only for a yes. filter shows every option.

Every label that fits
example.cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>
int main() try {
thinkthen::client client;
const thinkthen::tag appear{"Which topics appear?",
{"billing", "shipping"}};
// Every label that fits, possibly none.
const std::vector<std::string> topics =
client.tag(appear, "I was charged twice and my parcel is late again.")
.value();
for (const auto& topic : topics) std::cout << topic << '\n';
} catch (const thinkthen::error& failure) {
std::cerr << failure.code().message() << ": " << failure.what() << '\n';
return 1;
}

Output

billing
shipping

The message is about both billing and shipping. The answer is a std::vector<std::string>, empty when no label fits. tag shows every option.

A number from 0 to 1 on your scale
example.cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>
int main() try {
thinkthen::client client;
const thinkthen::score how_urgent{"How urgent is the request?",
{"routine", "urgent"}};
// 0 is routine, 1 is urgent.
const double urgency =
client.score(how_urgent, "Please refund my order.").value();
std::cout << urgency << '\n';
} catch (const thinkthen::error& failure) {
std::cerr << failure.code().message() << ": " << failure.what() << '\n';
return 1;
}

Output

0.05

The levels run lowest first, so a plain refund request scores near routine. score shows every option.

Every message, highest first
example.cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>
int main() try {
thinkthen::client client;
const thinkthen::rank by_urgency{"How urgent is the request?",
{"routine", "urgent"}};
const std::vector<std::string> messages{
"Please refund my order.",
"Where is my parcel?",
"I was charged twice for one invoice.",
};
// Rank 1 is the most urgent. item() is the message that rank belongs to.
for (const auto& ranked : client.rank_each(by_urgency, messages))
std::cout << ranked.value() << ' '
<< std::get<thinkthen::owned_text>(ranked.item()).value
<< '\n';
} catch (const thinkthen::error& failure) {
std::cerr << failure.code().message() << ": " << failure.what() << '\n';
return 1;
}

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. value() is the rank, starting at 1, and item() is the message you passed in. A thinkthen::rank question takes .top(n) to keep only the first few. rank shows every option.

The line that answers the question
example.cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>
int main() try {
thinkthen::client client;
const thinkthen::find requested{"Which line states the requested action?"};
const std::optional<thinkthen::span> action =
client
.find(requested,
"Morgan works for Example Group.\nPlease refund my order.")
.value();
// Empty when no line answers the question.
if (action) std::cout << action->text << '\n';
else std::cout << "no line answers\n";
} catch (const thinkthen::unsure&) {
std::cerr << "not sure: a person should read it\n";
return 1;
} catch (const thinkthen::error& failure) {
std::cerr << failure.code().message() << ": " << failure.what() << '\n';
return 1;
}

Output

Please refund my order.

The second line states the action. The answer is a std::optional<thinkthen::span>, with the line's text and its offsets in UTF-8 bytes, and it is empty when no line answers. find shows every option.

Each name in the text and its kind
example.cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>
int main() try {
thinkthen::client client;
const thinkthen::recognize people_and_groups{
std::vector<std::string>{"person", "organization"}};
const std::vector<thinkthen::name> names =
client.recognize(people_and_groups, "Morgan works for Example Group.")
.value();
for (const auto& name : names)
std::cout << name.kind << ": " << name.text << '\n';
} catch (const thinkthen::error& failure) {
std::cerr << failure.code().message() << ": " << failure.what() << '\n';
return 1;
}

Output

person: Morgan
organization: Example Group

Each thinkthen::name holds the name's text, its kind and its offsets in UTF-8 bytes. recognize shows every option.

Names linked by the relations you allow
example.cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>
int main() try {
thinkthen::client client;
const thinkthen::relate works_for{
std::vector<thinkthen::rule>{{"works_for", "person", "organization"}}};
const std::vector<thinkthen::relation> relations =
client
.relate(works_for,
"Morgan works for Example Group.\nPlease refund my order.")
.value();
for (const auto& relation : relations)
std::cout << relation.source.text << " --" << relation.label << "--> "
<< relation.target.text << '\n';
} catch (const thinkthen::error& failure) {
std::cerr << failure.code().message() << ": " << failure.what() << '\n';
return 1;
}

Output

Morgan --works_for--> Example Group

A thinkthen::rule names the relation, then the kind of name at each end. It also takes a description and whether the relation runs both ways. relate shows every option.

Several questions for each message
example.cpp
#include <iostream>
#include <thinkthen/thinkthen.hpp>
int main() try {
thinkthen::client client;
const thinkthen::annotate triage_form{thinkthen::annotation_questions{
{"refund", thinkthen::decide{"Does the customer request a refund?"}},
{"topic", thinkthen::choose{"What is the message about?",
{"billing", "shipping", "other"}}},
{"urgency", thinkthen::score{"How urgent is the request?",
{"routine", "urgent"}}},
}};
const std::vector<std::string> messages{
"Please refund my order.",
"Where is my parcel?",
"I was charged twice for one invoice.",
};
for (const auto& triage : client.annotate_each(triage_form, messages)) {
// Each answer prints bare, or as its not-sure or failed status.
const thinkthen::annotations& answers = triage.value();
std::cout << thinkthen::get<thinkthen::decide>(answers, "refund")
<< ' ' << thinkthen::get<thinkthen::choose>(answers, "topic")
<< ' '
<< thinkthen::get<thinkthen::score>(answers, "urgency")
<< '\n';
}
} catch (const thinkthen::error& failure) {
std::cerr << failure.code().message() << ": " << failure.what() << '\n';
return 1;
}

Output

true billing 0.05
false shipping 0.14
true billing 0.45

thinkthen::get<thinkthen::decide>(answers, "refund") reads one answer by name, as the question type it was built with. Each answer has its own value(), which throws when not sure, as decide does. Printed directly, it shows unsure or failed: CODE instead of an answer. For one message, call client.annotate(question, message). annotate shows every option.

Every question takes .field("/text") to judge one field of a JSON message, passed as thinkthen::json_text. Each _each call also takes a thinkthen::record_source, a function that returns the next message or std::nullopt after the last. A stream is read once; leaving the loop or calling close() stops it. The headers in include/thinkthen/ declare every type and method.