fix: update Kimi tool call marker detection to handle both delimiter formats

This commit is contained in:
Roo Code 2025-12-13 09:13:00 +00:00
parent 464a7b5600
commit 582c639640
2 changed files with 56 additions and 9 deletions

View file

@ -7,11 +7,16 @@ import {
describe("kimi-tool-call-extractor", () => {
describe("hasKimiEmbeddedToolCalls", () => {
it("should return true when content contains tool call markers", () => {
it("should return true when content contains tool call markers with delimiters", () => {
const content = "Some reasoning <|tool_calls_section_begin|> stuff <|tool_calls_section_end|>"
expect(hasKimiEmbeddedToolCalls(content)).toBe(true)
})
it("should return true when content contains tool call markers without delimiters", () => {
const content = "Some reasoning tool_calls_section_begin stuff tool_calls_section_end"
expect(hasKimiEmbeddedToolCalls(content)).toBe(true)
})
it("should return false when content does not contain tool call markers", () => {
const content = "Just regular reasoning content without any tool calls"
expect(hasKimiEmbeddedToolCalls(content)).toBe(false)
@ -23,7 +28,7 @@ describe("kimi-tool-call-extractor", () => {
})
describe("extractKimiToolCalls", () => {
it("should extract single tool call from reasoning content", () => {
it("should extract single tool call from reasoning content with delimiters", () => {
const content = `Some reasoning here
<|tool_calls_section_begin|>
<|tool_call_begin|>functions.read_file:0<|tool_call_argument_begin|>{"files":[{"path":"test.txt"}]}<|tool_call_end|>
@ -41,7 +46,30 @@ More content after`
arguments: '{"files":[{"path":"test.txt"}]}',
},
})
expect(result.cleanedReasoningContent).not.toContain("<|tool_calls_section_begin|>")
expect(result.cleanedReasoningContent).not.toContain("tool_calls_section_begin")
expect(result.cleanedReasoningContent).toContain("Some reasoning here")
expect(result.cleanedReasoningContent).toContain("More content after")
})
it("should extract single tool call from reasoning content without delimiters", () => {
const content = `Some reasoning here
tool_calls_section_begin
tool_call_begin functions.read_file:0 tool_call_argument_begin {"files":[{"path":"test.txt"}]} tool_call_end
tool_calls_section_end
More content after`
const result = extractKimiToolCalls(content)
expect(result.toolCalls).toHaveLength(1)
expect(result.toolCalls[0]).toEqual({
id: "kimi-functions.read_file:0",
type: "function",
function: {
name: "read_file",
arguments: '{"files":[{"path":"test.txt"}]}',
},
})
expect(result.cleanedReasoningContent).not.toContain("tool_calls_section_begin")
expect(result.cleanedReasoningContent).toContain("Some reasoning here")
expect(result.cleanedReasoningContent).toContain("More content after")
})
@ -110,9 +138,9 @@ More content after`
const result = extractKimiToolCalls(content)
// The cleaned content should not contain tool call markers
expect(result.cleanedReasoningContent).not.toContain("<|tool_calls_section_begin|>")
expect(result.cleanedReasoningContent).not.toContain("<|tool_calls_section_end|>")
expect(result.cleanedReasoningContent).not.toContain("<|tool_call_begin|>")
expect(result.cleanedReasoningContent).not.toContain("tool_calls_section_begin")
expect(result.cleanedReasoningContent).not.toContain("tool_calls_section_end")
expect(result.cleanedReasoningContent).not.toContain("tool_call_begin")
expect(result.cleanedReasoningContent).toContain("Before tool calls")
expect(result.cleanedReasoningContent).toContain("After tool calls")
})
@ -147,6 +175,18 @@ End reasoning`
expect(result.toolCalls[0].function.name).toBe("tool1")
expect(result.toolCalls[1].function.name).toBe("tool2")
})
it("should handle markers without delimiters", () => {
const content = `Some reasoning
tool_calls_section_begin
tool_call_begin functions.read_file:0 tool_call_argument_begin {"path":"test.txt"} tool_call_end
tool_calls_section_end`
const result = extractKimiToolCalls(content)
expect(result.toolCalls).toHaveLength(1)
expect(result.toolCalls[0].function.name).toBe("read_file")
})
})
describe("isKimiThinkingModel", () => {

View file

@ -6,6 +6,9 @@
* - <|tool_call_begin|> ... <|tool_call_end|> wraps each individual tool call
* - <|tool_call_argument_begin|> marks the start of arguments JSON
*
* Note: The model may output these markers with or without the <| |> delimiters,
* so patterns are designed to handle both formats.
*
* Format example:
* <|tool_calls_section_begin|>
* <|tool_call_begin|>functions.read_file:0<|tool_call_argument_begin|>{"files":[{"path":"test.txt"}]}<|tool_call_end|>
@ -36,9 +39,11 @@ export interface KimiToolCallExtractionResult {
/**
* Checks if the content contains Kimi K2 Thinking model's embedded tool call markers.
* Note: The model may output markers with or without <| |> delimiters, so we check
* for the core marker text only.
*/
export function hasKimiEmbeddedToolCalls(content: string): boolean {
return content.includes("<|tool_calls_section_begin|>")
return content.includes("tool_calls_section_begin")
}
/**
@ -58,7 +63,8 @@ export function extractKimiToolCalls(content: string): KimiToolCallExtractionRes
const toolCalls: KimiToolCall[] = []
// Pattern to match tool call sections
const sectionPattern = /<\|tool_calls_section_begin\|>(.*?)<\|tool_calls_section_end\|>/gs
// Note: Handles both <|marker|> and marker formats (delimiters are optional)
const sectionPattern = /(?:<\|)?tool_calls_section_begin(?:\|>)?(.*?)(?:<\|)?tool_calls_section_end(?:\|>)?/gs
const toolCallSections = content.match(sectionPattern)
if (!toolCallSections || toolCallSections.length === 0) {
@ -70,8 +76,9 @@ export function extractKimiToolCalls(content: string): KimiToolCallExtractionRes
// Pattern to extract individual tool calls
// Format: <|tool_call_begin|>functions.tool_name:index<|tool_call_argument_begin|>JSON_ARGS<|tool_call_end|>
// Note: Handles both <|marker|> and marker formats (delimiters are optional)
const funcCallPattern =
/<\|tool_call_begin\|>\s*([\w.]+:\d+)\s*<\|tool_call_argument_begin\|>\s*(.*?)\s*<\|tool_call_end\|>/gs
/(?:<\|)?tool_call_begin(?:\|>)?\s*([\w.]+:\d+)\s*(?:<\|)?tool_call_argument_begin(?:\|>)?\s*(.*?)\s*(?:<\|)?tool_call_end(?:\|>)?/gs
for (const section of toolCallSections) {
let match