ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从Java后端视角落地提示工程:Spring AI与Spring Boot实战

从Java后端视角落地提示工程:Spring AI与Spring Boot实战 1. 为什么现在必须把提示工程当成一门正经手艺来学这两年我身边做 Java 后端的兄弟十个里有八个在琢磨同一件事怎么把大模型能力塞进自己熟悉的 Spring Boot 项目里。以前我们写接口输入输出都是确定的一个RestController配个 Service 就完事现在业务方张口就是“能不能让系统自己理解用户意图”“能不能自动生成一段文案再落库”你没法再用 if-else 糊弄过去。这时候 Prompt Engineering提示工程就从一个听起来很虚的词变成了每天都要打交道的硬技能。我自己是从一个很土的需求开始接触提示工程的给一个多商户跨境商城做商品标题的自动润色。最开始我天真地以为把用户输入拼成一句话丢给模型就完事了结果线上跑了两天返回的内容要么带一堆营销感叹号要么把品牌名给改没了要么干脆答非所问。后来我才明白提示工程不是“会说话就行”它本质上是一套约束模型行为、稳定输出结构、控制成本与延迟的工程方法。你写的那段提示词其实就是给模型下的接口契约跟你在 Spring Boot 里定义 DTO 是一个道理。这篇文章我想聊的不是那种“十个提示词小技巧”的爽文而是从一个后端工程师的视角把提示工程从概念到落地完整走一遍。我会结合 Spring AI、Spring Boot、Function Calling 这些热搜里反复出现的关键词讲清楚提示词到底该怎么设计、怎么和 Java 代码结合、怎么排查线上那些莫名其妙的返回。适合已经会写 Spring Boot、想认真把 AI 能力接进生产系统的同学也适合刚接触提示工程、被各种玄学调参搞晕的新手。看完你至少能做到知道一段提示词为什么这么写、参数为什么这么设、出问题该往哪个方向查。2. 提示工程的本质给不确定的模型套上确定的壳2.1 从“聊天”到“接口契约”的思维转变很多人对提示工程的理解停留在“跟 AI 聊天聊得好”。这个认知在 demo 阶段没问题但一上生产就崩。原因很简单聊天是开放式的接口是封闭式的。你在 Spring Boot 里写一个对外接口输入字段、输出结构、异常码都是定死的第三方调用方依赖的就是这份确定性。而大模型天生是个概率机器同样的输入两次调用可能给你两种措辞。提示工程要解决的核心矛盾就在这用自然语言这种最不精确的工具去约束一个概率模型让它产出接近确定性的结果。我习惯把它类比成“给一个很聪明但很随性的实习生写工作手册”。你不能只说“帮我处理下订单”你得告诉他你是谁、你的职责边界在哪、输入长什么样、输出必须是什么格式、遇到模糊情况怎么办、哪些事绝对不能做。这份工作手册写得越清楚实习生出错的概率越低。所以我在项目里从来不把提示词当成一段随手写的字符串而是当成一个有版本、有测试、有评审的配置文件。它和你的application.yml一样重要改一个字都可能影响线上几万次调用。2.2 提示词的四层结构角色、任务、约束、示例一段能上生产的提示词我一般拆成四层来写缺一层都会出问题。第一层是角色设定Role。告诉模型它是谁这决定了它的语气、知识范围和自我约束。比如“你是一名跨境电商平台的商品文案助手只负责润色标题不负责翻译、不负责改价”。角色越具体模型越不容易越界。第二层是任务描述Task。用动词开头说清楚要做什么、输入是什么、输出是什么。这里最忌讳模糊比如“优化一下”就是典型的废话模型只能猜。要写成“将输入的原始标题改写为不超过 60 个字符、保留品牌名、去除夸张营销词的中文标题”。第三层是约束条件Constraints。这是提示工程里最值钱的部分也是新手最容易漏的。约束包括格式约束必须返回 JSON、长度约束、内容禁忌不得出现竞品名、失败处理信息不足时返回指定错误码。约束写得越细后期排查越省事。第四层是示例Few-shot。给一两个输入输出样例比写一百字描述都管用。模型是模仿高手你给它看一遍正确示范它照着抄的准确率会明显提升。但示例不能太多三到五个就够了太多会挤占上下文窗口还增加成本。这四层组合起来才是一段完整的、可维护的提示词。我见过太多人只写了第一层和第二层就上线然后抱怨模型不稳定其实问题出在自己没把契约写全。2.3 为什么 Spring AI 让提示工程变得更“工程化”早些年大家调模型都是自己拼 HTTP 请求把提示词塞进 JSON body 里发出去。这种方式能跑但没法管理。Spring AI 出现之后最大的价值不是帮你省了几行 HTTP 代码而是把提示词、模型参数、函数调用这些东西纳入了 Spring 的依赖注入和配置体系。你可以把提示词模板放在资源文件里用PromptTemplate加载可以把模型参数temperature、maxTokens写进配置文件不同环境用不同值可以用Tool注解把 Java 方法暴露给模型做 Function Calling。这意味着提示工程从“运维脚本”升级成了“应用代码”能进 Git、能写单测、能做灰度。这才是它值得后端工程师认真学的原因。3. 提示词设计的核心细节与实操要点3.1 角色与任务描述把模糊需求翻译成可执行指令我接过一个需求业务方原话是“让 AI 帮我们把用户评价分类一下”。这句话直接丢给模型它可能给你返回一段分析文字也可能返回一堆标签完全不可控。我的做法是先把它翻译成工程语言输入是用户评价文本输出是固定枚举值物流、质量、客服、价格、其他每条评价只能归一类无法判断时归入“其他”。翻译完之后提示词就变成了“你是一名电商评价分类器。输入一条用户评价输出它所属的类别只能从以下五个值中选择一个物流、质量、客服、价格、其他。只输出类别名称不要输出任何解释。”你看同样的需求翻译前后模型的表现天差地别。这里有个实操心得任务描述里尽量用“输出”“返回”“只输出”这类明确的动词避免“分析”“理解”“考虑”这种开放式动词。开放式动词会让模型自由发挥而生产系统最怕的就是自由发挥。3.2 约束条件的写法格式、长度、边界与失败处理约束是提示工程的灵魂。我一般从四个维度写约束。格式约束最常见就是要求返回 JSON。但光说“返回 JSON”不够你得给出 schema。比如“返回一个 JSON 对象包含 category 字段字符串和 confidence 字段0 到 1 之间的小数”。Spring AI 里可以配合结构化输出把返回直接映射成 Java 对象省去手动解析。长度约束要具体到数字。“简短一点”是废话“不超过 50 个字符”才是约束。跨境场景尤其要注意中英文混排时字符数计算方式不同最好在提示词里说明按什么口径算。边界约束是防止模型越界。比如“不得修改输入中的品牌名”“不得添加输入中不存在的信息”“不得输出任何网址”。这些负面约束要写得像法律条款一样明确。失败处理最容易被忽略。模型遇到它搞不定的输入时默认行为是硬编一个答案而不是承认自己不会。所以你必须显式告诉它“如果输入为空或无法判断返回 category 为‘其他’confidence 为 0。”这样下游代码才能正确处理异常情况。3.3 示例的选择少而精覆盖边界情况Few-shot 示例不是越多越好。我的经验是三个示例足够一个标准正例、一个容易混淆的边界例、一个失败例。标准正例告诉模型正常情况怎么答边界例告诉它模糊情况怎么处理失败例告诉它什么时候该认输。举个例子做评价分类时我会给这三个示例一条明确的物流投诉正例、一条既像质量又像价格的评价边界例、一条纯表情无文字的输入失败例。这三个覆盖了绝大多数线上会遇到的情况比堆十个相似的正例有用得多。注意示例里的输出格式必须和你要的最终格式完全一致一个标点都不能差。模型会严格模仿示例的格式你示例里多写了个句号它输出就全带句号。3.4 参数调优temperature、topP 与 maxTokens 的取舍提示词写好了参数没配对照样翻车。这三个参数我挨个说。temperature控制随机性范围一般 0 到 1。做分类、抽取、结构化输出这类任务我直接设 0 或 0.1要的就是稳定复现。做文案生成、创意润色可以设 0.7 到 0.9让它有点变化。千万别在需要确定性的任务上用高 temperature那等于自己给自己找麻烦。topP是另一种采样控制和 temperature 二选一调就行别同时大改。我一般固定 topP 默认值只调 temperature。maxTokens控制输出长度上限。这个参数直接关系到成本和延迟设太大浪费钱设太小会把答案截断。我的做法是先估算正常输出的 token 数然后留 30% 余量。比如分类任务输出就几个字设 50 足够文案生成可能几百字设 500 到 800。这里有个坑maxTokens 截断是静默的模型不会告诉你它没说完你拿到的就是一个半截 JSON解析直接报错。所以线上一定要监控解析失败率一旦飙升先怀疑是不是被截断了。4. 把提示工程接进 Spring Boot完整实操流程4.1 环境准备与依赖选型我以 Spring Boot 3.x 加 Spring AI 为例走一遍。选 Spring Boot 3 是因为 Spring AI 对它的支持最完整JDK 至少 17。依赖上核心是spring-ai-core和对应模型厂商的 starter比如对接阿里云百炼就引spring-ai-alibaba-starter。这里提醒一句Spring AI 各版本 API 变动比较频繁2.0 之后不少类名和包路径都调整过锁版本很重要别用动态版本号。配置文件里把模型相关的 key、endpoint、默认模型名放进去不同环境用不同 profile。我习惯把提示词模板单独放一个目录比如resources/prompts/每个模板一个文件用的时候按名字加载。这样做的好处是改提示词不用重新编译 Java 代码运维也能看懂。4.2 用 PromptTemplate 管理提示词模板Spring AI 的PromptTemplate支持占位符替换类似{input}这种。我把前面说的四层结构写进一个模板文件占位符只留真正动态的部分比如用户输入、当前语言、租户 ID。角色、任务、约束、示例这些静态内容全部写死在模板里。PromptTemplate template new PromptTemplate( new ClassPathResource(prompts/category-classifier.st); Map.of(input, userReview, lang, zh) ); Prompt prompt template.create();这样写的好处是提示词和业务代码解耦。产品经理要改约束条件直接改模板文件走配置发布流程就行不用动 Java。我实测下来这种管理方式让提示词迭代速度至少快了一倍。4.3 Function Calling让模型调用你的 Java 方法Function Calling 是提示工程里最容易被低估的能力。它的本质是你在提示词里告诉模型“你有这些工具可用”模型判断需要时会返回一个结构化的调用请求你的代码执行完再把结果喂回给模型让它继续生成最终答案。举个跨境商城的例子。用户问“我这个订单到哪了”模型自己不知道物流信息但它可以调用你暴露的queryOrderStatus(orderId)方法。你在方法上加Tool注解Spring AI 会自动把方法签名转成模型能理解的工具描述。模型返回调用意图框架帮你执行再把结果拼回上下文。Tool(description 根据订单号查询物流状态) public String queryOrderStatus(String orderId) { return logisticsService.query(orderId); }这里的关键在于工具描述要写得像提示词一样清楚。模型是根据描述来判断该不该调用、传什么参数的。描述里要说明方法干什么、参数含义、返回什么。描述写得含糊模型就会乱调或者不调。注意Function Calling 会增加一次甚至多次模型往返延迟和成本都会上升。不是所有场景都值得用简单的信息抽取直接靠提示词就够了只有需要访问外部实时数据时才上工具调用。4.4 结构化输出与结果校验模型返回的 JSON 不能直接信。我踩过的坑包括字段名大小写不一致、数字被写成字符串、该返回数组的返回了单个对象、JSON 外面裹了一层 markdown 代码块标记。所以拿到结果后必须做校验。Spring AI 提供了结构化输出的转换器能按你给的 Java 类型反序列化。但即便如此我仍然会在业务层加一层校验字段是否齐全、枚举值是否在允许范围内、数值是否越界。校验不过的走降级逻辑比如返回默认分类并打点告警。这套组合拳下来线上因为模型输出格式问题导致的故障基本清零。4.5 一个完整的分类接口实现把上面这些串起来一个评价分类接口大概长这样Controller 接收评价文本Service 加载提示词模板、调用模型、解析结构化输出、执行业务校验、返回分类结果。整个过程里提示词负责约束模型行为Java 代码负责兜底和校验两者各司其职。我特意把这个接口的提示词版本号也返回给调用方方便出问题时追溯是哪版提示词产生的这条结果。这个习惯是从一次线上事故学来的当时分类突然大面积出错排查半天才发现是有人改了提示词模板没通知加上版本号之后定位时间从两小时缩短到五分钟。5. 线上常见问题与排查技巧实录5.1 输出格式不稳定从 JSON 解析失败说起这是最高频的问题。表现是模型偶尔返回带 markdown 标记的 JSON偶尔字段缺失偶尔多返回一个解释段落。排查思路分三步先看提示词里格式约束够不够硬有没有明确说“只输出 JSON不要任何其他文字”再看示例里的格式是不是和期望完全一致最后看 maxTokens 是不是设小了导致截断。我的经验是在提示词末尾再强调一遍格式要求效果比只在开头说一次好。模型对末尾内容的注意力更高这叫“近因效应”。另外可以在系统提示里加一句“你的输出会被程序直接解析格式错误会导致系统故障”给它一点“压力”实测能降低格式错误率。5.2 模型“自作主张”越界与幻觉的抑制模型幻觉的典型表现是编造输入里没有的信息。比如润色商品标题时它给你加了个输入里根本没有的卖点。抑制幻觉的核心手段是负面约束加示例。明确写“不得添加输入中不存在的信息”再给一个“输入信息不足时保持原样”的示例。还有一种越界是模型不按你给的枚举值来自己造了个新类别。这种情况要在提示词里把枚举值列全并强调“只能从以下值中选择不得创造新值”。如果还压不住就在代码层做白名单校验不在白名单里的一律归入“其他”。5.3 成本与延迟优化提示词瘦身与缓存提示词越长token 消耗越大延迟越高。我做过统计一段 500 token 的提示词每次调用成本是 200 token 提示词的两倍多。所以提示词要定期瘦身删掉冗余描述、合并重复约束、示例只留必要的。另一个技巧是利用模型的上下文缓存。很多模型厂商对重复的前缀内容有缓存优惠你把静态的提示词部分放在前面动态的用户输入放在后面能吃到这个优惠。我实测下来在调用量大的场景光这一项就能省下可观的成本。5.4 常见问题速查表问题现象可能原因排查方向解决手段JSON 解析失败格式约束不足、被截断检查提示词格式说明、maxTokens末尾强调格式、调大 maxTokens返回内容跑题任务描述模糊检查任务动词是否明确改用明确动词、补充约束编造不存在信息缺少负面约束检查是否有禁止性条款加负面约束和失败示例分类结果不稳定temperature 过高检查模型参数分类任务设 temperature 为 0延迟突然升高提示词变长、工具调用增多对比提示词版本、调用链瘦身提示词、精简工具成本超预期提示词冗余、无缓存统计 token 消耗前缀缓存、删冗余内容这张表是我从多次线上排查里总结出来的基本覆盖了八成以上的问题。遇到新问题先往这几个方向套能省不少时间。6. 提示工程的版本管理与持续迭代6.1 把提示词当代码来管我坚持一个原则提示词必须进 Git必须有版本号必须能回滚。每次修改都要写清楚改了什么、为什么改、预期效果是什么。这听起来有点重但吃过亏就知道值。有一次我们改了一版提示词分类准确率从 92% 掉到 78%因为没记录改动内容只能靠人肉对比折腾了一下午。后来上了版本管理同样的问题十分钟就回滚了。具体做法是提示词模板文件命名带版本比如category-classifier-v3.st同时在代码里记录当前使用的版本。灰度发布时一部分流量走新版本对比两版的准确率和成本数据达标再全量。6.2 建立提示词评测集没有评测集的提示词迭代就是盲人摸象。我一般会攒一个几十到几百条的评测集覆盖正常输入、边界输入、异常输入。每次改提示词先跑一遍评测集看准确率、格式合规率、平均 token 消耗这几个指标有没有退化。评测集不用一开始就很全从线上真实请求里采样就行。我习惯每周从日志里抽一批典型 case 补进评测集慢慢就积累成一个很贴合业务的测试集。这个东西的价值随着时间越来越明显是提示工程从“玄学”走向“工程”的关键一步。6.3 从单轮提示到多步编排业务复杂到一定程度单段提示词就不够用了。比如一个完整的客服场景要先判断意图、再抽取关键信息、再查询数据、最后生成回复这是四个步骤。硬塞进一段提示词里模型会顾此失彼。这时候就要做多步编排每一步一个提示词前一步的输出作为后一步的输入。Spring AI 里可以用链式调用或者自己写编排逻辑。Dify 这类工作流工具也是这个思路只不过它把编排可视化。如果你团队有 Java 基础我建议直接用 Spring AI 在代码里编排可控性更强也方便和现有业务系统集成。提示多步编排会增加调用次数和延迟不是越复杂越好。我的判断标准是如果单段提示词的准确率能稳定在可接受范围就别拆只有当某一步的失败率明显拖累整体时才把它独立出来。7. 我踩过的那些坑和几条实在建议先说一个最典型的坑。早期我做结构化输出提示词里写了“返回 JSON”但没给 schema结果模型返回的字段名每次都不一样有时候是category有时候是type有时候是分类。下游代码根本没法解析。后来我学乖了schema 必须写死在提示词里字段名、类型、取值范围一个不落。第二个坑是关于示例的。我曾经在一个提示词里放了八个示例想着越多越准结果模型开始“过拟合”示例遇到和示例稍有不同的输入就硬往示例上套。删到三个之后反而更稳。示例是引导不是穷举这个度要把握好。第三个坑是忽略了对中文和特殊字符的处理。跨境场景里商品标题经常中英混排还带各种符号。模型有时候会把中文标点转成英文标点有时候会把全角字符转半角。这些细节在提示词里要专门说明否则下游做字符串匹配时会出问题。几条实在建议提示词改动一定要走评测别凭感觉模型参数别乱调先固定 temperature 为 0 把提示词调好再考虑要不要放开随机性Function Calling 的工具描述要当提示词一样认真写线上一定要有格式校验和降级逻辑别把模型的输出直接当可信数据用。最后分享一个我最近在用的技巧把提示词里的约束条件按重要性排序最重要的放最前面和最后面中间放次要的。模型对开头和结尾的注意力最高这个规律在长提示词里尤其明显。我调整之后关键约束的遵守率有明显提升。这个内容后续还可以往多模态提示、Agent 自主规划这些方向扩展等我把手上的项目跑稳了再单独写一篇。
返回列表