LiquidAI/LFM2.5-VL-3B-WebGPU
36
1import test from 'node:test';2import assert from 'node:assert/strict';3import { calculate, executeBuiltin, prepareToolCall, searchRecipeByDish } from '../src/tools/tool-registry.js';4import { createToolStreamFilter, displayTextFromRaw, parseToolCalls } from '../src/tools/tool-protocol.js';5 6test('calculator evaluates arithmetic without JavaScript evaluation', () => {7 assert.equal(calculate('2 + 3 * (4 - 1)^2'), 29);8 assert.equal(calculate('1.5e2 / 3'), 50);9 assert.throws(() => calculate('globalThis.alert(1)'), /Unsupported character/);10 assert.throws(() => calculate('2 / 0'), /Division by zero/);11});12 13test('calculator supports safe trigonometry for geometry problems', () => {14 assert.ok(Math.abs(calculate('sin(pi / 2)') - 1) < 1e-12);15 assert.ok(Math.abs(calculate('10 * tan_deg(30)') - 5.773502691896257) < 1e-12);16 assert.ok(Math.abs(calculate('sqrt(3^2 + 4^2)') - 5) < 1e-12);17 assert.ok(Math.abs(calculate('asin_deg(0.5)') - 30) < 1e-12);18 assert.throws(() => calculate('fetch(1)'), /Unsupported function/);19 assert.throws(() => calculate('sqrt(-1)'), /finite number/);20});21 22test('native Python-style protocol parses single and multiple calls', () => {23 const calls = parseToolCalls("before<|tool_call_start|>[calculate(expression='2 + 2'), current_datetime(time_zone='UTC')]<|tool_call_end|>");24 assert.deepEqual(calls.map(call => call.name), ['calculate', 'current_datetime']);25 assert.deepEqual(calls[0].arguments, { expression: '2 + 2' });26 assert.deepEqual(calls[1].arguments, { time_zone: 'UTC' });27 assert.equal(displayTextFromRaw("Answer<|tool_call_start|>[calculate(expression='1')]<|tool_call_end|><|im_end|>"), 'Answer');28});29 30test('protocol supports positional, primitive, and nested JSON values', () => {31 const [call] = parseToolCalls('<|tool_call_start|>random_integer(1, max=4)<|tool_call_end|>');32 assert.deepEqual(call.positional, [1]);33 assert.deepEqual(call.arguments, { max: 4 });34 const [nested] = parseToolCalls('<|tool_call_start|>custom(payload={"ok":true,"items":[1,2]}, optional=None)<|tool_call_end|>');35 assert.deepEqual(nested.arguments, { payload: { ok: true, items: [1, 2] }, optional: null });36 assert.throws(() => parseToolCalls('<|tool_call_start|>broken(a=1'), /incomplete/);37});38 39test('stream filter suppresses tool syntax even across chunks', () => {40 let visible = '';41 const states = [];42 const filter = createToolStreamFilter(chunk => { visible += chunk; }, {43 onToolCallStart: () => states.push('start'),44 onToolCallEnd: () => states.push('end'),45 });46 for (const chunk of ['Hello ', '<|tool_', "call_start|>[calculate(expression='2')]<|tool_call_", 'end|>', ' world']) filter.push(chunk);47 filter.finish();48 assert.equal(visible, 'Hello world');49 assert.deepEqual(states, ['start', 'end']);50});51 52test('tool schemas and arguments are validated', () => {53 const tool = { id: 'test:lookup_order', source: 'test', enabled: true, name: 'lookup_order', description: 'Look up an order.', parameters: { type: 'object', properties: { order_id: { type: 'string' } }, required: ['order_id'], additionalProperties: false } };54 const prepared = prepareToolCall({ name: 'lookup_order', arguments: { order_id: 'A-1' }, positional: [] }, [tool]);55 assert.deepEqual(prepared.args, { order_id: 'A-1' });56 assert.throws(() => prepareToolCall({ name: 'lookup_order', arguments: {}, positional: [] }, [tool]), /Missing required/);57 tool.enabled = false;58 assert.throws(() => prepareToolCall({ name: 'lookup_order', arguments: { order_id: 'A-1' }, positional: [] }, [tool]), /Unknown or disabled/);59});60 61test('datetime and random built-ins reject invalid inputs', async () => {62 await assert.rejects(executeBuiltin('current_datetime', { time_zone: 'Definitely/Invalid' }), /Invalid IANA time zone/);63 await assert.rejects(executeBuiltin('random_integer', { min: 5, max: 4 }), /min ≤ max/);64 for (let index = 0; index < 20; index += 1) {65 const result = await executeBuiltin('random_integer', { min: -2, max: 2 });66 assert.ok(result.value >= -2 && result.value <= 2);67 }68});69 70test('dish recipe search prefers an exact completed-dish name', async () => {71 const fetcher = async url => {72 assert.equal(new URL(url).searchParams.get('s'), 'Pad Thai');73 return {74 ok: true,75 status: 200,76 json: async () => ({ meals: [77 { idMeal: '2', strMeal: 'Quick Pad Thai', strIngredient1: 'Noodles' },78 { idMeal: '1', strMeal: 'Pad Thai', strCategory: 'Main', strArea: 'Thai', strIngredient1: 'Rice Noodles', strMeasure1: '200g', strInstructions: 'Cook it.', strSource: 'https://example.com/pad-thai' },79 ] }),80 };81 };82 const result = await searchRecipeByDish('Pad Thai', undefined, fetcher);83 assert.equal(result.provider, 'TheMealDB');84 assert.equal(result.recipes[0].name, 'Pad Thai');85 assert.deepEqual(result.recipes[0].ingredients, ['200g Rice Noodles']);86 assert.equal(result.recipes[0].source_url, 'https://example.com/pad-thai');87});88 89test('dish recipe search accepts whole-word title matches and rejects partial false matches', async () => {90 const response = meals => ({ ok: true, status: 200, json: async () => ({ meals }) });91 const friedRice = await searchRecipeByDish('fried rice', undefined, async () => response([92 { idMeal: '1', strMeal: 'Chicken Fried Rice' },93 ]));94 assert.equal(friedRice.recipes[0].name, 'Chicken Fried Rice');95 const falsePartial = await searchRecipeByDish('chicken parm', undefined, async () => response([96 { idMeal: '2', strMeal: 'Chicken Parmentier' },97 ]));98 assert.deepEqual(falsePartial.recipes, []);99});100 101test('dish recipe search validates names and handles no result', async () => {102 await assert.rejects(searchRecipeByDish('', undefined, async () => {}), /between 1 and 100/);103 await assert.rejects(searchRecipeByDish('x'.repeat(101), undefined, async () => {}), /between 1 and 100/);104 const result = await searchRecipeByDish('Caesar Salad', undefined, async () => ({ ok: true, status: 200, json: async () => ({ meals: null }) }));105 assert.deepEqual(result.recipes, []);106});107 