前言
AI 大模型浪潮来势汹汹,各大厂商的API 调用费用也随之水涨船高。对于我们这些靠代码吃饭的程序员来说,每一次 API 调用都在悄悄掏空钱包。与其持续给资本"充值",不如动手在本地搭建一个属于自己的 AI 大模型------省钱的同时,还能获得更多的掌控权。
特别注意: 本文同时涵盖了本地大模型对话 和 RAG 功能 的实现,篇幅较长。如果你只想快速实现本地大模型的基础对话功能,可以直接跳过所有标记为 "支持 RAG" 的内容和章节,这样能大幅缩短阅读和实践时间。
先知道
前置条件很简单:一台 Linux 服务器 + Docker 环境。这些老生常谈的基础设施我们就不多说了。重点来了------本次的绝对主角是开源框架 Ollama,它将成为我们在本地运行大模型的"引擎"。
Ollama 是什么?
Ollama 是一个开源框架,它让你可以轻松地在本地机器(甚至是个人电脑)上运行开源的大语言模型,比如 Llama、Mistral、Neural Chat 等。简单说,它就是一个"大模型运行器"。
Ollama 让在本地跑大模型就像运行一个普通的 Docker 容器一样简单。
官网:Ollama
Pgvector 是什么?
pgvector 是 PostgreSQL 关系数据库的一个扩展插件,它给 PostgreSQL 添加了向量数据类型和向量搜索能力。简单说,它让 PostgreSQL 能够存储、索引和查询高维向量。支持向量的存储、相似度计算和快速向量搜索。你可以用 SQL 语句直接进行向量操作,无需额外的向量数据库。但它不是向量数据库,好处是可以同时存储结构化数据和向量数据,集成度更高
RAG 是什么?
RAG 是 Retrieval-Augmented Generation 的缩写,中文叫"检索增强生成"。它是一种将信息检索和大模型生成相结合的技术。
简单分三步:
- 检索阶段 --- 用户提问时,系统从已有的文档库(就是上面 Pgvevtor 向量库中存储的数据)中检索出最相关的信息
- 增强阶段 --- 把检索到的相关文档作为上下文,与用户的问题一起发送给大模型
- 生成阶段 --- 大模型基于这些检索到的信息生成回答,而不是凭空捏造
部署
部署 Ollama 平台
注: Ollama 运行大模型需要大量计算资源。根据模型规模不同,CPU 和内存需求差异很大。即便是最轻量的模型(Qwen 3:0.6b),也需要 2 核 CPU 起步(不然运行后拉爆服务器卡死重启)。建议提前评估服务器配置,预留充足空间哦。
1: 下载 ollama docker 镜像
bash
docker pull ollama/ollama:0.12.6
2: 如果命令拉去失败了,也可本地上传镜像包到服务器
镜像包博主已经打包完成可自行下载:链接: pan.baidu.com/s/1y0GQidqv... 提取码: e7ij
上传到自己服务器后在当前文件夹中执行命令构建
css
docker load -i ollama_ollama_0.12.6-amd64.tar.gz

构建完成,可以看出镜像本身都 3.45G 了,非常吃资源

3: 构建 ollama 容器
使用 docker compose 的方式构建,自定义文件夹并创建 docker-compose.yml

yml
version: '3'
services:
ollama:
image: ollama/ollama:0.12.6
container_name: ollama
restart: unless-stopped
ports:
- "11434:11434"
也可在 github 仓库下载对应文件:GitHub - RemainderTime/docker-dosc: 存储docker相关命令文件
在当前文件夹中执行命令构建容器
docker compose up -d
执行完成查看容器

如果容器启动失败,检查下自己服务器内存或者 cpu 是否足够,或者暂时关闭其他启动容器再试试
部署 Pgvector 数据库(RAG 支持)
1: 拉去镜像
bash
docker pull pgvector/pgvector:pg18
2: 手动安装可参考上面 ollama 教程
链接: pan.baidu.com/s/1sc6BQ6od... 提取码: x2tw
3: 构建 pgvector 容器
创建对应文件以及文件夹


pgvector 文件夹中
data: 用于存储持久化数据库数据
sql:存储启动时需要初始化的 sql 语句,如没有则为空内容就行

