我开源了一套可运行的Spring AI项目应用:5大模块把AI从Demo接进真实业务 目录前言1. 为什么还要做一个Spring AI项目2. 先从整体架构看懂项目2.1 五个业务模块2.2 四个共享平台模块3. 一次AI请求怎样进入真实业务4. 五个Copilot分别解决了什么问题4.1 Data Copilot模型可以生成SQL但不能直接执行4.2 Knowledge Copilot答案必须能够被当前文档证明4.3 Support Copilot生成客服草稿但不替人做承诺4.4 Report Copilot报告中的数字不能靠模型自由发挥4.5 Resume CopilotAI只整理证据不替企业做招聘决定5. 为什么五个业务模块没有写成五套重复代码5.1 ai-core统一模型调用边界5.2 ai-guardrails只沉淀真正公共的规则5.3 ai-tool-audit记录元数据而不是复制敏感原文5.4 common-web保持API行为一致5.5 business-copilot-app唯一可执行入口6. 技术选型不是为了堆技术栈6.1 为什么使用PostgreSQL和pgvector6.2 为什么同时保留JDBC和MyBatis-Plus6.3 为什么没有引入独立前端工程6.4 为什么Chat和Embedding配置分开7. Docker Compose一键启动8. 这个项目适合谁8.1 正在从Java后端转向AI应用开发的人8.2 想做AI项目作品集的人8.3 想验证内部AI场景的中小团队8.4 想理解AI工程边界的人9. 它目前不是什么10. 做完这个项目后我对Java转AI有了新的理解11. 总结前言上一篇文章我分析了40条北京AI应用开发岗位的招聘信息。最后得出了一个结论企业真正需要的不是一个只会调用大模型API的人而是一个能够把AI能力接入业务系统并稳定完成交付的人。文章发布之后我没有继续做另一个聊天机器人也没有再写一套只有几百行代码的RAG Demo。而是把自己之前学习和实践过的Spring AI、结构化输出、RAG、Guardrails、人工确认、审计和Docker部署重新组织成了一套可以直接运行的Java AI业务项目Spring AI Business Copilot项目目前已经发布v1.0.0包含五个可以独立理解和运行的业务模块Data CopilotKnowledge CopilotSupport CopilotReport CopilotResume Copilot。项目地址GitHubspring-ai-business-copilotGiteespring-ai-business-copilot先看一下当前项目的统一工作台。这篇文章不会按照README逐个罗列接口。我更想讲清楚另一个问题当一个不完全确定的AI输出真正进入数据库、知识库、客服、报表和招聘辅助流程时我们还需要补齐哪些工程能力1. 为什么还要做一个Spring AI项目刚开始学习AI应用开发时我也做过聊天机器人和RAG知识库。这些项目很有价值。它们让我理解了Chat Model、Prompt、Embedding、向量检索和流式输出也让我完成了从传统Java后端到AI应用开发的第一步。但继续往真实业务里走很快就会遇到一些Demo不会回答的问题模型生成的SQL能不能直接执行RAG回答中引用的内容真的存在吗客服回复出现“保证退款”怎么办周报中的98.6%被模型改成98.8%怎么办简历分析能不能给候选人打分、排名和淘汰建议谁确认了这次操作出现问题以后怎样知道模型当时生成了什么、系统又做了什么这些问题的难点已经不再是怎样调用模型。真正困难的是怎样把模型的概率性输出放进一个需要确定性、可追踪和可控制的业务系统。因此Spring AI Business Copilot并不是另一个AI框架也不是给五个页面套上聊天窗口。它更像是一套Java AI业务应用样板用五个不同场景验证五类常见问题。业务问题项目中的处理方式模型输出格式不稳定结构化输出并映射为Java DTO模型可能编造字段和依据Schema、Citation、Evidence ID确定性校验高风险动作不能自动执行服务端Token与人工确认输入可能包含敏感数据入模、入库前脱敏模型可能越过业务边界业务级Guardrails出现问题无法复盘请求状态、模型、耗时和错误审计项目只能停留在本机Docker Compose、PostgreSQL、pgvector和Flyway这个项目最想表达的不是“我实现了五个AI功能”。而是一条贯穿所有模块的原则大模型负责生成候选结果确定性代码负责守住业务边界人负责确认高风险动作。2. 先从整体架构看懂项目项目采用一个可执行应用、五个业务模块和四个共享平台模块的结构。五个Copilot共用一个Spring Boot应用和统一工作台但业务规则不会互相混在一起。2.1 五个业务模块模块业务场景最重要的业务边界Data Copilot自然语言查询数据库只读SQL、白名单、执行前确认Knowledge Copilot企业内部文档问答强制引用、无依据拒答Support Copilot工单分类和回复草稿高风险转人工、禁止自动发送Report Copilot周报和经营报告指标与来源严格一致Resume Copilot单JD、单简历证据分析不评分、不排名、不做招聘决定2.2 四个共享平台模块模块主要职责platform/ai-core模型调用、Embedding、结构化输出、Prompt模板platform/ai-guardrailsSQL安全、Schema白名单、敏感字段和文本脱敏platform/ai-tool-auditData Copilot查询生命周期审计platform/common-web统一响应、错误码、异常处理和参数校验项目还有一个可执行装配模块模块主要职责app/business-copilot-app组装所有Copilot、Flyway、工作台、配置和Docker入口业务模块可以依赖平台能力但平台模块不会反过来依赖某个具体Copilot。这样既能复用模型调用、脱敏和异常处理又不会把所有业务规则都塞进一个巨大的公共模块。项目目录如下spring-ai-business-copilot/ ├── app/ │ └── business-copilot-app/ ├── platform/ │ ├── ai-core/ │ ├── ai-guardrails/ │ ├── ai-tool-audit/ │ └── common-web/ ├── modules/ │ ├──>3. 一次AI请求怎样进入真实业务虽然五个模块解决的问题不同但它们遵循的基础流程基本一致。用户输入 ↓ 业务校验与敏感信息脱敏 ↓ Prompt模板与模型调用 ↓ 生成结构化候选结果 ↓ Java确定性Guardrails ↓ 拒绝 / 进入人工复核 / 保存候选状态 ↓ 高风险动作人工确认 ↓ 返回安全结果并记录审计信息这里最容易混淆的是Prompt和Guardrails之间的区别。Prompt可以告诉模型只生成只读SQL必须返回引用不要承诺退款不要给候选人打分。但Prompt本质上仍然是对模型的文字要求。模型可能遵守也可能因为上下文、输出漂移或者Prompt Injection而违反。所以在项目中Prompt负责告诉模型“应该怎样生成”Guardrails负责决定系统“允许什么结果继续执行”。一个格式正确的JSON并不代表它就是安全的业务结果。它还要经过Java代码的二次判断。例如一个通用流程可以抽象为T candidate aiChatService.generateJson(prompt, responseType); ValidationResult validation businessGuardrail.validate(candidate, currentEvidence); if (!validation.passed()) { auditService.recordRejected(validation.reasons()); return BusinessResponse.needsReview(validation.reasons()); } StoredCandidate storedCandidate candidateService.save(candidate); if (storedCandidate.requiresConfirmation()) { return confirmationService.createConfirmation(storedCandidate); } return BusinessResponse.success(storedCandidate);真正接入业务系统以后我认为这段流程往往比调用ChatClient本身更重要。4. 五个Copilot分别解决了什么问题4.1 Data Copilot模型可以生成SQL但不能直接执行Data Copilot解决的是一个很常见的问题运营、产品或者业务负责人想查询数据但每一次都需要开发人员手写SQL。用户可以直接输入上个月销售额是多少 退款率最高的商品有哪些 本周新增用户数量是多少模型会根据允许访问的Schema生成SQL候选。但生成以后不会立即执行而是经过下面几个阶段读取白名单Schema模型生成结构化SQL候选第一次SQL Guardrails页面展示SQL用户确认服务端取回原始候选执行前再次校验执行只读查询对结果脱敏并记录审计。这里最关键的设计不是“自然语言转SQL”。而是执行接口根本不接收SQL。执行请求中只有确认Tokenpublic record SqlExecutionRequest( NotBlank Size(max 128) String confirmationToken) { }为什么要这样设计如果执行接口允许前端重新提交SQL用户就可以先让一条安全SQL通过校验再在浏览器中把它修改成另一条语句。这样第一次校验就失去了意义。因此真正执行的SQL只能来自服务端保存的候选。用户提交确认Token以后服务端重新取出候选SQL并执行第二次Guardrails。SQL校验会检查是否只有一条语句是否为SELECT或者WITH ... SELECT是否包含insert、update、delete、drop等禁用操作是否只访问白名单表和字段非聚合查询是否包含LIMIT是否涉及敏感字段。这保证了用户最终执行的必须是已经生成、已经展示、已经校验并且已经人工确认的同一条SQL。4.2 Knowledge Copilot答案必须能够被当前文档证明Knowledge Copilot面向企业内部文档问答。它完成的流程包括Markdown和TXT文档解析敏感数据脱敏Chunk切分Embedding向量化PostgreSQL与pgvector检索基于召回内容生成答案返回引用来源无依据时拒答问答审计。普通RAG Demo通常会要求模型“根据上下文回答并给出引用”。但只写进Prompt还不够。模型可能返回一个格式完全正确、实际上并不存在的chunkId。因此项目会用Java代码校验模型返回的每一个CitationSetLong validChunkIds retrievedChunks.stream() .map(item - item.chunk().id()) .collect(Collectors.toSet()); for (KnowledgeCitation citation : citations) { if (citation.chunkId() null) { violations.add(Citation contains null chunkId); continue; } if (!validChunkIds.contains(citation.chunkId())) { violations.add( Citation references a chunk that was not retrieved); } }模型只能引用本次真正检索到的Chunk。如果没有召回任何有效内容系统甚至不会继续调用大模型if (retrievedChunks null || retrievedChunks.isEmpty()) { return new KnowledgeAnswerResponse( KnowledgeAnswerStatus.NO_EVIDENCE, null, List.of(), List.of(), aiChatService.modelName()); }因为企业知识库并不是在比较谁回答得更流畅。更重要的是当前回答能不能被企业自己的文档证明。没有依据时拒绝回答通常比使用模型常识补全一个“看起来正确”的答案更安全。4.3 Support Copilot生成客服草稿但不替人做承诺Support Copilot会对一条客服工单完成工单分类情绪识别紧急程度判断知识依据检索回复草稿生成风险识别转人工建议草稿确认或者取消全流程审计。但它明确不做三件事不自动发送客服消息不自动退款不自动修改账号、合同或者订单状态。例如模型生成的回复中不能出现保证退款承诺赔偿24小时内一定处理马上完成合同变更。这些要求不仅写在Prompt中也会由服务端代码再次检查private static final String[] FORBIDDEN_COMMITMENTS { 承诺退款, 保证退款, 马上退款, 承诺赔偿, 保证赔偿, 合同变更, 24小时内处理, 48小时内处理, 马上处理, 立刻处理 };退款、账号安全、生产故障等业务类别也会被服务端自动提升为高风险。即使模型认为风险等级不高Java规则仍然可以把结果提升为HIGH并要求人工处理。这也是我在这个模块中想表达的边界模型可以帮助客服理解问题和生成草稿但不能代表企业作出确定性承诺。4.4 Report Copilot报告中的数字不能靠模型自由发挥Report Copilot用于生成团队周报、经营周报和项目状态报告。用户提供的不是一段模糊Prompt而是几类有明确类型的来源指标数据任务记录会议纪要。系统会先完成来源校验敏感信息脱敏数据归一化内容Hash结构化报告生成来源和指标校验人工确认Markdown导出。报告中的内容会被划分为有来源的执行摘要指标亮点已完成事项已知风险来源行动项AI建议。其中AI建议必须明确标记为AI_SUGGESTION不能伪装成已经发生的业务事实。这个模块最重要的Guardrail是指标值必须与来源完全一致。例如来源中是系统可用率98.6%模型不能把它写成98.8%也不能自行计算出一个来源中不存在的增长率。系统会同时检查指标名称指标数值指标单位被引用的来源ID。只要其中一项无法对应报告就会进入NEEDS_REVIEW。另外确认报告只代表允许从服务端导出Markdown不代表系统会自动发布到邮件、群聊或者外部平台。AI可以帮助整理和表达信息但原始指标仍然必须由确定性数据源负责。4.5 Resume CopilotAI只整理证据不替企业做招聘决定Resume Copilot是五个模块中边界最严格的一个。它只处理一个已经人工确认的JD以及一份已经脱敏的简历。输出内容包括逐条职位标准匹配对应的简历证据ID缺失的证据需要在面试中人工核验的问题当前分析的局限性。它明确禁止输出候选人总分星级排名通过概率录用或者淘汰建议多候选人比较基于年龄、性别、婚育、民族和健康状况的判断自动修改招聘流程状态。简历在进入模型之前会先移除邮箱、手机号、身份证和受保护属性String text SCRIPT.matcher(value).replaceAll( ); text HTML.matcher(text).replaceAll( ); text EMAIL.matcher(text).replaceAll([EMAIL_REMOVED]); text PHONE.matcher(text).replaceAll([PHONE_REMOVED]); text ID_CARD.matcher(text).replaceAll([ID_REMOVED]);模型输出以后系统还会检查是否包含自动招聘决策private static final ListString FORBIDDEN List.of( 建议录用, 建议淘汰, 建议拒绝, 总分, 排名, 通过率, 通过概率, hire, reject, ranking, overall score, probability );如果模型引用了当前简历之外的Evidence ID同样不能通过。Resume Copilot想做的不是“AI替HR筛选候选人”。而是AI只负责整理当前材料中已经存在的证据最终判断仍然属于人。5. 为什么五个业务模块没有写成五套重复代码五个模块的业务规则不同但模型调用、Prompt加载、结构化输出、脱敏和异常处理等能力可以复用。因此项目把真正稳定的公共能力放进了平台模块。5.1 ai-core统一模型调用边界业务模块不会到处直接创建ChatClient。它们统一通过AiChatService调用模型public T T generateJson(String prompt, ClassT type) { ChatClient chatClient requireChatClient(); try { return chatClient.prompt() .user(prompt) .call() .entity(type, spec - spec.validateSchema()); } catch (RuntimeException ex) { throw new BusinessException( ErrorCode.AI_OUTPUT_PARSE_ERROR, AI model output could not be mapped, ex); } }这样可以统一处理模型配置结构化输出Schema校验异常转换模型名称记录后续可能增加的Token和耗时统计。所有Prompt也集中放在platform/ai-core/src/main/resources/prompts/copilot/例如prompts/data-copilot/sql-generation.st prompts/knowledge-copilot/answer-generation.st prompts/support-copilot/reply-draft.st prompts/report-copilot/report-generation.st prompts/resume-copilot/resume-assessment.st这样做以后Prompt不会散落在Service代码中也更方便做版本管理和Badcase复盘。5.2 ai-guardrails只沉淀真正公共的规则ai-guardrails目前主要包含SQL只读校验单语句校验Schema白名单LIMIT校验敏感字段策略查询结果脱敏通用文本脱敏。但客服中的“禁止承诺”、报告中的“指标一致性”、简历中的“禁止评分排名”没有被强行抽到公共模块。因为这些规则属于具体业务。如果为了追求形式上的复用把所有规则都塞进ai-guardrails它最终很可能变成一个谁都不敢修改的巨大规则中心。我的处理方式是技术层面的通用边界可以复用业务层面的判断仍然保留在所属模块中。5.3 ai-tool-audit记录元数据而不是复制敏感原文Data Copilot会记录请求ID事件类型SQL状态是否确认返回行数模型名称执行耗时错误摘要。但不会把查询结果完整复制进审计表。因为审计的目标是帮助定位问题而不是再制造一份敏感数据副本。5.4 common-web保持API行为一致common-web只提供一组稳定、范围明确的Web能力ApiResponse业务错误码参数校验错误分页响应全局异常处理。业务DTO、业务状态和业务流程不会被放入common-web。5.5 business-copilot-app唯一可执行入口五个业务模块最终被组装进一个Spring Boot应用。这个模块负责应用启动运行配置Flyway数据库迁移Thymeleaf与原生JavaScript工作台健康检查Docker入口。6. 技术选型不是为了堆技术栈项目当前主要使用分层技术运行时Java 21、Spring Boot 4.1AISpring AI 2.0、Jackson 3数据访问JDBC、MyBatis-Plus 3.5.16数据库PostgreSQL 16、pgvector、FlywayWebSpring MVC、Thymeleaf、原生JavaScript这些选型并不是为了把尽可能多的技术写进README。6.1 为什么使用PostgreSQL和pgvector项目同时需要普通业务表审计表文档和Chunk元数据向量检索。使用PostgreSQL和pgvector以后一个数据库就可以覆盖当前样板项目的大部分存储需求。它未必适合所有生产系统但非常适合一个需要快速Clone、运行和理解的开源项目。6.2 为什么同时保留JDBC和MyBatis-Plus稳定的CRUD使用MyBatis-Plus。动态查询、批量操作和部分需要精确控制SQL的场景使用JDBC。我没有为了统一形式强迫所有数据库操作都使用同一种访问方式。6.3 为什么没有引入独立前端工程项目的主要目标是展示Java AI业务流程而不是前端工程能力。因此使用Thymeleaf和原生JavaScript提供统一工作台。这样Clone项目以后不需要再安装Node.js、执行前端构建或者维护两套开发环境。6.4 为什么Chat和Embedding配置分开很多兼容OpenAI协议的聊天模型服务并不一定同时提供兼容的Embedding接口。因此项目有意拆分了Chat Model和Embedding Model配置。这比默认假设一个Provider能够提供所有模型更接近实际接入过程。7. Docker Compose一键启动项目支持通过Docker Compose启动。git clone https://github.com/qcodingdev/spring-ai-business-copilot.git cd spring-ai-business-copilot/examples cp .env.example .env docker compose up --build启动后访问http://localhost:8080Flyway会自动创建示例业务表虚构示例数据五个Copilot需要的业务表审计表pgvector扩展和向量表。默认情况下Chat Model和Embedding Model都是关闭的。也就是说即使暂时没有API Key项目仍然可以启动用于查看模块结构、数据库迁移、工作台和非AI预览能力。启用聊天模型后可以在.env中配置SPRING_AI_MODEL_CHATopenai SPRING_AI_OPENAI_CHAT_API_KEYyour-chat-key SPRING_AI_OPENAI_CHAT_BASE_URLhttps://api.deepseek.com SPRING_AI_OPENAI_CHAT_OPTIONS_MODELdeepseek-v4-flashKnowledge Copilot还需要单独配置Embedding模型SPRING_AI_MODEL_EMBEDDINGopenai SPRING_AI_OPENAI_EMBEDDING_API_KEYyour-embedding-key SPRING_AI_OPENAI_EMBEDDING_BASE_URLhttps://api.openai.com SPRING_AI_OPENAI_EMBEDDING_MODELtext-embedding-3-small本地开发环境执行./mvnw -q -DskipTests install ./mvnw -pl app/business-copilot-app spring-boot:run执行全部测试./mvnw -q test项目中更详细的API、模块说明和测试命令都已经放在各模块自己的README中。文章不再重复展开所有接口因为接口只是使用入口真正值得理解的是背后的业务边界。8. 这个项目适合谁8.1 正在从Java后端转向AI应用开发的人项目展示的不是一个孤立的ChatClient示例。你可以从中看到Spring AI怎样进入业务Service模型输出怎样映射成Java对象RAG怎样进行Citation校验高风险动作怎样加入人工确认Guardrails怎样与业务代码结合AI功能怎样通过Docker交付。8.2 想做AI项目作品集的人项目包含Text to SQL企业RAG客服辅助报告生成简历证据分析GuardrailsHuman in the Loop审计Docker Compose交付。可以选择其中一个模块继续扩展而不必从空白项目开始搭建。8.3 想验证内部AI场景的中小团队团队可以替换示例数据、模型配置和业务接口把其中一个模块接入自己的内部系统。但需要注意这个项目提供的是业务样板不是开箱即用的生产平台。8.4 想理解AI工程边界的人项目重点不在于模型有多聪明。而在于模型出错时怎样拒绝没有依据时怎样停止高风险操作怎样转人工敏感数据怎样处理出现问题以后怎样审计。9. 它目前不是什么项目当前明确不做多租户IAM完整企业权限系统模型市场通用工作流编排平台任意模型生成工具的自动执行自动发送客服回复自动发布报告自动改变招聘流程状态批量候选人排名直接宣称已经满足生产环境全部安全要求。它是一套可运行、可学习、可修改的Java AI业务应用样板。真正进入生产环境前还需要根据企业自身情况补齐身份认证和细粒度权限租户隔离密钥管理限流、熔断和降级指标监控与告警数据备份安全测试合规审查更完整的AI评测体系。明确不做什么并不是项目能力不足。相反它可以避免一个开源样板为了显得“大而全”加入大量没有经过真实验证的抽象。10. 做完这个项目后我对Java转AI有了新的理解刚开始转向AI应用开发时我也会担心做了十年Java现在学习AI是不是意味着过去的经验都要推倒重来但真正完成这个项目后我的理解反而越来越明确。AI应用开发并不是抛弃传统后端能力。模型调用只是其中的一部分。当AI真正接入业务以后仍然需要处理数据库接口设计状态流转异常处理安全校验敏感数据日志审计模块边界测试部署和运维。这些恰恰是Java开发者长期积累的能力。不同的是我们现在需要在原来的确定性系统中接入一个概率性的模型。因此Java开发者真正需要补齐的并不是把过去全部丢掉。而是学会怎样用原有的工程能力控制、验证和承接大模型的输出。这也是Spring AI Business Copilot最终想表达的内容。11. 总结回过头看这个项目真正想回答的并不是怎样再实现五个AI功能而是一个Java AI功能从“可以调用模型”走向“可以接入业务”中间还要补齐什么在Data Copilot中答案是只读SQL、白名单、服务端候选、二次校验和人工确认。在Knowledge Copilot中答案是文档检索、强制引用、Citation校验和无依据拒答。在Support Copilot中答案是禁止确定性承诺、高风险转人工和只生成草稿。在Report Copilot中答案是来源绑定、指标严格比对和确认后导出。在Resume Copilot中答案是隐私脱敏、证据匹配以及禁止评分、排名和自动招聘决定。五个模块最终遵守的是同一条原则大模型负责生成候选结果确定性代码负责守住业务边界人负责确认高风险动作。项目地址GitHubhttps://github.com/qcodingdev/spring-ai-business-copilotGiteehttps://gitee.com/qcodingdev/spring-ai-business-copilot如果这个项目对你学习Spring AI或者设计企业AI应用有所帮助欢迎点一个Star。这也是对开源项目最直接的支持。下一篇预告AI Coding工具可以在短时间内修改几十个文件。效率提高以后新的问题也随之出现这次改动到底影响了哪些模块有没有遗漏的异常分支是否修改了不应该修改的文件提交前怎样快速识别高风险代码下一篇我会介绍自己开源的另一个工具一个专门用于Git Commit前代码改动风险检查的CLI工具。它不会替开发者决定代码是否可以提交而是帮助开发者在大量AI生成改动中更快发现值得重点检查的风险。 推荐专栏 AI开源项目实战持续分享Java AI业务项目、AI Coding效率工具和工程治理实践。https://blog.csdn.net/qupengkun/category_13189373.html 创建专栏后补充链接 AI技术专栏从0到1学习AI应用开发持续分享Spring AI、RAG、Agent和企业级AI项目实践。 https://blog.csdn.net/qupengkun/category_13184360.html AI转型日记记录一名10年Java开发者从传统后端转向AI应用开发的全过程。 https://blog.csdn.net/qupengkun/category_13183497.html‍ 关于作者QCoding专注AI应用开发与Java技术实践。持续分享Spring AI、RAG、Agent、企业级AI项目、架构设计与职业成长。