Dokumentation

Noul

Eine Noul-Frage bittet das TypeSafe-Modell, eine Ja/Nein-Frage auszuwerten und die Wahrscheinlichkeit zurückzugeben, dass die Antwort Ja lautet.

Verwende einen Noul, wenn die Antwort Ja oder Nein ist. Zum Beispiel, ob diese Nachricht nach einer Erstattung fragt, ob dieser Lebenslauf verteilte Systeme erwähnt, ob dieser Kommentar personenbezogene Daten enthält. Wenn die Antwort eine von mehreren Optionen ist, verwende einen Choice. Ist sie eine Position auf einem Spektrum, verwende einen Score. Einen Fragetyp wählen vergleicht alle drei.

Eine Noul-Antwort ist eine einzelne Zahl, die die Wahrscheinlichkeit darstellt, dass die Antwort Ja lautet, wobei 0 Nein und 1 Ja bedeutet.

Anfragestruktur

Der POST-Anfragekörper an die TypeSafe-API hat dieselben drei Felder auf oberster Ebene wie jeder andere Fragetyp: state, den auszuwertenden Inhalt; model; und questions. Jede Noul-Frage hat die folgenden Felder:

  • type: Immer "noul".
  • instructions: Die Ja/Nein-Frage, die das Modell beantwortet, oder eine Aussage, die es beurteilen soll.
  • criteria: Optional. Ein Objekt mit true- und false-Beschreibungen dessen, was ein Ja und ein Nein bedeuten.

Unten steht eine Anfrage, bei der der Zustand eine Support-Nachricht ist und die beiden Fragen lauten, ob der Kunde einen Menschen will und ob er den Support schon einmal kontaktiert hat:

request
{
  "state": "I have asked three times now. Can I please just talk to a real person?",
  "questions": {
    "is_human_escalation": {
      "type": "noul",
      "instructions": "Is the customer asking for a human agent?"
    },
    "is_repeat_contact": {
      "type": "noul",
      "instructions": "Has the customer contacted support about this before?",
      "criteria": {
        "true": "Mentions a prior attempt, ticket, or that they have asked before",
        "false": "No sign of any previous contact"
      }
    }
  }
}

Du wählst die Frage-IDs, hier is_human_escalation und is_repeat_contact. Die IDs werden nicht an das Modell gesendet. Jede Antwort wird unter derselben ID zurückgegeben. Die erste Frage stützt sich allein auf instructions. Die zweite fügt criteria hinzu, um zu sagen, was als Ja und was als Nein zählt.

Mit dem Python-SDK sind dieselben Fragen Noul-Objekte:

from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient

with TypeSafeClient() as client:
    response = client.system_one(
        model="jev-latest",
        state="I have asked three times now. Can I please just talk to a real person?",
        questions={
            "is_human_escalation": Noul(
                instructions="Is the customer asking for a human agent?",
            ),
            "is_repeat_contact": Noul(
                instructions="Has the customer contacted support about this before?",
                criteria=NoulCriteria(
                    true="Mentions a prior attempt, ticket, or that they have asked before",
                    false="No sign of any previous contact",
                ),
            ),
        },
    )

    print(response.answers["is_human_escalation"].noul)
    print(response.answers["is_repeat_contact"].noul)

Die Methode system_one und der Endpunkt https://api.typesafe.ai/v1/systemone sind beide nach System One benannt, dem KI-Modell von TypeSafe. Mit TypeSafe bauen behandelt, wo du es in deinem Code verwendest.

Wenn du einen Coding-Agenten verwendest, installiere zuerst den TypeSafe-Agent-Skill, damit er die Formen von Anfrage und Antwort kennt.

Antwortstruktur

Die Antwort hat einen Eintrag in answers pro Frage, unter den IDs aus der Anfrage:

{
  "model": "jev-1.13.0",
  "answers": {
    "is_human_escalation": {
      "type": "noul",
      "noul": 0.99
    },
    "is_repeat_contact": {
      "type": "noul",
      "noul": 0.93
    }
  },
  "usage": {
    "input_tokens": 360,
    "output_tokens": 39
  }
}

