Swift SDK
The Swift SDK gives your Swift code the ten functions as async methods on one client; each call gives back a result, and its get() gives the plain Swift value, such as true or false from decide, or throws when the answer is not sure.
import ThinkThen
let client = try Client()let isRefund = try await client.decide( "Does the customer request a refund?", record: .text("Please refund my order.")).get()print(isRefund)Output
true
true means yes: the message asks for a refund.
Add it to your Package.swift dependencies (Swift 6.3 or later; macOS 15 or later; on Linux, glibc 2.35 or later):
.package(url: "https://github.com/botassembly/thinkthen", exact: "0.2.0")Then add the ThinkThen product to your target. This Package.swift builds the program above from Sources/Example with swift run:
// swift-tools-version: 6.3import PackageDescription
let package = Package( name: "Example", platforms: [.macOS("15.0")], dependencies: [ .package(url: "https://github.com/botassembly/thinkthen", exact: "0.2.0"), ], targets: [ .executableTarget( name: "Example", dependencies: [.product(name: "ThinkThen", package: "thinkthen")], path: "Sources/Example"), ])The client uses the key from the Quickstart.
Catch not sure and failures
Section titled “Catch not sure and failures”Set a low bar and a high bar with .band(low:high:). Between them, decide is not sure, and get() throws RowError.unsure. When ThinkThen can't judge the message, get() throws RowError.failed instead. Neither one can pass for false.
import ThinkThen
let client = try Client()let refundCheck = try await client.decide( "Does the customer request a refund?", record: .text("I was charged twice for one invoice."), options: .init(threshold: .band(low: 0.2, high: 0.8)))do { print(try refundCheck.get())} catch RowError.unsure(let probability) { print("Not sure (\(probability)): ask a person.")} catch RowError.failed(let failure) { print("\(failure.code.rawValue): \(failure.message)")}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.
RowError.unsure carries the chance of yes. RowError.failed carries a RowFailure, whose code names the error and whose message says what went wrong; Troubleshooting lists each code. A blank message or a question ThinkThen can't accept throws ThinkThenError from the call itself, before there is a result to read; its failure holds the code. To check the outcome without throwing, test the result's isUnsure or isFailed, or switch on result().
The ten functions
Section titled “The ten functions”Each function reads the same support-inbox messages as the function pages. Where a program reads message.txt or messages.txt, the text it holds is named above the program.
decide
Section titled “decide”Yes or no about one message
message.txt holds Please refund my order.
import Foundationimport ThinkThen
let message = try String(contentsOfFile: "message.txt", encoding: .utf8)let client = try Client()// get() throws RowError.unsure when not sure, RowError.failed on failure.let isRefund = try await client.decide( "Does the customer request a refund?", record: .text(message)).get()print(isRefund)Output
true
Yes: the message asks for a refund. decide also takes yesMeans: and noMeans:, and options: .init(threshold:) with one bar, such as 0.8, or a low and a high bar with .band(low:high:). Pass records: instead of record: to judge many messages; it gives one result each, in order. decide shows every option.
choose
Section titled “choose”The one choice that fits
message.txt holds Please refund my order.
import Foundationimport ThinkThen
let message = try String(contentsOfFile: "message.txt", encoding: .utf8)let client = try Client()let topic = try await client.choose( "What is the message about?", choices: ["billing", "shipping", "other"], record: .text(message)).get()print(topic)Output
billing
A refund is about billing. A choice can carry what it means, as Choice("billing", description: "..."), and options: .init(threshold:) can make choose not sure. choose shows every option.
filter
Section titled “filter”Only the messages that answer yes
messages.txt holds three lines: Please refund my order., Where is my parcel? and I was charged twice for one invoice.
import Foundationimport ThinkThen
let messages = try String(contentsOfFile: "messages.txt", encoding: .utf8) .split(separator: "\n").map(String.init)let client = try Client()// Only the messages that answer yes.// A failed message throws after the kept ones.for try await message in client.filter( "Does the customer request a refund?", records: messages) { print(message)}Output
Please refund my order.
I was charged twice for one invoice.
The parcel question is left out. filter gives back your own messages, in the type you passed in, and leaves out the ones it is not sure about. If any message fails, it throws one RowFailure after the kept ones, and its failedRows lists every failure. filterResults gives every message with its outcome and kept instead. filter shows every option.
Every label that fits
message.txt holds I was charged twice and my parcel is late again.
import Foundationimport ThinkThen
let message = try String(contentsOfFile: "message.txt", encoding: .utf8)let client = try Client()let topics = try await client.tag( "Which topics appear?", labels: ["billing", "shipping"], record: .text(message)).get()print(topics)Output
["billing", "shipping"]
The message is about both billing and shipping. The answer is a [String], empty when no label fits. tag shows every option.
A number from 0 to 1 on your scale
message.txt holds Please refund my order.
import Foundationimport ThinkThen
let message = try String(contentsOfFile: "message.txt", encoding: .utf8)let client = try Client()let urgency = try await client.score( "How urgent is the request?", levels: ["routine", "urgent"], record: .text(message)).get()print(urgency)Output
0.05
The levels run lowest first, so a plain refund request scores near routine. The answer is a Double. score shows every option.
Every message, highest first
messages.txt holds the three messages from filter.
import Foundationimport ThinkThen
let messages = try String(contentsOfFile: "messages.txt", encoding: .utf8) .split(separator: "\n").map(String.init)let client = try Client()// Every message, most urgent first. get() is its rank, starting at 1.for try await ranked in client.rank( "How urgent is the request?", levels: ["routine", "urgent"], records: messages) { print(try ranked.get(), messages[ranked.position - 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. Each RankResult gives its rank from get(), starting at 1, and keeps your message as item and its place in your input as position. RankOptions(top:) keeps only the first few. rank shows every option.
The line that answers the question
message.txt holds two lines: Morgan works for Example Group. and Please refund my order.
import Foundationimport ThinkThen
let message = try String(contentsOfFile: "message.txt", encoding: .utf8)let client = try Client()// nil when no line answers the question.let action = try await client.find( "Which line states the requested action?", record: .text(message)).get()print(action?.text ?? "no line answers")Output
Please refund my order.
The second line states the action. The answer is a TextSpan? with text, start and end, or nil when no line answers. Offsets count Unicode code points; range(in:) turns them into a Range<String.Index>. find shows every option.
recognize
Section titled “recognize”Each name in the text and its kind
message.txt holds Morgan works for Example Group.
import Foundationimport ThinkThen
let message = try String(contentsOfFile: "message.txt", encoding: .utf8)let client = try Client()let names = try await client.recognize( kinds: ["person", "organization"], record: .text(message)).get()for name in names { print("\(name.kind): \(name.text)")}Output
person: Morgan
organization: Example Group
Each RecognizedName holds the name's text, kind, start, end and probability. recognize shows every option.
relate
Section titled “relate”Names linked by the relations you allow
message.txt holds the two lines from find.
import Foundationimport ThinkThen
let message = try String(contentsOfFile: "message.txt", encoding: .utf8)let worksFor = RelationRule( source: "person", target: "organization", relation: "works_for")let client = try Client()let relations = try await client.relate( rules: [worksFor], record: .text(message)).get()for relation in relations { print("\(relation.source.text) --\(relation.relation)--> " + "\(relation.target.text)")}Output
Morgan --works_for--> Example Group
A RelationRule names the kind of name at each end and the relation between them; either: true lets it read in both directions. relate shows every option.
annotate
Section titled “annotate”Several questions for each message
messages.txt holds the three messages from filter.
import Foundationimport ThinkThen
let messages = try String(contentsOfFile: "messages.txt", encoding: .utf8) .split(separator: "\n").map(String.init)let questions = AnnotationQuestions([ "refund": .decide(DecideQuestion("Does the customer request a refund?")), "topic": .choose(ChooseQuestion( "What is the message about?", choices: ["billing", "shipping", "other"])), "urgency": .score(ScoreQuestion( "How urgent is the request?", levels: ["routine", "urgent"])),])let client = try Client()for try await annotated in client.annotate( questions, records: messages.map(Record.text)) { let triage = try annotated.get() let isRefund = try triage.get("refund", as: Bool.self) let topic = try triage.get("topic", as: String.self) let urgency = try triage.get("urgency", as: Double.self) print(isRefund, topic, urgency)}Output
true billing 0.05
false shipping 0.14
true billing 0.45
Each answer is read by name with get(_:as:), and throws RowError.unsure when not sure, as decide does. For one message, pass record: instead of records:. annotate shows every option.
The full API
Section titled “The full API”Each options type takes field:, a JSON Pointer such as "/text", to judge one field of a structured message. A message is .text(...), or .json(...) for a structured one. Cancelling the task that runs a call stops it and throws CancellationError. The Swift API source lists every type and method.