SpringAI搭建
官方文档 -> https://springdoc.cn/spring-ai/ ,网上教程版本问题太多,官网最新最标准。
1、SpringAI-RAG
1、总工程通过引入ai包管理版本
Spring AI 物料清单(BOM)声明了指定版本所有依赖的推荐版本。此为纯 BOM 版本,仅包含依赖管理,不涉及插件声明或 Spring/Spring Boot 的直接引用。可使用 Spring Boot Parent POM 或 Spring Boot 的 BOM(spring-boot-dependencies)来管理 Spring Boot 版本。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>2、根据是使用ollama自配模型(ollama)还是API接口模型(openai)还是qianwen等等,引入对应的包。
<!-- spring ai v1.0.0 start -->
# opendi模型starter
<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-pgvector</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
# ollama模型starter
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>spring:
ai:
openai:
api-key: sk-6c5de8d7e46845d79c348a556ce16b41
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat #还可以选择 deepseek-reasoner,深度思考模型
ollama:
base-url: http://117.72.57.125:11434
embedding:
options:
num-batch: 512
model: nomic-embed-text
mcp:
client:
request-timeout: 360s
sse:
connections:
mcp-server-csdn:
url: http://117.72.57.125:8103
mcp-server-weixin:
url: http://117.72.57.125:8102
mcp-server-talk:
url: http://117.72.57.125:8101首先通过OpenAiApi/OllamaAPi来定义调用的模型基本信息,然后通过OpenAiChatModel/OllamaChatModel包装OpenAiApi/OllamaAPi,最后通过model包装ChatCLient。通过ChatCLient调用对话接口。(也不用写OpenAiApi、OpenAiChatModel,可以自动装配)
@Slf4j
@Configuration
public class ChatClientConfig {
@Bean
public OpenAiApi openAiApi(@Value("${spring.ai.openai.chat.base-url}") String baseUrl,@Value("${spring.ai.openai.api-key}") String apikey) {
return OpenAiApi.builder().apiKey(apikey).baseUrl(baseUrl).build();
}
@Bean
public OpenAiChatModel openAiChatModel(OpenAiApi openAiApi){
return OpenAiChatModel.builder().openAiApi(openAiApi).build();
}
// 可以不要,会自动装配
@Bean
public ChatClient chatClient(OpenAiChatModel model) {
log.debug("初始化ChatClient...");
// ChatClient chatClient = ChatClient.create(model); // 简单创建实例
// 或使用builder创建更高级控制
return ChatClient
.builder(model) // 也可以使用ChatClient.Builder,利用build获取ChatClient对象
.defaultSystem("你是一个小混混,说话非常不着边际,是一个非常没有礼貌的人!")
.build();
}
// 可以不要,会自动装配
@Bean
public TokenTextSplitter tokenTextSplitter(){
return new TokenTextSplitter();
}
// 嵌入模型
@Bean
public PgVectorStore pgVectorStore(JdbcTemplate jdbcTemplate,
OllamaEmbeddingModel embeddingModel) {
return PgVectorStore.builder(jdbcTemplate,embeddingModel)
.build();
}
}2、SpringAI-MCP
1、MCP调用
1、工程环境配置
MCP分客户端和服务端,先在客户端项目中引入Pom文件
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId>
</dependency>配置MCP信息(stdio模式,有SSE/stdio),调用分为sse/stdio两种方式调用
1.stdio调用配置
一般是一个json文件指定mcp信息,获取到此mcp的json后(公用 MCP 服务:https://smithery.ai/、https://glama.ai/mcp/servers),配置到resource下。然后再yml中配置位置。
stdio模式是本地按命令执行
//mcp-servers-config.json
{
"mcpServers": {
"mcp-server-csdn": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-jar",
"/Users/fuzhengwei/Applications/apache-maven-3.8.4/repository/cn/bugstack/mcp/mcp-server-csdn/1.0.0/mcp-server-csdn-1.0.0.jar",
"--csdn.api.categories=Java场景面试宝典",
"--csdn.api.cookie=xxxxxx"
]
},
"mcp-server-weixin": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-jar",
"/Users/fuzhengwei/Applications/apache-maven-3.8.4/repository/cn/bugstack/mcp/mcp-server-weixin/1.0.0/mcp-server-weixin-1.0.0.jar"
]
}
}
}classpath:(项目路径)可以改为filepath:(部署服务器路径)

