Go SDK
The Go SDK gives your code the ten functions as methods on a client; tt.Answer turns each result into a typed answer and an error, such as true or false from decide.
package main
import ( "context" "errors" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close()
refundQuestion := tt.NewDecide("Does the customer request a refund?") isRefund, err := tt.Answer(client.Decide(context.Background(), refundQuestion, tt.Text("Please refund my order."))) switch { case errors.Is(err, tt.ErrUnsure): fmt.Println("not sure: send to a person") case err != nil: log.Fatal(err) default: fmt.Println(isRefund) }}Output
true
Install it (Go 1.23 or later, with cgo and a C compiler):
go get github.com/botassembly/thinkthen/sdk/go/thinkthen@v0.2.0The client uses the key you set in the Quickstart. Call client.Close() when you're done with it.
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. tt.Answer then returns tt.ErrUnsure, and the answer beside it is the zero value, false. Check the error before the answer, or not sure reads as a no. This message lands between the bars:
package main
import ( "context" "errors" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background()
refundQuestion := tt.NewDecide( "Does the customer request a refund?", ).WithThreshold(tt.Band(0.2, 0.8)) message := tt.Text("I was charged twice for one invoice.") isRefund, err := tt.Answer( client.Decide(ctx, refundQuestion, message)) switch { case errors.Is(err, tt.ErrUnsure): fmt.Println("Not sure. Ask a person.") case err != nil: log.Fatal(err) case isRefund: fmt.Println("Send to refunds.") default: fmt.Println("Leave in the inbox.") }}Output
Not sure. Ask a person.
Send not sure to a person.
When a call fails
Section titled “When a call fails”Blank text fails at once, and nothing is sent. The error is a *tt.Error, and its Code() names the problem:
package main
import ( "context" "errors" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background()
refundQuestion := tt.NewDecide( "Does the customer request a refund?") _, err = tt.Answer( client.Decide(ctx, refundQuestion, tt.Text(""))) var failure *tt.Error if errors.As(err, &failure) { fmt.Println(failure.Code()) }}Output
record.empty
Any other failure, such as the service being down, comes back as a result whose Status() is tt.Failed, and tt.Answer returns its error. Match a family with errors.Is(err, tt.ErrRecord), or one code with errors.Is(err, tt.CodeRecordEmpty). A problem with the client itself, such as a wrong argument, comes back from tt.New or the call. 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. Each function has a one-message call, such as client.Decide, and an All call, such as client.DecideAll, that takes many messages and yields a result for each in a range loop. In an All loop, a failed message arrives as a result, and the loop's own error means the whole call failed.
decide
Section titled “decide”decide: yes, no or not sure
package main
import ( "context" "errors" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close()
refundQuestion := tt.NewDecide("Does the customer request a refund?") isRefund, err := tt.Answer(client.Decide(context.Background(), refundQuestion, tt.Text("Please refund my order."))) switch { case errors.Is(err, tt.ErrUnsure): fmt.Println("not sure: send to a person") case err != nil: log.Fatal(err) default: fmt.Println(isRefund) }}Output
true
The message asks for a refund, so decide says true. .WithThreshold(tt.Cutoff(0.8)) moves the bar, and tt.Band(0.2, 0.8) sets a low and a high bar, as above. .WithField("/text") picks the field to judge when a message is JSON. decide shows every answer.
choose
Section titled “choose”choose: one answer from your list
package main
import ( "context" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background()
topicQuestion := tt.NewChoose( "What is the message about?", []string{"billing", "shipping", "other"}) message := tt.Text("Please refund my order.") topic, err := tt.Answer( client.Choose(ctx, topicQuestion, message)) if err != nil { log.Fatal(err) } fmt.Println(topic)}Output
billing
A refund request is about billing. With .WithThreshold(tt.Cutoff(0.8)), choose is not sure when no choice reaches the bar, and tt.Answer returns tt.ErrUnsure with an empty string. client.ChooseAll reads many messages:
package main
import ( "context" "errors" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close()
topicQuestion := tt.NewChoose("What is the message about?", []string{"billing", "shipping", "other"}) inbox := tt.Texts( "Please refund my order.", "Where is my parcel?", "I was charged twice for one invoice.", ) // ChooseAll sends every message in one call and yields rows in input order. for message, err := range client.ChooseAll( context.Background(), topicQuestion, inbox, ) { if err != nil { log.Fatal(err) // the call failed, not one message } topic, err := message.Answer() switch { case errors.Is(err, tt.ErrUnsure): fmt.Println(message.Position(), "unsure: send to a person") case err != nil: // prints the status, code and reason fmt.Println(message.Position(), message) default: fmt.Println(message.Position(), topic) } }}Output
1 billing
2 shipping
3 billing
choose shows every answer.
filter
Section titled “filter”filter: keep the messages that answer yes
package main
import ( "context" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background()
refundQuestion := tt.NewFilter( "Does the customer request a refund?") messages := tt.Texts( "Please refund my order.", "Where is my parcel?", "I was charged twice for one invoice.", ) for kept, err := range client.FilterAll( ctx, refundQuestion, messages) { if err != nil { log.Fatal(err) } fmt.Println(kept.Item()) }}Output
Please refund my order.
I was charged twice for one invoice.
FilterAll yields the messages that answer yes: the refund request and the double charge. Item() is your own message. Not-sure messages are left out. A failed message arrives with its error in its place, and the loop may go on; this example stops on it. client.FilterRowsAll yields a result for every message instead, with Kept(). filter shows every answer.
tag: every label that fits
package main
import ( "context" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background()
topicsQuestion := tt.NewTag("Which topics appear?", []string{"billing", "shipping"}) message := tt.Text( "I was charged twice and my parcel is late again.") topics, err := tt.Answer( client.Tag(ctx, topicsQuestion, message)) if err != nil { log.Fatal(err) } fmt.Println(topics)}Output
[billing shipping]
The message names a double charge and a late parcel, so both labels fit. The answer is a []string, empty when none fits. tag shows every answer.
score: a number from 0 to 1
package main
import ( "context" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background()
urgencyQuestion := tt.NewScore( "How urgent is the request?", []string{"routine", "urgent"}) message := tt.Text("Please refund my order.") urgency, err := tt.Answer( client.Score(ctx, urgencyQuestion, message)) if err != nil { log.Fatal(err) } fmt.Println(urgency)}Output
0.05
A refund request is routine, so it scores near 0. The answer is a float64, and levels run lowest first. score shows every answer.
rank: put messages in order
package main
import ( "context" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background()
urgencyQuestion := tt.NewRank( "How urgent is the request?", []string{"routine", "urgent"}) messages := tt.Texts( "Please refund my order.", "Where is my parcel?", "I was charged twice for one invoice.", ) for ranked, err := range client.RankAll( ctx, urgencyQuestion, messages) { if err != nil { log.Fatal(err) } rank, err := ranked.Answer() if err != nil { log.Fatal(err) } fmt.Println(rank, ranked.Item()) }}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. The answer is an int, and Item() is the message it ranks. .WithTop(2) ranks only the first two. rank shows every answer.
find: the line that answers
package main
import ( "context" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background()
actionQuestion := tt.NewFind( "Which line states the requested action?") message := tt.Text("Morgan works for Example Group.\n" + "Please refund my order.") action, err := tt.Answer( client.Find(ctx, actionQuestion, message)) if err != nil { log.Fatal(err) } if action == nil { fmt.Println("No line answers.") } else { fmt.Println(action.Text) }}Output
Please refund my order.
The second line holds the request. The answer is a *tt.Span with Text, Start and End, counted in bytes, and it is nil when no line answers. find shows every answer.
recognize
Section titled “recognize”recognize: names and their kinds
package main
import ( "context" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background()
kinds := tt.NewRecognize( []string{"person", "organization"}) message := tt.Text("Morgan works for Example Group.") names, err := tt.Answer( client.Recognize(ctx, kinds, message)) if err != nil { log.Fatal(err) } for _, name := range names { fmt.Println(name.Kind, name.Text) }}Output
person Morgan
organization Example Group
Morgan is a person and Example Group an organization. Each tt.Name also has Start, End and Probability. recognize shows every answer.
relate
Section titled “relate”relate: how names connect
package main
import ( "context" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background()
worksFor := tt.NewRelate([]tt.Rule{{ Name: "works_for", Source: "person", Target: "organization", }}) message := tt.Text("Morgan works for Example Group.\n" + "Please refund my order.") relations, err := tt.Answer( client.Relate(ctx, worksFor, message)) if err != nil { log.Fatal(err) } for _, r := range relations { fmt.Println(r.Source.Text, r.Label, r.Target.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. Each tt.Relation also has Probability. relate shows every answer.
annotate
Section titled “annotate”annotate: several questions at once
package main
import ( "context" "fmt" "log"
tt "github.com/botassembly/thinkthen/sdk/go/thinkthen")
func main() { client, err := tt.New(tt.Config{}) if err != nil { log.Fatal(err) } defer client.Close() ctx := context.Background()
form := tt.NewAnnotate(map[string]tt.AnnotationQuestion{ "refund": tt.NewDecide( "Does the customer request a refund?"), "topic": tt.NewChoose("What is the message about?", []string{"billing", "shipping", "other"}), "urgency": tt.NewScore("How urgent is the request?", []string{"routine", "urgent"}), }) messages := tt.Texts( "Please refund my order.", "Where is my parcel?", "I was charged twice for one invoice.", ) for row, err := range client.AnnotateAll( ctx, form, messages) { if err != nil { log.Fatal(err) } answers, err := row.Answer() if err != nil { log.Fatal(err) } isRefund, err := tt.Answer(answers.Decide("refund")) if err != nil { log.Fatal(err) } topic, err := tt.Answer(answers.Choose("topic")) if err != nil { log.Fatal(err) } urgency, err := tt.Answer(answers.Score("urgency")) if err != nil { log.Fatal(err) } fmt.Println(isRefund, topic, urgency) }}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. answers.Decide("refund") and the other selectors pick one answer, and tt.Answer reads it as a one-question call does, so a not-sure answer inside returns tt.ErrUnsure too. client.Annotate answers one message. annotate shows every answer.
The full API
Section titled “The full API”tt.Config sets the batch size and retries, and tt.JSON and tt.Records build messages with ids. The Go source lists every name.