Skip to content

feat: zhipu 4v image generations - #2356

Merged
Calcium-Ion merged 1 commit into
QuantumNous:mainfrom
seefs001:feature/zhipiu_4v_image
Dec 9, 2025
Merged

feat: zhipu 4v image generations#2356
Calcium-Ion merged 1 commit into
QuantumNous:mainfrom
seefs001:feature/zhipiu_4v_image

Conversation

@seefs001

@seefs001 seefs001 commented Dec 2, 2025

Copy link
Copy Markdown
Collaborator

#2342

Summary by CodeRabbit

  • New Features

    • Added image generation support for the zhipu_4v channel with options for watermarking and user ID in requests.
    • Responses now include base64-encoded image payloads for generated images.
  • Bug Fixes / Reliability

    • Improved handling of image sources (URL or base64) and more consistent error mapping for upstream failures.

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

@coderabbitai

coderabbitai Bot commented Dec 2, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds zhipu_4v image-generation support: extends ImageRequest DTO with three json.RawMessage fields, implements adaptor routing and URL handling for the images/generations endpoint, and adds a handler that converts zhipu image responses into an OpenAI-compatible payload.

Changes

Cohort / File(s) Summary
DTO Updates
dto/openai_image.go
Added exported fields to ImageRequest: WatermarkEnabled, UserId, and Image (all json.RawMessage). Watermark retained; JSON marshal/unmarshal logic updated to include new fields.
Adaptor updates
relay/channel/zhipu_4v/adaptor.go
ConvertImageRequest now returns the incoming ImageRequest unchanged (nil error). GetRequestURL added handling for RelayModeImagesGenerations to point to <baseURL>/api/paas/v4/images/generations. DoResponse routes images generation responses to zhipu4vImageHandler.
Image handler (new)
relay/channel/zhipu_4v/image.go
New file implementing zhipu4vImageHandler: parses upstream zhipu responses, extracts/decodes base64 image data (or fetches from URL), handles timestamps, converts zhipu errors to OpenAI-style errors, and writes an OpenAI-compatible JSON payload to the client.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Adaptor as zhipu_4v Adaptor
    participant Zhipu as Zhipu API
    participant Handler as zhipu4vImageHandler
    participant ClientResp as OpenAI-format Response

    Client->>Adaptor: POST ImageRequest
    Adaptor->>Adaptor: ConvertImageRequest()
    Adaptor->>Zhipu: POST /api/paas/v4/images/generations (zhipu format)
    Zhipu-->>Handler: HTTP response (data[], created, errors?)
    Handler->>Handler: Validate response, pick timestamp
    Handler->>Handler: For each item: use B64Json/B64Image or fetch URL → base64
    Handler->>Handler: Build OpenAI-style payload (created, data[])
    Handler-->>ClientResp: JSON payload
    ClientResp-->>Client: HTTP 200 with OpenAI-compatible body
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay extra attention to relay/channel/zhipu_4v/image.go (response parsing, base64 extraction, URL fetch error handling).
  • Verify dto/openai_image.go marshal/unmarshal preserve backward compatibility for unknown fields.
  • Check routing in adaptor.go to ensure images generation requests use the new handler and correct URL.

Possibly related PRs

Suggested reviewers

  • creamlike1024

Poem

🐰
I hopped through code fields bright and new,
Watermarks, user IDs, and images too,
I fetched base64 skies and stitched the light,
Now zhipu paintings leap into OpenAI's sight,
A tiny rabbit cheers — pixels take flight! 🎨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: zhipu 4v image generations' accurately summarizes the main change: implementing image generation support for the zhipu 4v channel adapter.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

🧹 Nitpick comments (5)
dto/openai_image.go (1)

30-34: Align UserId naming with Go conventions / other structs

Functionally this is fine and integrates cleanly with the existing custom JSON (un)marshal logic. The only nit is naming: UserId here vs UserID in zhipuImageRequest (and typical Go style). Renaming this field to UserID (keeping the json:"user_id" tag) would avoid confusion and keep things consistent across DTOs while not affecting JSON compatibility.

relay/channel/zhipu_4v/adaptor.go (2)

39-52: Clarify watermark_enabled parsing and fallback behavior