Beide Antworten hier liegen nahe bei 1. Der Kunde sagt „mit einem echten Menschen sprechen“, also ist is_human_escalation 0.99. „Ich habe jetzt dreimal gefragt“ passt zur true-Beschreibung von is_repeat_contact, also ist sie 0.93.

Einen Noul lesen

Die Zahl vereint Antwort und Sicherheit in einem. Ein Wert nahe 1 ist ein starkes Ja. Ein Wert nahe 0 ist ein starkes Nein. Ein Wert nahe 0.5 bedeutet, dass das Modell Ja und Nein eine ähnliche Wahrscheinlichkeit gibt.

Die Tabelle unten zeigt aufgezeichnete jev-1.13.0-Antworten auf die Frage is_human_escalation für verschiedene Kundennachrichten:

Zustand noul
Danke, das hat es behoben! 0.02
Wie setze ich mein Passwort zurück? 0.07
Ich brauche das heute geklärt, koste es, was es wolle. 0.26
Bist du ein Bot? 0.40
Gibt es eine Möglichkeit, mit jemandem über meine Rechnung zu sprechen? 0.84
Ich habe jetzt dreimal gefragt. Kann ich bitte mit einem echten Menschen sprechen? 0.99

Die ersten zwei und die letzten zwei sind klar. „Ich brauche das heute geklärt“ ist dringend, verlangt aber nie nach einem Menschen, und erhält 0.26. „Bist du ein Bot?“ deutet den Wunsch nach einem Menschen an, ohne ihn zu äußern, und das Modell teilt fast gleichmäßig bei 0.40. Beide sind die Art von Nachricht, bei der eine Entscheidung anhand eines Schwellenwerts in deinem Code getroffen werden muss.

Für einen Noul gibt es keinen separaten confidence-Wert, anders als bei einem Choice oder einem Score. Die Wahrscheinlichkeitsverteilung eines Noul hat nur zwei Ergebnisse, Ja und Nein, also beschreibt der einzelne noul-Wert sie vollständig. Ein Choice oder Score verteilt die Wahrscheinlichkeit über mehrere Optionen oder Stufen, und confidence fasst diese Verteilung zusammen.

Meistens macht dein Code aus noul per Schwellenwert einen booleschen Wert:

wants_human = response.answers["is_human_escalation"].noul > 0.9

if wants_human:
    route_to_agent(ticket)
else:
    route_to_bot(ticket)

Wo du den Schwellenwert setzt, hängt von den Kosten eines Fehlers ab. Verwende 0.5, wenn sich Ja und Nein gleich leicht umsetzen lassen. Hebe ihn an, wenn das Handeln auf ein falsches Ja teuer ist, etwa jemanden per Bereitschaft zu alarmieren oder eine Erstattung auszuzahlen. Senke ihn, wenn das Verpassen eines echten Ja teuer ist, etwa ein Sicherheitsproblem nicht zu melden. Werte dazwischen können an einen Menschen gehen statt an einen der beiden Codepfade. Das ist dieselbe Dreiteilung, die die Seite Konfidenz für Choice- und Score-Antworten beschreibt.

Ein Noul-Wert läuft von 0 bis 1, ist aber keine Skala der Sache, nach der du gefragt hast. Er ist die Wahrscheinlichkeit, dass die Antwort Ja lautet. Wenn die Frage eigentlich einen Grad betrifft, misst der Wert nicht den Grad. Unten wird „Ist der Kandidat stark in Python?“ über vier Kandidaten gestellt, neben einem Score mit vier Stufen: keine Erfahrung, etwas Vertrautheit, regelmäßige Nutzung im Beruf, tiefe Expertise.