docker compose 方式运行容器
yml
version: '3'
services:
vector_db:
image: pgvector/pgvector:pg18
container_name: vector_db
restart: always
environment:
- POSTGRES_USER=postgres #用户名
- POSTGRES_PASSWORD=postgres #密码
- POSTGRES_DB=springai
- PGPASSWORD=postgres
volumes:
- ./pgvector/data:/var/lib/postgresql/data # 添加这行,保证数据持久化
- ./pgvector/sql/init.sql:/docker-entrypoint-initdb.d/init.sql
logging:
options:
max-size: 10m
max-file: "3"
ports:
- '5432:5432'
healthcheck:
test: "pg_isready -U postgres -d vector_store"
interval: 2s
timeout: 20s
retries: 10
networks:
- my-network
networks:
my-network:
driver: bridge
也可在 github 仓库下载对应文件:GitHub - RemainderTime/docker-dosc: 存储docker相关命令文件
4: 构建命令与上面 ollama 同理
注:记得在自己服务器上面开发对应的访问端口
5: 连接数据库
这里使用 DBeaver 工具软件连接,可在官网下载:DBeaver Community | Free Open-Source Database Management Tool

填入 docker-compose 文件中自定义的账号密码连接即可

安装大模型
大模型需要进入 ollama 容器中安装,所以前面说 ollama 是大模型的"引擎"呢
安装 LLM 大语言模型
1: 选取大模型
打开浏览器访问 ollama 官网查询
目前开源大模型最好的是 meta 公司的 Llama 模型,但很遗憾排名前三的大模型 openai 的 chatgpt、Anthropic 的 claude 以及 google 的 gemini 都是闭源的。
我们国内也是有优秀的开源大模型:deepseek、阿里的 qwen 大模型都是很不错的


这里选用阿里 Qwen 3:0.6b 模型。"b" 代表 billion(10 亿参数),0.6b 即 6 亿参数,是个轻量级模型。参数数越多模型能力越强,但对资源的要求也越高。0.6b 参数量相对较小,资源占用低,非常适合本地部署学习。
2: 命令进入 ollama 容器中
bash
docker exec -it <容器名或容器ID> /bin/bash
如果你安装了 portainer 容器,可直接通过访问其面板并找到 ollama 容器进入
如果你是 mac 电脑,可安装 docker desktop 桌面软件进入容器内部
3: 安装大模型
拉取命令和 docker 命令差不多,只是把 docker 换成了 ollama
ollama pull qwen3:0.6b

拉取成功后,查看已有的大模型命令
ollama list

安装Embedding 嵌入模型(RAG 支持)
1: 选取安装模型
我们选取的嵌入模型是:nomic-embed-text

