Skip to content

Commit 2d0768b

Browse files
committed
wip fix anthropic agentic streaming sanitization
1 parent 155bb7a commit 2d0768b

File tree

1 file changed

+55
-22
lines changed

1 file changed

+55
-22
lines changed

server/utils/agents/aibitat/providers/anthropic.js

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -55,24 +55,57 @@ class AnthropicProvider extends Provider {
5555
// For array content, we need special handling for tool use sequences
5656
if (msg.content.some((item) => item.type === "tool_use")) {
5757
// If this is a tool_use message, we only need the tool_use part to be valid
58-
return msg.content.some(
58+
const hasValidToolUse = msg.content.some(
5959
(item) => item.type === "tool_use" && item.name && item.id
6060
);
61+
62+
// Ensure there's at least one valid text content if tool_use exists
63+
const hasValidText = msg.content.some(
64+
(item) =>
65+
item.type === "text" && item.text && item.text.trim().length > 0
66+
);
67+
68+
return hasValidToolUse && hasValidText;
6169
}
6270

63-
// For other array content, ensure each item has required fields
71+
// For other array content, ensure each item has required fields and is non-empty
6472
return msg.content.every((item) => {
65-
if (item.type === "text")
73+
if (item.type === "text") {
6674
return item.text && item.text.trim().length > 0;
67-
if (item.type === "tool_result")
68-
return item.tool_use_id && item.content;
75+
}
76+
if (item.type === "tool_result") {
77+
return (
78+
item.tool_use_id &&
79+
item.content &&
80+
(typeof item.content === "string"
81+
? item.content.trim().length > 0
82+
: JSON.stringify(item.content).length > 2)
83+
);
84+
}
6985
return false;
7086
});
7187
}
7288
return false;
7389
})
7490
.map((msg) => {
7591
const { role, content } = msg;
92+
// For array content, ensure we have a valid text message
93+
if (
94+
Array.isArray(content) &&
95+
content.some((item) => item.type === "tool_use")
96+
) {
97+
const textContent = content.find((item) => item.type === "text");
98+
if (
99+
!textContent ||
100+
!textContent.text ||
101+
!textContent.text.trim().length
102+
) {
103+
content.unshift({
104+
type: "text",
105+
text: "I'll use a tool to help answer this question.",
106+
});
107+
}
108+
}
76109
return { role, content };
77110
});
78111
}
@@ -84,27 +117,27 @@ class AnthropicProvider extends Provider {
84117
[...messages].forEach((msg, i) => {
85118
if (msg.role !== "function") return normalized.push(msg);
86119

87-
// If the last message is a role "function" this is our special aibitat message node.
88-
// and we need to remove it from the array of messages.
89-
// Since Anthropic needs to have the tool call resolved, we look at the previous chat to "function"
90-
// and go through its content "thought" from ~ln:143 and get the tool_call id so we can resolve
91-
// this tool call properly.
92120
const functionCompletion = msg;
93121
const toolCallId = messages[i - 1]?.content?.find(
94122
(msg) => msg.type === "tool_use"
95123
)?.id;
96124

97-
// Append the Anthropic acceptable node to the message chain so function can resolve.
98-
normalized.push({
99-
role: "user",
100-
content: [
101-
{
102-
type: "tool_result",
103-
tool_use_id: toolCallId,
104-
content: functionCompletion.content,
105-
},
106-
],
107-
});
125+
// Skip if we can't find a matching tool_use
126+
if (!toolCallId) return;
127+
128+
// Only add if we have actual content
129+
if (functionCompletion.content) {
130+
normalized.push({
131+
role: "user",
132+
content: [
133+
{
134+
type: "tool_result",
135+
tool_use_id: toolCallId,
136+
content: functionCompletion.content,
137+
},
138+
],
139+
});
140+
}
108141
});
109142
return normalized;
110143
}
@@ -225,7 +258,7 @@ class AnthropicProvider extends Provider {
225258
}
226259
}
227260

228-
if (!!result.functionCall) {
261+
if (result.functionCall) {
229262
result.functionCall.arguments = safeJsonParse(
230263
result.functionCall.arguments,
231264
{}

0 commit comments

Comments
 (0)