Skip to content

25 · Feedback and the learned vocabulary

The router reads a turn by the words your tools declare. Your users have words of their own -- take order 4411 off the card where your tool says refund_card -- and their thumbs teach the router those words. This chapter is what a thumb records, how the hosted miner turns thumbs into a new vocabulary, and how a vocabulary is reverted.

Language support

The selector reads turns written in English and in Brazilian Portuguese (pt-BR). The language is chosen, never guessed from the turn: the console's Settings page (Language the selector reads), language on a plan request, or the settings.language of your vocabulary file; with none of them, English. A Portuguese turn may be typed with its accents or without them. European Portuguese is not a grammar the selector has, and a turn in any other language is unread.

A thumb

A person's thumb, up or down, on what the router chose is one feedback record. In a session:

with Client() as client:
    chat = client.session("desk", catalogue=catalogue, rulebook=rulebook)
    plan = chat.read("show me order 4411")
    print(chat.thumbs(plan, up=True))                                   # the router chose well
    plan = chat.read("take order 4411 off the card")
    print(plan.steps[0].selection.tool, "->", chat.thumbs(plan, up=False, corrected="refund_card"))

The second turn reached get_order, and the thumb down says the tool meant was refund_card (corrected). A retry of the same thumb is one record on the service. An agent of Part I does the same with agent.thumbs(reply, up=...), and LangChain's callback with StalwartFeedbackCallback (chapter 27).

A thumb down is worth most with what was meant. agent.meant_choices(reply) -- meant_choices(rated) beside LangChain's callback -- lists what to offer the person: the tools the router weighed first -- the ones it asked between, or both readings of a word it read two ways -- then every other tool the agent could reach, and "" last for "none of these". Their answer is corrected.

What a record holds

desk = Agent.from_file("agent.yaml")
reply = desk.chat("show me order 4411", user="u-17")
print(json.dumps(desk.feedback_record(reply, up=True), indent=1, sort_keys=True))
{'accepted': 1}
get_order -> {'accepted': 1}
{
 "agent": "desk",
 "cited": [],
 "context": {
  "before": [],
  "history": "first",
  "previous": [],
  "readings": [],
  "step": 0,
  "steps": 1
 },
 "corrected": "",
 "decided_by": "tool: admission",
 "delegated": false,
 "outcomes": [
  "get_order:done"
 ],
 "reply_digest": "1bfd98bf2f6401b4e33432e2778566c3d8b4d5a315f0b10ca25d1706a6a2d733",
 "rewrites": [],
 "router_digest": "",
 "thumb": "up",
 "tool": "get_order",
 "turn": "show me order 4411",
 "verdict": "resolved",
 "visible": [
  "charge_card",
  "get_order",
  "hand_off",
  "read_inbox",
  "send_receipt"
 ],
 "vocabulary_digest": "2430f1a2ad2982d0067885488a4c89e21ad1d7c83b115ba8f1b20acc88dfaea8"
}
ThumbRefused: personal data in the request, so nothing was sent: a telephone number at turn. Remove it, or put a placeholder in its place (an address at example.com, say), and send the request again

The record is the rated turn's words with the router's reading of them: the turn, the agent, the tools it could see (visible), the tool it chose and the rule that chose it (decided_by), the outcome (verdict), the vocabulary it read with, and the thumb. What the reply said is held only as what was done -- each action's outcome (outcomes) and the passages it cited -- and a digest of its text: never the reply's words, an argument's value, a tool's result or an earlier turn.

A record also holds the context the turn was read in (context), because the router reads by context and is right or wrong only in it: tag lists please asks for the tag lists where your tools only list them, and tags lists where a tool tags them. The context is the rated step's place in its plan (step, steps) and the tools of the steps before it (before), what the turn before did -- first, asked, answered or declined (history) -- and the tools it called or asked between (previous), and, where the router read a word two ways, the tools both readings reached (readings). Tool names only: no earlier turn's words.

The miner learns from the turn's words, so the record is screened for personal data on your machine before it is sent, and a thumb on a turn that holds some is refused, with nothing sent:

reply = desk.chat("show me order 4411, my number is 07700 900123", user="u-17")
try:
    desk.thumbs(reply, up=True)
except ThumbRefused as refused:
    print("ThumbRefused:", refused)

The output's last line. A thumb on a whole turn of several instructions, on an answer the conversation decided, or on the reply after a confirmation is refused too, naming what to thumb instead: these are not readings of the person's words.

Records are kept under your plan's retention (chapter 16), read by nobody else, and never used for training. GET /v1/feedback/summary counts the last thirty days' thumbs by what they were on -- a tool the router chose, a turn the model took, a question the router asked, an answer from your sources -- and GET /v1/feedback/export gives every stored record as a line of JSON, which is what a team that runs its own miner reads.

The hosted miner

POST /v1/vocabulary/mine (or client.mine(model)) queues a learning run over your stored feedback and your live vocabulary, in one language (below). It proposes words your users used for your tools, and applies them only when every one of its five gates holds and the live vocabulary is still the version it mined over. The gates: no write is reached wrongly, on your evaluation corpus or on any feedback case; the feedback held back from learning is answered no worse than before; your corpus's baseline and your dialogues' baselines, where you keep them, still hold; and the new file checks clean. Its run is answered at once with an id; GET /v1/vocabulary/mine/{run_id} (client.ledger(run_id)) reads its state -- queued, running, finished, failed or cancelled -- whether it applied, the version it made live, or the gate that held it back. Its ledger names each case by digest, never by its words. DELETE /v1/vocabulary/mine/{run_id} withdraws a queued run; one already running finishes.

The console's Miner page shows the same runs, and its Vocabulary page each version and what its ledger says (GET /v1/vocabulary/history).

A vocabulary, versioned

The live vocabulary (GET /v1/vocabulary, client.vocabulary()) is a file of your words and the tools they mean. A version stays when the next is made live, so POST /v1/vocabulary/revert -- or client.revert_vocabulary(), or the Vocabulary page -- makes the one before it live again, one version per call. PUT /v1/vocabulary publishes a version you wrote yourself. Every plan names the vocabulary it read with (vocabulary_digest), so a plan that changed can be traced to the version that changed it.

One version is live for each language the router reads: a file is for the language its settings.language names -- en or pt-BR -- and for English where it names none. GET /v1/vocabulary and POST /v1/vocabulary/revert take ?language= -- else your Settings page's language -- a feedback record carries the language its turn was read in, and a learning run mines one language -- the language its body names, else your Settings page's -- into that language's file alone. A Portuguese thumb never teaches the English file a word.

The runtime on your machine takes the live version with its next lease refresh, so a version made live, or reverted, reaches your agents within that time.

A sentence shape the router could not read at all -- not a word, but how the sentence is built -- is kept by a mine run as its shape only, in the router's own alphabet and never a word of yours. GET /v1/grammar-reports/shipped lists the shapes of yours that a release of the router now reads. A shape goes with its context's shape, never the context: what the turn before did, where the step stood in its plan, how many tools a word read two ways reached, and each tool before it as its name's word classes (ACTION OBJECT), never its name. A shape is kept only where five distinct turns share it in one context. Sending one yourself is opt-in: a report not consented to is refused (grammar_report_not_consented), and so is one in an alphabet no release shipped (grammar_report_alphabet_unknown).

The limits

Chapter 20's limits hold here, and these besides:

  • A vocabulary teaches the router words, not judgement: a word applied for one tool reads that tool wherever it is said.
  • The miner applies only what its gates hold, and the gates read the cases you have. A word nobody has thumbed is not learned.