Kandidat Noul: „Ist der Kandidat stark in Python?“ Score: „Wie viel Python-Erfahrung hat der Kandidat?“
Meine Erfahrung liegt in Java und Go. Ich habe Python nicht verwendet. 0.03 0.0 (Keine Erfahrung)
Ich habe Python gelegentlich für kleine Skripte neben meiner Hauptarbeit in Java verwendet. 0.14 1.0 (Etwas Vertrautheit)
Ich habe Python zwei Jahre lang in meinem letzten Job täglich verwendet, überwiegend Datenpipelines. 0.81 2.05 (Regelmäßige Nutzung im Beruf)
Ich habe acht Jahre lang täglich Python geschrieben, einschließlich der Pflege einer großen Django-Codebasis. 0.92 2.89 (Tiefe Expertise)

Der Noul beurteilt eine einzige Aussage, „stark“, und die Werte geben an, wie wahrscheinlich sie ist. Du könntest in deinem Code Stufen im Bereich 0–1 anlegen, etwa 0.3–0.7 für „etwas Erfahrung“, aber das Modell sieht sie nicht, also wurde nichts in der Antwort gegen sie beurteilt. Ein mittlerer Wert kann mittlere Erfahrung oder einen unklaren Fall bedeuten, und der Abstand zwischen den Kandidaten ist nichts, was du gewählt hast. Der Score beurteilt jede Stufenbeschreibung für sich, daher landete jeder Kandidat auf oder nahe einer Stufe, die du geschrieben hast, und die zurückgegebenen Wahrscheinlichkeiten zeigen, wie das Modell sein Urteil zwischen den Stufen aufteilte. Wenn du anderer Meinung bist, formuliere eine Stufe um und führe sie erneut aus. Einen Fragetyp wählen erklärt den Unterschied.

Eine Noul-Frage schreiben

Stelle eine Ja/Nein-Frage pro Noul. Wenn eine Frage zwei Bedingungen hat, etwa „Ist der Kunde wütend und verlangt eine Erstattung?“, muss das Modell beide gleichzeitig beurteilen, und der Wert bedeutet weniger. Stelle zwei Nouls und kombiniere sie im Code.

Formuliere die Frage so, dass ein hoher Wert Ja bedeutet. „Enthält die Nachricht personenbezogene Daten?“ ist klar. „Ist die Nachricht frei von personenbezogenen Daten?“ kehrt die Bedeutung um, und Code, der sie später liest, versteht sie falsch herum.

Eine Aussage funktioniert genauso gut wie eine Frage. Für „Der Kunde verlangt eine Erstattung“ bedeutet ein Wert nahe 1, dass die Aussage wahr ist. Probiere beide Formulierungen mit deinen eigenen Daten aus, um zu sehen, welche besser funktioniert.

Mach die Grenze zwischen Ja und Nein eindeutig. „Hat dieser Kandidat irgendeine Python-Erfahrung?“ funktioniert gut, weil „irgendeine“ keinen Mittelweg lässt. Wenn die Grenze subtil ist, füge criteria mit true- und false-Beschreibungen hinzu, wie es die Frage is_repeat_contact oben tut. Für die meisten Nouls reicht die Anweisung, probiere deine Fragen also mit und ohne criteria aus und behalte die, die auf deinen Dokumenten bessere Antworten liefert.

Gute Praxis: stelle mehr als eine Frage pro Aufruf

Für eine Checkliste von Bedingungen stelle viele Noul-Fragen in einer Anfrage: eine Frage pro Bedingung, und der Code entscheidet, was die Kombination bedeutet. Fragen werden parallel ausgewertet, das Hinzufügen von Nouls ändert die Antwortzeit also kaum. Mehrere Fragen zusammen stellen erklärt das ausführlicher.

Mehrere Noul-Antworten im Code behandeln

Die Anfrage mit zwei Fragen oben gibt dem Code genug, um die Nachricht weiterzuleiten. Das Beispiel unten eskaliert an einen Menschen, wenn der Kunde danach verlangt, und hebt die Priorität an, wenn er sich schon einmal gemeldet hat. Ein mittlerer Wert bei einer der beiden Fragen geht an eine prüfende Person statt an einen Codepfad:

