↓ Skip to main content
  1. Blog/

Kan det fortrydes? Ellers bliver mennesket et gummistempel

·1866 ord·9 minutter
De kiilske principper for AI - Denne artikel er en del af en serie.
Del 5: Denne artikel

Det fjerde og sidste af de kiilske principper for AI er også et spørgsmål.

Hvilke handlinger kræver et menneske, og hvorfor netop dem?

Det sidste led er det vigtige. De fleste kan godt svare på, at der sidder et menneske et sted i processen. Færre kan forklare, hvorfor det sidder lige der.

Gummistemplet
#

Det mest almindelige svar, jeg får, er: »Et menneske godkender alt.«

Det holder i cirka tre uger. De første dage læser man hvert eneste udkast. Så skimmer man. Så klikker man. Til sidst er godkendelsen et gummistempel med en logfil bagved, og logfilen siger, at et menneske har kigget, selvom ingen har.

Det er værre end ingen godkendelse. Uden godkendelse ved alle, at maskinen handler alene. Med et gummistempel tror alle, at nogen holder øje.

Opmærksomhed er en ressource, der slipper op. Bruger I den på alt, har I ingen tilbage til det, der betyder noget.

To steder
#

Mennesket hører til to steder.

Hvor systemet ved, at det er i tvivl. Det byggede vi i indlægget om, hvad der sker, når modellen tager fejl. Når regler og model er uenige, ryger sagen til et menneske. Det er systemet selv, der siger: Den her kan jeg ikke afgøre.

Foran de handlinger, der ikke kan gøres om. Det er dem, dette indlæg handler om.

Mit kriterium er ét spørgsmål: Kan det fortrydes?

Et udkast på min egen disk kan jeg bare lave om, så det kører af sig selv. En ansøgning, der er sendt, kan jeg ikke kalde tilbage, så der venter systemet på mig.

Læg mærke til, hvad kriteriet ikke er. Det er ikke, hvor svært skridtet er, eller hvor klog modellen skal være for at tage det. Et svært skridt, der kan gøres om, kan sagtens køre alene. Et let skridt, der ikke kan, kan ikke. Det gælder for at sende en mail til en kunde, betale en faktura, slette data og offentliggøre noget.

Et eksempel: handlinger med og uden fortryd
#

Lad os bygge videre på trail.nu fra det forrige indlæg. Når screeningen siger »søg«, skal systemet nu skrive et udkast til ansøgningen og sende den. Det første kan fortrydes. Det andet kan ikke.

use trail.nu [log-event]

# What the system may do. An action without `undo: true` is treated as irreversible.
def catalog []: nothing -> record {
  {
    draft: {
      undo: true
      run: {|p| mkdir drafts; $p.body | save --force $"drafts/($p.case).md" }
    }
    send: {
      run: {|p| $p.body | ^mail -s $p.subject $p.to }
    }
  }
}

def lookup [action: string]: nothing -> record {
  let spec = catalog | get --optional $action
  if $spec == null {
    error make {msg: $"Unknown action: ($action)", help: "The system may only do what is listed in `catalog`."}
  }
  $spec
}

def require-reason [why: string] {
  if ($why | str trim | is-empty) {
    error make {msg: "A decision needs a reason", help: "Write one sentence your future self will understand."}
  }
}

def events [trail: path]: nothing -> table {
  open --raw $trail | from json --objects
}

# Reversible actions run at once. Everything else waits for a human.
export def propose [
  case: string
  action: string
  payload: record
  --trail: path = "decisions.jsonl"
]: nothing -> string {
  let spec = lookup $action
  if ($spec.undo? | default false) {
    {case: $case, event: "acted", action: $action, payload: $payload, by: "system"} | log-event $trail | ignore
    do $spec.run ($payload | insert case $case)
    "done"
  } else {
    {case: $case, event: "proposed", action: $action, payload: $payload, by: "system"} | log-event $trail | ignore
    "waiting"
  }
}

# Proposals whose latest event is still unanswered
export def pending [--trail: path = "decisions.jsonl"]: nothing -> table {
  events $trail
  | where event in [proposed approved rejected]
  | group-by case --to-table
  | each { get items | last }
  | where event == "proposed"
  | select case action payload
}

