Milesoft Vertex AI Extension (vertex-ext)
1. Overview & Purpose
The Vertex AI Extension (vertex-ext) is a lightweight, secure, and production-ready JVM wrapper library around the official Google GenAI platform designed specifically for the Milesoft ecosystem. It simplifies integration with Google's state-of-the-art Gemini LLM and vision models hosted on Vertex AI, while providing critical JSON sanitization, HTML block extraction, and stream-handling utility functions.
By encapsulating Google's raw developer libraries and offering a unified service-and-utility layer, the extension ensures architectural consistency and eliminates boilerplate code across downstream applications.
Key Benefits & Core Capabilities:
- Unified Gemini Client Lifecycle: Seamlessly manages the lifecycle of the underlying
com.google.genai.Clientand ensures safe disposal of connection channels using close-safe JVM patterns. - Multimodal Visual Reasoning: Offers native support for vision prompts by downloading remote/public image assets, extracting MIME types, and streaming them as binary payloads (
inlineData) directly to multimodal Gemini models. - Robust JSON Reconstruction & Sanitization: Features
LlmUtils.toJsonwhich scans text outputs, isolates the outermost brace boundaries, and applies a security-hardenedjson-sanitizerto automatically repair syntactic flaws (e.g., mismatched quotes, trailing commas) for direct mapping with Jackson. - Html Content Extraction: Easily isolates HTML structures from markdown code fence segments (
```html) to prevent formatting bleed-through on the frontend. - Standard Configuration Integration: Implements out-of-the-box support for Spring dependency injection, standardizing dual (fast vs. complex) model configurations and qualifier routing.
2. Installation & Dependency Configuration
The Vertex AI Extension is published in our secure Google Cloud Artifact Registry repository.
To pull this dependency into your downstream application, ensure you apply the Google Cloud Artifact Registry plugin and configure your maven repositories properly:
2.1 Gradle Setup (build.gradle)
plugins {
id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.5"
id "java"
}
repositories {
mavenLocal()
mavenCentral()
maven {
url "artifactregistry://us-west2-maven.pkg.dev/milesoft-repo/repo-maven"
}
}
dependencies {
// Required base foundation libraries
implementation "io.milesoft:commons:2.0.1"
// Milesoft Vertex AI Integration Extension
implementation "io.milesoft:vertex-ext:2.0.1"
}
3. Configuring Vertex AI in Spring Boot
To enable proper dependency injection of the LlmService within your Spring Boot application, define it as a managed bean.
3.1 Properties Configuration
In your environment properties files (e.g., application.yml), declare your Vertex AI targets:
vertex:
project: "your-gcp-project"
location: "us-central1"
model: "gemini-2.0-flash-lite"
3.2 Standard Bean Configuration
Register the service class as a Spring @Bean inside your configuration layer:
import io.milesoft.vertex.services.LlmService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class VertexConfig {
@Bean
public LlmService llmService(
@Value("${vertex.project}") String project,
@Value("${vertex.location}") String location,
@Value("${vertex.model}") String model) {
return new LlmService(project, location, model);
}
}
3.3 Advanced Dual Model Configuration
For workflows that balance latency with analytical complexity, you can register multiple LlmService instances using Spring's @Qualifier annotations:
vertex:
project: "your-gcp-project"
location:
fast: "us-central1"
complex: "us-west1"
model:
fast: "gemini-2.0-flash-lite" // Low-latency, cost-effective formatting & search tasks
complex: "gemini-2.5-pro" // Deep visual layout analysis & technical reasoning
import io.milesoft.vertex.services.LlmService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class VertexConfig {
@Bean
@Qualifier("complexLlmService")
public LlmService complexLlmService(
@Value("${vertex.project}") String project,
@Value("${vertex.location.complex}") String location,
@Value("${vertex.model.complex}") String model) {
return new LlmService(project, location, model);
}
@Bean
@Qualifier("fastLlmService")
public LlmService fastLlmService(
@Value("${vertex.project}") String project,
@Value("${vertex.location.fast}") String location,
@Value("${vertex.model.fast}") String model) {
return new LlmService(project, location, model);
}
}
4. API Reference Guide
4.1 LlmService
The central orchestration service used for interacting with the Google Vertex GenAI API. It manages connection clients internally and handles payload structures automatically.
| Method Signatures | Return Type | Description |
|---|---|---|
LlmService(String projectId, String location, String modelName) |
Constructor | Instantiates the service. All three parameters are mandatory, non-blank strings (validated via preconditions). |
generateText(String prompt) |
String |
Dispatches a simple text prompt. Uses default configuration (candidateCount(1)) and returns the resulting raw text. |
generateText(String imageUrl, String prompt) |
String |
Downloads the image, automatically detects the image type/MIME, creates inline binary data, and dispatches a multimodal vision request. |
4.2 LlmUtils
A thread-safe collection of static utility methods for sanitizing, transforming, and correcting raw LLM-generated responses before usage in application code.
| Method Signatures | Return Type | Description |
|---|---|---|
toJson(@Nullable String response) |
String |
Extracts the first bounded JSON block (from { to }) from a response string and applies security-hardened syntax correction. Returns "{}" if no valid brackets are found or the string is blank. |
toHtml(@Nullable String response) |
String |
Extracts clean, plain HTML content from markdown boundaries (```html) or simple tagged strings. Returns empty string "" if blank/null. |
getMimeType(String imageUrl) |
String |
Examines the image URL extension and builds a standard MIME type (e.g., image/jpeg). Defaults to image/png if an extension is missing or unresolvable. |
downloadBytes(String imageUrl) |
byte[] |
Opens a network input stream to the specified image URL and downloads the binary payload into memory. |
5. Real-World Integration Patterns
5.1 Pattern 1: JSON Structure Synthesis & Schema Mapping
Many service flows require getting a strongly typed Java DTO back from the LLM. In this pattern, the application instructs the model to generate a raw JSON schema, sanitizes the result with LlmUtils.toJson, and converts it using a standard Jackson ObjectMapper.
Here is a simplified, abstract example:
import com.fasterxml.jackson.databind.ObjectMapper;
import io.milesoft.commons.json.ObjectMapperFactory;
import io.milesoft.vertex.services.LlmService;
import io.milesoft.vertex.utils.LlmUtils;
import org.springframework.stereotype.Service;
@Service
public class StructuredAnalysisService {
private final LlmService llmService;
private final ObjectMapper objectMapper;
public StructuredAnalysisService(LlmService llmService) {
this.llmService = llmService;
// Use a lenient object mapper to ignore extra fields or minor syntax anomalies
this.objectMapper = ObjectMapperFactory.lenientObjectMapper();
}
public AnalysisResultDto analyzeInputText(String content) {
String prompt = String.format(
"Analyze the following content and extract key categories and keywords: '%s'. " +
"Your output must be structured strictly as a JSON object matching this schema: " +
"{ \"categories\": [\"string\"], \"keywords\": [\"string\"] }. CONTENT:\n" +
content
);
// 1. Dispatch prompt to Gemini model via our SDK
String rawLlmResponse = llmService.generateText(prompt);
// 2. Extract and sanitize JSON block, ignoring conversational pre/postambles
String sanitizedJson = LlmUtils.toJson(rawLlmResponse);
// 3. Map safely to a local DTO object
try {
return objectMapper.readValue(sanitizedJson, AnalysisResultDto.class);
} catch (Exception e) {
throw new IllegalStateException("Failed to parse synthesized analysis data for content", e);
}
}
}
5.2 Pattern 2: Multimodal Image Analysis & Insight Extraction
This pattern shows how downstream applications (like AiService) perform visual processing. It feeds a public image URL and a specific text query to the complex multimodal model, extracting structured technical observations.
import io.milesoft.vertex.services.LlmService;
import io.milesoft.vertex.utils.LlmUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class ConstructionAnalysisService {
private final LlmService complexLlmService;
// Inject our complex-reasoning Gemini instance
public ConstructionAnalysisService(@Qualifier("complexLlmService") LlmService complexLlmService) {
this.complexLlmService = complexLlmService;
}
public void auditSitePhoto(String photoUrl) {
String prompt = "Perform a safety audit and environmental analysis on this site photo. " +
"Identify any visible hazards, safety gear compliance issues, and general weather conditions. " +
"Output your findings in HTML format wrapped in an article element.";
// 1. Pass image URL + prompt directly (LlmService handles binary download and mime formatting)
String rawResponse = complexLlmService.generateText(photoUrl, prompt);
// 2. Clean out HTML code blocks for direct safe injection into web pages
String cleanHtml = LlmUtils.toHtml(rawResponse);
log.info("Site audit HTML report: {}", cleanHtml);
}
}
6. Troubleshooting & FAQ
Q: Why am I getting "Permission Denied" or application credential errors?
A: Vertex AI requires Google Cloud authentication.
- In GCP Production Environment (Cloud Run, App Engine, GKE): The underlying Google GenAI SDK automatically resolves credentials from the default service account attached to the execution resource.
- In Local Development: You must set your environment credentials:
- Export the environment variable
GOOGLE_APPLICATION_CREDENTIALS=/path/to/your/service-account.json. - Alternatively, make sure you are authenticated with the correct active project context by running:
gcloud auth application-default login
- Export the environment variable
Q: Why does Jackson parsing fail on some Gemini responses?
A: LLMs are conversational by nature and often return markdown wrap decorations (e.g., ```json ... ```) or conversational text around the requested JSON payload. Always pass the raw output of llmService.generateText(prompt) through LlmUtils.toJson(...) before parsing. This trims away markdown or conversation and repairs trailing commas, unquoted keys, or malformed quote tokens via the built-in JSON Sanitizer.
Q: What should I use as the "location" parameter?
A: Model availability varies by GCP region. Generally, we recommend deploying your services in standard locations such as us-central1 or us-west1. Check your organization's resource access configuration or consult the Google Cloud Vertex AI documentation for up-to-date regional model listings.