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:
@@ -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();
|
res.end();
|
||||||
return;
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
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,14 +118,33 @@ 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;
|
||||||
|
contentBlockIndex++;
|
||||||
|
toolCallJsonMap.set(toolCallId, ""); // Initialize JSON accumulator for this tool call
|
||||||
|
|
||||||
const toolBlock: ContentBlock = {
|
const toolBlock: ContentBlock = {
|
||||||
type: "tool_use",
|
type: "tool_use",
|
||||||
id: `toolu_${Date.now()}`,
|
id: toolCallId,
|
||||||
name: toolCall.function?.name,
|
name: toolCall.function?.name,
|
||||||
input: {},
|
input: {},
|
||||||
};
|
};
|
||||||
@@ -142,27 +162,29 @@ export async function streamOpenAIResponse(
|
|||||||
toolBlockStart
|
toolBlockStart
|
||||||
)}\n\n`
|
)}\n\n`
|
||||||
);
|
);
|
||||||
toolUseJson = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stream tool call JSON
|
// Stream tool call JSON
|
||||||
if (toolCall.function?.arguments) {
|
if (toolCall.function?.arguments && currentToolCallId) {
|
||||||
const jsonDelta: MessageEvent = {
|
const jsonDelta: MessageEvent = {
|
||||||
type: "content_block_delta",
|
type: "content_block_delta",
|
||||||
index: contentBlockIndex,
|
index: contentBlockIndex,
|
||||||
delta: {
|
delta: {
|
||||||
type: "input_json_delta",
|
type: "input_json_delta",
|
||||||
partial_json: toolCall.function?.arguments,
|
partial_json: toolCall.function.arguments,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
toolUseJson += 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 {
|
try {
|
||||||
const parsedJson = JSON.parse(toolUseJson);
|
const parsedJson = JSON.parse(toolUseJson);
|
||||||
currentContentBlocks[contentBlockIndex].input = parsedJson;
|
currentContentBlocks[contentBlockIndex].input = parsedJson;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log("JSON parsing error (continuing to accumulate):", e);
|
||||||
// JSON not yet complete, continue accumulating
|
// JSON not yet complete, continue accumulating
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +192,7 @@ export async function streamOpenAIResponse(
|
|||||||
`event: content_block_delta\ndata: ${JSON.stringify(jsonDelta)}\n\n`
|
`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
|
||||||
if (isToolUse) {
|
if (isToolUse) {
|
||||||
@@ -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,7 +305,8 @@ export async function streamOpenAIResponse(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close last content block
|
// Close last content block if any is open
|
||||||
|
if (isToolUse || hasStartedTextBlock) {
|
||||||
const contentBlockStop: MessageEvent = {
|
const contentBlockStop: MessageEvent = {
|
||||||
type: "content_block_stop",
|
type: "content_block_stop",
|
||||||
index: contentBlockIndex,
|
index: contentBlockIndex,
|
||||||
@@ -289,6 +315,7 @@ export async function streamOpenAIResponse(
|
|||||||
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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user