feat: implement robust JSON parsing and multimodal image capping
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
a22e3d5b63
commit
dde8358736
@@ -253,6 +253,51 @@ describe("parseModerationResponse", () => {
|
||||
|
||||
expect(result[0].score).toBe(0);
|
||||
});
|
||||
|
||||
it("extracts JSON correctly from complex conversational output with thinking blocks containing braces", () => {
|
||||
const content = `Based on the messages, I will analyze them.
|
||||
<thinking>
|
||||
The JSON structure should be:
|
||||
{
|
||||
"results": [ ... ]
|
||||
}
|
||||
</thinking>
|
||||
Here is the results array:
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"message_id": "m1",
|
||||
"status": "clean",
|
||||
"flags": [],
|
||||
"score": 0.2,
|
||||
"analysis": "Benign"
|
||||
}
|
||||
]
|
||||
}`;
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].messageId).toBe("m1");
|
||||
});
|
||||
|
||||
it("extracts JSON from markdown code block wrapping", () => {
|
||||
const content = `Sure! Here is the JSON structure:
|
||||
\`\`\`json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"message_id": "m1",
|
||||
"status": "clean",
|
||||
"flags": [],
|
||||
"score": 0.2,
|
||||
"analysis": "Benign"
|
||||
}
|
||||
]
|
||||
}
|
||||
\`\`\``;
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].messageId).toBe("m1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("runModerationAnalysis", () => {
|
||||
@@ -432,4 +477,101 @@ describe("runModerationAnalysis", () => {
|
||||
"You are a content moderation assistant.",
|
||||
);
|
||||
});
|
||||
|
||||
it("caps image attachments to 8 and prioritizes targets over context", async () => {
|
||||
const mockResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.1,
|
||||
analysis: "OK",
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
global.fetch = vi.fn().mockImplementation((url: string) => {
|
||||
if (url.includes("picser.tech") || url.includes("discord.com")) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
arrayBuffer: async () => {
|
||||
const buffer = Buffer.from("fake-bytes");
|
||||
return buffer.buffer.slice(
|
||||
buffer.byteOffset,
|
||||
buffer.byteOffset + buffer.byteLength,
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
text: async () => JSON.stringify(mockResponse),
|
||||
json: async () => mockResponse,
|
||||
});
|
||||
});
|
||||
|
||||
const createAttachment = (id: string, msgId: string, createdAt: number) => ({
|
||||
id,
|
||||
message_id: msgId,
|
||||
guild_id: "guild123",
|
||||
channel_id: "channel123",
|
||||
thread_id: null,
|
||||
user_id: "user123",
|
||||
filename: `${id}.png`,
|
||||
size: 500,
|
||||
type: "image/png",
|
||||
discord_url: `https://discord.com/${id}.png`,
|
||||
uploaded_url: `https://picser.tech/${id}.png`,
|
||||
upload_status: "uploaded" as const,
|
||||
upload_error: null,
|
||||
created_at: createdAt,
|
||||
uploaded_at: createdAt,
|
||||
});
|
||||
|
||||
// 10 attachments total (3 targets, 7 context)
|
||||
const attachments = [
|
||||
createAttachment("c1", "context1", 100),
|
||||
createAttachment("c2", "context2", 200),
|
||||
createAttachment("t1", "m1", 300), // Target 1
|
||||
createAttachment("c3", "context3", 400),
|
||||
createAttachment("t2", "m1", 500), // Target 2
|
||||
createAttachment("c4", "context4", 600),
|
||||
createAttachment("c5", "context5", 700),
|
||||
createAttachment("t3", "m1", 800), // Target 3
|
||||
createAttachment("c6", "context6", 900),
|
||||
createAttachment("c7", "context7", 1000),
|
||||
];
|
||||
|
||||
await runModerationAnalysis({
|
||||
targets: [createMessageRecord({ id: "m1" })],
|
||||
contextText: "test context",
|
||||
attachments,
|
||||
});
|
||||
|
||||
const fetchCalls = (global.fetch as any).mock.calls;
|
||||
// Should download exactly 8 images (since it's capped at 8) plus 1 call for completion API = 9 calls total.
|
||||
expect(fetchCalls.length).toBe(9);
|
||||
|
||||
// Target attachments (t3, t2, t1) must be fetched, then context in descending order of created_at:
|
||||
// Sorted order: t3 (800), t2 (500), t1 (300), c7 (1000), c6 (900), c5 (700), c4 (600), c3 (400)
|
||||
// Excluded: c2 (200), c1 (100)
|
||||
const downloadedUrls = fetchCalls
|
||||
.slice(0, 8)
|
||||
.map((call: any) => call[0]);
|
||||
|
||||
expect(downloadedUrls).toContain("https://picser.tech/t3.png");
|
||||
expect(downloadedUrls).toContain("https://picser.tech/t2.png");
|
||||
expect(downloadedUrls).toContain("https://picser.tech/t1.png");
|
||||
expect(downloadedUrls).toContain("https://picser.tech/c7.png");
|
||||
expect(downloadedUrls).not.toContain("https://picser.tech/c1.png");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user