char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aleksander Grygierui: Linting & Formatting scripts (#26819)92d1bb0c9

char/aliceai-t5-moe
5.9 KiB163 linesraw
1import { ContinueIntentKind, MessageRole, MessageType } from '$lib/enums';
2import type { DatabaseMessage } from '$lib/types/database';
3import { classifyContinueIntent } from '$lib/utils/agentic';
4import { describe, expect, it } from 'vitest';
5
6/**
7 * Tests for the Continue button intent classifier.
8 *
9 * The classifier walks the persisted message history to decide which of three
10 * resume paths a Continue click should take:
11 *
12 *   A. append_text  -> plain text assistant turn, resume with
13 *                      continue_final_message.
14 *   B. rerun_turn   -> assistant turn with tool_calls but no tool results yet,
15 *                      the stream was cut mid turn and the tool_calls are
16 *                      unrecoverable as a token level continuation. Drop the
17 *                      target and rerun from the previous history.
18 *   C. next_turn    -> assistant turn with tool_calls that were already
19 *                      resolved by trailing tool results. Hand the history
20 *                      back to the agentic loop so it starts the next turn.
21 */
22
23let nextId = 0;
24
25function makeMsg(role: MessageRole, opts: Partial<DatabaseMessage> = {}): DatabaseMessage {
26	nextId++;
27
28	return {
29		children: [],
30		content: '',
31		convId: 'conv-1',
32		id: `msg-${nextId}`,
33		parent: null,
34		role,
35		timestamp: nextId,
36		type: MessageType.TEXT,
37		...opts
38	};
39}
40
41function toolCall(id: string, name: string, args: string = '{}'): string {
42	return JSON.stringify([{ function: { arguments: args, name }, id, type: 'function' }]);
43}
44
45describe('classifyContinueIntent', () => {
46	it('returns append_text for a plain text assistant turn at the tail', () => {
47		const messages = [
48			makeMsg(MessageRole.USER, { content: 'hello' }),
49			makeMsg(MessageRole.ASSISTANT, { content: 'hi there' })
50		];
51		const intent = classifyContinueIntent(messages, 1);
52
53		expect(intent).toEqual({ kind: ContinueIntentKind.APPEND_TEXT });
54	});
55
56	it('returns append_text for a plain text assistant turn in the middle', () => {
57		const messages = [
58			makeMsg(MessageRole.USER, { content: 'q1' }),
59			makeMsg(MessageRole.ASSISTANT, { content: 'a1' }),
60			makeMsg(MessageRole.USER, { content: 'q2' }),
61			makeMsg(MessageRole.ASSISTANT, { content: 'a2' })
62		];
63
64		expect(classifyContinueIntent(messages, 1)).toEqual({ kind: ContinueIntentKind.APPEND_TEXT });
65	});
66
67	it('returns rerun_turn when the assistant has tool_calls without results', () => {
68		const messages = [
69			makeMsg(MessageRole.USER, { content: 'list files' }),
70			makeMsg(MessageRole.ASSISTANT, {
71				content: '',
72				toolCalls: toolCall('call_1', 'bash_tool', '{"command":"ls"}')
73			})
74		];
75		const intent = classifyContinueIntent(messages, 1);
76
77		expect(intent).toEqual({ kind: ContinueIntentKind.RERUN_TURN, truncateAfter: 0 });
78	});
79
80	it('returns next_turn when trailing tool results resolve the tool_calls', () => {
81		const messages = [
82			makeMsg(MessageRole.USER, { content: 'list files' }),
83			makeMsg(MessageRole.ASSISTANT, {
84				content: '',
85				toolCalls: toolCall('call_1', 'bash_tool')
86			}),
87			makeMsg(MessageRole.TOOL, { content: 'file1\nfile2', toolCallId: 'call_1' })
88		];
89		const intent = classifyContinueIntent(messages, 1);
90
91		expect(intent).toEqual({ kind: ContinueIntentKind.NEXT_TURN, truncateAfter: 2 });
92	});
93
94	it('next_turn keeps all consecutive trailing tool results, not just one', () => {
95		const messages = [
96			makeMsg(MessageRole.USER, { content: 'do many things' }),
97			makeMsg(MessageRole.ASSISTANT, {
98				content: '',
99				toolCalls: JSON.stringify([
100					{ function: { arguments: '{}', name: 'a' }, id: 'call_1', type: 'function' },
101					{ function: { arguments: '{}', name: 'b' }, id: 'call_2', type: 'function' }
102				])
103			}),
104			makeMsg(MessageRole.TOOL, { content: 'r1', toolCallId: 'call_1' }),
105			makeMsg(MessageRole.TOOL, { content: 'r2', toolCallId: 'call_2' })
106		];
107		const intent = classifyContinueIntent(messages, 1);
108
109		expect(intent).toEqual({ kind: ContinueIntentKind.NEXT_TURN, truncateAfter: 3 });
110	});
111
112	it('next_turn stops at the first non tool message after the target', () => {
113		const messages = [
114			makeMsg(MessageRole.USER, { content: 'go' }),
115			makeMsg(MessageRole.ASSISTANT, {
116				content: '',
117				toolCalls: toolCall('call_1', 'a')
118			}),
119			makeMsg(MessageRole.TOOL, { content: 'r1', toolCallId: 'call_1' }),
120			makeMsg(MessageRole.USER, { content: 'wait' }),
121			makeMsg(MessageRole.TOOL, { content: 'late', toolCallId: 'call_1' })
122		];
123		const intent = classifyContinueIntent(messages, 1);
124
125		// truncateAfter must point at the contiguous tool block, not jump over
126		// the user message to grab the dangling late tool.
127		expect(intent).toEqual({ kind: ContinueIntentKind.NEXT_TURN, truncateAfter: 2 });
128	});
129
130	it('returns append_text when toolCalls is set but parses to empty array', () => {
131		const messages = [
132			makeMsg(MessageRole.USER, { content: 'q' }),
133			makeMsg(MessageRole.ASSISTANT, { content: 'a', toolCalls: '[]' })
134		];
135
136		expect(classifyContinueIntent(messages, 1)).toEqual({ kind: ContinueIntentKind.APPEND_TEXT });
137	});
138
139	it('returns append_text when toolCalls is malformed JSON', () => {
140		const messages = [
141			makeMsg(MessageRole.USER, { content: 'q' }),
142			makeMsg(MessageRole.ASSISTANT, { content: 'a', toolCalls: '{not json' })
143		];
144
145		expect(classifyContinueIntent(messages, 1)).toEqual({ kind: ContinueIntentKind.APPEND_TEXT });
146	});
147
148	it('returns append_text defensively when idx points at a non assistant message', () => {
149		const messages = [
150			makeMsg(MessageRole.USER, { content: 'q' }),
151			makeMsg(MessageRole.ASSISTANT, { content: 'a' })
152		];
153
154		expect(classifyContinueIntent(messages, 0)).toEqual({ kind: ContinueIntentKind.APPEND_TEXT });
155	});
156
157	it('returns append_text defensively when idx is out of bounds', () => {
158		const messages = [makeMsg(MessageRole.ASSISTANT, { content: 'a' })];
159
160		expect(classifyContinueIntent(messages, 5)).toEqual({ kind: ContinueIntentKind.APPEND_TEXT });
161		expect(classifyContinueIntent([], 0)).toEqual({ kind: ContinueIntentKind.APPEND_TEXT });
162	});
163});