74a70880a8
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
139 lines
6.8 KiB
Markdown
139 lines
6.8 KiB
Markdown
# Phrasing evaluation — 31-07-2026
|
|
|
|
How Maven words a nudge, measured instead of argued. Counterpart to
|
|
`ROUTING-EVAL-31-07-2026.md`.
|
|
|
|
- Fixture + scorer: `internal/phraser/eval/` (`nudges_v1.json`, 15 cases; `eval.go`, `checks.go`)
|
|
- Reproduce: `MAVEN_LLM_URL=http://127.0.0.1:18099 make eval-phrasing`
|
|
- Model: Qwen3.5-0.8B Q4_K_M, the resident model. Not swapped.
|
|
- Commit: `a40bc55` (prompt fix)
|
|
|
|
Every check is a string or length test a human can read and disagree with. No model
|
|
grades another model here.
|
|
|
|
## Result
|
|
|
|
| | before | after |
|
|
|---|---|---|
|
|
| **cases passing every check** | **0/15** | **13/15** |
|
|
| mood in enum | 6/15 | 15/15 |
|
|
| Russian | 2/15 | 14/15 |
|
|
| length (≤120 chars, ≤16 words) | 13/15 | 15/15 |
|
|
| feminine self-reference | 15/15 | 15/15 |
|
|
| no cringe | 13/15 | 15/15 |
|
|
| on topic | 6/15 | 13/15 |
|
|
| p50 latency | 11.4s | 11.4s |
|
|
|
|
Latency did not move and is not good. 11s to word one nudge on this box.
|
|
|
|
## The bug reproduced
|
|
|
|
Yes, exactly as reported. 7 of 15 messages were the literal string `"..."`, and one was
|
|
`"full voice message"`. Both are text copied straight out of the prompt.
|
|
|
|
The system prompt said:
|
|
|
|
```
|
|
Respond ONLY with valid JSON: {"response": "full voice message", "mood": "neutral"}
|
|
```
|
|
|
|
and the user prompt said:
|
|
|
|
```
|
|
Respond as JSON: {"response": "...", "mood": "..."}
|
|
```
|
|
|
|
A 0.8B does not read `"..."` as "put your answer here". It reads it as the answer. The
|
|
prompt was a worked example whose worked part was blank, so the model filled the slot by
|
|
copying. This is the whole of finding 1.
|
|
|
|
## What else was wrong
|
|
|
|
Four separate faults, all prompt-side:
|
|
|
|
1. **Placeholder echo** (7 cases) — above.
|
|
2. **Wrong language** (13/15 failed the language check). The prompt was entirely English
|
|
and said "in the user's language (Russian or English)". The model picked English. It is
|
|
never English: the nudge is spoken by a Russian piper voice.
|
|
3. **Rule names are English identifiers.** `netdata_critical`, `service_down`, `break` went
|
|
into the prompt raw. The model cannot nudge about a topic it has not been told in words,
|
|
so 9/15 were off topic. The daemon knows what its own rules mean; now it says so.
|
|
4. **Mood invented** (`"warm"`, twice). The enum was listed in a parenthesis at the end of
|
|
an English sentence. Now it is its own line: "ровно одно из: neutral, happy, thinking,
|
|
tired, confused."
|
|
|
|
Plus two non-prompt faults the run exposed:
|
|
|
|
- **The no-parse fallback was English.** When the model returned nothing usable, the body
|
|
became `fmt.Sprintf("%s — %s", rule, sev)` — `"water — care"` — and that string went to
|
|
a Russian TTS. Now it falls back to plain Russian.
|
|
- **Durations were English.** `humanDur` returns "3 hours"; it was landing verbatim inside
|
|
Russian sentences. Nudges now use a Russian formatter.
|
|
|
|
## Three iterations, and what each taught
|
|
|
|
| | score | change |
|
|
|---|---|---|
|
|
| baseline | 0/15 | — |
|
|
| iter 1 | 2/15 | Russian prompt, filled-in examples, Russian durations |
|
|
| iter 2 | 11/15 | required keyword per rule, one example instead of five, Russian fallback |
|
|
| iter 3 | **13/15** | examples moved to topics that are not rules |
|
|
|
|
The interesting step is 1 → 2. Fixing the placeholder did not fix the disease, it moved it:
|
|
the model stopped copying `"..."` and started copying my first example instead. Five nudges
|
|
in a row came back as `"Ты не пил воду три часа. Налей стакан."` regardless of the rule.
|
|
|
|
**A small model copies the nearest concrete text in its prompt.** That is one failure mode
|
|
with two symptoms. The fix that stuck was making the examples about laundry and a laptop
|
|
battery — topics no rule ever produces, so copying them is visible in the score rather than
|
|
invisibly passing the water cases.
|
|
|
|
## Do not oversell 13/15
|
|
|
|
Seven of the thirteen passes are the **deterministic fallback**, not the model:
|
|
`"Напоминаю: таблетки."`, `"Сервис не отвечает."`, `"Критический алярм: проверь диск."`,
|
|
`"Ты давно не пил воду."`. Those are strings this commit added to Go. The model returned
|
|
nothing parseable and the fallback scored.
|
|
|
|
So the honest reading is roughly **6/15 from the model, 7/15 from a fallback, 2/15 failing**.
|
|
The prompt fix is real — `"..."` is nearly gone and the language and mood checks are clean —
|
|
but a large part of the jump is that failure now degrades into Russian instead of into
|
|
`"water — care"`. That is a genuine improvement for the operator and a weak one for the model.
|
|
|
|
The two remaining failures: one `"..."` recurrence (`routine-stretch`) and one meal nudge
|
|
that never says food.
|
|
|
|
## Broken, found, not fixed
|
|
|
|
1. **`checkFeminine` only catches half the constraint.** It scans for masculine
|
|
self-reference and passed 15/15 both runs — but three messages address the *owner* in
|
|
the feminine: "ты давно не отдыхал**а**", "он не ел". The owner is a man. The check has
|
|
no second-person gender test, so this scores clean while being exactly the persona
|
|
failure the constraint exists to prevent. This is the most important gap in the harness.
|
|
2. **Grammar is not checked at all, and it is bad.** `"Он не ел 11 дней"` (it was 11 hours),
|
|
`"Сонуждились 7 дней"` (not a word), `"Они забыли воду"` (wrong person entirely). Every
|
|
one of these passes all six checks. The fixture measures properties, not fluency, and at
|
|
0.8B fluency is the binding constraint.
|
|
3. **Unit confusion.** The model turns hours into days about a third of the time. The
|
|
prompt now says "11 ч"; it reads it as days.
|
|
4. **11s p50.** Unchanged and untouched here. A nudge the model takes eleven seconds to
|
|
word has missed its moment. Worth its own task.
|
|
5. **The keyword hint is close to teaching to the test.** `ruleKeywords` names the word the
|
|
on-topic check looks for. It is defensible — the daemon genuinely knows its rule topics
|
|
and the model genuinely cannot infer them from `netdata_critical` — but the on-topic
|
|
number is softer than the others because of it.
|
|
|
|
## Next steps
|
|
|
|
1. **Add a second-person gender check** to `checks.go`. Finding 1 above. Until it exists the
|
|
feminine column means less than it looks like.
|
|
2. **Decide whether the fallback should count as a pass.** Right now `Score` cannot tell a
|
|
model answer from a fallback. Either mark fallback bodies in `PhrasedNudge` or count them
|
|
in their own column. Without that, any future prompt change can score well by failing
|
|
more.
|
|
3. **Attack the 11s.** Nudge phrasing is short and non-interactive; thinking off is the first
|
|
thing to try, as it was for routing (#376).
|
|
4. **Re-measure when #122 lands.** The CPT'd Qwen3-1.7B is the target. 13/15 with seven
|
|
fallbacks is the floor it has to beat, and the fluency problems above are the ones a
|
|
bigger, Russian-trained checkpoint should actually fix.
|