SpringAI简单使用

SpringAI

介绍

SpringAI

了解原因

  1. AI趋势
  2. Spring生态

特性

  1. 支持大量模型OpenAI, Microsoft, Amazon, Google, and Huggingface.
  2. 支持大量模型输出:问答,做图,语音处理等
  3. 方便使用的已封装API接口:用户交互api,模型交互api
  4. 支持大量的向量数据库及相关交互
  5. 支持模型函数调用
  6. SpringBoot自动配置支持
  7. ETL(Extract, Transform,and Load) framework for Data Engineering

使用案例

  1. 基于大模型的问答
  2. 基于大模型的文生图

基于大模型的文档问答

实现方式

  1. Fine Tuning

  2. Prompt Stuffing

  3. Function Calling

Fine Tuning

将目标数据与模型一起训练

优点:

  • 与模型一体,不用额外处理

缺点:

  • 重新训练非常耗费资源

Prompt Stuffing

Prompt填充,代表技术:Retrieval Augmented Generation (RAG),先从向量数据库中获取相关信息然后填充到prompt中一起发送给大模型,从而达到大模型引用外部知识目的

将文档存入向量数据库这一步骤很重要,大致分为两步

  1. 将文档按照语义边界进行分割。避免将同一语义信息从中间进行分割(例如不要把一个function从中间分割了)
  2. 将文档进一步分割使其大小符合模型token限制

优点:

  • 方便灵活

缺点:

  • 受prompt限制,受相似度查询准确率限制,受大模型稳定性限制

Function Calling

大模型在训练完成后知识库便固定了正常无法获取外部数据,函数调用技术允许提供外部数据给大模型使用。

优点:

  • 灵活

缺点:

  • 不是所有大模型都有函数接口

SpringAI实现基于大模型的文档问答示例

环境信息

  • 框架:SpringAI 0.8.1
  • 大模型:LLAMA3-7B(ollama本地部署)
  • 检索方式:RAG - Prompt Stuffing
  • 向量数据库:Neo4j

核心代码

没啥特别的基本都有starter,按文档往pom和配置文件加下参数就能跑起来

这里更多的难点是在如何更好与大模型交互,在于prompt的编写

出现的问题记录

由于是SpringAI早期开发版本所以会有各种各样问题

1.Neo4j的向量维度限制

当前Neo4j的配置类Neo4jVectorStoreConfig源码将向量维度限制在了2048,而llama3模型为4096

1
2
3
4
5
public Builder withEmbeddingDimension(int newEmbeddingDimension) {
Assert.isTrue(newEmbeddingDimension >= 1 && newEmbeddingDimension <= 2048, "Dimension has to be withing the boundaries 1 and 2048 (inclusively)");
this.embeddingDimension = newEmbeddingDimension;
return this;
}

临时性的解决措施:通过反射修改bean的属性

2.Pinecone数据库相似度查询

当使用Pinecone作为向量数据库时,进行相似度查询时,相似度度量方式有特定限制,采用cosine度量可能会导致查询不到目标信息。这是PineconeVectorStorec.class源码,注意其中score部分限制

1
2
3
4
5
6
7
8
9
10
11
12
return queryResponse.getMatchesList()
.stream()
.filter(scoredVector -> scoredVector.getScore() >= request.getSimilarityThreshold()) // here my socres are negative numbers
.map(scoredVector -> {
var id = scoredVector.getId();
Struct metadataStruct = scoredVector.getMetadata();
var content = metadataStruct.getFieldsOrThrow(CONTENT_FIELD_NAME).getStringValue();
Map<String, Object> metadata = extractMetadata(metadataStruct);
metadata.put(DISTANCE_METADATA_FIELD_NAME, 1 - scoredVector.getScore());
return new Document(id, content, metadata);
})
.toList();

解决办法: 社区反馈会在1.0.0-M1版本支持更多度量方式,或者自己重写(然后去PR)

完整Demo

demo