Spaces:
Running
Running
File size: 6,024 Bytes
6c30253 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | export const TOOL_CALL_START = '<|tool_call_start|>';
export const TOOL_CALL_END = '<|tool_call_end|>';
const CONTROL_TOKENS = /<\|(?:im_start|im_end|tool_call_start|tool_call_end)\|>/g;
export function parseToolCalls(rawText) {
const calls = [];
let cursor = 0;
while (cursor < rawText.length) {
const start = rawText.indexOf(TOOL_CALL_START, cursor);
if (start < 0) break;
const contentStart = start + TOOL_CALL_START.length;
const end = rawText.indexOf(TOOL_CALL_END, contentStart);
if (end < 0) throw new Error('The model emitted an incomplete tool call.');
calls.push(...parseCallList(rawText.slice(contentStart, end)));
cursor = end + TOOL_CALL_END.length;
}
return calls;
}
export function displayTextFromRaw(rawText) {
let output = '';
let cursor = 0;
while (cursor < rawText.length) {
const start = rawText.indexOf(TOOL_CALL_START, cursor);
if (start < 0) {
output += rawText.slice(cursor);
break;
}
output += rawText.slice(cursor, start);
const end = rawText.indexOf(TOOL_CALL_END, start + TOOL_CALL_START.length);
if (end < 0) break;
cursor = end + TOOL_CALL_END.length;
}
return output.replace(CONTROL_TOKENS, '').replace(/^assistant\s*\n/i, '').trim();
}
export function createToolStreamFilter(onText, { onToolCallStart, onToolCallEnd } = {}) {
let buffer = '';
let insideCall = false;
const reserve = Math.max(TOOL_CALL_START.length, TOOL_CALL_END.length) - 1;
function flush(force = false) {
while (buffer) {
const marker = insideCall ? TOOL_CALL_END : TOOL_CALL_START;
const markerIndex = buffer.indexOf(marker);
if (markerIndex >= 0) {
if (!insideCall && markerIndex > 0) emit(buffer.slice(0, markerIndex));
buffer = buffer.slice(markerIndex + marker.length);
insideCall = !insideCall;
if (insideCall) onToolCallStart?.();
else onToolCallEnd?.();
continue;
}
const length = force ? buffer.length : Math.max(0, buffer.length - reserve);
if (!length) break;
if (!insideCall) emit(buffer.slice(0, length));
buffer = buffer.slice(length);
}
}
function emit(value) {
const clean = value.replace(CONTROL_TOKENS, '');
if (clean) onText(clean);
}
return {
push(chunk) { buffer += chunk; flush(false); },
finish() { flush(true); },
};
}
function parseCallList(source) {
const trimmed = source.trim();
const body = trimmed.startsWith('[') && trimmed.endsWith(']')
? trimmed.slice(1, -1)
: trimmed;
if (!body.trim()) return [];
return splitTopLevel(body).map(parseCall);
}
function parseCall(source) {
const match = /^([A-Za-z_][A-Za-z0-9_]*)\s*\(([\s\S]*)\)$/.exec(source.trim());
if (!match) throw new Error(`Malformed tool call: ${source.trim().slice(0, 80)}`);
const args = {};
const positional = [];
for (const part of splitTopLevel(match[2])) {
const assignment = findTopLevelAssignment(part);
if (assignment < 0) positional.push(parsePythonValue(part));
else {
const key = part.slice(0, assignment).trim();
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new Error(`Invalid argument name: ${key}`);
if (Object.hasOwn(args, key)) throw new Error(`Duplicate argument: ${key}`);
args[key] = parsePythonValue(part.slice(assignment + 1));
}
}
return { name: match[1], arguments: args, positional };
}
function splitTopLevel(source) {
if (!source.trim()) return [];
const parts = [];
let start = 0;
let quote = '';
let escaped = false;
const stack = [];
for (let index = 0; index < source.length; index += 1) {
const character = source[index];
if (quote) {
if (escaped) escaped = false;
else if (character === '\\') escaped = true;
else if (character === quote) quote = '';
continue;
}
if (character === '"' || character === "'") { quote = character; continue; }
if ('([{'.includes(character)) stack.push(character);
else if (')]}'.includes(character)) stack.pop();
else if (character === ',' && stack.length === 0) {
parts.push(source.slice(start, index).trim());
start = index + 1;
}
}
if (quote || stack.length) throw new Error('Unbalanced tool-call arguments.');
const finalPart = source.slice(start).trim();
if (finalPart) parts.push(finalPart);
return parts;
}
function findTopLevelAssignment(source) {
let quote = '';
let escaped = false;
let depth = 0;
for (let index = 0; index < source.length; index += 1) {
const character = source[index];
if (quote) {
if (escaped) escaped = false;
else if (character === '\\') escaped = true;
else if (character === quote) quote = '';
} else if (character === '"' || character === "'") quote = character;
else if ('([{'.includes(character)) depth += 1;
else if (')]}'.includes(character)) depth -= 1;
else if (character === '=' && depth === 0) return index;
}
return -1;
}
function parsePythonValue(source) {
const value = source.trim();
if (!value) throw new Error('Missing tool argument value.');
if (value === 'True' || value === 'true') return true;
if (value === 'False' || value === 'false') return false;
if (value === 'None' || value === 'null') return null;
if (/^-?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(value)) return Number(value);
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
if (value[0] === '"') return JSON.parse(value);
return value.slice(1, -1).replace(/\\(['\\nrt])/g, (_, escaped) => ({ "'": "'", '\\': '\\', n: '\n', r: '\r', t: '\t' })[escaped]);
}
if (value.startsWith('[') && value.endsWith(']')) return splitTopLevel(value.slice(1, -1)).map(parsePythonValue);
if (value.startsWith('{') && value.endsWith('}')) {
try { return JSON.parse(value); } catch { throw new Error('Object arguments must use valid JSON.'); }
}
throw new Error(`Unsupported tool argument value: ${value.slice(0, 80)}`);
}
|