Spring AI:RAG&函数调用
本文介绍了两种基于SpringAI的LLM增强技术:RAG和函数调用。RAG通过将外部知识注入大语言模型,解决了模型知识陈旧、缺乏私有数据等问题,其工作流程包括数据准备(文档加载、分割、嵌入、存储)和实时查询(问题嵌入、相似搜索、增强提示)。函数调用则赋予LLM执行动作的能力,通过与Java代码交互实现实时信息获取和外部系统操作。文章提供了基于Spring官方文档的RAG实现示例和天气查询的函数调
1. 为什么需要RAG?
标准的大语言模型(LLM)的知识仅限于其训练数据,这意味着:
-
知识陈旧:它们不知道训练日期之后发生的任何事情。
-
缺乏私有知识:它们不了解你公司的内部文档、产品手册或数据库内容。
-
可能产生幻觉:在不确定的领域,模型可能会编造听起来合理但实际上是错误的答案。
RAG通过将外部的、实时的、私有的知识注入到LLM的提示中,完美地解决了这些问题。它将LLM从一个“封闭的知识库”转变为一个能够利用特定数据进行推理的“开放式推理引擎”。
2. RAG在Spring AI中的工作流程
整个流程可以分解为两个阶段:
数据准备阶段(离线处理):
-
加载文档(Load):从各种来源(如PDF, MarkDown, JSON, 网站)加载你的私有数据。
-
分割(Split):将长文档分割成更小的、语义完整的文本块(Chunks)。
-
嵌入(Embed):使用
EmbeddingClient
将每个文本块转换成一个向量(一长串数字),这个向量代表了文本的语义含义。 -
存储(Store):将文本块和其对应的向量存储在一个专门的向量数据库中。
查询阶段(实时处理):
-
用户提问:用户提出一个问题。
-
嵌入问题:使用相同的
EmbeddingClient
将用户的问题也转换成一个向量。 -
相似性搜索(Search):在向量数据库中,使用问题的向量去搜索最相似的文本块向量(即内容最相关的知识片段)。
-
增强提示(Augment):将搜索到的相关知识片段与用户的原始问题结合,形成一个内容丰富的“增强提示”。
-
调用模型(Generate):将这个增强后的提示发送给
ChatClient
。由于模型现在有了相关的上下文,它能够生成一个基于你私有数据的、更准确、更具事实性的回答。
3. 代码示例:构建一个简单的RAG应用
假设我们要构建一个基于Spring官方文档的问答机器人。
依赖 (pom.xml): 你需要添加一个向量数据库的starter。为了简单起见,我们使用一个内存中的向量数据库。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-vector-store-simple</artifactId>
</dependency>
实现步骤:
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
@Service
public class RagService {
@Value("classpath:/docs/spring-documentation.pdf") // 假设你有一个PDF文档
private Resource pdfResource;
@Autowired
private VectorStore vectorStore; // Spring AI会自动注入配置好的VectorStore
// 1. 数据准备:在应用启动时加载PDF并存入向量数据库
@PostConstruct
public void init() {
// 使用Tika解析PDF
var pdfReader = new PagePdfDocumentReader(pdfResource);
var textSplitter = new TokenTextSplitter();
// 将文档分割、嵌入并存储
vectorStore.accept(textSplitter.apply(pdfReader.get()));
}
// 2. 查询阶段
public String answer(String question) {
// 在向量数据库中搜索相关文档
List<Document> similarDocuments = vectorStore.similaritySearch(question);
// 将文档内容组装成上下文
String context = similarDocuments.stream()
.map(Document::getContent)
.collect(Collectors.joining(System.lineSeparator()));
// 创建提示模板
String promptTemplate = """
根据以下信息回答问题:
---
{context}
---
问题: {question}
""";
PromptTemplate template = new PromptTemplate(promptTemplate);
Prompt prompt = template.create(Map.of("context", context, "question", question));
// 调用AI模型
return chatClient.call(prompt).getResult().getOutput().getContent();
}
}
通过这个服务,你的应用现在可以回答关于spring-documentation.pdf
的具体问题了,而不是依赖LLM的通用知识。
深度解析:函数调用 (Function Calling)
1. 为什么需要函数调用?
函数调用赋予了LLM“执行动作”的能力。它允许模型请求调用你的Java代码来完成特定任务,例如:
-
获取实时信息:查询天气、股票价格、航班信息。
-
与外部系统交互:在数据库中下单、发送邮件、调用另一个微服务。
-
执行复杂计算:执行精确的数学运算。
这极大地扩展了LLM的应用范围,使其从一个聊天机器人转变为一个智能的“调度中心”或“代理”(Agent)。
2. 函数调用在Spring AI中的工作流程
-
定义函数:在你的Java代码中创建一个
Bean
,它是一个标准的java.util.function.Function
。这个函数的输入和输出最好是结构化的POJO。 -
描述函数:在调用
ChatClient
时,通过ChatOptions
将函数的名称和描述传递给模型。这个描述至关重要,模型会根据这个描述来决定何时以及如何使用这个函数。 -
模型决策:当你向模型提问时(例如“上海今天的天气怎么样?”),模型会分析你的问题和它所知道的函数。如果它认为
getWeather
函数可以回答这个问题,它不会直接回答,而是返回一个特殊的指令,告诉Spring AI:“请调用名为getWeather
的函数,参数是{ "location": "上海" }
”。 -
Spring AI执行:Spring AI框架会自动拦截这个指令,查找名为
getWeather
的Bean,然后用模型提供的参数去执行这个Java函数。 -
返回结果:函数执行的结果(例如,天气数据对象)会被发送回给模型。
-
最终回答:模型接收到函数的返回结果后,会用自然语言组织并生成最终的用户答案(例如“上海今天晴天,气温25摄氏度。”)。
3. 代码示例:查询天气
定义一个Function Bean:
import java.util.function.Function;
@Configuration
public class AppConfiguration {
// 定义函数的输入和输出类型
public record Request(String location) {}
public record Response(double temp, String unit) {}
@Bean
@Description("获取指定城市的天气信息") // 这个描述非常重要!模型会读取它
public Function<Request, Response> getWeather() {
return request -> {
// 这里应该是调用真实天气API的逻辑
// 为简化示例,我们返回一个固定的值
System.out.println("正在调用天气API获取 " + request.location() + " 的天气...");
return new Response(25.0, "摄氏度");
};
}
}
在Service中调用:
@Service
public class WeatherService {
@Autowired
private ChatClient chatClient;
public String getWeather(String message) {
ChatResponse response = chatClient.call(new Prompt(
message,
OpenAiChatOptions.builder()
.withFunction("getWeather") // 告诉模型可以使用这个函数
.build()
));
// 检查模型是否请求调用函数
if (response.getResult().getOutput().getToolCalls() != null) {
// Spring AI会自动处理函数的调用和结果返回,对于开发者来说通常是透明的
// 最终会返回一个综合了函数结果的自然语言回答
}
return response.getResult().getOutput().getContent();
}
}
现在,当你调用weatherService.getWeather("帮我查一下伦敦的天气")
时,getWeather
这个Java Bean就会被自动触发,并将结果用于生成最终的回答。
总结
通过掌握RAG和函数调用,你可以利用Spring AI构建出远超简单问答机器人的复杂应用。
-
RAG 让你的应用变得博学,能够基于特定领域的私有数据提供精准回答。
-
函数调用让你的应用变得能干,能够与外部世界交互并执行实际任务。
将这两者结合起来,你就可以构建出真正能够解决复杂业务问题的、高度智能化的企业级Java应用。

为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。
更多推荐
所有评论(0)