fix(antigravity): 修复非流式 Claude To Antigravity 响应内容为空的问题
- 修复 TransformGeminiToClaude 的 JSON 解析逻辑,当 V1InternalResponse 解析成功但 candidates 为空时,尝试直接解析为 GeminiResponse 格式 - 修复 handleClaudeStreamToNonStreaming 收集流式响应的逻辑,累积所有 chunks 的内容而不是只保留最后一个(最后一个 chunk 通常 text 为空) - 新增 mergeCollectedPartsToResponse 函数,合并所有类型的 parts (text、thinking、functionCall、inlineData),保持原始顺序 - 连续的普通 text parts 合并为一个,thinking/functionCall/inlineData 保持原样
This commit is contained in:
@@ -20,6 +20,15 @@ func TransformGeminiToClaude(geminiResp []byte, originalModel string) ([]byte, *
|
|||||||
v1Resp.Response = directResp
|
v1Resp.Response = directResp
|
||||||
v1Resp.ResponseID = directResp.ResponseID
|
v1Resp.ResponseID = directResp.ResponseID
|
||||||
v1Resp.ModelVersion = directResp.ModelVersion
|
v1Resp.ModelVersion = directResp.ModelVersion
|
||||||
|
} else if len(v1Resp.Response.Candidates) == 0 {
|
||||||
|
// 第一次解析成功但 candidates 为空,说明是直接的 GeminiResponse 格式
|
||||||
|
var directResp GeminiResponse
|
||||||
|
if err2 := json.Unmarshal(geminiResp, &directResp); err2 != nil {
|
||||||
|
return nil, nil, fmt.Errorf("parse gemini response as direct: %w", err2)
|
||||||
|
}
|
||||||
|
v1Resp.Response = directResp
|
||||||
|
v1Resp.ResponseID = directResp.ResponseID
|
||||||
|
v1Resp.ModelVersion = directResp.ModelVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用处理器转换
|
// 使用处理器转换
|
||||||
@@ -174,16 +183,20 @@ func (p *NonStreamingProcessor) processPart(part *GeminiPart) {
|
|||||||
p.trailingSignature = ""
|
p.trailingSignature = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
p.textBuilder += part.Text
|
// 非空 text 带签名 - 特殊处理:先输出 text,再输出空 thinking 块
|
||||||
|
|
||||||
// 非空 text 带签名 - 立即刷新并输出空 thinking 块
|
|
||||||
if signature != "" {
|
if signature != "" {
|
||||||
p.flushText()
|
p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
|
||||||
|
Type: "text",
|
||||||
|
Text: part.Text,
|
||||||
|
})
|
||||||
p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
|
p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
|
||||||
Type: "thinking",
|
Type: "thinking",
|
||||||
Thinking: "",
|
Thinking: "",
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
// 普通 text (无签名) - 累积到 builder
|
||||||
|
p.textBuilder += part.Text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1985,6 +1985,58 @@ func getOrCreateGeminiParts(response map[string]any) (result map[string]any, exi
|
|||||||
return result, existingParts, setParts
|
return result, existingParts, setParts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mergeCollectedPartsToResponse 将收集的所有 parts 合并到 Gemini 响应中
|
||||||
|
// 这个函数会合并所有类型的 parts:text、thinking、functionCall、inlineData 等
|
||||||
|
// 保持原始顺序,只合并连续的普通 text parts
|
||||||
|
func mergeCollectedPartsToResponse(response map[string]any, collectedParts []map[string]any) map[string]any {
|
||||||
|
if len(collectedParts) == 0 {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _, setParts := getOrCreateGeminiParts(response)
|
||||||
|
|
||||||
|
// 合并策略:
|
||||||
|
// 1. 保持原始顺序
|
||||||
|
// 2. 连续的普通 text parts 合并为一个
|
||||||
|
// 3. thinking、functionCall、inlineData 等保持原样
|
||||||
|
var mergedParts []any
|
||||||
|
var textBuffer strings.Builder
|
||||||
|
|
||||||
|
flushTextBuffer := func() {
|
||||||
|
if textBuffer.Len() > 0 {
|
||||||
|
mergedParts = append(mergedParts, map[string]any{
|
||||||
|
"text": textBuffer.String(),
|
||||||
|
})
|
||||||
|
textBuffer.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, part := range collectedParts {
|
||||||
|
// 检查是否是普通 text part
|
||||||
|
if text, ok := part["text"].(string); ok {
|
||||||
|
// 检查是否有 thought 标记
|
||||||
|
if thought, _ := part["thought"].(bool); thought {
|
||||||
|
// thinking part,先刷新 text buffer,然后保留原样
|
||||||
|
flushTextBuffer()
|
||||||
|
mergedParts = append(mergedParts, part)
|
||||||
|
} else {
|
||||||
|
// 普通 text,累积到 buffer
|
||||||
|
_, _ = textBuffer.WriteString(text)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 非 text part(functionCall、inlineData 等),先刷新 text buffer,然后保留原样
|
||||||
|
flushTextBuffer()
|
||||||
|
mergedParts = append(mergedParts, part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新剩余的 text
|
||||||
|
flushTextBuffer()
|
||||||
|
|
||||||
|
setParts(mergedParts)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// mergeImagePartsToResponse 将收集到的图片 parts 合并到 Gemini 响应中
|
// mergeImagePartsToResponse 将收集到的图片 parts 合并到 Gemini 响应中
|
||||||
func mergeImagePartsToResponse(response map[string]any, imageParts []map[string]any) map[string]any {
|
func mergeImagePartsToResponse(response map[string]any, imageParts []map[string]any) map[string]any {
|
||||||
if len(imageParts) == 0 {
|
if len(imageParts) == 0 {
|
||||||
@@ -2168,6 +2220,7 @@ func (s *AntigravityGatewayService) handleClaudeStreamToNonStreaming(c *gin.Cont
|
|||||||
var firstTokenMs *int
|
var firstTokenMs *int
|
||||||
var last map[string]any
|
var last map[string]any
|
||||||
var lastWithParts map[string]any
|
var lastWithParts map[string]any
|
||||||
|
var collectedParts []map[string]any // 收集所有 parts(包括 text、thinking、functionCall、inlineData 等)
|
||||||
|
|
||||||
type scanEvent struct {
|
type scanEvent struct {
|
||||||
line string
|
line string
|
||||||
@@ -2262,9 +2315,12 @@ func (s *AntigravityGatewayService) handleClaudeStreamToNonStreaming(c *gin.Cont
|
|||||||
|
|
||||||
last = parsed
|
last = parsed
|
||||||
|
|
||||||
// 保留最后一个有 parts 的响应
|
// 保留最后一个有 parts 的响应,并收集所有 parts
|
||||||
if parts := extractGeminiParts(parsed); len(parts) > 0 {
|
if parts := extractGeminiParts(parsed); len(parts) > 0 {
|
||||||
lastWithParts = parsed
|
lastWithParts = parsed
|
||||||
|
|
||||||
|
// 收集所有 parts(text、thinking、functionCall、inlineData 等)
|
||||||
|
collectedParts = append(collectedParts, parts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
case <-intervalCh:
|
case <-intervalCh:
|
||||||
@@ -2287,6 +2343,11 @@ returnResponse:
|
|||||||
return nil, s.writeClaudeError(c, http.StatusBadGateway, "upstream_error", "Empty response from upstream")
|
return nil, s.writeClaudeError(c, http.StatusBadGateway, "upstream_error", "Empty response from upstream")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将收集的所有 parts 合并到最终响应中
|
||||||
|
if len(collectedParts) > 0 {
|
||||||
|
finalResponse = mergeCollectedPartsToResponse(finalResponse, collectedParts)
|
||||||
|
}
|
||||||
|
|
||||||
// 序列化为 JSON(Gemini 格式)
|
// 序列化为 JSON(Gemini 格式)
|
||||||
geminiBody, err := json.Marshal(finalResponse)
|
geminiBody, err := json.Marshal(finalResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user