Spring AI中的多轮对话艺术:让大模型主动提问获取明确需求

在日常我们与大模型交互的过程中,通常是我输入一个问题或者场景要求,然后大模型基于我给出的信息进行生成内容。这种方式很容易出现一个答非所问的场景(好像大模型并不能总是回答到我的心趴上🤣)

那么有什么好的解决办法吗?

熟练调教大模型的小伙伴,可能早就有自己的一套标准流程了 – 通过多次与大模型之间的多次问询回答,让大模型能更清晰的知道自己的诉求

那么如何让AI主动问询用户来获取更精确的信息呢?如果我开发的大模型应用,希望集成这样的能力,又可以怎么实现呢?

接下来我们看看,SpringAI中,如何通过巧妙的多轮对话艺术,来实现让大模型主动提问获取明确需求

一、方案设计

1.1 核心问题

在我们这个场景中,核心的问题点在于 如何让大模型在面对模糊需求时,能够主动发起询问,获取更详细的信息,从而提供更精准的响应。

常见的实现中,存在的问题如下:

  • 单次交互试图解决复杂问题
  • 对模糊输入不做处理直接响应
  • 缺少多轮对话的状态管理

这些问题的根本原因在于没有建立起AI与用户之间的有效反馈循环,导致大模型无法获取足够的上下文信息,但是对于大模型而言,响应内容是对它的一个非常重要的考量指标,所以就很容出现大模型话说八道 答非所问等幻觉那问题

1.2 主动问询设计

好的设计应该主动适应用户,而不是让用户适应设计。

很多人认为大模型只能被动接收输入,实际上 Spring AI 提供了 AskUserQuestionTool 这个强大的工具,让AI能够主动向用户提出问题。

它是通过什么实现的呢?可以看下面这张图

核心实现流程

  • 通过Function Callback来实现,将AskUserQuestionTool注册为大模型可回调使用的工具,通过这个工具来实现多轮问询应答

下面是这个工具的核心定义:


public class AskUserQuestionTool {
    @Tool(
        name = "AskUserQuestionTool",
        description = "Use this tool when you need to ask the user questions during execution. This allows you to:\n1. Gather user preferences or requirements\n2. Clarify ambiguous instructions\n3. Get decisions on implementation choices as you work\n4. Offer choices to the user about what direction to take.\n\nUsage notes:\n- Users will always be able to select \"Other\" to provide custom text input\n- Use multiSelect: true to allow multiple answers to be selected for a question\n- If you recommend a specific option, make that the first option in the list and add \"(Recommended)\" at the end of the label\n"
    )
    public String askUserQuestion(@ToolParam(description = "Questions to ask the user (1-4 questions)") List<Question> questions, @ToolParam(description = "User answers collected by the permission component",required = false) Map<String, String> answers) {
        this.validateQuestions(questions);
        // 省略...
    }
}

重点看一下这个工具的描述,将其翻译成中文,内容如下

在执行过程中需要向用户提问时,请使用此工具。这使您能够:
1. 收集用户偏好或需求
2. 明确含糊不清的指令
3. 在工作中就实施选择做出决策
4. 向用户提供选择,让他们决定要采取的方向。

使用须知:
- 用户始终可以选择“其他”来提供自定义文本输入
- 使用multiSelect: true,允许一个问题有多个答案选项
- 如果你推荐了一个特定选项,请将其作为列表中的第一个选项,并在标签末尾加上“(推荐)”

在这个工具类中,通过巧妙的设计,告诉大模型在具体响应之前,可以先进行用户问询

  • 大模型将需要问询的内容,转换为结构化的 List<Question>
  • 通过设计一个QuestionHandler来处理具体的用户问答交互流程,通过责任分离的设计思想,AI负责决定何时提问,具体的UI交互由开发者自定义。你可以将其适配到Web界面、移动端或任何其他平台。

因此具体的问询应答方式,完全是由应用方(即开发者)来控制的

接下来我们进入实战演练,看看如何实现这样的一个多轮问询

二、项目创建

2.1 基础环境要求

要体验SpringAI的问询机制,目前需要升级到SpringAI 2.x版本,同时我们的SpringBoot也可以升级到4.x

  • SpringAI: 2.0.0-M2
  • SpringBoot: 4.0.1
  • JDK21/17:这两版本都可以

