Skip to content

feat(gemini): implement markdown image handling in text processing - #2346

Merged
Calcium-Ion merged 1 commit into
mainfrom
nano-banana-multi-turn
Dec 1, 2025
Merged

feat(gemini): implement markdown image handling in text processing#2346
Calcium-Ion merged 1 commit into
mainfrom
nano-banana-multi-turn

Conversation

@Calcium-Ion

@Calcium-Ion Calcium-Ion commented Dec 1, 2025

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of images embedded in text content when using Gemini integration. The system now properly extracts markdown-formatted images and enforces image count limits to prevent exceeding API constraints.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 1, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

The CovertOpenAI2Gemini function now parses text content for embedded markdown image markers (![...](data:...)), extracting inline data images into separate GeminiPart segments while preserving surrounding text. Image count is tracked and enforced against a maximum limit with error handling.

Changes

Cohort / File(s) Summary
Markdown Image Extraction in Text Processing
relay/channel/gemini/relay-gemini.go
Modified text content handling to detect and parse markdown image syntax within text segments. When markdown images are found, they are decoded from data URLs and emitted as separate InlineData parts; remaining text is preserved. Image count is tracked with enforcement of GeminiVisionMaxImageNum limit. Image URL processing logic remains unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Careful review of markdown image parsing regex/logic and edge cases (malformed data URLs, encoding errors)
  • Verification of image counting mechanism and max limit enforcement
  • Testing considerations for mixed text-image segments and boundary conditions

Poem

🐰 A markdown sprite dances through your text,
Finding images tucked where they rest,
Extracting them whole with base64 grace,
While text flows around them—a perfect place! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: implementing markdown image handling in text processing for the Gemini relay.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch nano-banana-multi-turn

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36a739e and 4dbdbde.

