---
title: C# SDK
description: "C# SDK: ask the ten ThinkThen questions from .NET and get yes, no or not sure back as plain C# values."
goal: The reader runs a first decide in C#, installs the SDK, catches not sure and failures, and finds the C# call for each of the ten functions.
reader: A C# developer who wants to ask questions about text from code
kind: reference
verified: 2026-11-03
sidebar:
  label: "C#"
---

# C# SDK

The C# SDK gives your .NET code the ten functions as async methods on one client; each call gives back a plain C# value, such as `true` or `false` from decide, and throws when the answer is not sure.

```csharp
using ThinkThen;

await using var client = new ThinkThenClient();
bool isRefund = await client.DecideAsync(
    "Does the customer request a refund?",
    "Please refund my order.");
Console.WriteLine(isRefund);
```

Output

```text
True
```


`True` means yes: the message asks for a refund.

Install it (.NET 10 on Linux x64 or arm64 with glibc, macOS on Apple silicon, or Windows x64):

```sh
dotnet add package Botassembly.ThinkThen --version 0.2.0
```

Run it inside a console project made with `dotnet new console`, then `dotnet run`. The client uses the key from the [Quickstart](/quickstart/).

## Catch not sure and failures

Set a low bar and a high bar with `Threshold.Band`. Between them, decide is not sure, and reading the answer as a `bool` throws `UnsureException`. When ThinkThen can't judge the message, it throws `ThinkThenException` instead. Neither one can pass for `false`.

```csharp
using ThinkThen;

await using var client = new ThinkThenClient();
var bars = new DecideOptions
{
    Threshold = Threshold.Band(0.2, 0.8),
};
try
{
    bool isRefund = await client.DecideAsync(
        "Does the customer request a refund?",
        "I was charged twice for one invoice.", bars);
    Console.WriteLine(isRefund);
}
catch (UnsureException unsure)
{
    Console.WriteLine(
        $"Not sure ({unsure.Probability}): ask a person.");
}
catch (ThinkThenException failure)
{
    Console.WriteLine($"{failure.Code}: {failure.Message}");
}
```

Output

```text
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.

`UnsureException.Probability` is the chance of yes. `ThinkThenException.Code` names the error, such as `record.empty`; [Troubleshooting](/reference/troubleshooting/) lists each one. To check the outcome without an exception, keep the result as it comes back and test `IsUnsure` or `IsFailed`, or call `TryGetAnswer`.

## 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

<details class="example-disclosure">
<summary>Yes or no about one message</summary>

`message.txt` holds `Please refund my order.`

```csharp
using ThinkThen;

string message = File.ReadAllText("message.txt");
await using var client = new ThinkThenClient();

// Throws UnsureException when not sure,
// ThinkThenException when the message failed.
bool isRefund = await client.DecideAsync(
    "Does the customer request a refund?", message);
Console.WriteLine(isRefund);
```

Output

```text
True
```


Yes: the message asks for a refund. `DecideOptions` takes `Threshold` (one bar, or a low and a high bar with `Threshold.Band`), `YesMeans` and `NoMeans`. `DecideStreamAsync` takes many messages and gives one answer each, in order. [decide](/functions/decide/) shows every option.

</details>

### choose

<details class="example-disclosure">
<summary>The one choice that fits</summary>

`message.txt` holds `Please refund my order.`

```csharp
using ThinkThen;

string message = File.ReadAllText("message.txt");
await using var client = new ThinkThenClient();

string topic = await client.ChooseAsync(
    "What is the message about?", message,
    ["billing", "shipping", "other"]);
Console.WriteLine(topic);
```

Output

```text
billing
```


A refund is about billing. `ChooseOptions` takes `Descriptions`, a dictionary from each choice to what it means, and `Threshold`, which can make choose not sure. [choose](/functions/choose/) shows every option.

</details>

### filter

<details class="example-disclosure">
<summary>Only the messages that answer yes</summary>

`messages.txt` holds three lines: `Please refund my order.`, `Where is my parcel?` and `I was charged twice for one invoice.`

```csharp
using ThinkThen;

await using var client = new ThinkThenClient();

// Only the messages that answer yes.
// A failed message throws after the kept ones.
await foreach (string refund in client.FilterAsync(
    "Does the customer request a refund?", File.ReadLinesAsync("messages.txt")))
    Console.WriteLine(refund);
```

Output

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


The parcel question is left out. `FilterAsync` gives back your own messages, and leaves out the ones it is not sure about. If any message fails, it throws `ThinkThenException` after the kept ones, and its `FailedRows` lists every failure. `FilterStreamAsync` gives every message with its outcome instead. [filter](/functions/filter/) shows every option.

</details>

### tag

<details class="example-disclosure">
<summary>Every label that fits</summary>

`message.txt` holds `I was charged twice and my parcel is late again.`

```csharp
using ThinkThen;

string message = File.ReadAllText("message.txt");
await using var client = new ThinkThenClient();

ValueList<string> topics = await client.TagAsync(
    "Which topics appear?", message,
    ["billing", "shipping"]);
Console.WriteLine(topics);
```

Output

```text
[billing, shipping]
```


The message is about both billing and shipping. The answer is a `ValueList<string>`, empty when no label fits. [tag](/functions/tag/) shows every option.

</details>

### score

<details class="example-disclosure">
<summary>A number from 0 to 1 on your scale</summary>

`message.txt` holds `Please refund my order.`

```csharp
using ThinkThen;

