Complete Guide to Building a Spring AI + Ollama Embedding Vectorization Project
This guide walks through adding embedding support to a Spring AI application by configuring Ollama, creating an EmbeddingService for vector generation and similarity calculations, exposing REST endpoints via EmbeddingController, and providing a simple HTML front‑end for interactive testing, with step‑by‑step instructions and code samples.
Overview
The article shows how to extend a Spring Boot + LangChain4j + Ollama RAG project with text‑embedding (vectorization) capabilities, enabling private document search and similarity analysis.
1. Application configuration
server:
port: 885
logging:
level:
com.badao: debug
org.springframework.ai: debug
spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
options:
model: qwen2.5
temperature: 0.7
embedding:
options:
model: nomic-embed-textBefore running the service, pull the embedding model in Ollama (e.g., ollama pull nomic-embed-text) or replace it with another model such as bge-m3 or mxbai-embed-large.
2. EmbeddingService
package com.badao.ai.service;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class EmbeddingService {
private final EmbeddingModel embeddingModel;
public EmbeddingService(EmbeddingModel embeddingModel) { this.embeddingModel = embeddingModel; }
/** Convert a single text to a float vector */
public float[] embed(String text) { return embeddingModel.embed(text); }
/** Batch conversion returning the full response */
public EmbeddingResponse embedForResponse(List<String> texts) { return embeddingModel.embedForResponse(texts); }
/** Cosine similarity between two texts */
public double cosineSimilarity(String text1, String text2) {
float[] vec1 = embed(text1);
float[] vec2 = embed(text2);
return cosineSimilarity(vec1, vec2);
}
/** Cosine similarity between two vectors */
public static double cosineSimilarity(float[] vec1, float[] vec2) {
double dot = 0.0, norm1 = 0.0, norm2 = 0.0;
for (int i = 0; i < vec1.length; i++) {
dot += vec1[i] * vec2[i];
norm1 += vec1[i] * vec1[i];
norm2 += vec2[i] * vec2[i];
}
if (norm1 == 0 || norm2 == 0) return 0.0;
return dot / (Math.sqrt(norm1) * Math.sqrt(norm2));
}
/** Batch similarity matrix */
public Map<String, Object> batchSimilarity(List<String> texts) {
int n = texts.size();
List<float[]> vectors = new ArrayList<>();
for (String t : texts) vectors.add(embed(t));
List<List<Double>> matrix = new ArrayList<>();
for (int i = 0; i < n; i++) {
List<Double> row = new ArrayList<>();
for (int j = 0; j < n; j++) {
row.add(i == j ? 1.0 : cosineSimilarity(vectors.get(i), vectors.get(j)));
}
matrix.add(row);
}
return Map.of(
"texts", texts,
"dimension", vectors.get(0).length,
"similarityMatrix", matrix
);
}
}3. EmbeddingController
package com.badao.ai.controller;
import com.badao.ai.service.EmbeddingService;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@RestController
public class EmbeddingController {
private final EmbeddingService embeddingService;
public EmbeddingController(EmbeddingService embeddingService) { this.embeddingService = embeddingService; }
@GetMapping("/ai/embed")
public Map<String, Object> embed(@RequestParam(value = "text", defaultValue = "Hello World") String text) {
float[] vector = embeddingService.embed(text);
return Map.of(
"text", text,
"dimension", vector.length,
"vectorPreview", previewVector(vector, 10)
);
}
@PostMapping("/ai/embed/similarity")
public Map<String, Object> similarity(@RequestBody Map<String, String> req) {
String t1 = req.getOrDefault("text1", "");
String t2 = req.getOrDefault("text2", "");
float[] v1 = embeddingService.embed(t1);
float[] v2 = embeddingService.embed(t2);
double sim = EmbeddingService.cosineSimilarity(v1, v2);
return Map.of(
"text1", t1,
"text2", t2,
"similarity", Math.round(sim * 10000.0) / 100.0,
"dimension", v1.length
);
}
@PostMapping("/ai/embed/batch-similarity")
public Map<String, Object> batchSimilarity(@RequestBody Map<String, Object> req) {
@SuppressWarnings("unchecked")
List<String> texts = (List<String>) req.get("texts");
if (texts == null || texts.isEmpty()) texts = List.of("苹果", "香蕉", "电脑", "笔记本");
return embeddingService.batchSimilarity(texts);
}
@PostMapping("/ai/embed/batch")
public Map<String, Object> batchEmbed(@RequestBody Map<String, Object> req) {
@SuppressWarnings("unchecked")
List<String> texts = (List<String>) req.get("texts");
if (texts == null || texts.isEmpty()) texts = List.of("Hello", "World");
EmbeddingResponse resp = embeddingService.embedForResponse(texts);
List<Map<String, Object>> results = resp.getResults().stream()
.map(r -> Map.of(
"index", r.getIndex(),
"dimension", r.getOutput().size(),
"vectorPreview", previewVector(toFloatArray(r.getOutput()), 10)
))
.toList();
return Map.of(
"texts", texts,
"model", "ollama-nomic-embed-text",
"results", results
);
}
private static List<Float> previewVector(float[] vector, int max) {
int limit = Math.min(vector.length, max);
List<Float> preview = new ArrayList<>();
for (int i = 0; i < limit; i++) preview.add(Math.round(vector[i] * 10000f) / 10000f);
return preview;
}
private static float[] toFloatArray(List<Double> list) {
float[] arr = new float[list.size()];
for (int i = 0; i < list.size(); i++) arr[i] = list.get(i).floatValue();
return arr;
}
}4. Front‑end test page (embedding-test.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Embedding Test</title>
<style>/* minimal styling omitted for brevity */</style>
</head>
<body>
<div class="container">
<h1>🔢 Text Embedding</h1>
<!-- Single text embedding UI -->
<div class="card">
<h2>📌 1. Single Text</h2>
<textarea id="singleText" placeholder="Enter text...">人工智能正在改变世界</textarea>
<button onclick="doEmbed()">🔍 Embed</button>
<div class="result-box" id="singleResult"><span class="loading">Waiting…</span></div>
</div>
<!-- Pair similarity UI -->
<div class="card">
<h2>📊 2. Pair Similarity</h2>
<textarea id="textA" placeholder="Text A">苹果是一种水果</textarea>
<textarea id="textB" placeholder="Text B">香蕉也是一种水果</textarea>
<button onclick="doSimilarity()">📐 Compute</button>
<div class="result-box" id="similarityResult"><span class="loading">Waiting…</span></div>
</div>
<!-- Batch matrix UI -->
<div class="card">
<h2>🧮 3. Batch Matrix</h2>
<textarea id="batchText" placeholder="One line per text">苹果
香蕉
电脑
手机</textarea>
<button onclick="doBatchSimilarity()">🧮 Generate</button>
<div class="result-box" id="batchResult"><span class="loading">Waiting…</span></div>
</div>
</div>
<script>
// helper functions omitted for brevity – they set example texts, call the REST APIs and render results
</script>
</body>
</html>5. Usage steps
Pull the embedding model : ollama pull nomic-embed-text Start the Spring Boot application : run SpringAiDemoApplication or the provided 一键启动.bat Open the test page : http://localhost:885/embedding-test.html Existing functionality (e.g., function-call-test.html) remains unchanged
6. API reference
GET /ai/embed?text=xxx– single‑text vectorization POST /ai/embed/similarity – cosine similarity of two texts POST /ai/embed/batch-similarity – similarity matrix for a list of texts POST /ai/embed/batch – batch vectorization returning full responses
7. Maven pom (unchanged)
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<groupId>com.badao.ai</groupId>
<artifactId>spring-ai-ollama-functioncall</artifactId>
<version>1.0</version>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.0.0-M6</spring-ai.version>
<spring-ai-alibaba.version>1.0.0-M6.1</spring-ai-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-milestones</id>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>8. Test result
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
The Dominant Programmer
Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
