From 520da01be7ce09df263c8cbe74684f152c3d7c19 Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Tue, 19 May 2026 00:24:13 +0700 Subject: [PATCH] fix: handle message_id with extra wrapping quotes and request strict JSON output without reasoning --- src/moderation/llmModerationClient.ts | 18 ++++-- tests/moderation/llmModerationClient.test.ts | 59 ++++++++++++++++++++ 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/moderation/llmModerationClient.ts b/src/moderation/llmModerationClient.ts index 602adb0..5229601 100644 --- a/src/moderation/llmModerationClient.ts +++ b/src/moderation/llmModerationClient.ts @@ -132,6 +132,13 @@ export function parseModerationResponse( } let finalId = String(message_id).trim(); + // Remove wrapping double quotes if any (common in some LLM outputs) + if (finalId.startsWith('"') && finalId.endsWith('"')) { + finalId = finalId.slice(1, -1).trim(); + } + if (finalId.startsWith("'") && finalId.endsWith("'")) { + finalId = finalId.slice(1, -1).trim(); + } if (finalId.startsWith("[") && finalId.endsWith("]")) { finalId = finalId.slice(1, -1).trim(); } @@ -335,6 +342,7 @@ Each result must have: - score: confidence score from 0 to 1 - analysis: brief explanation +Do not include reasoning, analysis steps, markdown, prose, XML tags, or comments. Return ONLY valid JSON, no other text.`; // Check for image attachments to support multimodal analysis @@ -456,11 +464,11 @@ Return ONLY valid JSON, no other text.`; content: messageContent, }, ], - temperature: 0.6, - top_p: 0.95, - max_tokens: 65536, - reasoning_budget: 16384, - chat_template_kwargs: { enable_thinking: true }, + temperature: 0, + top_p: 1, + max_tokens: 8192, + response_format: { type: "json_object" }, + chat_template_kwargs: { enable_thinking: false }, }), }, ); diff --git a/tests/moderation/llmModerationClient.test.ts b/tests/moderation/llmModerationClient.test.ts index a0afe46..72c3c3d 100644 --- a/tests/moderation/llmModerationClient.test.ts +++ b/tests/moderation/llmModerationClient.test.ts @@ -320,6 +320,26 @@ describe("parseModerationResponse", () => { expect(result).toHaveLength(1); expect(result[0].messageId).toBe("m1"); }); + + it("handles message_id returned with extra wrapping quotes", () => { + const result = parseModerationResponse( + JSON.stringify({ + results: [ + { + message_id: '"test-msg-1"', + status: "clean", + flags: [], + score: 0.1, + analysis: "OK", + }, + ], + }), + ["test-msg-1"], + ); + + expect(result).toHaveLength(1); + expect(result[0].messageId).toBe("test-msg-1"); + }); }); describe("runModerationAnalysis", () => { @@ -359,6 +379,45 @@ describe("runModerationAnalysis", () => { expect(result.raw).toEqual(mockResponse); }); + it("requests strict JSON output without thinking", async () => { + const mockResponse = { + choices: [ + { + message: { + content: JSON.stringify({ + results: [ + { + message_id: "m1", + status: "clean", + flags: [], + score: 0.1, + analysis: "OK", + }, + ], + }), + }, + }, + ], + }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + text: async () => JSON.stringify(mockResponse), + json: async () => mockResponse, + }); + + await runModerationAnalysis({ + targets: [createMessageRecord()], + contextText: "test context", + }); + + const requestBody = JSON.parse((global.fetch as any).mock.calls[0][1].body); + expect(requestBody.temperature).toBe(0); + expect(requestBody.response_format).toEqual({ type: "json_object" }); + expect(requestBody.reasoning_budget).toBeUndefined(); + expect(requestBody.chat_template_kwargs).toEqual({ enable_thinking: false }); + }); + it("throws on non-ok HTTP response", async () => { global.fetch = vi.fn().mockResolvedValue({ ok: false,