# A human says yes, with a reason. Only then does the action run.
export def approve [case: string, why: string, --trail: path = "decisions.jsonl"] {
  require-reason $why
  let waiting = pending --trail $trail | where case == $case
  if ($waiting | is-empty) {
    error make {msg: $"Nothing is waiting for case ($case)"}
  }
  let proposal = $waiting | first
  {case: $case, event: "approved", action: $proposal.action, by: $env.USER, why: $why} | log-event $trail | ignore
  do (lookup $proposal.action).run ($proposal.payload | insert case $case)
}

# A human says no, also with a reason
export def reject [case: string, why: string, --trail: path = "decisions.jsonl"] {
  require-reason $why
  {case: $case, event: "rejected", by: $env.USER, why: $why} | log-event $trail | ignore
}

# How long did the human look before answering?
export def review-times [--trail: path = "decisions.jsonl"]: nothing -> table {
  let all = events $trail
  let asked = $all | where event == "proposed" | select case at | rename case asked
  let answered = $all | where event in [approved rejected] | select case at event | rename case answered verdict
  $asked
  | join $answered case
  | insert seconds {|r| (($r.answered | into datetime) - ($r.asked | into datetime)) / 1sec | math round }
  | select case verdict seconds
}

Gem den som actions.nu ved siden af de to andre. Tre ting er værd at bemærke.

catalog er den komplette liste over, hvad systemet må. En handling, der ikke står der, bliver afvist. En handling, der står der uden undo: true, venter på et menneske. Standarden er altså, at noget ikke kan fortrydes. Den, der vil lade en handling køre alene, skal skrive det ind med vilje.

propose kører ikke handlingen, når den ikke kan fortrydes. Den skriver et forslag i sporet og svarer waiting. Systemet kan foreslå alt det vil, men det er kun mennesket, der kan trykke på knappen.

approve følger reglerne fra sidst. Ingen godkendelse uden begrundelse, og godkendelsen skrives i sporet, før mailen bliver sendt.

Sådan ser det ud for opslaget, som screeningen sendte videre:

use actions.nu [propose pending approve reject review-times]

propose 0adbd15a draft {body: "Dear hiring team, ..."}
done

Udkastet ligger nu i drafts/0adbd15a.md. Jeg kan rette det, slette det eller lade systemet skrive det igen. Så foreslår systemet at sende det:

propose 0adbd15a send {
  to: "[email protected]"
  subject: "Nushell developer"
  body: (open --raw drafts/0adbd15a.md)
}
waiting

Der sker ingenting, før jeg kigger:

pending | select case action payload.to payload.subject
╭───┬──────────┬────────┬──────────────────┬───────────────────╮
│ # │   case   │ action │    payload.to    │  payload.subject  │
├───┼──────────┼────────┼──────────────────┼───────────────────┤
│ 0 │ 0adbd15a │ send   │ [email protected] │ Nushell developer │
╰───┴──────────┴────────┴──────────────────┴───────────────────╯

Forslaget indeholder hele brevet, præcis som det vil blive sendt. Jeg godkender altså ikke en overskrift, men det, der faktisk går ud ad døren. Når jeg har læst det:

approve 0adbd15a "Read it through. The tone fits."

Og så bliver ansøgningen sendt. Hvis jeg spørger om en uge, viser why 0adbd15a hele vejen fra screening over udkast og forslag til godkendelse.

Er godkendelsen ægte?
#

Nu kommer den del, de fleste springer over. Et menneske foran en handling hjælper kun, hvis mennesket faktisk kigger. Det kan sporet heldigvis fortælle. review-times måler, hvor lang tid der gik, fra systemet spurgte, til mennesket svarede.

Sådan kunne det se ud efter et par uger:

╭───┬──────────┬──────────┬─────────╮
│ # │   case   │ verdict  │ seconds │
├───┼──────────┼──────────┼─────────┤
│ 0 │ 0adbd15a │ approved │     214 │
│ 1 │ 58572c95 │ approved │      95 │
│ 2 │ c41e7a02 │ approved │      12 │
│ 3 │ 7d90b3f1 │ approved │       4 │
│ 4 │ e2a5c913 │ approved │       3 │
│ 5 │ 1b6f04d8 │ approved │       2 │
╰───┴──────────┴──────────┴─────────╯

