借助 Spring AI Alibaba 打造多功能的 “地铁小助手”

356 阅读10分钟

引言

AI浪潮下,身为程序员的我们虽然不用像专业人工智能工程师一样了解大模型的原理,但学会一些基本的概念和使用还是十分必要的,毕竟,这些知识能够帮助我们更好地融入当下的技术潮流,为开发出更具创新性和实用性的应用程序奠定基础。本文就带着大家来通过Spring AI Alibaba开发“地铁小助手”为例来实现一下AI包括基本对话,对话记忆,检索增加等功能,快搬好小板凳,打开编译器跟我一起出发吧。

Spring AI Alibaba能力

大模型经过这两年的发展,技术越发的成熟,成本也逐渐降低,越来越多公司也想要有自己的“智能客服”,基于此,Spring社区在5月发布Spring AI,方便开发者快速对接ChatGpt模型,但Spring Ai主要面向国外的各种模型,对于我们使用始终有所限制,于是乎,阿里巴巴重磅发布Spring AI Alibaba,基于Spring AI,搭配它提供的阿里同义系列大模型,极大地简化了java AI应用的开发 在项目开始前,我们先来看看为什么选择Spring AI Alibaba,他有哪些能力。

  • 开发复杂 AI 应用的高阶抽象 Fluent API — ChatClient
  • 提供多种大模型服务对接能力,包括主流开源与阿里云通义大模型服务(百炼)等
  • 支持的模型类型包括聊天、文生图、音频转录、文生语音等
  • 支持同步和流式 API,在保持应用层 API 不变的情况下支持灵活切换底层模型服务,支持特定模型的定制化能力(参数传递)
  • 支持 Structured Output,即将 AI 模型输出映射到 POJOs
  • 支持矢量数据库存储与检索
  • 支持函数调用 Function Calling
  • 支持构建 AI Agent 所需要的工具调用和对话内存记忆能力
  • 支持 RAG 开发模式,包括离线文档处理如 DocumentReader、Splitter、Embedding、VectorStore 等,支持 Retrieve 检索。

Spring AI Alibaba官网:Spring AI Alibaba 概述-阿里云Spring Cloud Alibaba官网
Spring AI Alibaba git地址:spring-ai-alibaba/README-zh.md at main · alibaba/spring-ai-alibaba

image.png

这些功能现在看不懂也没关系,下面我们通过“地铁小助手”再来详细介绍。

地铁小助手功能要求

当项目完成后,我们需要它能完成的功能有哪些:

  • 基于 AI 大模型与用户对话

    通过大模型技术,准确理解乘客自然语言所表达的需求,无论是关于行程规划、票务问题还是车站设施相关的询问,都能精准识别和处理。

  • 支持多轮连续对话

    能够在多轮对话的情境中保持对用户意图的清晰理解。例如,当乘客先询问某条线路的首末班车时间,后续又询问该线路某站点周边信息时,助手可以依据上下文准确回答,实现连贯、流畅的交互体验。

  • 理解地铁运营相关的术语与规范并严格遵守

    1. 票务规则
    • 了解特殊票务情况,如儿童票、老年票、残疾军人票等优惠票种的相关规定,包括购票资格、票价优惠幅度和使用注意事项。
    1. 乘车规则
    • 了解换乘规则,包括不同线路之间换乘的步行路线、换乘通道开放时间、是否需要出站换乘等信息,确保乘客顺利完成换乘。

项目搭建

1. 创建SpringBoot项目,版本选择3.3.x,jdk必须选择17+的。
2. 获取API-KEY

API-KEY是调用阿里大模型的必要参数,通过如何获取API Key_大模型服务平台百炼(Model Studio)-阿里云帮助中心开通“百炼大模型推理”服务,开通之后在右上角“我的”->“API-KEY”中查看,获取完之后请妥善保管。

image.png

3. 添加依赖,在pom中添加如下依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter</artifactId>
        <version>1.0.0-M3.1</version>
    </dependency>
</dependencies>
<!--这里需配置镜像,否则报错:spring-ai: 1.0.0-M3.1 dependency not found-->
<repositories>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/milestone</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>
4. 在 application.yml 配置文件中添加在百炼大模型服务平台获取的api-key
spring:
  application:
    name: springai_alibaba
  ai:
    dashscope:
      api-key: 申请的api-key

ChatClient 简单对话

项目搭建完毕,接下来就到了实现对话功能了, ChatClient是我们与大语言模型(LLM)之间的桥梁,与LLM之间的交互都需要ChatClient来帮助我们完成,使用ChatClient可以将与 LLM 及其他组件交互的复杂性隐藏在背后,因为基于 LLM 的应用程序通常要多个组件协同工作(例如,聊天记忆),这里我们先完成简单的对话功能,初始化一个ChatClient

@RestController
public class ChatGptController
{

    private final ChatClient chatClient;
    //初始化ChatClient
    public ChatGptController(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    /**
     * 同步API
     */
    @RequestMapping("/ai/chat")
    public String chat(@RequestParam("message") String message)
    {
        return this.chatClient.prompt().user(message).call().content();
    }

    /**
     * 流式API
     */
    @RequestMapping(value = "/ai/stream",produces = "text/html;charset=utf-8")
    public Flux<String> fluxChat(@RequestParam("message")String message) {
        return chatClient.prompt().user(message).stream().content();
    }
}

user(message)设置用户消息内容,call()向 AI 模型发送请求,content()以字符串形式返回 AI 模型的响应,stream()是一种异步的、持续的获得模型响应的方式。

启动项目,访问localhost:8080/ai/stream?message=你知道明天的地铁信息吗,得到结果如下

image.png 此时我们架构如下:

image.png

附加人设

现在我们的“客服”已经能够进行正常的交流,但它还不知道它需要为我们提供什么,因此我们需要在初始化ChatClient时调用defaultSystem(),添加人设信息。

public ChatGptController(ChatClient.Builder builder) {
    this.chatClient = builder.defaultSystem("""
                    您是 “地铁出行小助手”。请以热情、耐心且专业的方式来回复。
                    您正在通过智能交互系统与乘客互动。
                    在提供有关票务购买、行程规划、车站设施等具体信息或操作之前,您必须始终从用户处获取以下信息:
                    所在城市(因为不同城市地铁情况可能不同)、出行大致时间(如工作日早高峰、周末下午等,以便更精准提供运营时间等相关信息)。
                    在询问用户之前,请检查消息历史记录以获取此信息。
                    在处理如退票等涉及票务规则变更的操作之前,您必须确保相关地铁票务条款允许这样做。
                    如果操作涉及费用产生(如某些特殊票务处理可能有手续费等情况),您必须在继续之前征得用户同意。
                    使用提供的功能获取地铁运营详细信息(如各线路实时运行状况、首末班车时间等)、
                    进行票务相关操作(如购票、查询余额等)以及查询车站设施详情(如电梯位置、洗手间分布等)。
                    如果需要,可以调用相应函数调用完成辅助动作。""")
            .build();
}

通过这样明确的人设信息注入,使得机器人在后续与乘客交互时,能够依据设定好的规则和职责来准确回答各类问题,当我们再次询问关于地铁信息时,机器人便知道根据自己的人设来回答我们的问题。

image.png

对话记忆

“大模型的对话记忆” 是一个相当关键的概念,它指的是在模型与用户展开交互式对话的过程中,拥有追踪、理解以及利用之前对话所产生的上下文信息的能力。得益于这样的记忆机制,大模型可不单单只能对当下即时输入的请求做出回应,它还能够凭借着之前交流时所记住的内容,在后续的对话里依据这些信息给出合适的响应。下边是Spring AI Alibaba基于内存存储的对话记忆实现,而且,我们还可以自行实现 ChatMemory,通过类似于借助文件、Redis 等途径保存对话。

private final ChatClient chatClient;
//对话记忆对象
ChatMemory chatMemory = new InMemoryChatMemory();
//对话记忆的唯一标识
String conversantId = UUID.randomUUID().toString();
//初始化ChatClient
public ChatGptController(ChatClient.Builder builder) {
    this.chatClient = builder.defaultSystem("") //人设信息
            .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) //对话记忆
            .build();
}

/**
 * 流式API
 */
@RequestMapping(value = "/ai/stream",produces = "text/html;charset=utf-8")
public Flux<String> fluxChat(@RequestParam("message")String message) {
    return chatClient.prompt().user(message)
            .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, conversantId)
            .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10)) //对话记忆
            .stream().content();
}

有了对话记忆后,机器人也就能够根据对话内容做出更合适的响应。

image.png

检索增强生成(RAG)

RAG 即 “Retrieval-Augmented Generation(检索增强生成)”,是将信息检索与语言生成相结合的技术。 原理上,先从外部知识源(如文档集等)检索与查询相关内容,再让语言生成模型基于检索所得生成回复。也就是说我们可以提供外部的知识源给大模型,让大模型结合我们给的知识源回答,这个知识源可以是文本,文档,甚至是Mysql,ES等数据库,有了这项本领便可以让机器人根据我们的具体业务知识进行回答。

有了RAG,回答的流程就变成了这样:

image.png

这里先说一下,上图的Embedding Model(即嵌入模型),它的工作是将文本、图像和视频转换为Vectors(向量);而向量可以简单理解为大模型所能理解的数据,就像计算机理解不了代码是什么意思,需要将代码转成二进制给它。vectorstores(向量数据库)即向量存储的地方。

从上图可以看出,RAG的阶段主要包括将prompt文本内容转为向量、从向量数据库检索内容、对检索后的文档进行重排和重写、最后调用大模型进行结果的生成。

这里我们在resource下添加一个resource/rag目录下名为rule.txt的文件充当机器人需要学习的知识源(该文件包含地铁相关制度知识,如乘车购票、行程变更、退票相关内容)。

image.png

CommandLineRunner类型的 Bean会将rule中的文本信息进行处理,先将其转换为向量形式存储到向量存储(VectorStore)中,之后基于给定的查询条件,在向量存储里进行相似性搜索。

@Bean
CommandLineRunner ingestTermOfServiceToVectorStore(EmbeddingModel embeddingModel, VectorStore vectorStore,
                                                   @Value("classpath:rag/text.txt") Resource termsOfServiceDocs) {

    return args -> {
        // 获取文档内容转成向量
        vectorStore.write(new TokenTextSplitter().transform(new TextReader(termsOfServiceDocs).read()));

        // 相关性搜索
        vectorStore.similaritySearch("rule").forEach(doc -> {
            logger.info("Similar Document: {}", doc.getContent());
        });
    };
}

在初始化ChatClient时再将存储完知识源的向量交给ChatClient

public ChatGptController(ChatClient.Builder builder, VectorStore vectorStore) {
    this.chatClient = builder.defaultSystem("") //人设信息
            .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory),//对话记忆
                    new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults())) //RAG
            .build();
}

这样,在我们询问有关退票信息时,机器人便会根据我们给的rule中的相关规则进行回答。

image.png

美化

最后,弄个稍微好看点的输入界面。

image.png 完整代码:Spring Ai Alibaba: 使用Spring Ai Alibaba完成地铁助手

结尾

案例到这里就告一段落啦!非常感谢各位小伙伴能够耐心地看到这里呀。在如今这个科技日新月异、知识不断更新迭代的时代,我们都在努力追赶着前沿技术的步伐,希望通过这样一个个具体的案例分享,能让大家在探索大模型应用的道路上少一些迷茫,多一些清晰的思路。
如果本文能够让你对大模型的理解有所增益,哪怕只是一点点启发,那都是我莫大的荣幸。还希望你能不吝留下你的赞赞哦,您的每一个点赞都是对我们继续分享知识、探索技术的鼓励与支持呢。咱们下期再见啦。