📒 Files selected for processing (1)
  • relay/channel/gemini/relay-gemini.go (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: RedwindA
Repo: QuantumNous/new-api PR: 1537
File: relay/gemini_handler.go:330-342
Timestamp: 2025-08-08T17:12:43.157Z
Learning: In the new-api repository, the `GeminiEmbeddingHandler` function in `relay/gemini_handler.go` is designed specifically for native Gemini embedding requests and therefore does not require the `ConvertGeminiRequest` step that is used in the chat handler. The embedding requests are already in the native Gemini format and don't need conversion.
📚 Learning: 2025-08-08T17:12:43.157Z
Learnt from: RedwindA
Repo: QuantumNous/new-api PR: 1537
File: relay/gemini_handler.go:330-342
Timestamp: 2025-08-08T17:12:43.157Z
Learning: In the new-api repository, the `GeminiEmbeddingHandler` function in `relay/gemini_handler.go` is designed specifically for native Gemini embedding requests and therefore does not require the `ConvertGeminiRequest` step that is used in the chat handler. The embedding requests are already in the native Gemini format and don't need conversion.

Applied to files:

  • relay/channel/gemini/relay-gemini.go
📚 Learning: 2025-06-21T03:37:41.726Z
Learnt from: 9Ninety
Repo: QuantumNous/new-api PR: 1273
File: relay/channel/gemini/relay-gemini.go:97-116
Timestamp: 2025-06-21T03:37:41.726Z
Learning: In relay/channel/gemini/relay-gemini.go, the thinking budget calculation logic (including the MaxOutputTokens multiplication) was introduced in PR #1247. PR #1273 focused specifically on decoupling the thoughts summary feature from thinking budget settings and did not modify the existing thinking budget behavior.

Applied to files:

  • relay/channel/gemini/relay-gemini.go
🧬 Code graph analysis (1)
relay/channel/gemini/relay-gemini.go (3)
dto/gemini.go (2)
  • GeminiPart (241-254)
  • GeminiInlineData (183-186)
constant/env.go (1)
  • GeminiVisionMaxImageNum (13-13)
service/image.go (1)
  • DecodeBase64FileData (43-66)

Comment on lines +449 to +510
// check markdown image ![image](data:image/jpeg;base64,xxxxxxxxxxxx)
// 使用字符串查找而非正则,避免大文本性能问题
text := part.Text
hasMarkdownImage := false
for {
// 快速检查是否包含 markdown 图片标记
startIdx := strings.Index(text, "![")
if startIdx == -1 {
break
}
// 找到 ](
bracketIdx := strings.Index(text[startIdx:], "](data:")
if bracketIdx == -1 {
break
}
bracketIdx += startIdx
// 找到闭合的 )
closeIdx := strings.Index(text[bracketIdx+2:], ")")
if closeIdx == -1 {
break
}
closeIdx += bracketIdx + 2

hasMarkdownImage = true
// 添加图片前的文本
if startIdx > 0 {
textBefore := text[:startIdx]
if textBefore != "" {
parts = append(parts, dto.GeminiPart{
Text: textBefore,
})
}
}
// 提取 data URL (从 "](" 后面开始,到 ")" 之前)
dataUrl := text[bracketIdx+2 : closeIdx]
imageNum += 1
if constant.GeminiVisionMaxImageNum != -1 && imageNum > constant.GeminiVisionMaxImageNum {
return nil, fmt.Errorf("too many images in the message, max allowed is %d", constant.GeminiVisionMaxImageNum)
}
format, base64String, err := service.DecodeBase64FileData(dataUrl)
if err != nil {
return nil, fmt.Errorf("decode markdown base64 image data failed: %s", err.Error())
}
imgPart := dto.GeminiPart{
InlineData: &dto.GeminiInlineData{
MimeType: format,
Data: base64String,
},
}
if shouldAttachThoughtSignature {
imgPart.ThoughtSignature = json.RawMessage(strconv.Quote(thoughtSignatureBypassValue))
}
parts = append(parts, imgPart)
// 继续处理剩余文本
text = text[closeIdx+1:]
}
// 添加剩余文本或原始文本(如果没有找到 markdown 图片)
if !hasMarkdownImage {
parts = append(parts, dto.GeminiPart{
Text: part.Text,
})
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Trailing text after the last markdown image is dropped

When part.Text contains one or more markdown data‑URL images plus trailing text (e.g. foo ![img](data:...) bar), the trailing segment (" bar") is never appended:

  • Inside the loop you consume text and reassign text = text[closeIdx+1:].
  • After the loop, if hasMarkdownImage is true, you skip the fallback that appends part.Text as a whole.
  • There is no code that appends the remaining text value when at least one image was parsed.

This loses user content and is a functional correctness bug.

You can fix it by appending the leftover text after the loop when hasMarkdownImage is true:

-				// 添加剩余文本或原始文本(如果没有找到 markdown 图片)
-				if !hasMarkdownImage {
-					parts = append(parts, dto.GeminiPart{
-						Text: part.Text,
-					})
-				}
+				// 添加剩余文本或原始文本(如果没有找到 markdown 图片)
+				if hasMarkdownImage {
+					if text != "" {
+						parts = append(parts, dto.GeminiPart{
+							Text: text,
+						})
+					}
+				} else {
+					parts = append(parts, dto.GeminiPart{
+						Text: part.Text,
+					})
+				}

This keeps existing behavior when no markdown image is present, while preserving any tail text after the last parsed image.

🤖 Prompt for AI Agents
In relay/channel/gemini/relay-gemini.go around lines 449 to 510 the loop that
extracts markdown data‑URL images consumes and reassigns text but never appends
the trailing remainder when hasMarkdownImage is true, causing tail text to be
lost; after the image-parsing loop, if hasMarkdownImage is true and the
remaining text (variable text) is non-empty, append a dto.GeminiPart with Text:
text to parts (instead of or in addition to the existing fallback that only runs
when hasMarkdownImage is false), so you preserve any trailing text after the
last parsed image.

@Calcium-Ion
Calcium-Ion merged commit 86aeb72 into main Dec 1, 2025
1 check passed
@Calcium-Ion
Calcium-Ion deleted the nano-banana-multi-turn branch January 29, 2026 17:10
ennnnny pushed a commit to ennnnny/new-api that referenced this pull request Mar 17, 2026
…i-turn

feat(gemini): implement markdown image handling in text processing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant