Spring with AI (1): 起始集成——OpenAI接入
本文代码:https://github.com/JunTeamCom/ai-demo/tree/release-1.0
Spring with AI系列,只关注上层AI的应用程序(基于JAVA搭建),不关注底层的LLM原理、搭建等技术。
用不太精确的语言,描述Spring AI涉及的几个知识点,方便与非技术的人类沟通交流(BOSS/HR/PM):
- OpenAI接入:就是参考ChatGPT的接口文档,去接入千问、DeepSeek、ChatGPT等各家的在线AI程序
- RAG与向量数据库:
2.1 RAG:就是增强搜索,在大模型基础上再去读另一个数据库,实现实时联网的查询操作、补充AI的知识储备
2.2 向量数据库:就是给RAG使用的。能够把人类的自然语言,中文、英文等,变成计算机能够理解的快速查询的数据格式- MCP与Agent:
3.1 MCP就是给AI使用的,怎么接入的说明文档(协议)
3.2 Agent就是基于这个说明文档,做的执行相关操作的程序
1 代码生成(Spring Initializr)
依然是熟悉的 Start Spring:

仅仅集成需要的Spring Web + OpenAI即可。
然后创建Github仓库:

拖拽上传文件:


2 打开代码
本地克隆代码:
git clone https://github.com/JunTeamCom/ai-demo.git
git checkout release-1.0
然后用VSCode/IDEA等IDE打开工程。
直接启动运行会报错:

报错内容很明确,没有配置OpenAI的授权账密。

一种选择,是ChatGPT+Proxy的方式,进行使用;
另一种选择,是使用Qwen或DeepSeek账号配置(他们提供了和OpenAI兼容的API)。
本文选择了第二种策略,参考文档:
Qwen的OpenAI 接口兼容
相关配置示例(application.yaml):
# DeepSeek 配置,完全兼容openai配置
# spring:
# ai:
# openai:
# base-url: https://api.deepseek.com # DeepSeek的OpenAI式Endpoint
# api-key: ${DASHSCOPE_API_KEY}
# chat.options:
# model: deepseek-chat # 指定DeepSeek的模型名称
# 通义千问配置;api-key放到环境变量里,不要提交到Github上!!!
spring:
ai:
openai:
base-url: https://dashscope.aliyuncs.com/compatible-mode # Qwen的OpenAI式Endpoint
api-key: ${DASHSCOPE_API_KEY}
chat.options:
model: qwen3.5-plus
这样一个Web服务器,就正常启动了:

当然,如果要跑Chat,需要再定义Controller和相关处理逻辑。
3 第一次亲密Chat
自上而下构建Spring程序:
├───web
├───service
│ └───impl
└───model
3-1 Controller
注意的是:
- 通过
@RestController注解,实现一个Restful控制器类;通过@RequestBody注解,定义POST请求的报文 - 通过
@Autowired注解,引入Service类
package com.junteam.ai.demo.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.junteam.ai.demo.model.ChatAnswer;
import com.junteam.ai.demo.model.ChatQuestion;
import com.junteam.ai.demo.service.ChatService;
@RestController
@RequestMapping("/web")
public class WebController {
@Autowired
private ChatService chatService;
@PostMapping(path = "/ask", produces = "application/json")
public ChatAnswer ask(@RequestBody ChatQuestion chatQuestion) {
return chatService.ask(chatQuestion);
}
}
3-2 Service
接口类型定义:
package com.junteam.ai.demo.service;
import com.junteam.ai.demo.model.ChatAnswer;
import com.junteam.ai.demo.model.ChatQuestion;
public interface ChatService {
ChatAnswer ask(ChatQuestion question);
}
接口实现定义:
需要注意的是,通过@Service注解,将OpenAIChatServiceImpl实现类注入;
后续可以再实现一个LocalAIChatServiceImpl
package com.junteam.ai.demo.service.impl;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
import com.junteam.ai.demo.model.ChatAnswer;
import com.junteam.ai.demo.model.ChatQuestion;
import com.junteam.ai.demo.service.ChatService;
@Service
public class OpenAIChatServiceImpl implements ChatService {
private final ChatClient chatClient;
public OpenAIChatServiceImpl(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@Override
public ChatAnswer ask(ChatQuestion question) {
var answerText = chatClient.prompt()
.user(question.question())
.call()
.content();
return new ChatAnswer(answerText);
}
}
3-3 Model
实体类,全部使用record类型,简化操作;
当然也可以使用Lombok的@Data注解,尤其是小于JDK 14的情况下。
package com.junteam.ai.demo.model;
public record ChatQuestion(String question) {
}
package com.junteam.ai.demo.model;
public record ChatAnswer(String answer) {
}
4 运行结果
在GitBash中运行:
curl http://localhost:8080/web/ask \
-H "Content-Type: application/json" \
-d '{"question": "美国的首都是哪里?"}'
成功得到结果:
{"answer":"美国的首都是**华盛顿哥伦比亚特区**(Washington, D.C.),通常简称为**华盛顿**。"}
需要注意的是,LLM返回结果为Markdown格式。
浙公网安备 33010602011771号