From 0eb96533596be986bac65506909ee2869ce36779 Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 30 Sep 2026 23:33:13 +0200 Subject: [PATCH] chore(giellie): add MiniMax vision/tool-call probe Co-Authored-By: Claude Sonnet 5.5 --- scripts/smoke/giellie-vision-probe.ts | 85 +++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 scripts/smoke/giellie-vision-probe.ts diff --git a/scripts/smoke/giellie-vision-probe.ts b/scripts/smoke/giellie-vision-probe.ts new file mode 100644 index 0000000..b75aca8 --- /dev/null +++ b/scripts/smoke/giellie-vision-probe.ts @@ -0,0 +1,85 @@ +/** + * Probe: does the configured MiniMax endpoint accept (a) image blocks and + * (b) tool calls, and what content-block types come back? + * + * Read-only against PB (reads ai_settings); makes two small LLM calls. + * + * Run with: npx tsx scripts/smoke/giellie-vision-probe.ts + * Requires AI enabled + key set at /settings/ai. + */ +import "./_env"; // MUST be first +import Anthropic from "@anthropic-ai/sdk"; +import { createCanvas } from "@napi-rs/canvas"; +import { getActiveAiConfig } from "../../src/lib/server/ai-settings-service"; + +async function main() { + const cfg = await getActiveAiConfig(); + const client = new Anthropic({ + apiKey: cfg.apiKey, + ...(cfg.baseUrl ? { baseURL: cfg.baseUrl } : {}), + }); + console.log(`[probe] model=${cfg.modelId} baseUrl=${cfg.baseUrl || "(default)"}`); + + // (a) Vision: render "PRICE 4471" to a PNG and ask for the number. + const canvas = createCanvas(400, 120); + const ctx = canvas.getContext("2d"); + ctx.fillStyle = "#fff"; + ctx.fillRect(0, 0, 400, 120); + ctx.fillStyle = "#000"; + ctx.font = "48px sans-serif"; + ctx.fillText("PRICE 4471", 20, 75); + const png = canvas.toBuffer("image/png").toString("base64"); + + try { + const res = await client.messages.create({ + model: cfg.modelId, + max_tokens: 1024, + messages: [ + { + role: "user", + content: [ + { type: "image", source: { type: "base64", media_type: "image/png", data: png } }, + { type: "text", text: "What number is written in the image? Reply with just the number." }, + ], + }, + ], + }); + const text = res.content.map((b) => (b.type === "text" ? b.text : "")).join(""); + const types = res.content.map((b) => b.type).join(","); + console.log(`[probe] VISION block types=[${types}] answer=${JSON.stringify(text.trim())}`); + console.log(text.includes("4471") ? "[probe] VISION: PASS" : "[probe] VISION: FAIL (answer wrong)"); + } catch (e) { + console.log("[probe] VISION: FAIL (request rejected):", e instanceof Error ? e.message : e); + } + + // (b) Tool calling. + try { + const res = await client.messages.create({ + model: cfg.modelId, + max_tokens: 1024, + tools: [ + { + name: "orders__get", + description: "Get an order by order number.", + input_schema: { + type: "object", + properties: { order_number: { type: "string" } }, + required: ["order_number"], + }, + }, + ], + messages: [{ role: "user", content: "What is the status of order 12345? Use the tool." }], + }); + const types = res.content.map((b) => b.type).join(","); + const tu = res.content.find((b) => b.type === "tool_use"); + console.log(`[probe] TOOLS block types=[${types}] stop_reason=${res.stop_reason}`); + console.log(tu ? `[probe] TOOLS: PASS input=${JSON.stringify((tu as { input: unknown }).input)}` : "[probe] TOOLS: FAIL (no tool_use)"); + } catch (e) { + console.log("[probe] TOOLS: FAIL (request rejected):", e instanceof Error ? e.message : e); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +});