以 Smart Assistant 当前阶段为背景,复盘一个 Java RAG 知识库从 0 搭建、持续重构,到文档导入、向量检索和 SSE 流式问答的落地过程。
在这里插入图片描述

写在前面

调用一次大模型接口,做出一个能够回答问题的页面并不难。

真正困难的是:如何把用户上传的文档稳定地转化为知识,如何管理文档版本,如何保证向量模型与数据库维度一致,如何让回答能够追溯引用来源,又如何处理异步任务失败、流式连接中断和服务异常退出。

Smart Assistant 就是在解决这些问题。

它是一个基于 Java 17、Spring Boot、PostgreSQL、pgvector 和 Ollama 构建的 RAG 知识库项目。当前阶段已经打通从文档进入系统到模型生成答案的核心链路。

这个项目还有一个不同寻常的地方:

从需求拆解、架构设计、代码实现、问题排查到验证迭代,我一直借助 GPT 持续推进这个项目。

这里的“持续推进”,不是向大模型提交一句需求,然后等待它一次性生成整个项目。

Smart Assistant 是从 0 开始创建的。从 Maven 多模块骨架、数据库模型到文档处理和 RAG 问答,都是在持续开发中逐步建立起来的。随着代码和规则不断增加,GPT需要反复阅读项目的当前状态,理解前面已经确定的设计,在多个模块之间继续实现,根据真实日志修正判断,并保证后续阶段不破坏已有成果。

Smart Assistant 仍然处在持续开发过程中。本文记录的不是项目终局,而是一个重要的阶段性成果:RAG 核心后端链路已经建立,并开始具备可运行、可维护、可追溯和可继续演进的工程基础。

因此,这篇文章既会展示当前阶段的技术架构和核心实现,也会说明 GPT是如何持续参与这套系统建设的。


一、当前阶段实现了什么

Smart Assistant 不是一个把问题直接转发给大模型的聊天壳,而是一套围绕知识生命周期构建的后端系统。

它的核心闭环可以概括为:

知识文件

解析与清洗

版本化与分块

Embedding 向量化

pgvector 语义检索

RAG 上下文构建

大模型生成

SSE 流式输出

会话、回答与引用追溯

围绕这条主链路,项目进一步解决了四类工程问题:

领域 解决的问题
知识处理 文件如何安全进入系统,并转化成稳定的可检索分块
检索生成 问题如何找到正确知识,并约束模型基于依据回答
交互体验 长耗时模型生成如何以流式方式稳定返回
工程可靠性 异步任务、事务、并发、异常和历史状态如何收口

本文所说的“可落地”,也不是宣称系统已经达到最终生产形态,而是指当前阶段已经不再停留在概念和演示层:真实数据能够进入知识链路,关键异常有明确收口,技术边界能够支持后续继续建设。


二、技术架构:让模型能力服从业务边界

Smart Assistant 的整体架构可以分为接入层、业务编排层、知识处理层、模型适配层和数据层。

Smart Assistant

模型适配层

知识处理层

业务编排层

前端与 API 调用方

REST / SSE 接入层

知识库领域

文档导入

RAG 问答

会话与引用记录

多格式解析

文本清洗

重叠分块

异步任务工作器

Embedding Client

Chat Model Client

本地文件存储

PostgreSQL

pgvector

Ollama 本地模型

1. 三个模块各自承担什么

  • API 模块:维护输入 DTO、输出 VO 和统一响应契约;
  • Common 模块:维护持久化实体、公共常量以及 PostgreSQL 特殊类型处理;
  • Web 模块:承载 Controller、领域服务、数据访问、文档流水线、RAG 编排和模型客户端。

这种划分不是为了追求模块数量,而是让接口模型、持久化模型和运行实现互不混杂。

2. Embedding 与 Chat 为什么分开

文档向量化和答案生成是两种完全不同的能力:

  • Embedding 模型决定检索语义空间和向量维度;
  • Chat 模型决定答案质量、速度和生成风格。

项目分别为它们建立独立适配边界。当前运行时使用 Ollama,Embedding 模型为 nomic-embed-text,Chat 模型为 deepseek-r1:14b。

未来更换 OpenAI 兼容服务、Qwen 或企业内部模型时,核心文档流水线和 RAG 编排无需推翻。

3. GPT在架构中的角色

