fix(turnreply): 产出侧不再产出未配对的段落标签(安卓报障的回执)
安卓客户端登记(他们文档 02 末节):一条助手消息的 content 是
`<p><p>正文</p>`,按他们的解析契约(非贪婪)必然把 `<p>正文` 当内容 ⇒ 用户
直接看到字面 `<p>`。任何按该契约定实现的消费方都会踩。
根因(两半都证实,都在同一处):
- (a) 规整器**原样放过**模型吐出的 `<p><p>X</p>`:段块正则非贪婪,把它当成
一个"已完整包裹"的块逐字保留(旧的覆盖检查用同一个正则 ⇒ 护栏本身是盲的);
- (b) 规整器**亲手制造**该形状:末段漏 `</p>` 时无条件套一层 ⇒ `<p><p>X</p>`。
实证:模型吐 `<p>那现在…?`(无闭标签)经旧代码规整后与上报串**逐字节相同**;
- (c) 顺带修一个幂等漏洞:落单 `</p>` 旧行为会越跑越多(`<p></p></p>`)。
修法(新增 paragraph_repair.go:配对修复 + 接线):
`<p><p>X</p>`⇒`<p>X</p>`;`<p>X`⇒补 `</p>`;`<p>正文<p>`⇒合并;`<p>A</p><p>`⇒
丢空开标签;`<p></p>`⇒整对丢;落单 `</p>`⇒丢标签正文不动。不吞正文护栏:
`<p`(无 `>`)/`<p2>`/`1 < 2`/`a<b>`/代码围栏/`<action>` 内 `<p>`/自闭合
`<p/>` 一律原样(每条都有用例)。`normalizePiece` 改为"补闭不补开"——在造出
畸形的那一点上根治。
验收(主会话复跑 + 亲自变异):go build ./... + vet 绿;turnreply /
gateway 全量 / channelbridge 三包绿;我用 -overlay 摘掉配对修复接线 ⇒
TestNormalizeParagraphs_* 变红。生产库只读核查:177 行 assistant 里**仅 1 行**
畸形(各消费方已容错)⇒ 不做一次性修复脚本。
⚠️ 未部署:跑着的 :10942 仍是旧产物 ⇒ 畸形仍会产出,安卓端的容错请继续保留。
This commit is contained in:
1 parent
378faf212e
commit
8051906afa
4 files changed
+449
-3
No files matched your search
@@ -107,6 +107,40 @@ func TestTurnEnd_FullyBareReplyIsRegularized(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestTurnEnd_UnpairedParagraphTagIsMergedBeforePersist 是必测⑤(网关侧,真 loop + 真
|
||||
// SQLite):上报的**未配对段落标签**形状(`<p><p>正文</p>`)落库前被合并成一个 `<p>`。
|
||||
// 断言的就是客户端从历史接口拿到的那份 content —— 「任何按非贪婪 `<p>` 契约解析的消费方
|
||||
// 都会看到字面 `<p>`」这条症状在**产出侧**被掐掉。
|
||||
//
|
||||
// 字面量与 turnreply/paragraph_repair_test.go 的 reportedUnpairedReply / …Fixed
|
||||
// 逐字相同(两侧共用同一份上报原串)。
|
||||
func TestTurnEnd_UnpairedParagraphTagIsMergedBeforePersist(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const reported = "<p><action>满意地点点头</action></p>\n" +
|
||||
"<p>真乖♪ 这可是今天最让人家放心的一句。</p>\n" +
|
||||
"<p><p>那现在——想让人家陪你看会儿晚霞,还是安静坐着发会儿呆?</p>"
|
||||
const want = "<p><action>满意地点点头</action></p>\n" +
|
||||
"<p>真乖♪ 这可是今天最让人家放心的一句。</p>\n" +
|
||||
"<p>那现在——想让人家陪你看会儿晚霞,还是安静坐着发会儿呆?</p>"
|
||||
f := newKBTurnFixture(t, &kbScriptAdapter{toolSteps: 0, final: reported}, 0)
|
||||
res, err := f.submit(t)
|
||||
if err != nil {
|
||||
t.Fatalf("回合不该报错: %v", err)
|
||||
}
|
||||
assistant := f.assertUserAndSingleAssistant(t)
|
||||
if assistant.Content != want {
|
||||
t.Fatalf("落库内容 =\n%q\nwant\n%q", assistant.Content, want)
|
||||
}
|
||||
// res.Text 与落库同值 ⇒ REST reply / WS 事件 / 续跑尾部拿到的是同一份。
|
||||
if res.Text != want {
|
||||
t.Fatalf("Result.Text =\n%q\nwant 与落库同一份\n%q", res.Text, want)
|
||||
}
|
||||
if strings.Contains(assistant.Content, "<p><p>") {
|
||||
t.Fatalf("落库内容里仍有双开段落标签:%q", assistant.Content)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTurnEnd_NonChatModeReplyKeepsItsShape 守住规整的**作用范围**:非 chat 模式
|
||||
// (code/home/work/docs…)的消息在客户端是**按原样整体渲染**的(design/07 §2),
|
||||
// 往里塞 `<p>` 只会在那些形态里露出野生标签 —— 因此规整只对 chat 模式生效,
|
||||
|
||||
Reference in new issue
Block a user