乐于分享
好东西不私藏

Spring AI PromptTemplate 实践指南(附 LangChain 对比)

Spring AI PromptTemplate 实践指南(附 LangChain 对比)

一、核心概念与理论基础

1.1 为什么需要 PromptTemplate?

在大模型应用中,提示词(Prompt)本质上是人机之间的"接口契约"。直接使用字符串拼接存在三个核心问题:

  1. 可维护性差
    :提示词与业务逻辑耦合,修改需重新编译
  2. 可测试性弱
    :难以对提示词渲染逻辑进行单元测试
  3. 协作成本高
    :产品/算法/开发三方对提示词的修改缺乏版本管理

PromptTemplate 的设计目标正是解决这些问题,其理论基础来自软件工程的"关注点分离"原则:将提示词的结构定义变量注入渲染执行三个环节解耦。

1.2 消息角色的设计原理

大模型通常支持多角色消息(System/User/Assistant),这种设计源于对话系统的状态机模型:

  • SystemMessage
    :定义全局约束和行为边界,相当于函数的"前置条件"
  • UserMessage
    :表达当前请求意图,相当于函数的"输入参数"
  • AssistantMessage
    :提供历史响应,用于维持对话状态,相当于"上下文栈"

这种分层结构能有效减少指令冲突(Instruction Interference),提升模型对复杂任务的理解能力。


二、基础使用示例(保留原始代码)

辅助方法:打印流式响应

/** * 辅助方法:打印流式响应(ChatResponse) */privatevoidprintStreamResponseFromChatResponse(Flux<ChatResponse> response) throws InterruptedException {    CountDownLatch latch = new CountDownLatch(1);    response        .doOnNext(chatResponse -> {            String content = chatResponse.getResult().getOutput().getText();            if (content != null) {                System.out.print(content);            }        })        .doOnComplete(() -> {            System.out.println("\n[完成]");            latch.countDown();        })        .doOnError(error -> {            System.err.println("\n[错误]: " + error.getMessage());            latch.countDown();        })        .subscribe();    latch.await(30, TimeUnit.SECONDS);}

示例 1:最简单的字符串 Prompt

