fix: 修复 Antigravity 非流式响应文本丢失问题
Gemini 流式响应是增量的,需要累积所有 chunk 的文本内容。 原代码只保留最后一个有 parts 的 chunk,导致实际文本被空 text + thoughtSignature 的最终 chunk 覆盖。 添加 collectedTextParts 收集所有文本片段,返回前合并。
This commit is contained in:
@@ -1522,7 +1522,7 @@ func (s *AntigravityGatewayService) handleGeminiStreamingResponse(c *gin.Context
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleGeminiStreamToNonStreaming 读取上游流式响应,合并为非流式响应返回给客户端
|
// handleGeminiStreamToNonStreaming 读取上游流式响应,合并为非流式响应返回给客户端
|
||||||
// Gemini 流式响应中每个 chunk 都包含累积的完整文本,只需保留最后一个有效响应
|
// Gemini 流式响应是增量的,需要累积所有 chunk 的内容
|
||||||
func (s *AntigravityGatewayService) handleGeminiStreamToNonStreaming(c *gin.Context, resp *http.Response, startTime time.Time) (*antigravityStreamResult, error) {
|
func (s *AntigravityGatewayService) handleGeminiStreamToNonStreaming(c *gin.Context, resp *http.Response, startTime time.Time) (*antigravityStreamResult, error) {
|
||||||
scanner := bufio.NewScanner(resp.Body)
|
scanner := bufio.NewScanner(resp.Body)
|
||||||
maxLineSize := defaultMaxLineSize
|
maxLineSize := defaultMaxLineSize
|
||||||
@@ -1536,6 +1536,7 @@ func (s *AntigravityGatewayService) handleGeminiStreamToNonStreaming(c *gin.Cont
|
|||||||
var last map[string]any
|
var last map[string]any
|
||||||
var lastWithParts map[string]any
|
var lastWithParts map[string]any
|
||||||
var collectedImageParts []map[string]any // 收集所有包含图片的 parts
|
var collectedImageParts []map[string]any // 收集所有包含图片的 parts
|
||||||
|
var collectedTextParts []string // 收集所有文本片段
|
||||||
|
|
||||||
type scanEvent struct {
|
type scanEvent struct {
|
||||||
line string
|
line string
|
||||||
@@ -1638,12 +1639,15 @@ func (s *AntigravityGatewayService) handleGeminiStreamToNonStreaming(c *gin.Cont
|
|||||||
// 保留最后一个有 parts 的响应
|
// 保留最后一个有 parts 的响应
|
||||||
if parts := extractGeminiParts(parsed); len(parts) > 0 {
|
if parts := extractGeminiParts(parsed); len(parts) > 0 {
|
||||||
lastWithParts = parsed
|
lastWithParts = parsed
|
||||||
// 收集包含图片的 parts
|
// 收集包含图片和文本的 parts
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
if inlineData, ok := part["inlineData"].(map[string]any); ok {
|
if inlineData, ok := part["inlineData"].(map[string]any); ok {
|
||||||
collectedImageParts = append(collectedImageParts, part)
|
collectedImageParts = append(collectedImageParts, part)
|
||||||
_ = inlineData // 避免 unused 警告
|
_ = inlineData // 避免 unused 警告
|
||||||
}
|
}
|
||||||
|
if text, ok := part["text"].(string); ok && text != "" {
|
||||||
|
collectedTextParts = append(collectedTextParts, text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1671,6 +1675,11 @@ returnResponse:
|
|||||||
finalResponse = mergeImagePartsToResponse(finalResponse, collectedImageParts)
|
finalResponse = mergeImagePartsToResponse(finalResponse, collectedImageParts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果收集到了文本,需要合并到最终响应中
|
||||||
|
if len(collectedTextParts) > 0 {
|
||||||
|
finalResponse = mergeTextPartsToResponse(finalResponse, collectedTextParts)
|
||||||
|
}
|
||||||
|
|
||||||
respBody, err := json.Marshal(finalResponse)
|
respBody, err := json.Marshal(finalResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
return nil, fmt.Errorf("failed to marshal response: %w", err)
|
||||||
@@ -1742,6 +1751,82 @@ func mergeImagePartsToResponse(response map[string]any, imageParts []map[string]
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mergeTextPartsToResponse 将收集到的文本合并到 Gemini 响应中
|
||||||
|
// 流式响应是增量的,需要累积所有文本片段
|
||||||
|
func mergeTextPartsToResponse(response map[string]any, textParts []string) map[string]any {
|
||||||
|
if len(textParts) == 0 {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并所有文本
|
||||||
|
mergedText := strings.Join(textParts, "")
|
||||||
|
|
||||||
|
// 深拷贝 response 避免修改原始数据
|
||||||
|
result := make(map[string]any)
|
||||||
|
for k, v := range response {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取或创建 candidates
|
||||||
|
candidates, ok := result["candidates"].([]any)
|
||||||
|
if !ok || len(candidates) == 0 {
|
||||||
|
candidates = []any{map[string]any{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取第一个 candidate
|
||||||
|
candidate, ok := candidates[0].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
candidate = make(map[string]any)
|
||||||
|
candidates[0] = candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取或创建 content
|
||||||
|
content, ok := candidate["content"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
content = map[string]any{"role": "model"}
|
||||||
|
candidate["content"] = content
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取现有 parts
|
||||||
|
existingParts, ok := content["parts"].([]any)
|
||||||
|
if !ok {
|
||||||
|
existingParts = []any{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找并更新第一个 text part,或创建新的
|
||||||
|
textUpdated := false
|
||||||
|
newParts := make([]any, 0, len(existingParts)+1)
|
||||||
|
for _, p := range existingParts {
|
||||||
|
pm, ok := p.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
newParts = append(newParts, p)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 跳过空文本的 part(可能只有 thoughtSignature)
|
||||||
|
if _, hasText := pm["text"]; hasText && !textUpdated {
|
||||||
|
// 用累积的文本替换
|
||||||
|
newPart := make(map[string]any)
|
||||||
|
for k, v := range pm {
|
||||||
|
newPart[k] = v
|
||||||
|
}
|
||||||
|
newPart["text"] = mergedText
|
||||||
|
newParts = append(newParts, newPart)
|
||||||
|
textUpdated = true
|
||||||
|
} else {
|
||||||
|
newParts = append(newParts, pm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有找到 text part,添加一个新的
|
||||||
|
if !textUpdated {
|
||||||
|
newParts = append([]any{map[string]any{"text": mergedText}}, newParts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
content["parts"] = newParts
|
||||||
|
result["candidates"] = candidates
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (s *AntigravityGatewayService) writeClaudeError(c *gin.Context, status int, errType, message string) error {
|
func (s *AntigravityGatewayService) writeClaudeError(c *gin.Context, status int, errType, message string) error {
|
||||||
c.JSON(status, gin.H{
|
c.JSON(status, gin.H{
|
||||||
"type": "error",
|
"type": "error",
|
||||||
|
|||||||
Reference in New Issue
Block a user