teren de test TypeSafe: docs offline + script de proba

Separat de produsele ROA. docs/ = documentatia oficiala descarcata ca Markdown
(111 pagini), reluabila cu update_docs.sh. typesafe_test.py face un apel cu cate
o intrebare din fiecare tip (choice/noul/score) pe o linie de factura de furnizor.
Cheia API se ia din TYPESAFE_API_KEY, nu se versioneaza.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KHLUSsKP99G6ebv2fFUKQV
This commit is contained in:
2026-09-17 21:47:19 +03:00
commit 2d012a969c
116 changed files with 25116 additions and 0 deletions

View File

@@ -0,0 +1,320 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.typesafe.ai/llms.txt
> Use this file to discover all available pages before exploring further.
# Composite scoring
> Break a complex judgment into atomic scores, combine with weights you control in code.
export function TypesafeExample({example, display, title}) {
const keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
function compressToEncodedURIComponent(input) {
if (input == null) return "";
return _compress(input, 6, function (a) {
return keyStrUriSafe.charAt(a);
});
}
function _compress(uncompressed, bitsPerChar, getCharFromInt) {
if (uncompressed == null) return "";
var i, value, context_dictionary = {}, context_dictionaryToCreate = {}, context_c = "", context_wc = "", context_w = "", context_enlargeIn = 2, context_dictSize = 3, context_numBits = 2, context_data = [], context_data_val = 0, context_data_position = 0, ii;
for (ii = 0; ii < uncompressed.length; ii += 1) {
context_c = uncompressed.charAt(ii);
if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
context_dictionary[context_c] = context_dictSize++;
context_dictionaryToCreate[context_c] = true;
}
context_wc = context_w + context_c;
if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
context_w = context_wc;
} else {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i = 0; i < 8; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i = 0; i < 16; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
context_dictionary[context_wc] = context_dictSize++;
context_w = String(context_c);
}
}
if (context_w !== "") {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i = 0; i < 8; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i = 0; i < 16; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
}
value = 2;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
while (true) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data.push(getCharFromInt(context_data_val));
break;
} else context_data_position++;
}
return context_data.join("");
}
function buildHref(ex) {
const documentText = ex.state === undefined ? "" : typeof ex.state === "string" ? ex.state : JSON.stringify(ex.state, null, 2);
return "https://console.typesafe.ai/decode#share/" + compressToEncodedURIComponent(JSON.stringify({
apiVersion: "v1",
documentText,
promptsText: JSON.stringify(ex.questions, null, 2),
selectedModels: ex.selectedModels
}));
}
const displayedExample = display === "questions" ? example.questions : example.state === undefined ? {
questions: example.questions
} : {
state: example.state,
questions: example.questions
};
const code = JSON.stringify(displayedExample, null, 2);
const href = buildHref(example);
return <div style={{
margin: "1.25rem 0"
}}>
<CodeBlock language="json" filename={title ?? "request"}>
{code}
</CodeBlock>
<div className="pb-8">
<a href={href} target="_blank" rel="noreferrer" className="text-primary">
Try it in the Playground →
</a>
</div>
</div>;
}
Oftentimes we want to rank a set of items based on several criteria at once. Composite scoring is an easy way to think about this: break the judgment into independent dimensions, score each one separately, and combine them with weights you control in code.
## Example: resume screening
Let's imagine you are processing resumes for engineering roles. You want to rank the candidates based on several criteria, and ultimately select the top X candidates for further review.
### Step 1: score each dimension independently
<TypesafeExample
title="questions"
display="questions"
example={{
questions: {
python_depth: {
type: 'score',
instructions:
'How much depth of python experience does this candidate have, based on the supplied resume?',
criteria: [
'No Python experience mentioned',
'Mentioned but no detail',
'Used in projects, some specifics',
'Primary language, multiple projects',
'Deep expertise: architecture, performance, libraries',
],
},
team_leadership: {
type: 'score',
instructions:
'How much experience does this candidate have managing or leading engineering teams?',
criteria: [
'No management experience mentioned',
'Informal mentorship or tech lead role',
'Led a small team or project',
'Managed a team with direct reports',
'Managed multiple teams or an engineering org',
],
},
system_design: {
type: 'score',
instructions:
'How much experience does this candidate have designing large-scale or distributed systems?',
criteria: [
'No architecture work mentioned',
'Contributed to design discussions',
'Designed components of a larger system',
'Owned architecture of a significant system',
'Designed systems at scale across multiple domains',
],
},
generalist: {
type: 'score',
instructions:
'How much evidence is there that this candidate picks up unfamiliar tools, roles, or domains outside their core specialty?',
criteria: [
'Only one domain or role mentioned',
'Some variety but within a narrow field',
'Worked across a few different areas or tech stacks',
'Regularly moved between domains, wore many hats',
'Track record of ramping up in unfamiliar areas and delivering',
],
},
},
}}
/>
### Step 2: combine with weights
Each dimension is normalized to 0–1 and weighted. The weights give you an easy way to adjust the relative importance of each dimension, without losing any of the nuance of the individual scores.
```python title="scoring.py" theme={null}
py = response.answers["python_depth"].score / 4
lead = response.answers["team_leadership"].score / 4
arch = response.answers["system_design"].score / 4
general = response.answers["generalist"].score / 4
# Senior IC
ic_score = (0.40 * py) + (0.10 * lead) + (0.40 * arch) + (0.10 * general)
# Engineering Manager
em_score = (0.15 * py) + (0.40 * lead) + (0.20 * arch) + (0.25 * general)
```
This gives you the ability to rank the candidates based on the composite score. But more importantly, it gives you visibility into how exactly the final score is being calculated. If the highest ranking candidates are not matching your expectations, you can adjust the weights to find the right balance.

