☰
Java工程师的提示工程实战:Spring AI + RAG动态Prompt设计
2026/10/5 9:15:10 网站建设 项目流程

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核心技术卷III:AI协同篇》。

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,否则每次请求都重复传输,既浪费带宽,又增加模型混淆风险。正确做法是:

List<Message> 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协议。所谓“停更”,其实是部分博客作者把旧版starter(v0.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 List<Message> 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 2:Service层调用

@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 List<Message> 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 3:Spring Boot项目集成

<!-- pom.xml --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> <version>1.0.0-M5</version> <!-- 注意:用M5及以上版本,支持Qwen3.7 --> </dependency>
# application.yml spring: ai: ollama: base-url: http://localhost:11434 model: qwen:3.7

Step 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 5:RAG调用

@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的方式调试Prompt

4.1 不是“试试看”,而是“可测量、可对比、可回滚”

很多团队把prompt调优当成玄学,靠感觉改几个词,然后问“效果怎么样”。这在工程团队里是不可接受的。我们必须建立一套Java工程师熟悉的验证体系:

  • 单元测试驱动:为每个PromptTemplate写JUnit测试,验证生成的prompt是否符合结构预期;
  • A/B测试框架:同一业务场景,同时部署新旧prompt,用埋点统计用户满意度;
  • 效果仪表盘:用Prometheus+Grafana监控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 2:A/B测试配置

# application-abtest.yml prompt: ab-test: enabled: true variant: v2 # v1=旧prompt, v2=新prompt traffic-ratio: 0.5 # 50%流量走v2

Step 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瓶颈在哪”,他真想知道的是你是否理解技术栈的耦合点。以下是几个高频问题的深度拆解:

Q:RAG瓶颈是什么?A:不是检索慢,也不是模型慢,而是语义鸿沟。检索系统返回的是“相关文档”,但LLM需要的是“可推理的证据”。比如搜“Java线程安全”,检索返回《Effective Java》第78条,但这条讲的是synchronized用法,而用户问的是ConcurrentHashMap原理。瓶颈在于,检索系统无法理解“synchronized”和“ConcurrentHashMap”在“线程安全”这个概念下的逻辑关联。解决方案不是换更贵的向量模型,而是用Prompt Engineering,在检索后加一层“概念映射”:

你是一个Java技术专家,请将以下检索结果,映射到用户问题的核心概念上: 用户问题:Java线程安全的实现原理 检索结果:《Effective Java》第78条:使用synchronized确保线程安全 映射要求:指出synchronized与ConcurrentHashMap在解决线程安全问题上的异同,并说明适用场景。

Q:AI写代码+规则设定+提示词工程,三者关系?A:这是典型的三层抽象:

  • AI写代码:LLM的生成能力,是引擎;
  • 规则设定:Java代码里的if-else、循环、异常处理,是骨架;
  • 提示词工程:告诉LLM“在什么条件下生成什么代码”,是神经中枢。 三者缺一不可。没有规则设定,AI生成的代码无法融入现有系统;没有提示词工程,AI写的代码可能违反公司编码规范(比如不用Lombok)。

Q:Spring AI Agent和Dify工作流的区别?A:Dify是低代码平台,适合产品经理快速搭Demo;Spring 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陷阱

  1. 陷阱一:把prompt当SQL写

    • 错误做法:SELECT * FROM knowledge WHERE topic = 'Java内存模型',试图用自然语言模拟SQL;
    • 正确做法:请解释Java内存模型中主内存与工作内存的关系,重点说明volatile关键字如何保证可见性。AI不是数据库,它不支持WHERE条件过滤,而是靠上下文引导聚焦。
  2. 陷阱二:过度依赖模型记忆

    • 错误做法:在多轮对话中,不把历史消息传给AI,指望它记住上一轮的订单ID;
    • 正确做法:Spring AI的ChatMemory必须开启,且Message列表要包含全部历史。Qwen3.7的上下文窗口虽大,但不等于无限记忆。
  3. 陷阱三:忽略Token经济

    • 错误做法:把整本《Java并发编程实战》PDF塞进prompt;
    • 正确做法:用RAG只传最相关的3个段落,总Token控制在2048以内。Spring AI的ChatOptions里maxTokens参数,是防止OOM的保险丝。
  4. 陷阱四:混淆“能做”和“该做”

    • 错误做法:让AI生成SQL、执行数据库操作;
    • 正确做法:AI只生成SQL文本,Java代码负责校验、参数化、执行。安全边界必须由Java代码守住。
  5. 陷阱五:忽视模型版本差异

    • 错误做法:用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先转图片,再用OCR(Tesseract)提取文本;或换用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()前加断点,把List<Message>内容复制到Ollama Web UI(http://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的全过程。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询