/** * 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); });