Skip to content

Conversation

@lnx082
Copy link

@lnx082 lnx082 commented Dec 1, 2025

English | 简体中文

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Built its own designer, fully self-validated

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Background and solution

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Google Gemini API support with three model variants for enhanced AI capabilities.
    • Enabled multimodal content support, allowing text and image content in messages.
  • Bug Fixes

    • Fixed JSON deserialization errors in message content handling.
    • Improved model name format recognition and normalization.
  • Documentation

    • Added comprehensive integration guides and quickstart documentation.
    • Included detailed API examples and troubleshooting resources.

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 1, 2025

Walkthrough

Google Gemini API integration is added via an adapter-based pattern. New GeminiApiAdapter class transforms OpenAI-style requests to Gemini format and vice versa. Three Gemini model variants are registered. AiChatV1ServiceImpl detects and routes Gemini models through the adapter. Configuration and multimodal content support (Object-typed message content) enable end-to-end functionality.

Changes

Cohort / File(s) Change Summary
Gemini Adapter & Core Logic
base/src/main/java/com/tinyengine/it/service/app/adapter/GeminiApiAdapter.java, base/src/test/java/com/tinyengine/it/service/app/adapter/GeminiApiAdapterTest.java
New public adapter class with request/response transformation methods (convertRequestToGemini, convertResponseFromGemini, isGeminiModel). Includes role mapping, content part assembly, image handling, and test coverage for model recognition and format conversions.
Model Enumeration
base/src/main/java/com/tinyengine/it/common/enums/Enums.java
Added three Gemini model variants to FoundationModel enum: GEMINI_PRO, GEMINI_1_5_PRO, GEMINI_1_5_FLASH.
Configuration & Routing
base/src/main/java/com/tinyengine/it/config/AiChatConfig.java, base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java
AiChatConfig adds Gemini API URL constant and per-model configuration entries with x-goog-api-key header setup. AiChatV1ServiceImpl integrates GeminiApiAdapter with model detection, URL normalization, request body conversion, and response adaptation logic.
Multimodal Content Support
base/src/main/java/com/tinyengine/it/model/dto/AiMessages.java, base/src/main/java/com/tinyengine/it/service/app/impl/AiChatServiceImpl.java
AiMessages.content type changed from String to Object; added getContentAsString() accessor for backward compatibility. AiChatServiceImpl updated to safely extract and convert non-string content.
Infrastructure & Configuration
pom.xml, app/src/main/resources/application-dev.yml
MariaDB JDBC driver replaced with MySQL Connector/J. Database connection parameters updated (port, driver, credentials, URL).
Documentation & Examples
QUICKSTART_GEMINI.md, README_GEMINI.md, documents/gemini-integration.md, documents/gemini-examples.http, documents/gemini-test-examples.http, Gemini集成完成总结.md, 修复总结_*.md, 问题分析_*.md
Comprehensive documentation covering quickstart, integration architecture, usage examples (curl, Postman, code samples), best practices, troubleshooting, parameter references, and error handling guidance. Supplementary analysis and fix summary documents.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant AiChatV1ServiceImpl as AiChat Service
    participant GeminiAdapter as Gemini Adapter
    participant GeminiAPI as Gemini API
    
    Client->>AiChatV1ServiceImpl: chatCompletion(ChatRequest with Gemini model)
    activate AiChatV1ServiceImpl
    
    AiChatV1ServiceImpl->>GeminiAdapter: isGeminiModel(modelName)
    activate GeminiAdapter
    GeminiAdapter-->>AiChatV1ServiceImpl: true
    deactivate GeminiAdapter
    
    AiChatV1ServiceImpl->>AiChatV1ServiceImpl: normalizeApiUrl(Gemini format)
    AiChatV1ServiceImpl->>GeminiAdapter: convertRequestToGemini(requestBody)
    activate GeminiAdapter
    GeminiAdapter->>GeminiAdapter: convertMessagesToContents(roles, multimodal)
    GeminiAdapter-->>AiChatV1ServiceImpl: Gemini formatted request
    deactivate GeminiAdapter
    
    AiChatV1ServiceImpl->>GeminiAPI: POST /v1beta/models/{model}:generateContent
    activate GeminiAPI
    GeminiAPI-->>AiChatV1ServiceImpl: Gemini API response
    deactivate GeminiAPI
    
    AiChatV1ServiceImpl->>GeminiAdapter: convertResponseFromGemini(response, model)
    activate GeminiAdapter
    GeminiAdapter->>GeminiAdapter: build choices, map finish_reason, parse parts
    GeminiAdapter-->>AiChatV1ServiceImpl: OpenAI formatted response
    deactivate GeminiAdapter
    
    AiChatV1ServiceImpl-->>Client: OpenAI-compatible ChatResponse
    deactivate AiChatV1ServiceImpl
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–60 minutes