除了这几个基本依赖之外,我们可以选择一个支持Function Tool的大模型来作为这个实现的大脑中枢

我们这里选择智谱的大模型GLM-4.5-Flash (原因就是因为它免费,且效果还行,对所有想体验的小伙伴没有任何额外成本投入)

2.2 项目创建

接下来我们创建一个SpringAI应用,对于一个标准的SpringAI应用,在pom.xml配置中,你会看到下面这些基础版本指定,这个也没什么好说的

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.0.1</version>
    <relativePath /> 
</parent>
<properties>
    <maven.compiler.source>21</maven.compiler.source>
    <maven.compiler.target>21</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring-ai.version>2.0.0-M2</spring-ai.version>
</properties>


<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>${spring-ai.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

<repositories>
    <repository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/snapshot</url>
        <releases>
            <enabled>false</enabled>
        </releases>
    </repository>
    <repository>
        <name>Central Portal Snapshots</name>
        <id>central-portal-snapshots</id>
        <url>https://central.sonatype.com/repository/maven-snapshots/</url>
        <releases>
            <enabled>false</enabled>
        </releases>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>

接下来重点看一下我们这个项目所用到的几个核心依赖

<dependencies>
    <dependency>
        <groupId>org.springaicommunity</groupId>
        <artifactId>spring-ai-agent-utils</artifactId>
        <version>0.4.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-zhipuai</artifactId>
    </dependency>
</dependencies>
  • spring-ai-agent-utils: 这个就是SpringAI进行agent开发的关键依赖包
  • spring-ai-starter-model-zhipuai: 这个是智谱大模型进行交互的依赖包

2.3 项目配置

依赖搞定之后,接下来就是在配置文件中,配置LLM访问的相关信息、以及agent相关配置参数,对应的配置文件 resources/application.yml

spring:
  ai:
    zhipuai:
      # api-key 使用你自己申请的进行替换;如果为了安全考虑,可以通过启动参数进行设置
      api-key: ${zhipuai-api-key}
      chat: # 聊天模型
        options:
          model: GLM-4.7-Flash

三、核心实现

接下来我们进行快速体验,首先为了更好的观察应用与大模型之间的交互,我们实现一个LoggingAdvisor

3.1 交互日志打印: MyLoggingAdvisor

说明:这个实现非必需,不感兴趣的直接跳过

public class MyLoggingAdvisor implements BaseAdvisor {
    private final int order;

    public final boolean showSystemMessage;

    public final boolean showAvailableTools;

    private AtomicInteger cnt = new AtomicInteger(1);

    private MyLoggingAdvisor(int order, boolean showSystemMessage, boolean showAvailableTools) {
        this.order = order;
        this.showSystemMessage = showSystemMessage;
        this.showAvailableTools = showAvailableTools;
    }

    @Override
    public int getOrder() {
        return this.order;
    }

    @Override
    public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
        System.out.println("======================= 第 " + cnt.getAndAdd(1) + " 轮 ====================================");

        StringBuilder sb = new StringBuilder("\nUSER: ");

        if (this.showSystemMessage && chatClientRequest.prompt().getSystemMessage() != null) {
            sb.append("\n - SYSTEM: ").append(first(chatClientRequest.prompt().getSystemMessage().getText(), 300));
        }

        if (this.showAvailableTools) {
            Object tools = "No Tools";

            if (chatClientRequest.prompt().getOptions() instanceof ToolCallingChatOptions toolOptions) {
                tools = toolOptions.getToolCallbacks().stream().map(tc -> tc.getToolDefinition().name()).toList();
            }

            sb.append("\n - TOOLS: ").append(ModelOptionsUtils.toJsonString(tools));
        }

        Message lastMessage = chatClientRequest.prompt().getLastUserOrToolResponseMessage();

        if (lastMessage.getMessageType() == MessageType.TOOL) {
            ToolResponseMessage toolResponseMessage = (ToolResponseMessage) lastMessage;
            for (var toolResponse : toolResponseMessage.getResponses()) {
                var tr = toolResponse.name() + ": " + first(toolResponse.responseData(), 1000);
                sb.append("\n - TOOL-RESPONSE: ").append(tr);
            }
        } else if (lastMessage.getMessageType() == MessageType.USER) {
            if (StringUtils.hasText(lastMessage.getText())) {
                sb.append("\n - TEXT: ").append(first(lastMessage.getText(), 1000));
            }
        }

        System.out.println("before: " + sb);
        return chatClientRequest;
    }

    @Override
    public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
        StringBuilder sb = new StringBuilder("\nASSISTANT: ");

        if (chatClientResponse.chatResponse() == null || chatClientResponse.chatResponse().getResults() == null) {
            sb.append(" No chat response ");
            System.out.println("after: " + sb);
            return chatClientResponse;
        }

        for (var generation : chatClientResponse.chatResponse().getResults()) {
            var message = generation.getOutput();
            if (message.getToolCalls() != null) {
                for (var toolCall : message.getToolCalls()) {
                    sb.append("\n - TOOL-CALL: ")
                            .append(toolCall.name())
                            .append(" (")
                            .append(toolCall.arguments())
                            .append(")");
                }
            }

            if (message.getText() != null) {
                if (StringUtils.hasText(message.getText())) {
                    sb.append("\n - TEXT: ").append(first(message.getText(), 1200));
                }
            }
        }

        System.out.println("after: " + sb);
        return chatClientResponse;
    }

    private String first(String text, int n) {
        if (text.length() <= n) {
            return text;
        }
        return text.substring(0, n) + "...";
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {

        private int order = 0;

        private boolean showSystemMessage = true;

        private boolean showAvailableTools = true;

        public Builder order(int order) {
            this.order = order;
            return this;
        }

        public Builder showSystemMessage(boolean showSystemMessage) {
            this.showSystemMessage = showSystemMessage;
            return this;
        }

        public Builder showAvailableTools(boolean showAvailableTools) {
            this.showAvailableTools = showAvailableTools;
            return this;
        }

        public MyLoggingAdvisor build() {
            MyLoggingAdvisor advisor = new MyLoggingAdvisor(this.order, this.showSystemMessage,
                    this.showAvailableTools);
            return advisor;
        }
    }

}