安装流程和上面大模型同理
arduino
ollama pull nomic-embed-text
2: 嵌入模型作用
- 专门用来将文本转换成向量(embedding)的模型。你可以理解为一个"文本向量化工具"
它就是为了配合上面安装的pgvector 数据库一起使用的
- pgvector 虽然能存储和搜索向量,但向量本身需要由某个模型生成。nomic-embed-text 就是这个生成向量的模型。它相对轻量级,专业度高,特别适合用在本地 RAG 系统中。
程序实践
自此准备工作都完成了,现在正式进入 code 时间
完整源码获取:spring-ai-lab/ai-ollama-rag at master · RemainderTime/spring-ai-lab
本文只给出 ai 相关核心代码,完整功能可查看源码
注:使用 Spring AI 需要 Spring Boot 3.3+ 和 JDK 17+ 的支持。
配置文件 pom.xml
1: 根目录 pom.xml 依赖引入
spring-ai-lab 是一个多模型集成项目。我们在根目录 pom.xml 中统一配置 Spring AI 的版本控制,以便后续在该仓库中集成和管理各类 AI 模型相关的源码。
xml
<properties>
<java.version>17</java.version>
<spring-ai-version>1.1.0</spring-ai-version>
</properties>
<dependencyManagement>
<dependencies>
<!-- 引入 Spring AI bom 统一版本 -->
<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>
2:子目录 pom.xml 引入依赖
xml
<dependencies>
<!-- 用于ai rag 解析上传的数据文件(RAG支持)-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
<!-- 用于ai rag 存储数据向量数据库(RAG支持)-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store</artifactId>
</dependency>
<!-- 集成ollama依赖-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
</dependencies>
如果不需要实现 RAG 功能,那么只需要 ollama 依赖就可以了
3: 设置配置文件 application.yml
由于源码中使用的 nacos 作为配置中心,如果你未使用那就直接放在项目 yml 中即可
yml
spring:
servlet:
multipart:
enabled: true
max-file-size: 10MB # 单个文件最大大小
max-request-size: 50MB # 整个请求最大大小(多文件上传)
datasource:
pgvector:
driver-class-name: org.postgresql.Driver
username: postgres
password: postgres
url: jdbc:postgresql://127.0.0.1:5432/postgres
type: com.zaxxer.hikari.HikariDataSource
hikari:
pool-name: HikariCP
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
ai:
ollama:
base-url: http://127.0.0.1:11434
chat:
options:
model: deepseek-r1:1.5b
temperature: 0.7
embedding:
options:
num-batch: 512
model: nomic-embed-text
配置内容也很清晰明了,记得替换为自己服务器 ip 地址
pgvector:配置pgvector 数据库连接( rag 支持)
ollama: 配置 ollama 平台地址
chat: 配置大模型默认名称
embedding:配置嵌入式模型(rag 支持)
配置类实现(RAG 支持)
1: 配置类实现(rag 支持)
pgvector 数据库连接配置类 PgVectorDataSourceConfig.class
java
@Configuration
public class PgVectorDataSourceConfig {
@Bean(name = "pgvectorDataSource")
@ConfigurationProperties(prefix = "spring.datasource.pgvector")
public DataSource pgvectorDataSource() {
return new DriverManagerDataSource();
}
@Bean
public JdbcTemplate pgvectorJdbcTemplate(@Qualifier("pgvectorDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
OllamaChatConfig.class
这个配置类负责融合 Embedding 模型和 pgvector 数据库,建立从文本向量化到向量存储、检索的完整链路。
java
@Configuration
public class OllamaChatConfig {
/**
* 注入文本分割器-重点注意配置参数
* @return
*/
@Bean
public TokenTextSplitter tokenTextSplitter() {
//短文本配置
return new TokenTextSplitter(256, 5, 10, 500, true);
}
/**
TextSplitter splitter = new TokenTextSplitter(
500, // chunkSize: 每块最多 500 tokens
100, // minChunkSizeChars: 至少 100 个字符才分块
50, // minChunkLengthToEmbed: 小于 50 个字符不进行 embedding
1000, // maxNumChunks: 最多 1000 块
true // keepSeparator: 保留句号、换行符
);
*/
/**
* 使用 Ollama API 将文本等对象转换成向量
* 存储在 内存中 或者简单实现
* 小数据量、临时存储
* @param ollamaApi
* @return
*/
@Bean("ollamaSimpleVectorStore")
public SimpleVectorStore vectorStore(OllamaApi ollamaApi) {
OllamaEmbeddingModel embeddingModel = OllamaEmbeddingModel
.builder()
.ollamaApi(ollamaApi)
.defaultOptions(OllamaEmbeddingOptions.builder().model("nomic-embed-text").build())
.build();
return SimpleVectorStore.builder(embeddingModel).build();
}
/**
* 使用 Ollama API 生成向量
* 存储在 PostgreSQL 中持久化
* 支持向量搜索、分页等数据库操作
* @param ollamaApi
* @param jdbcTemplate
* @return
*/
@Bean("ollamaPgVectorStore")
public PgVectorStore pgVectorStore(OllamaApi ollamaApi, JdbcTemplate jdbcTemplate) {
OllamaEmbeddingModel embeddingModel = OllamaEmbeddingModel
.builder()
.ollamaApi(ollamaApi)
.defaultOptions(OllamaEmbeddingOptions.builder().model("nomic-embed-text").build())
.build();
return PgVectorStore.builder(jdbcTemplate, embeddingModel)
.vectorTableName("vector_store_ollama_deepseek")
.build();
}
}
2: 创建向量表 vector_store_ollama_deepseek
这里创建向量表存储 RAG 知识库。表名以 deepseek 结尾,如果按本文使用 Qwen 模型,可改为 qwen 结尾。这样做是为了在同时部署多个大模型时,将不同模型的知识库隔离到不同表中。本文仅实现单模型场景,多模型配置不赘述。
sql
create table public.vector_store_ollama_deepseek (
id UUID primary key default gen_random_uuid(),
content TEXT not null,
metadata JSONB,
embedding VECTOR(768)
)
特别注意:VECTOR(768) 中的 768 是向量维度。不同 Embedding 模型输出的维度不同------nomic-embed-text 是 768 维,其他模型可能是 384 维、1024 维等。表结构中的维度数必须与实际使用的 Embedding 模型相对应。

Ollama 大模型对话实现
实现 2 个接口
直接对话:大模型会把回答的内容一次性通过接口返回
流式对话:大模型会逐步将内容返回(和市面上使用的 ai 大模型效果一样)
知识点:流式对话使用的 SSE 协议这里采用 SSE(Server-Sent Events)协议实现流式对话。SSE 是服务器推送技术,允许服务器主动向客户端持续发送数据,而不需要客户端反复请求。这样用户可以实时看到大模型的逐字生成过程,而不是等待完整响应后才显示。(因此前端也需要实现 SSE 功能)
java
@RestController
@RequestMapping("/ai/ollama")
public class OllamaController {
@Resource
private OllamaChatModel ollamaChatModel;
/**
* ollama 直接应答
*/
@RequestMapping(value = "/generate", method = RequestMethod.GET)
public ChatResponse generate(@RequestParam("model") String model, @RequestParam("message") String message) {
return ollamaChatModel.call(new Prompt(message, OllamaChatOptions.builder()
.model(model)
.build()));
}
/**
* ollama 流式应答
* @return
*/
@GetMapping(value = "/generate_stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> generateStream(
@RequestParam String model,
@RequestParam String message) {
return ollamaChatModel.stream(
new Prompt(message, OllamaChatOptions.builder().model(model).build())
)
.map(chatResponse -> {
String text = chatResponse.getResults()
.stream()
.filter(r -> r.getOutput().getText() != null)
.map(r -> r.getOutput().getText())
.findFirst()
.orElse("");
return ServerSentEvent.<String>builder()
.data(text)
.build();
})
.concatWith(Flux.just(
ServerSentEvent.<String>builder()
.data("[DONE]")
.build()
))
.doOnError(Throwable::printStackTrace);
}
Ollama 大模型实现 RAG 功能(RAG支持)
java
@RestController
@RequestMapping("/ai/ollama")
public class OllamaController {
@Resource
private OllamaChatModel ollamaChatModel;
@Resource
private PgVectorStore pgVectorStore;
/**
* ollama 带rag的直接应答
*/
@RequestMapping(value = "/generateCallRag", method = RequestMethod.GET, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public ChatResponse generateCallRag(@RequestParam("model") String model, @RequestParam("ragTag") String ragTag, @RequestParam("message") String message) {
return ollamaChatModel.call(new Prompt(
this.createSystemMessage(message, ragTag),
OllamaChatOptions.builder()
.model(model)
.build()));
}
/**
* ollama 带rag的流式应答
*/
@RequestMapping(value = "/generate_stream_rag", method = RequestMethod.GET)
@Transactional
public Flux<ServerSentEvent<String>> generateStreamRag(@RequestParam("model") String model, @RequestParam("ragTag") String ragTag, @RequestParam("message") String message) {
return ollamaChatModel.stream(
new Prompt(
this.createSystemMessage(message, ragTag),
OllamaChatOptions.builder()
.model(model)
.build())
).map(chatResponse -> {
String text = chatResponse.getResults()
.stream()
.map(r -> r.getOutput().getText() != null ? r.getOutput().getText() : "")
.findFirst()
.orElse("");
return ServerSentEvent.<String>builder()
.data(text)
.build();
})
.concatWith(Flux.just(
ServerSentEvent.<String>builder()
.data("[DONE]")
.build()
))
.doOnError(Throwable::printStackTrace);
}
/**
* 获取向量知识库提示
*/
private ArrayList<Message> createSystemMessage(String message, String ragTag) {
String SYSTEM_PROMPT = """
Use the information from the DOCUMENTS section to provide accurate answers but act as if you knew this information innately.
If unsure, simply state that you don't know.
Another thing you need to note is that your reply must be in Chinese!
DOCUMENTS:
{documents}
""";
SearchRequest request = SearchRequest.builder()
.query(message)
.topK(5)
.filterExpression("knowledge == '" + ragTag + "'")
.build();
List<Document> documents = pgVectorStore.similaritySearch(request);
String documentsCollectors = documents.stream().map(Document::getText).collect(Collectors.joining());
Message ragMessage = new SystemPromptTemplate(SYSTEM_PROMPT).createMessage(Map.of("documents", documentsCollectors));
ArrayList<Message> messages = new ArrayList<>();
messages.add(ragMessage);
messages.add(new UserMessage(message));
return messages;
}
}
实现知识库文件上传(RAG 支持)
需要实现将需要存储的知识库文件容器处理成向量并保存在数据库中
java
@Slf4j
@RestController
@RequestMapping("/ai/ollama/rag")
public class RAGController {
@Resource
private TokenTextSplitter tokenTextSplitter;
@Resource(name = "ollamaPgVectorStore")
private PgVectorStore pgVectorStore;
@Resource
private RedisTemplate<String, String> redisTemplate;
/**
* 查询 rag 标签列表
*
* @return
*/
@RequestMapping(value = "query_rag_tag_list", method = RequestMethod.GET)
public List<String> queryRagTagList() {
List<String> range = redisTemplate.opsForList().range("ai:rag:tags", 0, -1);
return range;
}
/**
* 上传知识库文件,并传入标签
*
* @param ragTag
* @param files
* @return
*/
@RequestMapping(value = "file/upload", method = RequestMethod.POST, headers = "content-type=multipart/form-data")
public String uploadFile(@RequestParam String ragTag, @RequestParam("file") List<MultipartFile> files) {
log.info("上传知识库开始 {}", ragTag);
for (MultipartFile file : files) {
TikaDocumentReader documentReader = new TikaDocumentReader(file.getResource());
List<Document> documents = documentReader.get();
List<Document> documentSplitterList = tokenTextSplitter.apply(documents);
// 添加知识库标签
documents.forEach(doc -> doc.getMetadata().put("knowledge", ragTag));
documentSplitterList.forEach(doc -> doc.getMetadata().put("knowledge", ragTag));
pgVectorStore.accept(documentSplitterList);
// 添加知识库记录
List<String> elements = redisTemplate.opsForList().range("ai:rag:tags", 0, -1);
assert elements != null;
if (!elements.contains(ragTag)) {
redisTemplate.opsForList().leftPush("ai:rag:tags", ragTag);
}
}
log.info("上传知识库完成 {}", ragTag);
return "success";
}
}
自此就可以运行项目通过 postman 工具调用接口测试了
实现简单前端对话功能/上传知识库功能
这里我们引入thymeleaf 依赖实现后端项目访问 html 页面
xml
<!--访问templates文件下的静态资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在资源文件家中创建 chat.html 文件

实现 WebCotroller.class 访问页面
java
@Controller
public class WebController {
@GetMapping("/chat")
public String chatPage() {
return "chat"; // 对应 templates/chat.html
}
}
chat.html 中的内容过长这里就不贴出来了,可访问上面给的源码地址获取,博主也是交给 AI 实现的。
访问前端页面
简单对话
访问地址:http://localhost:10001/chat

如果许久未返回内容,请查看自己服务器 CPU 是不是"爆炸"了
上传知识库文件

上传成功查看数据库数据

实现 rag 对话

总结
本地部署大模型通过 Ollama 实现,既能节省持续的 API 调用费用,又能获得数据隐私和完全自主控制;配合 RAG 技术,通过 Embedding 模型和 pgvector 向量库,让大模型基于你的私有文档数据生成答案,彻底解决模型幻觉问题。但这一切的前提是服务器有充足的 CPU、内存和磁盘空间------即便最轻量的模型也需要 2 核 CPU 起步,资源不足会导致性能严重下降甚至崩溃。