从需求分析到模型部署上线,记录一个Java全栈开发者如何独立完成"智能客服问答系统"功能的全过程。Spring Boot后端 + Vue前端 + LLM集成 + 数据处理
5年经验,后端Java/Spring Boot + 前端Vue3 + AI集成都能搞。在AI创业公司里,一个人扛起从数据建模到接口部署的全链路。
今天的目标:独立完成"智能客服问答系统"MVP — 用户输入问题,系统先从公司文档库搜索相关内容,再让LLM基于搜索结果生成回答,返回答案+引用来源。
这可不是简单的"调一下ChatGPT API",需要做RAG(检索增强生成),涉及向量数据库、embedding、prompt工程、异步任务、流式输出...Java全栈的挑战在于:把AI能力和企业级Java架构融合起来,保证稳定性、可维护性。
PM发来需求文档,拆解任务和技术选型
这个需求最关键的是RAG。很多人以为AI客服就是调ChatGPT,但通用LLM根本不知道你公司的年假政策是什么,会胡编乱造(hallucination)。
RAG的思路:先从文档库"查资料",再把资料喂给LLM让它"看着资料回答",准确率提升几个数量级。
类比:纯LLM = 闭卷考试(凭记忆答,可能记错);RAG = 开卷考试(翻书找答案,准确率高得多)。
| # | 任务 | 优先级 | 预估 |
|---|---|---|---|
| 1 | 数据库设计 + pgvector配置 | P0 | 1h |
| 2 | Spring Boot项目骨架 + Controller | P0 | 1h |
| 3 | RAG核心逻辑(embedding+搜索+LLM) | P0 | 1.5h |
| 4 | @Async异步文档导入 | P1 | 1h |
| 5 | Vue3聊天界面 + SSE | P1 | 1.5h |
| 6 | 前后端联调 | P1 | 0.5h |
| 7 | Docker部署 | P2 | 0.5h |
| 8 | 测试验证 + 性能优化 | P2 | 0.5h |
| 对比维度 | 纯LLM方案 | RAG方案(选用) |
|---|---|---|
| 原理 | 用户问题直接给LLM生成回答 | 先检索相关文档,再让LLM基于文档回答 |
| 准确性 | ❌ 容易胡编(幻觉严重) | ✅ 基于真实文档,可溯源 |
| 公司知识 | ❌ 不知道公司内部政策 | ✅ 已学习所有公司文档 |
| 更新成本 | 每次更新要重新Fine-tune(贵) | 删旧文档+传新文档即可(便宜) |
| 类比 | 闭卷考试(凭记忆) | 开卷考试(翻书找答案) |
| 延迟 | 1-2秒 | 3-4秒(多了检索环节) |
| 成本 | 每次调用LLM | embedding便宜 + LLM调用 |
设计PostgreSQL表结构,引入pgvector做语义搜索
为什么不直接用 LIKE '%关键词%' 搜索?
SQL的LIKE是字面匹配,用户问"年假怎么请"但文档里写的是"带薪休假申请流程",LIKE根本搜不到。语义不同但意思相同的内容会漏掉。
向量搜索是语义匹配 — 把文本变成1536维的向量,用余弦相似度找"意思最接近"的内容。"年假怎么请"和"带薪休假申请"在向量空间里距离很近,能搜到。
类比:LIKE = 精确匹配关键词(就像只能搜书名);向量搜索 = 理解意思搜索(就像图书管理员知道你想找什么类型的书)。
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| pgvector ✅ 选用 |
复用现有PG、事务一致、运维简单 | 超大规模(亿级)性能一般 | 中小规模文档库(<100万) |
| Milvus | 专业向量库、性能强、支持十亿级 | 需要单独部署维护、架构复杂 | 大规模向量搜索 |
| Elasticsearch + dense_vector | 已有ES团队可复用,全文+向量混合搜索 | 向量搜索成熟度不如专业库 | 已有ES基础设施的团队 |
| Redis Stack (RediSearch) | 延迟极低,和缓存共用 | 内存成本高,不适合大量持久化 | 小规模实时搜索 |
为什么选pgvector?因为我们公司已经有PostgreSQL了,加个扩展就能用,不用多维护一个组件。MyBatis对PostgreSQL的JSONB和自定义类型也有很好的支持。
类比:家里已经有冰箱了(PostgreSQL),买个保鲜盒(pgvector扩展)就能存菜,不需要再买一个冰柜(Milvus)。文档量500-1000篇,pgvector完全够用。
搭建项目结构、pom.xml依赖、Controller入口、DTO校验
Spring Boot vs FastAPI vs Express 选型思考:
FastAPI适合Python生态的快速原型,Express适合轻量Node项目。但公司主要技术栈是Java,Spring Boot的优势在于:成熟的依赖注入、统一的异常处理(@RestControllerAdvice)、声明式事务(@Transactional)、完善的监控体系(Actuator + Prometheus)。
AI应用也需要稳定的企业级架构 — 连接池管理、线程池隔离、优雅停机,这些都是Spring Boot的强项。
Embedding向量化 → pgvector语义搜索 → 构造Prompt → 调LLM生成
Java调LLM的两种方式:
1. 直接用WebClient(我们用的) — 构造HTTP请求调OpenAI API,最灵活,不依赖第三方SDK。WebClient天然支持响应式流(Flux),做SSE流式输出很方便。
2. 用LangChain4j / Spring AI — 封装了更多的AI操作(RAG链、记忆管理、Agent等),但对底层控制力弱一些。MVP阶段先自己写,后面如果需求复杂再引入框架。
为什么temperature设0.3?客服场景要准确,不要"创造性"。temperature越低回答越确定、越稳定。写诗可以设0.9,做客服0.3就够了。
| 维度 | ❌ 坏的Prompt | ✅ 好的Prompt |
|---|---|---|
| 示例 | "用户问了{question},请回答" | "你是公司客服助手。基于以下文档回答。找不到就说'未找到'。文档:{context}。问题:{question}" |
| 文档来源 | 不提供文档,让LLM自己编 | 明确给出参考文档 |
| 兜底指令 | 没有 | "如果文档中没有相关信息,请说'未找到'" |
| 幻觉率 | 很高(50%+) | 很低(<5%) |
用Spring @Async + 线程池处理文档批量上传,不阻塞API
午饭回来想到一个问题:管理员上传100个文档,每个都要做embedding,如果同步处理,HTTP请求会超时(100个文档embedding要好几分钟)。
必须异步处理 — 上传接口立刻返回"处理中",后台用线程池慢慢做,做完通知前端。
核心机制:Spring @Async = 方法加个注解就变成异步。调用方立即返回,实际执行在线程池的工作线程上。配合CompletableFuture可以做回调。
Spring @Async的底层原理:
当你调用一个@Async方法时,Spring不是直接执行,而是通过AOP代理把任务提交到线程池的队列里。调用线程立即返回,工作线程从队列取任务执行。
三个关键坑:
1. @Async方法必须是public — Spring通过代理调用,private/protected方法不生效
2. 不能在同一个类内this调用 — this.xxx()不走代理,@Async不生效。必须通过注入的Bean调用
3. 必须配合@EnableAsync — 在配置类上加这个注解才开启异步支持
| 维度 | Python Celery | Spring @Async |
|---|---|---|
| 部署方式 | 需要单独的Worker进程 | 应用内线程池,无需额外进程 |
| 消息中间件 | 必须配Redis/RabbitMQ | 不需要(线程池内置队列) |
| 任务持久化 | ✅ 重启不丢任务 | ❌ 重启丢未完成任务 |
| 分布式 | ✅ 多机器Worker | ❌ 单机(需XXL-JOB做分布式) |
| 适用场景 | 大规模、高可靠异步 | 轻量级、单机内异步 |
聊天界面 + SSE流式接收 + 文档上传组件
为什么用SSE(Server-Sent Events)流式接收?
LLM生成完整答案要3秒,如果等全部生成完再返回,用户盯着空白屏幕干等3秒,体验极差。
用SSE,LLM每生成一个token就推送给前端,用户看到打字机效果,首字延迟<500ms,体感上"AI在实时思考"。
类比:一次性端上一桌菜(普通HTTP) vs 一道道菜上(SSE流式) — 后者体验好太多,虽然总时间一样。
SSE vs WebSocket vs HTTP:SSE = 收音机广播(服务器单向推送);WebSocket = 电话(双向通话);HTTP = 寄信(一问一答)。AI回答场景用SSE最合适。
解决CORS跨域、SSE接收、JSON格式校验三大坑
CORS是什么?为什么浏览器要限制跨域?
浏览器有同源策略:A网站的JS不能随意请求B网站的API(防止恶意网站窃取数据)。当前端(5173)和后端(8080)端口不同,就是"跨域",浏览器会拦截。
解决:后端响应里加上 Access-Control-Allow-Origin 头,告诉浏览器"我允许这个前端访问"。
类比:CORS = 写字楼的访客登记制度。不是所有访客都能随便进大楼,必须前台登记(CORS头)确认后才能进。
看到打字机效果的那一刻 — 前后端终于跑通了!SSE流式输出让AI回答有了"实时思考"的感觉,体验远好于等3秒一次性返回。
Dockerfile + docker-compose一键编排三个服务
为什么用docker-compose?这个项目有3个服务:Spring Boot、PostgreSQL、Redis。手动启动要开3个终端,敲十几条命令,还容易漏环境变量。
docker-compose把所有服务、依赖关系、端口映射写在一个YAML里,docker compose up -d 一条命令全部启动。
Spring Boot的Dockerfile技巧:多阶段构建(Stage1 Maven打包 → Stage2 JRE运行),最终镜像只有JRE + jar包,体积从900MB缩到180MB。
API测试、边界case、Redis缓存优化
| 场景 | 输入 | 预期结果 | 实际结果 |
|---|---|---|---|
| 正常问题 | "年假怎么请?" | 返回答案+引用 | ✅ 2.8s |
| 无关问题 | "今天天气怎么样?" | "未找到相关资料" | ✅ 正确兜底 |
| 空问题 | "" | 400 "问题不能为空" | ✅ @Valid拦截 |
| 超长问题 | 500字+的提问 | 400 "问题不能超过500字" | ✅ @Size拦截 |
| 无文档时 | 清空documents表后提问 | "文档库中没有找到" | ✅ 正确兜底 |
| LLM超时 | 断网模拟LLM不可达 | 500 "服务器内部错误" | ✅ 全局异常处理 |
一天完成智能客服MVP,从需求到部署全流程
| 维度 | Java (Spring Boot) | Python (FastAPI) |
|---|---|---|
| 类型安全 | ✅ 编译期检查,DTO强类型 | ❌ 运行时才发现错误 |
| 性能 | ✅ JIT优化,高并发强 | ⚠️ GIL限制,IO密集还行 |
| AI生态 | ⚠️ LangChain4j还在发展 | ✅ LangChain/LlamaIndex最成熟 |
| 企业级 | ✅ 事务/监控/线程池完善 | ⚠️ 需要自己搭 |
| 部署体积 | ⚠️ JRE + jar ~180MB | ✅ Python镜像~120MB |
| 流式处理 | ✅ SseEmitter + WebClient | ✅ StreamingResponse + async |
| 团队成本 | ✅ Java团队直接上手 | ⚠️ 需要学Python |
1. AI是调用方,架构是根基 — LLM只是HTTP API调用,真正决定系统质量的是Spring Boot的事务管理、线程池控制、异常处理等企业级能力
2. 异步是必须的 — LLM调用动辄2-3秒,@Async + SseEmitter让用户不等待、不超时
3. 缓存是免费的性能提升 — 高频问题Redis缓存5分钟,响应时间从秒级降到毫秒级,零额外成本