fix(reasoning): never cut a lead-in that carries an image

This commit is contained in:
姚章浩
2026-09-18 16:49:16 +08:00
parent 5ccaeb3b96
commit 48b741984f
2 changed files with 19 additions and 0 deletions
@@ -63,6 +63,12 @@ func SplitReasoningAnswerObserved(content string, observation NarrationObservati
// The stream showed no narration at all: the whole answer is body.
return content, true
}
// An image reference in front of the boundary is answer content (the
// agent published a picture in its lead-in), never step narration, so
// the safe answer is to keep everything.
if strings.Contains(content[:offset], "![") {
return content, true
}
body := strings.TrimLeft(content[offset:], "\r\n")
if strings.TrimSpace(body) != "" {
return body, true
@@ -68,6 +68,19 @@ func TestNarrationObservationFallsBackWithoutUsableBoundary(t *testing.T) {
}
}
func TestNarrationObservationKeepsImageLeadIn(t *testing.T) {
// The reported production shape: the lead-in carries the answer prose plus
// the picture, and the boundary observed by the stream only lands after it.
content := "根据知识库中《末端负载曲线-CRA系列》文档,以下是 **CR5A 的末端负载曲线图**:\n\n" +
"![CR5A 末端负载曲线](resource://FsVe56UYMz64sm8NsUZNrg)\n\n" +
"### 如何查看该负载曲线\n负载能力与偏心距离成负相关。\n"
offset := strings.Index(content, "### 如何查看该负载曲线")
observation := NarrationObservation{Observed: true, AnswerRuns: 4, ToolEvents: 6, LastRunStart: offset, LastRunClean: true}
if got := TrimReasoningNarrationObserved(content, observation); got != content {
t.Fatalf("image lead-in must survive the trim, got %q", got)
}
}
func TestNarrationObservationKeepsSingleRunAnswer(t *testing.T) {
// The quick-answer agent publishes the whole answer as a single answer run
// (with tool_call/tool_result steps around it). Nothing in that stream is