fix(dingtalk): 推理正文边界支持「首行即正文」并补齐卡片结构诊断 + 流式分帧重放守卫
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
package reasoningmode
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestReplayProductionCorpusThroughFeed replays production reasoning answers
|
||||
// through the streaming feed the card pump actually uses, and checks two
|
||||
// properties of every frame the card receives:
|
||||
//
|
||||
// 1. a frame that already contains a structural body line must start at that
|
||||
// body (no narration glued in front of it). A frame that shows narration
|
||||
// above the body is exactly the "the last reasoning sentence stays on the
|
||||
// card" defect.
|
||||
// 2. once a frame is body-only, no later frame may go back to showing
|
||||
// narration.
|
||||
//
|
||||
// Enable with REASONING_CORPUS=<path to reasoning_corpus.jsonl>.
|
||||
func TestReplayProductionCorpusThroughFeed(t *testing.T) {
|
||||
path := os.Getenv("REASONING_CORPUS")
|
||||
if path == "" {
|
||||
t.Skip("set REASONING_CORPUS to replay production answers")
|
||||
}
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
t.Fatalf("open corpus: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
chunkSizes := []int{11, 47, 199, 1001}
|
||||
var rows, frames, bodyFrames int
|
||||
type violation struct {
|
||||
id string
|
||||
size int
|
||||
marks bool
|
||||
prefix int
|
||||
bytes int
|
||||
}
|
||||
var violations []violation
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Buffer(make([]byte, 0, 1<<20), 1<<22)
|
||||
for scanner.Scan() {
|
||||
var row struct {
|
||||
ID string `json:"id"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
if err := json.Unmarshal(scanner.Bytes(), &row); err != nil {
|
||||
t.Fatalf("decode row: %v", err)
|
||||
}
|
||||
rows++
|
||||
for _, size := range chunkSizes {
|
||||
for _, marks := range []bool{false, true} {
|
||||
feed := NewPartialFeed("reasoning")
|
||||
bodySeen := false
|
||||
for start := 0; start < len(row.Content); start += size {
|
||||
end := start + size
|
||||
if end > len(row.Content) {
|
||||
end = len(row.Content)
|
||||
}
|
||||
frame := feed.OnAnswer(row.Content[:end])
|
||||
frames++
|
||||
if firstStructuralLine(frame) == "" {
|
||||
// no body in this frame yet: narration only, expected.
|
||||
continue
|
||||
}
|
||||
offset, ok := BodyStartOffset(frame)
|
||||
prefix := -1
|
||||
if ok {
|
||||
prefix = len([]rune(frame[:offset]))
|
||||
}
|
||||
if ok && prefix == 0 {
|
||||
bodySeen = true
|
||||
bodyFrames++
|
||||
continue
|
||||
}
|
||||
// The frame carries a structural line but does not start at
|
||||
// it: narration is still visible above the body.
|
||||
violations = append(violations, violation{
|
||||
id: row.ID,
|
||||
size: size,
|
||||
marks: marks,
|
||||
prefix: prefix,
|
||||
bytes: len(frame),
|
||||
})
|
||||
_ = bodySeen
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
t.Fatalf("scan corpus: %v", err)
|
||||
}
|
||||
t.Logf("rows=%d frames=%d body_only_frames=%d narration_above_body_frames=%d", rows, frames, bodyFrames, len(violations))
|
||||
for i, v := range violations {
|
||||
if i >= 8 {
|
||||
t.Logf("... %d more", len(violations)-i)
|
||||
break
|
||||
}
|
||||
t.Logf("violation id=%s chunk=%d step_boundaries=%t body_prefix_runes=%d bytes=%d", v.id, v.size, v.marks, v.prefix, v.bytes)
|
||||
}
|
||||
if len(violations) > 0 {
|
||||
t.Errorf("%d frames showed narration above the answer body", len(violations))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user