Areas requiring extra attention:

  • GeminiApiAdapter.java: Core conversion logic for request/response transformation; verify all role mappings (system→user, assistant→model), content part assembly for multimodal payloads, image base64 handling, and edge cases in field mapping (e.g., stopSequences, finish_reason translation).
  • AiChatV1ServiceImpl.java: Extensive modifications to request flow with conditional Gemini handling; ensure correct URL normalization, header construction (x-goog-api-key placement, Authorization header omission for Gemini), and response routing for both standard and streaming paths.
  • AiMessages.java: Field type change from String to Object; verify all downstream callers handle the new type safely and that getContentAsString() is called appropriately where string content is expected.
  • AiChatConfig.java: Per-model configuration additions; verify Gemini endpoint URLs, header injection, and that configuration entries are correctly wired for all three Gemini model variants.
  • Multimodal content handling: Test cases and real-world payloads with image data embedded as base64 to ensure proper serialization and deserialization.

Poem

🐰 A rabbit hops through Gemini's glow,
Transforming requests to and fro!
OpenAI meets Google's clever way,
With images dancing and models at play.
Adapter magic makes it so—
Five new files, and the system's aglow! 🎨✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Lnx082/my branch' appears to be a branch name reference rather than a descriptive summary of the changes. Replace the title with a clear, concise description of the main change, such as 'Add Google Gemini API integration support' or 'Integrate Gemini API adapter with OpenAI-compatible interface'.
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
pom.xml (1)

30-30: Remove unused MariaDB version property and clean up commented code.

The mariadb-java-client.version property (line 30) is now unused after the database driver migration. Additionally, the commented-out MariaDB dependency block (lines 121-125) should be removed rather than left as stale comments to improve code maintainability.

Apply this diff to clean up:

  <properties>
      <java.version>17</java.version>
      <fastjson.version>2.0.50</fastjson.version>
      <hutool.version>5.8.27</hutool.version>
      <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
      <org.projectlombok.version>1.18.32</org.projectlombok.version>
      <druid-spring-boot-starter.version>1.2.21</druid-spring-boot-starter.version>
      <mybatis-plus.version>3.5.7</mybatis-plus.version>
      <jackson.version>2.17.2</jackson.version>
-     <mariadb-java-client.version>3.3.3</mariadb-java-client.version>
      <freemarker.version>2.3.32</freemarker.version>
      ...
  </properties>
  ...
- <!--        <dependency>-->
- <!--            <groupId>org.mariadb.jdbc</groupId>-->
- <!--            <artifactId>mariadb-java-client</artifactId>-->
- <!--            <version>${mariadb-java-client.version}</version>-->
- <!--        </dependency>-->
      <dependency>
          <groupId>com.mysql</groupId>
          <artifactId>mysql-connector-j</artifactId>
          <scope>runtime</scope>
      </dependency>

Also applies to: 121-125

base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java (3)

180-183: Duplicate temperature assignment.

Temperature is set unconditionally on line 180, then conditionally again on lines 181-183. The unconditional assignment should be removed.

-        body.put("temperature", request.getTemperature());
         if (request.getTemperature() != null) {
             body.put("temperature", request.getTemperature());
         }

196-198: Incorrect condition check - copy-paste error.

