Skip to content

Commit aed6ad3

Browse files
tiberius1701dclaude
andcommitted
refactor(plausibility): grounding-based rules + R3 transcript contradiction
The plausibility prompt previously instructed the judge with "lexical patterns" and "no judgment calls" and listed five drop rules as exact token enumerations: R3 role-leak token list, R4 unresolved-placeholder regex tied to the constrained {Person|City|Country|Org|Thing} vocabulary, R5 sentinel-object token list, R6 dot-separated HA-style identifier. Two pivots applied here: * Drop R4. The constrained-prefix regex became incoherent with the open-vocabulary anonymizer pivot (commit d8de45c) and was already structurally redundant with _strip_residual_placeholders running inside _apply_bindings before the deanon-stage plausibility judge. * Add R3 transcript contradiction. The transcript was rendered into the prompt but no rule referenced it. The judge now drops facts whose object contradicts the transcript on the same (subject, predicate). Live probe confirms deterministic catch across 3 runs at temp=0. * Soften R3 (Role leak → Conversation-role reference), R4 (Empty / sentinel object → Content-free object), R5 (System entity ID → Namespaced system identifier) from "Subject or object is exactly one of: ..." token enumerations to grounding-flavoured category descriptions with the original tokens kept as illustrative parentheticals. The rule examples are retained verbatim. The structure of the prompt is preserved exactly: KEEP / DROP / Input / Output sections; numbered R-rules with bold name + description + `Example: [N] {...} → DROP (Rk) — include N` line; Input section rendering transcript and indexed facts. The _DEFAULT_PLAUSIBILITY_PROMPT inline default is kept byte-equivalent to the file (parity-gated by tests/test_prompts_contract.py::test_inline_default_matches_file). Empirical baseline (probe at /tmp/probe_plausibility_grounding.py, 10-fact synthetic input on local Mistral 7B): deterministic {"drop": [3, 6, 8, 9]} across 3 runs. Catches R1 (self-loop), R2 (name-swap pair, both indices), R3 (transcript contradiction). Misses on this probe: R4 (Assistant), R5 (Unknown), R6 (system identifier). The misses share a pattern - Mistral 7B's KEEP-bias on ambiguous cases overrides the rule when the fact reads as plausible content (Assistant could be a name; Unknown could be a noun; controls media_player.X sounds like a smart-home claim). Strengthening these rules without regressing the structural KEEP-default behaviour is tracked as an open optimization in the project memory; revisit condition is whether SOTA enrichment in production actually emits these failure modes. Pre-existing miss: R6 system identifier was missed by the original lexical prompt on the same probe input. Pattern-match wording vs. semantic-judgment wording made no difference for the specific fact shape "Person_1 controls media_player.X" - Mistral reads it as a legitimate smart-home claim regardless. The miss is a Mistral 7B capability gap, not a prompt bug. Pytest CI-parity scope (-m "not gpu", --timeout=60): 3656 passed, 26 deselected, 0 failed in 11:19. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent d8de45c commit aed6ad3

3 files changed

Lines changed: 48 additions & 37 deletions

File tree

configs/prompts/sota_plausibility.txt

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,32 @@
1-
You are filtering enriched personal facts from a voice assistant conversation. Inputs may be anonymized (placeholders like Person_1) or real-named — apply the same rules either way.
2-
3-
You decide DROP or KEEP per fact. Output ONLY the indices of facts that match a DROP rule. Never echo, modify, or add facts.
1+
You are filtering enriched personal facts from a voice assistant conversation. Decide KEEP or DROP per fact, grounding each against the transcript. Output ONLY the indices of facts that match a DROP rule. Never echo, modify, or add facts.
42

53
## KEEP — leave the fact in the output
64

7-
Default action. Apply whenever none of the DROP rules below match the fact's lexical pattern. Do not drop on semantic judgment. When uncertain, KEEP.
5+
Default action. Keep the fact when its subject, object, and predicate are supported by the transcript (literally or by clear implication). When uncertain, KEEP.
86