View File

@@ -0,0 +1,291 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.typesafe.ai/llms.txt
> Use this file to discover all available pages before exploring further.
# Confidence-gated routing
> Use confidence as a second axis. The answer tells you what; confidence tells you whether to act.
export function TypesafeExample({example, display, title}) {
const keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
function compressToEncodedURIComponent(input) {
if (input == null) return "";
return _compress(input, 6, function (a) {
return keyStrUriSafe.charAt(a);
});
}
function _compress(uncompressed, bitsPerChar, getCharFromInt) {
if (uncompressed == null) return "";
var i, value, context_dictionary = {}, context_dictionaryToCreate = {}, context_c = "", context_wc = "", context_w = "", context_enlargeIn = 2, context_dictSize = 3, context_numBits = 2, context_data = [], context_data_val = 0, context_data_position = 0, ii;
for (ii = 0; ii < uncompressed.length; ii += 1) {
context_c = uncompressed.charAt(ii);
if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
context_dictionary[context_c] = context_dictSize++;
context_dictionaryToCreate[context_c] = true;
}
context_wc = context_w + context_c;
if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
context_w = context_wc;
} else {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i = 0; i < 8; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i = 0; i < 16; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
context_dictionary[context_wc] = context_dictSize++;
context_w = String(context_c);
}
}
if (context_w !== "") {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i = 0; i < 8; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i = 0; i < 16; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
}
value = 2;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
while (true) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data.push(getCharFromInt(context_data_val));
break;
} else context_data_position++;
}
return context_data.join("");
}
function buildHref(ex) {
const documentText = ex.state === undefined ? "" : typeof ex.state === "string" ? ex.state : JSON.stringify(ex.state, null, 2);
return "https://console.typesafe.ai/decode#share/" + compressToEncodedURIComponent(JSON.stringify({
apiVersion: "v1",
documentText,
promptsText: JSON.stringify(ex.questions, null, 2),
selectedModels: ex.selectedModels
}));
}
const displayedExample = display === "questions" ? example.questions : example.state === undefined ? {
questions: example.questions
} : {
state: example.state,
questions: example.questions
};
const code = JSON.stringify(displayedExample, null, 2);
const href = buildHref(example);
return <div style={{
margin: "1.25rem 0"
}}>
<CodeBlock language="json" filename={title ?? "request"}>
{code}
</CodeBlock>
<div className="pb-8">
<a href={href} target="_blank" rel="noreferrer" className="text-primary">
Try it in the Playground →
</a>
</div>
</div>;
}
One of TypeSafe's most powerful features is [confidence](/confidence). By being intentional with the way you gate decisions on confidence, you can build systems that are both reliable and safe.
## Example: voice banking commands
Let's imagine you are building a voice banking interface to allow the user to interact with their account verbally. While you always want to have reasonable confidence in interpreting the user's intent, some actions are riskier than others and thus demand a higher confidence threshold.
### Step 1: determine the user's intent
<TypesafeExample
title="questions"
display="questions"
example={{
questions: {
intent: {
type: 'choice',
instructions: 'What action is the user requesting?',
criteria: {
check_balance: 'Check the balance of an account',
approve_transfer: 'Approve the pending transfer request',
other: 'Something else',
},
},
},
}}
/>
### Step 2: confidence-gated routing
```python theme={null}
action = response.answers["intent"]
# Below 0.6 confidence on any action, route to a human
if action.confidence < 0.6:
route_to_support_agent(account_id)
elif action.choice == "check_balance":
# Low stakes. 0.6 confidence is sufficient.
show_balance(account_id)
elif action.choice == "approve_transfer":
if action.confidence > 0.85:
# High stakes, but high confidence. Safe to act automatically.
approve_transfer(account_id)
else:
# High stakes, moderate confidence. Verify intent first.
ask_user_to_confirm("Just to confirm: you would like to approve this transfer, is that correct?")
else:
route_to_support_agent(account_id)
```
The 0.6 floor catches anything the model is genuinely uncertain about. Above that floor, each action type has its own threshold based on the consequences of acting on a wrong classification. Checking a balance at 0.6 is fine because the worst case is the user having to listen to the balance read-out. But approving a transfer requires very high confidence (>0.85), otherwise the system should ask the user to confirm.
See [Confidence](/confidence) for more details on how to think about confidence in your systems.