GPT并不是系统运行时使用的问答模型,而是整个项目的研发协作者。

它持续参与:

  • 阅读和理解当前代码;
  • 识别早期设计逐渐无法支撑的业务场景;
  • 提出重构方案;
  • 完成跨模块实现;
  • 根据编译、启动和接口结果继续修正;
  • 维护已经确定的项目规范。

这一区分非常重要:Ollama 为系统提供模型能力,GPT帮助我持续构建这套系统。


三、项目是怎样一步步演进的

Smart Assistant 不是一次生成出来的,而是在多个阶段持续演进的。

从 0 创建多模块项目

打通环境与启动链路

重建 RAG 领域模型

统一接口与对象边界

完成异步文档流水线

接入向量模型与 pgvector

实现知识库问答

补充 SSE 流式生成

完善会话、引用与异常恢复

每个阶段都要建立在上一阶段的真实结果上。

例如,在接口模型已经统一后,后续功能必须继续使用 DTO 和 VO;在文件存储被确定为只记录必要相对路径后,后续文档工作器就必须沿用这一设计;在回答必须可追溯后,流式和非流式问答都必须保存引用来源。

GPT需要记住的,不只是代码内容,还包括这些已经确认的工程决定。

这使项目开发从“生成一段实现”变成了“维护一个不断增长的技术上下文”。


四、核心节点一:从简单文档表到知识生命周期

项目从 0 起步时,第一版数据结构先满足了基础知识文档管理,但随着目标深入,很快就不足以支撑真正的 RAG。

在 GPT重新分析业务后,数据模型不再围绕单张文档表扩展,而是围绕知识生命周期重新组织。

contains

owns

versions

splits

embeds

processes

chats

contains

cites

KNOWLEDGE_BASE

DOCUMENT

DOCUMENT_FILE

DOCUMENT_VERSION

DOCUMENT_CHUNK

CHUNK_EMBEDDING

INGEST_JOB

CHAT_CONVERSATION

CHAT_MESSAGE

MESSAGE_SOURCE

文档主表只保存生命周期

文档主表保存标题、来源、状态和最新版本等元信息,不直接承载大段正文。

这样做可以让文档列表、状态查询和权限判断保持轻量,同时避免正文更新破坏历史版本。

文档版本负责可追溯性

每次导入或重新处理都会生成独立版本。原始文本、清洗文本、摘要和解析结果都有稳定归属。

当解析规则或分块策略发生变化时,系统可以创建新版本,而不是直接覆盖旧数据。

分块是 RAG 的最小召回单元

模型回答通常不需要整篇文档,只需要与问题最相关的部分。

因此系统以文档分块作为检索单元,并记录分块序号、字符位置和所属版本,为后续引用定位提供基础。

向量独立存储

向量与分块正文分离,并记录生成向量所使用的模型和维度。

这样既能重新生成向量,也为同一分块使用不同 Embedding 模型留下空间。

回答引用保存快照

系统不仅保存回答内容,还保存本次回答召回了哪些分块、对应文档标题、相似度和内容摘要。

即使知识文档后来发生变化,历史回答依然能够说明当时使用了什么依据。

这个模型重构是整个项目最关键的转折点。

GPT 没有停留在第一版结构上机械增加字段,而是帮助我把“知识库管理”重新理解为“知识从进入系统到被模型引用的完整生命周期”。


五、核心节点二:文档异步导入流水线

文件上传成功,并不等于知识已经可检索。

一份文档真正进入 RAG 系统,需要经历文件保存、格式解析、文本清洗、版本生成、内容分块、向量化和状态更新。

异常

异常

异常

异常

异常

接收上传文件

文件校验与安全存储

保存相对路径与文件摘要

创建 pending 导入任务

工作器原子抢占任务

按格式选择解析器

清洗并规范化正文

创建文档版本

固定窗口重叠分块

批量生成向量

文档进入 ready 状态

成功事务整体回滚

独立事务记录 failed 与原因

1. 上传阶段先保证文件安全

系统会校验文件大小、扩展名和文件名,拒绝非法上级目录路径。文件先写入临时位置,同时计算 SHA‑256,完成后再移动到最终目录。

数据库只保存相对路径和必要元数据。后台工作器重新定位文件时,还会再次进行路径归一化和目录越界校验。

2. 解析器与业务流水线解耦

