SAP HANA 云
本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。 |
先决条件
-
需具备 SAP HANA Cloud 向量引擎账户——参考 SAP HANA Cloud 向量引擎试用账户创建指南 开通试用账户。
-
如需为 EmbeddingModel 生成存储在向量库中的向量,需配置 API Key。
自动配置
Spring AI 未提供 SAP Hana 向量存储专用模块。用户需使用 Spring AI 的标准向量存储模块 spring-ai-hanadb-store
在应用中自行配置。
请参考 “依赖管理” 部分将 Spring AI BOM 添加至构建文件。 |
请查阅 HanaCloudVectorStore 属性列表 以了解默认值及配置选项。
请参考 “Artifact 仓库” 部分将 Maven Central 和/或 Snapshot 仓库添加至构建文件。 |
此外,需配置 EmbeddingModel
Bean。详细信息请参阅 EmbeddingModel 部分。
HanaCloudVectorStore 配置属性
可通过以下 Spring Boot 配置属性自定义 SAP Hana 向量存储:
-
使用
spring.datasource.*
配置 Hana 数据源 -
使用
spring.ai.vectorstore.hanadb.*
配置 Hana 向量存储
属性 | 说明 | 默认值 |
---|---|---|
|
驱动类名 |
com.sap.db.jdbc.Driver |
|
数据源 URL |
- |
|
用户名 |
- |
|
密码 |
- |
|
TODO |
- |
|
TODO |
- |
|
是否初始化必要表结构 |
|
构建示例 RAG 应用
演示如何配置使用 SAP Hana Cloud 作为向量数据库,并利用 OpenAI 实现 RAG 模式的项目。
-
在 SAP Hana 数据库中创建
CRICKET_WORLD_CUP
表:
CREATE TABLE CRICKET_WORLD_CUP ( _ID VARCHAR2(255) PRIMARY KEY, CONTENT CLOB, EMBEDDING REAL_VECTOR(1536) )
-
在
pom.xml
中添加以下依赖:
可设置属性 <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
文件中添加以下属性:
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
创建继承 HanaVectorEntity
的 CricketWorldCup
实体类:
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;
}
-
创建实现
HanaVectorRepository
接口的CricketWorldCupRepository
:
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 Controller 类
CricketWorldCupHanaController
,并自动注入ChatModel
和VectorStore
依赖。在该 Controller 中实现以下 REST 端点:-
/ai/hana-vector-store/cricket-world-cup/purge-embeddings
- 清除向量存储中的所有向量嵌入。 -
/ai/hana-vector-store/cricket-world-cup/upload
- 上传Cricket_World_Cup.pdf
文件,将其数据以向量嵌入形式存储至 SAP Hana Cloud 向量数据库。 -
/ai/hana-vector-store/cricket-world-cup
- 在 SAP HANA 数据库中使用 Cosine_Similarity 实现 RAG(检索增强生成)。
-
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 向量存储支持不提供自动配置模块,你还需要在应用中提供 VectorStore
Bean,如下所示示例。
@Bean
public VectorStore hanaCloudVectorStore(CricketWorldCupRepository cricketWorldCupRepository,
EmbeddingModel embeddingModel) {
return HanaCloudVectorStore.builder(cricketWorldCupRepository, embeddingModel)
.tableName("CRICKET_WORLD_CUP")
.topK(1)
.build();
}
-
使用来自维基百科的
contextual
pdf 文件

通过在上一步中创建的 file-upload REST 端点上传此 PDF 文件。