3.2 命令行的问询应答实现:CommandLineQuestionHandler

在前面的设计篇中介绍了AskUserQuestionTool中,设计了一个扩展点 QuestionHandler 由使用者来实现具体的问答交互方案,因此我们先实现一个基础的基于命令行的问答

  • CommandLineQuestionHandler:实现QuestionHandler接口,处理用户问题列表
  • handle方法:遍历问题,显示选项供用户选择,支持单选/多选模式
  • parseResponse方法:解析用户输入,将数字编号转换为对应选项标签,或直接返回自定义文本答案
/**
 * 命令行问题处理器,实现 AskUserQuestionTool.QuestionHandler 接口
 * 用于处理AI工具向用户提出的问题,支持单选、多选和自定义文本输入
 */
public class CommandLineQuestionHandler implements AskUserQuestionTool.QuestionHandler {

    /**
     * 处理传入的问题列表,逐个显示问题和选项,收集用户输入的答案
     *
     * @param questions 需要处理的问题列表
     * @return 包含问题和对应答案的映射表
     */
    @Override
    public Map<String, String> handle(List<AskUserQuestionTool.Question> questions) {
        Map<String, String> answers = new HashMap<>();

        Scanner scanner = new Scanner(System.in);

        for (AskUserQuestionTool.Question q : questions) {
            // 显示问题标题和内容
            System.out.println("\n" + q.header() + ": " + q.question());

            // 获取并显示选项
            List<AskUserQuestionTool.Question.Option> options = q.options();
            for (int i = 0; i < options.size(); i++) {
                AskUserQuestionTool.Question.Option opt = options.get(i);
                System.out.printf("  %d. %s - %s%n", i + 1, opt.label(), opt.description());
            }

            // 根据是否为多选显示提示信息
            if (q.multiSelect()) {
                System.out.println("  (Enter numbers separated by commas, or type custom text)");
            } else {
                System.out.println("  (Enter a number, or type custom text)");
            }

            // 读取用户输入并解析答案
            String response = scanner.nextLine().trim();
            answers.put(q.question(), parseResponse(response, options));
        }

        return answers;
    }