当前支持文本、Markdown、DOCX 和 HTML 等格式。不同解析器只负责把文件转成原始文本,后续清洗、分块和向量化使用同一条流水线。

这种设计让新增格式只需要扩展解析边界,而不需要复制整套导入逻辑。

3. 分块策略简单但可替换

当前采用 800 字符窗口和 120 字符重叠。

它不是最复杂的语义分块方案,却足够稳定,能够先建立可靠的 RAG 基线。未来升级为标题感知、Markdown 结构分块或语义分块时,只需要替换分块组件。

4. 数据库任务表承担第一阶段异步能力

项目暂时没有直接引入消息队列,而是使用导入任务表配合定时工作器。

工作器按创建顺序获取任务,再通过“只有 pending 状态才能更新为 running”的条件更新完成抢占。即使多实例同时运行,也只有一个实例能够处理同一任务。

这个方案组件少、状态清晰、容易排查,适合项目当前阶段;未来切换到消息队列时,核心导入流水线仍然可以复用。

5. 成功事务和失败事务必须分开

文档导入会同时产生版本、分块和向量。如果向量化失败,前面生成的中间数据不应该作为半成品保留下来。

因此成功链路在同一事务中完成,任何步骤失败都会整体回滚。回滚之后,再使用独立事务写入失败状态和错误原因。

这项设计是在 GPT持续分析异常路径后逐步补齐的。它体现了项目从“功能能够执行”向“失败后数据仍然可信”的转变。


六、核心节点三:从向量召回到受约束回答

RAG 问答不是把用户问题直接交给聊天模型,而是先检索知识,再将有限上下文交给模型。

会话与引用记录 Chat 模型 pgvector Embedding 模型 RAG 服务 用户 会话与引用记录 Chat 模型 pgvector Embedding 模型 RAG 服务 用户 提交问题 创建会话与消息状态 生成问题向量 返回向量 校验模型与向量维度 检索指定知识库的相关分块 返回 TopK 分块 保存引用来源快照 限制上下文并构建 Prompt 基于知识上下文生成 完整内容或流式增量 保存回答状态与耗时 返回结果

1. 检索和入库必须使用同一向量空间

系统会校验三个维度:

  • 当前 Embedding 模型声明的维度;
  • 本次问题实际返回的向量长度;
  • pgvector 字段使用的存储维度。

任何一项不一致都会提前终止,避免在数据库写入或检索阶段才出现难以理解的错误。

2. 检索边界不只包含知识库

向量召回同时按知识库和 Embedding 模型过滤。

这样既能保证不同知识库之间的数据隔离,也能避免不同模型生成的向量在同一语义空间中被错误比较。

3. 上下文长度需要主动控制

召回结果按照相关度排序,高相关分块优先进入上下文。系统限制上下文总字符数,避免本地模型因输入过长显著变慢。

这也是项目没有简单把 TopK 结果全部拼接给模型的原因。

4. 没有依据时不让模型自由发挥

Prompt 明确要求模型只能使用知识库提供的信息,并在关键结论后标注引用。

如果没有召回到有效内容,系统不会继续调用聊天模型,而是直接返回“当前知识库中未找到足够依据”。

这种处理不能从理论上完全消除幻觉,但能够显著减少模型在无依据场景下的自由编造。


七、核心节点四:SSE 流式问答与状态收口

对于本地大模型,生成一个完整回答可能需要较长时间。普通同步接口会让用户在整个过程中看不到任何反馈,因此项目同时实现了 SSE 流式输出。

流式过程按照固定事件推进:

异常

meta
会话与模型信息

sources
引用来源

delta × N
回答增量

done
生成完成

error
失败信息

1. 长耗时推理使用独立有界线程池

本地模型调用属于阻塞型长任务。

项目为流式问答配置独立线程池,当前核心线程数为 2、最大线程数为 4、队列容量为 20。队列满后直接拒绝新任务,并向调用方返回明确提示。

这避免了模型请求无限堆积,也避免长连接持续占用全部 Web 请求线程。

2. 数据库事务不包住模型生成

会话、问题和 AI 回答占位记录通过短事务创建,真正的模型推理在事务之外执行。

生成完成、停止或失败时,再通过独立短事务更新最终状态。这样不会因为一次耗时推理长期占用数据库连接。

