Просмотр исходного кода

Don't output newline-only reasoning (#8990)

Co-authored-by: Roo Code <[email protected]>
Matt Rubens 1 месяц назад
Родитель
Сommit
5b753f5855

+ 100 - 0
src/api/providers/__tests__/base-openai-compatible-provider.spec.ts

@@ -228,6 +228,106 @@ describe("BaseOpenAiCompatibleProvider", () => {
 		})
 	})
 
+	describe("reasoning_content field", () => {
+		it("should filter out whitespace-only reasoning_content", async () => {
+			mockCreate.mockImplementationOnce(() => {
+				return {
+					[Symbol.asyncIterator]: () => ({
+						next: vi
+							.fn()
+							.mockResolvedValueOnce({
+								done: false,
+								value: { choices: [{ delta: { reasoning_content: "\n" } }] },
+							})
+							.mockResolvedValueOnce({
+								done: false,
+								value: { choices: [{ delta: { reasoning_content: "   " } }] },
+							})
+							.mockResolvedValueOnce({
+								done: false,
+								value: { choices: [{ delta: { reasoning_content: "\t\n  " } }] },
+							})
+							.mockResolvedValueOnce({
+								done: false,
+								value: { choices: [{ delta: { content: "Regular content" } }] },
+							})
+							.mockResolvedValueOnce({ done: true }),
+					}),
+				}
+			})
+
+			const stream = handler.createMessage("system prompt", [])
+			const chunks = []
+			for await (const chunk of stream) {
+				chunks.push(chunk)
+			}
+
+			// Should only have the regular content, not the whitespace-only reasoning
+			expect(chunks).toEqual([{ type: "text", text: "Regular content" }])
+		})
+
+		it("should yield non-empty reasoning_content", async () => {
+			mockCreate.mockImplementationOnce(() => {
+				return {
+					[Symbol.asyncIterator]: () => ({
+						next: vi
+							.fn()
+							.mockResolvedValueOnce({
+								done: false,
+								value: { choices: [{ delta: { reasoning_content: "Thinking step 1" } }] },
+							})
+							.mockResolvedValueOnce({
+								done: false,
+								value: { choices: [{ delta: { reasoning_content: "\n" } }] },
+							})
+							.mockResolvedValueOnce({
+								done: false,
+								value: { choices: [{ delta: { reasoning_content: "Thinking step 2" } }] },
+							})
+							.mockResolvedValueOnce({ done: true }),
+					}),
+				}
+			})
+
+			const stream = handler.createMessage("system prompt", [])
+			const chunks = []
+			for await (const chunk of stream) {
+				chunks.push(chunk)
+			}
+
+			// Should only yield the non-empty reasoning content
+			expect(chunks).toEqual([
+				{ type: "reasoning", text: "Thinking step 1" },
+				{ type: "reasoning", text: "Thinking step 2" },
+			])
+		})
+
+		it("should handle reasoning_content with leading/trailing whitespace", async () => {
+			mockCreate.mockImplementationOnce(() => {
+				return {
+					[Symbol.asyncIterator]: () => ({
+						next: vi
+							.fn()
+							.mockResolvedValueOnce({
+								done: false,
+								value: { choices: [{ delta: { reasoning_content: "  content with spaces  " } }] },
+							})
+							.mockResolvedValueOnce({ done: true }),
+					}),
+				}
+			})
+
+			const stream = handler.createMessage("system prompt", [])
+			const chunks = []
+			for await (const chunk of stream) {
+				chunks.push(chunk)
+			}
+
+			// Should yield reasoning with spaces (only pure whitespace is filtered)
+			expect(chunks).toEqual([{ type: "reasoning", text: "  content with spaces  " }])
+		})
+	})
+
 	describe("Basic functionality", () => {
 		it("should create stream with correct parameters", async () => {
 			mockCreate.mockImplementationOnce(() => {

+ 5 - 2
src/api/providers/base-openai-compatible-provider.ts

@@ -124,8 +124,11 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
 				}
 			}
 
-			if (delta && "reasoning_content" in delta && delta.reasoning_content) {
-				yield { type: "reasoning", text: (delta.reasoning_content as string | undefined) || "" }
+			if (delta && "reasoning_content" in delta) {
+				const reasoning_content = (delta.reasoning_content as string | undefined) || ""
+				if (reasoning_content?.trim()) {
+					yield { type: "reasoning", text: reasoning_content }
+				}
 			}
 
 			if (chunk.usage) {