Spring AI 实战复盘:单测全绿,为何 AI 产品仍会失效

一位开发者在 Spring AI 项目中写了 80 多个单元测试,全部通过,但用户仍遇到 AI 自行生成「岗位描述为空」的回复。该案例显示,AI 应用工程化不能只依赖传统单测,还需要行为测试、评估基线和完整的上下文设计。

在 AI 应用开发中,单元测试全部通过并不意味着产品可以正常使用。一位开发者在基于 Spring AI 2.0、Spring AI Alibaba 和通义千问 qwen-plus 构建岗位分析工具时,写了 80 多个单元测试,全部为绿色,但用户实际使用时,AI 却返回了「你提供的岗位描述为空」这样的提示。更关键的是,这句话并不在代码里,而是模型自行生成的回复。

这一案例折射出 AI 应用工程化中的一个核心问题:传统测试可以验证代码逻辑,却很难验证模型是否按照预期理解输入。围绕 Spring AI 的这次实践,开发者总结了 14 个踩坑记录,涉及依赖配置、上下文管理、输出解析、提示词设计、测试方法和评估体系等多个层面。

单测全绿,产品为何仍然失效

该项目名为 ai-augmented-employer-toolkit,功能是输入一段岗位描述,由 AI 分析该岗位受 AI 影响的程度,并给出转岗路径建议。项目背后有一个明确理念:企业引入 AI 并不必然等于减少人员。文中提到,格力在引入自动化时曾找出老职工技能与新岗位之间的最大公约数,实现转岗成功率 100%,收入平均增长 8%,没有老职工因技术替代掉队。开发者希望将这一理念产品化。

问题出现在一次提示词注入防护设计中。开发者为了隔离用户输入,将说明文字和用户提交内容一起放进了 user message,并用 <<>> 与 <<>> 作为包裹标记。模型在理解时并未把这些标记视为纯技术结构,而是将其当作可讨论的上下文。当用户输入为空时,模型生成了「你提供的岗位描述为空(<<>> 与 <<>> 之间无内容)」这样的回复。

更值得警惕的是,开发者在代码库中搜索这句回复时,并没有找到对应文案。也就是说,这句话不是系统预设,而是模型基于上下文自行生成的表达。原有的单元测试只验证了字符串拼接是否正确,没有验证模型拿到这些提示词后会如何回应。

为此,开发者补充了行为测试:真实调用模型,并检查回复中是否出现包裹标记、「岗位描述为空」「之间无内容」等元信息。这类测试不再只验证机制,而是验证模型行为。对于 LLM 应用而言,「单测通过」只能证明代码按既定逻辑执行,不能证明模型会按照开发者期望的方式理解输入。

依赖、上下文与输出处理的工程陷阱

除了测试问题,该项目还暴露出 Spring AI 应用在依赖和上下文处理上的多个细节风险。

在依赖配置方面,spring-ai-alibaba-starter-dashscope 会自动注册多模态嵌入和音频相关的自动配置类,但项目本身并不需要这些能力,最终通过显式排除相关自动配置解决。另一个问题是,spring-ai-starter-vector-store-pgvector 传递依赖了 spring-boot-starter-jdbc,导致 Spring Boot 触发 DataSourceAutoConfiguration,即使项目并未使用数据库也会启动失败。开发者指出,Maven 的 optional 只阻止向下游项目传递,当前项目仍会引入相关依赖,因此最终移除依赖,改用 SimpleVectorStore 内存向量库。

在知识库处理方面,项目最初使用 TokenTextSplitter 按 token 数切分文档,但检索出来的知识片段语义不完整,表格数据经常只剩半张表。原因在于这种方式不看文档结构。开发者随后换成 Spring AI Alibaba 提供的 RecursiveCharacterTextSplitter,按照「空行 → 换行 → 空格 → 字符」逐级尝试切分,以尽量保持段落和表格行完整。

在输出处理方面,结构化输出与流式输出也发生了冲突。使用 BeanOutputConverter 时,生成的 JSON Schema 混进了流式文本,导致用户看到大段 JSON Schema 和 JSON 原文滚屏。开发者最初尝试创建两个 ChatClient Bean,一个用于同步,一个用于流式,但后来发现该方案无效,因为格式指令并不是在 ChatClient 中注入,而是在 Service 层手工拼进 user message。最终,开发者通过过滤中间内容,并在流式结尾单独输出压缩后的 JSON 结果来解决问题。

另一个输出问题出现在 SSE 场景中。模型如果输出带缩进换行的格式化 JSON,SSE 会按换行分隔事件,前端收到的结构化结果可能被切成多个 data 事件,拼回去后不是合法 JSON。解决方式是在输出和接收两端都做防护。

AI 应用测试需要引入行为验证与评估基线

这次实践还涉及测试和评估体系的设计。开发者发现,本机配置 AI_DASHSCOPE_API_KEY 后,每次执行 mvn test 都会触发 9 分钟的评估基线测试。原因是仅使用了 @EnabledIfEnvironmentVariable 判断 key 是否存在。最终,开发者增加了 RUN_LIVE_EVAL 显式开关,只有两个条件同时满足,才会运行真实模型调用测试。

批量测试也会受到限流影响。22 条评估用例跑到第 20 条左右开始出现限流报错,原因是应用默认限流容量为 20,主要面向线上请求设计。开发者选择在批量测试中通过 @SpringBootTest 单独放宽参数,而不是修改生产默认值。

在评估结果上,第一次跑出的平均相关性只有 0.36。开发者最初认为模型表现不佳,但随后发现,评委比对的是「概括性的分析结论」和「检索到的知识库片段原文」,二者文本形态天然不同,重合度不高属于正常情况。由于项目没有人工标注的标准答案,该指标被作为同口径下的相对基线使用:修改提示词前后各跑一次,对比均值。回归门槛设置为 0.3,用于发现明显劣化,而不是追求绝对高分。

这些经验表明,AI 应用的测试不能沿用纯后端项目的思路。对于传统服务,输入输出明确,断言可以围绕函数返回值展开;而在 LLM 应用中,模型输出具有概率性和语义性,测试必须覆盖模型理解、提示词结构、外部依赖和输出解析等环节。

对 AI 工程化的启示

开发者最后总结了四条经验。第一,先定位问题,再动手修改。曾有一次他以为问题在 ChatClient,实际在 Service,错误诊断导致修复方向偏移。第二,规则归 system prompt,数据归 user message,不要在用户输入中向模型解释包装结构,否则可能引导模型讨论结构本身。第三,测试要验证行为,而不是只验证机制。第四,可选能力必须有降级路径,缺少 Bean 不应导致启动失败,可以通过 ObjectProvider.getIfAvailable() 和开关实现自动降级。

他还提到,自己使用 Spring AI Alibaba 数月,代码中没有直接引用 com.alibaba.cloud.ai,只将其当作模型驱动使用。直到查看 jar 包后才发现,其中已经提供 Rerank、知识库加载等更多能力。随后,他将部分能力接入项目,并全部设计为可选项:默认使用 Spring AI 原生实现,开启配置并注入 RerankModel 后才升级,缺少模型时自动降级,不影响启动。

对于 AI 应用开发者而言,这次踩坑记录的价值不在于某个具体框架的使用技巧,而在于提醒团队重新定义「测试通过」。在 LLM 系统中,绿色单测只是工程基础,真正需要验证的是模型在真实输入下的行为是否符合产品预期。提示词、上下文、外部依赖、集成测试和评估集,都应成为质量保障体系的一部分。

原创文章,作者:点点,如若转载,请注明出处:https://www.dian8dian.com/spring-ai-shi-zhan-fu-pan-dan-ce-quan-lyu-wei-he-ai-chan

Like (0)
点点的头像点点
Previous 13小时前
Next 2025年4月20日

相关推荐