Files
alice-backend/test/providers-tools.test.mjs

78 lines
4.5 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import http from 'node:http';
const requests = [];
let responder;
const server = http.createServer(async (req, res) => {
let body = ''; for await (const chunk of req) body += chunk;
requests.push(JSON.parse(body));
res.writeHead(200, { 'Content-Type': 'text/event-stream' });
for (const event of responder(requests.length)) res.write(`data: ${JSON.stringify(event)}\n\n`);
res.end('data: [DONE]\n\n');
});
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
process.env.OPENROUTER_URL = `http://127.0.0.1:${server.address().port}`;
process.env.OPENROUTER_API_KEY = 'test';
process.env.LLM_MODE = 'openrouter';
const { streamAnswer } = await import('../server/providers.mjs');
test.after(() => server.close());
const event = (delta, finish_reason = null) => ({ choices: [{ delta, finish_reason }] });
const settings = { systemPrompt: 'test', llmModel: 'test' };
test('chooses delivery in the answer request and keeps audio tags out of history', async () => {
requests.length = 0;
responder = () => [event({ content: '[exci' }), event({ content: 'ted] Ура! [shouts] Поздравляю!' }, 'stop')];
const spoken = [];
const expressive = { ...settings, ttsModel: 'eleven_v4_turbo' };
assert.equal(await streamAnswer(expressive, [], 'Я выиграл!', AbortSignal.timeout(2000), part => spoken.push(part)), 'Ура! Поздравляю!');
assert.deepEqual(spoken, ['[excited] Ура! Поздравляю!']);
assert.equal(requests.length, 1);
assert.match(requests[0].messages[0].content, /\[curious\]/);
const neutral = { ...settings, ttsModel: 'eleven_flash_v2_5' };
spoken.length = 0;
assert.equal(await streamAnswer(neutral, [], 'Я выиграл!', AbortSignal.timeout(2000), part => spoken.push(part)), 'Ура! Поздравляю!');
assert.deepEqual(spoken, ['Ура! Поздравляю!']);
assert.doesNotMatch(requests[1].messages[0].content, /\[curious\]/);
});
test('assembles fragmented calls before executing and streams text', async () => {
requests.length = 0;
responder = n => n === 1 ? [event({ content: 'Сейчас включу.', tool_calls: [{ index: 0, id: 'call_1', type: 'function', function: { name: 'spotify_play', arguments: '{"query":"' } }] }), event({ tool_calls: [{ index: 0, function: { arguments: 'Queen"}' } }] }, 'tool_calls')] : [event({ content: 'Готово' }, 'stop')];
const calls = [], text = [];
const answer = await streamAnswer(settings, [], 'play', new AbortController().signal, part => text.push(part), async (name, args) => { calls.push([name, args]); return { ok: true }; });
assert.equal(answer, 'Готово'); assert.deepEqual(text, ['Готово']);
assert.deepEqual(calls, [['spotify_play', { query: 'Queen' }]]);
assert.equal(requests[0].tools[0].type, 'openrouter:web_search');
assert.equal(requests[1].messages.at(-1).tool_call_id, 'call_1');
});
test('confirms Spotify play once without reading the volume or an old track', async () => {
requests.length = 0;
responder = () => [event({ content: 'Громкость 40.', tool_calls: [{ index: 0, id: 'play', function: { name: 'spotify_play', arguments: '{"query":"Aphex Twin"}' } }] }, 'tool_calls')];
const text = [];
const answer = await streamAnswer(settings, [], 'включи музыку', AbortSignal.timeout(2000), part => text.push(part), async () => ({
accepted: true, status: 'pending', selection: { name: 'Aphex Twin' }, item: { name: 'Old track' }, volume: 40,
}));
assert.equal(answer, 'Включаю «Aphex Twin».');
assert.deepEqual(text, [answer]);
assert.equal(requests.length, 1);
});
test('does not execute incomplete tool calls', async () => {
requests.length = 0;
responder = () => [event({ tool_calls: [{ index: 0, id: 'x', function: { name: 'spotify_control', arguments: '{"action":"next"}' } }] }, 'length')];
let called = false;
await assert.rejects(streamAnswer(settings, [], 'next', new AbortController().signal, () => {}, async () => { called = true; }), /llm_incomplete/);
assert.equal(called, false);
});
test('limits repeated tool calls', async () => {
requests.length = 0;
responder = n => [event({ tool_calls: [{ index: 0, id: `call_${n}`, function: { name: 'spotify_control', arguments: '{"action":"next"}' } }] }, 'tool_calls')];
let calls = 0;
await assert.rejects(streamAnswer(settings, [], 'next', new AbortController().signal, () => {}, async () => { calls++; return { ok: true }; }), /llm_limit/);
assert.equal(calls, 3);
});