智能体实现关键技术

CoT 思维链

CoT(Chain of Thought)思维链是一种让 AI 像人类一样 “思考” 的技术,帮助 AI 在处理复杂问题时能够按步骤思考。而且还可以让模型在生成答案时展示推理过程,便于我们理解和优化 AI。

CoT 的实现方式其实很简单,可以在输入 Prompt 时,给模型提供额外的提示或引导,比如 “让我们一步一步思考这个问题”,让模型以逐步推理的方式生成回答。还可以运用 Prompt 的优化技巧 few shot,给模型提供包含思维链的示例问题和答案,让模型学习如何构建自己的思维链。

Agent Loop 执行循环

Agent Loop 是智能体最核心的工作机制,指智能体在没有用户输入的情况下,自主重复执行推理和工具调用的过程。

在传统的聊天模型中,每次用户提问后,AI 回复一次就结束了。但在智能体中,AI 回复后可能会继续自主执行后续动作(如调用工具、处理结果、继续推理),形成一个自主执行的循环,直到任务完成(或者超出预设的最大步骤数)。

ReAct 模式

ReAct(Reasoning + Acting)是一种结合推理和行动的智能体架构,它模仿人类解决问题时 ”思考 - 行动 - 观察” 的循环,目的是通过交互式决策解决复杂任务,是目前最常用的智能体工作模式之一。

核心思想:

  1. 推理(Reason):将原始问题拆分为多步骤任务,明确当前要执行的步骤,比如 “第一步需要打开编程导航网站”。

  2. 行动(Act):调用外部工具执行动作,比如调用搜索引擎、打开浏览器访问网页等。

  3. 观察(Observe):获取工具返回的结果,反馈给智能体进行下一步决策。比如将打开的网页代码输入给 AI。

  4. 循环迭代:不断重复上述 3 个过程,直到任务完成或达到终止条件。

所需支持系统

1)首先是 AI 大模型,大模型提供了思考、推理和决策的核心能力。

2)记忆系统,智能体需要记忆系统来存储对话历史、中间结果和执行状态,这样它才能够进行连续对话并根据历史对话分析接下来的工作步骤。

3)知识库

4)工具调用

示例代码

智能体族谱(继承关系)

 BaseAgent  ←  ReActAgent  ←  ToolCallAgent  ←  AiRobot
  • AiRobot 只做“个性化配置”(提示词、最大步数、ChatClient)。

  • ToolCallAgent 落地了 ReAct 的 think/act 两大核心:

    • think():让模型决定“要不要用工具、用哪些工具、工具参数是什么”;

    • act()自己执行工具(不是交给 Spring AI 自动跑),把结果塞回对话上下文。

ReAct 循环(BaseAgent + ReActAgent)

AgentState枚举类

 package com.yupi.aiagent.agent.model;
 ​
 /**
  * 代理执行状态的枚举类  
  */  
 public enum AgentState {  
   
     /**  
      * 空闲状态  
      */  
     IDLE,  
   
     /**  
      * 运行中状态  
      */  
     RUNNING,  
   
     /**  
      * 已完成状态  
      */  
     FINISHED,  
   
     /**  
      * 错误状态  
      */  
     ERROR  
 }

BaseAgent 类

 package com.yupi.aiagent.agent;                       
 ​
 import cn.hutool.core.util.StrUtil;                  
 import com.yupi.aiagent.agent.model.AgentState;       
 import lombok.Data;                                   
 import lombok.extern.slf4j.Slf4j;                     
 import org.springframework.ai.chat.client.ChatClient; 
 import org.springframework.ai.chat.messages.Message;  
 import org.springframework.ai.chat.messages.UserMessage; 
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 
 ​
 import java.util.ArrayList;                           
 import java.util.List;                                
 import java.util.concurrent.CompletableFuture;        
 ​
 /**
  * 抽象基础代理类,用于管理代理状态和执行流程。
  * 提供状态转换、内存管理和基于步骤的执行循环的基础功能。
  * 子类必须实现step方法。
  */
 @Data                                               
 @Slf4j                                             
 public abstract class BaseAgent {                      // 定义抽象类:不可直接实例化,需子类实现 step()
 ​
     // 核心属性
     private String name;                                // 字段:代理(智能体)的名字,便于区分或日志标识
 ​
     // 提示
     private String systemPrompt;                               // 字段:系统提示词(一般给 LLM 的“角色/规则”)
     private String nextStepPrompt;                               // 字段:引导模型进行下一步的提示词(中间指令)
 ​
     // 状态
     private AgentState state = AgentState.IDLE;               // 字段:代理当前状态,默认 IDLE
 ​
     // 执行控制
     private int maxSteps = 10;                               // 字段:最多执行多少步
     private int currentStep = 0;                             // 字段:当前正在执行到第几步(从 1 开始)
 ​
     // LLM
     private ChatClient chatClient;                           // 字段:与大模型交互的客户端(Spring AI)
 ​
     // Memory(需要自主维护会话上下文)
     private List<Message> messageList = new ArrayList<>();  // 字段:会话历史(消息上下文)。供 step() 使用或留存
 ​
     /**
      * 运行代理(同步执行)
      *
      * @param userPrompt 用户提示词
      * @return 执行结果
      */
     public String run(String userPrompt) {                   // 同步执行入口:阻塞直到完成,返回字符串结果
         if (this.state != AgentState.IDLE) {                 // 如果当前不在空闲状态,拒绝启动
             throw new RuntimeException("Cannot run agent from state: " + this.state);
         }
         //如果提示词为空,抛异常
         if (StrUtil.isBlank(userPrompt)) {                   // 判空:userPrompt 为空则抛错
             throw new RuntimeException("Cannot run agent with empty user prompt");
         }
         //更改状态为运行中
         state = AgentState.RUNNING;                          // 将状态切换为 RUNNING
         // 记录消息上下文
         messageList.add(new UserMessage(userPrompt));        // 把用户输入保存到会话历史(用于 LLM 上下文)
 ​
         // 保存结果列表
         List<String> results = new ArrayList<>();            // 用于累计每一步的字符串结果
 ​
         try {
             // 循环执行步骤,直到完成或达到最大步骤
             for (int i = 0; i < maxSteps && state != AgentState.FINISHED; i++) { // 最多跑 maxSteps;若中途标记 FINISHED 则提前结束
                 int stepNumber = i + 1;                      // 人类友好编号:从 1 开始
                 currentStep = stepNumber;                    // 更新当前步号(用于后续判断或日志)
                 log.info("Executing step " + stepNumber + "/" + maxSteps); // 记录日志:开始第几步
 ​
                 //调用单步执行的方法
                 String stepResult = step();                  // 调用抽象方法:由子类实现每一步的实际逻辑
                 String result = "Step " + stepNumber + ": " + stepResult; // 组装展示用的结果行
 ​
                 results.add(result);                            // 把当前步骤的结果压入累计列表
             }
             // 检查是否超出步骤限制
             if (currentStep >= maxSteps) {                   // 如果跑到了最大步数
                 state = AgentState.FINISHED;                 // 强制标记完成(防止无限循环)
                 results.add("Terminated: Reached max steps (" + maxSteps + ")"); // 追加终止原因
             }
             return String.join("\n", results);               // 把所有步骤结果用换行拼成一个字符串返回
         } catch (Exception e) {                              // 任意异常
             state = AgentState.ERROR;                        // 标记错误状态
             log.error("Error executing agent", e);           // 打日志(带异常栈)
             return "执行错误" + e.getMessage();                // 把错误信息返回给调用方
         } finally {
             // 清理资源
             this.cleanup();                                  // 无论成功/失败,执行清理(子类可重写)
         }
     }
 ​
     /**
      * 运行代理(流式输出)
      *
      * @param userPrompt 用户提示词
      * @return SseEmitter实例  SseEmitter实例(用于服务端向客户端推送流数据)
      */
     public SseEmitter runStream(String userPrompt) {         // 流式执行入口:返回 SSE 管道,异步推送每步结果
         // 创建SseEmitter,设置较长的超时时间
         SseEmitter emitter = new SseEmitter(300000L); // 5分钟超时 // 创建 SSE 对象,客户端可持续接收“服务器推送”
 ​
         // 使用线程异步处理,避免阻塞主线程
         CompletableFuture.runAsync(() -> {                    // 开一个异步任务,不阻塞当前线程
             try {
                 if (this.state != AgentState.IDLE) {         // 再次检查启动条件:必须是空闲状态
                     emitter.send("错误:无法从状态运行代理: " + this.state); // 状态不对,推送错误消息给前端
                     emitter.complete();                      // 结束 SSE
                     return;                                  // 退出异步任务
                 }
                 if (StrUtil.isBlank(userPrompt)) {           // 判空:userPrompt 不合法
                     emitter.send("错误:不能使用空提示词运行代理");
                     emitter.complete();
                     return;
                 }
 ​
                 // 更改状态
                 state = AgentState.RUNNING;                  // 切换为运行中
                 // 记录消息上下文
                 messageList.add(new UserMessage(userPrompt));// 保存用户输入到上下文
 ​
                 try {
                     for (int i = 0; i < maxSteps && state != AgentState.FINISHED; i++) { // 循环直到完成或达到上限
                         int stepNumber = i + 1;              // 友好步骤号
                         currentStep = stepNumber;            // 更新当前步
                         log.info("Executing step " + stepNumber + "/" + maxSteps);
 ​
                         // 单步执行
                         String stepResult = step();          // 调用子类实现的单步逻辑
                         String result = "Step " + stepNumber + ": " + stepResult;
 ​
                         // 发送每一步的结果到客户端
                         emitter.send(result);                // 通过 SSE 把当前步结果推给前端(浏览器/客户端)
                     }
 ​
                     // 检查是否超出步骤限制
                     if (currentStep >= maxSteps) {           // 到达上限仍未标记完成
                         state = AgentState.FINISHED;         // 标记完成
                         emitter.send("执行结束: 达到最大步骤 (" + maxSteps + ")"); // 通知前端原因
                     }
                     //正常完成流输出
                     emitter.complete();                      // 正常结束 SSE 流
                 } catch (Exception e) {                      // 单步或推送过程中出错
                     state = AgentState.ERROR;                // 标记错误
                     log.error("执行智能体失败", e);
                     try {
                         emitter.send("执行错误: " + e.getMessage()); // 把错误推给前端
                         emitter.complete();                  // 结束 SSE
                     } catch (Exception ex) {
                         emitter.completeWithError(ex);       // 结束并带错误
                     }
                 } finally {
                     // 清理资源
                     this.cleanup();                          // 结束时清理(子类可重写)
                 }
             } catch (Exception e) {                          // 异步任务的外围异常
                 emitter.completeWithError(e);                // 结束 SSE 并上报错误
             }
         });
 ​
         // 设置超时回调
         emitter.onTimeout(() -> {                            // 如果超过 5 分钟未完成/心跳,触发超时
             this.state = AgentState.ERROR;                   // 标记错误状态
             this.cleanup();                                  // 清理资源
             log.warn("SSE connection timed out");            // 打超时警告日志
         });
 ​
         //设置完成回调
         emitter.onCompletion(() -> {                         // SSE 正常/异常完成后都会回调
             if (this.state == AgentState.RUNNING) {          // 如果仍处在 RUNNING(未显式标记完成)
                 this.state = AgentState.FINISHED;            // 兜底标记为 FINISHED
             }
             this.cleanup();                                  // 再次清理(与 finally 中的清理并不冲突,需保证幂等)
             log.info("SSE connection completed");            // 记录完成日志
         });
 ​
         return emitter;                                      // 把可供前端订阅的 SSE 管道返回
     }
 ​
     /**
      * 执行单个步骤
      *
      * @return 步骤执行结果
      */
     public abstract String step();                           // 抽象方法:子类必须实现每一步做什么、何时更新 state
 ​
     /**
      * 清理资源
      */
     protected void cleanup() {                               // 钩子方法:默认空实现,子类可重写(如清空状态、关闭连接等)
         // 子类可以重写此方法来清理资源
     }
 }
 ​

ReActAgent类

 ​
 /**
  * ReAct (Reasoning and Acting) 模式的代理抽象类  
  * 实现了思考-行动的循环模式
  *
  * 继承 BaseAgent,自动拥有 run()、runStream()、messageList、state 等能力。
  * 这个类额外定义了 ReAct 模式的抽象方法。
  */  
 @EqualsAndHashCode(callSuper = true)  /// 继承父类的equals和hashCode实现
 @Data
 public abstract class ReActAgent extends BaseAgent {  
   
     /**  
      * 处理当前状态并决定下一步行动  
      *  
      * @return 是否需要执行行动,true表示需要执行,false表示不需要执行  
      */  
     public abstract boolean think();  
   
     /**  
      * 执行决定的行动  
      *  
      * @return 行动执行结果  
      */  
     public abstract String act();  
   
     /**  
      * 执行单个步骤:思考和行动  
      *  
      * @return 步骤执行结果  
      */  
     @Override  
     public String step() {  
         try {  
             boolean shouldAct = think();  // 先思考,判断是否需要行动
             if (!shouldAct) {  
                 return "思考完成 - 无需行动";  
             }  
             return act();  // 需要行动则执行  
         } catch (Exception e) {  
             // 记录异常日志  
             e.printStackTrace();  
             return "步骤执行失败: " + e.getMessage();  
         }  
     }  
 }

ReActAgent.java:给出 step() 的统一模板:

BaseAgent.java:提供运行状态、消息上下文、run(...) / runStream(...) 循环框架。

  • runStream(String userPrompt)

    • 创建 SseEmitter(300_000 ms)

    • 校验状态 IDLE & 非空提示;

    • state = RUNNING,把 new UserMessage(userPrompt) 放入 messageList

    • 异步循环:反复调用 step(),每步把返回文本 emitter.send(...)

    • 结束/异常/超时:改状态、cleanup()、关闭连接。

ToolCallAgent(多步“思考-行动”核心)

 ​
 /**
  * 处理工具调用的基础代理类,具体实现了 think 和 act 方法,可以用作创建实例的父类  
  */  
 @EqualsAndHashCode(callSuper = true)                 // Code(callSuper = true) 是 Lombok 框架提供的注解,用于自动生成类的 equals() 和 hashCode() 方法
 @Data                                               
 @Slf4j                                              
 public class ToolCallAgent extends ReActAgent {      // 定义类:继承 ReActAgent(已有 think/act 框架),可直接实例化
   
     // 可用的工具  
     private final ToolCallback[] availableTools;     // 可被模型“调用”的工具集合(你自己注册好的工具列表)
                                                     
 ​
     // 保存了工具调用信息的响应  (要调用哪些工具)
     private ChatResponse toolCallChatResponse;       // 保存大模型(LLM)在“思考阶段”返回的完整响应对象,里面记录了 模型打算调用哪些工具、工具名字、工具参数,
 ​
     // 工具调用管理者  
     private final ToolCallingManager toolCallingManager; // 负责按照模型指令真正去执行工具、并收集结果
   
     // 禁用内置的工具调用机制,自己维护上下文  
      private final ChatOptions chatOptions;              // 让模型只返回“工具调用指令”然后由你自己去决定怎么执行工具、怎么把结果写回上下文。
 ​
 ​
 ​
     
     // 构造方法:初始化工具和配置
     public ToolCallAgent(ToolCallback[] availableTools) {  // 构造器:外部把可用工具传进来
         super();                                           // 调父类构造(BaseAgent/ReActAgent)
         this.availableTools = availableTools;              // 保存可用工具数组
         this.toolCallingManager = ToolCallingManager.builder().build(); // 创建工具调用管理器(默认配置)
         // 禁用 Spring AI 内置的工具调用机制,自己维护选项和消息上下文  
         this.chatOptions = DashScopeChatOptions.builder()
                 .withProxyToolCalls(true)                  // withProxyToolCalls(true)表示“代理模式”:不要让 Spring AI 自动帮你调用工具
                 .build();  
     }
 ​
     
     
     /**
      * 处理当前状态并决定下一步行动
      *
      * @return 是否需要执行行动
      */
     @Override
     public boolean think() {                               // ReAct 的“思考”阶段:询问模型要不要用工具、用哪个、传什么参
         // 如果有下一步提示词,添加到上下文
         if (getNextStepPrompt() != null && !getNextStepPrompt().isEmpty()) {
             UserMessage userMessage = new UserMessage(getNextStepPrompt()); // 把 nextStepPrompt 当作一条用户消息
             getMessageList().add(userMessage);             // 压入对话历史,参与本次 Prompt
         }
         List<Message> messageList = getMessageList();         // 获取会话上下文(用户/助手/工具等历史消息)
         Prompt prompt = new Prompt(messageList, chatOptions); // 构建 Prompt(消息 + ChatOptions)
 ​
         try {
             // 调用大模型,获取工具调用建议
             ChatResponse chatResponse = getChatClient().prompt(prompt) // 用 ChatClient 发送本次 Prompt
                     .system(getSystemPrompt())              // 附带系统提示词(角色/规则)
                     .tools(availableTools)                  // 告诉模型:能用哪些工具(名称/签名/描述)
                     .call()                                 // 触发调用
                     .chatResponse();                        // 拿到统一响应对象(含消息/工具调用结构)
 ​
             // 记录响应,用于 Act
             this.toolCallChatResponse = chatResponse;       // 保存,以便 act() 阶段按照它来执行工具
             AssistantMessage assistantMessage = chatResponse.getResult().getOutput(); // 模型“助手消息”(含自然语言 + 工具调用列表)
 ​
             // 输出提示信息(记录模型“思考”的自然语言结果)
             String result = assistantMessage.getText();     // 助手文本(模型的解释/思考/计划)
             List<AssistantMessage.ToolCall> toolCallList = assistantMessage.getToolCalls(); // 解析出模型建议调用的工具清单
             log.info(getName() + "的思考: " + result);      // 打日志:看到模型在想什么
             log.info(getName() + "选择了 " + toolCallList.size() + " 个工具来使用"); // 打日志:本轮准备用几个工具
 ​
             // 格式化工具调用信息并打印日志(工具名 + 参数)
             String toolCallInfo = toolCallList.stream()
                     .map(toolCall -> String.format("工具名称:%s,参数:%s",
                             toolCall.name(),
                             toolCall.arguments())
                     )
                     .collect(Collectors.joining("\n"));
 ​
             log.info(toolCallInfo);                         // 打印每个工具调用的细节
 ​
             //如果不调用工具返回false
             if (toolCallList.isEmpty()) {                   // 没工具要用 ⇒ 本轮无需“行动”
                 // 只有不调用工具时,才记录助手消息
                 getMessageList().add(assistantMessage);     // 把助手自然语言回写进对话历史
                 return false;                               // 返回 false:ReActAgent.step() 将不会调用 act()
             } else {
                 // 需要调用工具时,无需记录助手消息,因为调用工具时会自动记录
                 return true;                                // 返回 true:ReActAgent.step() 将进入 act() 执行工具
             }
         } catch (Exception e) {                             // 调模型出错(网络/鉴权/响应不合法等)
             log.error(getName() + "的思考过程遇到了问题: " + e.getMessage());
             getMessageList().add(
                     new AssistantMessage("处理时遇到错误: " + e.getMessage())); // 把错误也写回会话,便于后续提示
             return false;                                   // 出错就不再行动
         }
     }
 ​
     
     
     /**
      * 执行工具调用并处理结果
      *
      * @return 执行结果
      */
     @Override
     public String act() {                                   // ReAct 的“行动”阶段:真正按模型指令去跑工具
         // 如果没有工具调用,返回提示
         if (!toolCallChatResponse.hasToolCalls()) {         // 防御:万一没有工具调用(或 think 出错)
             return "没有工具调用";
         }
         // 构建提示词(把最新 messageList + chatOptions 打包)
         Prompt prompt = new Prompt(getMessageList(), chatOptions);
 ​
         // 执行工具调用
         ToolExecutionResult toolExecutionResult =
                 toolCallingManager.executeToolCalls(prompt, toolCallChatResponse);
                                                             // 关键:让 ToolCallingManager 按 chatResponse 中的 ToolCalls
                                                             // 逐个找到对应 ToolCallback,传入参数,执行,并把返回值
                                                             // 写成 ToolResponseMessage 追加到会话历史中
 ​
         // 记录消息上下文,conversationHistory 已经包含了助手消息和工具调用返回的结果
         setMessageList(toolExecutionResult.conversationHistory());
                                                             // 用执行后的“完整会话历史”覆盖本地 messageList
 ​
         // 当前工具调用的结果:取最后一条(一般是最近一次 ToolResponseMessage)
         ToolResponseMessage toolResponseMessage =
                 (ToolResponseMessage) CollUtil.getLast(toolExecutionResult.conversationHistory());
 ​
         // 格式化工具执行结果(把每个工具的返回拼成字符串)
         String results = toolResponseMessage.getResponses().stream()
                 .map(response -> "工具 " + response.name() + " 完成了它的任务!结果: " + response.responseData())
                 .collect(Collectors.joining("\n"));
 ​
         // 判断是否调用了终止工具(典型做法:约定有个 doTerminate 工具用来结束流程)
         boolean terminateToolCalled = toolResponseMessage.getResponses().stream()
                 .anyMatch(response -> "doTerminate".equals(response.name()));
         if (terminateToolCalled) {
             setState(AgentState.FINISHED);                  // 标记流程完成(BaseAgent 的 run/runStream 会据此停循环)
         }
 ​
         log.info(results);                                  // 打印工具执行结果
         return results;                                     // 返回给上层(同步:拼接到结果;流式:SSE 推给前端)
     }
 }
 ​

