WFDASH-WEBAPP/scripts/smoke/giellie-vision-probe.ts
Dave 0eb9653359 chore(giellie): add MiniMax vision/tool-call probe
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 23:33:13 +02:00

85 lines
3 KiB
TypeScript

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