The condition checks getMaxInputTokens() but the body sets vl_high_resolution_images. This should check getVlHighResolutionImages().

-        if (request.getMaxInputTokens() != null) {
+        if (request.getVlHighResolutionImages() != null) {
             body.put("vl_high_resolution_images", request.getVlHighResolutionImages());
         }

291-322: Gemini streaming responses are not converted to OpenAI format.

The processStreamResponse method accepts isGemini and model parameters but never uses them. Gemini streaming responses will be returned in Gemini's native format instead of being converted to OpenAI-compatible format, breaking API consistency.

This could cause client-side parsing issues when streaming from Gemini models.

Consider implementing streaming response conversion for Gemini, or at minimum, document this limitation. The non-streaming path correctly converts responses (lines 282-286).

🧹 Nitpick comments (16)
QUICKSTART_GEMINI.md (1)

14-22: Avoid hard‑coding a personal Windows path in quickstart commands

cd E:\tiny-engine-backend-java makes the guide look environment‑specific and may confuse users on other OSes or with different checkout locations. Consider using a placeholder like <project-root> instead.

-# 编译项目
-cd E:\tiny-engine-backend-java
+# 编译项目(将 <project-root> 替换为你的项目根目录)
+cd <project-root>
documents/gemini-test-examples.http (1)

1-148: Examples look consistent with the unified chat schema

The request bodies (model/apiKey/messages/temperature/stream/baseUrl) all align with the adapter’s OpenAI-style contract and cover the important Gemini cases (simple/streaming/multi-turn/custom baseUrl). You may optionally add a brief note explaining when to prefer /app-center/api/chat/completions vs /app-center/api/ai/chat to avoid confusion.

base/src/main/java/com/tinyengine/it/service/app/impl/AiChatServiceImpl.java (1)

265-291: Multimodal-safe content handling looks correct; consider null behavior

Switching to Object contentObj and normalizing via String.valueOf plus getContentAsString() avoids ClassCastException after AiMessages.content became Object, and keeps the prompt-prefix logic intact. One small edge case: if messages.get(0).getContent() is null, this will append the literal "null" string into the prompt. If that’s undesirable, you could special‑case null to an empty string before concatenation.

-Object contentObj = messages.get(0).getContent();
-// 确保content是字符串类型
-String content = contentObj instanceof String ? (String) contentObj : String.valueOf(contentObj);
+Object contentObj = messages.get(0).getContent();
+// 确保content是字符串类型,并避免 null → "null"
+String content;
+if (contentObj == null) {
+    content = "";
+} else if (contentObj instanceof String) {
+    content = (String) contentObj;
+} else {
+    content = String.valueOf(contentObj);
+}
base/src/main/java/com/tinyengine/it/model/dto/AiMessages.java (1)

25-65: Multimodal content design is reasonable; consider tightening Lombok usage

Switching content to Object plus getContentAsString() and the overloaded setContent(String/Object) methods is a clean way to support multimodal payloads while keeping existing text-only callers working.

To reduce future confusion around setter generation, you might explicitly disable Lombok’s setter just for content and rely on the two manual setters:

@Getter
@Setter
public class AiMessages {
    @Setter(lombok.AccessLevel.NONE)
    private Object content;
    private String role;
    private String name;

    // keep getContentAsString + both setContent overloads
}

This makes it obvious to readers that content has custom semantics while role / name remain standard Lombok properties.

修复总结_Model_Not_Exist错误.md (1)

29-31: Add a language to this fenced code block to satisfy markdownlint

The URL example block uses plain ``` without a language, which triggers MD040 (fenced-code-language). Declaring a language (e.g., `text`) will silence the linter and improve rendering.

-```
+```text
 https://generativelanguage.googleapis.com/v1beta/models/models/gemini-1.5-pro:generateContent

As per static analysis hints.

</blockquote></details>
<details>
<summary>修复总结_JSON反序列化错误.md (1)</summary><blockquote>

`9-12`: **Minor markdown and example-alignment tweaks**

Two small suggestions for this summary doc:

1. The error snippet fenced block (`Failed to deserialize...`) has no language; consider adding `text` to be consistent with other code fences and avoid markdownlint warnings.

2. The test payloads use models `gpt-4` / `gpt-4-vision`, while the current `FoundationModel` enum in this project focuses on `gpt-3.5-turbo`, local models, ERNIE, Moonshot, DeepSeek, and Gemini. If the runtime doesn’t actually support `gpt-4*` here, you may want to switch these examples to a model that is supported in this codebase to avoid misleading readers.




Also applies to: 104-129

</blockquote></details>
<details>
<summary>问题分析_JSON反序列化错误.md (1)</summary><blockquote>

`1-5`: **Internal debugging documentation should not be committed.**

This file appears to be internal problem analysis/debugging notes rather than production documentation. Consider:
1. Moving this to a wiki or internal documentation system
2. Removing it from the codebase before merging

If kept, add a language specifier to the fenced code block at line 4 per markdown lint rules.


```diff
 ## 错误信息
-```
+```text
 Failed to deserialize the JSON body into the target type: 
documents/gemini-integration.md (1)

159-159: Convert bare URL to markdown link format.

Per markdown lint rules, bare URLs should use proper markdown link syntax.

-| baseUrl | string | 否 | API 基础 URL,默认为 https://generativelanguage.googleapis.com |
+| baseUrl | string | 否 | API 基础 URL,默认为 `https://generativelanguage.googleapis.com` |
base/src/test/java/com/tinyengine/it/service/app/adapter/GeminiApiAdapterTest.java (2)

216-232: Verify empty candidates behavior and add assertion for message content.

The test verifies that an empty candidates array still produces 1 choice, but doesn't verify the state of that choice. Looking at the adapter implementation, when candidates is empty, the choice will lack a message field, which could cause issues downstream.

Consider adding assertions to verify the choice structure:

         @SuppressWarnings("unchecked")
         List<Map<String, Object>> choices = (List<Map<String, Object>>) openAiResponse.get("choices");
         assertEquals(1, choices.size());
+        
+        // Verify the choice structure when no candidates exist
+        Map<String, Object> choice = choices.get(0);
+        assertEquals(0, choice.get("index"));
+        // Note: message may be null/missing when candidates is empty
+        assertNull(choice.get("message"), "Message should be null when candidates is empty");
     }

28-43: Consider adding test for multimodal content conversion.

The adapter supports converting image_url content types to Gemini's inlineData format, but there's no test coverage for this path. This is important for validating the base64 image handling logic.

Would you like me to generate a test case for multimodal content (text + image_url) conversion?

base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java (1)

224-227: Debug log may expose sensitive message content.

The debug log outputs the full request body which may contain user messages with sensitive information. Ensure debug logging is disabled in production or consider sanitizing the output.

If this is intentional for debugging, consider:

  1. Truncating the body or logging only metadata
  2. Using a dedicated debug flag that's disabled by default
base/src/main/java/com/tinyengine/it/config/AiChatConfig.java (1)

53-60: Header may contain null API key value.

When the model doesn't match any Gemini model, geminiApiKey remains null, and line 60 sets x-goog-api-key to null. While this header map is only used for Gemini configs, having an explicit null value could be confusing.

         Map<String, String> geminiHeaders = new HashMap<>();
         String geminiApiKey = null;
         if (Enums.FoundationModel.GEMINI_PRO.getValue().equals(model) ||
             Enums.FoundationModel.GEMINI_1_5_PRO.getValue().equals(model) ||
             Enums.FoundationModel.GEMINI_1_5_FLASH.getValue().equals(model)) {
             geminiApiKey = token;
         }
-        geminiHeaders.put("x-goog-api-key", geminiApiKey);
+        if (geminiApiKey != null) {
+            geminiHeaders.put("x-goog-api-key", geminiApiKey);
+        }
base/src/main/java/com/tinyengine/it/service/app/adapter/GeminiApiAdapter.java (1)

37-76: Request/response mapping is coherent; consider future multi-candidate support

