大模型LLM之SpringAI:Web+AI(一)

官网:https://docs.spring.io/spring-ai/reference/index.html

版本:1.0.0-M1

Spring AI是一个旨在简化开发人员构建人工智能应用程序的项目的名称,它通过消除不必要的复杂性来实现这一目标。该项目受到了Python项目的启发,如LangChain和LlamaIndex,但它并不是后者的直接移植。Spring AI相信,下一个人工智能应用浪潮将不仅仅局限于Python开发人员,而是会普及到许多编程语言。

特点:

  • 对所有主要模型供应商(如OpenAI、Microsoft、Amazon、Google和Hugging Face)的支持。
  • 支持的模型类型包括聊天、文本转图像、音频转录、文字转语音等,未来还会支持更多。
  • 在所有模型中提供跨AI供应商的统一API。同时支持同步和流式API选项。也可以访问特定于模型的特性。
  • 将AI模型的输出映射到POJO
  • 支持所有主要向量数据库供应商(如Apache Cassandra、Azure向量搜索、Chroma、Milvus、MongoDB Atlas、Neo4j、Oracle、PostgreSQL/PGVector、PineCone、Qdrant、Redis和Weaviate)。

1、概念

1.1、模型Model

功能分类:文生文、文生图、图生文、声音转文字、文字转声音、文本(图片)转向量(嵌套)等

temperature温度:这个参数控制着生成的随机性。较高的温度值会增加文本的多样性和创造性,但可能会牺牲一些准确性或连贯性。
在这里插入图片描述

1.2、提示词prompts

提示词作为基于语言的输入的基础,指导人工智能模型生成具体的输出,但提示词不仅仅是对话框中输入的文本。ChatGPT 的 API 在提示中有多个文本输入,每个文本输入都被分配一个角色。例如,存在系统角色,它告诉模型如何行为并为交互设置上下文。还有用户角色,通常是来自用户的输入。

提示词模板:创建有效的提示涉及建立请求的上下文,并用特定于用户输入的值替换请求的部分。此过程使用传统的基于文本的模板引擎进行提示创建和管理。Spring AI 使用 OSS 库 StringTemplate 来实现此目的。

官网例子:

Tell me a {adjective} joke about {content}.

在 Spring AI 中,提示模板可以比作 Spring MVC 架构中的“视图”。提供了一个模型对象,通常为 java.util.Map ,用于填充模板中的占位符。

prompts后续会根据OpenAI的文档单独写一章

1.3、嵌入Embeddings

翻译的软件说是嵌入,即文本、图像或视频的数字表示形式,用于捕获输入之间的关系。**我更认为是文本和图像的向量化。**嵌入的工作原理是将文本、图像和视频转换为浮点数数组,称为向量。这些向量旨在捕获文本、图像和视频的含义。嵌入数组的长度称为向量的维数。

通过计算两段文本的向量表示之间的数值距离,应用程序可以确定用于生成嵌入向量的对象之间的相似性。

在这里插入图片描述
嵌入在检索增强生成 (RAG) 模式等实际应用中尤为重要。它们使数据能够表示为语义空间中的点,语义空间类似于欧几里得几何的二维空间,但维度更高。这意味着,就像欧几里得几何中平面上的点根据其坐标可以近或远一样,在语义空间中,点的接近反映了含义的相似性。在这个多维空间中,关于相似主题的句子位置更近,就像图表上彼此靠近的点一样。这种接近性有助于文本分类、语义搜索甚至产品推荐等任务,因为它允许人工智能根据它们在这个扩展的语义景观中的“位置”来辨别和分组相关概念。

RAG、agent(智能体)等后续文章会讲,期待后续吧!

1.4、令牌Token

在英语中,一个token大致相当于一个单词的 75%。自己买的API通过token进行消耗,狭义理解为使用OpenAI的流量。在托管 AI 模型的上下文中,您的费用由使用的令牌数量决定。输入和输出都会影响总令牌计数。

1.5、结构化输出

一般输出以String形式出现,也可以是JSON,JSON不一定准。
在这里插入图片描述

结构化输出转换采用精心设计的提示,通常需要与模型进行多次交互才能实现所需的格式设置。

1.6、AI整合私有数据

一般三种方法:

  1. 使用私有数据微调模型,修改模型权重等
  2. 将私有数据转换为向量存储到向量数据库,在对话时进行查询检索,即检索增强生成(RAG)
  3. 函数调用,直接调用OpenAI、Ollama等

推荐使用2和3

1.7、检索增强生成 Retrieval Augmented Generation(RAG)

该方法涉及批处理样式编程模型,其中作业从文档中读取非结构化数据,对其进行转换,然后将其写入向量数据库。概括地说,这是一个 ETL(提取、转换和加载)管道。向量数据库用于RAG技术的检索部分。

作为将非结构化数据加载到矢量数据库的一部分,最重要的转换之一是将原始文档拆分为较小的部分。将原始文档拆分为较小部分的过程有两个重要步骤:

  1. 将文档拆分为多个部分,同时保留内容的语义边界。例如,对于包含段落和表格的文档,应避免将文档拆分在段落或表格的中间。对于代码,请避免在方法实现的过程中拆分代码。
  2. 将文档的各个部分进一步拆分为多个部分,其大小仅占 AI 模型令牌限制的一小部分。

RAG 的下一阶段是处理用户输入。当 AI 模型要回答用户的问题时,该问题和所有“相似”的文档片段都会被放入发送到 AI 模型的提示中。这就是使用向量数据库的原因。它非常擅长寻找类似的内容。
在这里插入图片描述

1.8、函数调用

Spring AI 大大简化了您需要编写以支持函数调用的代码。它为您处理函数调用对话。您可以将函数提供@Bean ,然后在提示选项中提供函数的 bean 名称以激活该函数。此外,您可以在单个提示中定义和引用多个函数。
在这里插入图片描述

  1. 执行聊天请求,发送功能定义信息。后者提供 namedescription (例如解释模型何时应调用函数)和 input parameters (例如函数的输入参数架构)。
  2. 当模型决定调用函数时,它将使用输入参数调用函数,并将输出返回给模型。
  3. Spring AI 为您处理此对话。它将函数调用分派给相应的函数,并将结果返回给模型。
  4. 模型可以执行多个函数调用来检索它需要的所有信息。
  5. 一旦获取了所需的所有信息,模型将生成响应。

2、使用

2.1、开始

要使用SpringBoot3,OpenJDK17或以上

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.8</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ai</groupId>
    <artifactId>spring-ai</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-ai</name>
    <description>spring-ai</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0-M1</spring-ai.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--需要ollama时候再使用,否则在自动装配情况下启动报错,提示chatClient.Builder需要指定一个大模型,在不使用自动装配时可以不注释-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.ai</groupId>-->
<!--            <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>

2.2、API

AI Model API分为普通Model和StreamingModel。StreamingModel是以流式返回响应,类似与AI回答一个个字显示而不是一下全部显示出来。
在这里插入图片描述
支持 OpenAI、Microsoft、Amazon、Google、Amazon Bedrock、Hugging Face 等的 AI 模型。支持 14 个向量数据库。Spring AI 可以轻松地让 AI 模型调用 POJO java.util.Function 对象。

2.2.1、Chat Client (聊天客户端 )API

SpringAI提供了一个流畅的 API,用于与 AI 模型进行通信。它支持同步和反应式编程模型(最简单、最基础的API)。AI 模型处理两种主要类型的消息:用户消息(来自用户的直接输入)和系统消息(由系统生成以指导对话)。这些消息通常包含占位符,这些占位符在运行时根据用户输入进行替换,以自定义 AI 模型对用户输入的响应。还可以指定提示选项,例如要使用的 AI 模型的名称和控制生成输出的随机性或创造力设置。

ChatClient 使用 ChatClient.Builder 对象创建的。您可以为任何 ChatModel Spring Boot 自动配置获取自动配置 ChatClient.Builder 的实例,也可以以编程方式创建一个实例。

官网代码

package com.ai.springai.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Title: MyController
 * @Author lizhe
 * @Package com.ai.springai.controller
 * @Date 2024/8/15 下午11:33
 * @description: 测试chat client API
 */

@RestController
public class MyController {
    private final ChatClient chatClient;
    public MyController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }
    @GetMapping("/ai")
    String generation(String userInput) {
        return this.chatClient.prompt()
                .user(userInput)
                .call()
                .content();
    }


}

将函数改造一下(调用OpenAI接口)

package com.ai.springai.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Title: MyController
 * @Author lizhe
 * @Package com.ai.springai.controller
 * @Date 2024/8/15 下午11:33
 * @description: 测试chat client API
 */

@RestController
public class MyController {
    private final ChatClient chatClient;
    public MyController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }
    @GetMapping("/ai")
    String generation(@RequestParam(value = "message", defaultValue = "Tell me a joke") String userInput) {
        return this.chatClient.prompt()
                .user(userInput)
                .call()
                .content();
    }


}

配置文件

spring:
  application:
    name: spring-ai

  ai:
  	#api-key需要自己买,base-url买的时候会告诉你
    openai:
      api-key: sk-***********************************
      base-url: https://*********


测试结果
在这里插入图片描述
如果要使用多个大模型进行聊天,需要把Spring的自动注入关闭。可以通过设置属性 spring.ai.chat.client.enabled=false 来禁用 ChatClient.Builder 自动配置。

修改配置文件

spring:
  application:
    name: spring-ai

  ai:
  	#api-key需要自己买,base-url买的时候会告诉你
    openai:
      api-key: sk-***********************************
      base-url: https://*********
    chat:
      client:
        enabled: false


修改代码

package com.ai.springai.controller;

import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Title: MyController
 * @Author lizhe
 * @Package com.ai.springai.controller
 * @Date 2024/8/15 下午11:33
 * @description: 测试chat client API
 */

@RestController
public class MyController {
    private final ChatClient chatClient;
	// 在使用多模型可能会用到,后续遇到再写
    // private final ChatClient chatClientOther;
    @Autowired
    public MyController(OpenAiChatModel openAiChatModel) {
        this.chatClient = ChatClient.create(openAiChatModel);
    }



    @GetMapping("/ai")
    String generation(@RequestParam(value = "message", defaultValue = "Tell me a joke") String userInput) {
        return this.chatClient.prompt()
                .user(userInput)
                .call()
                .content();
    }


}

测试结果
在这里插入图片描述

客户端返回响应

AI 模型的响应是由 ChatResponse 类型定义的丰富结构。它包括有关如何生成响应的元数据,还可以包含多个响应(称为“生成”),每个响应都有自己的元数据。元数据包括用于创建响应的标记数(每个标记大约为单词的 3/4)。此信息很重要,因为托管 AI 模型根据每个请求使用的令牌数量收费。

    @GetMapping("/getChatResponse")
    String index() {
        ChatResponse chatResponse = chatClient.prompt()
                .user("Tell me a joke")
                .call()
                .chatResponse();
        return chatResponse.toString();
    }

返回结果

ChatResponse [metadata={ @type: org.springframework.ai.openai.metadata.OpenAiChatResponseMetadata, id: chatcmpl-DsGla7joCoVCOc5ZiBoUDbFbNZYnA, usage: Usage[completionTokens=17, promptTokens=11, totalTokens=28], rateLimit: { @type: org.springframework.ai.openai.metadata.OpenAiRateLimit, requestsLimit: null, requestsRemaining: null, requestsReset: null, tokensLimit: null; tokensRemaining: null; tokensReset: null } }, generations=[Generation{assistantMessage=AssistantMessage{content='Sure thing! What kind of shoes do ninjas wear? Sneakers!', properties={finishReason=STOP, role=ASSISTANT, id=chatcmpl-DsGla7joCoVCOc5ZiBoUDbFbNZYnA, messageType=ASSISTANT}, messageType=ASSISTANT}, chatGenerationMetadata=org.springframework.ai.chat.metadata.ChatGenerationMetadata$1@5e07f126}]]
返回实体对象
    @GetMapping("/getEntity")
    ActorFilms getEntity() {
        ActorFilms actorFilms = chatClient.prompt()
                .user("Generate the filmography for a random actor.")
                .call()
                .entity(ActorFilms.class);
        return actorFilms;
    }

结果
在这里插入图片描述

流式响应**

使 stream 您可以获得异步响应

@GetMapping("/getStreamOutput")
    Flux<String> getStreamOutput() {
        Flux<String> output = chatClient.prompt()
                .user("讲一个不少于500字的通话故事")
                .stream()
                .content();
        return output;
    }

生成的结果文字是通过流的方式一点点的呈现,对于生成大量文字的用户体验感更好。
在这里插入图片描述

使用默认值

@Configuration 类中使用默认系统文本创建 ChatClient 可简化运行时代码。通过设置默认值,您只需在调用 ChatClient 时指定用户文本,而无需为运行时代码路径中的每个请求设置系统文本。

系统默认文本可以提前设置输入给模型的角色、任务、方式等

config

package com.ai.springai.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Title: AIConfig
 * @Author lizhe
 * @Package com.ai.springai.config
 * @Date 2024/8/16 下午11:10
 * @description: 配置系统默认文本
 */
@Configuration
public class AIConfig {
    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("你是一名专业的中国法律顾问,回答问题时要引用中国的法律条文")
                .build();
    }
}

contrller

package com.ai.springai.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
 * @Title: TestDefaultText
 * @Author lizhe
 * @Package com.ai.springai.controller
 * @Date 2024/8/16 下午11:14
 * @description: 测试默认系统文本
 */
@RestController
public class TestDefaultTextController {
    private final ChatClient chatClient;
    TestDefaultTextController(ChatClient chatClient){
        this.chatClient = chatClient;
    }
    @GetMapping("/defaultText")
    public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "单位克扣工资违法吗") String message) {
        return Map.of("completion", chatClient.prompt().user(message).call().content());
    }

}

结果
在这里插入图片描述
在默认文本中加入参数,在参数中可以设置AI的不同角色

config

package com.ai.springai.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Title: AIConfig
 * @Author lizhe
 * @Package com.ai.springai.config
 * @Date 2024/8/16 下午11:10
 * @description: 配置系统默认文本
 */
@Configuration
public class AIConfig {
    @Bean
    ChatClient chatClient(ChatClient.Builder builder) {
        return builder.defaultSystem("你是一名专业的中国{profession}")
                .build();
    }
}

controller

package com.ai.springai.controller;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
 * @Title: TestDefaultText
 * @Author lizhe
 * @Package com.ai.springai.controller
 * @Date 2024/8/16 下午11:14
 * @description: 测试默认系统文本
 */
@RestController
public class TestDefaultTextController {
    private final ChatClient chatClient;
    TestDefaultTextController(ChatClient chatClient){
        this.chatClient = chatClient;
    }
    @GetMapping("/defaultText")
    public Map<String, String> completion(@RequestParam(value = "message", defaultValue = "关于环保说出你的专业看法") String message,String profession) {
        return Map.of("completion", chatClient.prompt().system(systemSpec -> systemSpec.param("profession",profession)).user(message).call().content());
    }

}

农民角色
在这里插入图片描述
环保主义者角色
在这里插入图片描述

聊天记忆

ChatMemory 代表聊天对话历史记录的存储。它提供了向对话添加消息、从对话中检索消息以及清除对话历史记录的方法。

有两种实现 InMemoryChatMemoryCassandraChatMemory 它们为聊天对话历史记录、内存中和 time-to-live 相应的持久化提供存储。

使用 ChatMemory 接口通过对话历史记录向提示提供建议,这些对话历史记录在如何将内存添加到提示的详细信息上有所不同。

  • MessageChatMemoryAdvisor :检索内存并将其作为消息集合添加到提示符中
  • PromptChatMemoryAdvisor :检索内存并将其添加到提示的系统文本中。
  • VectorStoreChatMemoryAdvisor :构造函数 VectorStoreChatMemoryAdvisor(VectorStore vectorStore, String defaultConversationId, int chatHistoryWindowSize) 允许您指定要从中检索聊天记录的 VectorStore、唯一对话 ID、要检索的聊天记录的大小(以令牌大小表示)。

官网代码

import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY;
import static org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor.CHAT_MEMORY_RETRIEVE_SIZE_KEY;

@Service
public class CustomerSupportAssistant {

    private final ChatClient chatClient;

    public CustomerSupportAssistant(ChatClient.Builder builder, VectorStore vectorStore, ChatMemory chatMemory) {

    this.chatClient = builder
            .defaultSystem("""
                    You are a customer chat support agent of an airline named "Funnair".", Respond in a friendly,
                    helpful, and joyful manner.

                    Before providing information about a booking or cancelling a booking, you MUST always
                    get the following information from the user: booking number, customer first name and last name.

                    Before changing a booking you MUST ensure it is permitted by the terms.

                    If there is a charge for the change, you MUST ask the user to consent before proceeding.
                    """)
            .defaultAdvisors(
                    new PromptChatMemoryAdvisor(chatMemory),
                    // new MessageChatMemoryAdvisor(chatMemory), // CHAT MEMORY
                    new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()),
                    new LoggingAdvisor()) // RAG
            .defaultFunctions("getBookingDetails", "changeBooking", "cancelBooking") // FUNCTION CALLING
            .build();
}

public Flux<String> chat(String chatId, String userMessageContent) {

    return this.chatClient.prompt()
            .user(userMessageContent)
            .advisors(a -> a
                    .param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                    .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
            .stream().content();
    }
}

但是我粘贴后,导包后还是报红,不知道咋回事,估计后续官网会更新吧
在这里插入图片描述
源码中的参数并没有LoggingAdvisor
在这里插入图片描述

日志记录

SimpleLoggerAdvisor 用于记录 ChatClient 的 requestresponse 数据。这对于调试和监视 AI 交互非常有用。

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空指针异常Null_Point_Ex

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值