AiRobot(智能体)

 ​
 /**
  * 有自主规划能力的超级智能体
  */
 @Component                                           // 标注为 Spring 组件,启动时自动注册到容器
 public class  AiRobot extends ToolCallAgent {         // 定义类 YuManus,继承 ToolCallAgent,具备“调用工具”的能力
 ​
     // 构造方法:初始化工具和大模型
     public YuManus(ToolCallback[] allTools,          // 构造参数:可用工具的数组,供代理在对话中调用
                    ChatModel dashscopeChatModel) {   // 构造参数:具体的大语言模型实现(例如 DashScope),由外部注入
         super(allTools);                             // 调用父类构造,注册全部工具到基础代理
         this.setName("yuManus");                     // 设置代理名称,便于区分或日志标识
 ​
        
         // 系统提示词:定义代理角色
         String SYSTEM_PROMPT = """                   // 使用 Java 文本块("""),""" ... """  //:这是 Java 15 的语法让多行文本更简洁直观定义系统级提示词
                 You are AiRobots, an all-capable AI assistant, aimed at solving any task presented by the user.  
                 You have various tools at your disposal that you can call upon to efficiently complete complex requests.  
                 """;                                 
         this.setSystemPrompt(SYSTEM_PROMPT);         // 将系统提示词注入到代理,用于设定模型的“身份/基调”
 ​
         // 下一步提示词:引导代理规划行动
         String NEXT_STEP_PROMPT = """                // 定义“下一步提示词”,指导代理如何做任务分解与工具编排
                 Based on user needs, proactively select the most appropriate tool or combination of tools.  
                 For complex tasks, you can break down the problem and use different tools step by step to solve it.  
                 After using each tool, clearly explain the execution results and suggest the next steps.  
                 If you want to stop the interaction at any point, use the `terminate` tool/function call.  
                 """;                                 
         this.setNextStepPrompt(NEXT_STEP_PROMPT);    // 设置“下一步提示词”,用于驱动代理的规划/思考框架
         this.setMaxSteps(20);                        // 限制单次会话内可执行的最大工具步骤数为 20,防止无限循环
 ​
         // 初始化客户端(集成大模型和日志增强器)
         ChatClient chatClient = ChatClient.builder(dashscopeChatModel) // 基于注入的 ChatModel 构建 ChatClient
                 .defaultAdvisors(new MyLoggerAdvisor()) // 配置默认的 Advisor(如日志记录、审计、埋点等横切逻辑)
                 .build();                               // 生成 ChatClient 实例
         this.setChatClient(chatClient);                 // 将 ChatClient 注入到代理,后续对话/工具调用由其承载
     }                                                   
 }                                                      
 ​

总结

  • 系统提示:AiRobot(你的 Agent Bean)在构造函数里设置配置名、提示词、步数上限、可用工具与 ChatClient

  • ToolCallback[](工具集合) 把你自己写的工具,注册成一个工具类,之后传给AiRobot让他去调用工具

  • 运行机制(见 BaseAgentReActAgentToolCallAgent):

    1. think():把当前消息上下文 + nextStepPrompt 送给模型,让模型决定是否调用工具hasToolCalls())。

    2. act():若有工具调用,交给 ToolCallingManager 执行,结果作为 ToolResponseMessage 回填上下文。

    3. 如调用了 doTerminateTerminateTool),状态置为 FINISHED;否则继续下一步,最多 20 步。

    4. SSE (也可选同步)输出每一步的结果。

 

Logo

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

更多推荐