97
Example: `[0] {{"subject": "Alex", "predicate": "lives_in", "object": "Portland"}}` → KEEP (do not include 0 in the output)
108
Example: `[1] {{"subject": "Alex", "predicate": "likes", "object": "Uptown Funk"}}` → KEEP (do not include 1 in the output)
119

1210
## DROP — emit this fact's index
1311

14-
Apply when any of the lexical patterns below match. No judgment calls.
12+
Apply when any of the rules below matches.
1513

1614
**R1. Self-loop.** `subject` and `object` are the same string (case-insensitive), regardless of predicate.
1715
Example: `[2] {{"subject": "Person_1", "predicate": "has_name", "object": "Person_1"}}` → DROP (R1) — include 2 in the output.
1816

1917
**R2. Name-swap pair.** Both `A has_name B` and `B has_name A` are in the input (also `named`, `is`, `equals`). Drop both indices.
2018
Example: `[3] {{"subject": "Alex", "predicate": "is", "object": "Bob"}}` AND `[4] {{"subject": "Bob", "predicate": "is", "object": "Alex"}}` → DROP both (R2) — include 3 and 4.
2119

22-
**R3. Role leak.** Subject or object is exactly one of: "Assistant", "User", "Speaker", "the bot", "the model". Note: "Person_1" / "City_1" / "Org_1" / "Thing_1" are valid entity placeholders, NOT role leaks.
23-
Example: `[5] {{"subject": "Assistant", "predicate": "responded_to", "object": "Alex"}}` → DROP (R3) — include 5.
20+
**R3. Transcript contradiction.** The transcript states a different value for the same subject and predicate.
21+
Example: `[5] {{"subject": "Alex", "predicate": "lives_in", "object": "Tokyo"}}` (when the transcript says Alex lives in Portland) → DROP (R3) — include 5.
2422

25-
**R4. Unresolved placeholder in real-name input.** When the input is real-named (no `Person_N` / `City_N` / `Country_N` / `Org_N` / `Thing_N` placeholders expected in the transcript), drop any fact whose subject or object still matches `^(Person|City|Country|Org|Thing)_\d+$`.
26-
Example: `[6] {{"subject": "Alex", "predicate": "owns", "object": "Person_4"}}` → DROP (R4, real-name input) — include 6.
23+
**R4. Conversation-role reference.** Subject or object refers to a conversation-role label (e.g. "Assistant", "User", "Speaker", "the bot", "the model") rather than a real-world entity introduced by the transcript.
24+
Example: `[6] {{"subject": "Assistant", "predicate": "responded_to", "object": "Alex"}}` → DROP (R4) — include 6.
2725

28-
**R5. Empty / sentinel object.** Object is exactly one of: "", "Unknown", "None", "Various", "Something", "N/A".
26+
**R5. Content-free object.** Object provides no transcript-grounded claim — empty, whitespace-only, or a content-free placeholder (e.g. "Unknown", "None", "Various", "Something", "N/A").
2927
Example: `[7] {{"subject": "Alex", "predicate": "is_from", "object": "Unknown"}}` → DROP (R5) — include 7.
3028

31-
**R6. System entity ID.** Subject or object contains a dot-separated HA-style identifier (e.g. `media_player.sonos_office`, `sensor.temperature_kitchen`).
29+
**R6. Namespaced system identifier.** Subject or object is a system / automation identifier — a dot-separated or URI-shaped namespaced token (e.g. `media_player.sonos_office`, `sensor.temperature_kitchen`, `climate.kitchen_thermostat`) referring to a device, sensor, or service rather than a personal entity.
3230
Example: `[8] {{"subject": "Alex", "predicate": "controls", "object": "media_player.sonos_office"}}` → DROP (R6) — include 8.
3331

3432
## Input