string message = File.ReadAllText("message.txt");
await using var client = new ThinkThenClient();

double urgency = await client.ScoreAsync(
    "How urgent is the request?", message,
    ["routine", "urgent"]);
Console.WriteLine(urgency);
```

Output

```text
0.05
```


The levels run lowest first, so a plain refund request scores near routine. [score](/functions/score/) shows every option.

</details>

### rank

<details class="example-disclosure">
<summary>Every message, highest first</summary>

`messages.txt` holds the three messages from filter.

```csharp
using ThinkThen;

await using var client = new ThinkThenClient();

// Every message, most urgent first. Each answer is a rank, starting at 1.
await foreach (var ranked in client.RankAsync(
    "How urgent is the request?", File.ReadLinesAsync("messages.txt"),
    ["routine", "urgent"]))
{
    long rank = ranked.Row;
    Console.WriteLine($"{rank} {ranked.Item}");
}
```

Output

```text
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 `RankedItem` holds your message as `Item` and its rank as `Row`, starting at 1. `RankOptions` takes `Top` to keep only the first few. [rank](/functions/rank/) shows every option.

</details>

### find

<details class="example-disclosure">
<summary>The line that answers the question</summary>

`message.txt` holds two lines: `Morgan works for Example Group.` and `Please refund my order.`

```csharp
using ThinkThen;

string message = File.ReadAllText("message.txt");
await using var client = new ThinkThenClient();

// Null when no line answers the question.
TextSpan? action = await client.FindAsync(
    "Which line states the requested action?", message);
Console.WriteLine(action?.Text ?? "no line answers");
```

Output

```text
Please refund my order.
```


The second line states the action. The answer is a `TextSpan` with `Text`, `Start` and `End`, or `null` when no line answers. Offsets count UTF-16 units, as .NET strings do. [find](/functions/find/) shows every option.

</details>

### recognize

<details class="example-disclosure">
<summary>Each name in the text and its kind</summary>

`message.txt` holds `Morgan works for Example Group.`

```csharp
using ThinkThen;

string message = File.ReadAllText("message.txt");
await using var client = new ThinkThenClient();

ValueList<Entity> names = await client.RecognizeAsync(
    message, ["person", "organization"]);
foreach (Entity name in names)
    Console.WriteLine($"{name.Mention.Kind}: {name.Mention.Span.Text}");
```

Output

```text
person: Morgan
organization: Example Group
```


Each `Entity` holds a `Mention`, with the name's `Span` and `Kind`, and its `Probability`. [recognize](/functions/recognize/) shows every option.

</details>

### relate

<details class="example-disclosure">
<summary>Names linked by the relations you allow</summary>

`message.txt` holds the two lines from find.

```csharp
using ThinkThen;

string message = File.ReadAllText("message.txt");
await using var client = new ThinkThenClient();

var worksFor = new RelationRule
{
    Source = "person",
    Target = "organization",
    Name = "works_for",
};
ValueList<Relation> relations = await client.RelateAsync(message, [worksFor]);
foreach (Relation relation in relations)
    Console.WriteLine($"{relation.Source.Span.Text} --{relation.Name}--> "
        + $"{relation.Target.Span.Text}");
```

Output

```text
Morgan --works_for--> Example Group
```


A `RelationRule` names the kind of name at each end and the relation between them. [relate](/functions/relate/) shows every option.

</details>

### annotate

<details class="example-disclosure">
<summary>Several questions for each message</summary>

`messages.txt` holds the three messages from filter.

```csharp
using ThinkThen;

await using var client = new ThinkThenClient();
using var refundQuestion = DecideQuestion.Create(
    "Does the customer request a refund?");
using var topicQuestion = ChooseQuestion.Create("What is the message about?",
    ["billing", "shipping", "other"]);
using var urgencyQuestion = ScoreQuestion.Create("How urgent is the request?",
    ["routine", "urgent"]);
using var questions = AnnotationQuestions.Create(
    new Dictionary<string, AnnotationQuestion>
    {
        ["refund"] = new AnnotationQuestion.Decide(refundQuestion),
        ["topic"] = new AnnotationQuestion.Choose(topicQuestion),
        ["urgency"] = new AnnotationQuestion.Score(urgencyQuestion),
    });

await foreach (var form in client.AnnotateStreamAsync(
    questions, File.ReadLinesAsync("messages.txt")))
{
    // Each answer throws UnsureException when not sure, as a row does.
    ValueMap<string, AnnotationEntry> answers = form;
    bool isRefund = answers["refund"];
    string topic = answers["topic"];
    double urgency = answers["urgency"];
    Console.WriteLine($"{isRefund} {topic} {urgency}");
}
```

Output

```text
True billing 0.05
False shipping 0.14
True billing 0.45
```


Each answer is read by name and converts to its own type, and throws `UnsureException` when not sure, as decide does. For one message, call `AnnotateAsync(questions, message)`. [annotate](/functions/annotate/) shows every option.

</details>

## The full API

Each options type takes `Field`, such as `Pointer.From("/text")`, to judge one field of a structured message. Every call also takes a final `CancellationToken`, and cancelling it throws `OperationCanceledException`. In ASP.NET, `services.AddThinkThen(...)` registers one shared client. The [C# API reference](https://github.com/botassembly/thinkthen/blob/v0.2.0/sdk/csharp/API.md) lists every type and method.
