package toolstream import ( "strings" "testing" ) // 波浪线围栏内的工具调用标签不应触发工具调用 func TestProcessToolSieveTildeFenceDoesNotTriggerToolCall(t *testing.T) { var state State chunks := []string{ "示例:\n~~~xml\n", "README.md\n", "~~~\n", "完毕。", } var events []Event for _, c := range chunks { events = append(events, ProcessChunk(&state, c, []string{"read_file"})...) } events = append(events, Flush(&state, []string{"read_file"})...) var textContent strings.Builder toolCalls := 0 for _, evt := range events { textContent.WriteString(evt.Content) toolCalls += len(evt.ToolCalls) } if toolCalls != 0 { t.Fatalf("expected tilde-fenced tool example to stay text, got %d tool calls", toolCalls) } if !strings.Contains(textContent.String(), "示例") || !strings.Contains(textContent.String(), "完毕") { t.Fatalf("expected surrounding text preserved, got %q", textContent.String()) } } // 4 反引号嵌套 3 反引号(内含工具标签)不应触发 func TestProcessToolSieveNestedFourBacktickFenceDoesNotTrigger(t *testing.T) { var state State input := "说明:\n````xml\n```\nx\n```\n````\n结束。" chunks := strings.SplitAfter(input, "\n") var events []Event for _, c := range chunks { events = append(events, ProcessChunk(&state, c, []string{"read_file"})...) } events = append(events, Flush(&state, []string{"read_file"})...) var textContent strings.Builder toolCalls := 0 for _, evt := range events { textContent.WriteString(evt.Content) toolCalls += len(evt.ToolCalls) } if toolCalls != 0 { t.Fatalf("expected 4-backtick fenced example to stay text, got %d tool calls", toolCalls) } } func TestProcessToolSieveMarkdownDocumentationExamplesDoNotTrigger(t *testing.T) { var state State chunks := []string{ "解析器支持多种工具调用格式。\n\n", "入口函数 `ParseToolCalls(text, availableToolNames)` 会返回调用列表。\n\n", "核心流程会解析 XML 格式的 `` / `` 标记。\n\n", "### 标准 XML 结构\n", "```xml\n", "\n", " \n", " config.json\n", " \n", "\n", "```\n\n", "DSML 风格形如 `...`,也可能提到 `` 包裹。\n", } var events []Event for _, c := range chunks { events = append(events, ProcessChunk(&state, c, []string{"read_file"})...) } events = append(events, Flush(&state, []string{"read_file"})...) var textContent strings.Builder toolCalls := 0 for _, evt := range events { textContent.WriteString(evt.Content) toolCalls += len(evt.ToolCalls) } if toolCalls != 0 { t.Fatalf("expected markdown documentation examples to stay text, got %d tool calls", toolCalls) } if !strings.Contains(textContent.String(), "标准 XML 结构") || !strings.Contains(textContent.String(), "DSML 风格") { t.Fatalf("expected documentation text preserved, got %q", textContent.String()) } } func TestProcessToolSieveInlineMarkdownToolCallSplitAcrossChunksDoesNotTrigger(t *testing.T) { var state State chunks := []string{ "示例:`", "README.md", "` 完毕。", } var events []Event for _, c := range chunks { events = append(events, ProcessChunk(&state, c, []string{"read_file"})...) } events = append(events, Flush(&state, []string{"read_file"})...) var textContent strings.Builder toolCalls := 0 for _, evt := range events { textContent.WriteString(evt.Content) toolCalls += len(evt.ToolCalls) } if toolCalls != 0 { t.Fatalf("expected split inline markdown tool example to stay text, got %d tool calls", toolCalls) } if !strings.Contains(textContent.String(), "") || !strings.Contains(textContent.String(), "完毕") { t.Fatalf("expected inline example text preserved, got %q", textContent.String()) } } func TestProcessToolSieveUnclosedInlineMarkdownBeforeToolDoesTrigger(t *testing.T) { var state State input := "note with stray ` before real call " + "real.md" var events []Event events = append(events, ProcessChunk(&state, input, []string{"read_file"})...) events = append(events, Flush(&state, []string{"read_file"})...) var textContent strings.Builder var calls []string for _, evt := range events { textContent.WriteString(evt.Content) for _, call := range evt.ToolCalls { if path, _ := call.Input["path"].(string); path != "" { calls = append(calls, path) } } } if len(calls) != 1 || calls[0] != "real.md" { t.Fatalf("expected real tool call after stray backtick, got %#v from events %#v", calls, events) } if !strings.Contains(textContent.String(), "stray ` before real call") { t.Fatalf("expected stray-backtick prefix preserved, got %q", textContent.String()) } } func TestProcessToolSieveUnclosedInlineMarkdownBeforeSplitToolDoesTriggerOnFlush(t *testing.T) { var state State chunks := []string{ "note with stray ` before real call ", "real.md", } var events []Event for _, c := range chunks { events = append(events, ProcessChunk(&state, c, []string{"read_file"})...) } events = append(events, Flush(&state, []string{"read_file"})...) var calls []string for _, evt := range events { for _, call := range evt.ToolCalls { if path, _ := call.Input["path"].(string); path != "" { calls = append(calls, path) } } } if len(calls) != 1 || calls[0] != "real.md" { t.Fatalf("expected split real tool call after stray backtick, got %#v from events %#v", calls, events) } }