2.SSE调用配置
SSE模式是将mcp-server部署到服务器以SSE访问
spring:
ai:
mcp:
client:
request-timeout: 360s
sse:
connections:
mcp-server-csdn:
url: http://127.0.0.1:8101
mcp-server-weixin:
url: http://127.0.0.1:8102
# stdio:
# servers-configuration: classpath:/config/mcp-servers-config-4.json2、客户端装配调用
1.自动装配MCPTool
ToolCallbackProvider 会自动装配配置的MCPtool,先将tool注册到chatClient中即可用。
//Spring-MCP的BUG,会重复配置MCPtools导致报错----->multiname Fund
@Bean("syncMcpToolCallbackProvider")
public SyncMcpToolCallbackProvider syncMcpToolCallbackProvider(List<McpSyncClient> mcpClients) {
// mcpClients.remove(0);
// 用于记录 name 和其对应的 index
Map<String, Integer> nameToIndexMap = new HashMap<>();
// 用于记录重复的 index
Set<Integer> duplicateIndices = new HashSet<>();
// 遍历 mcpClients 列表
for (int i = 0; i < mcpClients.size(); i++) {
String name = mcpClients.get(i).getServerInfo().name();
if (nameToIndexMap.containsKey(name)) {
// 如果 name 已经存在,记录当前 index 为重复
duplicateIndices.add(i);
} else {
// 否则,记录 name 和 index
nameToIndexMap.put(name, i);
}
}
// 删除重复的元素,从后往前删除以避免影响索引
List<Integer> sortedIndices = new ArrayList<>(duplicateIndices);
sortedIndices.sort(Collections.reverseOrder());
for (int index : sortedIndices) {
mcpClients.remove(index);
}
return new SyncMcpToolCallbackProvider(mcpClients);
}
@Bean
public ChatClient chatClient(OpenAiChatModel model,@Qualifier("syncMcpToolCallbackProvider") ToolCallbackProvider toolCallbackProvider,ChatMemory memory) {
log.info("初始化ChatClient...");
// ChatClient chatClient = ChatClient.create(model); // 简单创建实例
// 或使用builder创建更高级控制
DefaultChatClientBuilder defaultChatClientBuilder = new DefaultChatClientBuilder(model, ObservationRegistry.NOOP, (ChatClientObservationConvention) null);
return defaultChatClientBuilder.defaultSystem("你是一个小混混,说话非常不着边际,是一个非常没有礼貌的人!")
.defaultTools(toolCallbackProvider)
.defaultAdvisors(new PromptChatMemoryAdvisor(memory))
.build();
// return ChatClient
// .builder(model) // 也可以使用ChatClient.Builder,利用build获取ChatClient对象
// .defaultSystem("你是一个小混混,说话非常不着边际,是一个非常没有礼貌的人!")
// .build();
}2.手动装配MCPTool
可以按需将对应的MCPtool,装配到对应的Client中去。
OpenAiApi openAiApi = OpenAiApi.builder()
.baseUrl("https://apis.itedus.cn")
.apiKey("sk-lIqVNiHon00O6veJ15Cc57DaF5Dd401f93B3A107B4B3677e")
.completionsPath("v1/chat/completions")
.embeddingsPath("v1/embeddings")
.build();
chatModel = OpenAiChatModel.builder()
.openAiApi(openAiApi)
.defaultOptions(OpenAiChatOptions.builder()
.model("gpt-4.1-mini")
=================================================手动装配===========================================================
.toolCallbacks(new SyncMcpToolCallbackProvider(stdioMcpClient(), sseMcpClient01(), sseMcpClient02()).getToolCallbacks())
=================================================手动装配===========================================================
.build())
.build();
=================================================手动装配===========================================================
public McpSyncClient stdioMcpClient() {
// based on
// https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem
var stdioParams = ServerParameters.builder("npx")
.args("-y", "@modelcontextprotocol/server-filesystem", "/Users/fuzhengwei/Desktop", "/Users/fuzhengwei/Desktop")
.build();
var mcpClient = McpClient.sync(new StdioClientTransport(stdioParams))
.requestTimeout(Duration.ofSeconds(10)).build();
var init = mcpClient.initialize();
System.out.println("Stdio MCP Initialized: " + init);
return mcpClient;
}
public McpSyncClient sseMcpClient01() {
HttpClientSseClientTransport sseClientTransport = HttpClientSseClientTransport.builder("http://192.168.1.108:8102").build();
McpSyncClient mcpSyncClient = McpClient.sync(sseClientTransport).requestTimeout(Duration.ofMinutes(180)).build();
var init = mcpSyncClient.initialize();
System.out.println("SSE MCP Initialized: " + init);
return mcpSyncClient;
}
=================================================手动装配===========================================================2、MCP服务搭建
实际就是预先定义好调用服务的执行逻辑。
以CSDN发帖为例。
1、工程基本配置
导入Pom文件
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
<!-- sse 模式需要配置 webflux -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
</dependency>application配置文件编写
spring:
application:
name: movie-mcp
ai:
mcp:
server:
name: ${spring.application.name}
version: 1.0.0
main:
banner-mode: off
# stdio 模式打开,sse 模式,注释掉。
# web-application-type: none
logging:
# stdio 模式打开,sse 模式,注释掉。
# pattern:
# console:
file:
name: data/log/${spring.application.name}.log
server:
port: 8101
servlet:
encoding:
charset: UTF-8
force: true
enabled: true2、MCP服务编写
@Tool(description = "发布文章到CSDN")
- 将普通 Java 方法暴露给 AI 模型作为可调用工具
- AI 模型可以根据方法描述自动决定何时调用该方法
@Slf4j
@Service
public class CSDNArticleService {
@Resource
private ICSDNPort port;
@Tool(description = "发布文章到CSDN")
public ArticleFunctionResponse saveArticle(ArticleFunctionRequest request) throws IOException {
log.info("CSDN发帖,标题:{} 内容:{} 标签:{}", request.getTitle(), request.getMarkdowncontent(), request.getTags());
return port.writeArticle(request);
}
}
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ArticleFunctionRequest {
@JsonProperty(required = true, value = "title")
@JsonPropertyDescription("文章标题")
private String title;
@JsonProperty(required = true, value = "markdowncontent")
@JsonPropertyDescription("文章内容")
private String markdowncontent;
@JsonProperty(required = true, value = "tags")
@JsonPropertyDescription("文章标签,英文逗号隔开")
private String tags;
@JsonProperty(required = true, value = "Description")
@JsonPropertyDescription("文章简述")
private String Description;
public String getContent() {
return MarkdownConverter.convertToHtml(markdowncontent);
}
}3、注册Bean
@Bean
public ToolCallbackProvider csdnTools(CSDNArticleService csdnArticleService) {
return MethodToolCallbackProvider.builder().toolObjects(csdnArticleService).build();
}4、打包部署
# 基础镜像,可以先执行 docker pull openjdk:17-jdk-slim
FROM openjdk:17-jdk-slim
# 作者
MAINTAINER yandddeat
# 配置
ENV PARAMS=""
# 时区
ENV TZ=PRC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 添加应用
ADD ./mcp-server-movie.jar /mcp-server-movie.jar
ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS /mcp-server-movie.jar $PARAMS"]version: '3.8'
services:
mcp-server-movie:
build: .
container_name: mcp-server-movie
ports:
- "8101:8101"
environment:
- TZ=Asia/Shanghai
- JAVA_OPTS=-Xmx512m -Xms256m
- PARAMS=--spring.profiles.active=prod
restart: unless-stopped
volumes:
- ./logs:/app/logs3、AiAgent组装
通过读取自定义好的多个AIAPI、知识库、MCP配置等,组装为一个个的Client1、Client2等,最后通过拖拉拽拼接Client组合成一个AIAgent工作流。
1.大模型自定义:
openaiAPI可以通过自定义 .apiKey(apiKey) 、.baseUrl(baseUrl),实现调用不同大模型
2.MCP工具自定义组装
可以通过OpenAiChatModel的 .toolCallbacks(new SyncMcpToolCallbackProvider(mcpSSEClient3(),sseMcpClient02()).getToolCallbacks()) 来自定义这个大模型能使用的MCP有哪些。
3.知识库自定义组装
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(MessageWindowChatMemory.builder()
.maxMessages(100)
.build()).build(),
.filterExpression(knowledge)
.build()),
SimpleLoggerAdvisor.builder().build(
)
4.完整示例
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class AiAgentTest {
private ChatModel chatModel;
private ChatClient chatClient;
@Resource
private PgVectorStore vectorStore;
public static final String CHAT_MEMORY_CONVERSATION_ID_KEY = "chat_memory_conversation_id";
public static final String CHAT_MEMORY_RETRIEVE_SIZE_KEY = "chat_memory_response_size";
// 根据自定配置读取后装配Client
@Before
public void armory(){
String baseUrl="https://api.deepseek.com";
String apiKey="sk-6c5de8d7e46845d79c348a556ce16b41";
String modelName="deepseek-chat";
// https://api.deepseek.com/v1/completions
String completionsPath="/v1/chat/completions";
String knowledge="knowledge == 'article-prompt-words'";
//deepseek暂未提供embedding模型
String embeddingsPath="";
OpenAiApi api = OpenAiApi.builder()
.apiKey(apiKey)
.baseUrl(baseUrl)
.completionsPath(completionsPath)
// .embeddingsPath("")
.build();
chatModel = OpenAiChatModel.builder()
.openAiApi(api)
.defaultOptions(OpenAiChatOptions.builder()
.model(modelName)
// 自定义MCP工具
.toolCallbacks(new SyncMcpToolCallbackProvider(mcpSSEClient3(),sseMcpClient02()).getToolCallbacks())
.build())
.build();
chatClient=ChatClient.builder(chatModel)
.defaultSystem("""
你是一个 AI Agent 智能体,可以根据用户输入信息生成文章,并发送到 CSDN 平台以及完成微信公众号消息通知,今天是 {current_date}。
你擅长使用Planning模式,帮助用户生成质量更高的文章。
你的规划应该包括以下几个方面:
1. 分析用户输入的内容,生成技术文章。
2. 提取,文章标题(需要含带技术点)、文章内容、文章标签(多个用英文逗号隔开)、文章简述(100字)将以上内容发布文章到CSDN
3. 获取发送到 CSDN 文章的 URL 地址。
4. 微信公众号消息通知,平台:CSDN、主题:为文章标题、描述:为文章简述、跳转地址:从发布文章到CSDN获取 URL 地址
""")
// .defaultToolCallbacks()
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(MessageWindowChatMemory.builder()
.maxMessages(100)
.build()).build(),
new RagAnswerAdvisor(vectorStore, SearchRequest.builder()
.topK(5)
.filterExpression(knowledge)
.build()),
SimpleLoggerAdvisor.builder().build()
)
.build();
}
// 根据配置协同调用多个Client组成Agent
@Test
public void test_chat_model_stream_01() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
Prompt prompt = Prompt.builder()
.messages(new UserMessage(
"""
有哪些工具可以使用
"""))
.build();
// 非流式,chatModel.call(prompt)
Flux<ChatResponse> stream = chatModel.stream(prompt);
stream.subscribe(
chatResponse -> {
AssistantMessage output = chatResponse.getResult().getOutput();
log.info("测试结果: {}", JSON.toJSONString(output));
},
Throwable::printStackTrace,
() -> {
countDownLatch.countDown();
System.out.println("Stream completed");
}
);
countDownLatch.await();
}
@Test
public void test_chat_model_call() {
Prompt prompt = Prompt.builder()
.messages(new UserMessage(
"""
有哪些工具可以使用
"""))
.build();
ChatResponse chatResponse = chatModel.call(prompt);
log.info("测试结果(call):{}", JSON.toJSONString(chatResponse));
}
@Test
public void test_02() {
String userInput = "严福建是谁";
System.out.println("\n>>> QUESTION: " + userInput);
System.out.println("\n>>> ASSISTANT: " + chatClient
.prompt(userInput)
.system(s -> s.param("current_date", LocalDate.now().toString()))
.call().content());
}
@Test
public void test_client03() {
ChatClient chatClient01 = ChatClient.builder(chatModel)
.defaultSystem("""
你是一个专业的AI提示词优化专家。请帮我优化以下prompt,并按照以下格式返回:
# Role: [角色名称]
## Profile
- language: [语言]
- description: [详细的角色描述]
- background: [角色背景]
- personality: [性格特征]
- expertise: [专业领域]
- target_audience: [目标用户群]
## Skills
1. [核心技能类别]
- [具体技能]: [简要说明]
- [具体技能]: [简要说明]
- [具体技能]: [简要说明]
- [具体技能]: [简要说明]
2. [辅助技能类别]
- [具体技能]: [简要说明]
- [具体技能]: [简要说明]
- [具体技能]: [简要说明]
- [具体技能]: [简要说明]
## Rules
1. [基本原则]:
- [具体规则]: [详细说明]
- [具体规则]: [详细说明]
- [具体规则]: [详细说明]
- [具体规则]: [详细说明]
2. [行为准则]:
- [具体规则]: [详细说明]
- [具体规则]: [详细说明]
- [具体规则]: [详细说明]
- [具体规则]: [详细说明]
3. [限制条件]:
- [具体限制]: [详细说明]
- [具体限制]: [详细说明]
- [具体限制]: [详细说明]
- [具体限制]: [详细说明]
## Workflows
- 目标: [明确目标]
- 步骤 1: [详细说明]
- 步骤 2: [详细说明]
- 步骤 3: [详细说明]
- 预期结果: [说明]
## Initialization
作为[角色名称],你必须遵守上述Rules,按照Workflows执行任务。
请基于以上模板,优化并扩展以下prompt,确保内容专业、完整且结构清晰,注意不要携带任何引导词或解释,不要使用代码块包围。
""")
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(
MessageWindowChatMemory.builder()
.maxMessages(100)
.build()
).build(),
new RagAnswerAdvisor(vectorStore, SearchRequest.builder()
.topK(5)
.filterExpression("knowledge == 'article-prompt-words'")
.build())
)
.defaultOptions(OpenAiChatOptions.builder()
.model("gpt-4.1")
.build())
.build();
String content = chatClient01
.prompt("生成一篇文章")
.system(s -> s.param("current_date", LocalDate.now().toString()))
.advisors(a -> a
.param(CHAT_MEMORY_CONVERSATION_ID_KEY, "chatId-101")
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
.call().content();
System.out.println("\n>>> ASSISTANT: " + content);
ChatClient chatClient02 = ChatClient.builder(chatModel)
.defaultSystem("""
你是一个 AI Agent 智能体,可以根据用户输入信息生成文章,并发送到 CSDN 平台以及完成微信公众号消息通知,今天是 {current_date}。
你擅长使用Planning模式,帮助用户生成质量更高的文章。
你的规划应该包括以下几个方面:
1. 分析用户输入的内容,生成技术文章。
2. 提取,文章标题(需要含带技术点)、文章内容、文章标签(多个用英文逗号隔开)、文章简述(100字)将以上内容发布文章到CSDN
3. 获取发送到 CSDN 文章的 URL 地址。
4. 微信公众号消息通知,平台:CSDN、主题:为文章标题、描述:为文章简述、跳转地址:为发布文章到CSDN获取 URL地址 CSDN文章链接 https 开头的地址。
""")
// .defaultTools(new SyncMcpToolCallbackProvider(sseMcpClient01(), sseMcpClient02()))
.defaultAdvisors(
PromptChatMemoryAdvisor.builder(
MessageWindowChatMemory.builder()
.maxMessages(100)
.build()
).build(),
new SimpleLoggerAdvisor()
)
.defaultOptions(OpenAiChatOptions.builder()
.model("gpt-4.1")
.build())
.build();
String userInput = "生成一篇文章,要求如下 \r\n" + content;
System.out.println("\n>>> QUESTION: " + userInput);
System.out.println("\n>>> ASSISTANT: " + chatClient02
.prompt(userInput)
.system(s -> s.param("current_date", LocalDate.now().toString()))
.advisors(a -> a
.param(CHAT_MEMORY_CONVERSATION_ID_KEY, "chatId-101")
.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
.call().content());
}
public McpSyncClient mcpSSEClient3(){
HttpClientSseClientTransport sseClientTransport = HttpClientSseClientTransport.builder("http://222.196.35.250:8103").build();
McpSyncClient mcpSyncClient = McpClient.sync(sseClientTransport).requestTimeout(Duration.ofMinutes(180)).build();
var init = mcpSyncClient.initialize();
System.out.println("SSE MCP Initialized: " + init);
return mcpSyncClient;
}
public McpSyncClient sseMcpClient02() {
HttpClientSseClientTransport sseClientTransport = HttpClientSseClientTransport.builder("http://222.196.35.250:8102").build();
McpSyncClient mcpSyncClient = McpClient.sync(sseClientTransport).requestTimeout(Duration.ofMinutes(180)).build();
var init = mcpSyncClient.initialize();
System.out.println("SSE MCP Initialized: " + init);
return mcpSyncClient;
}
public McpSyncClient sseMcpClient01() {
HttpClientSseClientTransport sseClientTransport = HttpClientSseClientTransport.builder("http://222.196.35.250:8101").build();
McpSyncClient mcpSyncClient = McpClient.sync(sseClientTransport).requestTimeout(Duration.ofMinutes(180)).build();
var init = mcpSyncClient.initialize();
System.out.println("SSE MCP Initialized: " + init);
return mcpSyncClient;
}
}踩坑总结:
1.版本问题
1.0.0是正式版本,1.0.0-M~系列,所对应的openai、ollama等starter都不一致。
解决:1.看官网更换正确artifactId。2.对应部分的操作参照官网配置。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>2.多模型源配置问题
1.embedding模型问题
使用VectorStore上传向量库时,若是openai模型,配置后会自动配置embedding模型,但是同时又装配了用的ollama embedding,会导致有两个embeddingmodel。这时需要自己进行手动装配VectorStore。
@Bean
public PgVectorStore pgVectorStore(JdbcTemplate jdbcTemplate, OllamaEmbeddingModel embeddingModel) {
return PgVectorStore.builder(jdbcTemplate, embeddingModel)
.vectorTableName("vector_store")
.dimensions(768)
.build();
}