Skip to content

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.

Sources/Example/main.swift
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):

Terminal window
.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:

Package.swift
// swift-tools-version: 6.3
import 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.

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.

Sources/Example/main.swift
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().

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.

Yes or no about one message

message.txt holds Please refund my order.

Sources/Example/main.swift
import Foundation
import 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.

The one choice that fits

message.txt holds Please refund my order.

Sources/Example/main.swift
import Foundation
import 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.

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.

Sources/Example/main.swift
import Foundation
import 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.

Sources/Example/main.swift
import Foundation
import 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.

Sources/Example/main.swift
import Foundation
import 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.

Sources/Example/main.swift
import Foundation
import 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.

Sources/Example/main.swift
import Foundation
import 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.

Each name in the text and its kind

message.txt holds Morgan works for Example Group.

Sources/Example/main.swift
import Foundation
import 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.

Names linked by the relations you allow

message.txt holds the two lines from find.

Sources/Example/main.swift
import Foundation
import 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.

Several questions for each message

messages.txt holds the three messages from filter.

Sources/Example/main.swift
import Foundation
import 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.

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.