    /**
     * 解析用户输入的响应,根据选项索引或自定义文本返回对应的值
     *
     * @param response 用户输入的原始字符串
     * @param options  可用的选项列表
     * @return 解析后的答案字符串
     */
    private static String parseResponse(String response, List<AskUserQuestionTool.Question.Option> options) {
        try {
            // 尝试将输入解析为选项编号(支持多个选项)
            String[] parts = response.split(",");
            List<String> labels = new ArrayList<>();
            for (String part : parts) {
                int index = Integer.parseInt(part.trim()) - 1;
                if (index >= 0 && index < options.size()) {
                    labels.add(options.get(index).label());
                }
            }
            // 如果没有找到有效的选项编号,则返回原始输入;否则返回选项标签的组合
            return labels.isEmpty() ? response : String.join(", ", labels);
        } catch (NumberFormatException e) {
            // 如果输入不是数字,则将其视为自由文本
            return response;
        }
    }
}

3.3 问询对话实现

创建一个简易聊天客户端,配置了三个Advisor:工具调用顾问、消息记忆顾问和自定义日志顾问(隐藏可用工具和系统消息)。

然后启动一个命令行循环,用户输入问题后通过聊天客户端处理并输出AI助手的回答

@Bean
CommandLineRunner commandLineRunner(ChatClient.Builder chatClientBuilder) throws IOException {
    return args -> {
        // 构建ChatClient实例,配置所需的工具和顾问
        ChatClient chatClient = chatClientBuilder
                // 设置默认工具,这里使用AskUserQuestionTool,它允许AI向用户提问
                .defaultTools(AskUserQuestionTool.builder()
                        // 使用命令行问题处理器来处理AI向用户提出的问题
                        .questionHandler(new CommandLineQuestionHandler())
                        .build())

                // 配置默认的顾问(拦截器),按顺序执行
                .defaultAdvisors(
                        // 工具调用顾问:处理工具调用逻辑
                        ToolCallAdvisor.builder().conversationHistoryEnabled(false).build(),
                        
                        // 消息记忆顾问:维护对话历史,使用消息窗口聊天记忆
                        // 位于工具调用顾问之后,以确保能够记住工具调用结果
                        MessageChatMemoryAdvisor.builder(
                            MessageWindowChatMemory.builder()
                                .maxMessages(500)  // 最多保存500条消息
                                .build())
                        .build(),
                        
                        // 自定义日志顾问:记录对话过程,可控制是否显示可用工具和系统消息
                        MyLoggingAdvisor.builder()
                                .showAvailableTools(true)   // 显示可用工具列表
                                .showSystemMessage(true)    // 显示系统消息
                                .build())
                .build();

        // 启动聊天循环的提示信息
        System.out.println("\nI am your assistant.\n");

        // 使用try-with-resources确保Scanner资源得到正确释放
        try (Scanner scanner = new Scanner(System.in)) {
            // 无限循环,持续接收用户输入并返回AI回复
            while (true) {
                System.out.print("\n$->USER: ");  // 提示用户输入
                
                // 获取用户输入,通过chatClient发送请求,指定对话ID,并输出AI回复
                System.out.println("\n$->ASSISTANT: " + 
                        chatClient.prompt(scanner.nextLine())  // 发送用户输入到AI
                                .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "113331"))  // 设置对话ID以维护上下文
                                .call().content());  // 执行调用并获取AI回复内容
            }
        }
    };
}

3.4 测试验证

直接启动项目,然后再控制台中输入我的原始诉求

下面是完整的文字版本输出内容

I am your assistant.


$->USER: 我准备设计一张锦鲤主题的微信红包封面
=======================1====================================
before: 
USER: 
 - SYSTEM: 
 - TOOLS: ["AskUserQuestionTool"]
 - TEXT: 我准备设计一张锦鲤主题的微信红包封面