3. 浏览器断开后停止上游生成

每个模型增量都会先进入内存,再尝试发送给浏览器。

如果 SSE 连接已经关闭,系统会停止继续读取 Ollama 流,保存已经生成的部分内容,并将回答标记为 stopped。

4. 服务重启后恢复遗留状态

如果服务在生成过程中异常退出,数据库里可能留下 generating 状态。

应用启动后会检查超过合理时限的生成中消息,并将其恢复为异常中断状态,避免历史会话永久显示“回答中”。

这些机制并不直接提升模型回答质量,却决定了系统在真实使用中是否可靠。


八、工程实现中最重要的几个收口

除四条核心业务链路外,项目还补充了一些直接影响长期维护的工程边界。

1. 输入、输出和持久化对象分离

输入使用 DTO,输出使用 VO,数据库实体只负责持久化。

这让接口契约不会随着数据库字段调整而被动变化,也避免内部状态被无意暴露给调用方。

2. 普通接口保持统一响应

除 SSE 事件流外,REST 接口统一使用 code、success、data、msg 四个字段。

业务异常、参数校验异常和未知系统异常都通过统一入口转换,调用方不需要适配多种错误结构。

3. 特殊数据库类型集中处理

PostgreSQL 的 JSONB 和 pgvector 都不是普通字符串字段。

项目将它们的 Java 映射集中在类型处理边界,避免业务代码到处进行字符串拼接或手工转换。

4. 配置代替硬编码

API 前缀、上传目录、文件大小、导入频率、向量维度、模型名称、上下文长度、线程池参数和超时时间都可以通过配置或环境变量调整。

这样项目可以在不同环境中部署,而不需要修改业务代码。

这些内容看起来不像 RAG 的“核心算法”,却让模型能力真正进入了一个可维护的工程。


九、GPT是如何持续推进这一阶段的

如果只看当前架构,很容易误以为我一开始就想清楚了所有设计。

实际过程更加接近下面这个循环:

提出当前阶段目标

读取真实工程与历史约束

分析影响范围和技术方案

完成跨模块实现

编译、启动或接口验证

结果符合预期?

反馈真实日志与现象

沉淀规则并进入下一阶段

1. 先读项目,而不是先生成代码

每次进入一个新阶段,GPT 都需要先确认当前模块、配置、实体、服务和数据脚本。

项目持续演进后,早期 README 曾与真实技术栈不一致。如果只相信说明文档,就会继续基于已经过期的结构设计。

因此整个开发过程始终以当前源码和真实运行结果为准。

2. 将要求从单次指令升级为项目规则

随着项目推进,我逐步明确了长期约束:

  • 输入使用 DTO,输出使用 VO;
  • 普通接口保持统一响应结构;
  • 数据库实体不能直接暴露;
  • 文件存储保持最小化,只保存必要相对路径;
  • 中文注释需要帮助真实阅读,而不是形式化堆砌;
  • 数据库写操作必须经过明确授权;
  • 不能把“已经实现”描述成“已经完整验证”。

GPT后续生成和修改代码时,都要继续遵守这些规则。

3. 用真实错误更新判断

持续开发过程中,最有价值的输入不是“继续优化”,而是准确的运行证据。

项目先后经历过:

真实问题 GPT的处理方向
Maven 使用错误的 Java 版本 检查真实运行时,而不是继续修改业务代码
Spring Boot 找不到主类 区分父工程和可运行 Web 模块
MyBatis 映射文件启动失败 定位 pgvector 操作符与 XML 解析冲突
JSONB 写入类型不匹配 将问题收敛到数据库类型绑定边界
向量无法正常写入或检索 增加模型维度、结果维度和存储维度校验
流式回答中断后状态异常 补充连接关闭、部分回答和最终状态收口
服务重启后仍显示生成中 增加启动阶段的历史状态恢复

AI 并不是永远第一次就能给出最终答案。

真正有效的是:它能够根据新证据放弃原有假设,保留已经正确的部分,只修复真正的问题边界。

4. 每个阶段都留下可继承结果

一次提示词生成的代码很容易在下一次修改中被重新推翻。

而持续工程要求前一个阶段留下明确成果:

  • 启动问题解决后,后续先复用正确环境;
  • 数据模型确定后,文档流水线围绕版本和分块实现;
  • 向量边界确定后,问答检索沿用相同模型和维度;
  • 会话记录确定后,普通问答和流式问答使用同一生命周期;
  • 接口规范确定后,新增功能继续使用相同 DTO、VO 和响应结构。

