SpringAi与大模型在项目中的应用(2)

4.SpringAI

SpringAI整合了全球(主要是国外)的大多数模型,而且对于大模型开发的三种技术架构都有⽐较
好的封装和支持,开发起来⾮常方便。不同的模型能够接收的输⼊类型、输出类型不⼀定相同。SpringAI根据模型的输⼊和输出类型不同对 模型进⾏了分类:
⼤模型应用开发大多数情况下使⽤的都是基于对话模型(Chat Model),也就是输出结果为⾃然语言或代码的模型
⽬前SpringAI⽀持的⼤约19种对话模型,这是SpringAi官方文档
其中功能最完整的就是OpenAIOllama平台的模型了。

5..SpringAI⼊⻔

5.1.创建⼯程

创建⼀个新的SpringBoot⼯程,勾选Web、MySQL驱动即可
注意jdk版本必须大于等于17
工程结构如图:

5.2引入依赖

SpringAI完全适配了SpringBoot的⾃动装配功能,⽽且给不同的⼤模型提供了不同的starter,比如:
我们可以根据⾃⼰选择的平台来选择引⼊不同的依赖。这⾥我们先以Ollama为例。
⾸先,在项⽬pom.xml中添加spring-ai的版本信息:
<spring-ai.version>1.0.0-M6</spring-ai.version>
然后,添加spring-ai的依赖管理项:
<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>
最后,引⼊spring-ai-ollama的依赖:
   <dependency>
         <groupId>org.springframework.ai</groupId>
          <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
   </dependency>
为了⽅便后续开发,我们再⼿动引⼊⼀个Lombok依赖:
<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
❗ 注意:
千万不要⽤start.spring.io提供的lombok,有bug!!

5.3. 配置模型信息

  • 配置application.yaml
    设置Ollama服务地址和模型参数:

  • spring:  
      ai:  
        ollama:  
          base-url: http://localhost:11434  
          chat:  
            model: deepseek-r1:7b  
            options:  
              temperature: 0.8  

5.4. 定义ChatClient

创建配置类
CommonConfiguration中注入ChatClient

@Configuration  
public class CommonConfiguration {  
    @Bean  
    public ChatClient chatClient(OllamaChatModel model) {  
        return ChatClient.builder(model)  
                .defaultSystem("您的客服助手设定")  
                .build();  
    }  
}  

5.5. 同步调用接口

  • 编写Controller
    处理用户输入并返回AI响应:

    @RestController  
    @RequestMapping("/ai")  
    public class ChatController {  
        private final ChatClient chatClient;  
    
        @RequestMapping("/chat")  
        public String chat(@RequestParam String prompt) {  
            return chatClient.prompt(prompt).call().content();  
        }  
    }  

    访问示例:http://localhost:8080/ai/chat?prompt=你好

5.6. 流式调用优化

  • 修改Controller
    使用Flux实现流式响应:

@RequestMapping(value = "/chat", produces = "text/html;charset=UTF-8")  
public Flux<String> chat(@RequestParam String prompt) {  
    return chatClient.prompt(prompt).stream().content();  
}  

优势:逐字返回结果,提升用户体验

5.7.System设定

可以发现,当我们询问AI你是谁的时候,它回答⾃⼰是DeepSeek-R1,这是⼤模型底层的设定。如果 我们希望AI按照新的设定⼯作,就需要给它设置System背景信息。 在SpringAI中,设置System信息⾮常⽅便,不需要在每次发送时封装到Message,⽽是创建 ChatClient时指定即可。
我们修改 CommonConfiguration 中的代码,给 ChatClient 设定默认的System信息:
@Bean
    public ChatClient chatClient(OllamaChatModel model) {
        return ChatClient.builder(model) // 创建ChatClient⼯⼚实例
                .defaultSystem("您是一位大学生,你的名字叫⼩⿊。请以友好、乐于助⼈和愉快的⽅式解答学⽣的各种问题。")
                .defaultAdvisors(new SimpleLoggerAdvisor())
                .build(); // 构建ChatClient实例
    }

5.8.⽇志功能

默认情况下,应用于AI的交互时不记录⽇志的,我们无法得知SpringAI组织的提⽰词到底⻓什么样,有 没有问题。这样不⽅便我们调试。
Spring提供了⼀些Advisor的默认实现,来实现⼀些基本的增强功能:
  • SimpleLoggerAdvisor:⽇志记录的Advisor
  • MessageChatMemoryAdvisor:会话记忆的Advisor
  • QuestionAnswerAdvisor:实现RAG的Advisor

5.8.1.添加⽇志Advisor

⾸先,我们需要修改 CommonConfiguration ,给 ChatClient 添加⽇志Advisor:
 @Bean
    public ChatClient chatClient(OllamaChatModel model) {
        return ChatClient.builder(model) // 创建ChatClient⼯⼚实例
                .defaultSystem("你是⼀个热⼼、可爱的智能助⼿,你的名字叫⼩团团,请以⼩团团的⾝份和语⽓回答问题。")
                .defaultAdvisors(new SimpleLoggerAdvisor()) // 添加默认的Advisor,记录⽇志
                .build(); // 构建ChatClient实例
    }

5.8.2.修改⽇志级别

接下来,我们在 application.yaml 中添加⽇志配置,更新⽇志级别:
logging:
    level:
        org.springframework.ai: debug # AI对话的⽇志级别
        com.itheima.ai: debug # 本项⽬的⽇志级别

5.9前端

前端这个可以根据自己需求自己编写要注意跨域问题

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")  // 允许所有域名访问
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*");
    }
}

5.10.会话记忆功能

现在,我们的AI聊天机器⼈是没有记忆功能的,上⼀次聊天的内容,下⼀次就忘掉了。 我们之前说过,让AI有会话记忆的⽅式就是把每⼀次历史对话内容拼接到Prompt中,⼀起发送过去。
是不是还挺麻烦的。 别担心,好消息是,我们并不需要自己来拼接,SpringAI⾃带了会话记忆功能,可以帮我们把历史会 话保存下来,下⼀次请求AI时会⾃动拼接,⾮常⽅便。

5.10.1.ChatMemory

会话记忆功能同样是基于AOP实现,Spring提供了⼀个 MessageChatMemoryAdvisor 的通知,我
们可以像之前添加⽇志通知⼀样添加到 ChatClient 即可。
不过,要注意的是, MessageChatMemoryAdvisor 需要指定⼀个 ChatMemory 实例,也就是会
话历史保存的⽅式。
public interface ChatMemory {
        // TODO: consider a non-blocking interface for streaming usages
        default void add(String conversationId, Message message) {
            this.add(conversationId, List.of(message));
        }

        // 添加会话信息到指定conversationId的会话历史中
        void add(String conversationId, List<Message> messages);

        // 根据conversationId查询历史会话
        List<Message> get(String conversationId, int lastN);

        // 清除指定conversationId的会话历史
        void clear(String conversationId);
    }
可以看到,所有的会话记忆都是与 conversationId 有关联的,也就是会话Id,将来不同会话id的
记忆自然是分开管理的。
⽬前,在SpringAI中有两个ChatMemory的实现:
  1. InMemoryChatMemory :会话历史保存在内存中
  2. CassandraChatMemory :会话保存在Cassandra数据库中(需要引入额外依赖,并且绑定了向量数据库,不够灵活)
我们暂时选择⽤ InMemoryChatMemory 来实现
CommonConfiguration 中注册 ChatMemory 对象:

@Bean
public ChatMemory chatMemory() {
return new InMemoryChatMemory();
}
然后添加 MessageChatMemoryAdvisor ChatClient
 @Bean
    public ChatClient chatClient(OllamaChatModel model, ChatMemory chatMemory) {
        return ChatClient.builder(model) // 创建ChatClient⼯⼚实例
                .defaultSystem("您是以为大学生,你" +
                        "的名字叫⼩⿊。请以友好、乐于助⼈和愉快的⽅式解答学⽣的各种问题。")
                                .defaultAdvisors(new SimpleLoggerAdvisor()) // 添加默认的Advisor,记录⽇志
                                .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
                                .build(); // 构建ChatClient实例
    }
OK,现在聊天会话已经有记忆功能了,不过现在的会话记忆还是不完善的,我们还有继续补充。

5.11.会话历史

会话历史与会话记忆是两个不同的事情:
  • 会话记忆:是指让⼤模型记住每⼀轮对话的内容,不⾄于前⼀句刚问完,下⼀句就忘了。
  • 会话历史:是指要记录总共有多少不同的对话

5.11.1管理会话id(会话历史)

由于会话记忆是以 conversationId 来管理的,也就是会话id(以后简称为chatId)。将来要查询
会话历史,其实就是查询历史中有哪些chatId. 因此,为了实现查询会话历史记录,我们必须记录所有的chatId,我们需要定义⼀个管理会话历史的 标准接口。
public interface ChatHistoryRepository {
    /**
     * 保存对话记录
     * @param type 业务类型
     * @param chatId 会话id
     */
    void save(String type,String chatId);

    /**
     * 获取会话id列表
     * @param type 业务类型
     * @return 会话ID列表
     */
    List<String> getChatIdList(String type);
}
然后定义⼀个实现类 InMemoryChatHistoryRepository
@Component
public class InMemoryChatRepository implements ChatHistoryRepository{
    private final Map<String,List<String>> chatHistory =new HashMap<>();
    
    @Override
    public void save(String type, String chatId) {
        if(!chatHistory.containsKey(type)){
            chatHistory.put(type,new ArrayList<>());
        }
        List<String> chatIdList= chatHistory.get(type);
        if(chatIdList.contains(chatId)){
           return;
        }
         chatIdList.add(chatId);
    }

    @Override
    public List<String> getChatIdList(String type) {
        List<String> chatIds = chatHistory.get(type);
        return chatIds!=null?chatIds:new ArrayList<>();
    }
}
❗ 注意:
⽬前我们业务⽐较简单,没有⽤⼾概念,但是将来会有不同业务,因此简单采⽤内存保存
type与chatId关系。
将来⼤家也可以根据业务需要把会话id持久化保存到Redis、MongoDB、MySQL等数据库。
如果业务中有user的概念,还需要记录userId、chatId、time等关联关系

5.11.2保存会话id

接下来,修改ChatController中的chat⽅法,做到3点:
  • 添加⼀个请求参数:chatId,每次前端请求AI时都需要传递chatId
  • 每次处理请求时,将chatId存储到ChatRepository
  • 每次发请求到AI⼤模型时,都传递⾃定义的chatId
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMO RY_CONVERSATION_ID_KEY;
    @CrossOrigin("*")
    @RequiredArgsConstructor
    @RestController
    @RequestMapping("/ai")
    public class ChatController {
        private final ChatClient chatClient;
        private final ChatMemory chatMemory;
        private final ChatHistoryRepository chatHistoryRepository;
        @RequestMapping(value = "/chat", produces = "text/html;charset=UTF-8")
        public Flux<String> chat(@RequestParam(defaultValue = "讲个笑话") String
                                         prompt, String chatId) {
            chatHistoryRepository.addChatId(chatId);
            return chatClient
                    .prompt(prompt)
                    .advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))
                    .stream()
                    .content();
        }
    }
注意,这⾥传递chatId给Advisor的⽅式是通过AdvisorContext,也就是以key-value形式存⼊上下文
chatClient.advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId))

其中的 CHAT_MEMORY_CONVERSATION_ID_KEY 是AbstractChatMemoryAdvisor中定义的常量 key,将来 MessageChatMemoryAdvisor 执⾏的过程中就可以拿到这个chatId了

5.11.3.查询会话历史

接着,我们定义⼀个新的Controller,专⻔实现会话历史的查询。包含两个接⼝:
  • 根据业务类型查询会话历史列表(我们将来有3个不同业务,需要分别记录历史。⼤家的业务可能 是按userId记录,根据UserId查询)
  • 根据chatId查询指定会话的历史消息
其中,查询会话历史消息,也就是Message集合。但是由于Message并不符合页面的需要,我们需要自己定义⼀个VO
@NoArgsConstructor
@Data
public class MessageVO {
    private String role;
    private String content;

    public MessageVO(Message message) {
        switch (message.getMessageType()) {
            case USER:
                role = "user";
                break;
            case ASSISTANT:
                role = "assistant";
                break;
            default:
                role = "unknown";
                break;
        }
        this.role = role;
        this.content = message.getText();
    }
}
然后在 新建⼀个 ChatHistoryController
@RestController
@RequestMapping("/ai/history")
@RequiredArgsConstructor
public class ChatHistoryController {
    public final ChatHistoryRepository chatHistoryRepository;
    private final ChatMemory chatMemory;

    @GetMapping("{type}")
    public List<String> getChatIdList(@PathVariable("type") String type) {
        return chatHistoryRepository.getChatIdList(type);
    }

    @GetMapping("{type}/{chatId}")
    public List<MessageVO> getChatHistory(@PathVariable("type") String type, @PathVariable("chatId") String chatId) {
        List<Message> messages = chatMemory.get(chatId, Integer.MAX_VALUE);
        if(messages.isEmpty()){
            return null;
        }
        return messages.stream().map(MessageVO::new).toList();

    }

}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值