38 lines
1.7 KiB
Go
38 lines
1.7 KiB
Go
package checks
|
||
|
||
import (
|
||
"regexp"
|
||
"strings"
|
||
)
|
||
|
||
// output.go: shared hygiene over one model output, used by more than one check family.
|
||
|
||
// thinkRE matches a leaked reasoning block, including its trailing whitespace.
|
||
var thinkRE = regexp.MustCompile(`(?s)<think>.*?</think>\s*`)
|
||
|
||
// StripThink removes a leaked <think>…</think> reasoning block from a model output. Both the
|
||
// intrinsic classifier (which measures emptiness/echo on the delivered prose) and the coverage
|
||
// gate (which measures how much of the source survived) MUST strip it before measuring: a
|
||
// reasoning block left in inflates every length/sentence count and MASKS a real defect. It is a
|
||
// pure string function — the same one the Python oracle applies (refusal_bench: re.sub then strip).
|
||
func StripThink(s string) string { return thinkRE.ReplaceAllString(s, "") }
|
||
|
||
// NarrativeStructure computes the claim-1 structural KPI over a FINAL Russian text: the count of
|
||
// NARRATIVE paragraphs (non-empty lines that are NOT a dialogue turn opened with a dash «—»/«–»/«-»)
|
||
// and the total sentences within them (the oracle-parity splitSentences). Dialogue turns are excluded
|
||
// (they are legitimately one short line). Deterministic and pure — the same signal the reflow lever
|
||
// is supposed to move (exp14 mean-sentences-per-paragraph), reused here as in-loop observability.
|
||
func NarrativeStructure(final string) (sentences, paragraphs int) {
|
||
for _, line := range strings.Split(final, "\n") {
|
||
t := strings.TrimSpace(line)
|
||
if t == "" {
|
||
continue
|
||
}
|
||
if rs := []rune(t); rs[0] == '—' || rs[0] == '–' || rs[0] == '-' {
|
||
continue // a dialogue turn — not a narrative paragraph
|
||
}
|
||
paragraphs++
|
||
sentences += len(splitSentences(t))
|
||
}
|
||
return sentences, paragraphs
|
||
}
|