Embedding(文本嵌入):把自然语言文字 → 固定长度浮点数字向量
检索底层原理【RAG】


1、pom
<properties>
<java.version>17</java.version>
<spring-ai.version>2.0.0-M7</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2、application.yml
2.1
${OPENAI_API_KEY}

2.2
ollama docker容器
宿主机:192.168.91.164

容器ollama启动模型qwen2.5:0.5b

访问宿主机模型
http://192.168.91.164:11434/

spring:
application:
name: dashscope18088
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
chat:
model: qwen3.6-plus
embedding:
options:
model: text-embedding-v4
dimensions: 2048 # 输出向量维度2048
# chat:
# model: qwen3.6-plus
# 可选:超时/重试配置
ollama:
base-url: http://192.168.91.164:11434
chat:
model: qwen2.5:0.5b
# options:
# temperature: 0.7
# max-tokens: 2000
server:
port: 18088
logging:
level:
org.springframework.ai: debug
3、config
import com.sb.dashscope18088.advisor.MySimpleLoggerAdvisor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiConfiguration {
@Bean
public ChatMemory chatMemory(){
return MessageWindowChatMemory.builder()
.maxMessages(10) // 设置消息窗口大小为10
.chatMemoryRepository(new InMemoryChatMemoryRepository()) // 内存存储
.build();
}
@Bean
public ChatClient chatClient(OpenAiChatModel model,ChatMemory chatMemory){
return ChatClient
.builder(model) // 创建ChatClient对象,以及设置模型model
//.defaultAdvisors(new MySimpleLoggerAdvisor())//添加日志拦截器
.defaultAdvisors(
new SimpleLoggerAdvisor(),//添加一个日志拦截器(内置日志拦截器,)
MessageChatMemoryAdvisor.builder(chatMemory).build()//添加一个聊天记录截器
)
.build(); // 构建ChatClient对象
}
@Bean
public ChatClient chatClient2(OllamaChatModel model){
return ChatClient
.builder(model) // 创建ChatClient对象,以及设置模型model
.defaultAdvisors(new MySimpleLoggerAdvisor())//添加日志拦截器
.build(); // 构建ChatClient实例
}
@Bean
public ChatClient chatClient3(OpenAiChatModel model){
// System Prompt 工程:清晰定义 AI 的角色、任务、约束和输出格式
//【系统提示词】
String systemPrompt = """
你是一个资深的 Java 技术顾问。
禁止回答任何非技术类问题,例如天气或娱乐八卦。
代码示例必须符合 Java 17+ 规范。
回答需要符合以下格式:首先一句话概括问题的核心,然后提供代码示例,最后补充注意事项。
如果自己不确定,可以说"关于这个问题,我目前没有确切的信息",禁止编造内容。
""";
return ChatClient
.builder(model) // 创建ChatClient对象,以及设置模型model
.defaultSystem(systemPrompt)
//.defaultAdvisors(new MySimpleLoggerAdvisor())//添加日志拦截器
.defaultAdvisors(new SimpleLoggerAdvisor())//内置日志拦截器
.build(); // 构建ChatClient对象
}
}
4、pojo
import java.util.List;
//使用 Java 16+ 的 Record 特性,编译器会自动生成构造器、equals/hashCode等方法
public record TopicBook(
String topic, // 主题
List<String> books // 推荐书籍
){}
// Java16+ Record 不可变实体,用于书籍评论结构化接收
public record BookReview(
String reviewerName, // 评价人
int rating, // 评分
String comment // 评价内容
){}
5、tool

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @author Administrator
*/
@Component
public class DateTimeTools {
@Tool(description = "获取用户在指定时区的当前日期和时间,用于回答需要实时时间的问题")
public String getCurrentTime(){
// 获取用户的时区偏好设置
var zoneId = LocaleContextHolder.getTimeZone().toZoneId();
var now = LocalDateTime.now().atZone(zoneId);
// 格式化为 yyyy-MM-dd HH:mm:ss
return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(now);
}
@Tool(description = "设置闹钟,调用此工具可在指定时间触发提醒。时间参数必须是 ISO-8601 格式," +
"例如: 2026-05-03 15:30:00")
public void setAlarm(@ToolParam(description = "闹钟的触发时间,标准格式: yyyy-MM-dd HH:mm:ss")
String alarmTime) {
System.out.println("⏰ 闹钟已设置,将在 " + alarmTime + " 提醒用户。");
// 可扩展:持久化入库、定时任务、消息推送等业务逻辑
}
}
6、controller
import org.springframework.ai.openai.OpenAiEmbeddingModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
@RestController
public class MyEmController {
@Autowired
private OpenAiEmbeddingModel embeddingModel;
@RequestMapping("/em")
public String em(){
float[] textVector1 = embeddingModel.embed("Java_java2026.com");
System.out.println(textVector1.length + " " + Arrays.toString(textVector1));
return "【length】:"+textVector1.length
+"【textVector1】:"+ Arrays.toString(textVector1);
}
@RequestMapping("/em2")
public String em2(){
// 3段文本向量化
float[] textVector1 = embeddingModel.embed("学java上:java2026.com");
float[] textVector2 = embeddingModel.embed("java2026.com网站教学Java质量真不错");
float[] textVector3 = embeddingModel.embed("我喜欢吃水晶梨");
// 打印3组2048维向量
System.out.println(textVector1.length + ":"+Arrays.toString(textVector1));
System.out.println(textVector2.length + ":"+Arrays.toString(textVector2));
System.out.println(textVector3.length + ":"+Arrays.toString(textVector3));
// 计算欧式距离
double dist12 = euclideanDistance(textVector1, textVector2);
double dist13 = euclideanDistance(textVector1, textVector3);
System.out.println("textVector1 与 textVector2 欧式距离: " + dist12);
System.out.println("textVector1 与 textVector3 欧式距离: " + dist13);
return "【textVector1 与 textVector2 欧式距离:】:"+dist12+
"【textVector1 与 textVector3 欧式距离:】:"+dist13;
}
/**
* 计算两个向量的欧式距离
* @param a 向量1
* @param b 向量2
* @return 欧式距离
*/
private static double euclideanDistance(float[] a, float[] b) {
// 校验:两个向量维度必须相同,否则抛异常
if (a.length != b.length) {
throw new IllegalArgumentException("向量维度不一致");
}
double sumSq = 0;
// 遍历每个维度,差值平方累加
for (int i = 0; i < a.length; i++) {
double d = a[i] - b[i];
sumSq += d * d;
}
// 平方根得到欧式距离
return Math.sqrt(sumSq);
}
}
7、访问
7.1
http://localhost:18088/em

7.2
http://localhost:18088/em2

