1. 项目定位为什么是 Spring Boot AI 对话接口1.1 HR 政策问答机器人的核心需求拆解这个项目叫“AI全栈12-02”名字里已经透露出完整的信息它属于一条 AI 全栈学习路线的中期实践前一步多半是把模型 API 跑通从这一篇开始进入真正的业务容器——Spring Boot。目标不是写一个玩具 Demo而是做一个 HR 政策问答机器人能够接收员工的自然语言提问并返回关于考勤、请假、报销、社保、公积金等政策的准确回答。需求看起来简单但如果拆开看背后藏着几个必须解决的痛点。第一HR 政策文本通常以 PDF、Word、企业内网公告的形式存在员工想知道“产假到底能休多少天”“年假未休完怎么折算”最快的途径往往是问 HR但 HR 的重复工作量极大。第二政策条文有很强的时效性和边界条件比如“累计工作满一年”不是“入职满一年”如果模型拿不到准确的上下文极易出现幻觉式回答。第三企业内部部署场景中数据不能直接丢给公网大模型接口层面就需要考虑权限、审计和合规问题。所以这个机器人最终的形态不是简单调一下外部 API而是一个带有业务上下文的问答服务用户提出问题服务端从政策知识库中检索相关信息再把这些信息作为参考材料连同问题一起交给大模型由大模型生成最终答案。后面这套机制在业界有个常用说法叫 RAG也就是检索增强生成。在这一课里先把 Spring Boot 工程搭起来跑通第一个对话接口同时把知识库和检索模块留出接口位方便后续迭代。1.2 技术选型的三个关键理由为什么选 Spring Boot而不是 Node.js、Python FastAPI 或者干脆用一个云函数我自己的判断主要有三点。第一企业内部系统的主流技术栈依然是 Java。HR 政策问答机器人通常要接入企业微信、钉钉、OA 门户或者 HR 系统这些系统的后端接口大多是 Java 生态。Spring Boot 能让你把 AI 能力直接嵌入现有微服务体系不需要额外维护一套异构服务身份认证、配置中心、监控链路都能直接复用。第二Spring Boot 对 AI 客户端封装的支持越来越成熟。Spring AI 项目已经把主流大模型 API 统一封装成类似 Spring 风格的模板类比如 ChatClient、ChatModel 这些接口。你不需要自己手写 HTTP 调用、JSON 解析、错误重试只用配置 base-url 和 api-key 就能像操作 JdbcTemplate 一样操作大模型。这对团队协作和代码维护来说优势非常明显。第三Java 的类型系统和工程化能力更适合复杂业务。问答机器人一旦接入知识库、做多轮对话、做日志审计涉及的数据结构和状态管理会变得复杂。Java 的强类型、Spring 的依赖注入和 AOP能帮你把这些逻辑组织得井井有条。说白了用 Python 写个原型很爽但要做成生产级的内部服务Spring Boot 会更稳。2. 环境准备与依赖接入2.1 基础工程骨架搭建在动手写代码之前先把环境准备好。我用的版本组合是JDK 17 Spring Boot 3.2.x Maven。如果你还在用 JDK 8 或者 Spring Boot 2.x建议直接升级Spring AI 的很多特性依赖 Jakarta EE 规范和 Java 17 的语法支持。创建工程有两种方式。一种是去 Spring Initializr 网站生成另一种是在 IDEA 里直接 New Project。我推荐后者因为可以选择 Maven 镜像和本地仓库路径省去后续配置的麻烦。关键依赖如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version1.0.0-M6/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency这里有一个容易踩坑的地方。Spring AI 的版本迭代很快不同版本之间的 API 改动不小。我最早用的是 0.8.1 版本里面的 ChatClient 写法是ChatClient.create(...)到了 1.0.0-M6 就变成了ChatClient.builder(...)。所以如果你是照着网上的教程做一定先确认版本一致否则编译报错会让你怀疑人生。我的建议是直接锁定一个版本号比如 1.0.0-M6后续所有代码都以这个版本为准。工程骨架建好之后先写一个简单的 HealthController 确认 Spring Boot 能跑起来再去做 AI 接入。这一步虽然看起来多余但能帮你把“Spring 启动失败”和“AI 配置错误”这两类问题隔离开排错时不用两头猜。2.2 大模型 API 客户端的接入方式Spring AI 支持很多大模型厂商OpenAI、通义千问、文心一言、智谱等等。接入方式大同小异核心就是配置 base-url、api-key、model 名称这三个要素。我这次用的是国产大模型的兼容接口地址指向其兼容 OpenAI 格式的端点。这么做有一个明显的好处代码层面完全复用 Spring AI 的 OpenAI 客户端只需要把 base-url 改掉。Spring AI 的配置项如下spring: ai: openai: base-url: https://your-model-endpoint.example.com/v1 api-key: ${LLM_API_KEY} chat: options: model: your-model-name temperature: 0.2temperature这个参数值得多说两句。它的取值范围一般是 0 到 2数值越低输出越确定、越保守适合问答机器人这种需要稳定性的场景。我做 HR 政策问答时直接设成了 0.2因为政策回答不需要创造性需要的是准确和一致。如果你做的是文案生成或者头脑风暴类产品再调高到 0.7 以上也不迟。API Key 不要写死在 application.yml 里。这一点我强调过很多次但还是有人会把密钥提交到 Git 仓库。正确做法是使用环境变量比如上面配置里的${LLM_API_KEY}在本地用 IDEA 的 Environment variables 配置在服务器上写到部署环境的系统变量或者密钥管理服务里。3. 核心实现从配置到对话闭环3.1 提示词模板的构造思路对话接口跑通的关键不是代码有多复杂而是提示词模板的设计。HR 政策问答机器人跟通用聊天机器人最大的区别在于它必须限定回答范围不能什么都说也不能编造不存在的政策条款。我设计的提示词模板包含四个要素角色设定、回答原则、知识上下文、用户问题。模板文件放在src/main/resources/prompts/hr-policy.st里你是一名企业 HR 政策智能助手负责回答员工关于考勤、请假、薪酬、福利、报销等制度的问题。 回答时必须遵守以下规则 1. 只根据参考资料中的内容回答不要凭常识推测。 2. 如果参考资料中没有答案明确回复“该问题暂未收录相关政策信息请联系 HR 部门确认”。 3. 回答时使用简洁、清晰的口语化表达避免列大段法律条文。 4. 若问题涉及多个条款分点说明并标注出处。 参考资料 {context} 员工问题 {question}这个模板里有三个变量需要动态填充{context}是检索到的政策片段{question}是用户输入。Spring AI 的 PromptTemplate 可以直接渲染这个模板PromptTemplate promptTemplate new PromptTemplate(templateContent); Prompt prompt promptTemplate.create( Map.of(context, context, question, question) );为什么要把“参考资料”放进提示词而不是直接让模型凭记忆回答这里涉及大模型的一个核心特性模型的知识截止时间有限而且回答问题时会基于训练数据里的统计规律“猜测”答案。HR 政策这种高频更新的内容模型很可能给出过时甚至完全虚构的信息。把政策文本作为上下文喂给模型等于给它划定了回答的边界能把幻觉率压到很低的水平。3.2 服务层的完整代码实现服务层是整个机器人的大脑职责是把控制器传来的问题转换成请求调用大模型再把结果返回。我先把核心代码贴出来再逐段解释。Service RequiredArgsConstructor public class HrPolicyChatService { private final ChatClient chatClient; public String answer(String question) { String context retrievePolicyContext(question); String answer chatClient.prompt() .system(systemSpec - systemSpec.text(loadSystemPrompt())) .user(userSpec - userSpec.text( 问题 question \n\n参考资料 context )) .call() .content(); return answer; } private String retrievePolicyContext(String question) { // 第一阶段先用固定政策文本后续替换为向量检索 return policyRepository.loadAllAsText(); } }这段代码有几个细节需要展开讲。注意我用了ChatClient的链式调用风格system()方法传入系统提示词user()方法传入用户问题和参考资料最后call()触发完整对话。这种写法比手动构造 Prompt 要清晰也更容易扩展多轮对话。retrievePolicyContext方法目前只是简单地把所有政策文本加载出来拼成字符串因为这一课的目的是先跑通接口还没有引入向量检索。但方法名已经暗示了后续演进方向知识库检索。实际做的时候等你把接口跑通了回来替换这个方法接入向量数据库机器人就从“背靠整本文档回答问题”升级成“精准检索片段回答问题”效果会有质的飞跃。3.3 控制器层与参数校验控制器层承担两个职责接收 HTTP 请求以及做基本参数校验。我给了两个端点一个用于单次问答一个用于健康检查。RestController RequestMapping(/api/hr-bot) public class HrPolicyChatController { private final HrPolicyChatService hrPolicyChatService; public HrPolicyChatController(HrPolicyChatService hrPolicyChatService) { this.hrPolicyChatService hrPolicyChatService; } PostMapping(/chat) public ResponseEntityChatResponse chat(RequestBody ChatRequest request) { if (request.question() null || request.question().trim().isEmpty()) { return ResponseEntity.badRequest() .body(new ChatResponse(问题不能为空)); } String answer hrPolicyChatService.answer(request.question()); return ResponseEntity.ok(new ChatResponse(answer)); } GetMapping(/health) public MapString, String health() { return Map.of(status, UP); } }我用了 Java 16 的 record 来定义请求和响应结构代码更精简。ChatRequest只有一个字段questionChatResponse只有一个字段answer。别小看这种写法项目大了之后DTO 数量会爆炸用 record 能少写大量样板代码。校验放在 Controller 层看起来有点简陋但对于一个教学项目足够用了。生产环境你可以引入spring-boot-starter-validation用NotBlank注解配合Valid完成声明式校验。我是故意先用手动校验让你看清逻辑再考虑框架封装不然出了错你都不知道该查哪儿。3.4 配置类的补充说明Spring AI 的自动配置帮我们省了很多事但有时候需要手动调整底层参数。比如设置超时时间防止模型接口卡住导致 HTTP 请求悬挂Configuration public class AiClientConfig { Bean public ChatClient chatClient(ChatClient.Builder builder) { return builder .defaultSystem(你是一名严谨的 HR 政策助手) .defaultOptions( OpenAiChatOptions.builder() .withTemperature(0.2) .withMaxTokens(500) .build() ) .build(); } }超时时间的设置我推荐在环境变量层面处理。Spring AI 底层使用的是 RestClient你可以在 application.yml 中加spring.ai.openai.chat.timeout配置项。这里有个我踩过的坑默认超时时间偏长如果模型接口不稳定一次请求可能挂 60 秒甚至更久用户体验极差。我把超时时间设成 30 秒并且在上游加了接口限流和缓存效果好了很多。4. 测试、调优与常见问题排查4.1 本地跑通后的首批测试用例接口写完启动 Spring Boot用 curl 或者 IDEA 自带的 HTTP Client 发一个请求试试curl -X POST http://localhost:8080/api/hr-bot/chat \ -H Content-Type: application/json \ -d {question: 年假未休完可以折算工资吗}我建议先准备五到六个覆盖不同场景的测试问题不要只测一个就宣布成功。我第一批测试问题包括正常边界类年假未休完可以折算工资吗模糊问题类请假怎么弄知识库外问题公司有没有宠物津贴多条件组合类入职半年申请婚假能休几天敏感话题类最近在裁员吗这五个问题的设计逻辑是第一个验证主流程第二个验证意图识别和追问能力第三个验证“不知道”的兜底能力第四个验证多条件推理第五个验证敏感内容处理。如果这五类都能给出合理回答这个机器人就基本具备上线能力了。实测下来第一个问题模型回答得不错按照政策条款解释了未休年假按日工资 300% 折算。第二个问题模型选择了追问“请问您想问哪类请假”而不是直接猜这归功于系统提示词里的规则。第三个问题模型正确返回了“未收录”提示。第四个问题回答出现了一个小瑕疵对“入职半年”和“婚假”的关联分析不够完整——这也是后续需要引入知识库片段检索的原因。第五个问题模型拒绝回答并引导联系 HR 部门逻辑没毛病。4.2 常见报错与解决方案速查表跑 AI 接口最容易遇到的几个报错我整理成一个速查表都是我亲手踩过的坑。报错信息原因分析解决方案Invalid API keyAPI Key 配置错误或环境变量未加载检查 IDEA 环境变量配置确认没有把$符号写进 ymlConnection timed out模型接口地址不通或网络受限先用 curl 直接测 base-url 是否可达再检查代理设置404 Not Found模型名称不存在或 endpoint 路径不对登录模型服务商后台确认模型标识符是否与代码一致400 Bad Request请求体格式错误context 过长检查提示词模板变量是否全部填充减少参考文本长度JSON parse error返回内容中包含非法 JSON在调用content()前打印原始响应检查模型输出格式BeanCreationExceptionSpring AI 版本冲突或依赖缺失确认spring-ai-openai-spring-boot-starter版本一致这里面最隐蔽的问题是模型名称。很多兼容 OpenAI 格式的服务商模型名称不是通用的gpt-3.5-turbo而是他们自己的标识符比如qwen-plus或者glm-4。我一开始没注意到直接复制默认模型名结果 404 报了很久。排查方法很简单看服务商文档或者用服务商提供的 Playground 发一个请求抓包看实际调用的模型名。另一个隐蔽问题是长文本截断。政策文档动辄几万字全部塞进上下文不仅消耗 token还会让模型注意力分散。Spring AI 默认请求时会自动截断吗并不会。你需要自己控制长度我的经验是单次检索的上下文控制在 1500 字以内超过的部分去掉或分块。这个优化在后续接向量检索时会自然解决但在当前阶段你可以先手动限制retrievePolicyContext返回的长度。4.3 响应延迟与并发的实战优化思路QA 机器人跑通之后你会立刻发现两个问题响应慢、并发能力弱。模型接口的延迟通常是 2 到 5 秒这还只是单次请求的耗时。如果在接口里做了多次模型调用比如先做意图识别再生成答案延迟会翻倍。针对延迟我做了三个层级的优化。第一层是缓存。对于相同的用户问题在短时间内直接返回缓存结果不再调用模型。我用简单的ConcurrentHashMap加过期时间实现了一个极简缓存避免为这个教学项目引入 Redis。实测下来命中率大概 20% 到 30%因为员工反复问的是那几个高频问题比如“年假怎么算”“加班费标准”。第二层是流式输出。把普通请求改成 SSEServer-Sent Events让模型一个字一个字地返回用户看到“正在输入”的反馈心理等待时间会大幅缩短。Spring AI 支持.stream()方法Controller 层返回SseEmitter即可。这个改动不复杂但体验提升非常明显。第三层是异步化。如果业务允许把请求丢进消息队列AI 处理完再回调通知用户不用干等。但这会引入额外的架构复杂度这个阶段的项目不建议做等用户量真正上来再考虑。并发方面Spring Boot 默认的内嵌 Tomcat 最大线程池是 200但模型接口作为外部依赖它的吞吐上限可能远低于 200。如果不对模型接口做保护一旦突发流量涌入很容易把账户限流甚至封禁。我的做法是在 AI 调用外层加一个简单的限流器用RateLimiter限制每秒最多 5 个请求剩下的请求进入队列等待或者直接返回繁忙提示。4.4 日志链路与效果评估排查问题和后续调优都不能靠猜必须看日志。我把日志级别单独调出来logging: level: org.springframework.ai: DEBUG org.springframework.web.client.RestTemplate: DEBUG当日志级别设为 DEBUG 时Spring AI 会自动打印出完整的请求内容和响应内容方便你检查发送出去的提示词是否携带了正确的上下文以及模型返回的原始 JSON 是否正确。生产环境下把级别调回 INFO避免日志膨胀。效果评估这个环节很多教程不会讲但它决定了机器人能不能真正上线。我给每个问答对录入了结构化日志包含问题、标准答案、模型答案、用户反馈四个字段。标准答案是从政策文档中人工抽出的名称用户反馈是从前端收集的“有帮助/没帮助”按钮。每周汇总一次准确率和反馈率低于 80% 的问题组就针对性调整提示词或索引文本。这种“数据驱动调优”的思路比凭感觉改提示词靠谱得多。5. 从单机接口到 AI 全栈的下一步接口跑通只是整个 AI 全栈项目的第一步。从这一课往下走还有三个方向需要继续深入。第一个方向是知识库增强。现在的retrievePolicyContext还是全量加载文本当政策文档变多、变长之后全量塞进上下文既不经济也不高效。标准做法是引入向量数据库如某开源向量库或云上的向量服务把政策文档切块、向量化存储查询时通过相似度检索只拉取最相关的 3 到 5 个片段。这个改动会让回答质量产生质变模型不会再被无关信息干扰。第二个方向是多轮对话记忆。目前的接口是无状态的用户问一个问题就答一个问题不会有对话上下文。但实际使用中员工往往会追问“那试用期呢”“如果我是实习生呢”。这要求系统能把历史对话压缩成摘要或缓存成消息列表在每次调用时一并传入模型。Spring AI 的ChatMemory模块专门干这个接入成本不高。第三个方向是管理后台与审计。企业内部 AI 服务一定要有完整的审计链路。请求日志、操作人、时间戳、回答内容都要可追溯。可以给接口加上基于 OAuth2 或企业内部 SSO 的鉴权让每一条问答都能定位到具体用户。这一步不是锦上添花而是合规要求。以我个人的经验从这一讲往后的每一步迭代难度都不亚于把接口跑通但价值也在成倍增长。特别是知识库增强这一步做完之后你会明显感觉到机器人从“什么都敢说”变成了“有理有据地说”那种体验上的差异用户一眼就能分辨出来。最后分享一个小技巧所有 AI 接口的参数包括模型名称、温度、超时时间都尽量做成可配置的而不是硬编码在代码里。因为模型服务商的版本迭代很快今天能用v1接口明天可能就出了v2把所有开关留在配置层将来升级只需要改一行配置不用改代码重新发布。这也是我在多个项目中验证过的实用经验。