The conversion of OpenAI-style parameters (temperature, max_tokens, top_p, stop) into Gemini’s generationConfig and the mapping back from usageMetadata into OpenAI-style usage look consistent and safe for the current single-candidate use case.

If you plan to expose n > 1 completions later, you’ll need to extend convertResponseFromGemini to iterate all candidates instead of hard-coding a single choice at index 0. For the current behavior (single completion) this is fine as-is.

Also applies to: 161-234

README_GEMINI.md (1)

61-67: Add languages to fenced code blocks to satisfy markdownlint (MD040)

The test output block (Line 61) and the ASCII architecture diagram block (Line 106) use bare triple backticks, which markdownlint flags (MD040).

You can fix this by specifying a language, e.g.:

-```
+```text
 -------------------------------------------------------
 T E S T S
 ...
-```
+```


```diff
-```
+```text
 ┌─────────────────┐
 │   客户端请求     │ (OpenAI 格式)
 ...
-```
+```

This keeps rendering identical while making linters happy.

Also applies to: 106-135

Gemini集成完成总结.md (2)

70-82: Specify a language for the architecture code block (MD040)

The architecture flow diagram block uses bare triple backticks, which triggers markdownlint MD040.

Consider marking it as plain text:

-```
+```text
 客户端请求(OpenAI 格式)
     ↓
 ...
-```
+```

This preserves the visual layout and satisfies the linter.


96-100: Align “工具调用消息处理” description with actual implementation

This section lists “工具调用消息处理” as part of the 内容格式转换功能. From the adapter code we see explicit handling for text and image_url content types; tool‑call specific shapes are not obviously mapped here.

If tool‑call handling is implemented elsewhere (e.g., pre‑/post‑processing around GeminiApiAdapter), consider clarifying that in the doc; otherwise, either add the missing conversion logic or adjust the wording so it doesn’t over‑claim current capabilities.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1e25eb and 587c98c.

📒 Files selected for processing (18)
  • Gemini集成完成总结.md (1 hunks)
  • QUICKSTART_GEMINI.md (1 hunks)
  • README_GEMINI.md (1 hunks)
  • app/src/main/resources/application-dev.yml (2 hunks)
  • base/src/main/java/com/tinyengine/it/common/enums/Enums.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/config/AiChatConfig.java (3 hunks)
  • base/src/main/java/com/tinyengine/it/model/dto/AiMessages.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/adapter/GeminiApiAdapter.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/impl/AiChatServiceImpl.java (1 hunks)
  • base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java (7 hunks)
  • base/src/test/java/com/tinyengine/it/service/app/adapter/GeminiApiAdapterTest.java (1 hunks)
  • documents/gemini-examples.http (1 hunks)
  • documents/gemini-integration.md (1 hunks)
  • documents/gemini-test-examples.http (1 hunks)
  • pom.xml (1 hunks)
  • 修复总结_JSON反序列化错误.md (1 hunks)
  • 修复总结_Model_Not_Exist错误.md (1 hunks)
  • 问题分析_JSON反序列化错误.md (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
base/src/main/java/com/tinyengine/it/config/AiChatConfig.java (1)
base/src/main/java/com/tinyengine/it/common/enums/Enums.java (1)
  • Enums (20-1183)
base/src/test/java/com/tinyengine/it/service/app/adapter/GeminiApiAdapterTest.java (1)
base/src/main/java/com/tinyengine/it/service/app/adapter/GeminiApiAdapter.java (1)
  • GeminiApiAdapter (28-245)
base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java (2)
base/src/main/java/com/tinyengine/it/service/app/adapter/GeminiApiAdapter.java (1)
  • GeminiApiAdapter (28-245)
base/src/main/java/com/tinyengine/it/common/utils/JsonUtils.java (1)
  • JsonUtils (45-349)
🪛 markdownlint-cli2 (0.18.1)
问题分析_JSON反序列化错误.md

4-4: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

README_GEMINI.md

61-61: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


106-106: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

修复总结_Model_Not_Exist错误.md

29-29: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

documents/gemini-integration.md

159-159: Bare URL used

(MD034, no-bare-urls)

Gemini集成完成总结.md

70-70: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (10)
pom.xml (2)

121-130: Align this change with database configuration and verify codebase consistency.

The switch from MariaDB to MySQL Connector/J is a significant dependency migration. Ensure that:

  1. Application configuration files (e.g., application.yml, application-dev.yml) have updated the JDBC driver class and connection URL to MySQL format (e.g., com.mysql.cj.jdbc.Driver instead of MariaDB driver)
  2. All environment configurations across dev, test, and production are updated consistently
  3. Any MariaDB-specific JDBC URL parameters are replaced with MySQL equivalents

The AI summary mentions changes in application-dev.yml switching to MySQL, but verify all environments are aligned.


126-130: MySQL Connector/J uses inherited versioning from Spring Boot parent POM.

The mysql-connector-j dependency (line 128) does not specify an explicit version and inherits it from the Spring Boot parent POM. This is standard practice with Spring Boot and ensures dependency consistency. Verify the inherited version meets your MySQL database requirements; Spring Boot's dependency management is designed to provide stable, tested versions.

Additionally, remove the unused mariadb-java-client.version property from the pom.xml properties section.

app/src/main/resources/application-dev.yml (1)

1-92: Mismatch between PR objectives and actual file changes.

The PR summary states that this change adds "Google Gemini API integration via an adapter-based pattern" with a new GeminiApiAdapter class and Gemini model registration. However, the only file provided for review is a database configuration file (application-dev.yml) with no Gemini-related configuration or code changes visible.

Please verify:

  • Is this the complete set of changed files, or were Gemini API adapter classes omitted from this review batch?
  • Should the PR include additional Java source files, configuration for API keys, or other Gemini integration code?
base/src/main/java/com/tinyengine/it/common/enums/Enums.java (1)

780-815: Gemini model enum additions look consistent

The new GEMINI_PRO, GEMINI_1_5_PRO, and GEMINI_1_5_FLASH constants and their values match the documented model strings used in the examples and adapter logic. No issues here.

documents/gemini-examples.http (1)

1-344: Comprehensive Gemini examples align well with the adapter contract

The HTTP requests and multi-language client snippets all match the unified model/apiKey/baseUrl/messages/temperature/stream/maxTokens schema and cover key scenarios (basic, streaming, multi-turn, codegen, system prompts, image input). This file should be very helpful for consumers; no changes needed from my side.

documents/gemini-integration.md (1)

1-252: LGTM - Well-structured integration documentation.

The documentation provides comprehensive coverage of Gemini API integration including configuration, usage examples, error handling, and extension guidance.

base/src/test/java/com/tinyengine/it/service/app/adapter/GeminiApiAdapterTest.java (1)

32-42: LGTM - Good model detection test coverage.

The test properly covers positive cases (gemini-pro, gemini-1.5-pro, gemini-1.5-flash, models/gemini-pro) and negative cases (gpt-3.5-turbo, deepseek-chat, null).

base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java (2)

146-158: LGTM - Model name normalization.

The normalizeGeminiModelName method correctly handles the models/ prefix removal and provides a sensible default.


77-81: LGTM - Correct authentication handling for Gemini.

Properly skips Bearer token header for Gemini (which uses API key in URL query parameter) while maintaining it for other providers.

base/src/main/java/com/tinyengine/it/config/AiChatConfig.java (1)

80-94: LGTM - Gemini model configurations.

The three Gemini model configurations follow the established pattern and correctly use the Gemini API URL structure with the x-goog-api-key header approach.

Note: These configs use :generateContent endpoint (non-streaming). If streaming requests use this config, they would need the :streamGenerateContent endpoint instead. Verify that the application either uses separate streaming configuration or handles endpoint switching at the call site.

Comment on lines +17 to +18
password: Nn2005,050618
url: jdbc:mysql://localhost:3306/tiny_engine_db?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name ".gitignore" -type f -exec grep -l "application\|yml" {} \;

Repository: opentiny/tiny-engine-backend-java

Length of output: 210


🏁 Script executed:

# Check repository structure for config files
find . -path "*resources*" -name "application*.yml" -o -name "application*.yaml" | head -20

Repository: opentiny/tiny-engine-backend-java

Length of output: 210


🏁 Script executed:

# Check if there's a git history showing if this file is tracked
git ls-files app/src/main/resources/ | grep application

Repository: opentiny/tiny-engine-backend-java

Length of output: 210


Database credentials hardcoded in development configuration file.

Lines 17-18 contain a database password and JDBC URL. Even for development configurations tracked in version control, hardcoding credentials is not recommended. If this is a shared repository, these details become accessible to all developers.

Recommended approach:

  1. Use environment variables for sensitive values:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
-   username: root
-   password: Nn2005,050618
-   url: jdbc:mysql://localhost:3306/tiny_engine_db?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+   username: ${DB_USERNAME:root}
+   password: ${DB_PASSWORD}
+   url: ${DB_URL:jdbc:mysql://localhost:3306/tiny_engine_db?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true}
  1. Add application-dev.yml to .gitignore if it's environment-specific, or exclude sensitive properties and use a committed application-dev.yml.example template instead.

  2. Set environment variables locally or through a .env file (excluded from git).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/src/main/resources/application-dev.yml around lines 17-18 the database
password and JDBC URL are hardcoded; replace these sensitive values with
environment variable references (e.g., read password and URL from env vars or
Spring placeholders like ${DB_PASSWORD} and ${JDBC_URL}) and update the file to
omit secrets, then add application-dev.yml to .gitignore if it contains
environment-specific secrets or alternatively commit an
application-dev.yml.example template without credentials and document using a
.env or environment variables for local setup.

Comment on lines +123 to +141
Map<?, ?> imageUrl = (Map<?, ?>) contentItem.get("image_url");
if (imageUrl != null) {
Map<String, Object> part = new HashMap<>();
Map<String, Object> inlineData = new HashMap<>();
String url = (String) imageUrl.get("url");

// Handle base64 images
if (url.startsWith("data:")) {
String[] parts_url = url.split(",");
if (parts_url.length == 2) {
String mimeType = parts_url[0].split(";")[0].substring(5);
inlineData.put("mimeType", mimeType);
inlineData.put("data", parts_url[1]);
}
}

part.put("inlineData", inlineData);
parts.add(part);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard against null/invalid image URLs and avoid emitting empty inlineData

In the image_url branch:

  • url can be null, so url.startsWith("data:") can throw an NPE at runtime.
  • For non‑data: URLs, the current code still adds a part with an empty inlineData map, which is almost certainly not what you want and can produce invalid requests downstream.

You can both prevent the NPE and avoid emitting empty inlineData by guarding the cast and only creating the part when a valid data URI was parsed, e.g.:

-                            if (imageUrl != null) {
-                                Map<String, Object> part = new HashMap<>();
-                                Map<String, Object> inlineData = new HashMap<>();
-                                String url = (String) imageUrl.get("url");
-
-                                // Handle base64 images
-                                if (url.startsWith("data:")) {
-                                    String[] parts_url = url.split(",");
-                                    if (parts_url.length == 2) {
-                                        String mimeType = parts_url[0].split(";")[0].substring(5);
-                                        inlineData.put("mimeType", mimeType);
-                                        inlineData.put("data", parts_url[1]);
-                                    }
-                                }
-
-                                part.put("inlineData", inlineData);
-                                parts.add(part);
-                            }
+                            if (imageUrl != null) {
+                                Object urlObj = imageUrl.get("url");
+                                if (urlObj instanceof String) {
+                                    String url = (String) urlObj;
+                                    // Handle base64 data: URLs only for now
+                                    if (url.startsWith("data:")) {
+                                        Map<String, Object> part = new HashMap<>();
+                                        Map<String, Object> inlineData = new HashMap<>();
+                                        String[] parts_url = url.split(",");
+                                        if (parts_url.length == 2) {
+                                            String mimeType = parts_url[0].split(";")[0].substring(5);
+                                            inlineData.put("mimeType", mimeType);
+                                            inlineData.put("data", parts_url[1]);
+                                        }
+                                        if (!inlineData.isEmpty()) {
+                                            part.put("inlineData", inlineData);
+                                            parts.add(part);
+                                        }
+                                    }
+                                }
+                            }

This keeps current base64 support, avoids NPEs, and doesn’t send structurally empty image parts. You can extend the branch later if you decide to support non‑data: URLs.

🤖 Prompt for AI Agents
In
base/src/main/java/com/tinyengine/it/service/app/adapter/GeminiApiAdapter.java
around lines 123 to 141, the code assumes image_url->url is a non-null String
and always emits a part with an (possibly empty) inlineData map; to fix, first
verify imageUrl.get("url") is a non-null instance of String before calling
startsWith, then only parse and create the part when the URL is a valid data:
URI that yields both mimeType and data (e.g., check parts_url.length == 2 and
that mimeType/data are non-empty); do not add a part or inlineData when the
checks fail (thus avoiding NPEs and empty inlineData entries), and optionally
log or ignore non-data URLs for future handling.

Comment on lines +243 to +247
for (Object msg : messageList) {
if (!(msg instanceof Map)) {
normalizedMessages.add((Map<String, Object>) msg);
continue;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Unsafe cast when message is not a Map.

If msg is not a Map, the code attempts to cast it to Map<String, Object> anyway, which will throw a ClassCastException.

         for (Object msg : messageList) {
             if (!(msg instanceof Map)) {
-                normalizedMessages.add((Map<String, Object>) msg);
                 continue;
             }

Alternatively, if non-Map messages should be preserved, handle them appropriately without an unsafe cast.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (Object msg : messageList) {
if (!(msg instanceof Map)) {
normalizedMessages.add((Map<String, Object>) msg);
continue;
}
for (Object msg : messageList) {
if (!(msg instanceof Map)) {
continue;
}
🤖 Prompt for AI Agents
In
base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java
around lines 243-247, the loop currently casts every msg to Map<String,Object>
even when it's not a Map which causes ClassCastException; change the logic so
that if msg is an instance of Map you cast and add it, otherwise create a safe
Map<String,Object> wrapper (e.g. put the original msg under a "content" key and
any default metadata you need) and add that wrapper to normalizedMessages so no
unsafe casts occur and non-Map messages are preserved.

Comment on lines +52 to +63
修改 `OpenAIConfig.java` 的默认配置:

```java
@Data
@Configuration
public class OpenAIConfig {
private String apiKey = "YOUR_GEMINI_API_KEY";
private String baseUrl = "https://generativelanguage.googleapis.com";
private String defaultModel = "gemini-1.5-pro";
private int timeoutSeconds = 300;
}
```
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent configuration class reference.

The documentation references OpenAIConfig.java but based on the PR changes, Gemini configuration is managed in AiChatConfig.java. This could confuse developers.

 #### 方式二:修改配置文件
 
-修改 `OpenAIConfig.java` 的默认配置:
+修改 `AiChatConfig.java` 或 `OpenAIConfig.java` 的默认配置:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
修改 `OpenAIConfig.java` 的默认配置:
```java
@Data
@Configuration
public class OpenAIConfig {
private String apiKey = "YOUR_GEMINI_API_KEY";
private String baseUrl = "https://generativelanguage.googleapis.com";
private String defaultModel = "gemini-1.5-pro";
private int timeoutSeconds = 300;
}
```
修改 `AiChatConfig.java``OpenAIConfig.java` 的默认配置:
🤖 Prompt for AI Agents
In documents/gemini-integration.md around lines 52 to 63, the doc wrongly
references OpenAIConfig.java; update the documentation to reference
AiChatConfig.java (the actual config class used by the PR), and adjust any
example property names or descriptions to match AiChatConfig.java's fields and
defaults so the sample configuration and filenames are consistent with the
codebase.

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