Skip to content

Commit fca015c

Browse files
authored
Merge pull request #2397 from seefs001/fix/tool-call-claude
fix: try to fix tool call issues
2 parents 23292a5 + 1cb2b6f commit fca015c

File tree

1 file changed

+198
-103
lines changed

1 file changed

+198
-103
lines changed

service/convert.go

Lines changed: 198 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ func generateStopBlock(index int) *dto.ClaudeResponse {
201201
}
202202

203203
func StreamResponseOpenAI2Claude(openAIResponse *dto.ChatCompletionsStreamResponse, info *relaycommon.RelayInfo) []*dto.ClaudeResponse {
204+
if info.ClaudeConvertInfo.Done {
205+
return nil
206+
}
207+
204208
var claudeResponses []*dto.ClaudeResponse
205209
if info.SendResponseCount == 1 {
206210
msg := &dto.ClaudeMediaMessage{
@@ -218,53 +222,125 @@ func StreamResponseOpenAI2Claude(openAIResponse *dto.ChatCompletionsStreamRespon
218222
Type: "message_start",
219223
Message: msg,
220224
})
221-
claudeResponses = append(claudeResponses)
222225
//claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
223226
// Type: "ping",
224227
//})
225228
if openAIResponse.IsToolCall() {
226229
info.ClaudeConvertInfo.LastMessagesType = relaycommon.LastMessageTypeTools
230+
var toolCall dto.ToolCallResponse
231+
if len(openAIResponse.Choices) > 0 && len(openAIResponse.Choices[0].Delta.ToolCalls) > 0 {
232+
toolCall = openAIResponse.Choices[0].Delta.ToolCalls[0]
233+
} else {
234+
first := openAIResponse.GetFirstToolCall()
235+
if first != nil {
236+
toolCall = *first
237+
} else {
238+
toolCall = dto.ToolCallResponse{}
239+
}
240+
}
227241
resp := &dto.ClaudeResponse{
228242
Type: "content_block_start",
229243
ContentBlock: &dto.ClaudeMediaMessage{
230-
Id: openAIResponse.GetFirstToolCall().ID,
244+
Id: toolCall.ID,
231245
Type: "tool_use",
232-
Name: openAIResponse.GetFirstToolCall().Function.Name,
246+
Name: toolCall.Function.Name,
233247
Input: map[string]interface{}{},
234248
},
235249
}
236250
resp.SetIndex(0)
237251
claudeResponses = append(claudeResponses, resp)
252+
// 首块包含工具 delta,则追加 input_json_delta
253+
if toolCall.Function.Arguments != "" {
254+
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
255+
Index: &info.ClaudeConvertInfo.Index,
256+
Type: "content_block_delta",
257+
Delta: &dto.ClaudeMediaMessage{
258+
Type: "input_json_delta",
259+
PartialJson: &toolCall.Function.Arguments,
260+
},
261+
})
262+
}
238263
} else {
239264

240265
}
241266
// 判断首个响应是否存在内容(非标准的 OpenAI 响应)
242-
if len(openAIResponse.Choices) > 0 && len(openAIResponse.Choices[0].Delta.GetContentString()) > 0 {
243-
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
244-
Index: &info.ClaudeConvertInfo.Index,
245-
Type: "content_block_start",
246-
ContentBlock: &dto.ClaudeMediaMessage{
247-
Type: "text",
248-
Text: common.GetPointer[string](""),
249-
},
250-
})
267+
if len(openAIResponse.Choices) > 0 {
268+
reasoning := openAIResponse.Choices[0].Delta.GetReasoningContent()
269+
content := openAIResponse.Choices[0].Delta.GetContentString()
270+
271+
if reasoning != "" {
272+
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
273+
Index: &info.ClaudeConvertInfo.Index,
274+
Type: "content_block_start",
275+
ContentBlock: &dto.ClaudeMediaMessage{
276+
Type: "thinking",
277+
Thinking: common.GetPointer[string](""),
278+
},
279+
})
280+
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
281+
Index: &info.ClaudeConvertInfo.Index,
282+
Type: "content_block_delta",
283+
Delta: &dto.ClaudeMediaMessage{
284+
Type: "thinking_delta",
285+
Thinking: &reasoning,
286+
},
287+
})
288+
info.ClaudeConvertInfo.LastMessagesType = relaycommon.LastMessageTypeThinking
289+
} else if content != "" {
290+
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
291+
Index: &info.ClaudeConvertInfo.Index,
292+
Type: "content_block_start",
293+
ContentBlock: &dto.ClaudeMediaMessage{
294+
Type: "text",
295+
Text: common.GetPointer[string](""),
296+
},
297+
})
298+
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
299+
Index: &info.ClaudeConvertInfo.Index,
300+
Type: "content_block_delta",
301+
Delta: &dto.ClaudeMediaMessage{
302+
Type: "text_delta",
303+
Text: common.GetPointer[string](content),
304+
},
305+
})
306+
info.ClaudeConvertInfo.LastMessagesType = relaycommon.LastMessageTypeText
307+
}
308+
}
309+
310+
// 如果首块就带 finish_reason,需要立即发送停止块
311+
if len(openAIResponse.Choices) > 0 && openAIResponse.Choices[0].FinishReason != nil && *openAIResponse.Choices[0].FinishReason != "" {
312+
info.FinishReason = *openAIResponse.Choices[0].FinishReason
313+
claudeResponses = append(claudeResponses, generateStopBlock(info.ClaudeConvertInfo.Index))
314+
oaiUsage := openAIResponse.Usage
315+
if oaiUsage == nil {
316+
oaiUsage = info.ClaudeConvertInfo.Usage
317+
}
318+
if oaiUsage != nil {
319+
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
320+
Type: "message_delta",
321+
Usage: &dto.ClaudeUsage{
322+
InputTokens: oaiUsage.PromptTokens,
323+
OutputTokens: oaiUsage.CompletionTokens,
324+
CacheCreationInputTokens: oaiUsage.PromptTokensDetails.CachedCreationTokens,
325+
CacheReadInputTokens: oaiUsage.PromptTokensDetails.CachedTokens,
326+
},
327+
Delta: &dto.ClaudeMediaMessage{
328+
StopReason: common.GetPointer[string](stopReasonOpenAI2Claude(info.FinishReason)),
329+
},
330+
})
331+
}
251332
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
252-
Index: &info.ClaudeConvertInfo.Index,
253-
Type: "content_block_delta",
254-
Delta: &dto.ClaudeMediaMessage{
255-
Type: "text_delta",
256-
Text: common.GetPointer[string](openAIResponse.Choices[0].Delta.GetContentString()),
257-
},
333+
Type: "message_stop",
258334
})
259-
info.ClaudeConvertInfo.LastMessagesType = relaycommon.LastMessageTypeText
335+
info.ClaudeConvertInfo.Done = true
260336
}
261337
return claudeResponses
262338
}
263339

