Skip to content

Commit c40931c

Browse files
committed
Confirmed anthropic web_search & mcp call working
1 parent 37b2838 commit c40931c

3 files changed

Lines changed: 81 additions & 12 deletions

File tree

src/BE/DB/Extensions/StepContent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public static IEnumerable<StepContent> FromFullResponse(InternalChatSegment last
112112
{
113113
Base64PreviewImage => null, // skip preview images
114114
TextChatSegment text => FromText(text.Text),
115-
ThinkChatSegment think => FromThink(think.Think),
115+
ThinkChatSegment think => FromThink(think.Think, think.Signature != null ? Convert.FromBase64String(think.Signature) : null),
116116
ImageChatSegment image => FromFile(imageMcCache[image].Task.GetAwaiter().GetResult()),
117117
ToolCallSegment tool => FromTool(tool.Id ?? tool.Index.ToString(), tool.Name!, tool.Arguments!),
118118
ToolCallResponseSegment toolResp => FromToolResponse(toolResp.ToolCallId, toolResp.Response, toolResp.DurationMs, toolResp.IsSuccess),

src/BE/Services/Models/ChatServices/Anthropic/AnthropicChatService.cs

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -264,16 +264,86 @@ static object MakeFieldDefObject(JsonNode? node)
264264

265265
static List<MessageParam> ConvertMessages(IEnumerable<Step> messages, bool allowThinkingBlocks)
266266
{
267-
List<Step> mergedToolMessages = MergeToolMessages(messages);
267+
List<Step> mergedToolMessages = [..SwitchServerToolResponsesAsUser(MergeToolMessages(messages))];
268268
return [.. mergedToolMessages.Select(x => ToAnthropicMessage(x, allowThinkingBlocks))];
269269

270-
static List<Step> MergeToolMessages(IEnumerable<Step> messages)
270+
static IEnumerable<Step> SwitchServerToolResponsesAsUser(IEnumerable<Step> steps)
271+
{
272+
// Anthropic requires tool responses to be in user messages
273+
// When response like web_search_tool_response comes back from server, we need to switch them to user role from assistant
274+
// For example: [user, assistant(think, tool, tool_response, text), user] -> [user, assistant(think, tool), user(tool_response), assistant(text), user]
275+
foreach (Step step in steps)
276+
{
277+
if (step.ChatRole != DBChatRole.Assistant)
278+
{
279+
yield return step;
280+
continue;
281+
}
282+
283+
List<StepContent> assistantBuffer = new();
284+
List<StepContent> userBuffer = new();
285+
286+
foreach (StepContent part in step.StepContents)
287+
{
288+
if (part.StepContentToolCallResponse is not null)
289+
{
290+
// flush any accumulated assistant parts before emitting user tool responses
291+
if (assistantBuffer.Count > 0)
292+
{
293+
yield return new Step()
294+
{
295+
ChatRoleId = (byte)DBChatRole.Assistant,
296+
StepContents = [.. assistantBuffer],
297+
};
298+
assistantBuffer.Clear();
299+
}
300+
301+
userBuffer.Add(part);
302+
}
303+
else
304+
{
305+
// if we have accumulated user tool responses, emit them first
306+
if (userBuffer.Count > 0)
307+
{
308+
yield return new Step()
309+
{
310+
ChatRoleId = (byte)DBChatRole.User,
311+
StepContents = [.. userBuffer],
312+
};
313+
userBuffer.Clear();
314+
}
315+
316+
assistantBuffer.Add(part);
317+
}
318+
}
319+
320+
// flush remaining buffers in the original order
321+
if (assistantBuffer.Count > 0)
322+
{
323+
yield return new Step()
324+
{
325+
ChatRoleId = (byte)DBChatRole.Assistant,
326+
StepContents = [.. assistantBuffer],
327+
};
328+
}
329+
330+
if (userBuffer.Count > 0)
331+
{
332+
yield return new Step()
333+
{
334+
ChatRoleId = (byte)DBChatRole.User,
335+
StepContents = [.. userBuffer],
336+
};
337+
}
338+
}
339+
}
340+
341+
static IEnumerable<Step> MergeToolMessages(IEnumerable<Step> messages)
271342
{
272343
// openai will omit tool messages, but anthropic needs them merged into the user message
273344
// for example:
274345
// openai: [user, assistant(request tool call, probably multiple), tool(tool response 1), tool(tool response 2), assistant]
275346
// anthropic: [user, assistant(request tool call, probably multiple), user(tool response 1 + 2), assistant]
276-
List<Step> result = [];
277347
List<StepContent> toolBuffer = [];
278348

279349
foreach (Step message in messages)
@@ -286,27 +356,25 @@ static List<Step> MergeToolMessages(IEnumerable<Step> messages)
286356
{
287357
if (toolBuffer.Count > 0)
288358
{
289-
result.Add(new Step()
359+
yield return new Step()
290360
{
291361
ChatRoleId = (byte)DBChatRole.User,
292362
StepContents = [.. toolBuffer],
293-
});
363+
};
294364
toolBuffer.Clear();
295365
}
296-
result.Add(message);
366+
yield return message;
297367
}
298368
}
299369

300370
if (toolBuffer.Count > 0)
301371
{
302-
result.Add(new Step()
372+
yield return new Step()
303373
{
304374
ChatRoleId = (byte)DBChatRole.User,
305375
StepContents = [.. toolBuffer],
306-
});
376+
};
307377
}
308-
309-
return result;
310378
}
311379

312380
static MessageParam ToAnthropicMessage(Step message, bool allowThinkingBlocks)

src/BE/Services/Models/Dtos/ChatSegmentItem.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,11 @@ public static void AddOne(this List<ChatSegmentItem> items,
289289
// ───── 思考片段合并 ─────────────────────────────────
290290
else if (last is ThinkChatSegment lastThink && item is ThinkChatSegment curThink)
291291
{
292+
string? signature = lastThink.Signature != null ? lastThink.Signature + curThink.Signature : curThink.Signature;
292293
items[^1] = lastThink with
293294
{
294295
Think = lastThink.Think + curThink.Think,
295-
Signature = (lastThink.Signature ?? "") + curThink.Signature
296+
Signature = signature
296297
};
297298
}
298299
// ───── Tool‑Call 片段合并 ───────────────────────────

0 commit comments

Comments
 (0)