mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: strip XML tags from kimi-k2-thinking model reasoning output
- Added stripXmlTags method to remove XML tags from reasoning content - Applied XML tag stripping specifically for moonshot/kimi-k2-thinking model - Added tests to verify XML tags are stripped for kimi model but preserved for others - Fixes #9172 where XML tags were displayed in thinking prompts
This commit is contained in:
parent
6e6341346e
commit
bfabe5da76
2 changed files with 125 additions and 6 deletions
|
|
@ -130,6 +130,109 @@ describe("OpenRouterHandler", () => {
|
|||
})
|
||||
|
||||
describe("createMessage", () => {
|
||||
it("strips XML tags from reasoning content for kimi-k2-thinking model", async () => {
|
||||
const handler = new OpenRouterHandler({
|
||||
...mockOptions,
|
||||
openRouterModelId: "moonshot/kimi-k2-thinking",
|
||||
})
|
||||
|
||||
const mockStream = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: {
|
||||
reasoning:
|
||||
"<thinking>This is reasoning content with <tag>XML tags</tag></thinking>",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: {
|
||||
content: "Regular content",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
yield {
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 20,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
|
||||
;(OpenAI as any).prototype.chat = {
|
||||
completions: { create: mockCreate },
|
||||
} as any
|
||||
|
||||
const chunks = []
|
||||
const generator = handler.createMessage("test", [])
|
||||
for await (const chunk of generator) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
// Check that reasoning content has XML tags stripped
|
||||
expect(chunks[0]).toEqual({
|
||||
type: "reasoning",
|
||||
text: "This is reasoning content with XML tags",
|
||||
})
|
||||
expect(chunks[1]).toEqual({
|
||||
type: "text",
|
||||
text: "Regular content",
|
||||
})
|
||||
})
|
||||
|
||||
it("does not strip XML tags from reasoning content for other models", async () => {
|
||||
const handler = new OpenRouterHandler({
|
||||
...mockOptions,
|
||||
openRouterModelId: "openai/gpt-4",
|
||||
})
|
||||
|
||||
const mockStream = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield {
|
||||
choices: [
|
||||
{
|
||||
delta: {
|
||||
reasoning:
|
||||
"<thinking>This is reasoning content with <tag>XML tags</tag></thinking>",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
yield {
|
||||
usage: {
|
||||
prompt_tokens: 10,
|
||||
completion_tokens: 20,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
|
||||
;(OpenAI as any).prototype.chat = {
|
||||
completions: { create: mockCreate },
|
||||
} as any
|
||||
|
||||
const chunks = []
|
||||
const generator = handler.createMessage("test", [])
|
||||
for await (const chunk of generator) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
// Check that reasoning content keeps XML tags for non-kimi models
|
||||
expect(chunks[0]).toEqual({
|
||||
type: "reasoning",
|
||||
text: "<thinking>This is reasoning content with <tag>XML tags</tag></thinking>",
|
||||
})
|
||||
})
|
||||
|
||||
it("generates correct stream chunks", async () => {
|
||||
const handler = new OpenRouterHandler(mockOptions)
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,15 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
|
|||
this.client = new OpenAI({ baseURL, apiKey, defaultHeaders: DEFAULT_HEADERS })
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip XML tags from reasoning content for models that include them
|
||||
* Some models like kimi-k2-thinking include XML tags in their reasoning output
|
||||
*/
|
||||
private stripXmlTags(text: string): string {
|
||||
// Remove XML tags but preserve the content between them
|
||||
return text.replace(/<[^>]*>/g, "")
|
||||
}
|
||||
|
||||
override async *createMessage(
|
||||
systemPrompt: string,
|
||||
messages: Anthropic.Messages.MessageParam[],
|
||||
|
|
@ -180,14 +189,21 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
|
|||
throw new Error(`OpenRouter API Error ${error?.code}: ${error?.message}`)
|
||||
}
|
||||
|
||||
const delta = chunk.choices[0]?.delta
|
||||
const delta = chunk.choices?.[0]?.delta
|
||||
|
||||
if ("reasoning" in delta && delta.reasoning && typeof delta.reasoning === "string") {
|
||||
yield { type: "reasoning", text: delta.reasoning }
|
||||
}
|
||||
if (delta) {
|
||||
const modelId = this.options.openRouterModelId ?? openRouterDefaultModelId
|
||||
|
||||
if (delta?.content) {
|
||||
yield { type: "text", text: delta.content }
|
||||
if ("reasoning" in delta && delta.reasoning && typeof delta.reasoning === "string") {
|
||||
// Strip XML tags for kimi-k2-thinking model which includes them in reasoning output
|
||||
const reasoningText =
|
||||
modelId === "moonshot/kimi-k2-thinking" ? this.stripXmlTags(delta.reasoning) : delta.reasoning
|
||||
yield { type: "reasoning", text: reasoningText }
|
||||
}
|
||||
|
||||
if (delta.content) {
|
||||
yield { type: "text", text: delta.content }
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue