SAP HANA Cloud

Prerequisites

Auto-configuration

Spring AI 不提供用于 SAP Hana 矢量存储器的专用模块。用户需要在使用 Spring AI 中的 SAP Hana 矢量存储器的标准矢量存储器模块 spring-ai-hanadb-store 的应用程序中提供自己的配置。

Spring AI does not provide a dedicated module for SAP Hana vector store. Users are expected to provide their own configuration in the applications using the standard vector store module for SAP Hana vector store in Spring AI - spring-ai-hanadb-store.

  1. 参见 Dependency Management 部分,将 Spring AI BOM 添加到你的构建文件中。

Refer to the Dependency Management section to add the Spring AI BOM to your build file.

请查看矢量存储器的 HanaCloudVectorStore Properties 列表,了解默认值和配置选项。

Please have a look at the list of hanacloudvectorstore-properties for the vector store to learn about the default values and configuration options.

将Maven Central和/或Snapshot存储库添加到您的构建文件中,请参阅 Artifact Repositories 部分。

Refer to the Artifact Repositories section to add Maven Central and/or Snapshot Repositories to your build file.

此外,您还需要一个配置好的 EmbeddingModel bean。有关更多信息,请参阅 EmbeddingModel 部分。

Additionally, you will need a configured EmbeddingModel bean. Refer to the EmbeddingModel section for more information.

HanaCloudVectorStore Properties

您可以在 Spring Boot 配置中使用以下属性来自定义 SAP Hana 矢量存储器。它使用 spring.datasource. properties to configure the Hana datasource and the spring.ai.vectorstore.hanadb. 属性来配置 Hana 矢量存储器。

You can use the following properties in your Spring Boot configuration to customize the SAP Hana vector store. It uses spring.datasource. properties to configure the Hana datasource and the spring.ai.vectorstore.hanadb. properties to configure the Hana vector store.

Property Description Default value

spring.datasource.driver-class-name

Driver class name

com.sap.db.jdbc.Driver

spring.datasource.url

Hana Datasource URL

-

spring.datasource.username

Hana datasource username

-

spring.datasource.password

Hana datasource password

-

spring.ai.vectorstore.hanadb.top-k

TODO

-

spring.ai.vectorstore.hanadb.table-name

TODO

-

spring.ai.vectorstore.hanadb.initialize-schema

whether to initialize the required schema

false

Build a Sample RAG application

演示如何设置一个项目,该项目使用 SAP Hana Cloud 作为矢量数据库并利用 OpenAI 实现 RAG 模式。

Shows how to setup a project that uses SAP Hana Cloud as the vector DB and leverage OpenAI to implement RAG pattern

  • 在 SAP Hana DB 中创建表 CRICKET_WORLD_CUP

  • Create a table CRICKET_WORLD_CUP in SAP Hana DB:

CREATE TABLE CRICKET_WORLD_CUP (
    _ID VARCHAR2(255) PRIMARY KEY,
    CONTENT CLOB,
    EMBEDDING REAL_VECTOR(1536)
)
  • 在您的 pom.xml 中添加以下依赖项:

  • Add the following dependencies in your pom.xml

您可以将属性 spring-ai-version 设置为 <spring-ai-version>1.0.0-SNAPSHOT</spring-ai-version>

You may set the property spring-ai-version as <spring-ai-version>1.0.0-SNAPSHOT</spring-ai-version>:

<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>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-hana</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>
</dependency>
  • application.properties 文件中添加以下属性:

  • Add the following properties in application.properties file:

spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.embedding.options.model=text-embedding-ada-002

spring.datasource.driver-class-name=com.sap.db.jdbc.Driver
spring.datasource.url=${HANA_DATASOURCE_URL}
spring.datasource.username=${HANA_DATASOURCE_USERNAME}
spring.datasource.password=${HANA_DATASOURCE_PASSWORD}

spring.ai.vectorstore.hanadb.tableName=CRICKET_WORLD_CUP
spring.ai.vectorstore.hanadb.topK=3

Create an Entity class named CricketWorldCup that extends from HanaVectorEntity:

package com.interviewpedia.spring.ai.hana;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.jackson.Jacksonized;
import org.springframework.ai.vectorstore.hanadb.HanaVectorEntity;

@Entity
@Table(name = "CRICKET_WORLD_CUP")
@Data
@Jacksonized
@NoArgsConstructor
public class CricketWorldCup extends HanaVectorEntity {
    @Column(name = "content")
    private String content;
}
  • 创建一个名为 CricketWorldCupRepositoryRepository ,它实现了 HanaVectorRepository 接口:

  • Create a Repository named CricketWorldCupRepository that implements HanaVectorRepository interface:

package com.interviewpedia.spring.ai.hana;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import org.springframework.ai.vectorstore.hanadb.HanaVectorRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class CricketWorldCupRepository implements HanaVectorRepository<CricketWorldCup> {
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    @Transactional
    public void save(String tableName, String id, String embedding, String content) {
        String sql = String.format("""
                INSERT INTO %s (_ID, EMBEDDING, CONTENT)
                VALUES(:_id, TO_REAL_VECTOR(:embedding), :content)
                """, tableName);

		this.entityManager.createNativeQuery(sql)
                .setParameter("_id", id)
                .setParameter("embedding", embedding)
                .setParameter("content", content)
                .executeUpdate();
    }

    @Override
    @Transactional
    public int deleteEmbeddingsById(String tableName, List<String> idList) {
        String sql = String.format("""
                DELETE FROM %s WHERE _ID IN (:ids)
                """, tableName);

        return this.entityManager.createNativeQuery(sql)
                .setParameter("ids", idList)
                .executeUpdate();
    }

    @Override
    @Transactional
    public int deleteAllEmbeddings(String tableName) {
        String sql = String.format("""
                DELETE FROM %s
                """, tableName);

        return this.entityManager.createNativeQuery(sql).executeUpdate();
    }

    @Override
    public List<CricketWorldCup> cosineSimilaritySearch(String tableName, int topK, String queryEmbedding) {
        String sql = String.format("""
                SELECT TOP :topK * FROM %s
                ORDER BY COSINE_SIMILARITY(EMBEDDING, TO_REAL_VECTOR(:queryEmbedding)) DESC
                """, tableName);

        return this.entityManager.createNativeQuery(sql, CricketWorldCup.class)
                .setParameter("topK", topK)
                .setParameter("queryEmbedding", queryEmbedding)
                .getResultList();
    }
}
  • 现在,创建 REST 控制器类 CricketWorldCupHanaController ,并将 ChatModelVectorStore 自动注入为依赖项。在此控制器类中,创建以下 REST 端点:

    • /ai/hana-vector-store/cricket-world-cup/purge-embeddings - 从矢量存储器中清除所有嵌入。

    • /ai/hana-vector-store/cricket-world-cup/purge-embeddings - to purge all the embeddings from the Vector Store

    • /ai/hana-vector-store/cricket-world-cup/upload - 上传 Cricket_World_Cup.pdf,以便其数据作为嵌入存储在 SAP Hana Cloud 矢量数据库中。

    • /ai/hana-vector-store/cricket-world-cup/upload - to upload the Cricket_World_Cup.pdf so that its data gets stored in SAP Hana Cloud Vector DB as embeddings

    • /ai/hana-vector-store/cricket-world-cup - 使用 Cosine_Similarity in SAP Hana DB 实现 RAG

    • /ai/hana-vector-store/cricket-world-cup - to implement RAG using Cosine_Similarity in SAP Hana DB

  • Now, create a REST Controller class CricketWorldCupHanaController, and autowire ChatModel and VectorStore as dependencies In this controller class, create the following REST endpoints:

    • /ai/hana-vector-store/cricket-world-cup/purge-embeddings - 从矢量存储器中清除所有嵌入。

    • /ai/hana-vector-store/cricket-world-cup/purge-embeddings - to purge all the embeddings from the Vector Store

    • /ai/hana-vector-store/cricket-world-cup/upload - 上传 Cricket_World_Cup.pdf,以便其数据作为嵌入存储在 SAP Hana Cloud 矢量数据库中。

    • /ai/hana-vector-store/cricket-world-cup/upload - to upload the Cricket_World_Cup.pdf so that its data gets stored in SAP Hana Cloud Vector DB as embeddings

    • /ai/hana-vector-store/cricket-world-cup - 使用 Cosine_Similarity in SAP Hana DB 实现 RAG

    • /ai/hana-vector-store/cricket-world-cup - to implement RAG using Cosine_Similarity in SAP Hana DB

package com.interviewpedia.spring.ai.hana;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.hanadb.HanaCloudVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@RestController
@Slf4j
public class CricketWorldCupHanaController {
    private final VectorStore hanaCloudVectorStore;
    private final ChatModel chatModel;

    @Autowired
    public CricketWorldCupHanaController(ChatModel chatModel, VectorStore hanaCloudVectorStore) {
        this.chatModel = chatModel;
        this.hanaCloudVectorStore = hanaCloudVectorStore;
    }

    @PostMapping("/ai/hana-vector-store/cricket-world-cup/purge-embeddings")
    public ResponseEntity<String> purgeEmbeddings() {
        int deleteCount = ((HanaCloudVectorStore) this.hanaCloudVectorStore).purgeEmbeddings();
        log.info("{} embeddings purged from CRICKET_WORLD_CUP table in Hana DB", deleteCount);
        return ResponseEntity.ok().body(String.format("%d embeddings purged from CRICKET_WORLD_CUP table in Hana DB", deleteCount));
    }

    @PostMapping("/ai/hana-vector-store/cricket-world-cup/upload")
    public ResponseEntity<String> handleFileUpload(@RequestParam("pdf") MultipartFile file) throws IOException {
        Resource pdf = file.getResource();
        Supplier<List<Document>> reader = new PagePdfDocumentReader(pdf);
        Function<List<Document>, List<Document>> splitter = new TokenTextSplitter();
        List<Document> documents = splitter.apply(reader.get());
        log.info("{} documents created from pdf file: {}", documents.size(), pdf.getFilename());
		this.hanaCloudVectorStore.accept(documents);
        return ResponseEntity.ok().body(String.format("%d documents created from pdf file: %s",
                documents.size(), pdf.getFilename()));
    }

    @GetMapping("/ai/hana-vector-store/cricket-world-cup")
    public Map<String, String> hanaVectorStoreSearch(@RequestParam(value = "message") String message) {
        var documents = this.hanaCloudVectorStore.similaritySearch(message);
        var inlined = documents.stream().map(Document::getText).collect(Collectors.joining(System.lineSeparator()));
        var similarDocsMessage = new SystemPromptTemplate("Based on the following: {documents}")
                .createMessage(Map.of("documents", inlined));

        var userMessage = new UserMessage(message);
        Prompt prompt = new Prompt(List.of(similarDocsMessage, userMessage));
        String generation = this.chatModel.call(prompt).getResult().getOutput().getContent();
        log.info("Generation: {}", generation);
        return Map.of("generation", generation);
    }
}

由于 HanaDB 矢量存储器支持不提供自动配置模块,您还需要在应用程序中提供矢量存储器 bean,示例如下。

Since HanaDB vector store support does not provide the autoconfiguration module, you also need to provide the vector store bean in your application, as shown below, as an example.

@Bean
public VectorStore hanaCloudVectorStore(CricketWorldCupRepository cricketWorldCupRepository,
        EmbeddingModel embeddingModel) {

    return HanaCloudVectorStore.builder(cricketWorldCupRepository, embeddingModel)
        .tableName("CRICKET_WORLD_CUP")
        .topK(1)
        .build();
}
  • 使用来自维基百科的 contextual PDF 文件。

  • Use a contextual pdf file from wikipedia

转到 wikipediadownload Cricket World Cup 页面作为 PDF 文件。

Go to wikipedia and download Cricket World Cup page as a PDF file.

wikipedia

使用我们在上一步中创建的文件上传 REST 端点上传此 PDF 文件。

Upload this PDF file using the file-upload REST endpoint that we created in the previous step.