response_transformer.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. package antigravity
  2. import (
  3. "crypto/rand"
  4. "encoding/json"
  5. "fmt"
  6. "log"
  7. "strings"
  8. "sync/atomic"
  9. "time"
  10. )
  11. // TransformGeminiToClaude 将 Gemini 响应转换为 Claude 格式(非流式)
  12. func TransformGeminiToClaude(geminiResp []byte, originalModel string) ([]byte, *ClaudeUsage, error) {
  13. // 解包 v1internal 响应
  14. var v1Resp V1InternalResponse
  15. if err := json.Unmarshal(geminiResp, &v1Resp); err != nil {
  16. // 尝试直接解析为 GeminiResponse
  17. var directResp GeminiResponse
  18. if err2 := json.Unmarshal(geminiResp, &directResp); err2 != nil {
  19. return nil, nil, fmt.Errorf("parse gemini response: %w", err)
  20. }
  21. v1Resp.Response = directResp
  22. v1Resp.ResponseID = directResp.ResponseID
  23. v1Resp.ModelVersion = directResp.ModelVersion
  24. } else if len(v1Resp.Response.Candidates) == 0 {
  25. // 第一次解析成功但 candidates 为空,说明是直接的 GeminiResponse 格式
  26. var directResp GeminiResponse
  27. if err2 := json.Unmarshal(geminiResp, &directResp); err2 != nil {
  28. return nil, nil, fmt.Errorf("parse gemini response as direct: %w", err2)
  29. }
  30. v1Resp.Response = directResp
  31. v1Resp.ResponseID = directResp.ResponseID
  32. v1Resp.ModelVersion = directResp.ModelVersion
  33. }
  34. // 使用处理器转换
  35. processor := NewNonStreamingProcessor()
  36. claudeResp := processor.Process(&v1Resp.Response, v1Resp.ResponseID, originalModel)
  37. // 序列化
  38. respBytes, err := json.Marshal(claudeResp)
  39. if err != nil {
  40. return nil, nil, fmt.Errorf("marshal claude response: %w", err)
  41. }
  42. return respBytes, &claudeResp.Usage, nil
  43. }
  44. // NonStreamingProcessor 非流式响应处理器
  45. type NonStreamingProcessor struct {
  46. contentBlocks []ClaudeContentItem
  47. textBuilder string
  48. thinkingBuilder string
  49. thinkingSignature string
  50. trailingSignature string
  51. hasToolCall bool
  52. }
  53. // NewNonStreamingProcessor 创建非流式响应处理器
  54. func NewNonStreamingProcessor() *NonStreamingProcessor {
  55. return &NonStreamingProcessor{
  56. contentBlocks: make([]ClaudeContentItem, 0),
  57. }
  58. }
  59. // Process 处理 Gemini 响应
  60. func (p *NonStreamingProcessor) Process(geminiResp *GeminiResponse, responseID, originalModel string) *ClaudeResponse {
  61. // 获取 parts
  62. var parts []GeminiPart
  63. if len(geminiResp.Candidates) > 0 && geminiResp.Candidates[0].Content != nil {
  64. parts = geminiResp.Candidates[0].Content.Parts
  65. }
  66. // 处理所有 parts
  67. for _, part := range parts {
  68. p.processPart(&part)
  69. }
  70. if len(geminiResp.Candidates) > 0 {
  71. if grounding := geminiResp.Candidates[0].GroundingMetadata; grounding != nil {
  72. p.processGrounding(grounding)
  73. }
  74. }
  75. // 刷新剩余内容
  76. p.flushThinking()
  77. p.flushText()
  78. // 处理 trailingSignature
  79. if p.trailingSignature != "" {
  80. p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
  81. Type: "thinking",
  82. Thinking: "",
  83. Signature: p.trailingSignature,
  84. })
  85. }
  86. // 构建响应
  87. return p.buildResponse(geminiResp, responseID, originalModel)
  88. }
  89. // processPart 处理单个 part
  90. func (p *NonStreamingProcessor) processPart(part *GeminiPart) {
  91. signature := part.ThoughtSignature
  92. // 1. FunctionCall 处理
  93. if part.FunctionCall != nil {
  94. p.flushThinking()
  95. p.flushText()
  96. // 处理 trailingSignature
  97. if p.trailingSignature != "" {
  98. p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
  99. Type: "thinking",
  100. Thinking: "",
  101. Signature: p.trailingSignature,
  102. })
  103. p.trailingSignature = ""
  104. }
  105. p.hasToolCall = true
  106. // 生成 tool_use id
  107. toolID := part.FunctionCall.ID
  108. if toolID == "" {
  109. toolID = fmt.Sprintf("%s-%s", part.FunctionCall.Name, generateRandomID())
  110. }
  111. item := ClaudeContentItem{
  112. Type: "tool_use",
  113. ID: toolID,
  114. Name: part.FunctionCall.Name,
  115. Input: part.FunctionCall.Args,
  116. }
  117. if signature != "" {
  118. item.Signature = signature
  119. }
  120. p.contentBlocks = append(p.contentBlocks, item)
  121. return
  122. }
  123. // 2. Text 处理
  124. if part.Text != "" || part.Thought {
  125. if part.Thought {
  126. // Thinking part
  127. p.flushText()
  128. // 处理 trailingSignature
  129. if p.trailingSignature != "" {
  130. p.flushThinking()
  131. p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
  132. Type: "thinking",
  133. Thinking: "",
  134. Signature: p.trailingSignature,
  135. })
  136. p.trailingSignature = ""
  137. }
  138. p.thinkingBuilder += part.Text
  139. if signature != "" {
  140. p.thinkingSignature = signature
  141. }
  142. } else {
  143. // 普通 Text
  144. if part.Text == "" {
  145. // 空 text 带签名 - 暂存
  146. if signature != "" {
  147. p.trailingSignature = signature
  148. }
  149. return
  150. }
  151. p.flushThinking()
  152. // 处理之前的 trailingSignature
  153. if p.trailingSignature != "" {
  154. p.flushText()
  155. p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
  156. Type: "thinking",
  157. Thinking: "",
  158. Signature: p.trailingSignature,
  159. })
  160. p.trailingSignature = ""
  161. }
  162. // 非空 text 带签名 - 特殊处理:先输出 text,再输出空 thinking 块
  163. if signature != "" {
  164. p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
  165. Type: "text",
  166. Text: part.Text,
  167. })
  168. p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
  169. Type: "thinking",
  170. Thinking: "",
  171. Signature: signature,
  172. })
  173. } else {
  174. // 普通 text (无签名) - 累积到 builder
  175. p.textBuilder += part.Text
  176. }
  177. }
  178. }
  179. // 3. InlineData (Image) 处理
  180. if part.InlineData != nil && part.InlineData.Data != "" {
  181. p.flushThinking()
  182. markdownImg := fmt.Sprintf("![image](data:%s;base64,%s)",
  183. part.InlineData.MimeType, part.InlineData.Data)
  184. p.textBuilder += markdownImg
  185. p.flushText()
  186. }
  187. }
  188. func (p *NonStreamingProcessor) processGrounding(grounding *GeminiGroundingMetadata) {
  189. groundingText := buildGroundingText(grounding)
  190. if groundingText == "" {
  191. return
  192. }
  193. p.flushThinking()
  194. p.flushText()
  195. p.textBuilder += groundingText
  196. p.flushText()
  197. }
  198. // flushText 刷新 text builder
  199. func (p *NonStreamingProcessor) flushText() {
  200. if p.textBuilder == "" {
  201. return
  202. }
  203. p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
  204. Type: "text",
  205. Text: p.textBuilder,
  206. })
  207. p.textBuilder = ""
  208. }
  209. // flushThinking 刷新 thinking builder
  210. func (p *NonStreamingProcessor) flushThinking() {
  211. if p.thinkingBuilder == "" && p.thinkingSignature == "" {
  212. return
  213. }
  214. p.contentBlocks = append(p.contentBlocks, ClaudeContentItem{
  215. Type: "thinking",
  216. Thinking: p.thinkingBuilder,
  217. Signature: p.thinkingSignature,
  218. })
  219. p.thinkingBuilder = ""
  220. p.thinkingSignature = ""
  221. }
  222. // buildResponse 构建最终响应
  223. func (p *NonStreamingProcessor) buildResponse(geminiResp *GeminiResponse, responseID, originalModel string) *ClaudeResponse {
  224. var finishReason string
  225. if len(geminiResp.Candidates) > 0 {
  226. finishReason = geminiResp.Candidates[0].FinishReason
  227. if finishReason == "MALFORMED_FUNCTION_CALL" {
  228. log.Printf("[Antigravity] MALFORMED_FUNCTION_CALL detected in response for model %s", originalModel)
  229. if geminiResp.Candidates[0].Content != nil {
  230. if b, err := json.Marshal(geminiResp.Candidates[0].Content); err == nil {
  231. log.Printf("[Antigravity] Malformed content: %s", string(b))
  232. }
  233. }
  234. }
  235. }
  236. stopReason := "end_turn"
  237. if p.hasToolCall {
  238. stopReason = "tool_use"
  239. } else if finishReason == "MAX_TOKENS" {
  240. stopReason = "max_tokens"
  241. }
  242. // 注意:Gemini 的 promptTokenCount 包含 cachedContentTokenCount,
  243. // 但 Claude 的 input_tokens 不包含 cache_read_input_tokens,需要减去
  244. usage := ClaudeUsage{}
  245. if geminiResp.UsageMetadata != nil {
  246. cached := geminiResp.UsageMetadata.CachedContentTokenCount
  247. usage.InputTokens = geminiResp.UsageMetadata.PromptTokenCount - cached
  248. usage.OutputTokens = geminiResp.UsageMetadata.CandidatesTokenCount + geminiResp.UsageMetadata.ThoughtsTokenCount
  249. usage.CacheReadInputTokens = cached
  250. usage.ImageOutputTokens = geminiResp.UsageMetadata.ImageOutputTokens()
  251. }
  252. // 生成响应 ID
  253. respID := responseID
  254. if respID == "" {
  255. respID = geminiResp.ResponseID
  256. }
  257. if respID == "" {
  258. respID = generateAnthropicMsgID()
  259. }
  260. return &ClaudeResponse{
  261. ID: respID,
  262. Type: "message",
  263. Role: "assistant",
  264. Model: originalModel,
  265. Content: p.contentBlocks,
  266. StopReason: stopReason,
  267. Usage: usage,
  268. }
  269. }
  270. func buildGroundingText(grounding *GeminiGroundingMetadata) string {
  271. if grounding == nil {
  272. return ""
  273. }
  274. var builder strings.Builder
  275. if len(grounding.WebSearchQueries) > 0 {
  276. _, _ = builder.WriteString("\n\n---\nWeb search queries: ")
  277. _, _ = builder.WriteString(strings.Join(grounding.WebSearchQueries, ", "))
  278. }
  279. if len(grounding.GroundingChunks) > 0 {
  280. var links []string
  281. for i, chunk := range grounding.GroundingChunks {
  282. if chunk.Web == nil {
  283. continue
  284. }
  285. title := strings.TrimSpace(chunk.Web.Title)
  286. if title == "" {
  287. title = "Source"
  288. }
  289. uri := strings.TrimSpace(chunk.Web.URI)
  290. if uri == "" {
  291. uri = "#"
  292. }
  293. links = append(links, fmt.Sprintf("[%d] [%s](%s)", i+1, title, uri))
  294. }
  295. if len(links) > 0 {
  296. _, _ = builder.WriteString("\n\nSources:\n")
  297. _, _ = builder.WriteString(strings.Join(links, "\n"))
  298. }
  299. }
  300. return builder.String()
  301. }
  302. // fallbackCounter 降级伪随机 ID 的全局计数器,混入 seed 避免高并发下 UnixNano 相同导致碰撞。
  303. var fallbackCounter uint64
  304. // generateRandomID 生成密码学安全的随机 ID
  305. func generateRandomID() string {
  306. const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  307. id := make([]byte, 12)
  308. randBytes := make([]byte, 12)
  309. if _, err := rand.Read(randBytes); err != nil {
  310. // 避免在请求路径里 panic:极端情况下熵源不可用时降级为伪随机。
  311. // 这里主要用于生成响应/工具调用的临时 ID,安全要求不高但需尽量避免碰撞。
  312. cnt := atomic.AddUint64(&fallbackCounter, 1)
  313. seed := uint64(time.Now().UnixNano()) ^ cnt
  314. seed ^= uint64(len(err.Error())) << 32
  315. for i := range id {
  316. seed ^= seed << 13
  317. seed ^= seed >> 7
  318. seed ^= seed << 17
  319. id[i] = chars[int(seed)%len(chars)]
  320. }
  321. return string(id)
  322. }
  323. for i, b := range randBytes {
  324. id[i] = chars[int(b)%len(chars)]
  325. }
  326. return string(id)
  327. }
  328. // generateAnthropicMsgID 生成 Anthropic 官方格式的 message ID:msg_01 + 22 位 Base62
  329. func generateAnthropicMsgID() string {
  330. const charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
  331. const idLen = 22
  332. randomBytes := make([]byte, idLen)
  333. if _, err := rand.Read(randomBytes); err != nil {
  334. return "msg_01" + generateRandomID() + generateRandomID()[:10]
  335. }
  336. b := make([]byte, idLen)
  337. for i := range b {
  338. b[i] = charset[int(randomBytes[i])%len(charset)]
  339. }
  340. return "msg_01" + string(b)
  341. }