@Testvoid demo1_SimpleStringPrompt() throws InterruptedException {    System.out.println("========== 示例 1:简单字符串 Prompt ==========");    // 直接传入字符串,Spring AI 内部会转换为 Prompt    String message = "你好,请介绍一下自己";    Flux<String> response = dashScopeChatModel.stream(message);    printStreamResponse(response);}

执行结果:

========== 示例1:简单字符串 Prompt ==========你好!😊 我是通义千问(Qwen),阿里巴巴集团旗下的超大规模语言模型。我能够理解并生成多种语言的文本,比如中文、英文、法语、西班牙语、葡萄牙语、俄语、阿拉伯语、日语、韩语、越南语、泰语、印尼语等,支持跨语言交流与创作。我可以帮你:🔹 解答各类问题(科学、文化、生活、技术等)  🔹 创作文字(写故事、公文、邮件、剧本、诗歌、歌词等)  🔹 编程辅助(解释代码、调试建议、多语言代码生成)  🔹 逻辑推理与数学计算  🔹 多轮对话与个性化表达(比如模仿某种风格、调整语气)  🔹 文件处理(支持上传 PDF、Word、Excel、PPT、TXT 等格式,可阅读并分析其中内容)我注重准确性、安全性和实用性,也会尽力以友好、清晰、有温度的方式与你交流。不过需要说明的是:我的知识截止于2024年,无法实时获取最新网络信息(如今日天气、股市行情等),也不具备个人经历或主观意识。很高兴认识你!如果你有任何问题、想法,或者想一起写点什么、学点什么——随时告诉我吧 🌟[完成]Process finished with exit code 0

LangChain 对比实现

from langchain_openai import ChatOpenAIllm = ChatOpenAI(model="qwen-turbo", streaming=True)# 方式 1:直接字符串for chunk in llm.stream("你好,请介绍一下自己"):    print(chunk.content, end="", flush=True)# 方式 2:使用 PromptTemplate(为后续变量替换做准备)from langchain_core.prompts import PromptTemplateprompt = PromptTemplate.from_template("你好,请介绍一下自己")for chunk in llm.stream(prompt.invoke({})):    print(chunk.content, end="", flush=True)

示例 2:使用 Prompt 对象

@Testvoid demo2_PromptObject() throws InterruptedException {    System.out.println("\n========== 示例 2:Prompt 对象 ==========");    // 创建 UserMessage    UserMessage userMessage = new UserMessage("什么是人工智能?");    // 创建 Prompt 对象    Prompt prompt = new Prompt(userMessage);    Flux<ChatResponse> response = dashScopeChatModel.stream(prompt);    printStreamResponseFromChatResponse(response);}

执行结果:

========== 示例2:Prompt 对象 ==========人工智能(Artificial Intelligence,简称 AI)是指由人类设计和构建的系统或机器所展现出的、通常与人类智能相关的认知能力。这些能力包括但不限于:🔹 **感知**:如通过计算机视觉识别图像中的物体,或通过语音识别理解人类语言;  🔹 **学习**:从数据中自动发现规律与模式(如机器学习、深度学习),无需显式编程;  🔹 **推理与决策**:基于已有知识或规则进行逻辑推断、权衡利弊并做出选择(例如下棋程序AlphaGo、医疗诊断辅助系统);  🔹 **语言理解与生成**:理解自然语言含义,并能生成连贯、符合语境的文本或对话(如大语言模型ChatGPT、文心一言);  🔹 **规划与问题求解**:在复杂环境中设定目标、分解任务、制定策略(如自动驾驶路径规划、物流调度系统);  🔹 **适应与自主性**:在动态环境中持续学习、调整行为(如机器人自主导航避障)。📌 **关键说明**- AI ≠ 人类意识或通用智能:当前绝大多数AI属于“狭义AI”(Narrow AI),专精于特定任务(如翻译、人脸识别)。尚不存在具备自我意识、常识推理和跨领域泛化能力的“通用人工智能”(AGI),这仍是理论探索与长期研究目标。- AI 的实现依赖多学科交叉:包括计算机科学、数学(统计学、优化理论)、神经科学、语言学、哲学等。- 技术基础不断演进:从早期基于规则的专家系统,到以数据驱动的机器学习,再到如今以大模型为代表的生成式AI(GenAI),其能力边界正快速拓展。💡 简单类比:  > 人工智能就像为机器装上“可编程的大脑”,让它不仅能执行指令,还能从经验中学习、理解模糊信息、并完成原本需要人类智慧的任务——但它没有感受、意图或主观体验。如果你对某类AI(如大模型、AI伦理、AI如何工作)感兴趣,我可以进一步为你深入解析 😊[完成]

LangChain 对比实现

from langchain_core.prompts import ChatPromptTemplatefrom langchain_core.messages import HumanMessage# 方式 1:使用 ChatPromptTemplateprompt = ChatPromptTemplate.from_messages([    ("user""什么是人工智能?")])for chunk in llm.stream(prompt.invoke({})):    print(chunk.content, end="", flush=True)# 方式 2:直接使用消息对象human_msg = HumanMessage(content="什么是人工智能?")for chunk in llm.stream([human_msg]):    print(chunk.content, end="", flush=True)

示例 3:带系统消息的 Prompt

@Testvoid demo3_PromptWithSystemMessage() throws InterruptedException {    System.out.println("\n========== 示例 3:带系统消息的 Prompt ==========");    // 系统消息:设定 AI 的角色    SystemMessage systemMessage = new SystemMessage(        "你是一个专业的编程助手,擅长解答技术问题。" +        "请用简洁明了的方式回答问题,并提供代码示例。"    );    // 用户消息    UserMessage userMessage = new UserMessage("如何用 Java 实现单例模式?");    // 创建包含多个消息的 Prompt    Prompt prompt = new Prompt(List.of(systemMessage, userMessage));    Flux<ChatResponse> response = dashScopeChatModel.stream(prompt);    printStreamResponseFromChatResponse(response);}

执行结果:

========== 示例3:带系统消息的 Prompt ==========在 Java 中,单例模式(Singleton Pattern)确保一个类**只有一个实例**,并提供一个全局访问点。以下是几种常用、线程安全的实现方式(按推荐程度排序):---### ✅ 1. **枚举单例(最推荐 ✅)—— 简洁、线程安全、防反射/反序列化攻击**```javapublic enum Singleton {    INSTANCE;    publicvoiddoSomething() {        System.out.println("Singleton instance is working...");    }}// 使用Singleton.INSTANCE.doSomething();```> ✅ 优点:天然线程安全、防止反射破坏、防止反序列化创建新实例;简洁优雅。  > ⚠️ 注意:不能延时加载(JVM 加载类时即初始化)。---### ✅ 2. **静态内部类(推荐 ✅)—— 懒汉式 + 线程安全 + 高效**```javapublic class Singleton {    privateSingleton() {} // 私有构造器    private static class SingletonHolder {        private static final Singleton INSTANCE = new Singleton();    }    publicstatic Singleton getInstance() {        return SingletonHolder.INSTANCE;    }    publicvoiddoSomething() {        System.out.println("Lazy-loaded singleton");    }}```> ✅ 优点:懒加载(首次调用 `getInstance()` 才初始化)、线程安全(JVM 类加载机制保证)、无同步开销。---### ✅ 3. **双重检查锁定(DCL)+ volatile(经典但需谨慎)**```javapublic class Singleton {    private static volatile Singleton instance;    privateSingleton() {}    publicstatic Singleton getInstance() {        if (instance == null) {                    // 第一次检查(避免同步开销)            synchronized (Singleton.class) {                if (instance == null) {            // 第二次检查(确保只创建一次)                    instance = new Singleton();                }            }        }        return instance;    }}```> ⚠️ 注意:`volatile` 关键字**必不可少**(禁止指令重排序,保证可见性和有序性)。  > ✅ 优点:懒加载、线程安全、高性能(仅首次创建加锁)。[完成]

LangChain 对比实现

from langchain_core.prompts import ChatPromptTemplateprompt = ChatPromptTemplate.from_messages([    ("system""你是一个专业的编程助手,擅长解答技术问题。请用简洁明了的方式回答问题,并提供代码示例。"),    ("user""如何用 Java 实现单例模式?")])for chunk in llm.stream(prompt.invoke({})):    print(chunk.content, end="", flush=True)

示例 4:带聊天选项的 Prompt

@Testvoid demo4_PromptWithChatOptions() throws InterruptedException {    System.out.println("\n========== 示例 4:带聊天选项的 Prompt ==========");    UserMessage userMessage = new UserMessage("写一首关于春天的诗");    // 创建聊天选项    ChatOptions options = ChatOptions.builder()        .temperature(0.8)  // 温度:控制随机性,0-1 之间,越高越有创造性        .maxTokens(500)    // 最大 token 数:限制回答长度        .build();    // 创建带选项的 Prompt    Prompt prompt = new Prompt(userMessage, options);    Flux<ChatResponse> response = dashScopeChatModel.stream(prompt);    printStreamResponseFromChatResponse(response);}

执行结果:

========== 示例4:带聊天选项的 Prompt ==========《春笺》风在枝头试笔,写第一行新绿,  柳线垂落,蘸着溪水临摹云影。  泥土松动,蚯蚓在暗处誊抄暖意,  草尖微颤,抖落一粒粒细小的光。桃夭未落,已把粉红寄给山岗;  梨雪初凝,又将素白托付给斜阳。  蜂翅驮着蜜的邮戳,在花间往返,  而布谷的啼鸣,是大地盖下的印章。我伫立田埂,看犁沟蜿蜒如句读——  春不押韵,却自有节律;  不设伏笔,却处处伏着青翠的伏笔。  当夕照把影子拉长成一行未写完的诗,  风轻轻翻页:万物正破题。[完成]

LangChain 对比实现

from langchain_core.prompts import PromptTemplateprompt = PromptTemplate.from_template("写一首关于春天的诗")# 通过 bind 方法传递采样参数llm_with_options = llm.bind(temperature=0.8, max_tokens=500)for chunk in llm_with_options.stream(prompt.invoke({})):    print(chunk.content, end="", flush=True)

示例 5:完整的 Prompt(系统消息 + 用户消息 + 选项)

@Testvoid demo5_CompletePrompt() throws InterruptedException {    System.out.println("\n========== 示例 5:完整的 Prompt ==========");    // 系统消息:设定角色    SystemMessage systemMessage = new SystemMessage(        "你是一个经验丰富的技术面试官,擅长提出有深度的技术问题。"    );    // 用户消息    UserMessage userMessage = new UserMessage(        "请给我出 3 个 Java 中级开发者的面试问题"    );    // 聊天选项    ChatOptions options = ChatOptions.builder()        .temperature(0.7)        .maxTokens(100)        .build();    // 创建完整的 Prompt    Prompt prompt = new Prompt(        List.of(systemMessage, userMessage),        options    );    Flux<ChatResponse> response = dashScopeChatModel.stream(prompt);    printStreamResponseFromChatResponse(response);}

执行结果:

========== 示例5:完整的 Prompt ==========当然可以!以下是3个针对**Java中级开发者**(通常指有2–4年经验,熟悉核心API、JVM基础、常用框架和设计原则)的面试问题,兼顾深度、实践性和考察维度(概念理解、代码能力、问题分析与权衡意识):---**1. 【JVM & 内存模型】**  > 请解释 `String str = new String("hello")` 这行代码在JVM中会[完成]Process finished with exit code 0

LangChain 对比实现

prompt = ChatPromptTemplate.from_messages([    ("system""你是一个经验丰富的技术面试官,擅长提出有深度的技术问题。"),    ("user""请给我出 3 个 Java 中级开发者的面试问题")])llm_with_options = llm.bind(temperature=0.7, max_tokens=100)for chunk in llm_with_options.stream(prompt.invoke({})):    print(chunk.content, end="", flush=True)

示例 6:多轮对话

@Testvoid demo6_MultiTurnConversation() throws InterruptedException {    System.out.println("\n========== 示例 6:多轮对话 Prompt ==========");    // 构建对话历史    SystemMessage systemMessage = new SystemMessage("你是一个友好的助手");    UserMessage firstQuestion = new UserMessage("Java 和 Python 有什么区别?");    UserMessage followUp = new UserMessage("那在性能方面呢?");    ChatOptions chatOptions = ChatOptions.builder().maxTokens(100).build();    // 将所有消息放入 Prompt    Prompt prompt = new Prompt(List.of(        systemMessage,        firstQuestion,        followUp    ),                               chatOptions);    Flux<ChatResponse> response = dashScopeChatModel.stream(prompt);    printStreamResponseFromChatResponse(response);}

执行结果:

========== 示例6:多轮对话 Prompt ==========在性能方面,Java 通常显著优于 Python,主要原因在于它们的执行机制和设计哲学不同。以下是关键对比点:✅ **执行方式**- **Java**:编译为字节码 → 由 JVMJava 虚拟机)即时编译(JIT)为高度优化的本地机器码运行。JIT 可在运行时分析热点代码并动态优化(如方法内联、逃逸分析、锁消除等),长期运行[完成]Disconnected from the target VMaddress'127.0.0.1:54900'transport'socket'Process finished with exit code 0

LangChain 对比实现

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder# 方式 1:直接列出所有消息prompt = ChatPromptTemplate.from_messages([    ("system""你是一个友好的助手"),    ("user""Java 和 Python 有什么区别?"),    ("user""那在性能方面呢?")])for chunk in llm.bind(max_tokens=100).stream(prompt.invoke({})):    print(chunk.content, end="", flush=True)# 方式 2:使用 MessagesPlaceholder 动态管理历史(推荐)dynamic_prompt = ChatPromptTemplate.from_messages([    ("system""你是一个友好的助手"),    MessagesPlaceholder("history"),    ("user""{question}")])# 调用时传入历史消息列表response = dynamic_prompt.invoke({    "history": [        ("user""Java 和 Python 有什么区别?"),        ("assistant""Java 是编译型语言,Python 是解释型语言...")    ],    "question""那在性能方面呢?"})for chunk in llm.stream(response.to_messages()):    print(chunk.content, end="", flush=True)

示例 7:温度参数对比

@Testvoid demo7_TemperatureComparison() throws InterruptedException {    System.out.println("\n========== 示例 7:温度参数对比 ==========");    String question = "给创业公司起一个名字";    // 低温度:更保守、确定性高    System.out.println("\n--- 低温度 (0.2) - 更保守 ---");    ChatOptions lowTemp = ChatOptions.builder().maxTokens(150).temperature(0.2).build();    Prompt prompt1 = new Prompt(new UserMessage(question), lowTemp);    printStreamResponseFromChatResponse(dashScopeChatModel.stream(prompt1));    Thread.sleep(1000);    // 高温度:更有创造性    System.out.println("\n--- 高温度 (0.9) - 更有创造性 ---");    ChatOptions highTemp = ChatOptions.builder().maxTokens(150).temperature(0.9).build();    Prompt prompt2 = new Prompt(new UserMessage(question), highTemp);    printStreamResponseFromChatResponse(dashScopeChatModel.stream(prompt2));}

LangChain 对比实现

question = "给创业公司起一个名字"prompt = PromptTemplate.from_template(question)print("\n--- 低温度 (0.2) - 更保守 ---")for chunk in llm.bind(temperature=0.2, max_tokens=150).stream(prompt.invoke({})):    print(chunk.content, end="", flush=True)print("\n\n--- 高温度 (0.9) - 更有创造性 ---")for chunk in llm.bind(temperature=0.9, max_tokens=150).stream(prompt.invoke({})):    print(chunk.content, end="", flush=True)

三、模板引擎原理与变量渲染

3.1 StTemplateRenderer 工作机制

Spring AI 默认的 PromptTemplate 使用 StTemplateRenderer,其底层基于 StringTemplate 4 引擎。渲染流程如下:

  1. 词法分析
    :扫描模板字符串,识别 {variable} 格式的占位符
  2. 语法树构建
    :将模板解析为抽象语法树(AST),支持嵌套表达式
  3. 上下文绑定
    :将传入的 Map<String, Object> 变量与 AST 节点绑定
  4. 求值渲染
    :遍历 AST,执行变量替换、条件判断、格式转换等操作
  5. 输出生成
    :拼接最终字符串结果

这种设计相比简单的字符串替换有两个关键优势:

  • 支持复杂逻辑
    :可在模板中使用条件表达式、列表迭代等(虽然多数场景不需要)
  • 安全隔离
    :模板逻辑与数据完全分离,配合自动转义可防止注入攻击

3.2 变量冲突问题与解决方案

当模板中包含代码块时,代码中的 {} 可能与模板变量语法冲突:

// 模板内容请分析以下{language}代码:```{code}```// 用户传入的 code 变量public classTest{ }  // 含 {}

Spring AI 解决方案:配置自定义分隔符

PromptTemplate promptTemplate = PromptTemplate.builder()    .renderer(StTemplateRenderer.builder()        .startDelimiterToken('<')   // 使用<var>替代{var}        .endDelimiterToken('>')        .build())    .template("请分析<language>代码:\n```<code>```")    .build();String result = promptTemplate.render(Map.of(    "language""java",    "code""public class Test { }"  // 安全,不会冲突));

LangChain 处理方式:手动转义或预处理

# LangChain 默认使用{var}语法,需手动转义代码中的{}template = "请分析{language}代码:\n```{code}```"# 方案:将代码中的{}替换为{{}}(f-string 转义规则)code_escaped = code.replace("{""{{").replace("}""}}")prompt = PromptTemplate.from_template(template)result = prompt.format(language="java", code=code_escaped)

实践建议:涉及代码分析的模板,优先使用<var>格式分隔符,或在模板设计阶段就将代码变量放在独立代码块中,减少冲突概率。


四、文件化模板管理实践

4.1 模板文件组织规范

将提示词模板从代码中抽离,推荐使用以下目录结构:

src/main/resources/└── prompts/    ├── code-review.st              # 代码审查模板    ├── expert-explanation.st       # 专家解释模板    ├── open-source-recommendation.st  # 开源项目推荐    └── README.md                   # 模板说明与版本记录

模板文件内容示例(open-source-recommendation.st):

请给我推荐几个关于{topic}的开源项目

4.2 从文件加载并渲染变量

@Testvoiddemo1_LoadSimplePromptFromFile() throws IOException, InterruptedException {    System.out.println("========== 示例 1:从文件加载简单提示词 ==========");    ChatClient chatClient = chatClientBuilder.build();    // 从 classpath 读取提示词文件    ClassPathResource resource = new ClassPathResource("prompts/open-source-recommendation.st");    String templateContent = resource.getContentAsString(StandardCharsets.UTF_8);    System.out.println("\n读取的模板内容:" + templateContent.trim());    // 创建 PromptTemplate    PromptTemplate promptTemplate = new PromptTemplate(templateContent);    System.out.println("\n--- 测试 1:Java 相关 ---");    String renderedPrompt1 = promptTemplate.render(Map.of("topic""Java"));    System.out.println("渲染后的提示词:" + renderedPrompt1);    Flux<String> response1 = chatClient.prompt()    .user(renderedPrompt1)    .stream()    .content();    printStreamResponse(response1);    Thread.sleep(500);    System.out.println("\n\n--- 测试 2:微服务相关 ---");    String renderedPrompt2 = promptTemplate.render(Map.of("topic""微服务"));    System.out.println("渲染后的提示词:" + renderedPrompt2);    Flux<String> response2 = chatClient.prompt()    .user(renderedPrompt2)    .stream()    .content();    printStreamResponse(response2);    System.out.println("\n\n💡 说明:");    System.out.println("• 提示词保存在独立的 .st 文件中");    System.out.println("• 通过 ClassPathResource 读取文件内容");    System.out.println("• 修改提示词无需重新编译代码");}

LangChain 对比实现

from langchain_core.prompts import PromptTemplatefrom pathlib import Path# 读取模板文件template_path = Path("prompts/open-source-recommendation.txt")template_content = template_path.read_text(encoding="utf-8")# 创建 PromptTemplateprompt_template = PromptTemplate.from_template(template_content)# 渲染变量(两种写法等价)rendered_1 = prompt_template.invoke({"topic""Java"})  # 返回 PromptValuerendered_2 = prompt_template.format(topic="Java")        # 返回字符串# 调用模型for chunk in llm.stream(rendered_1):    print(chunk.content, end="", flush=True)

4.3 多变量复杂模板示例

模板文件 expert-explanation.st

你是一个{role}专家。请用{language}语言,为{audience}受众解释{topic}的概念。要求:{requirements}@Testvoid demo2_LoadComplexPromptFromFile() throws IOExceptionInterruptedException {    System.out.println("\n========== 示例 2:从文件加载复杂提示词 ==========");    ChatClient chatClient = chatClientBuilder.build();    // 从 classpath 读取提示词文件    ClassPathResource resource = new ClassPathResource("prompts/expert-explanation.st");    String templateContent = resource.getContentAsString(StandardCharsets.UTF_8);    System.out.println("\n读取的模板内容:\n" + templateContent);    // 创建 PromptTemplate    PromptTemplate promptTemplate = new PromptTemplate(templateContent);    System.out.println("\n--- 场景 1:技术专家向初学者解释 ---");    Map<StringObject> variables1 = Map.of(        "role""技术",        "language""简单易懂的中文",        "audience""编程初学者",        "topic""面向对象编程",        "requirements""使用生活中的比喻,避免专业术语"    );    String renderedPrompt1 = promptTemplate.render(variables1);    System.out.println("渲染后的提示词:\n" + renderedPrompt1);    Flux<String> response1 = chatClient.prompt()            .user(renderedPrompt1)            .stream()            .content();    printStreamResponse(response1);    Thread.sleep(500);    System.out.println("\n\n--- 场景 2:教育专家向学生讲解 ---");    Map<StringObject> variables2 = Map.of(        "role""教育",        "language""生动的中文",        "audience""大学生",        "topic""机器学习",        "requirements""结合实例,深入浅出"    );    String renderedPrompt2 = promptTemplate.render(variables2);    System.out.println("渲染后的提示词:\n" + renderedPrompt2);    Flux<String> response2 = chatClient.prompt()            .user(renderedPrompt2)            .stream()            .content();    printStreamResponse(response2);    System.out.println("\n\n💡 说明:");    System.out.println("• 复杂的多变量模板也可以保存在文件中");    System.out.println("• 提示词格式更清晰,易于阅读和修改");    System.out.println("• 适合团队协作文档化");}

4.4 实际应用场景:代码审查

模板文件 code-review.st

请分析以下代码,找出其中的问题并提供改进建议:```{language}{code}

请按照以下步骤进行分析:

  1. 识别代码中的问题(bug、性能问题、安全漏洞等)
  2. 解释问题的原因和影响
  3. 提供改进后的代码示例
  4. 说明改进的理由
@Testvoiddemo3_CodeReviewPrompt() throws IOException, InterruptedException {    System.out.println("\n========== 示例 3:代码审查提示词 ==========");    ChatClient chatClient = chatClientBuilder.build();    // 从 classpath 读取提示词文件    ClassPathResource resource = new ClassPathResource("prompts/code-review.st");    String templateContent = resource.getContentAsString(StandardCharsets.UTF_8);    System.out.println("\n读取的模板内容:\n" + templateContent);    // 创建 PromptTemplate    PromptTemplate promptTemplate = new PromptTemplate(templateContent);    System.out.println("\n--- 代码审查示例 ---");    Map<String, Object> variables = Map.of(        "language""java",        "code""""            public class UserService {                public User getUserById(String id) {                    String sql = "SELECT * FROM users WHERE id = '" + id + "'";                    return jdbcTemplate.queryForObject(sql, User.class);                }            }            """    );    String renderedPrompt = promptTemplate.render(variables);    System.out.println("渲染后的提示词:\n" + renderedPrompt);    Flux<String> response = chatClient.prompt()            .user(renderedPrompt)            .stream()            .content();    printStreamResponse(response);    System.out.println("\n\n💡 说明:");    System.out.println("• 可以将常用的提示词模板统一管理");    System.out.println("• 便于维护和更新");    System.out.println("• 可以在不同项目中复用");}

五、采样参数的理论说明

5.1 Temperature 的统计学原理

Temperature 参数控制模型输出时的随机性,其本质是对模型输出的 logits 进行缩放后再应用 Softmax:

原始 logits: [2.0, 1.0, 0.1]  # token A/B/C 的偏好分数应用 Temperature:scaled = logits / T- T=0.2: [10.0, 5.0, 0.5]  → 概率分布更尖锐,输出更确定- T=1.0: [2.0, 1.0, 0.1]   → 原始分布- T=2.0: [1.0, 0.5, 0.05]  → 概率分布更平缓,输出更多样Softmax 归一化后采样下一个 token

实践建议

任务类型

推荐 Temperature

原因

事实问答、代码生成

0.0~0.3

降低随机性,提升准确性

创意写作、头脑风暴

0.7~1.0

增加多样性,激发新颖组合

对话交互、角色扮演

0.5~0.8

平衡连贯性与趣味性

多轮推理任务

0.1~0.4

保持逻辑一致性

5.2 Top-p(Nucleus Sampling)的补充作用

Top-p 参数通过动态截断概率分布来控制采样范围:

原始概率: [A:0.5, B:0.3, C:0.15, D:0.03, E:0.02]Top-p=0.9 时:1. 累加概率:A(0.5)+B(0.3)+C(0.15)=0.95 ≥ 0.92. 保留{A,B,C},重新归一化后采样3. 自动排除低概率"噪音"token

组合策略参考

  • 保守模式:temperature=0.2 + top_p=0.9 → 高确定性 + 排除极端异常
  • 创意模式:temperature=0.9 + top_p=0.95 → 高多样性 + 保留合理长尾
  • 通用模式:temperature=0.7 + top_p=0.9 → 平衡质量与多样性

六、工程实践建议

6.1 模板版本管理

为重要模板建立版本控制机制(类似你们公司项目里的SQL管理):

prompts/├── code-review-v1.0.st      # 初始版本├── code-review-v1.1.st      # 增加安全检测维度├── code-review-v2.0.st      # 适配新模型,优化 token 使用└── CHANGELOG.md             # 记录变更内容与适用场景

版本命名建议采用语义化版本:

  • MAJOR:输出格式或分析步骤变更(需下游适配)
  • MINOR:新增分析维度或优化文案(向后兼容)
  • PATCH:错别字修复或格式调整

6.2 安全注意事项

  1. 输入净化
    :对用户传入的变量值进行长度限制和特殊字符过滤
  2. 模板隔离
    :使用PromptTemplate的自动转义功能,避免变量内容被解析为指令
  3. 输出验证
    :对模型响应进行关键词检查,防止执行敏感操作
// 启用自动转义(配置自定义分隔符时)StTemplateRenderer renderer = StTemplateRenderer.builder()    .startDelimiterToken('<')    .endDelimiterToken('>')    .autoEscape(true)  // 关键配置    .build();

6.3 性能优化技巧

  1. 预编译模板
    PromptTemplate 对象可复用,避免重复解析
  2. 异步渲染
    :变量渲染与模型调用可并行执行
  3. 缓存策略
    :对相同变量组合的渲染结果进行缓存(注意变量敏感性)

七、对比总结

特性

Spring AI

LangChain

选型建议

模板引擎

StringTemplate 4(AST 解析)

f-string 风格(正则替换)

复杂逻辑选 Spring AI,简单场景选 LangChain

变量语法

{var} / 可配置<var>

{var}

Spring AI 更易处理代码块冲突

消息角色

强类型对象(SystemMessage 等)

元组列表[("role", "content")]

大型项目推荐强类型,快速原型可用元组

流式处理

Reactor Flux<T>

Python Generator

根据技术栈选择,功能等价

参数控制

ChatOptions 构建器

llm.bind(**kwargs)

设计模式不同,效果一致

历史管理

手动拼接消息列表

Memory 组件自动管理

LangChain 封装更友好

安全机制

模板引擎支持自动转义

需手动实现转义逻辑

Spring AI 默认更安全

部署集成

Spring Boot 自动配置

需自行封装配置管理

企业级项目推荐 Spring AI


参考资料

  1. Spring AI 官方文档:https://docs.springframework.org.cn/spring-ai/reference/api/prompt.html
  2. LangChain Prompt Templates:https://python.langchain.com/docs/concepts/prompt_templates/
  3. StringTemplate 引擎文档:https://www.stringtemplate.org/