注意

  1. langchain4j需要JDK17以上的版本,比如JDK21.
  2. 使用@Bean的原因:

Spring容器管理:让Spring框架管理这个对象的生命周期,自动创建、初始化和销毁

依赖注入:其他组件可以通过@Resource@Autowired直接注入使用,无需手动new对象

单例复用:默认情况下Spring会保证整个应用只有一个实例,避免重复创建开销

配置集中:在@Configuration类中统一管理所有服务的创建逻辑,便于维护

在这个项目中,通过@Bean注册AI服务后,Controller或其他Service就能直接使用这个具备AI能力的服务了。

一、ai对话——chatModel

导入阿里大模型

阿里大模型maven依赖地址
在pox.xml添加依赖:

<!-- Source: https://mvnrepository.com/artifact/dev.langchain4j/langchain4j-community-dashscope-spring-boot-starter -->
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
    <version>1.14.1-beta24</version>
    <scope>compile</scope>
</dependency>

在下面链接找到对应模型的依赖导入,然后复制下来到搜索框,找到最新版本号依赖,添加
langchain4j中文教程对应LLM模型依赖

对chat单元测试报错:

报错内容:

Exception in thread "main" java.lang.NoSuchMethodError: 'java.lang.String org.junit.platform.engine.discovery.MethodSelector.getMethodParameterTypes()

解决:
在搜索任何方法之前,先检查你的JDK版本,在IDEA终端运行MVN

二、多模态

多模态是指能够同时处理、理解和生成多种不同类型数据的能力,比如文本、图像、音频、视频、PDF等等

  //简单对话,自定义消息类型
 public String ChatWithMessage(UserMessage userMessage) {
        ChatResponse chatResponse = qwenChatModel.chat(userMessage);
        AiMessage aiMessage = chatResponse.aiMessage();
        log.info("AI 输出:" + aiMessage.toString());
        return aiMessage.text();
    }

测试

在这里插入图片描述
**原因:**因为qwen大模型本身不支持多模态进而导致识别不出来

三、系统提示词SystemMessage

系统提示词是设置AI模型行为规则和角色定位的隐藏指令,用户通常不能直接看到。系统Prompt 相当于给AI设定人格和能力边界,也就是告诉AI"你是谁?你能做什么?
区别于用户提示词

//系统消息是全局唯一,如果有多个,会替换掉旧的
private static final String SYSTEM_MESSAGE = """  
        你是编程领域的小助手,帮助用户解答编程学习和求职面试相关的问题,并给出建议。重点关注4 个方向:1.规划清晰的编程学习路线
        2.提供项目学习建议
        3.给出程序员求职全流程指南(比如简历优化、投递技巧)
        4.分享高频面试题和面试技巧
        请用简洁易懂的语言回答,助力用户高效学习与求职。
        """;
    //简单对话
public String chat(String message) {
    SystemMessage systemMessage = SystemMessage.from(SYSTEM_MESSAGE);
    UserMessage userMessage = UserMessage.from(message);
    ChatResponse chatResponse = qwenChatModel.chat(systemMessage,userMessage);
    AiMessage aiMessage = chatResponse.aiMessage();
    log.info("AI 输出:" + aiMessage.toString());
    return aiMessage.text();
}

开发agent应用,这一部分系统提示词要写的严谨一点。

四、ai service

在学习更多特性前,我们要了解LangChain4j 最重要的开发模式–AI Service,提供了很多高层抽象的、用起来更方便的API,把AI应用当做服务来开发。

注意:前面写的chatModel是低层api,自由度比较高。
而高级api,则会封装好很多东西,直接调用,没有那么灵活。

public class AiCodeHelperServiceFactory {

    @Resource
    private ChatModel qwenChatModel;

    @Bean
    public AiCodeHelperService aiCodeHelperService() {
        return AiServices.create(AiCodeHelperService.class, qwenChatModel);//创建接口实现类
    }
}

调用 Aiservices.create 方法就可以创建出AI Service 的实现类了,背后的原理是利用Java 反射机制创建了一个实现接口的代理对象,AiServices.create生成代理对象,代理对象负责输入和输出的转换,比如把String类型的用户消息参数转为UserMessage类型并调用 CHatModel, 再将 AI返回的AiMessage类型转换为 String类型作为返回值。

五、会话记忆 ChatMemory

会话记忆是指让AI能够记住用户之前的对话内容,并保持上下文连贯性,这是实现AI应用的核心特性。

LangChain4j提供了对应类

