43 · Reply safety¶
With replies: {rules: ...} set, every turn a person sends is screened for personal data before the router
or the model reads it, and every reply the model writes is screened again before it is shown, whether or not
an action came before it. Your rules say what each class of personal data gets, which phrases are never said,
which offers a reply may make and which sentences must go with a write. No model judges any of it: the runtime
on your machine decides each rule.
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.
Personal data¶
The classes the runtime finds, how each is found, what it gets where your rules say nothing, and how it is masked, generated from the runtime's detectors:
| Class | How it is found | Default | Masked as |
|---|---|---|---|
email |
an address of the form name@domain | allow |
j***@example.com |
iban |
an account number of the length its country gives, whose check digits hold | redact |
GB** **** 2681 |
national_id |
a US social security number, a UK national insurance number, or a Brazilian CPF whose check digits hold | redact |
***6789 |
card |
13 to 19 digits whose checksum (Luhn) holds: a number that fails it is left as it is | redact |
**** 1111 |
phone |
an international number of 8 to 15 digits, or a national one of 10 or 11 | allow |
***0123 |
A card number is found by its checksum, not by its shape: sixteen digits that fail the Luhn check are left as they are, because they are not a card number. Each class takes one of three actions:
| Action | In a turn sent to the model | In a reply the model wrote |
|---|---|---|
redact |
masked before the router and the model read the turn | masked in the sentence |
withhold |
the whole turn is held back: nothing reads it, and the person is asked to send it again without it | the sentence is removed |
allow |
left as it is | left as it is |
A value an outcome sentence may repeat. A sentence reporting a write may name a value the write was sent -- the card a refund went to -- and that value is masked in it rather than removed, whatever the class's action, unless the class is allowed.
Wording¶
never_say: phrases never said. A sentence holding one, whole words and any case, is removed.offers: the offers a reply may make. A sentence holding one of themarkers(such as discount or voucher) and none of theallowedoffers is removed.must_accompany: sentences that must go with something: a disclaimer added when a tool's call ran in this turn (when_done), or when an answer cites a passage with a tag (when_cited). It is added once, where the reply does not already say it.
The reply rules file¶
The rules are one file, YAML or JSON, named in agent.yaml as replies: {rules: reply-rules.yaml} or in Python
as Agent(..., replies={"rules": "reply-rules.yaml"}). The shop's:
# The shop's reply rules, as the console's Reply rules page saves them.
personal_data:
card: redact # the default, written out
national_id: withhold # a turn holding one reaches no model; a reply holding one loses the sentence
email: allow
never_say:
- "guaranteed refund"
must_accompany:
- when_done: refund_card
add: "Refunds reach the card in three to five working days."
Every key the runtime reads, generated from the runtime:
| Key | What it says |
|---|---|
personal_data |
for each class of personal data, redact, withhold or allow |
must_accompany |
sentences that must go with a write done or a passage cited |
must_accompany[].add |
the sentence added |
must_accompany[].when_done |
added when this tool's call ran in this turn |
must_accompany[].when_cited |
added when an answer cites a passage with this tag |
reply_exactly |
replies given word for word in place of any other reply, with no model and nothing added |
reply_exactly[].when_cited |
given when an answer cites a passage with this tag |
reply_exactly[].when_asked |
given when the turn says one of the topic's phrases: its words, else the topic's name with _ and - read as spaces |
reply_exactly[].say |
the exact reply, with nothing added |
reply_exactly[].words |
with when_asked: the phrases that ask about the topic, matched as whole words in any case, e.g. allergy, allergic, nut |
never_say |
phrases never to say: a sentence holding one is removed |
offers |
the offers a reply may make |
offers.allowed |
the offers allowed |
offers.markers |
the words that make a sentence an offer: one with a marker and no allowed offer is removed |
The same rules are kept on the console's Reply rules page, as a form over the same file, and at
/v1/reply-rules, in versions. A file the runtime cannot read -- a class it does not know, an action that is
not one of the three -- is refused with the key that is wrong.
The shop's replies, screened¶
The shop runs on an Enterprise licence. Its orders hold the card on file and the customer, which is personal data, and a model reads personal data a tool returns on Enterprise only, with your own key; on another tier the reply says it was written without a model.
The model below stands in for yours, and records what it was given:
SEEN = [] # what the model was given, turn by turn
# Stands in for your model: what it is asked, and the reply it writes.
SCRIPT = {
"zork": (None, ""),
"my card is **** 1111, is it on file?": (
None,
"Your card 4111 1111 1111 1111 is on file; the card 4111 1111 1111 1112 is not."),
"the parcel for 4412 vanished": (
{"name": "refund_card", "arguments": {"order_id": "4412", "amount": 12.50}},
"I've refunded 12.50 pounds for order 4412. It is a guaranteed refund."),
}
def model(conversation):
turn = [m for m in conversation if m["role"] == "user"][-1]["content"]
SEEN.append(turn)
call, reply = SCRIPT[turn]
if conversation[-1]["role"] == "user" and call:
return {"content": "", "tool_calls": [call]}
return reply
desk = Agent("desk", tools=[get_order, refund_card], model=model, on_unread="model",
replies={"rules": "reply-rules.yaml"})
for text in ("zork",
"my card is 4111 1111 1111 1111, is it on file?",
"my social security number is 123-45-6789",
"the parcel for 4412 vanished"):
reply = desk.chat(text, user="u-17")
if reply.waiting:
reply = desk.confirm("u-17", "approve")
print(f"{text}\n -> {reply.text}")
print("The model was given:", SEEN)
zork
-> I didn't recognise anything in that. I can get order or refund card. Which do you mean?
my card is 4111 1111 1111 1111, is it on file?
-> Your card **** 1111 is on file; the card 4111 1111 1111 1112 is not.
my social security number is 123-45-6789
-> That message held details I can't take here, so I haven't read it or done anything. Please send it again without them.
the parcel for 4412 vanished
-> I've refunded 12.50 pounds for order 4412. Refunds reach the card in three to five working days.
The model was given: ['my card is **** 1111, is it on file?', 'the parcel for 4412 vanished', 'the parcel for 4412 vanished']
- The card number the person typed was masked before anything read the turn: the model was given
**** 1111. - The model's reply named the same valid card, masked, and a sixteen-digit number that fails the checksum, left as it is.
- The turn with a social security number was held back whole, under
national_id: withhold: nothing read it, and the person was asked to send it again without it. It is not in what the model was given. - After the refund ran, It is a guaranteed refund. was removed (
never_say), and the disclaimer the rules require for a refund was added.
The counts¶
Each check is counted, by the meter it is recorded under, with your licence's other meters. A count is a number: it carries no text, no value and no turn.
| Count | What it counts |
|---|---|
reply_temperature_kept |
a varied wording that read back with another mood or other locked words, so the template's own sentence was sent |
reply_temperature_varied |
a template sentence worded another way, with reply temperature on |
reply_exact_reply |
a reply given word for word in place of any other, by reply_exactly |
reply_pd_detected_<class> |
a value of that class of personal data found |
reply_pd_masked |
a value a write was sent, repeated in its own outcome sentence, masked |
reply_pd_withheld |
a turn held back, or a sentence removed, for a value the rules withhold |
reply_pd_redacted |
a value masked because the rules redact its class |
reply_assertion_refused |
a sentence saying a write was done that nothing in the session attempted, replaced |
reply_outcome_replaced |
a sentence its action's outcome does not allow, replaced by the phrasing |
reply_value_highlighted |
a sentence in staff work stating a value no record holds, kept and highlighted |
reply_value_replaced |
a sentence stating a value its action was neither sent nor returned, replaced |
reply_outcome_added |
a write of this turn the reply left out, added from its phrasing |
reply_wording_removed |
a sentence holding a phrase never to say, removed |
reply_offer_removed |
a sentence making an offer outside the allowed ones, removed |
reply_wording_added |
a sentence the rules require, added |
reply_facts_untraced |
a value the reply states that no call's record holds |
reply_facts_traced |
a value the reply states, traced to the call and the field it came from |
reply_facts_conflict |
a traced value another source this turn gives differently, for the same thing |
reply_facts_stale |
a traced value read longer ago than its source says a result holds |
reply_facts_out_of_range |
a traced value outside the range its tool's output schema allows |
The limits¶
- A detector finds the classes it names, and no others. A name, a postal address, a date of birth or an identifier of another country is not detected, and is left as it is.
- A detector finds a value written as the detector reads it. A card number spelt out in words, or split across two messages, is not found.
- No model judges a reply. A rule matches the words it is given: a phrase said in other words is not a
phrase
never_saylists. - The screen holds the turns the agent reads and the replies its model writes. A reply no model wrote -- a fixed question, a refusal, a phrasing -- is the product's own wording.