264340
if len(openAIResponse.Choices) == 0 {
265341
// no choices
266342
// 可能为非标准的 OpenAI 响应,判断是否已经完成
267-
if info.Done {
343+
if info.ClaudeConvertInfo.Done {
268344
claudeResponses = append(claudeResponses, generateStopBlock(info.ClaudeConvertInfo.Index))
269345
oaiUsage := info.ClaudeConvertInfo.Usage
270346
if oaiUsage != nil {
@@ -288,16 +364,110 @@ func StreamResponseOpenAI2Claude(openAIResponse *dto.ChatCompletionsStreamRespon
288364
return claudeResponses
289365
} else {
290366
chosenChoice := openAIResponse.Choices[0]
291-
if chosenChoice.FinishReason != nil && *chosenChoice.FinishReason != "" {
292-
// should be done
367+
doneChunk := chosenChoice.FinishReason != nil && *chosenChoice.FinishReason != ""
368+
if doneChunk {
293369
info.FinishReason = *chosenChoice.FinishReason
294-
if !info.Done {
295-
return claudeResponses
370+
}
371+
372+
var claudeResponse dto.ClaudeResponse
373+
var isEmpty bool
374+
claudeResponse.Type = "content_block_delta"
375+
if len(chosenChoice.Delta.ToolCalls) > 0 {
376+
toolCalls := chosenChoice.Delta.ToolCalls
377+
if info.ClaudeConvertInfo.LastMessagesType != relaycommon.LastMessageTypeTools {
378+
claudeResponses = append(claudeResponses, generateStopBlock(info.ClaudeConvertInfo.Index))
379+
info.ClaudeConvertInfo.Index++
380+
}
381+
info.ClaudeConvertInfo.LastMessagesType = relaycommon.LastMessageTypeTools
382+
383+
for i, toolCall := range toolCalls {
384+
blockIndex := info.ClaudeConvertInfo.Index
385+
if toolCall.Index != nil {
386+
blockIndex = *toolCall.Index
387+
} else if len(toolCalls) > 1 {
388+
blockIndex = info.ClaudeConvertInfo.Index + i
389+
}
390+
391+
idx := blockIndex
392+
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
393+
Index: &idx,
394+
Type: "content_block_start",
395+
ContentBlock: &dto.ClaudeMediaMessage{
396+
Id: toolCall.ID,
397+
Type: "tool_use",
398+
Name: toolCall.Function.Name,
399+
Input: map[string]interface{}{},
400+
},
401+
})
402+
403+
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
404+
Index: &idx,
405+
Type: "content_block_delta",
406+
Delta: &dto.ClaudeMediaMessage{
407+
Type: "input_json_delta",
408+
PartialJson: &toolCall.Function.Arguments,
409+
},
410+
})
411+
412+
info.ClaudeConvertInfo.Index = blockIndex
413+
}
414+
} else {
415+
reasoning := chosenChoice.Delta.GetReasoningContent()
416+
textContent := chosenChoice.Delta.GetContentString()
417+
if reasoning != "" || textContent != "" {
418+
if reasoning != "" {
419+
if info.ClaudeConvertInfo.LastMessagesType != relaycommon.LastMessageTypeThinking {
420+
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
421+
Index: &info.ClaudeConvertInfo.Index,
422+
Type: "content_block_start",
423+
ContentBlock: &dto.ClaudeMediaMessage{
424+
Type: "thinking",
425+
Thinking: common.GetPointer[string](""),
426+
},
427+
})
428+
}
429+
info.ClaudeConvertInfo.LastMessagesType = relaycommon.LastMessageTypeThinking
430+
claudeResponse.Delta = &dto.ClaudeMediaMessage{
431+
Type: "thinking_delta",
432+
Thinking: &reasoning,
433+
}
434+
} else {
435+
if info.ClaudeConvertInfo.LastMessagesType != relaycommon.LastMessageTypeText {
436+
if info.ClaudeConvertInfo.LastMessagesType == relaycommon.LastMessageTypeThinking || info.ClaudeConvertInfo.LastMessagesType == relaycommon.LastMessageTypeTools {
437+
claudeResponses = append(claudeResponses, generateStopBlock(info.ClaudeConvertInfo.Index))
438+
info.ClaudeConvertInfo.Index++
439+
}
440+
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
441+
Index: &info.ClaudeConvertInfo.Index,
442+
Type: "content_block_start",
443+
ContentBlock: &dto.ClaudeMediaMessage{
444+
Type: "text",
445+
Text: common.GetPointer[string](""),
446+
},
447+
})
448+
}
449+
info.ClaudeConvertInfo.LastMessagesType = relaycommon.LastMessageTypeText
450+
claudeResponse.Delta = &dto.ClaudeMediaMessage{
451+
Type: "text_delta",
452+
Text: common.GetPointer[string](textContent),
453+
}
454+
}
455+
} else {
456+
isEmpty = true
296457
}
297458
}
298-
if info.Done {
459+
460+
claudeResponse.Index = &info.ClaudeConvertInfo.Index
461+
if !isEmpty && claudeResponse.Delta != nil {
462+
claudeResponses = append(claudeResponses, &claudeResponse)
463+
}
464+
465+
if doneChunk || info.ClaudeConvertInfo.Done {
299466
claudeResponses = append(claudeResponses, generateStopBlock(info.ClaudeConvertInfo.Index))
300-
oaiUsage := info.ClaudeConvertInfo.Usage
467+
oaiUsage := openAIResponse.Usage
468+
if oaiUsage == nil {
469+
oaiUsage = info.ClaudeConvertInfo.Usage
470+
}
301471
if oaiUsage != nil {
302472
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
303473
Type: "message_delta",
@@ -315,83 +485,8 @@ func StreamResponseOpenAI2Claude(openAIResponse *dto.ChatCompletionsStreamRespon
315485
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
316486
Type: "message_stop",
317487
})
318-
} else {
319-
var claudeResponse dto.ClaudeResponse
320-
var isEmpty bool
321-
claudeResponse.Type = "content_block_delta"
322-
if len(chosenChoice.Delta.ToolCalls) > 0 {
323-
if info.ClaudeConvertInfo.LastMessagesType != relaycommon.LastMessageTypeTools {
324-
claudeResponses = append(claudeResponses, generateStopBlock(info.ClaudeConvertInfo.Index))
325-
info.ClaudeConvertInfo.Index++
326-
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
327-
Index: &info.ClaudeConvertInfo.Index,
328-
Type: "content_block_start",
329-
ContentBlock: &dto.ClaudeMediaMessage{
330-
Id: openAIResponse.GetFirstToolCall().ID,
331-
Type: "tool_use",
332-
Name: openAIResponse.GetFirstToolCall().Function.Name,
333-
Input: map[string]interface{}{},
334-
},
335-
})
336-
}
337-
info.ClaudeConvertInfo.LastMessagesType = relaycommon.LastMessageTypeTools
338-
// tools delta
339-
claudeResponse.Delta = &dto.ClaudeMediaMessage{
340-
Type: "input_json_delta",
341-
PartialJson: &chosenChoice.Delta.ToolCalls[0].Function.Arguments,
342-
}
343-
} else {
344-
reasoning := chosenChoice.Delta.GetReasoningContent()
345-
textContent := chosenChoice.Delta.GetContentString()
346-
if reasoning != "" || textContent != "" {
347-
if reasoning != "" {
348-
if info.ClaudeConvertInfo.LastMessagesType != relaycommon.LastMessageTypeThinking {
349-
//info.ClaudeConvertInfo.Index++
350-
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
351-
Index: &info.ClaudeConvertInfo.Index,
352-
Type: "content_block_start",
353-
ContentBlock: &dto.ClaudeMediaMessage{
354-
Type: "thinking",
355-
Thinking: common.GetPointer[string](""),
356-
},
357-
})
358-
}
359-
info.ClaudeConvertInfo.LastMessagesType = relaycommon.LastMessageTypeThinking
360-
// text delta
361-
claudeResponse.Delta = &dto.ClaudeMediaMessage{
362-
Type: "thinking_delta",
363-
Thinking: &reasoning,
364-
}
365-
} else {
366-
if info.ClaudeConvertInfo.LastMessagesType != relaycommon.LastMessageTypeText {
367-
if info.LastMessagesType == relaycommon.LastMessageTypeThinking || info.LastMessagesType == relaycommon.LastMessageTypeTools {
368-
claudeResponses = append(claudeResponses, generateStopBlock(info.ClaudeConvertInfo.Index))
369-
info.ClaudeConvertInfo.Index++
370-
}
371-
claudeResponses = append(claudeResponses, &dto.ClaudeResponse{
372-
Index: &info.ClaudeConvertInfo.Index,
373-
Type: "content_block_start",
374-
ContentBlock: &dto.ClaudeMediaMessage{
375-
Type: "text",
376-
Text: common.GetPointer[string](""),
377-
},
378-
})
379-
}
380-
info.ClaudeConvertInfo.LastMessagesType = relaycommon.LastMessageTypeText
381-
// text delta
382-
claudeResponse.Delta = &dto.ClaudeMediaMessage{
383-
Type: "text_delta",
384-
Text: common.GetPointer[string](textContent),
385-
}
386-
}
387-
} else {
388-
isEmpty = true
389-
}
390-
}
391-
claudeResponse.Index = &info.ClaudeConvertInfo.Index
392-
if !isEmpty {
393-
claudeResponses = append(claudeResponses, &claudeResponse)
394-
}
488+
info.ClaudeConvertInfo.Done = true
489+
return claudeResponses
395490
}
396491
}
397492

0 commit comments

Comments
 (0)