The fallback from WatermarkEnabledWatermark is reasonable, but note the exact semantics:

  • If watermark_enabled is invalid JSON for a bool (e.g. "foo" or null), parseOptionalBool returns an error, enabled stays nil, and you fall through to the Watermark field when it’s set.
  • That means malformed watermark_enabled is silently ignored instead of surfacing a client error.

If that’s intentional (best-effort compatibility), this is fine. If you’d rather fail fast on bad types, you may want to treat err != nil as a hard error instead of quietly falling back.


39-56: Confirm whether zhipu image API needs additional fields (e.g. n, image)

Right now zhipuImageRequest only receives Model, Prompt, Quality, Size, WatermarkEnabled, and UserID. Fields like N or Image from dto.ImageRequest aren’t forwarded at all.

If zhipu’s image generation API truly ignores those concepts, this is fine. If it supports multiple outputs or an input image, it might be worth wiring them through now to avoid a follow-up change.

relay/channel/zhipu_4v/image.go (2)

27-35: Propagate provider usage when available

zhipuImageResponse defines a Usage *dto.Usage, but zhipu4vImageHandler always returns &dto.Usage{} regardless of what the provider sends:

// ...
service.IOCopyBytesGracefully(c, resp, jsonResp)

return &dto.Usage{}, nil

If upstream is already giving you a well-shaped Usage, it’s better to pass it through so accounting/metrics remain accurate. For example:

 	service.IOCopyBytesGracefully(c, resp, jsonResp)
-
-	return &dto.Usage{}, nil
+	if zhipuResp.Usage != nil {
+		return zhipuResp.Usage, nil
+	}
+	return &dto.Usage{}, nil

This keeps the current behavior as a fallback when Usage is missing.

Also applies to: 120-128


130-150: Optional: treat JSON null as “unset” in optional parsers

parseOptionalBool / parseOptionalString treat only len(raw) == 0 as “absent”; a literal null causes common.Unmarshal to fail. Combined with the adaptor swallowing errors for these helpers, null behaves differently from “missing” in a slightly surprising way.

Not a blocker, but if zhipu (or clients) ever send null explicitly, you may want to normalize it to “unset” instead of an error, e.g.:

if len(raw) == 0 || string(raw) == "null" {
    return nil, nil // or "" for the string case
}

This keeps the helpers’ behavior closer to typical JSON optional-field semantics.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ffc45a7 and 53cd6e6.

📒 Files selected for processing (3)
  • dto/openai_image.go (1 hunks)
  • relay/channel/zhipu_4v/adaptor.go (3 hunks)
  • relay/channel/zhipu_4v/image.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
relay/channel/zhipu_4v/adaptor.go (1)
relay/constant/relay_mode.go (1)
  • RelayModeImagesGenerations (14-14)
🔇 Additional comments (2)
relay/channel/zhipu_4v/adaptor.go (2)

83-84: New images/generations URL path looks consistent

The new RelayModeImagesGenerations branch follows the same pattern as embeddings/chat (base URL + /api/paas/v4/...). There’s no special-plan override for images yet, but that matches how this code was previously structured.


136-138: Correctly routes image generations to the zhipu image handler

Branching on RelayModeImagesGenerations and delegating to zhipu4vImageHandler before falling back to the OpenAI adaptor is the right integration point for the new flow. No issues here.

Comment on lines +84 to +118
for _, data := range zhipuResp.Data {
url := data.Url
if url == "" {
url = data.ImageUrl
}
if url == "" {
logger.LogWarn(c, "zhipu_image_missing_url")
continue
}

var b64 string
switch {
case data.B64Json != "":
b64 = data.B64Json
case data.B64Image != "":
b64 = data.B64Image
default:
_, downloaded, err := service.GetImageFromUrl(url)
if err != nil {
logger.LogError(c, "zhipu_image_get_b64_failed: "+err.Error())
continue
}
b64 = downloaded
}

if b64 == "" {
logger.LogWarn(c, "zhipu_image_empty_b64")
continue
}

imageData := openAIImageData{
B64Json: b64,
}
payload.Data = append(payload.Data, imageData)
}

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 | 🟠 Major

Images with only base64 but no URL are currently dropped