LangChain4j为我们提供了开箱即用的Messagewindow ChatMemory会话记忆,最多保存N条消息,多余的会自动淘汰。创建会话记忆后,在构造AI Service 设置chatMemory:

  public AiCodeHelperService aiCodeHelperService() {
        //创建一个会话内存,之处区分用户的
        ChatMemory chatMemory= MessageWindowChatMemory.withMaxMessages(10);
        //创建接口实现类,不需要原先那种创建接口实现的类,直接可以动态创建接口实现类
        //构建ai-service
       AiCodeHelperService aiCodeHelperService = AiServices.builder(AiCodeHelperService.class)
                .chatModel(qwenChatModel)
                .chatMemory(chatMemory) //会话记忆
                .build();
        return aiCodeHelperService;
    }
}

进阶语法

会话记忆默认是存储在内存的,重启后会丢失,可以通过自定义ChatMemoryStore接口的实现类,将消息保存到MySQL等其他数据源中。

六、结构化输出

结构化输出是指将大模型返回的文本输出转换为结构化的数据格式,比如一段JSON、一个对象、或者是复杂的对象列表。
在这里插入图片描述

三种实现方式

  • 利用大模型的JSONschema
  • 利用 Prompt + JSON Mode
  • 利用Prompt

前两种效果要好于第三种;
原因第一种是langchain4j内在的设计好的框架,第三种,则需要用户设置好prompt,让agent知道要这么做,有点强硬。没有第一种原生态的效果好。

自然语言是给人看的,结构化输出是给程序运行的
在这里插入图片描述
在这里插入图片描述

代码实现

1.接口

在这里插入图片描述

2.具体实现–测试

在这里插入图片描述

七、RAG 检索增强生成

简述
RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合信息检索技术和AI 内容生成的混合架构,可以解决大模型的知识时效性限制和幻觉问题。

简单来说,RAG就像给AI配了一个“小抄本”,让AI回答问题前先查一查特定的知识库来获取知识,确保回答是基于真实资料而不是凭空想象。很多企业也基于RAG搭建了自己的智能客服,可以用自己积累的领域知识回复用户。


如果某一个用户问的问题,和知识库越接近,那么两个向量距离距离就会越接近。
在这里插入图片描述
注意这里向量就理解成表示文本的一种方式

RAG的风格

基础版一切都是基于内置,效果不太好,不建议使用,在实战中,建议用下面两种。

标准版

下面来试试标准版RAG实现,为了更好地效果,我们需要:

  • 加载Markdown文档并按需切割
  • Markdown文档补充文件名信息
  • 自定义Embedding模型:选择一些更好的模型,提高维度,最后检索效果会更好
  • 自定义内容检索器:怎么样从向量数据库里面检测内容
重叠切片的含义

在这里插入图片描述

public class RagConfig {

    @Resource
    private EmbeddingModel qwenEmbeddedModel;

    @Resource
    private EmbeddingStore<TextSegment> embeddingStore;

    //为aiservice提供一个内容检索器
    @Bean
    public ContentRetriever contentRetriever() {
        //-----RAG----
        //1.加载文档
        List<Document> documents=FileSystemDocumentLoader.loadDocuments("src/main/resources/docs");
        //2.切割文档:每个文档按照段落切割,最大1000字符,重叠200字符
        DocumentByParagraphSplitter documentByParagraphSplitter =
                new DocumentByParagraphSplitter(1000, 200);
        //3.自定义文档加载器,把文档转换成向量保存在向量数据库里面
        EmbeddingStoreIngestor ingestor=EmbeddingStoreIngestor.builder()
                .documentSplitter(documentByParagraphSplitter)
                //为了提高文档切片质量,为切割后的文档碎片TextSegment添加文档名称作为元信息,加载向量
                .textSegmentTransformer(textSegment ->
                            TextSegment.from(textSegment.metadata().getString("file_name")+"\n"
                                    +textSegment.text(),textSegment.metadata()))
                //使用向量模型,保存到向量存储中
                .embeddingModel(qwenEmbeddedModel)
                .embeddingStore(embeddingStore)
                .build();
        //加载文档
        ingestor.ingest(documents);
        //4.自定义内容加载器
       EmbeddingStoreContentRetriever contentRetriever= EmbeddingStoreContentRetriever.builder()
                .embeddingStore(embeddingStore)
                .embeddingModel(qwenEmbeddedModel)
                .maxResults(5) //最多五条结果
                .minScore(0.75)  //过滤分数小于0.75的结果
                .build();
       return contentRetriever;
    }
}
怎么样实现ai回答的内容后面带上依据文件

在这里插入图片描述
在LangChain4j 中,实现这个功能很简单。在AI Service 中新增方法,在原本的返回类型外封装一层Result类,就可以获得封装后的结果,从中能够获取到RAG引用的源文档、以及Token的消耗情况等等。

先在接口里面添加第一模块;
在这里插入图片描述

进阶版

八、工具调用-Tools