from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient

SUPPORT_QUESTIONS = {
    "is_human_escalation": Noul(
        instructions="Is the customer asking for a human agent?",
    ),
    "is_repeat_contact": Noul(
        instructions="Has the customer contacted support about this before?",
        criteria=NoulCriteria(
            true="Mentions a prior attempt, ticket, or that they have asked before",
            false="No sign of any previous contact",
        ),
    ),
}

YES = 0.8
NO = 0.2

def route(message: str) -> None:
    with TypeSafeClient() as client:
        response = client.system_one(
            model="jev-latest",
            state=message,
            questions=SUPPORT_QUESTIONS,
        )
    answers = response.answers

    wants_human = answers["is_human_escalation"].noul
    repeat = answers["is_repeat_contact"].noul

    if NO < wants_human < YES or NO < repeat < YES:
        # The model isn't sure either way. Let a person decide.
        send_to_review(message)
        return

    priority = "high" if repeat > YES else "normal"
    if wants_human > YES:
        route_to_agent(message, priority=priority)
    else:
        route_to_bot(message, priority=priority)

Für die Nachricht oben ist der Noul-Antwortwert für is_human_escalation 0.99 und is_repeat_contact 0.93, der Code leitet sie also mit hoher Priorität an einen Agenten. Die Nachricht „Wie setze ich mein Passwort zurück?“ liegt bei beiden Fragen bei 0.07 und wird an den Bot geleitet.

Die Schwellenwerte liegen in deinem Code. Wenn prüfende Personen zu viele Nachrichten sehen, verkleinere den Abstand zwischen NO und YES. Wenn zu viele falsche Wege durchkommen, vergrößere ihn. Wenn du später wissen musst, ob die Nachricht eine Zahlung erwähnt oder ob sie personenbezogene Daten enthält, füge einen weiteren Noul zu SUPPORT_QUESTIONS hinzu. Die Anzahl der Anfragen bleibt bei eins.

Strukturierte Anweisungen

Anweisungen können ein Objekt statt einer Zeichenkette sein, mit der Frage in einem Feld und ergänzenden Daten in den anderen. Struktur in den Fragen verwenden behandelt, wann das hilft. Hier wird es für eine mit Code gebaute Frage verwendet: Ein gerade eingegangener Lebenslauf wird gegen Datensätze in einer Kandidatendatenbank abgeglichen, die dieselbe Person sein könnten. Jeder Datensatz geht so, wie er ist, in ein Feld potential_duplicate, die question ist für jeden Datensatz dieselbe, und alle Datensätze werden in einer Anfrage geprüft. Die vom Code erzeugten Frageschlüssel enthalten die Datenbank-ID jedes Datensatzes:

request
{
  "state": {
    "resume": {
      "name": "John Smith",
      "location": "Oakland, CA",
      "summary": "Backend engineer with eight years of Python and Go experience.",
      "experience": [
        {
          "employer": "Google",
          "title": "Senior Backend Engineer",
          "years": "2021-2025"
        },
        {
          "employer": "Microsoft",
          "title": "Software Engineer",
          "years": "2017-2021"
        }
      ]
    }
  },
  "questions": {
    "same_as_record_18": {
      "type": "noul",
      "instructions": {
        "potential_duplicate": {
          "name": "Jon Smith",
          "location": "Oakland, CA",
          "last_employer": "Google"
        },
        "question": "Is the resume for the same person as `potential_duplicate`?"
      }
    },
    "same_as_record_42": {
      "type": "noul",
      "instructions": {
        "potential_duplicate": {
          "name": "John Smith",
          "location": "Austin, TX",
          "last_employer": "Lone Star Freight"
        },
        "question": "Is the resume for the same person as `potential_duplicate`?"
      }
    },
    "same_as_record_77": {
      "type": "noul",
      "instructions": {
        "potential_duplicate": {
          "name": "John Smithers",
          "location": "Oakland, CA",
          "last_employer": "Bay Health Clinic"
        },
        "question": "Is the resume for the same person as `potential_duplicate`?"
      }
    }
  }
}

