Merge pull request #37 from Evyatar108/patch-1

Fix (1) multi/parallel-tool invocation and (2) `API Error: A.map` permanently
This commit is contained in:
musi
2025-06-20 21:54:59 +08:00
committed by GitHub

View File

@@ -53,39 +53,38 @@ export async function streamOpenAIResponse(
}; };
const messageId = "msg_" + Date.now(); const messageId = "msg_" + Date.now();
if (!body.stream) { if (!body.stream) {
res.json({ let content: any = [];
if (completion.choices[0].message.content) {
content = [ { text: completion.choices[0].message.content, type: "text" } ];
}
else if (completion.choices[0].message.tool_calls) {
content = completion.choices[0].message.tool_calls.map((item: any) => {
return {
type: 'tool_use',
id: item.id,
name: item.function?.name,
input: item.function?.arguments ? JSON.parse(item.function.arguments) : {},
};
});
}
const result = {
id: messageId, id: messageId,
type: "message", type: "message",
role: "assistant", role: "assistant",
// @ts-ignore // @ts-ignore
content: completion.choices[0].message.content || content: content,
completion.choices[0].message.tool_calls?.map((item) => { stop_reason: completion.choices[0].finish_reason === 'tool_calls' ? "tool_use" : "end_turn",
return {
type: "tool_use",
id: item.id,
name: item.function?.name,
input: item.function?.arguments
? JSON.parse(item.function.arguments)
: {},
};
}) || [
{
type: "text",
text: "",
},
],
stop_reason:
completion.choices[0].finish_reason === "tool_calls"
? "tool_use"
: "end_turn",
stop_sequence: null, stop_sequence: null,
usage: { };
input_tokens: 100, try {
output_tokens: 50, res.json(result);
}, res.end();
}); return;
res.end(); } catch (error) {
return; log("Error sending response:", error);
res.status(500).send("Internal Server Error");
}
} }
let contentBlockIndex = 0; let contentBlockIndex = 0;
@@ -110,6 +109,8 @@ export async function streamOpenAIResponse(
let isToolUse = false; let isToolUse = false;
let toolUseJson = ""; let toolUseJson = "";
let hasStartedTextBlock = false; let hasStartedTextBlock = false;
let currentToolCallId: string | null = null;
let toolCallJsonMap = new Map<string, string>();
try { try {
for await (const chunk of completion) { for await (const chunk of completion) {
@@ -117,58 +118,80 @@ export async function streamOpenAIResponse(
const delta = chunk.choices[0].delta; const delta = chunk.choices[0].delta;
if (delta.tool_calls && delta.tool_calls.length > 0) { if (delta.tool_calls && delta.tool_calls.length > 0) {
const toolCall = delta.tool_calls[0]; for (const toolCall of delta.tool_calls) {
const toolCallId = toolCall.id;
// Check if this is a new tool call by ID
if (toolCallId && toolCallId !== currentToolCallId) {
// End previous tool call if one was active
if (isToolUse && currentToolCallId) {
const contentBlockStop: MessageEvent = {
type: "content_block_stop",
index: contentBlockIndex,
};
write(
`event: content_block_stop\ndata: ${JSON.stringify(
contentBlockStop
)}\n\n`
);
}
if (!isToolUse) { // Start new tool call block
// Start new tool call block isToolUse = true;
isToolUse = true; currentToolCallId = toolCallId;
const toolBlock: ContentBlock = { contentBlockIndex++;
type: "tool_use", toolCallJsonMap.set(toolCallId, ""); // Initialize JSON accumulator for this tool call
id: `toolu_${Date.now()}`,
name: toolCall.function?.name,
input: {},
};
const toolBlockStart: MessageEvent = { const toolBlock: ContentBlock = {
type: "content_block_start", type: "tool_use",
index: contentBlockIndex, id: toolCallId,
content_block: toolBlock, name: toolCall.function?.name,
}; input: {},
};
currentContentBlocks.push(toolBlock); const toolBlockStart: MessageEvent = {
type: "content_block_start",
index: contentBlockIndex,
content_block: toolBlock,
};
write( currentContentBlocks.push(toolBlock);
`event: content_block_start\ndata: ${JSON.stringify(
toolBlockStart
)}\n\n`
);
toolUseJson = "";
}
// Stream tool call JSON write(
if (toolCall.function?.arguments) { `event: content_block_start\ndata: ${JSON.stringify(
const jsonDelta: MessageEvent = { toolBlockStart
type: "content_block_delta", )}\n\n`
index: contentBlockIndex, );
delta: {
type: "input_json_delta",
partial_json: toolCall.function?.arguments,
},
};
toolUseJson += toolCall.function.arguments;
try {
const parsedJson = JSON.parse(toolUseJson);
currentContentBlocks[contentBlockIndex].input = parsedJson;
} catch (e) {
log(e);
// JSON not yet complete, continue accumulating
} }
write( // Stream tool call JSON
`event: content_block_delta\ndata: ${JSON.stringify(jsonDelta)}\n\n` if (toolCall.function?.arguments && currentToolCallId) {
); const jsonDelta: MessageEvent = {
type: "content_block_delta",
index: contentBlockIndex,
delta: {
type: "input_json_delta",
partial_json: toolCall.function.arguments,
},
};
// Accumulate JSON for this specific tool call
const currentJson = toolCallJsonMap.get(currentToolCallId) || "";
toolCallJsonMap.set(currentToolCallId, currentJson + toolCall.function.arguments);
toolUseJson = toolCallJsonMap.get(currentToolCallId) || "";
try {
const parsedJson = JSON.parse(toolUseJson);
currentContentBlocks[contentBlockIndex].input = parsedJson;
} catch (e) {
log("JSON parsing error (continuing to accumulate):", e);
// JSON not yet complete, continue accumulating
}
write(
`event: content_block_delta\ndata: ${JSON.stringify(jsonDelta)}\n\n`
);
}
} }
} else if (delta.content) { } else if (delta.content) {
// Handle regular text content // Handle regular text content
@@ -187,6 +210,8 @@ export async function streamOpenAIResponse(
); );
contentBlockIndex++; contentBlockIndex++;
isToolUse = false; isToolUse = false;
currentToolCallId = null;
toolUseJson = ""; // Reset for safety
} }
if (!delta.content) continue; if (!delta.content) continue;
@@ -280,15 +305,17 @@ export async function streamOpenAIResponse(
); );
} }
// Close last content block // Close last content block if any is open
const contentBlockStop: MessageEvent = { if (isToolUse || hasStartedTextBlock) {
type: "content_block_stop", const contentBlockStop: MessageEvent = {
index: contentBlockIndex, type: "content_block_stop",
}; index: contentBlockIndex,
};
write( write(
`event: content_block_stop\ndata: ${JSON.stringify(contentBlockStop)}\n\n` `event: content_block_stop\ndata: ${JSON.stringify(contentBlockStop)}\n\n`
); );
}
// Send message_delta event with appropriate stop_reason // Send message_delta event with appropriate stop_reason
const messageDelta: MessageEvent = { const messageDelta: MessageEvent = {