工具调用(Tool Calling)可以理解为让AI大模型借用外部工具来完成它自己做不到的事情。
跟人类一样,如果只凭手脚完成不了工作,那么就可以利用工具箱来完成。
工具可以是任何东西,比如网页搜索、对外部API的调用、访问外部数据、或执行特定的代码等。
比如用户提问“帮我查询上海最新的天气”,AI本身并没有这些知识,它就可以调用“查询天气工具”,来完成任务。
需要注意的是,工具调用的本质并不是AI服务器自己调用这些工具、也不是把工具的代码发送给 AI服务器让它执行,它只能提出要求,表示“我需要执行XX工具完成任务”。而真正执行工具的是我们自己的应用程序,执行后再把结果告诉AI,让它继续工作
在这里插入图片描述
我们需要的网络搜索能力,就可以通过工具调用来实现。这里我们细化下需求:让AI能够通过我的面试鸭刷题网站来搜索面试题。

实现方案很简单,因为面试鸭网站的搜索页面支持通过URL参数传入不同的搜索关键词,我们只需要利用Jsoup库抓取面试鸭搜索页面的题目列表就可以了

九、模型上下文协议——MCP

MCP(Model Context Protocol,模型上下文协议)是一种开放标准,目的是增强AI与外部系统的交互能力。MCP为AI提供了与外部工具、资源和服务交互的标准化方式,让AI能够访问最新数据、执行复杂操作,并与现有系统集成。
————————————————————————————————————————————
可以将MCP想象成AI应用的USB接口。就像USB为设备连接各种外设和配件提供了标准化方式一样,MCP为AI模型连接不同的数据源和工具提供了标准化的方法。

刚刚我们通过工具调用实现了面试题的搜索,下面我们利用MCP实现全网搜索内番:这也是一个典型的MCP应用场景了

  • 一种是在线使用别人的服务器
  • 另一种则需要下载到本到来连接MCP

十、护栏机制 Graudrail(拦截器)

Guardrails 是一种机制,可以让你验证 LLM(大语言模型)的输入和输出确保其符合预期。通过 Guardrails,你可以完成以下操作:

  • 验证用户输入是否不在允许范围内
  • 确保输入在调用 LLM 前满足特定条件(例如防御 提示注入攻击)
  • 确保输出格式正确(例如是符合正确模式的 JSON 文档)
  • 确保 LLM 输出符合业务规则和约束(例如,如果这是 X 公司的聊天机器人,回答中不能包含对竞争对手 Y 的引用)
  • 检测幻觉(hallucinations)

以上只是示例,你可以用 Guardrails 做更多的事情。在这里插入图片描述
在这里插入图片描述

十、日志和可观测性

之前我们都是通过Debug查看运行信息,不仅不便于调试,而且生产环境肯定不能这么做。

官方提供了日志和可观测性,来帮我们更好地调试程序、发现问题。

日志 用国外的大模型才支持
在这里插入图片描述
所以我们这里只记录可观测性

问题:下面写完listener之后,测试时发现没有监听到自定义的model
在这里插入图片描述
解决方法:自己定义一个大模型

package com.yupi.aicodehelper.ai.model;

import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.listener.ChatModelListener;
import jakarta.annotation.Resource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
@ConfigurationProperties(prefix = "langchain4j.community.dashscope.chat-model")
@Data
public class QwenChatModelConfig {

    private String modelName;

    private String apiKey;

    @Resource
    private ChatModelListener chatModelListener;

    @Bean
    public ChatModel myQwenChatModel() {
        return QwenChatModel.builder()
                .apiKey(apiKey)
                .modelName(modelName)
                .listeners(List.of(chatModelListener))
                .build();
    }
}

十一、服务化SSE流式输出

AI服务化

至此,AI的能力基本开发完成,但是目前只支持本地运行,需要编写一个接口提供给前端调用,让AI能够成为一个服务。

我们平时开发的大多数接口都是同步接口,也就是等后端处理完再返回。但是对于AI应用,特别是响应时间较长的对话类应用,可能会让用户失去耐心等待,因此推荐使用 SSE(Server-Sent Events)技术实现实时流式输出,类似打字机效果,大幅提升用户体验。

1.引入依赖
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-reactor</artifactId>
            <version>1.1.0-beta7</version>
        </dependency>
2.写入service接口
    // 流式对话,因为是给前端使用,每次会话要隔离,所以用到chatmemory的另一个特性,隔离会话
   Flux<String> chatStream(@MemoryId int memoryId, @UserMessage String userMessage);

3.在这里插入图片描述
4.写一个contorller

package com.yupi.aicodehelper.controller;

import com.yupi.aicodehelper.ai.AiCodeHelperService;
import jakarta.annotation.Resource;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/ai")
public class AiController {

    @Resource
    private AiCodeHelperService aiCodeHelperService;

