AI学习
Java AI
示例项目地址:【前端在front目录里面】
github: https://github.com/Jack-txf/ai-helper
本文章仅做入门,一些概念的学习。
1.Java在AI里的位置
现实情况就是,java在工程化方面还是相对主流的,提供基础服务的模型层面当然还是python的天下了。
- 训练模型、研究算法:主流是 Python(PyTorch、TensorFlow)。
- 工程化、系统化落地:Java / Spring Boot 是相对主力,尤其企业级系统。
两个语言层面:
- Python 侧: 做一些训练 / 微调模型的工作(可选)。
- Java 侧:负责所有“系统”的事情:
- 模型服务调用(REST / gRPC)
- 微服务编排
- 数据管道(ETL)
- 向量检索、缓存、消息队列、网关等
很多“AI 微服务”架构也是这么干的:模型服务单独部署,Java 通过 HTTP/gRPC 调用
2.一些概念
要进入 AI 开发领域,作为 Java 程序员,你不需要去啃复杂的数学公式(那是算法工程师的事),但你必须理解大模型应用层的“黑话”和架构逻辑。
2.1. 核心交互概念
- LLM (Large Language Model): 大语言模型。你可以把它想象成一个概率预测机器,它根据上文预测下一个字出现的概率。
- Prompt (提示词): 你对 AI 说的指令。Prompt 的质量直接决定了输出的质量,因此衍生了 Prompt Engineering (提示词工程)。
Prompt(提示词) 就是你给 AI 下达的指令、问题或一段上下文。在程序员的视角里,如果把大模型(LLM)比作一个极其聪明但没有记忆的“函数”,那么 Prompt 就是这个函数的“入参”。
大模型本质上是一个“概率预测机器”。你给它的初始信息(Prompt)不同,它预测出的下一个字的方向就会完全不同。糟糕的 Prompt:“写段 Java 代码。”(AI 可能会给你写个 Hello World,毫无用处)优质的 Prompt:“你是一个资深架构师,请基于 Spring Boot 3.0 和 Redis 实现一个高并发下的秒杀扣减库存逻辑,要求考虑分布式锁和原子性。”(AI 会给你生产级的代码)
在开发 AI 应用时,我们通常遵循 RTCE 框架 来编写 Prompt:
- Role (角色):定义 AI 的身份。“你是一个拥有 10 年经验的 Java 性能优化专家。”
- Task (任务):明确要做什么。“请分析这段代码是否存在内存泄漏风险。”
- Context (上下文):提供背景信息或限制条件。“这段代码运行在 K8s 环境中,JVM 堆内存限制为 2GB,使用的是 G1 垃圾回收器。”
- Exemplar (示例/格式要求):规定输出的长相。“请先列出问题点,然后给出优化后的代码,最后解释原因。请用 Markdown 格式输出。”
在 Java 调用 API(如 SpringAI 或 LangChain4j)时,你会遇到这三种消息角色,它们共同构成了完整的 Prompt:
| 角色 | 名称 | 作用 |
|---|---|---|
| System | 系统提示词 | 设置规则。比如:“你只能回答技术问题,不能聊天。”(用户不可见,权限最高) |
| User | 用户提示词 | 具体提问。即用户在输入框里打的内容。 |
| Assistant | 助手提示词 | AI 的回复。在多轮对话中,我们会把之前的 Assistant 回复传回去,让 AI 拥有“记忆”。 |
- Token (令牌): AI 处理文本的最小单位。不是按字符算的,一个汉字或单词可能对应 1~2 个 Token。模型收费、上下文长度限制都是基于 Token 的。
- Context Window (上下文窗口): 模型能“记得”的最长对话长度。一旦超过这个限制,模型就会“失忆”。
- 多模态
多模态(Multimodal) 指的是模型能够处理和理解多种不同类型的数据输入或输出,而不仅仅局限于纯文本。早期的 GPT-3。它像是一个深居简出的学者,只能通过阅读文字来了解世界。你发给它一张照片,它是“看不见”的。现在的 GPT-4o、DeepSeek-V3 或 Gemini。它们更像是一个真实的人,拥有视觉、听觉和表达能力。你可以给它看一张报错截图,它能直接定位代码行;你可以录一段音频,它能听出你的情绪。
作为一个 Java 程序员,你可以从 I/O(输入/输出) 的角度来理解多模态:
| 维度 | 包含的模态 | 应用场景举例 |
|---|---|---|
| 输入 (Input) | 文本、图片、音频、视频、PDF、代码 | 上传一张原型图,让 AI 直接生成 Vue3 代码。 |
| 输出 (Output) | 文本、语音、图片 (DALL-E)、短视频 (Veo) | 给 AI 一段文字描述,让它生成一张数据库架构图。 |
在底层,AI 将图片和文字都转化成了向量(Embedding)。
- 文字“猫”会被转化成空间中的一组坐标。
- 图片里的“猫”也会被转化成一组坐标。
- 多模态预训练的目标,就是让这两个坐标在数学空间里尽可能地靠近(对齐)。
2.2. 工程化核心概念
如果你要用 Java 写一个 AI 项目,以下几个概念是比较重要:
RAG (Retrieval-Augmented Generation) - 检索增强生成
- 本质:给 AI 挂一个“外挂硬盘”或“图书馆”。
- 流程:先从你的私有数据库(如 PDF、MySQL)里检索相关内容,把内容塞进 Prompt 喂给 AI,再让 AI 回答。
- 为什么重要:它解决了 AI 幻觉问题,让 AI 能基于企业内部数据回答。
Embedding (向量化/嵌入)
- 本质:将文字转化为一串数字(向量,如 [0.12, -0.5, 0.8...])。
- 作用:让计算机通过数学距离计算两段话的意思是否相近。它是 RAG 的核心技术。通过计算两段文本的向量表示之间的数值距离,应用程序可以确定用于生成嵌入向量的对象之间的相似性。嵌入的工作原理是将文本、图像和视频转换为浮点数数组,称为向量。这些向量旨在捕捉文本、图像和视频的含义。嵌入数组的长度称为向量的维度。
Vector Database (向量数据库)
- 常见工具:Milvus, Pinecone, Weaviate, 或者 Redis 的向量插件。
- 作用:专门存储 Embedding 后的向量,并进行超大规模的语义搜索。
Agent (智能体)
- 本质:让 AI 拥有“手”和“大脑”。
- 逻辑:AI 不仅会说,还会思考“我应该先查数据库,还是先调天气接口”,并自动执行。这涉及到 Function Calling (函数调用) 技术。
Function Calling(函数调用) 和 Tool Calling(工具调用)
它允许模型与一组 API 或工具进行交互,从而增强其功能。
工具主要用于:
-
信息检索。此类工具可用于从外部来源(例如数据库、Web 服务、文件系统或网络搜索引擎)检索信息。其目标是增强模型的知识,使其能够回答原本无法回答的问题。因此,它们可用于检索增强生成 (RAG) 场景。例如,可以使用此类工具检索给定位置的当前天气、检索最新新闻文章或查询数据库中的特定记录。
-
执行操作。此类工具可用于在软件系统中执行操作,例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。其目标是自动化那些原本需要人工干预或显式编程的任务。例如,该工具可用于为与聊天机器人交互的客户预订航班、填写网页上的表单,或在代码生成场景中基于自动化测试(TDD)实现 Java 类。
| 维度 | 传统 API 开发 | Tool Calling 开发 |
|---|---|---|
| 控制权 | 程序员写死 if-else 逻辑 |
AI 决定什么时候调哪个接口 |
| 参数提取 | 手写正则或 JSON 解析 | AI 自动提取用户话里的参数 |
| 扩展性 | 每加一个功能都要改路由逻辑 | 只需要往 Tool 列表里塞个新方法 |
| 容错性 | 参数不合法直接 400 | AI 能自动纠错,甚至追问用户缺少的参数 |
2.3. 模型参数调节(开发者必知)
在调用 API 或 Spring AI 配置时,你会看到这些参数:
- Temperature (温度): 控制随机性。
0: 严谨、固定,适合写代码、逻辑推导。1: 更有创意、发散,适合写小说、头脑风暴。
- System Message (系统消息): 给 AI 设定人设。比如:“你是一个资深的 Java 架构师”。
- Streaming (流式传输): 类似 WebSoket 或 SSE,让 AI 一个字一个字地吐出来,而不是等 30 秒憋出一个大招(提升用户体验的关键)。
| 传统 Java 开发 | AI 驱动的 Java 开发 |
|---|---|
| 持久化: MySQL / JPA | 知识库: 向量数据库 (Milvus/pgvector) |
| 通信: REST / RPC | 交互: Prompt + Function Calling |
| 中间件: Redis / MQ | 编排: LangChain4j / Spring AI |
| 处理对象: 结构化数据 (JSON) | 处理对象: 非结构化数据 (文本/图片/音频) |
3. 实践部分
本小节做一个简单的案例,根据自己的知识库,搭建一个巩固自己面试的AI应用。【框架是采用的SpringAI】
3.1 基本聊天
核心就是配置好模型就ok了。
@Configuration
public class ZhipuConfig {
private String zhipuApikey;
public ZhipuConfig() {
String zhipuEnv = System.getenv("ZHIPU_ENV");
if (zhipuEnv == null) {
throw new RuntimeException("请设置环境变量 ZHIPU_ENV");
}
this.zhipuApikey = zhipuEnv;
}
@Bean(name = "myZhipu")
public ZhiPuAiChatModel zhiPuAiChatModel() {
ZhiPuAiApi aiApi = ZhiPuAiApi.builder()
.apiKey(zhipuApikey)
.build();
return new ZhiPuAiChatModel(
aiApi,
ZhiPuAiChatOptions.builder()
.model("glm-4.6v")
.build()
);
}
}
然后就是流式接口
@GetMapping(path = "/chat2Stream", produces = "text/event-stream;charset=UTF-8")
public Flux<String> chat2Stream(@RequestParam("message") String message) {
return myZhipu.stream(message);
}
多模态的话,一定要记住配置的模型要支持多模态,不然是不行的。本文示例的glm-4.6v是支持的
@GetMapping(path = "/chatMultimodality", produces = "text/event-stream;charset=UTF-8")
// message: 请你描述一下这个图片!
public Flux<String> chatMultimodality(@RequestParam("message") String message) {
var resource = new ClassPathResource("/cqupt.png");
UserMessage userMessage = UserMessage.builder().text(message)
.media(new Media(MimeTypeUtils.IMAGE_PNG, resource))
.build();
return myZhipu.stream(userMessage);
}
// AI就会向你描述cy这个图片了。。。
让AI具备记忆也是一个需要配置的点。
// 首先就是配置类配置一个Bean
// 基于内存的会话记忆
@Bean("inMemoryChatMemory")
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.maxMessages(10)
.build();
}
// 然后根据SpringAi的官网,维护一下这个记忆就ok了,分sessionId隔离的
// 有会话记忆的聊天
@GetMapping(path = "/chatMemory", produces = "text/event-stream;charset=UTF-8")
public Flux<String> chatMemory(@RequestParam("sessionId") String sessionId,
@RequestParam("message") String message) {
// 1.构建用户消息
UserMessage userMessage = UserMessage.builder().text(message).build();
List<Message> messages = new ArrayList<>(inMemoryChatMemory.get(sessionId));
messages.add(userMessage);
// // 2.构建模型的返回数据
StringBuilder aiRes = new StringBuilder();
Prompt prompt = new Prompt(messages);
return myZhipu.stream(prompt)
.mapNotNull(res -> res.getResult().getOutput().getText())
.doOnNext(aiRes::append)
.doOnComplete(() -> {
// 流结束时,构造助手消息并保存
AssistantMessage assistantMessage = new AssistantMessage(aiRes.toString());
// 保存用户消息和助手消息
inMemoryChatMemory.add(sessionId, List.of(userMessage, assistantMessage));
});
}
3.2 Tool Calling
基本概念我们都知道了,现在就是在实际场景中的应用,现在我们定义两个工具:
@Component
public class BookQueryTool {
@Resource
private BookMapper bookMapper;
@Tool(description = "The book inquiry tool returns the total number of books currently in the database " +
"or the total number of book titles currently being sold")
public String queryBookCount() {
// 模拟图书查询逻辑
System.out.println("bookCount Tool calling........");
Long count = bookMapper.selectCount(null);
return "There are a total of " + count + " books now";
}
// 工具 2:根据作者查找(新增)
@Tool(description = "根据作者姓名查找该作者编写的所有图书列表")
public String findBooksByAuthor(@ToolParam(description = "作者姓名") String authorName) {
System.out.println("findBooksByAuthor Tool calling, author: " + authorName);
QueryWrapper<Book> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("author", authorName);
List<Book> books = bookMapper.selectList(queryWrapper);
StringBuilder list = new StringBuilder();
books.forEach(book -> list.append(book.getBookName()).append("、"));
return String.format("找到作者 %s 的书籍如下:%s", authorName, list);
}
}
然后根据springAi的官方文档,配置好Client:
@Configuration
public class ZhipuConfig {
@Resource
private BookQueryTool bookQueryTool;
private String zhipuApikey;
public ZhipuConfig() {
String zhipuEnv = System.getenv("ZHIPU_ENV");
if (zhipuEnv == null) {
throw new RuntimeException("请设置环境变量 ZHIPU_ENV");
}
this.zhipuApikey = zhipuEnv;
}
@Bean(name = "myZhipu")
public ZhiPuAiChatModel zhiPuAiChatModel() {
ZhiPuAiApi aiApi = ZhiPuAiApi.builder()
.apiKey(zhipuApikey)
.build();
return new ZhiPuAiChatModel(
aiApi,
ZhiPuAiChatOptions.builder()
.model("glm-4.6v")
.build()
);
}
@Bean("zhipuClient")
public ChatClient chatClient(ZhiPuAiChatModel zhiPuAiChatModel,
BookQueryTool bookQueryTool,
ChatMemory chatMemory) {
return ChatClient.builder(zhiPuAiChatModel)
// 关键点 1:注册工具回调,ChatClient 会自动处理函数执行过程
.defaultTools(bookQueryTool)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
// 基于内存的会话记忆
@Bean("inMemoryChatMemory")
public ChatMemory chatMemory() {
return MessageWindowChatMemory.builder()
.maxMessages(10)
.build();
}
}
然后与AI对话,就可以达到效果了!


与我们的数据库是吻合的。
3.3 RAG
RAG 的本质就是:把你的私有文档转化成“数据库”可搜索的格式,然后在 AI 回答前,先去这个“数据库”里查一下相关资料,一起喂给 AI。
可以把 RAG 拆解为三个步骤:
- ETL 阶段 (读取与向量化):把你的
.md面试笔记读出来,切成碎片(Chunks),转成一串数字(Embedding),存入向量数据库。 - Retrieval 阶段 (检索):当用户问“Java 怎么处理死锁?”时,系统先去向量数据库里找最相似的笔记片段。
- Generation 阶段 (生成):把找出来的笔记 + 用户的提问,一起发给 AI。
首先看第一阶段,就是将我们的知识转化为向量存储起来,这里就采用SpringAI官网简单的SimpleVectorStore了,本文的向量模型也是采用的智谱的。
这里的RAG只是一个简单的示例。
@Configuration
public class VectorStoreConfig {
private String zhipuApikey;
public VectorStoreConfig() {
String zhipuEnv = System.getenv("ZHIPU_ENV");
if (zhipuEnv == null) {
throw new RuntimeException("请设置环境变量 ZHIPU_ENV");
}
this.zhipuApikey = zhipuEnv;
}
// 配置一个简单的向量模型
@Bean(name = "myZhipuEmbeddingModel")
public EmbeddingModel embeddingModel() {
ZhiPuAiApi aiApi = ZhiPuAiApi.builder()
.apiKey(zhipuApikey)
.build();
return new ZhiPuAiEmbeddingModel(aiApi, MetadataMode.EMBED);
}
// 配置一个简单的向量存储
@Bean("zhipuSimpleVectorStore")
public VectorStore vectorStore(EmbeddingModel myZhipuEmbeddingModel) {
return SimpleVectorStore.builder(myZhipuEmbeddingModel)
.build();
}
}
接下来就是知识库的初始化加载
@Service
public class InterviewKnowledgeService {
@Resource(name = "zhipuSimpleVectorStore")
private VectorStore vectorStore;
// 定义持久化文件路径
private static final String INDEX_FILE_PATH = "interview_index.json";
public void initKnowledgeBase() {
File file = new File(INDEX_FILE_PATH);
if (file.exists()) {
// 情况 A: 如果 JSON 文件已存在,直接从文件加载向量数据
System.out.println("检测到本地向量索引文件,正在加载...");
if ( vectorStore instanceof SimpleVectorStore simpleStore) {
simpleStore.load(file);
}
System.out.println("向量索引加载完毕!");
} else {
ClassPathResource resource = new ClassPathResource("docs/a_面试宝库.md");
// 1. 读取 MD 文件
TikaDocumentReader reader = new TikaDocumentReader(resource);
List<Document> documents = reader.get();
// 2. 文本切分 (TokenTextSplitter)
// 设为 500 Token 一个块,重叠 50 Token 防止知识点被切断
TokenTextSplitter splitter = new TokenTextSplitter(500, 50, 5, 10000, true);
List<Document> splitDocs = splitter.apply(documents);
// 3. 存入向量库 (这里会自动调用 Embedding 模型)
vectorStore.accept(splitDocs);
// 如果是 SimpleVectorStore,可以持久化到本地
if (vectorStore instanceof SimpleVectorStore simpleStore) {
simpleStore.save(new File("interview_index.json"));
}
}
}
@PostConstruct
public void init() {
System.out.println("初始化面试知识库...");
initKnowledgeBase();
}
}
知识库配置好了之后,接下来在client端配置一下QuestionAnswerAdvisor就可以了:
@GetMapping(path = "/chatMemory", produces = "text/event-stream;charset=UTF-8")
public Flux<String> chatMemory(@RequestParam("sessionId") String sessionId,
@RequestParam("message") String message) {
return zhipuClient.prompt()
.user(message)
// 1. 显式指定要启用的工具方法名
.toolNames("queryBookCount", "findBooksByAuthor")
.advisors( spec ->
// RAG
spec.advisors(QuestionAnswerAdvisor.builder(vectorStore).build())
.param(ChatMemory.CONVERSATION_ID, sessionId) // 记忆参数
)
.stream().content();
}
我们来看看效果:



回答的时候有的问题也调用工具了,也根据知识库文档回答了相关问题,并且也根据数据库中的错误数据给出了罗贯中先生的水浒传。。。上下文记忆也有。。。
3.4 MCP
MCP (Model Context Protocol)。第一次看这个的时候,有点儿懵逼,模型上下文协议啥啥啥的,云里雾里的,感觉有点儿像Function Call的plus版本。
经过一些了解和阅读之后感觉就是Function call的升级版,强烈建议先阅读这篇关于MCP的文章,理解其中的关键概念:
知乎-mcp || 【https://zhuanlan.zhihu.com/p/29001189476】。
这里借用一张该博主的图片:

在 MCP 出现之前,如果你想让 AI 访问你的本地文件、数据库或者 GitHub 仓库,开发者通常需要为每一种组合编写专属的连接代码:
- Claude 连接 GitHub 需要写一套代码。
- ChatGPT 连接 GitHub 又要写一套。
- 如果想换成连接 Slack,还得从头再来。
这种“点对点”的集成方式非常低效,Anthropic 推出了 MCP,旨在建立一个通用的标准,让 AI 能够以统一的方式连接任何数据源。简单来说,它是为了解决 AI 模型与外部数据、工具之间“沟通不畅”而诞生的一套开放标准。无论你对接的是 GPT、Claude、Gemini 还是开源模型,只要遵循 MCP 规范,工具调用的请求 / 响应格式、上下文传递、权限控制逻辑都是统一的,真正实现了 “一次开发,多模型兼容”。
【节选自上面知乎-mcp文章下面的zodiac评论】:有了MCP之后,LLM不需要每调用一个接口就要学习一次接口文档了。function call中,每个工具接口的调用方式不一样,固然可以让llm读取接口文档调用,但是还是很麻烦。MCP就是让所有的工具接口都用同一个方式调用,相当于用MCP协议包装了一层。这样,LLM就不需要每调用一个接口就要学习一次接口文档了。
总结:mcp就是规范了所有大模型厂商,他们在开发自己的服务的时候按照一个标准来,这样可以做到大一统的状态。
4. 结束
一些基础概念就到这里吧。请见后续

浙公网安备 33010602011771号