Team Ai
Modelpublic

Felipe97/llama-cpp-compiled

sourceHugging Faceupdated 21d agoView on Hugging Face
0likes1.2kdownloads
partial-tool-call-cleanup.test.ts98 linesDownload Raw Back to unit
1import { MessageRole } from '$lib/enums';2import type { DatabaseMessage } from '$lib/types/database';3import { deriveAgenticSections } from '$lib/utils/agentic';4import { describe, expect, it } from 'vitest';5 6function makeAssistant(overrides: Partial<DatabaseMessage> = {}): DatabaseMessage {7	return {8		children: [],9		content: overrides.content ?? '',10		convId: 'conv-1',11		id: overrides.id ?? 'ast-1',12		parent: null,13		role: MessageRole.ASSISTANT,14		timestamp: Date.now(),15		type: 'text',16		...overrides17	} as DatabaseMessage;18}19 20// Mirrors the filter inside ChatService.convertDbMessageToApiChatMessageData:21// a partial tool call captured mid-stream must not survive into the next request22// payload. The fix in chatStore.savePartialResponseIfNeeded clears toolCalls to ''23// on Stop/Send immediately, mirroring what the agentic flow already does in24// onAssistantTurnComplete(...undefined).25function buildApiToolCalls(message: DatabaseMessage): unknown[] | undefined {26	if (!message.toolCalls) return undefined;27 28	try {29		const parsed = JSON.parse(message.toolCalls);30 31		return Array.isArray(parsed) && parsed.length > 0 ? parsed : undefined;32	} catch {33		return undefined;34	}35}36 37describe('partial tool call cleanup', () => {38	// Reproduces the broken payload from the user's screenshot: model was39	// streaming a tool call whose arguments JSON was cut mid-string. The outer40	// envelope still parses, but the arguments themselves are invalid JSON and41	// the server rejects the request.42	it('marks a partial tool call payload as unsafe to re-send', () => {43		const message = makeAssistant({44			content: 'partial reasoning',45			toolCalls: JSON.stringify([46				{47					function: {48						arguments: '{"command":`grep -n \\"read_to\\" ` /Users',49						name: 'exec_shell_command'50					},51					id: 'call_1',52					type: 'function'53				}54			])55		});56		const apiToolCalls = buildApiToolCalls(message);57 58		// The bug: even though arguments are invalid, the outer array parses and59		// the request gets sent. Function arguments must be parseable JSON on their60		// own for the server to execute the tool.61		expect(apiToolCalls).toBeDefined();62		const args = (apiToolCalls![0] as { function: { arguments: string } }).function.arguments;63 64		expect(() => JSON.parse(args)).toThrow();65	});66 67	// After Stop, savePartialResponseIfNeeded clears toolCalls and the agentic68	// flow does the same in its silent-return detection. The next request reads69	// toolCalls = '' and the conversion drops the field entirely so the server70	// never sees the half-streamed call.71	it('drops tool_calls from the API request after toolCalls is cleared', () => {72		const clearedMessage = makeAssistant({73			content: 'partial reasoning',74			toolCalls: ''75		});76		const apiToolCalls = buildApiToolCalls(clearedMessage);77 78		expect(apiToolCalls).toBeUndefined();79	});80 81	// The cleanup path keeps the partial reasoning content visible in the UI;82	// only the tool_calls field is reset. deriveAgenticSections should still83	// surface the reasoning as interrupted (no content / no tool calls behind84	// it) without resurrecting the dead tool call block.85	it('keeps reasoning content visible after cleanup, without a tool call block', () => {86		const cleared = makeAssistant({87			content: '',88			reasoningContent: 'thinking about read_to',89			toolCalls: ''90		});91		const sections = deriveAgenticSections(cleared);92 93		expect(sections).toHaveLength(1);94		expect(sections[0].type).toBe('reasoning');95		expect(sections.some((s) => s.type.includes('tool_call'))).toBe(false);96	});97});98