after: 
ASSISTANT: 
 - TOOL-CALL: AskUserQuestionTool ({"questions":[{"header": "设计风格", "multiSelect": false, "options": [{"description": "简约线条和现代色彩,适合日常使用", "label": "简约现代风"}, {"description": "传统水墨画风格,展现国潮韵味", "label": "国潮水墨风"}, {"description": "可爱的卡通形象,活泼有趣", "label": "可爱卡通风"}, {"description": "金色为主,配以传统纹样,显得高级", "label": "金色奢华风"}], "question": "你希望红包封面的设计风格是什么样的?"}, {"header": "使用场景", "multiSelect": true, "options": [{"description": "新年、春节等传统节日", "label": "传统节日"}, {"description": "婚礼、生日、庆典等喜庆场合", "label": "喜庆场合"}, {"description": "日常送礼、朋友间互动", "label": "日常互动"}, {"description": "商务场合、客户关系维护", "label": "商务场合"}], "question": "这个红包封面主要会在什么场合使用?"}, {"header": "颜色偏好", "multiSelect": false, "options": [{"description": "红色为主,配金色或白色", "label": "红金配色"}, {"description": "蓝色为主,配金色或白色", "label": "蓝金配色"}, {"description": "纯色背景,简洁大方", "label": "单色背景"}, {"description": "根据锦鲤自然色彩搭配", "label": "自然色彩"}], "question": "你更倾向于什么颜色搭配?"}, {"header": "文字内容", "multiSelect": false, "options": [{"description": "只放锦鲤图案,简洁大方", "label": "纯图案"}, {"description": "锦鲤图案+简单祝福语", "label": "图案+祝福语"}, {"description": "锦鲤图案+品牌或公司信息", "label": "图案+文字"}], "question": "红包封面是否需要添加文字内容?"}]})
 - TEXT: 我来帮你设计锦鲤主题的微信红包封面!为了给你提供更符合需求的设计方案,我需要了解一些具体信息。

设计风格: 你希望红包封面的设计风格是什么样的?
  1. 简约现代风 - 简约线条和现代色彩,适合日常使用
  2. 国潮水墨风 - 传统水墨画风格,展现国潮韵味
  3. 可爱卡通风 - 可爱的卡通形象,活泼有趣
  4. 金色奢华风 - 金色为主,配以传统纹样,显得高级
  (Enter a number, or type custom text)
1

使用场景: 这个红包封面主要会在什么场合使用?
  1. 传统节日 - 新年、春节等传统节日
  2. 喜庆场合 - 婚礼、生日、庆典等喜庆场合
  3. 日常互动 - 日常送礼、朋友间互动
  4. 商务场合 - 商务场合、客户关系维护
  (Enter numbers separated by commas, or type custom text)
1

颜色偏好: 你更倾向于什么颜色搭配?
  1. 红金配色 - 红色为主,配金色或白色
  2. 蓝金配色 - 蓝色为主,配金色或白色
  3. 单色背景 - 纯色背景,简洁大方
  4. 自然色彩 - 根据锦鲤自然色彩搭配
  (Enter a number, or type custom text)
1

文字内容: 红包封面是否需要添加文字内容?
  1. 纯图案 - 只放锦鲤图案,简洁大方
  2. 图案+祝福语 - 锦鲤图案+简单祝福语
  3. 图案+文字 - 锦鲤图案+品牌或公司信息
  (Enter a number, or type custom text)
2
=======================2====================================
before: 
USER: 
 - SYSTEM: 
 - TOOLS: ["AskUserQuestionTool"]
 - TOOL-RESPONSE: AskUserQuestionTool: "User has answered your questions: {\"这个红包封面主要会在什么场合使用?\":\"传统节日\",\"你希望红包封面的设计风格是什么样的?\":\"简约现代风\",\"你更倾向于什么颜色搭配?\":\"红金配色\",\"红包封面是否需要添加文字内容?\":\"图案+祝福语\"}"
after: 
ASSISTANT: 
 - TEXT: 根据您的需求,我为您设计了一个**简约现代风、红金配色的锦鲤主题微信红包封面**,非常适合传统节日使用。

## 🎨 设计方案详情

### 📐 尺寸规格
- **主尺寸**: 1080×1920像素(符合微信红包封面标准)
- **安全区域**: 上下左右各留出100px,确保重要内容不被遮挡

### 🎯 设计元素
1. **背景**: 
   - 使用渐变红色背景(从深红到浅红),营造节日氛围
   - 添加微妙的金色网格纹理,增加现代感

2. **锦鲤图案**:
   - 选用简约线条风格的锦鲤剪影
   - 锦鲤呈向上游动姿态,寓意"鱼跃龙门"
   - 金色线条勾勒,与红色背景形成对比