Ingen læser en ansøgning på tre sekunder. Og når alt bliver godkendt, er der ingen grund til at tro, at noget ville være blevet afvist. Her er gummistemplet i gang med at vokse frem, og det er sporet, der afslører det.

Så er der to veje. Den ene er at sætte mennesket foran færre ting, så opmærksomheden rækker til det, der er tilbage. Den anden er at gøre handlingen fortrydelig. En mail, der ligger i en udbakke i ti minutter, før den bliver sendt, kan fortrydes i ti minutter. Et betalingsforslag, som banken først gennemfører i morgen, kan fortrydes i dag. Det flytter handlingen fra den ene kategori til den anden, og så behøver den ikke længere et menneske foran sig. Den skal bare have et, der opdager det, hvis noget går galt.

For mine ansøgninger vælger jeg den første vej. Der kommer ikke så mange, at jeg ikke kan nå at læse dem.

Se det fejle, igen
#

Den samme regel som de to sidste gange. Et menneske foran handlingen er også et tjek, og det skal ses sige fra:

use std/assert
use actions.nu [propose approve reject]

# An irreversible action does not run on its own
assert equal (propose "x" send {to: "[email protected]", subject: "Hi", body: "Hi"} --trail test.jsonl) "waiting"

# An action the system has not been given is refused
assert error { propose "x" delete-everything {} --trail test.jsonl }

# Approval without a reason is refused
assert error { approve "x" "  " --trail test.jsonl }

# Nothing to approve, nothing happens
assert error { approve "nope" "Looks fine." --trail test.jsonl }

# A rejected proposal cannot be approved afterwards
reject "x" "Wrong address." --trail test.jsonl
assert error { approve "x" "Changed my mind." --trail test.jsonl }

Den første test er den vigtigste. Den viser, at en handling, der ikke kan fortrydes, ikke kører, uanset hvad modellen mener. Ligesom i indlægget om regler og skøn er det koden, der sørger for det, og ikke en sætning i en prompt.

Har man ombestemt sig efter et nej, laver systemet et nyt forslag. Det gamle nej bliver stående i sporet.

Loven og ledelsen
#

EU’s AI-forordning kræver menneskeligt tilsyn med højrisikosystemer. Den nævner udtrykkeligt risikoen for, at mennesker kommer til at stole for meget på maskinens output. Gummistemplet har altså også et juridisk navn.

Men lov eller ej: Når ledelsen siger, at der er et menneske i loopet, så spørg, hvor det sidder, hvorfor det sidder lige der, og hvor længe det kigger.

For at opsummere
#

Når I bygger noget med en model i, så vær skarpe på, hvor mennesket skal være, og hvorfor:

  • Spørg, om det kan fortrydes. Det, der kan, må køre alene. Det, der ikke kan, venter på et menneske.
  • Behandl det ukendte som uigenkaldeligt. En handling er irreversibel, indtil nogen har skrevet andet.
  • Vis det, der faktisk sker. Mennesket skal godkende hele handlingen og ikke en overskrift.
  • Mål opmærksomheden. Godkendelser på få sekunder er et gummistempel.
  • Gør hellere handlingen fortrydelig end at sætte et menneske foran alt.

Fire spørgsmål
#

Dermed er serien slut. De fire spørgsmål var:

  1. Hvilke dele er regler, og hvilke er model?
  2. Hvad sker der, når modellen svarer forkert?
  3. Kan I bagefter vise mig, hvorfor systemet traf en bestemt beslutning?
  4. Hvilke handlinger kræver et menneske, og hvorfor netop dem?

Ingen af dem handler om, hvilken model I bruger. Alle fire kan besvares med lidt kode, et spor og et klart svar på, hvem der bestemmer hvad. Og det lille jobsystem, vi har bygget undervejs, kan svare på dem alle fire.

Hvis I vil have hjælp til at finde ud af, hvor mennesket skal sidde i jeres egne systemer, så skriv eller ring.

De kiilske principper for AI - Denne artikel er en del af en serie.
Del 5: Denne artikel