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:

  1. Role (角色):定义 AI 的身份。“你是一个拥有 10 年经验的 Java 性能优化专家。”
  2. Task (任务):明确要做什么。“请分析这段代码是否存在内存泄漏风险。”
  3. Context (上下文):提供背景信息或限制条件。“这段代码运行在 K8s 环境中,JVM 堆内存限制为 2GB,使用的是 G1 垃圾回收器。”
  4. 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 或工具进行交互,从而增强其功能。

工具主要用于:

  1. 信息检索。此类工具可用于从外部来源(例如数据库、Web 服务、文件系统或网络搜索引擎)检索信息。其目标是增强模型的知识,使其能够回答原本无法回答的问题。因此,它们可用于检索增强生成 (RAG) 场景。例如,可以使用此类工具检索给定位置的当前天气、检索最新新闻文章或查询数据库中的特定记录。

  2. 执行操作。此类工具可用于在软件系统中执行操作,例如发送电子邮件、在数据库中创建新记录、提交表单或触发工作流。其目标是自动化那些原本需要人工干预或显式编程的任务。例如,该工具可用于为与聊天机器人交互的客户预订航班、填写网页上的表单,或在代码生成场景中基于自动化测试(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 拆解为三个步骤:

  1. ETL 阶段 (读取与向量化):把你的 .md 面试笔记读出来,切成碎片(Chunks),转成一串数字(Embedding),存入向量数据库。
  2. Retrieval 阶段 (检索):当用户问“Java 怎么处理死锁?”时,系统先去向量数据库里找最相似的笔记片段。
  3. 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. 结束

一些基础概念就到这里吧。请见后续

end. 参考

  1. 知乎-mcp || 【https://zhuanlan.zhihu.com/p/29001189476】

  2. SpringAI官网:https://docs.spring.io/spring-ai/reference/

posted @ 2026-03-19 15:23  别来无恙✲  阅读(7)  评论(0)    收藏  举报