3. **文字内容**:
   - **主标题**: "福满锦鲤"(金色立体效果)
   - **祝福语**: "年年有余,福运绵长"
   - **装饰元素**: 水波纹、祥云等传统元素点缀

4. **品牌信息**(可选):
   - 底部小字:"[您的品牌/姓名]祝您新年快乐"

### 🌈 色彩方案
- **主色**: #D32F2F(深红)→ #FFEB3B(金色渐变)
- **辅助色**: #FFF8E1(浅米色,用于文字背景)

### ✨ 设计亮点
- 简约现代的线条风格,避免过于繁复
- 红金配色经典大气,符合传统节日氛围
- 锦鲤向上游动的姿态,寓意积极向上
- 节奏感的布局,视觉平衡协调

### 📝 使用建议
- 适合春节、元宵节、中秋等传统节日发送
- 可用于亲友祝福、商务拜年、客户答谢等场合
- 红金配色既有传统韵味,又不失现代感

这个设计方案既保持了传统节日的喜庆氛围,又符合现代审美,相信会是一个很受欢迎的红包封面!您觉得这个设计方案如何?如果需要调整任何细节,请告诉我。

$->ASSISTANT: 根据您的需求,我为您设计了一个**简约现代风、红金配色的锦鲤主题微信红包封面**,非常适合传统节日使用。

## 🎨 设计方案详情

### 📐 尺寸规格
- **主尺寸**: 1080×1920像素(符合微信红包封面标准)
- **安全区域**: 上下左右各留出100px,确保重要内容不被遮挡

### 🎯 设计元素
1. **背景**: 
   - 使用渐变红色背景(从深红到浅红),营造节日氛围
   - 添加微妙的金色网格纹理,增加现代感

2. **锦鲤图案**:
   - 选用简约线条风格的锦鲤剪影
   - 锦鲤呈向上游动姿态,寓意"鱼跃龙门"
   - 金色线条勾勒,与红色背景形成对比

3. **文字内容**:
   - **主标题**: "福满锦鲤"(金色立体效果)
   - **祝福语**: "年年有余,福运绵长"
   - **装饰元素**: 水波纹、祥云等传统元素点缀

4. **品牌信息**(可选):
   - 底部小字:"[您的品牌/姓名]祝您新年快乐"

### 🌈 色彩方案
- **主色**: #D32F2F(深红)→ #FFEB3B(金色渐变)
- **辅助色**: #FFF8E1(浅米色,用于文字背景)

### ✨ 设计亮点
- 简约现代的线条风格,避免过于繁复
- 红金配色经典大气,符合传统节日氛围
- 锦鲤向上游动的姿态,寓意积极向上
- 节奏感的布局,视觉平衡协调

### 📝 使用建议
- 适合春节、元宵节、中秋等传统节日发送
- 可用于亲友祝福、商务拜年、客户答谢等场合
- 红金配色既有传统韵味,又不失现代感

这个设计方案既保持了传统节日的喜庆氛围,又符合现代审美,相信会是一个很受欢迎的红包封面!您觉得这个设计方案如何?如果需要调整任何细节,请告诉我。

我们可以将这个方案,拿来生成图片,比如下面是豆包、千问、元宝等不同平台的生成效果

模型 效果图
豆包 豆包
元宝 元宝效果图
千问 千问效果图
百度 百度
ChatGPT ChatGPT效果图

四、小结

本片内容通过设计+实例给大家演示了一下Spring AI中的多轮对话艺术,如何让大模型主动提问获取明确需求? 下面几部即可:

  • 主动询问机制:使用AskUserQuestionTool让AI在信息不足时主动提问
  • 灵活的响应处理:通过自定义QuestionHandler适配不同平台的交互方式
  • 对话状态管理:利用MessageChatMemoryAdvisor维护多轮对话的上下文
  • 工具组合使用:将询问工具与记忆顾问、日志顾问等配合使用,构建完整的交互体系
  • 异步处理支持:在Web环境中使用SSE实现流畅的多轮对话体验

项目源码:rag-qa-bot


对于大模型应用开发感兴趣的小伙伴,或者对上面提到的知识点有疑惑的小伙伴,不妨看看以下几篇内容(每篇耗时不超过五分钟😊)

零基础入门:


实战

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