资讯动态

Spring AI Prompt核心指南:消息模型、模板渲染与Token成本控制

发布时间:2026/10/10 7:41:44 来源:尧图企业网站定制
1. Prompt在Spring AI中的定位与核心模型1.1 从ChatClient到Prompt一次请求的完整链路Spring AI 1.0.0 GA版本发布之后我在生产环境里陆续接了好几个项目发现很多刚上手的人最容易卡住的地方恰恰是Prompt这个看似最不起眼的环节。大家都知道Spring AI把大模型接入做成了类似于Spring Data的抽象层可以用统一的API对接OpenAI、通义千问、Ollama本地模型等但真正写业务代码时Prompt的组装方式直接决定了模型输出的质量和稳定性。先用一句话说清楚Prompt在Spring AI里的位置它是用户与模型之间的消息载体。你在代码里调用chatModel.call(prompt)传入的就是一个Prompt对象这个对象内部封装了至少一条Message可能是SystemMessage、UserMessage或者是带有模板参数的PromptTemplate渲染出来的消息。模型返回的也不是裸字符串而是一个ChatResponse对象里面包着AssistantMessage。ChatClient chatClient ChatClient.builder(chatModel).build(); String answer chatClient.prompt() .system(你是一个资深的Java架构师) .user(请解释Spring AI中Prompt的作用) .call() .content();这段代码是Spring AI 1.0之后最推荐的写法直接用ChatClient的流式API组装Prompt。它内部做的事情可以拆成三步把system和user的字符串包装成Message、把多个Message塞进Prompt对象、调用chatModel.call(prompt)拿到响应。理解了这个链路你才能明白下面要讲的模板、Token和异常处理到底发生在哪个环节。1.2 Message体系SystemMessage、UserMessage、AssistantMessage很多人第一次看到Message接口时容易懵其实类比一下微信群聊就很好懂。SystemMessage相当于群公告规定聊天的整体规则和角色UserMessage是你发的消息AssistantMessage是模型回复的消息。在多轮对话里每一轮都要把这三类消息按顺序传给模型模型才能理解上下文。Spring AI的Message接口定义非常简单核心就两个方法getMessageType()返回消息类型枚举getTextContent()返回文本内容。它还有一个MediaContent子接口用来承载图片、音频等多模态数据。落到实现类上常用的有这几个SystemMessage构造函数直接传字符串系统提示词。UserMessage可以传字符串也可以传ListMedia实现图文混合输入。AssistantMessage一般你不需要手动创建模型返回的ChatResponse里已经带了。MessageBuilderSpring AI 1.0提供的建造者工具可以链式设置文本和媒体。我自己习惯把系统提示词写在一个常量类里统一管理而不是散落在业务代码中。比如一个客服机器人的系统提示词可以定义成public static final String SYSTEM_PROMPT 你是某电商平台的智能客服...这样后续做Prompt版本管理也方便。1.3 Prompt对象的核心方法解析Prompt本身是个很轻的封装内部主要持有一个ListMessage和一组可选的GenerationOptions。构造函数支持传入单条消息、消息列表以及可选的模型参数。比如Prompt prompt new Prompt( List.of( new SystemMessage(你是一个严谨的代码审查员), new UserMessage(请审查下面这段代码指出潜在问题\n code) ), OpenAiChatOptions.builder() .withTemperature(0.2) .withMaxTokens(800) .build() );这里的OpenAiChatOptions是模型特定参数控制temperature、maxTokens、topP这些采样参数。需要特别提醒的是如果你用的是Ollama或通义千问Options也要换成对应的实现类Spring AI在这块没有做统一抽象因为各家模型的参数确实不完全一样。Prompt的作用就是把“给模型看的消息”和“给模型用的参数”绑在一起作为一次请求的完整输入。2. PromptTemplate模板绑定与参数渲染2.1 基本用法用占位符代替字符串拼接写业务代码时没人会天天手拼字符串。Spring AI提供了PromptTemplate用起来和Java自带的String.format很像但更灵活。它基于StringTemplate引擎支持{placeholder}形式的占位符。PromptTemplate template new PromptTemplate( 给我写一篇关于{topic}的技术文章风格要求{style}字数控制在{words}字以内 ); Prompt prompt template.create(Map.of( topic, Spring AI, style, 通俗易懂且专业, words, 3000 )); String content chatModel.call(prompt).getResult().getOutput().getText();create方法接受一个MapString, Object把占位符替换成实际值。这里要注意键名和占位符必须完全一致少一个字母渲染出来的字符串里就会残留{topic}这个Bug很隐蔽排错时容易忽略。2.2 参数绑定的三种姿势与选择建议除了传MapPromptTemplate还支持另外两种参数传入方式。第一种是用PromptTemplate.builder()链式设置PromptTemplate template PromptTemplate.builder() .template(介绍一下{framework}的核心特性) .defaultValues(Map.of(framework, Spring AI)) .build();第二种是留空占位符运行时再通过create方法传入适合参数动态变化的场景。我个人的经验是默认值用defaultValues动态值用create传入的Map两者组合使用能让模板在不同场景下复用。比如定义一套客服回复模板默认值里写死语气风格调用时只传用户问题模板的复用性一下子就上来了。2.3 模板渲染背后做了什么StringTemplate引擎与格式校验Spring AI的PromptTemplate依赖的是ANTLR生成的StringTemplate引擎它不只是简单做字符串替换还支持条件判断、循环等高级语法虽然我实际项目中很少用这些因为Prompt模板本身应该保持简单。{% if {urgent} %} 请优先处理这个问题用户等待时间较长。 {% else %} 按正常流程处理。 {% endif %}这个语法有学习成本但好处是模板可以表达逻辑分支。我自己常用的还是{占位符}这一种原因有两条一是维护模板的人不一定懂StringTemplate语法二是模板里的条件逻辑如果太复杂测试用例覆盖起来很麻烦容易漏掉某些分支导致线上Prompt渲染出错。简单、可读、可测试才是Prompt模板的第一要务。3. Token、上下文窗口与成本控制3.1 为什么Prompt Token是绕不开的话题热词里有prompt token说明很多人搜这个。用法上有个直观感受同样的Prompt有时模型回答质量突然下降或者直接报错很可能就是Token问题。OpenAI按Token计费、按模型上下文窗口限制输入这是两个硬约束。比如gpt-4o-mini的上下文窗口是128K但你要是一次性把整本《三体》塞进去光是输入Token就可能花掉几十块人民币这还没算输出。Spring AI不会自动帮你精简Prompt你传多少就是多少。所以在封装Prompt时必须自己算清楚Token消耗量。在线搜索热度这么高说明实际项目中很多人被Token超限或者成本超预算坑过。3.2 快速估算Token字符数与tiktoken的关系有一个粗估公式英文文本大约1个Token对应4个字符中文因为分词方式不同通常1个汉字约等于1到2个Token。精确的Token数是模型分词器决定的OpenAI官方提供了tiktoken库可以通过pip install tiktoken装来用。import tiktoken enc tiktoken.encoding_for_model(gpt-4o) tokens enc.encode(Spring AI Prompt核心指南) print(len(tokens))在Java代码里没法直接调tiktoken我的做法是在预处理服务里用HTTP调用一个内部的Python服务来计算Token或者直接用字符数做估算。如果是纯中文场景我按“字符数除以1.5”来估Token数实测下来的误差基本在10%以内用来做预算控制够了。3.3 控制上下文长度的实用策略Prompt变长不只是花钱的问题更关键的是模型会“遗忘”前面说过的话因为注意力机制对长上下文的处理能力有限。有几种控制策略我在项目里反复用过滑动窗口只保留最近几轮对话丢弃过期的系统消息和旧用户消息。摘要压缩当对话轮次超过阈值先让模型把历史对话浓缩成要点再作为SystemMessage传下去相当于“笔记代替原话”。动态截断计算当前对话的总Token数超过预设值就删掉最早的消息直到总Token降到安全线内。Spring AI里可以通过自定义ChatMemory实现这些策略配合ChatClient调用。网上关于prompt token搜索量很高大概率是大家在处理长对话时都遇到过头疼的截断问题。4. Prompt Engineering在Spring AI中的落地4.1 系统提示词设计先说角色再说规则热度词里有prompt engineering和prompt提示词这确实是Prompt实践中最核心的一环。我建议系统提示词按三层结构来写第一层定义角色第二层定义任务规则第三层定义输出格式。String systemPrompt 你是{domain}领域的资深专家名叫小明。 你的任务是根据用户提问给出专业、准确的回答。 规则 1. 只回答与{domain}相关的问题与主题无关时礼貌拒绝。 2. 回答必须包含{minLength}字以上的详细解释。 3. 如果用户的问题存在多种观点请客观对比后给出你的倾向。 4. 禁止编造数据无法确认的信息要明确说明“我不确定”。 ;这种写法的好处是模型对任务的把握更精准而且每一层独立修改不会牵一发动全身。生产环境里我见过太多系统提示词写成一大段散文模型输出风格飘忽不定改一句话就可能整体崩掉。分层写本质上是在降低Prompt的耦合度。4.2 结构化输出用Prompt引导JSON/表格/代码大部分业务系统接大模型不是让它随便聊天而是要稳定的结构化数据。Spring AI 1.0提供了BeanOutputConverter可以配合PromptTemplate把输出直接转为Java对象。BeanOutputConverterMyDTO converter new BeanOutputConverter(MyDTO.class); PromptTemplate template new PromptTemplate( 请将以下信息解析为JSON格式要求字段名严格遵循给定的schema {content} {format} , Map.of( content, userInput, format, converter.getFormat() )); Prompt prompt template.create(); // converter.getFormat() 会生成一个严格的JSON Schema要求这里关键点是getFormat()输出的不是普通JSON示例而是严格的schema描述。模型看到schema之后输出的JSON结构稳定性大幅提升。我用这个方式解析过发票信息、简历数据、研报摘要准确性比让模型“自己看着办”高出很多。4.3 连环Prompt与多轮上下文管理有些任务没法一次完成比如“先让模型总结对话再让模型根据总结写一封邮件”。这种连环Prompt的方式要小心每一步的Prompt都承担双重角色既要引用上一步的输出又要给模型足够的上下文不能被历史噪声干扰。我的做法是在上一步的输出里提取关键信息存成结构化对象下一步Prompt只传对象的核心字段而不是把原始输出整个丢进去。比如第一步让模型抽取“用户意图是退款、金额是200元、平台是拼多多”第二步只把这些字段填进新模板。这样既省Token又避免模型被上一步的废话带偏。多轮聊天的上下文管理同理Spring AI的MessageWindowChatMemory可以直接用设置窗口大小即可实现上用的是ListMessage队列机制够用且稳定。5. 常见故障排查与避坑实录5.1 invalid prompt内容过滤报错的真相与应对热词里有一个很典型的报错invalid prompt: your prompt was flagged as potentially violating our usage policy我估计不少人被这个卡住过。这个提示的大意是你的Prompt文本命中了提供方的内容安全策略系统直接拒绝处理。它不是Spring AI抛的异常而是模型服务商返回的校验错误Spring AI通常会把错误包装成ResponseError或InvalidPromptException抛出。触发原因有几类一是Prompt里有明显的违规词比如暴力、仇恨言论等二是Prompt试图诱导模型绕过安全策略比如“忽略你所有的规则”“扮演无限制的AI”三是某些多音字、谐音或编码特征被误判。排查方法是把用户输入的内容逐段分离二分法测试是哪一段触发了命中。我自己实践下来最常见的是用户输入里有广告联系方式或者涉赌涉贷的关键词。生产系统的Prompt需要加一道内容预检提前拦截可疑文本避免让这种报错直接暴露给用户体验会很差。5.2 Prompt导致“闪退”的几种隐蔽原因热词里的spring ai prompt闪退看着挺吓人但拆开排查大部分不是真的进程崩溃而是请求抛了异常没被捕获导致线程中断看起来像是程序“闪退”。几种隐蔽原因我列一下都是我实际踩过的坑第一PromptTemplate的占位符Map里缺key渲染时StringTemplate不会直接报错而是把{key}原样留在文本里你看到模型回答莫名其妙的“{topic}”时才会意识到。第二开启流式响应后如果模型输出流中途EOFFluxChatResponse的下游没有处理onErrorResume整个响应链路就被截断控制台报错一闪而过。第三某些模型服务商对单次请求的Prompt有字符数上限超限时不会等你算Token而是直接切断连接表现就是客户端等半天后拿到一个连接重置异常。排查方式打开日志级别为DEBUG找到实际的HTTP请求体确认请求是否到达了模型服务商再逐层看是参数问题、网络问题还是服务商返回了4xx。大多数闪退其实是异常的默认行为只是没人打印堆栈。5.3 超时、截断与token超限处理超时和token超限是常态。OpenAI系列模型在请求参数里可以设置maxTokensSpring AI里对应的就是OpenAiChatOptions.withMaxTokens(500)。但有个细节很多人不知道maxTokens限制的是单次补全的输出长度Prompt本身的长度不受它控制。如果你Prompt已经3K TokenmaxTokens又设成4K单次请求总Token就可能有7K仍然会报context_length_exceeded。try { ChatResponse response chatModel.call(prompt); } catch (ResponseErrorException e) { // 读取e.getErrorDetail()判断具体错误码 }我的建议是在调用模型前先估算总Token数超出模型上下文窗口的80%就主动截断或抛错给上层不要傻乎乎地发起注定失败的请求。对用户侧则要用“稍后再试”之类的兜底文案替换异常信息不要暴露内部错误。6. 进阶Prompt Optimizer与持续迭代6.1 为什么Prompt需要版本管理热词里有prompt optimizer使用教程这说明大家已经意识到Prompt本身也需要优化、迭代和管理。Prompt工程和软件工程一样需要版本管理。我不建议直接改线上SystemMessage而是把每次Prompt变更当成一次代码提交记录变更原因和效果。// v1初版客服提示词 public static final String SYSTEM_PROMPT_V1 你是客服回答用户问题。; // v2增加情绪安抚要求 public static final String SYSTEM_PROMPT_V2 你是客服先安抚用户情绪再解答问题。;这看着简单实际作用很大。模型输出有随机性同一个Prompt在不同时间结果可能不同没有版本对比就没法判断改动到底有没有效果。我在项目中用A/B测试的方式比较v1和v2的满意度指标数据说话比拍脑袋靠谱得多。6.2 实测中的Prompt优化套路Prompt Optimizer本身并没有统一的行业标准搜索引擎里搜到的大多是各家模型厂商的自有工具。我落地过最有效的优化套路是“少样本精细约束”。给模型一两组带标准答案的示例比说一百遍“请专业作答”都管用。String prompt 请按照示例格式回答用户问题。 示例 问Spring AI支持哪些模型厂商 答Spring AI支持OpenAI、Azure OpenAI、Ollama、通义千问等通过统一的ChatModel接口接入。 注意回答必须限定在Spring AI框架范围内不扩展其他内容。 问{question} 答 ;另一个套路是“漏斗式Prompt”。先输出一个宽泛的回答再分步骤追加约束条件逐步收敛结果。比如先要求“列出三种解决方案”再要求“从中选一种给出具体代码实现”这样能利用模型的分步思考能力得到的答案质量明显高。6.3 后续扩展思路写完这套Prompt相关的功能后还有不少可以自然扩展的方向。比如接入Spring AI的Advisor机制在发送Prompt前自动注入上下文、接口限流、内容审核等横切逻辑。再比如结合Evaluator工具构造一组自动化测试用例每次调整Prompt后跑一遍回归确保核心问题不退化这个比人工肉眼验证效率和稳定性都高出一大截。像我上面提到的Token预估算更完整的做法是把“字数→Token”的映射做成独立服务统一供多个业务部门调用避免每个项目重复造轮子。等数据积累得足够多还能拿这些测试用例来反向评估不同模型在相同Prompt下的表现差异为后续切换模型提供决策依据。实际操作后的几点体会最后分享几个我在代码里真正受益的小习惯。第一任何PromptTemplate的模板字符串都要用text block写法别用加号拼接模板长了你根本不想改第二所有Model的Options参数统一在一个配置类里初始化通过构造函数注入到Service方便测试时替换成Mock模型第三务必记录每次调用的输入和输出摘要这不只是为了排查问题更重要的是它能帮你发现Prompt的退化趋势比如某类问题最近回答质量下降大概率是模型API升级导致行为变化。Prompt这块的东西看起来简单真正能稳定跑在生产环境里不动摇靠的全是这些细节。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