In the data loop, you require a non-empty URL before checking B64Json / B64Image:

url := data.Url
if url == "" {
    url = data.ImageUrl
}
if url == "" {
    logger.LogWarn(c, "zhipu_image_missing_url")
    continue
}

var b64 string
switch {
case data.B64Json != "":
    b64 = data.B64Json
case data.B64Image != "":
    b64 = data.B64Image
// ...

If zhipu returns entries that only contain base64 fields (no URL), those images will be skipped even though you already have usable data.

Reordering the logic so you prefer existing base64 fields and only fall back to fetching via URL when both are empty would avoid this data loss. For example:

-	for _, data := range zhipuResp.Data {
-		url := data.Url
-		if url == "" {
-			url = data.ImageUrl
-		}
-		if url == "" {
-			logger.LogWarn(c, "zhipu_image_missing_url")
-			continue
-		}
-
-		var b64 string
-		switch {
-		case data.B64Json != "":
-			b64 = data.B64Json
-		case data.B64Image != "":
-			b64 = data.B64Image
-		default:
-			_, downloaded, err := service.GetImageFromUrl(url)
-			if err != nil {
-				logger.LogError(c, "zhipu_image_get_b64_failed: "+err.Error())
-				continue
-			}
-			b64 = downloaded
-		}
+	for _, data := range zhipuResp.Data {
+		var b64 string
+		switch {
+		case data.B64Json != "":
+			b64 = data.B64Json
+		case data.B64Image != "":
+			b64 = data.B64Image
+		default:
+			url := data.Url
+			if url == "" {
+				url = data.ImageUrl
+			}
+			if url == "" {
+				logger.LogWarn(c, "zhipu_image_missing_url")
+				continue
+			}
+
+			_, downloaded, err := service.GetImageFromUrl(url)
+			if err != nil {
+				logger.LogError(c, "zhipu_image_get_b64_failed: "+err.Error())
+				continue
+			}
+			b64 = downloaded
+		}

The rest of the block (b64 == "" check and appending to payload.Data) can remain unchanged.

🤖 Prompt for AI Agents
In relay/channel/zhipu_4v/image.go around lines 84 to 118, the loop currently
rejects entries with no URL before checking B64Json/B64Image, causing images
that only contain base64 to be dropped; change the logic to first check
data.B64Json and data.B64Image and use those if present, and only when both
base64 fields are empty then resolve url (data.Url or data.ImageUrl) and call
service.GetImageFromUrl; log the "missing url" or "get b64 failed"
warnings/errors only when neither base64 is present and fetching by URL fails or
is absent, then proceed to the existing b64=="" check and append to
payload.Data.

@seefs001
seefs001 force-pushed the feature/zhipiu_4v_image branch from 981d0ed to 2e37347 Compare December 2, 2025 14:57

@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: 0

♻️ Duplicate comments (1)
relay/channel/zhipu_4v/image.go (1)

83-117: Reorder data loop to use base64 fields even when URL is missing

Right now you require a non-empty URL before looking at B64Json/B64Image, so entries that only contain base64 data are dropped as "zhipu_image_missing_url", even though they’re perfectly usable.

Refactor the loop to prefer base64 fields and only fall back to URL download when both are empty. For example:

-	for _, data := range zhipuResp.Data {
-		url := data.Url
-		if url == "" {
-			url = data.ImageUrl
-		}
-		if url == "" {
-			logger.LogWarn(c, "zhipu_image_missing_url")
-			continue
-		}
-
-		var b64 string
-		switch {
-		case data.B64Json != "":
-			b64 = data.B64Json
-		case data.B64Image != "":
-			b64 = data.B64Image
-		default:
-			_, downloaded, err := service.GetImageFromUrl(url)
-			if err != nil {
-				logger.LogError(c, "zhipu_image_get_b64_failed: "+err.Error())
-				continue
-			}
-			b64 = downloaded
-		}
+	for _, data := range zhipuResp.Data {
+		var b64 string
+		switch {
+		case data.B64Json != "":
+			b64 = data.B64Json
+		case data.B64Image != "":
+			b64 = data.B64Image
+		default:
+			url := data.Url
+			if url == "" {
+				url = data.ImageUrl
+			}
+			if url == "" {
+				logger.LogWarn(c, "zhipu_image_missing_url")
+				continue
+			}
+
+			_, downloaded, err := service.GetImageFromUrl(url)
+			if err != nil {
+				logger.LogError(c, "zhipu_image_get_b64_failed: "+err.Error())
+				continue
+			}
+			b64 = downloaded
+		}

The existing b64 == "" check and append logic can stay as-is.

🧹 Nitpick comments (3)
dto/openai_image.go (1)

30-34: New Zhipu-specific fields integrate cleanly; minor naming nit only

The added WatermarkEnabled, UserId, and Image fields fit the existing RawMessage pattern and work with the current custom (un)marshal logic. If you care about Go naming consistency, you might consider renaming UserIdUserID (JSON tag can remain user_id), but that’s purely cosmetic.

relay/channel/zhipu_4v/adaptor.go (1)

65-67: ImagesGenerations URL wiring looks correct; consider special-plan override later

Routing RelayModeImagesGenerations to "%s/api/paas/v4/images/generations" matches the other Zhipu v4 paths. If you ever introduce a special-plan base for images (analogous to OpenAIBaseURL for chat/embeddings), this is the place to hook it in.

relay/channel/zhipu_4v/image.go (1)

119-127: Propagate upstream usage instead of always returning an empty struct

You already parse zhipuResp.Usage but then discard it and return &dto.Usage{}. If upstream provides useful usage info (for logging or billing), it’s better to pass it through:

-	jsonResp, err := common.Marshal(payload)
-	if err != nil {
-		return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
-	}
-
-	service.IOCopyBytesGracefully(c, resp, jsonResp)
-
-	return &dto.Usage{}, nil
+	jsonResp, err := common.Marshal(payload)
+	if err != nil {
+		return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
+	}
+
+	service.IOCopyBytesGracefully(c, resp, jsonResp)
+
+	if zhipuResp.Usage != nil {
+		return zhipuResp.Usage, nil
+	}
+	return &dto.Usage{}, nil

This keeps existing behavior as a fallback while preserving real usage data when present.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53cd6e6 and 2e37347.

📒 Files selected for processing (3)
  • dto/openai_image.go (1 hunks)
  • relay/channel/zhipu_4v/adaptor.go (3 hunks)
  • relay/channel/zhipu_4v/image.go (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-26T09:59:00.337Z
Learnt from: Sh1n3zZ
Repo: QuantumNous/new-api PR: 1659
File: relay/relay_task.go:285-305
Timestamp: 2025-08-26T09:59:00.337Z
Learning: In controller/task_video.go, data: URLs (containing base64 encoded video data) are prevented from being stored in task.FailReason by checking if the URL starts with "data:" before assignment. This same pattern should be applied consistently across the codebase.

Applied to files:

  • relay/channel/zhipu_4v/image.go
🧬 Code graph analysis (1)
relay/channel/zhipu_4v/adaptor.go (1)
relay/constant/relay_mode.go (1)
  • RelayModeImagesGenerations (14-14)
🔇 Additional comments (2)
relay/channel/zhipu_4v/adaptor.go (2)

118-120: ImagesGenerations correctly bypasses generic OpenAI adapter

Branching to zhipu4vImageHandler for RelayModeImagesGenerations ensures the Zhipu-specific image payload is normalized before returning. The rest of the formats still go through the generic OpenAI adapter, which keeps behavior consistent.


38-40: Passing ImageRequest through as-is is acceptable for now

Returning the dto.ImageRequest directly keeps the adapter simple and lets the HTTP layer marshal it to Zhipu's JSON. This is fine as long as Zhipu's API accepts the current OpenAI-shaped payload (including the new raw fields).

@Calcium-Ion
Calcium-Ion merged commit 9561c7b into QuantumNous:main Dec 9, 2025
1 check passed
ennnnny pushed a commit to ennnnny/new-api that referenced this pull request Mar 17, 2026
jiutubaba pushed a commit to jiutubaba/fx-api that referenced this pull request May 17, 2026
…es-multi-tool-continuation

Preserve multi-tool context in OpenAI messages continuation
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.

2 participants