mirror of
https://github.com/HeyPuter/puter.git
synced 2026-10-09 05:12:03 +00:00
chat_completions tool call fixes (#2434)
* chat_completions tool call fixes * update chat completions test
This commit is contained in:
1 parent
06fe07e384
commit
28cedec9de
7 files changed
+509
-23
No files matched your search
@@ -132,6 +132,8 @@ module.exports = eggspress('/openai/v1/chat/completions', {
|
||||
|
||||
let buffer = '';
|
||||
let usage = null;
|
||||
let toolCallIndex = 0;
|
||||
let sawToolCalls = false;
|
||||
|
||||
const sendChunk = (delta, finishReason = null, extra = {}) => {
|
||||
const payload = {
|
||||
@@ -170,9 +172,11 @@ module.exports = eggspress('/openai/v1/chat/completions', {
|
||||
sendChunk({ content: event.text });
|
||||
}
|
||||
if ( event.type === 'tool_use' ) {
|
||||
sawToolCalls = true;
|
||||
sendChunk({
|
||||
tool_calls: [
|
||||
{
|
||||
index: toolCallIndex++,
|
||||
id: event.id,
|
||||
type: 'function',
|
||||
function: {
|
||||
@@ -181,7 +185,7 @@ module.exports = eggspress('/openai/v1/chat/completions', {
|
||||
},
|
||||
},
|
||||
],
|
||||
}, 'tool_calls');
|
||||
});
|
||||
}
|
||||
if ( event.type === 'usage' ) {
|
||||
usage = event.usage;
|
||||
@@ -190,7 +194,8 @@ module.exports = eggspress('/openai/v1/chat/completions', {
|
||||
});
|
||||
|
||||
streamValue.on('end', () => {
|
||||
sendChunk({}, 'stop', usage ? { usage: buildUsage(usage) } : {});
|
||||
const finishReason = sawToolCalls ? 'tool_calls' : 'stop';
|
||||
sendChunk({}, finishReason, usage ? { usage: buildUsage(usage) } : {});
|
||||
res.write('data: [DONE]\n\n');
|
||||
res.end();
|
||||
});
|
||||
|
||||
@@ -54,9 +54,13 @@ export const normalize_tools_object = (tools) => {
|
||||
fn.parameters ||
|
||||
fn.input_schema;
|
||||
|
||||
normal_fn.parameters = parameters ?? {
|
||||
type: 'object',
|
||||
};
|
||||
if ( !parameters || typeof parameters !== 'object' ) {
|
||||
parameters = { type: 'object' };
|
||||
} else if ( ! parameters.type ) {
|
||||
parameters.type = 'object';
|
||||
}
|
||||
|
||||
normal_fn.parameters = parameters;
|
||||
|
||||
if ( parameters.properties ) {
|
||||
parameters = normalize_json_schema(parameters);
|
||||
|
||||
@@ -44,6 +44,22 @@ export const normalize_single_message = (message, params = {}) => {
|
||||
throw new Error('each message must have a \'content\' property');
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize OpenAI-style tool results into internal tool_result blocks
|
||||
if ( message.role === 'tool' ) {
|
||||
const tool_use_id = message.tool_call_id || message.tool_use_id || message.id;
|
||||
const tool_content = message.content;
|
||||
message.tool_use_id = tool_use_id;
|
||||
message.content = [
|
||||
{
|
||||
type: 'tool_result',
|
||||
tool_use_id,
|
||||
content: typeof tool_content === 'string'
|
||||
? tool_content
|
||||
: JSON.stringify(tool_content ?? {}),
|
||||
},
|
||||
];
|
||||
}
|
||||
if ( ! Array.isArray(message.content) ) {
|
||||
message.content = [message.content];
|
||||
}
|
||||
@@ -90,33 +106,43 @@ export const normalize_messages = (messages, params = {}) => {
|
||||
messages[i] = normalize_single_message(messages[i], params);
|
||||
}
|
||||
|
||||
// Split messages with tool_use content into separate messages
|
||||
// Split messages with multiple content blocks into separate messages.
|
||||
// Keep assistant tool_use blocks together to preserve OpenAI tool-call ordering.
|
||||
// TODO: unit test this
|
||||
messages = [...messages];
|
||||
for ( let i = 0 ; i < messages.length ; i++ ) {
|
||||
let message = messages[i];
|
||||
let separated_messages = [];
|
||||
const has_tool_use = message.role === 'assistant' &&
|
||||
message.content?.some(c => c?.type === 'tool_use');
|
||||
if ( has_tool_use ) {
|
||||
separated_messages.push(message);
|
||||
messages.splice(i, 1, ...separated_messages);
|
||||
continue;
|
||||
}
|
||||
for ( let j = 0 ; j < message.content.length ; j++ ) {
|
||||
if ( message.content[j].type === 'tool_result' ) {
|
||||
separated_messages.push({
|
||||
...message,
|
||||
content: [message.content[j]],
|
||||
});
|
||||
} else {
|
||||
separated_messages.push({
|
||||
...message,
|
||||
content: [message.content[j]],
|
||||
});
|
||||
}
|
||||
separated_messages.push({
|
||||
...message,
|
||||
content: [message.content[j]],
|
||||
});
|
||||
}
|
||||
messages.splice(i, 1, ...separated_messages);
|
||||
}
|
||||
|
||||
// If multiple messages are from the same role, merge them
|
||||
// but avoid merging tool_use/tool_result messages, since order matters
|
||||
const hasToolContent = (message) => {
|
||||
if ( !message || !Array.isArray(message.content) ) return false;
|
||||
return message.content.some((part) =>
|
||||
part && (part.type === 'tool_use' || part.type === 'tool_result'));
|
||||
};
|
||||
let merged_messages = [];
|
||||
let current_role = null;
|
||||
for ( let i = 0 ; i < messages.length ; i++ ) {
|
||||
if ( current_role === messages[i].role ) {
|
||||
const can_merge = current_role === messages[i].role &&
|
||||
!hasToolContent(messages[i]) &&
|
||||
!hasToolContent(merged_messages[merged_messages.length - 1]);
|
||||
if ( can_merge ) {
|
||||
merged_messages[merged_messages.length - 1].content.push(...messages[i].content);
|
||||
} else {
|
||||
merged_messages.push(messages[i]);
|
||||
@@ -180,4 +206,4 @@ export const extract_text = (messages) => {
|
||||
return '';
|
||||
}
|
||||
}).join(' ');
|
||||
};
|
||||
};
|
||||
Reference in new issue
Block a user