    @GetMapping("/chat")
    public Flux<ServerSentEvent<String>> chat(int memoryId, String message) {
        return aiCodeHelperService.chatStream(memoryId, message)
                .map(chunk -> ServerSentEvent.<String>builder()
                        .data(chunk)
                        .build());
    }
}

5.此时前端还没写,怎么测试
(1)用api测试工具
(2)在项目文件夹运行git
在这里插入图片描述

十二、前端开发

直接使用cursor开发;
提示词
首先准备一段详细的Prompt,一般要包括需求、技术选型、后端接口信息,还可以提供一些原型图、后端代码等

你是一位专业的前端开发,请帮我根据下列信息来生成对应的前端项目代码。

## 需求

应用为《AI 编程小助手》,帮助用户解答编程学习和求职面试相关的问题,并给出建议。

只有一个页面,就是主页:页面风格为聊天室,上方是聊天记录(用户信息在右边,AI 信息在左边),下方是输入框,进入页面后自动生成一个聊天室 id,用于区分不同的会话。通过 SSE 的方式调用 chat 接口,实时显示对话内容。

## 技术选型

1. Vue3 项目
2. Axios 请求库

## 后端接口信息

接口地址前缀:http://localhost:8081/api

## SpringBoot 后端接口代码

@RestController
@RequestMapping("/ai")
public class AiController {

    @GetMapping("/chat")
    public Flux<ServerSentEvent<String>> chat(int memoryId, String message) {
        return aiCodeHelperService.chatStream(memoryId, message)
                .map(chunk -> ServerSentEvent.<String>builder()
                        .data(chunk)
                        .build());
    }
}

注意:如果使用的是 Windows 系统,最好在 prompt 中补充“你应该使用 Windows 支持的命令来完成任务”。

开发

1.因为项目还是比较复杂的,选择带大脑的claude4来执行

在这里插入图片描述

2.报错了,查找错误方法:按F12

在这里插入图片描述
a,查找错误方法:按F12,打开开发者工具,看看是前端还是后端错误
b,在这里插入图片描述
c,再查看后端debug的报错
在这里插入图片描述
d,找到报错信息
在这里插入图片描述

十三、总结

回到开头的那个问题:实际开发中应该如何选择 AI 开发框架呢

就拿 Spring AI 和 LangChain4j 来说,不知道大家更喜欢哪个框架?我其实会更喜欢 Spring AI 的开发模式,而且 Spring AI 目前支持的能力更多,还有国内 Spring AI Alibaba 的巨头加持,生态更好,遇到问题更容易解决;LangChain4j 的优势在于可以独立于 Spring 项目使用,更自由灵活一些。
在这里插入图片描述

last:注意要点

一.企业实战笔记:启动阶段依赖外网的正确姿势

  1. 先区分依赖类型
  • 核心依赖:缺失就不能安全/正确运行(可 fail fast)。
  • 增强依赖:只影响部分能力(应降级,不阻塞启动)。
  1. 不要让增强能力阻塞应用启动
  • RAG、MCP、推荐、外部搜索等应懒加载/异步初始化。
  • 外网失败时只关闭该功能,不让整个服务起不来。
  1. 必备工程手段
  • 功能开关(Feature Flag),如 app.mcp.enabled。
  • 超时、重试、熔断、限流。
  • 清晰日志和告警(记录“已降级”而非静默失败)。
  1. 健康检查要分层
  • liveness:进程是否存活。
  • readiness:是否可接流量(可按核心依赖判断)。
  1. 增强依赖异常不应直接判定服务死亡。
  • 配置与安全
  • API Key 放环境变量/配置中心,不写死在仓库。
  • 外部地址和策略参数化(可按环境切换)。
  1. 一句话原则
    核心链路:失败即停(Fail Fast)。
    增强链路:失败降级(Graceful Degradation)。

二、项目本质

无论是RAG。还是TOOLS,它们本质上面都是讲最后额结果重新给ai,只不过这个结果是增强了或者说准确了。ai就是大脑。

三、测试技巧

直接用 Maven 跑最稳。

在项目根目录 E:\github\ai-code-helper-master 打开终端后:

 跑全部测试
mvn test

只跑某个测试类(示例)
mvn -Dtest=AiCodeHelperTest test

只跑某个测试方法(示例)
mvn -Dtest=AiCodeHelperTest#testChat test

比如,要跑AiCodeHelperServiceTest里面的chatWithMcp方法

mvn -Dtest=AiCodeHelperServiceTest#chatWithMcp test

四、解决前后端跨域代码,

1.创建config软件包

2.在里面复制这一段代码即可,无需改动

package com.yupi.aicodehelper.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 全局跨域配置
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 覆盖所有请求
        registry.addMapping("/**")
                // 允许发送 Cookie
                .allowCredentials(true)
                // 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突)
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .exposedHeaders("*");
    }
}

五、报错代码解释

400,用户请求参数出现问题

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