328
docs/patterns/fan-out.md Normal file
View File

@@ -0,0 +1,328 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.typesafe.ai/llms.txt
> Use this file to discover all available pages before exploring further.
# Speculative fan-out
> Send many questions in a single call, including speculative ones, and let your code decide what's relevant.
export function TypesafeExample({example, display, title}) {
const keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
function compressToEncodedURIComponent(input) {
if (input == null) return "";
return _compress(input, 6, function (a) {
return keyStrUriSafe.charAt(a);
});
}
function _compress(uncompressed, bitsPerChar, getCharFromInt) {
if (uncompressed == null) return "";
var i, value, context_dictionary = {}, context_dictionaryToCreate = {}, context_c = "", context_wc = "", context_w = "", context_enlargeIn = 2, context_dictSize = 3, context_numBits = 2, context_data = [], context_data_val = 0, context_data_position = 0, ii;
for (ii = 0; ii < uncompressed.length; ii += 1) {
context_c = uncompressed.charAt(ii);
if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
context_dictionary[context_c] = context_dictSize++;
context_dictionaryToCreate[context_c] = true;
}
context_wc = context_w + context_c;
if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
context_w = context_wc;
} else {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i = 0; i < 8; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i = 0; i < 16; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
context_dictionary[context_wc] = context_dictSize++;
context_w = String(context_c);
}
}
if (context_w !== "") {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i = 0; i < 8; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i = 0; i < 16; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
}
value = 2;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
while (true) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data.push(getCharFromInt(context_data_val));
break;
} else context_data_position++;
}
return context_data.join("");
}
function buildHref(ex) {
const documentText = ex.state === undefined ? "" : typeof ex.state === "string" ? ex.state : JSON.stringify(ex.state, null, 2);
return "https://console.typesafe.ai/decode#share/" + compressToEncodedURIComponent(JSON.stringify({
apiVersion: "v1",
documentText,
promptsText: JSON.stringify(ex.questions, null, 2),
selectedModels: ex.selectedModels
}));
}
const displayedExample = display === "questions" ? example.questions : example.state === undefined ? {
questions: example.questions
} : {
state: example.state,
questions: example.questions
};
const code = JSON.stringify(displayedExample, null, 2);
const href = buildHref(example);
return <div style={{
margin: "1.25rem 0"
}}>
<CodeBlock language="json" filename={title ?? "request"}>
{code}
</CodeBlock>
<div className="pb-8">
<a href={href} target="_blank" rel="noreferrer" className="text-primary">
Try it in the Playground →
</a>
</div>
</div>;
}
Because TypeSafe supports sending many questions in a single API call, we recommend putting all of the questions your system needs in a single request, and then using code to decide what is relevant after the fact. All questions are evaluated in parallel, so adding more questions to a call typically doesn't add any latency to the response.
## Example: support ticket triage
Let's imagine you are building a support system that needs to triage support tickets. You need to classify the ticket into a category. If it's a bug report, you also need to determine the severity of the bug.
Instead of asking for the category first and then the severity in a follow-up call, you can ask for both at the same time. If the ticket is not a bug report, you simply ignore the results of the bug severity question.
### Step 1: speculative fan-out
<TypesafeExample
title="questions"
display="questions"
example={{
state:
"Hi, I placed an order (#98423) last Thursday and was charged twice. I also can't log in after the site update, and adding Apple Pay would be really helpful. This is getting frustrating.",
questions: {
category: {
type: 'choice',
instructions: 'Determine the broad category of this support ticket',
criteria: {
bug_report:
'The user is reporting something that is broken or producing errors',
billing: 'Charges, invoices, refunds, subscriptions',
feature_request: 'The user is requesting new functionality',
account: 'Login, permissions, profile, security',
},
},
bug_severity: {
type: 'score',
instructions: 'How severe is the reported issue',
criteria: [
'Cosmetic; no impact to functionality',
'Broken or degraded feature; workaround exists',
'Blocking issue; no workaround exists',
],
},
has_reproducible_steps: {
type: 'noul',
instructions:
'The user describes specific steps to reproduce the issue',
},
refund_requested: {
type: 'noul',
instructions: 'The user is explicitly asking for a refund or credit',
},
frustration: {
type: 'score',
instructions: 'How frustrated the user appears',
criteria: ['Calm, matter-of-fact', 'Frustrated but civil', 'Very angry'],
},
},
}}
/>
<Note>
**Speculative questions:** `bug_severity` and `has_reproducible_steps` only matter if the ticket is a bug report. `refund_requested` only matters for billing. We include all upfront because there is no speed cost for additional questions. If the ticket turns out to be a feature request, the bug severity result will be irrelevant, in which case your code path simply ignores it.
</Note>
### Step 2: route with code
Your code decides what is relevant based on the classification result:
```python title="triage.py" theme={null}
category = response.answers["category"]
bug_severity = response.answers["bug_severity"]
bug_repro = response.answers["has_reproducible_steps"]
refund = response.answers["refund_requested"]
frustration = response.answers["frustration"]
if category.choice == "bug_report":
if bug_severity.score > 1.5 and bug_repro.noul > 0.6:
escalate_to_engineering(ticket_id, severity="high")
else:
add_to_bug_backlog(ticket_id)
elif category.choice == "billing":
if refund.noul > 0.7:
route_to_billing_with_flag(ticket_id, refund_likely=True)
else:
route_to_billing(ticket_id)
elif category.choice == "feature_request":
log_feature_request(ticket_id)
# Frustration is useful regardless of category
if frustration.score > 1.5:
flag_for_priority_response(ticket_id)
```
Everything needed for the full decision tree comes from one call. Speculative questions are ignored when irrelevant and save a round trip when they are not.

