@@ -201,6 +201,10 @@ func generateStopBlock(index int) *dto.ClaudeResponse {
201201}
202202
203203func 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