Skip to content
This repository was archived by the owner on May 13, 2026. It is now read-only.

Commit 2c9127a

Browse files
fix(openai): strip leaked tool result markers
1 parent 3569ae1 commit 2c9127a

2 files changed

Lines changed: 22 additions & 1 deletion

File tree

internal/httpapi/openai/leaked_output_sanitize_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,24 @@ func TestSanitizeLeakedOutputRemovesFullwidthDelimitedMetaMarkers(t *testing.T)
3535
}
3636
}
3737

38+
func TestSanitizeLeakedOutputRemovesAssistantEndOfToolCallsMarkers(t *testing.T) {
39+
fw := "\uff5c"
40+
raw := "A<|Assistant_END_OF_TOOL_CALLS|>B<" + fw + "Assistant▁END▁OF▁TOOL_CALLS" + fw + ">C<|end_of_tool_calls|>D"
41+
got := sanitizeLeakedOutput(raw)
42+
if got != "ABCD" {
43+
t.Fatalf("unexpected sanitize result for assistant end-of-tool-calls markers: %q", got)
44+
}
45+
}
46+
47+
func TestSanitizeLeakedOutputRemovesFullToolResultSection(t *testing.T) {
48+
fw := "\uff5c"
49+
raw := "开始<" + fw + "Tool" + fw + ">[{\"content\":\"openjdk version 21\"}]<" + fw + "end▁of▁toolresults" + fw + ">结束"
50+
got := sanitizeLeakedOutput(raw)
51+
if got != "开始结束" {
52+
t.Fatalf("unexpected sanitize result for leaked tool result section: %q", got)
53+
}
54+
}
55+
3856
func TestSanitizeLeakedOutputRemovesThinkAndBosMarkers(t *testing.T) {
3957
raw := "A<think>B</think>C<|begin▁of▁sentence|>D<| begin_of_sentence |>E<|begin_of_sentence|>F"
4058
got := sanitizeLeakedOutput(raw)

internal/httpapi/openai/shared/leaked_output_sanitize.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
var emptyJSONFencePattern = regexp.MustCompile("(?is)```json\\s*```")
1111
var leakedToolCallArrayPattern = regexp.MustCompile(`(?is)\[\{\s*"function"\s*:\s*\{[\s\S]*?\}\s*,\s*"id"\s*:\s*"call[^"]*"\s*,\s*"type"\s*:\s*"function"\s*}\]`)
1212
var leakedToolResultBlobPattern = regexp.MustCompile(`(?is)<\s*\|\s*tool\s*\|\s*>\s*\{[\s\S]*?"tool_call_id"\s*:\s*"call[^"]*"\s*}`)
13+
var leakedToolResultSectionPattern = regexp.MustCompile(`(?is)<[\|\x{ff5c}]\s*tool\s*[\|\x{ff5c}]>[\s\S]*?<[\|\x{ff5c}]\s*end[_▁]of[_▁]tool[_▁]?results\s*[\|\x{ff5c}]>`)
1314

1415
var leakedThinkTagPattern = regexp.MustCompile(`(?is)</?\s*think\s*>`)
1516

@@ -29,7 +30,8 @@ var leakedThoughtMarkerPattern = regexp.MustCompile(`(?i)<[\|\x{ff5c}]\s*(?:begi
2930
// halfwidth or legacy U+FF5C fullwidth delimiters:
3031
// - ASCII underscore: <|end_of_sentence|>, <|end_of_toolresults|>, <|end_of_instructions|>
3132
// - U+2581 variant: <|end▁of▁sentence|>, <|end▁of▁toolresults|>, <|end▁of▁instructions|>
32-
var leakedMetaMarkerPattern = regexp.MustCompile(`(?i)<[\|\x{ff5c}]\s*(?:assistant|tool|end[_▁]of[_▁]sentence|end[_▁]of[_▁]thinking|end[_▁]of[_▁]thought|end[_▁]of[_▁]toolresults|end[_▁]of[_▁]instructions)\s*[\|\x{ff5c}]>`)
33+
// - compound assistant markers: <|Assistant_END_OF_TOOL_CALLS|>
34+
var leakedMetaMarkerPattern = regexp.MustCompile(`(?i)<[\|\x{ff5c}]\s*(?:assistant(?:[_▁]end[_▁]of[_▁]tool[_▁]?calls)?|tool|end[_▁]of[_▁]sentence|end[_▁]of[_▁]thinking|end[_▁]of[_▁]thought|end[_▁]of[_▁]tool[_▁]?results|end[_▁]of[_▁]tool[_▁]?calls|end[_▁]of[_▁]instructions)\s*[\|\x{ff5c}]>`)
3335

3436
// leakedAgentXMLBlockPatterns catch agent-style XML blocks that leak through
3537
// when the sieve fails to capture them. These are applied only to complete
@@ -52,6 +54,7 @@ func sanitizeLeakedOutput(text string) string {
5254
}
5355
out := emptyJSONFencePattern.ReplaceAllString(text, "")
5456
out = leakedToolCallArrayPattern.ReplaceAllString(out, "")
57+
out = leakedToolResultSectionPattern.ReplaceAllString(out, "")
5558
out = leakedToolResultBlobPattern.ReplaceAllString(out, "")
5659
out = stripDanglingThinkSuffix(out)
5760
out = leakedThinkTagPattern.ReplaceAllString(out, "")

0 commit comments

Comments
 (0)