
1. 这不是“写提示词”是重构人与AI协作的底层逻辑Prompt Engineering提示工程这个词最近半年在Java工程师群里刷屏频率已经快赶上“OOM”和“线程池参数怎么设”了。但很多人点开教程第一眼看到“请用清晰、具体、带约束的句子描述任务”就默默关掉了页面——这哪是技术这不就是语文课造句练习吗我干了八年Java后端去年第一次给大模型写prompt时也这么想。直到我把一个原本需要3小时人工核对的合同条款比对任务压缩到47秒自动生成带依据标注的差异报告才真正明白提示工程不是教AI怎么听懂人话而是教人怎么用工程化思维把模糊意图翻译成AI可执行的确定性指令流。它和Spring AI、RAG、Java生态深度咬合不是独立技能树而是嵌入在Java开发者日常开发链路里的新基础设施。比如你用Spring AI调用Qwen3.7做客服问答真正的瓶颈往往不在模型响应速度而在你传给它的prompt里漏掉了“仅基于知识库内容作答禁止编造”的硬性约束再比如你用Langchain4j搭简易RAG检索结果明明有答案但LLM却输出“我不知道”问题八成出在prompt里没把检索片段和用户问题的逻辑关系显式建模。这不是玄学是可测量、可调试、可版本管理的工程实践。本文不讲“5个万能模板”而是带你从Java工程师视角拆解一套真实生产环境里跑得通的Prompt Engineering方法论怎么设计prompt结构、怎么用Java代码动态组装、怎么和RAG pipeline耦合、怎么验证效果、怎么应对Qwen3.7这类国产模型的特性。所有案例代码都基于Spring AI 2.0.1 Alibaba百炼接入实测连本地Ollama部署的RAG知识库怎么和Java服务对接都给你写清楚。如果你正被“AI写代码规则设定提示词工程”这种组合需求压得喘不过气或者面试官突然问“RAG知识库能存图片吗”这篇文章就是你手边那本翻烂了的《Java核心技术卷IIIAI协同篇》。2. 提示工程的本质把非结构化意图转译成结构化指令流2.1 别再背模板了先理解AI的“认知边界”很多教程一上来就列“角色-任务-约束-输出格式”四要素模板这就像教人开车先背《道路交通安全法》全文。真正卡住Java工程师的从来不是记不住格式而是不理解为什么必须这么写。举个最典型的例子你在Spring AI里调用Qwen3.7做日志分析输入是“找出异常堆栈最多的三个服务”如果直接这么发模型大概率会返回一段泛泛而谈的分析甚至编造不存在的服务名。为什么因为Qwen3.7这类大语言模型本质是个概率预测器它没有“查找”“统计”“排序”这些计算能力它只能基于训练数据中的模式猜测你最可能想要什么答案。它看到“最多”就倾向于生成高频词看到“服务”就从常见微服务名里挑几个凑数。真正的提示工程第一步是识别并填补AI的认知盲区。这个盲区具体表现为三类缺失上下文缺失模型不知道你指的“服务”是K8s里的Deployment名还是Spring Boot应用名还是数据库实例名操作缺失模型不会自动执行“分组-计数-排序”这个链式操作它需要你把每一步都拆解成它能理解的语言约束缺失模型默认可以自由发挥你不说“禁止编造”它就会为凑满字数而胡说。所以一个合格的prompt不是描述目标而是描述路径。比如上面的日志分析需求有效prompt应该长这样你是一个运维分析助手请严格按以下步骤处理 1. 从提供的日志片段中提取每一行末尾的方括号内字符串如[order-service]作为服务标识 2. 统计每个服务标识出现的次数 3. 按出现次数降序排列取前3名 4. 输出格式为纯文本每行一个结果格式为“服务名:次数”不加任何解释性文字。 --- 日志片段 2024-06-15 10:23:45 ERROR [user-service] java.lang.NullPointerException 2024-06-15 10:23:46 WARN [payment-service] timeout 2024-06-15 10:23:47 ERROR [order-service] java.lang.NullPointerException ...看到区别了吗这里没有“请找出”而是明确告诉AI“提取-统计-排序-输出”四步动作没有“最多”而是定义“降序排列取前3”没有模糊的“服务”而是锁定“方括号内字符串”这个可解析的结构。这背后是典型的指令驱动Instruction-driven范式它把AI从“猜你想问什么”的被动应答者变成“严格执行步骤”的主动执行者。Spring AI 2.0.1的AiResponse接口设计正是为了承接这种结构化指令流——它的content字段接收的就是这种可编程的、带明确操作语义的文本。提示Java工程师最容易忽略的一点是prompt里的“步骤”不是给人看的是给AI执行的。所以步骤编号必须用阿拉伯数字1. 2. 3.不能用中文一二三因为模型对数字序列的模式识别更稳定动词必须用“提取”“统计”“输出”等无歧义动作词避免“分析”“理解”“思考”这类抽象词。2.2 Java视角下的Prompt分层架构从静态模板到动态装配在Java项目里把prompt写死在代码字符串里是初级陷阱。真正的工程化是建立一套分层、可复用、可测试的prompt管理体系。我们以Spring AI为基础构建三层结构基础层Base Prompt定义模型角色、能力边界、输出规范。这是所有prompt的“宪法”比如针对Qwen3.7我们会固定写入你是一个严谨的Java后端开发助手只基于提供的上下文信息回答问题。禁止编造、推测或引用未提供的知识。输出必须为纯文本不加markdown格式不加解释性语句。这段话解决了模型的“幻觉”问题是RAG场景的基石。它必须前置且不可省略。领域层Domain Prompt封装业务逻辑模板。比如合同比对场景我们会定义一个ContractDiffPromptTemplate类它不包含具体数据只定义结构public class ContractDiffPromptTemplate { private static final String TEMPLATE 你是一个法律合规审查员请严格按以下步骤比对两份合同条款\n 1. 对照条款编号逐条检查A合同第%s条与B合同第%s条是否一致\n 2. 若不一致指出差异类型新增/删除/修改并引用原文\n 3. 输出格式每条差异占一行格式为条款%s vs %s: [差异类型] - [原文A] / [原文B]。\n ---\n A合同条款%s\n B合同条款%s; public String build(String clauseA, String clauseB, String numA, String numB) { return String.format(TEMPLATE, numA, numB, numA, numB, clauseA, clauseB); } }这里用String.format动态注入变量比拼接字符串更安全也便于单元测试。实例层Instance Prompt运行时组装。在Service层我们调用ContractDiffPromptTemplate.build()传入从数据库查出的具体条款文本生成最终发送给AI的prompt。这个过程可以和Spring的Value、ConfigurationProperties结合实现prompt参数的外部化配置。这种分层的价值在于把“写prompt”变成了“写Java代码”。你可以对ContractDiffPromptTemplate写JUnit测试验证不同输入下生成的prompt是否符合预期可以在application.yml里配置不同环境的prompt变体如测试环境启用debug模式输出中间步骤甚至可以用AOP拦截prompt生成过程做审计日志。这才是Java工程师熟悉的工程化路径而不是在Notepad里反复改字符串。注意Spring AI 2.0.1的ChatClient支持Message对象其中SystemMessage对应基础层UserMessage对应实例层。千万别把基础约束写进UserMessage否则每次请求都重复传输既浪费带宽又增加模型混淆风险。正确做法是ListMessage messages Arrays.asList( new SystemMessage(basePrompt), // 基础层一次定义 new UserMessage(instancePrompt) // 实例层每次动态 );2.3 RAG不是“加个知识库”就完事它是Prompt的增强外挂现在90%的RAG教程都在教你怎么用Langchain4j把PDF切块、向量化、存进ChromaDB。但没人告诉你RAG真正的价值不在于“检索到了什么”而在于“怎么把检索结果喂给LLM”。很多团队花了两周搭好RAG pipeline结果发现AI还是胡说八道问题就出在prompt设计上。我们来看一个真实案例某金融客户要做“监管政策问答”知识库存了300份PDF文件。最初prompt是根据以下监管政策回答问题{retrieved_context} 问题{user_query}结果模型经常从检索片段里断章取义或者把不同文件的条款混在一起解读。后来我们重构prompt加入三层强化来源锚定在每个检索片段前加文件标识如[文件银保监发〔2023〕12号 第5条]并在prompt里强调“回答必须严格引用标注的文件来源”矛盾消解添加指令“若检索结果中存在相互冲突的条款需明确指出冲突点并说明最新生效版本”推理显式化要求模型“先复述相关条款原文再基于条款进行推理最后给出结论”。重构后的prompt长这样你是一个持牌金融机构的合规顾问必须严格依据提供的监管文件作答。请按以下步骤处理 1. 审阅所有标注了文件来源的条款格式[文件XXX] 条款内容 2. 若问题涉及多个条款需逐一分析其适用性 3. 若条款间存在冲突明确指出冲突文件及条款编号并依据“新法优于旧法”原则判断效力 4. 输出结构先写“依据条款”列出所有引用的原文再写“分析”说明推理过程最后写“结论”给出明确答复。 --- {retrieved_context} --- 问题{user_query}这个变化让准确率从62%提升到89%。关键点在于RAG检索出的文本只是原材料prompt才是加工图纸。Spring AI 2.0.1的RetrievalAugmentedGeneration抽象正是为了让你把这张图纸画得更精细。它允许你在ChatOptions里设置maxTokens、temperature等参数但更重要的是它让你能把检索逻辑和prompt组装逻辑解耦——你可以用VectorStore独立优化检索质量用PromptTemplate独立优化生成质量两者互不干扰。实操心得别迷信“检索越多越好”。我们实测发现把top_k从10降到3配合强化prompt效果反而更好。因为模型处理10段碎片化文本的负担远大于3段容易丢失重点。真正的瓶颈从来不是检索不到而是模型看不懂检索结果之间的关系。3. Spring AI实战从零搭建可落地的Prompt Engineering工作流3.1 环境准备与依赖选型为什么选Spring AI 2.0.1而非Langchain4j很多Java团队在“Spring AI”和“Langchain4j”之间纠结。我的建议很明确新项目无脑选Spring AI 2.0.1老项目迁移成本可控再换。理由不是因为它“新”而是它和Java生态的咬合度更高。Langchain4j是Python LangChain的Java移植版核心设计哲学是“一切皆Chain”导致Java开发者要写大量Builder模式代码比如一个简单RAG流程Langchain4j需要// Langchain4j伪代码实际代码更冗长 Retriever retriever ChromaRetriever.builder().build(); LLM llm Qwen37Llm.builder().build(); Chain chain RetrievalQAChain.builder() .retriever(retriever) .llm(llm) .promptTemplate(...) .build();而Spring AI 2.0.1直接暴露ChatClient和RetrievalAugmentedGeneration两个核心接口用法更贴近Spring Boot习惯// Spring AI 2.0.1标准用法 Autowired private ChatClient chatClient; Autowired private RetrievalAugmentedGeneration rag; public String ask(String query) { // 直接调用无需Builder链 return chatClient.call(new UserMessage(query)).getResult().getOutput(); } public String ragAsk(String query) { // RAG调用同样简洁 return rag.generate(query).getContent(); }更重要的是Spring AI 2.0.1原生支持Alibaba百炼API配置只需三行spring: ai: alibaba: endpoint: https://dashscope.aliyuncs.com/compatible-mode/v1 api-key: ${ALIYUN_API_KEY} model-name: qwen-max # 或qwen3.7而Langchain4j对接百炼需要自己写Qwen37ChatModel实现类还要处理Token计费、流式响应等细节。对于赶工期的业务系统这种开箱即用的体验能省下至少两天联调时间。注意网上流传的“Spring AI Alibaba停更了吗”是误传。Spring AI官方仓库的spring-ai-alibaba-spring-boot-starter模块持续更新至2024年6月且已适配Qwen3.7的最新API协议。所谓“停更”其实是部分博客作者把旧版starterv0.8.x和新版v1.0.0混淆了。3.2 动态Prompt组装用Java代码控制AI的“思考路径”静态prompt只能应付简单场景。真实业务中prompt必须随上下文动态变化。比如客服对话系统用户第一句问“订单怎么退款”第二句问“上次那个订单”这时prompt必须自动注入上一轮的订单ID。Spring AI提供了ChatOptions和Message的灵活组合我们用一个电商场景完整演示需求用户咨询“我的订单#10086退款进度”系统需调用订单服务查状态再生成自然语言回复。Step 1定义Prompt模板Component public class OrderStatusPrompt { // 基础约束所有prompt共用 private static final String SYSTEM_PROMPT 你是一个电商客服机器人只回答与订单状态相关的问题。禁止编造订单信息。输出必须为口语化中文不加专业术语。; // 动态模板含占位符 private static final String USER_PROMPT_TEMPLATE 用户订单号%s\n 订单当前状态%s\n 物流单号%s\n 预计送达时间%s\n 用户原始问题%s\n 请用一句话告知用户退款进度重点突出时间节点和责任方。; public ListMessage build(String orderId, OrderStatus status, String trackingNo, String eta, String userQuery) { String userPrompt String.format(USER_PROMPT_TEMPLATE, orderId, status.getDesc(), trackingNo, eta, userQuery); return Arrays.asList( new SystemMessage(SYSTEM_PROMPT), new UserMessage(userPrompt) ); } }Step 2Service层调用Service public class OrderService { Autowired private ChatClient chatClient; Autowired private OrderStatusPrompt promptBuilder; public String getRefundStatus(String orderId) { // 1. 调用内部服务查订单 Order order orderRepository.findById(orderId); if (order null) { return 抱歉没找到订单号 orderId; } // 2. 构建动态prompt ListMessage messages promptBuilder.build( orderId, order.getStatus(), order.getTrackingNo(), order.getEta(), 我的订单#10086退款进度 ); // 3. 调用AI生成回复 AiResponse response chatClient.call(messages); return response.getResult().getOutput(); } }Step 3效果对比旧方案静态prompt回复“您的订单正在处理中”用户追问“多久能到账”又得重新调用新方案动态prompt回复“您的订单#10086已进入退款流程预计3个工作日内原路退回由支付宝处理”一次到位。这个案例揭示了Prompt Engineering的核心它不是AI的输入而是业务逻辑的输出。Java代码负责采集、聚合、校验上下文数据prompt只是数据的载体。Spring AI的ChatClient本质上是一个“AI调用门面”它把复杂的HTTP请求、Token管理、错误重试都封装掉了让你专注在“怎么把业务数据变成AI能懂的语言”这件事上。3.3 RAG知识库实战Ollama Spring AI本地化部署全记录“有没有本地的RAG文本拆解工具”——这是Java工程师最常问的问题。答案是Ollama Spring AI是最轻量、最可控的本地RAG方案连Docker都不用装。以下是我在Mac M1 Pro上实测的完整流程Windows/Linux命令略有差异但思路一致Step 1安装Ollama# Mac一键安装 curl -fsSL https://ollama.com/install.sh | sh # 启动服务后台运行 ollama serve Step 2拉取并运行Qwen3.7# 拉取模型约4GB首次较慢 ollama pull qwen:3.7 # 验证运行 ollama run qwen:3.7 你好Step 3Spring Boot项目集成!-- pom.xml -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-ollama-spring-boot-starter/artifactId version1.0.0-M5/version !-- 注意用M5及以上版本支持Qwen3.7 -- /dependency# application.yml spring: ai: ollama: base-url: http://localhost:11434 model: qwen:3.7Step 4构建本地知识库关键点来了RAG知识库不是“把PDF扔进去就行”而是要解决“怎么拆、怎么存、怎么查”。我们不用Langchain4j的复杂切块器用Java原生方案Component public class LocalRagBuilder { private final VectorStore vectorStore; // Spring AI的VectorStore接口 public void buildFromPdf(String pdfPath) throws IOException { // 1. PDF解析用Apache PDFBox PDDocument doc PDDocument.load(new File(pdfPath)); PDFTextStripper stripper new PDFTextStripper(); String text stripper.getText(doc); // 2. 智能分块不用固定长度按语义 String[] paragraphs text.split(\n\\s*\n); // 按空行分段 for (String para : paragraphs) { if (para.trim().length() 50) continue; // 过滤短段落 // 3. 向量化存储 Document document Document.from(para) .withMetadata(source, pdfPath) .withMetadata(chunk_id, UUID.randomUUID().toString()); vectorStore.add(List.of(document)); } } }这段代码的精妙之处在于它用PDF的自然段落空行分隔作为切块单位比固定token切块更符合人类阅读习惯。一个“退货政策”段落不会被切成两半保证了语义完整性。Step 5RAG调用Service public class LocalRagService { Autowired private RetrievalAugmentedGeneration rag; public String query(String question) { // Spring AI自动完成检索prompt组装调用模型 return rag.generate(question).getContent(); } }整个流程从PDF到可问答的知识库不超过50行Java代码。没有Python环境、没有Docker、没有复杂配置这就是Java工程师该有的RAG体验。常见问题RAG知识库能存图片吗答案是不能直接存但可以存图片的文本描述。比如用CLIP模型生成图片特征向量再存入VectorStore查询时用文字描述找相似图片。但这已超出Prompt Engineering范畴属于多模态RAG需要额外引入模型。对99%的Java业务系统文本RAG已足够。4. 效果验证与迭代用Java的方式调试Prompt4.1 不是“试试看”而是“可测量、可对比、可回滚”很多团队把prompt调优当成玄学靠感觉改几个词然后问“效果怎么样”。这在工程团队里是不可接受的。我们必须建立一套Java工程师熟悉的验证体系单元测试驱动为每个PromptTemplate写JUnit测试验证生成的prompt是否符合结构预期A/B测试框架同一业务场景同时部署新旧prompt用埋点统计用户满意度效果仪表盘用PrometheusGrafana监控prompt调用的准确率、响应时长、Token消耗。我们以合同比对场景为例构建一个最小可行验证框架Step 1定义测试用例Test public void testContractDiffPrompt() { // 准备测试数据 String clauseA 甲方应在收到货物后30日内支付货款。; String clauseB 甲方应在收到货物后15日内支付货款。; // 生成prompt String prompt template.build(clauseA, clauseB, 5.1, 5.1); // 断言关键结构 assertThat(prompt).contains(条款5.1 vs 5.1:); assertThat(prompt).contains(修改); assertThat(prompt).contains(30日内); assertThat(prompt).contains(15日内); }Step 2A/B测试配置# application-abtest.yml prompt: ab-test: enabled: true variant: v2 # v1旧prompt, v2新prompt traffic-ratio: 0.5 # 50%流量走v2Step 3效果埋点Service public class PromptMetrics { private final Counter promptCounter; public void recordResult(String variant, boolean isAccurate) { // 上报到Micrometer promptCounter.tag(variant, variant) .tag(accuracy, String.valueOf(isAccurate)) .increment(); } }这套体系让prompt优化从“我觉得更好”变成“数据证明更好”。我们曾用此方法在一周内将客服问答准确率从73%提升到86%所有改进点都有对应测试用例和埋点数据支撑。4.2 Java面试题里的Prompt Engineering那些你忽略的底层原理“Java是静态链接的”“Java怎么保证数据一致性”——这些面试题表面考Java实则考工程思维。Prompt Engineering同理。面试官问“RAG瓶颈在哪”他真想知道的是你是否理解技术栈的耦合点。以下是几个高频问题的深度拆解QRAG瓶颈是什么A不是检索慢也不是模型慢而是语义鸿沟。检索系统返回的是“相关文档”但LLM需要的是“可推理的证据”。比如搜“Java线程安全”检索返回《Effective Java》第78条但这条讲的是synchronized用法而用户问的是ConcurrentHashMap原理。瓶颈在于检索系统无法理解“synchronized”和“ConcurrentHashMap”在“线程安全”这个概念下的逻辑关联。解决方案不是换更贵的向量模型而是用Prompt Engineering在检索后加一层“概念映射”你是一个Java技术专家请将以下检索结果映射到用户问题的核心概念上 用户问题Java线程安全的实现原理 检索结果《Effective Java》第78条使用synchronized确保线程安全 映射要求指出synchronized与ConcurrentHashMap在解决线程安全问题上的异同并说明适用场景。QAI写代码规则设定提示词工程三者关系A这是典型的三层抽象AI写代码LLM的生成能力是引擎规则设定Java代码里的if-else、循环、异常处理是骨架提示词工程告诉LLM“在什么条件下生成什么代码”是神经中枢。 三者缺一不可。没有规则设定AI生成的代码无法融入现有系统没有提示词工程AI写的代码可能违反公司编码规范比如不用Lombok。QSpring AI Agent和Dify工作流的区别ADify是低代码平台适合产品经理快速搭DemoSpring AI Agent是代码级框架适合Java团队做深度定制。比如Dify工作流转成Spring AI Java代码核心就是把Dify的“节点-连线”逻辑翻译成Spring AI的ChatClient调用链和RetrievalAugmentedGeneration组合。GitHub上已有开源项目dify-to-spring-ai但真正难点不在转换而在如何把Dify里拖拽出来的“条件分支”用Java的if-else和Optional优雅表达。这些问题的答案没有标准模板只有对Java工程和AI原理的双重理解。这也是Prompt Engineering成为Java高级工程师分水岭的原因——它要求你既是代码的建造者也是AI的指挥官。4.3 避坑指南Java工程师最容易踩的5个Prompt陷阱陷阱一把prompt当SQL写错误做法SELECT * FROM knowledge WHERE topic Java内存模型试图用自然语言模拟SQL正确做法请解释Java内存模型中主内存与工作内存的关系重点说明volatile关键字如何保证可见性。AI不是数据库它不支持WHERE条件过滤而是靠上下文引导聚焦。陷阱二过度依赖模型记忆错误做法在多轮对话中不把历史消息传给AI指望它记住上一轮的订单ID正确做法Spring AI的ChatMemory必须开启且Message列表要包含全部历史。Qwen3.7的上下文窗口虽大但不等于无限记忆。陷阱三忽略Token经济错误做法把整本《Java并发编程实战》PDF塞进prompt正确做法用RAG只传最相关的3个段落总Token控制在2048以内。Spring AI的ChatOptions里maxTokens参数是防止OOM的保险丝。陷阱四混淆“能做”和“该做”错误做法让AI生成SQL、执行数据库操作正确做法AI只生成SQL文本Java代码负责校验、参数化、执行。安全边界必须由Java代码守住。陷阱五忽视模型版本差异错误做法用Qwen2.5的prompt直接跑Qwen3.7正确做法Qwen3.7增强了数学推理和代码生成但对中文长文本摘要能力下降。必须为每个模型版本维护独立的PromptTemplate。这些坑我都在生产环境里踩过。最惨的一次是把一个5000字的API文档全文塞进prompt导致Qwen3.7响应超时整个订单服务雪崩。教训是Prompt Engineering的第一守则不是“怎么写好”而是“怎么写小”。用最少的Token传递最确定的信息。5. 常见问题速查表与独家调试技巧问题现象可能原因排查步骤解决方案AI回复“我不知道”检索结果为空或prompt未提供足够上下文1. 打印rag.retrieve(query)返回的Document列表2. 检查prompt里是否有“仅基于以下内容回答”的硬性约束在prompt开头加你必须基于以下提供的材料回答问题即使材料不完整也不得说“我不知道”AI编造不存在的API基础层prompt缺失“禁止编造”约束1. 检查SystemMessage内容2. 用chatClient.call()单独测试基础prompt在SystemMessage中强制加入禁止编造任何函数名、类名、包名。若不确定回答“该信息未提供”响应时长波动大Ollama模型加载不稳定或网络抖动1.curl http://localhost:11434/api/tags确认模型状态2.ollama ps查看容器资源占用重启Ollama服务ollama kill ollama serve 或升级到Ollama v0.3.0支持模型预加载RAG检索结果不相关PDF解析失败或向量化模型不匹配1. 打印vectorStore.similaritySearch(Java线程)返回结果2. 检查PDF是否加密、是否含扫描图片用pdf2image先转图片再用OCRTesseract提取文本或换用all-MiniLM-L6-v2等轻量向量模型Spring AI调用报401百炼API Key过期或endpoint配置错误1.curl -H Authorization: Bearer YOUR_KEY https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions手动测试2. 检查application.yml中spring.ai.alibaba.endpoint末尾是否有/百炼endpoint必须以/v1结尾不能多加斜杠API Key需在阿里云百炼控制台重新生成独家调试技巧Prompt“反向工程”法当AI输出错误答案时不要改prompt而是把AI的错误输出当输入再问“你刚才的回答依据是什么请引用原文”。这能快速暴露prompt里缺失的约束。Java断点调试Prompt在chatClient.call()前加断点把ListMessage内容复制到Ollama Web UIhttp://localhost:11434里直接测试绕过Spring Boot环境干扰。Token可视化工具用https://platform.openai.com/tokenizer兼容Qwen粘贴prompt实时查看Token数避免超限。RAG效果热力图在RetrievalAugmentedGeneration的generate()方法上加AOP记录每次检索的score值用Elasticsearch存日志Grafana画热力图直观看出哪些query的检索质量差。最后分享一个小技巧在Spring Boot的application-dev.yml里把spring.ai.ollama.model设为qwen:3.7在application-prod.yml里设为qwen:3.7:latest。这样开发时用稳定版生产时自动拉取最新补丁既保证稳定性又享受更新红利。Prompt Engineering的终极目标不是让AI更聪明而是让Java代码更可靠。当你能用JUnit测试一个prompt用Prometheus监控一次AI调用用Git管理prompt版本时你就已经吃透了从0到1的全过程。