paramem/graph/extractor.py

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1810,22 +1810,21 @@ def _validate_with_ha_context(graph: SessionGraph, ha_context: dict) -> SessionG
18101810
# fmt: off
18111811
_DEFAULT_PLAUSIBILITY_PROMPT = (
18121812
"You are filtering enriched personal facts from a voice assistant conversation. " # noqa: E501
1813-
"Inputs may be anonymized (placeholders like Person_1) or real-named — apply the same rules either way.\n" # noqa: E501
1814-
"\n"
1815-
"You decide DROP or KEEP per fact. Output ONLY the indices of facts that match a DROP rule. " # noqa: E501
1813+
"Decide KEEP or DROP per fact, grounding each against the transcript. "
1814+
"Output ONLY the indices of facts that match a DROP rule. "
18161815
"Never echo, modify, or add facts.\n"
18171816
"\n"
18181817
"## KEEP — leave the fact in the output\n"
18191818
"\n"
1820-
"Default action. Apply whenever none of the DROP rules below match the fact's lexical pattern. " # noqa: E501
1821-
"Do not drop on semantic judgment. When uncertain, KEEP.\n"
1819+
"Default action. Keep the fact when its subject, object, and predicate are supported by the transcript " # noqa: E501
1820+
"(literally or by clear implication). When uncertain, KEEP.\n"
18221821
"\n"
18231822
'Example: `[0] {{"subject": "Alex", "predicate": "lives_in", "object": "Portland"}}` → KEEP (do not include 0 in the output)\n' # noqa: E501
18241823
'Example: `[1] {{"subject": "Alex", "predicate": "likes", "object": "Uptown Funk"}}` → KEEP (do not include 1 in the output)\n' # noqa: E501
18251824
"\n"
18261825
"## DROP — emit this fact's index\n"
18271826
"\n"
1828-
"Apply when any of the lexical patterns below match. No judgment calls.\n"
1827+
"Apply when any of the rules below matches.\n"
18291828
"\n"
18301829
"**R1. Self-loop.** `subject` and `object` are the same string (case-insensitive), regardless of predicate.\n" # noqa: E501
18311830
'Example: `[2] {{"subject": "Person_1", "predicate": "has_name", "object": "Person_1"}}` → DROP (R1) — include 2 in the output.\n' # noqa: E501
@@ -1835,24 +1834,23 @@ def _validate_with_ha_context(graph: SessionGraph, ha_context: dict) -> SessionG
18351834
'Example: `[3] {{"subject": "Alex", "predicate": "is", "object": "Bob"}}` AND '
18361835
'`[4] {{"subject": "Bob", "predicate": "is", "object": "Alex"}}` → DROP both (R2) — include 3 and 4.\n' # noqa: E501
18371836
"\n"
1838-
"**R3. Role leak.** Subject or object is exactly one of: "
1839-
'"Assistant", "User", "Speaker", "the bot", "the model". '
1840-
'Note: "Person_1" / "City_1" / "Org_1" / "Thing_1" are valid entity placeholders, NOT role leaks.\n' # noqa: E501
1841-
'Example: `[5] {{"subject": "Assistant", "predicate": "responded_to", "object": "Alex"}}` → DROP (R3) — include 5.\n' # noqa: E501
1842-
"\n"
1843-
"**R4. Unresolved placeholder in real-name input.** When the input is real-named " # noqa: E501
1844-
"(no `Person_N` / `City_N` / `Country_N` / `Org_N` / `Thing_N` placeholders expected in the transcript), " # noqa: E501
1845-
"drop any fact whose subject or object still matches "
1846-
r"`^(Person|City|Country|Org|Thing)_\d+$`." # noqa: E501
1837+
"**R3. Transcript contradiction.** The transcript states a different value for the same subject and predicate.\n" # noqa: E501
1838+
'Example: `[5] {{"subject": "Alex", "predicate": "lives_in", "object": "Tokyo"}}` '
1839+
"(when the transcript says Alex lives in Portland) → DROP (R3) — include 5.\n"
18471840
"\n"
1848-
'Example: `[6] {{"subject": "Alex", "predicate": "owns", "object": "Person_4"}}` → DROP (R4, real-name input) — include 6.\n' # noqa: E501
1841+
"**R4. Conversation-role reference.** Subject or object refers to a conversation-role label " # noqa: E501
1842+
'(e.g. "Assistant", "User", "Speaker", "the bot", "the model") '
1843+
"rather than a real-world entity introduced by the transcript.\n"
1844+
'Example: `[6] {{"subject": "Assistant", "predicate": "responded_to", "object": "Alex"}}` → DROP (R4) — include 6.\n' # noqa: E501
18491845
"\n"
1850-
"**R5. Empty / sentinel object.** Object is exactly one of: "
1851-
'"", "Unknown", "None", "Various", "Something", "N/A".\n'
1846+
"**R5. Content-free object.** Object provides no transcript-grounded claim — empty, " # noqa: E501
1847+
'whitespace-only, or a content-free placeholder (e.g. "Unknown", "None", "Various", "Something", "N/A").\n' # noqa: E501
18521848
'Example: `[7] {{"subject": "Alex", "predicate": "is_from", "object": "Unknown"}}` → DROP (R5) — include 7.\n' # noqa: E501
18531849
"\n"
1854-
"**R6. System entity ID.** Subject or object contains a dot-separated HA-style identifier " # noqa: E501
1855-
"(e.g. `media_player.sonos_office`, `sensor.temperature_kitchen`).\n"
1850+
"**R6. Namespaced system identifier.** Subject or object is a system / automation identifier — " # noqa: E501
1851+
"a dot-separated or URI-shaped namespaced token "
1852+
"(e.g. `media_player.sonos_office`, `sensor.temperature_kitchen`, `climate.kitchen_thermostat`) " # noqa: E501
1853+
"referring to a device, sensor, or service rather than a personal entity.\n"
18561854
'Example: `[8] {{"subject": "Alex", "predicate": "controls", "object": "media_player.sonos_office"}}` → DROP (R6) — include 8.\n' # noqa: E501
18571855
"\n"
18581856
"## Input\n"

tests/test_prompts_contract.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,17 +175,32 @@ def test_renders_without_format_errors(self):
175175
assert "{facts_json}" not in rendered
176176

177177
def test_lists_drop_rules(self):
178-
"""Plausibility judge relies on six numbered drop rules (R1-R6). Verify they
179-
all exist so a prompt edit that removes a rule is caught at unit-test time.
178+
"""Plausibility judge relies on six numbered drop rules (R1-R6).
179+
180+
The prior R4 ("Unresolved placeholder in real-name input") was
181+
tied to the constrained ``^(Person|City|Country|Org|Thing)_\\d+$``
182+
regex that became incoherent with the open-vocabulary anonymizer
183+
pivot. It was also structurally redundant with
184+
``_strip_residual_placeholders`` running inside ``_apply_bindings``
185+
at the deanon stage, before plausibility.
186+
187+
The grounding refactor revised the remaining rules: lexical
188+
token lists became illustrative parentheticals, and a new R3
189+
(transcript contradiction) closes the gap that the prior
190+
"no judgment calls" framing left open. The structure of the
191+
prompt and of the examples is unchanged.
192+
193+
Verify each rule's identifying substring still exists so a
194+
prompt edit that removes a rule is caught at unit-test time.
180195
"""
181196
tmpl = _load_prompt("sota_plausibility.txt", _DEFAULT_PLAUSIBILITY_PROMPT)
182197
required_rules = [
183198
"self-loop", # R1
184199
"name-swap", # R2
185-
"role leak", # R3
186-
"placeholder", # R4
187-
"sentinel", # R5
188-
"system entity", # R6
200+
"contradiction", # R3 — new transcript-grounded rule
201+
"conversation-role", # R4 — was "Role leak", grounded
202+
"content-free", # R5 — was "Empty / sentinel"
203+
"system identifier", # R6 — was "System entity ID"
189204
]
190205
for rule in required_rules:
191206
assert rule.lower() in tmpl.lower(), f"Plausibility prompt missing rule: {rule!r}"

0 commit comments

Comments
 (0)