View File

@@ -0,0 +1,306 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.typesafe.ai/llms.txt
> Use this file to discover all available pages before exploring further.
# Intent routing
> Classify incoming requests and route each to the optimal handler: deterministic logic, a specialist LLM, or a human.
export function TypesafeExample({example, display, title}) {
const keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$";
function compressToEncodedURIComponent(input) {
if (input == null) return "";
return _compress(input, 6, function (a) {
return keyStrUriSafe.charAt(a);
});
}
function _compress(uncompressed, bitsPerChar, getCharFromInt) {
if (uncompressed == null) return "";
var i, value, context_dictionary = {}, context_dictionaryToCreate = {}, context_c = "", context_wc = "", context_w = "", context_enlargeIn = 2, context_dictSize = 3, context_numBits = 2, context_data = [], context_data_val = 0, context_data_position = 0, ii;
for (ii = 0; ii < uncompressed.length; ii += 1) {
context_c = uncompressed.charAt(ii);
if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
context_dictionary[context_c] = context_dictSize++;
context_dictionaryToCreate[context_c] = true;
}
context_wc = context_w + context_c;
if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
context_w = context_wc;
} else {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i = 0; i < 8; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i = 0; i < 16; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
context_dictionary[context_wc] = context_dictSize++;
context_w = String(context_c);
}
}
if (context_w !== "") {
if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
if (context_w.charCodeAt(0) < 256) {
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
}
value = context_w.charCodeAt(0);
for (i = 0; i < 8; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
} else {
value = 1;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = 0;
}
value = context_w.charCodeAt(0);
for (i = 0; i < 16; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
delete context_dictionaryToCreate[context_w];
} else {
value = context_dictionary[context_w];
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
}
context_enlargeIn--;
if (context_enlargeIn == 0) {
context_enlargeIn = Math.pow(2, context_numBits);
context_numBits++;
}
}
value = 2;
for (i = 0; i < context_numBits; i++) {
context_data_val = context_data_val << 1 | value & 1;
if (context_data_position == bitsPerChar - 1) {
context_data_position = 0;
context_data.push(getCharFromInt(context_data_val));
context_data_val = 0;
} else {
context_data_position++;
}
value = value >> 1;
}
while (true) {
context_data_val = context_data_val << 1;
if (context_data_position == bitsPerChar - 1) {
context_data.push(getCharFromInt(context_data_val));
break;
} else context_data_position++;
}
return context_data.join("");
}
function buildHref(ex) {
const documentText = ex.state === undefined ? "" : typeof ex.state === "string" ? ex.state : JSON.stringify(ex.state, null, 2);
return "https://console.typesafe.ai/decode#share/" + compressToEncodedURIComponent(JSON.stringify({
apiVersion: "v1",
documentText,
promptsText: JSON.stringify(ex.questions, null, 2),
selectedModels: ex.selectedModels
}));
}
const displayedExample = display === "questions" ? example.questions : example.state === undefined ? {
questions: example.questions
} : {
state: example.state,
questions: example.questions
};
const code = JSON.stringify(displayedExample, null, 2);
const href = buildHref(example);
return <div style={{
margin: "1.25rem 0"
}}>
<CodeBlock language="json" filename={title ?? "request"}>
{code}
</CodeBlock>
<div className="pb-8">
<a href={href} target="_blank" rel="noreferrer" className="text-primary">
Try it in the Playground →
</a>
</div>
</div>;
}
Not every user request needs the same kind of handler. Some can be answered with a database lookup. Some need an LLM with domain-specific context. Some need a human. TypeSafe can sit in front of all of these as a fast, cheap classifier that determines which handler to invoke.
## Example: customer service routing
Let's imagine you are building a customer service system. Messages come in and need to be routed to the right handler. Rather than sending every message through an expensive LLM to figure out what kind of request it is, you classify first and route accordingly.
### Step 1: classify intent and complexity
<TypesafeExample
title="questions"
display="questions"
example={{
questions: {
intent: {
type: 'choice',
instructions: 'The primary intent of this customer message',
criteria: {
order_status: 'Asking about an existing order',
product_question: 'Asking about a product before buying',
return_exchange: 'Wants to return or exchange something',
complaint: 'Unhappy with experience, wants resolution',
},
},
complexity: {
type: 'score',
instructions: 'How complex is this request to resolve',
criteria: [
'Simple lookup or standard procedure',
'Requires some judgment or multi-step process',
'Unusual situation, edge case, or escalation needed',
],
},
},
}}
/>
### Step 2: route to the optimal handler
```python title="routing.py" theme={null}
def route_ticket(ticket_id, response):
intent = response.answers["intent"]
complexity = response.answers["complexity"]
if intent.confidence < 0.5:
# If we don't have enough confidence to classify, route to a human agent
return route_to_human_agent(ticket_id)
if intent.choice == "order_status":
handle_order_status(ticket_id)
elif intent.choice == "product_question":
handle_with_llm(ticket_id, PRODUCT_SPECIALIST)
elif intent.choice == "return_exchange":
handle_with_llm(ticket_id, RETURNS_SPECIALIST)
elif intent.choice == "complaint":
low_confidence = complexity.confidence < 0.5
# A higher complexity.score leans toward the "escalation needed" end of the scale.
if complexity.score > 1 or low_confidence:
# Too complex for safe automation, or we're not sure about the complexity; route to a human.
route_to_human_agent(ticket_id)
else:
handle_with_llm(ticket_id, COMPLAINT_RESOLUTION)
```
One intent routes to deterministic code with no LLM involved. Two route to different specialist LLMs, each loaded with different context. One uses the complexity score to decide between an LLM and a human. TypeSafe handles the classification all in a single quick call; the expensive resources only get invoked for the requests that actually need them.
Note the additional confidence check on the complexity score. As discussed in [Confidence](/confidence), it is always important to consider the meaning of a low confidence score in the context of the system and the stakes of the decision.