Die Antwort:

{
  "model": "jev-1.13.0",
  "answers": {
    "same_as_record_18": {
      "type": "noul",
      "noul": 0.74
    },
    "same_as_record_42": {
      "type": "noul",
      "noul": 0.09
    },
    "same_as_record_77": {
      "type": "noul",
      "noul": 0.08
    }
  },
  "usage": {
    "input_tokens": 535,
    "output_tokens": 58
  }
}

Jede Antwort ist die Wahrscheinlichkeit, dass der Lebenslauf zu der Person in diesem Datensatz gehört. Datensatz 18 schreibt den Namen anders, passt aber bei Ort und Arbeitgeber zusammen und erhält 0.74. Datensatz 42 hat denselben Namen in einer anderen Stadt mit einem anderen Arbeitgeber und erhält 0.09. Datensatz 77 ist ein ähnlicher Name am selben Ort mit einem anderen Arbeitgeber und erhält 0.08. Belege jeden Wert in deinem Code mit einem Schwellenwert, wie in Mehrere Noul-Antworten im Code behandeln, und schicke die mittleren Werte an einen Menschen.

Mit dem Python-SDK werden die Fragen aus den Kandidatendatensätzen gebaut. Der Fragetext ist fest, und der Datensatz ändert sich:

from typesafe_sdk import Noul, TypeSafeClient

SAME_PERSON = "Is the resume for the same person as `potential_duplicate`?"

def duplicate_questions(candidates: list[dict]) -> dict[str, Noul]:
    """One Noul per candidate record, all asking the same question."""
    return {
        f"same_as_record_{candidate['id']}": Noul(
            instructions={
                "potential_duplicate": {
                    "name": candidate["name"],
                    "location": candidate["location"],
                    "last_employer": candidate["last_employer"],
                },
                "question": SAME_PERSON,
            },
        )
        for candidate in candidates
    }

def find_duplicates(resume: dict, candidates: list[dict]) -> list[str]:
    with TypeSafeClient() as client:
        response = client.system_one(
            model="jev-latest",
            state={"resume": resume},
            questions=duplicate_questions(candidates),
        )
    return [
        question_id
        for question_id, answer in response.answers.items()
        if answer.noul > 0.7
    ]

Das Cookbook zur Extraktion strukturierter Daten per Kaskade verwendet strukturierte Anweisungen, um einen extrahierten Datensatz zu verifizieren. Jedes Feld erhält dieselbe Gruppe von Fragen. Das instructions-Objekt jeder Frage hat den Fragetext in der Eigenschaft main_question. Es gibt außerdem die Eigenschaften field_spec und extracted_field, die sich für jedes Feld ändern.

Noul in den Cookbooks

Wirf einen Blick in unsere Cookbooks, um Anwendungen mit Noul-Fragen zu sehen:

  • Parallele Fragen führt eine regulatorische Checkliste mit 13 Fragen über einen Artikel in einer einzigen Anfrage aus.
  • Selbstkonsistenz: nouls bewertet einen Versicherungsanspruch gegen eine Rubrik mit 15 Fragen und misst, wie stabil die Werte über mehrere Läufe sind.
  • Re-Ranking verwendet die Wahrscheinlichkeit selbst, nicht einen Schwellenwert: einen Noul pro Anfrage-Kandidat-Paar, dann sortiert es die Kandidaten nach dem Wert.
  • Zeile-für-Zeile-Suche kombiniert einen Choice, der die passende Zeile findet, mit einem Noul, der prüft, ob das Dokument überhaupt eine Antwort enthält.
  • Strukturwiederherstellung stellt einen Noul pro Zeilenpaar, ob ein Zeilenumbruch einen Satz geteilt hat, um Absätze aus reinem Text wiederherzustellen.