GPT的价值不只是完成当前任务,还在于让下一个任务、下一个阶段能够站在当前结果之上继续推进。


十、我和 GPT的实际分工

“全程使用 GPT”并不意味着开发者完全退出项目。

我们的协作更接近下面这样:

我负责 GPT负责
明确业务目标和优先级 阅读当前工程并梳理影响范围
提出不能违反的约束 设计技术方案和实现顺序
决定架构取舍 完成跨模块代码实现
提供运行环境、日志和真实现象 沿调用链定位问题根因
判断结果是否符合使用预期 执行搜索、编译和必要验证
决定数据库和外部系统操作权限 在授权范围内持续推进
接受或否决技术方案 整理注释、风险与后续计划

我负责方向、边界和验收,GPT同时承担架构讨论、实现、检查和排障。

这种方式并不是把项目简单“外包给 AI”,而是把软件开发变成一个高频、可追踪、可验证的协作过程。


十一、阶段成果展示:从 Demo 到 RAG 工程闭环

下面的表格更能说明项目发生了什么变化。

建设阶段 早期状态 当前结果
项目运行 环境和启动链路存在多个阻塞点 正确 JDK 下可完成多模块编译
知识模型 以简单文档管理为中心 形成版本、分块、向量和任务模型
文档处理 文档内容主要依赖直接录入 形成可追踪的异步导入流水线
向量能力 缺少稳定的真实模型链路 Ollama Embedding 与 pgvector 完成适配
RAG 问答 缺少完整检索生成闭环 实现召回、上下文约束、生成和引用
交互方式 只考虑普通同步结果 支持 SSE 增量输出和连接生命周期
历史追溯 回答与来源关系不足 会话、消息、引用快照完整持久化
异常处理 主要关注正常路径 增加任务回滚、失败记录和状态恢复
工程规范 接口模型和返回方式不统一 DTO、VO、统一响应和异常边界稳定

效果演示

大模型实现智能助手阶段演示视频一


十二、这次 AI 原生开发带给我的理解

1. 一次生成适合 Demo,持续协作才能完成工程

真实项目会不断出现新要求、旧约束和运行错误。

大模型必须在当前工程上继续工作,而不是每次重新给出一套看似完整的新方案。

2. AI 编程的核心不是代码速度,而是验证闭环

代码写入项目,只能说明“已经实现”。

编译通过、应用启动、接口可用、异常路径成立,是不同层级的结果。

GPT能够持续参与这个项目,一个重要原因就是整个过程始终在区分这些验证边界。

3. 开发者的重心正在变化

AI 可以承担越来越多具体实现,但开发者仍然需要:

  • 把模糊想法转换成可验证目标;
  • 明确长期约束和架构边界;
  • 提供真实日志,而不是模糊描述;
  • 判断方案是否适合当前阶段;
  • 识别正常路径之外的风险;
  • 对最终结果负责。

AI 并没有消除工程判断,反而放大了工程判断的重要性。


结语

当前阶段的 Smart Assistant,已经不再只是一个能够调用本地大模型的后端。

它建立了一条完整的知识链路:

文件进入系统,经过解析、版本化、分块和向量化,成为可检索知识;用户问题经过语义召回和上下文约束,由模型生成带有来源依据的回答,并最终沉淀为可追溯会话。

这一阶段也验证了一条可持续的 AI 研发链路:从真实目标和工程上下文出发,完成架构与实现,再经过编译、运行、错误反馈和修正沉淀,回到更新后的项目上下文继续迭代。

持续使用 GPT,并不意味着项目由一句提示词自动生成,也不意味着当前阶段就是项目的结束。

恰恰相反,它要求我更清楚地表达目标,更严格地维护约束,更主动地验证结果,也更诚实地区分哪些已经完成、哪些只完成了部分验证。

一次提示词可以生成一个看起来不错的项目。

而一个真正能够持续演进的系统,需要人和 AI 一起理解过去、解决现在,并为下一步保留空间。

这就是我持续借助 GPT推进 Smart Assistant,并完成当前 RAG 阶段建设后,对 AI 原生开发最真实的理解。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