前面的博文里,我们用 spacecloud 展示了 Spring AI 2.0 的全栈能力,用 babi 探索了三种框架的选型差异。这次轮到 LangChain4j——whatsmars-ai 模块在两天内完成了六大批量更新,从基础对话一跃成为涵盖多 Agent 编排、高级 RAG 管线、输入输出护栏、结构化输出、多模态、可观测性的全功能 AI 应用。

如果说 Spring AI 2.0 的关键词是"全栈",那 LangChain4j 的关键词就是"高级"——它不追求覆盖面,但在 Agent 编排和 RAG 管线上做得更深。

一、更新概览

先看本次新增了哪些能力:

能力 核心类/模块 说明
Agentic 多 Agent 编排 AgenticConfig + 11 个 Agent 接口 5 种编排模式:基础、顺序、循环、并行、Supervisor
Guardrails 护栏 ProfanityInputGuardrail / QualityOutputGuardrail 输入侧敏感词拦截 + 输出侧质量检查 + reprompt
高级 RAG 管线 5 阶道流水线 查询压缩 → 智能路由 → 检索 → LLM 重排 → 注入
结构化输出 StructuredOutputAssistant AI 方法直接返回 Java record
多模态 Vision / Image / Audio 视觉理解 + 文生图异步 API + 语音合成
ConditionalOnMcpStatusUp 自定义 @Conditional TCP 端口探测,MCP Server 在线才加载 Bean
ChatModelListener LoggingChatModelListener Token 用量 + 耗时 + 消息分布全监听

下面逐个展开,每个模块都有代码片段和设计思路解析。

二、Agentic:五种 Agent 编排模式

这是本次更新最大的模块——13 个文件,11 个 Agent 接口,覆盖了 LangChain4j AgenticServices 的全部编排能力。

Agent 接口:声明式定义

所有 Agent 都是声明式接口,用 @Agent + @SystemMessage + @UserMessage 注解定义:

public interface ResearchAgent {
    @Agent("研究指定主题并生成详细的研究报告")
    @SystemMessage("""
            你是一个专业的技术研究员。...
            关于网络搜索工具的使用原则:
            - 仅当主题涉及近期事件、实时数据或你知道截止日期之后的变化时才使用搜索工具
            - 对于已有充分知识的成熟技术概念和框架特性,直接基于自身知识撰写报告即可
            """)
    @UserMessage("请研究以下主题:{{topic}}")
    String research(@V("topic") String topic);
}

流式变体只需把返回类型从 String 改为 TokenStream

public interface StreamingResearchAgent {
    @Agent("研究指定主题并生成详细的研究报告(流式)")
    TokenStream research(@V("topic") String topic);
}

返回结构化对象的 Agent 也很直观——QualityReviewerAgent 直接返回 record:

public interface QualityReviewerAgent {
    @Agent(name = "质量审查专家", description = "评审技术文档的质量")
    @UserMessage("请评审以下技术文档:\n\n{{document}}")
    QualityReview reviewDocument(@V("document") String document);

    record QualityReview(double score, String feedback, String strengths) {}
}

这种声明式风格是 LangChain4j 的核心设计:Agent 的定义就是接口 + 注解,不需要写实现类,框架在运行时自动生成代理。

模式一:顺序工作流

Research → Summarize → Translate,前一个 Agent 的输出通过 AgenticScope 自动传递给下一个:

@Bean
public UntypedAgent sequentialWorkflow(ChatModel chatModel, WebSearchTool webSearchTool) {
    var research = AgenticServices.agentBuilder(ResearchAgent.class)
            .chatModel(chatModel).tools(webSearchTool)
            .outputKey("researchResult").build();
    var summarize = AgenticServices.agentBuilder(SummarizerAgent.class)
            .chatModel(chatModel).outputKey("summary").build();
    var translate = AgenticServices.agentBuilder(TranslatorAgent.class)
            .chatModel(chatModel).outputKey("translation").build();
    return AgenticServices.sequenceBuilder()
            .subAgents(research, summarize, translate)
            .outputKey("translation")
            .build();
}

每个 Agent 通过 outputKey 声明自己的输出存入 AgenticScope 的哪个 key,后续 Agent 可以从 scope 中读取。最后的 outputKey("translation") 指定整个工作流的最终输出取 translation。

模式二:流式顺序工作流

中间步骤用同步 ChatModel,最后一步换成 StreamingChatModel,最终结果通过 SSE 流式输出:

var translate = AgenticServices.agentBuilder(StreamingTranslatorAgent.class)
        .streamingChatModel(streamingChatModel)
        .outputKey("translation")
        .build();

这个设计很巧妙:用户只需要最终翻译结果流式输出,中间的研究和摘要步骤不需要流式。这样可以减少不必要的 LLM 调用开销,同时保证用户体验——一气呵成地看到最终结果。

模式三:循环工作流

Writer 写文档 → QualityReviewer 评审,score >= 0.7 退出,最多 3 轮:

@Bean
public UntypedAgent loopWorkflow(WriterAgent writerAgent, QualityReviewerAgent qualityReviewerAgent) {
    return AgenticServices.loopBuilder()
            .subAgents(writerAgent, qualityReviewerAgent)
            .outputKey("document")
            .exitCondition(scope -> {
                QualityReviewerAgent.QualityReview review =
                        (QualityReviewerAgent.QualityReview) scope.readState("review");
                return review != null && review.score() >= 0.7;
            })
            .maxIterations(3)
            .build();
}

exitCondition 接收一个 Predicate<AgenticScope>,从 scope 中读取上一步的 review 结果,检查评分是否达标。maxIterations(3) 是安全阀——如果 LLM 一直无法达到质量标准,3 轮后强制退出。

Controller 层的流式循环更复杂——手动编排写-审循环,每轮发送 SSE 事件:iteration(状态:writing/reviewing)、stream(写作 token 流)、review(评分+反馈)、document(最终文档)。这让用户能实时看到"AI 正在写第几稿"和"评审打了几分"。

模式四:并行工作流

三个代码审查 Agent(安全、性能、最佳实践)并行执行,通过 output 回调聚合结果:

return AgenticServices.parallelBuilder()
        .subAgents(securityReviewer, performanceReviewer, bestPracticeReviewer)
        .outputKey("fullReview")
        .output(scope -> {
            String security = (String) scope.readState("securityReview");
            String performance = (String) scope.readState("performanceReview");
            String bestPractice = (String) scope.readState("bestPracticeReview");
            return """
                === 综合代码审查报告 ===
                【安全审查】%s
                【性能审查】%s
                【最佳实践】%s
                """.formatted(security, performance, bestPractice);
        })
        .build();

并行模式下,所有子 Agent 同时执行,output 回调在全部完成后被调用,从 scope 中读取各个 Agent 的输出并合并为最终报告。

模式五:Supervisor 编排

Supervisor 是最灵活的模式——它不是预定义的流程,而是一个"主管 Agent",根据用户问题动态决定调用哪些子 Agent、以什么顺序:

return AgenticServices.supervisorBuilder()
        .chatModel(chatModel)
        .subAgents(researcher, summarizer, securityReviewer,
                   performanceReviewer, bestPracticeReviewer)
        .contextGenerationStrategy(SupervisorContextStrategy.CHAT_MEMORY_AND_SUMMARIZATION)
        .responseStrategy(SupervisorResponseStrategy.SUMMARY)
        .supervisorContext("你是一个技术顾问主管。根据用户的问题,合理调度专家团队。...")
        .build();

两个关键策略:CHAT_MEMORY_AND_SUMMARIZATION 让 Supervisor 保持对话记忆并在上下文过长时自动摘要;SUMMARY 让 Supervisor 在所有子 Agent 执行完后汇总结果而非直接转发。Supervisor 模式最接近真实的"AI 团队"——你给它一群专家,它自己决定怎么用。

AgenticServices 的设计哲学

回顾这五种模式,可以发现 LangChain4j 的编排设计有一个清晰的思路:把"Agent 的定义"和"Agent 的编排"分离。Agent 的定义是声明式接口——@Agent + @SystemMessage + @UserMessage,每个 Agent 只关心自己的职责。Agent 的编排是命令式 Builder——sequenceBuilder()loopBuilder()parallelBuilder()supervisorBuilder(),编排逻辑在配置类中集中管理。

这种分离的好处是:同一个 Agent 可以被多种编排模式复用。比如 ResearchAgent 既在顺序工作流中做第一步(研究 → 摘要 → 翻译),也可以在 Supervisor 模式中作为一个子 Agent 被动态调度。Agent 接口不需要知道自己在哪种编排模式中,它只负责"收到输入 → 返回输出",编排逻辑由 Builder 层处理。

另一个值得注意的细节是 AgenticScope 的角色。它是一个运行时上下文,在 Agent 间传递数据——每个 Agent 通过 outputKey 把结果写入 scope,后续 Agent 通过 scope.readState() 读取。这比把所有数据塞进 ChatMemory 更清晰——ChatMemory 是对话历史,AgenticScope 是工作流数据。两者职责分离,互不干扰。

对比 Python 生态,LangChain4j 的 AgenticServices 大致对应 LangGraph 的 StateGraph——都是基于状态传递的图编排。但 LangChain4j 的声明式接口(@Agent 注解的 Java 接口)比 LangGraph 的函数定义更类型安全,IDE 支持也更好。代价是灵活性低一些——LangGraph 可以自由定义图的边和条件分支,LangChain4j 目前只提供了五种预设模式。

三、Guardrails:输入输出护栏

AI 应用的安全问题不需要过多解释——用户可能输入不当内容,LLM 也可能输出低质量回答。LangChain4j 提供了 InputGuardrailOutputGuardrail 两个接口,通过注解绑定到 AI Service 上。

输入护栏:敏感词拦截

public class ProfanityInputGuardrail implements InputGuardrail {
    private static final String[] BLOCKED_WORDS = {"fuck", "shit", "damn", "ass"};

    @Override
    public InputGuardrailResult validate(UserMessage userMessage) {
        String text = userMessage.singleText().toLowerCase();
        for (String word : BLOCKED_WORDS) {
            if (text.contains(word)) {
                log.warn("检测到敏感词汇: {}", word);
                return failure("消息包含不当内容,请重新表述您的问题");
            }
        }
        return success();
    }
}

实现很直接——关键词匹配,命中就 failure()。Controller 层捕获 InputGuardrailException 返回拦截提示。

输出护栏:质量检查 + reprompt

更有意思的是输出侧——如果 AI 回复太短(<50 字),不是直接拒绝,而是触发 reprompt() 让 LLM 重新回答:

public class QualityOutputGuardrail implements OutputGuardrail {
    private static final int MIN_LENGTH = 50;

    @Override
    public OutputGuardrailResult validate(AiMessage responseFromLLM) {
        String text = responseFromLLM.text();
        if (text == null || text.length() < MIN_LENGTH) {
            log.warn("AI 回复过短 ({} 字符),触发 reprompt",
                     text == null ? 0 : text.length());
            return reprompt(
                "回复过于简短,请提供更详细、更有价值的回答",
                "请用至少 3-4 句话详细回答用户的问题,确保提供有价值的信息。"
            );
        }
        return success();
    }
}

reprompt() 是 LangChain4j 的一个精巧设计——它不是简单地把 LLM 的短回答丢弃,而是把"回复太短"这个反馈和"请详细回答"的指令一起发给 LLM,让它基于自己的上一轮回答重新生成。这比简单的拒绝-重试更智能,因为 LLM 知道自己上次答得不好,会调整输出。

注解绑定

Guardrail 不是在 Config 里绑定的,而是直接注解在 AI Service 接口上:

public interface GuardrailsAssistant {
    @SystemMessage("你是一个专业的 Java 技术专家,回答要详细、准确、有深度。")
    @InputGuardrails(ProfanityInputGuardrail.class)
    TokenStream chat(String message);
}

这种声明式风格和 Agent 接口一致——安全策略跟着接口定义走,而不是散落在配置类里。

四、高级 RAG:五阶段流水线

之前 whatsmars-ai 的 RAG 是简单版:embed → search → inject → chat。这次升级为完整的五阶段管线:

用户查询 → 查询压缩 → 智能路由 → 检索 → LLM 重排 → 注入 → LLM → SSE 响应 + sources

阶段一:查询压缩

用户提问往往很口语化,包含寒暄和冗余。CompressingQueryTransformer 用 LLM 把冗长提问压缩为检索关键词:

String compressed = chatModel.chat("""
        将以下用户查询压缩为简洁的检索关键词,用于在知识库中搜索。
        要求:只保留核心概念和技术术语,去除寒暄、语气词和冗余表达。
        直接输出关键词,不要任何解释,不超过15个字。
        用户查询: %s
        """.formatted(original));

比如 “你好,我想问一下 Spring Boot 有哪些核心特性?” 会被压缩为 “Spring Boot 核心特性”。短查询(<=20 字)直接跳过压缩。这个设计的价值在于:embedding 模型对简洁的技术术语比对长句更敏感,压缩后检索质量更高。

阶段二:智能路由

不是所有问题都需要检索知识库——"你好"和"讲个笑话"不需要 RAG。SmartQueryRouter 用 LLM 做分类:

private boolean classify(String query) {
    String response = chatModel.chat("""
            判断以下用户查询是否需要从知识库检索资料来回答。
            如果查询涉及技术知识、概念解释、原理说明、最佳实践、框架用法等,回复 KNOWLEDGE
            如果是问候、闲聊、与知识库无关的通用问题,回复 CHAT
            只回复 KNOWLEDGE 或 CHAT,不要其他内容。
            用户查询: %s
            """.formatted(query));
    return response != null && response.trim().toUpperCase().contains("KNOWLEDGE");
}

判定为 CHAT 时路由到空检索器(跳过检索),判定为 KNOWLEDGE 时路由到实际的内容检索器。这减少了不必要的 embedding 计算和向量搜索开销。

阶段三:自定义检索器

CustomContentRetriever 在标准检索基础上,把相似度分数写入 metadata,供后续阶段使用:

EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder()
        .queryEmbedding(queryEmbedding)
        .maxResults(maxResults)
        .minScore(minScore)
        .build();
// ... 把 match.score() 写入 Document metadata
meta.put("score", String.valueOf(match.score()));

minScore 设为 0.5——相似度低于 0.5 的文档直接过滤,避免低质量检索结果干扰 LLM。

阶段四:LLM 重排

这是最复杂的阶段。ReRankingContentAggregator 收集所有检索结果,去重后让 LLM 打分(0-10),取 Top-3:

sb.append("请对以下候选资料与原始问题的相关性进行评分(0-10分)。\n");
sb.append("原始问题: ").append(queryToContents.keySet().iterator().next().text()).append("\n\n");
sb.append("候选资料:\n");
for (int i = 0; i < allCandidates.size(); i++) {
    String snippet = allCandidates.get(i).textSegment().text();
    if (snippet.length() > 150) snippet = snippet.substring(0, 150) + "...";
    sb.append(i + 1).append(". ").append(snippet).append("\n");
}

LLM 打分后,解析分数(正则 (\d+)\.\s*(\d+)),按分数降序排列取前 3 条。同时把 source 信息(文本、文件名、原始相似度分数、LLM 重排分数)存入 RetrievalContext(ThreadLocal),供 Controller 在 SSE 响应中推送 sources 事件。

这个设计的核心思想是:向量相似度 ≠ 语义相关性。两段文本可能在向量空间接近,但实际内容不直接回答用户问题。LLM 重排基于对问题的真正理解来排序,比纯向量相似度更准。

阶段五:注入与来源引用

var contentInjector = DefaultContentInjector.builder()
        .promptTemplate(new PromptTemplate("""
                {{userMessage}}
                基于以下参考资料回答用户问题。
                回答时请引用资料来源编号(如 [1]、[2])。
                参考资料: {{contents}}
                """))
        .metadataKeysToInclude(List.of("file_name", "score"))
        .build();

注入模板让 LLM 在回答时标注来源编号,同时把文件名和相似度分数也注入到上下文中。Controller 在 SSE 流完成后推送一个 sources 事件,包含每条引用的 SourceInfo(text, fileName, score, reRankScore)——前端可以展示"这段回答引用了哪些文档,各自相似度多少"。

管线组装

整个管线通过 DefaultRetrievalAugmentor.builder() 手动组装:

var retrievalAugmentor = DefaultRetrievalAugmentor.builder()
        .queryTransformer(queryTransformer)
        .queryRouter(queryRouter)
        .contentAggregator(contentAggregator)
        .contentInjector(contentInjector)
        .build();

注意:retrievalAugmentor 不暴露为 Spring Bean,而是直接传给 AiServices.builder()。这是刻意的设计——如果暴露为 Bean,Spring 会把它自动注入到所有 @AiService 标注的接口中,导致不需要 RAG 的 AI Service 也被污染。后面会详细讲这个架构决策。

为什么高级 RAG 需要这么多阶段?

简单 RAG(embed → search → inject)在原型阶段够用,但生产环境会遇到一系列问题:用户提问太口语化导致 embedding 质量差(需要查询压缩);闲聊问题也触发检索浪费算力(需要智能路由);向量相似度高但语义不相关(需要 LLM 重排);用户不知道答案从哪来(需要来源引用和分数展示)。每个阶段解决一个具体的"RAG 质量天花板"问题。

当然,不是所有 RAG 应用都需要全套管线。对于简单的文档问答,Spring AI 的 QuestionAnswerAdvisor 一行搞定,开发效率高得多。whatsmars 展示的是"把 RAG 做到极致"的可能性,不是"所有 RAG 都应该这么做"的模板。实际项目中,通常从简单 RAG 开始,遇到具体问题再加对应阶段——检索质量差就加查询压缩,准确率低就加重排,响应慢就加智能路由跳过无关检索。

五、结构化输出:AI 方法返回 Java Record

LLM 返回的是自然语言文本,但实际应用中我们经常需要结构化数据。LangChain4j 的 AI Service 支持方法返回 Java record——框架自动指导 LLM 输出 JSON 并反序列化:

public interface StructuredOutputAssistant {
    @SystemMessage("""
            你是一个信息提取助手,请从用户描述中提取个人信息。
            你必须严格以 JSON 格式回复,包含字段: name(string), age(integer),
            email(string), occupation(string), hobbies(string数组)。
            只返回 JSON,不要包含任何其他文字。
            """)
    @UserMessage("请从以下描述中提取用户信息:{{message}}")
    UserInfo extractUserInfo(@V("message") String message);

    record UserInfo(String name, Integer age, String email,
                    String occupation, List<String> hobbies) {}
}

四个示例覆盖了典型场景:信息提取(UserInfo)、情感分析(SentimentResult 带 enum)、任务列表(TodoList 嵌套 List<TodoItem>)、SQL 生成(SqlResultList<String>)。调用方拿到的是类型安全的 Java 对象,不需要自己做 JSON 解析。

六、多模态:视觉、文生图、语音

这三个能力和 spacecloud 的实现思路一致,但用 LangChain4j 的 API 实现。

视觉理解使用 LangChain4j 的 ImageContent + TextContent 组合:

UserMessage userMessage = UserMessage.from(
        TextContent.from(prompt),
        ImageContent.from(imageUrl)
);
ChatResponse response = multimodalChatModel.chat(userMessage);

文生图和语音合成同样需要自定义适配 DashScope 原生 API——DashScopeImageModel 实现异步提交 + 轮询,DashScopeTtsModel 调用 SpeechSynthesizer 端点返回 MP3。这和 spacecloud 的实现完全一致,说明 DashScope 的 API 差异不因框架不同而改变——无论 Spring AI 还是 LangChain4j,都需要适配。

七、ConditionalOnMcpStatusUp:MCP 依赖的优雅处理

whatsmars-ai 的 MCP Client 依赖 whatsmars-mcp(端口 8886)先启动。如果 MCP Server 没启动,Client 会连接失败导致应用启动报错。解决方案是自定义 Spring 条件注解:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnMcpStatusUpCondition.class)
public @interface ConditionalOnMcpStatusUp {
    String host() default "localhost";
    int port() default 8886;
    int timeout() default 2000;
}

OnMcpStatusUpCondition 通过 TCP socket 探测端口:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    try (Socket socket = new Socket()) {
        socket.connect(new InetSocketAddress(host, port), timeout);
        log.info("MCP Server 端口检测通过: {}:{},加载 MCP 相关 Bean", host, port);
        return true;
    } catch (Exception e) {
        log.warn("MCP Server 端口检测失败: {}:{},跳过 MCP 相关 Bean", host, port);
        return false;
    }
}

MCP Server 在线时加载 MCP Client 相关 Bean,不在线时跳过——应用依然能启动,只是 MCP 功能不可用。这比 @DependsOn 或启动顺序控制更优雅,因为 MCP Server 可能是独立部署的,不在同一个应用上下文中。

八、架构决策:手动构建 AiService

这次更新的一个重要架构变化:全部 AI Service 从 @AiService 注解改为 AiServices.builder() 手动构建。

// 之前:注解驱动,Spring 自动注入
// @AiService
// public interface RetrievalAssistant { ... }

// 现在:手动构建
@Bean
public RetrievalAssistant retrievalAssistant(StreamingChatModel streamingChatModel, ...) {
    return AiServices.builder(RetrievalAssistant.class)
            .streamingChatModel(streamingChatModel)
            .retrievalAugmentor(retrievalAugmentor)
            .build();
}

原因在前文提到过:如果用 @AiService 注解,且 Spring 上下文中存在 RetrievalAugmentor Bean,LangChain4j 会把它自动注入到所有 @AiService 接口中。这意味着 SimpleAssistant、StreamingAssistant 这些不需要 RAG 的 AI Service 也会被挂上检索管线——每次对话都先去知识库检索一番,完全不合理。

手动构建的好处是精确控制:只有 RetrievalAssistant 拿到 retrievalAugmentor,其他 AI Service 不受影响。代价是配置类变长了,但这种显式控制对于功能复杂的项目是值得的。

九、可观测性:ChatModelListener

LoggingChatModelListener 实现了 ChatModelListener 接口,监听所有 LLM 调用:

@Override
public void onResponse(ChatModelResponseContext responseContext) {
    ChatResponse chatResponse = responseContext.chatResponse();
    TokenUsage usage = chatResponse.tokenUsage();
    String tokenInfo = String.format("prompt=%d, completion=%d, total=%d",
            usage.inputTokenCount(),
            usage.outputTokenCount(),
            usage.totalTokenCount());
    long elapsed = System.currentTimeMillis()
            - (long) responseContext.attributes().getOrDefault(ATTR_START_TIME, System.currentTimeMillis());
    log.info("[LLM Response] provider={}, tokens=[{}], elapsed={}ms", ...);
}

三个回调点:onRequest(记录开始时间 + 消息分布)、onResponse(Token 用量 + 耗时)、onError(异常 + 耗时)。这在多 Agent 编排场景下特别有价值——一次用户请求可能触发 5-10 次 LLM 调用(各子 Agent 各调一次 + 重排打分 + 查询压缩 + 查询路由分类),有了 Listener 就能看清每次调用的 Token 消耗和耗时,快速定位性能瓶颈。

十、写在最后

如果把这波更新和 spacecloud、babi 放在一起看,三个项目恰好展示了 Java AI 生态的三个维度:

spacecloud 展示了 Spring AI 2.0 的全栈能力——从对话到生成、从 RAG 到 MCP,强调的是覆盖面和微服务集成。whatsmars 展示了 LangChain4j 的高级能力——多 Agent 编排、高级 RAG 管线、Guardrails,强调的是深度和编排能力。babi 则是三框架选型对比——用 AgentScope、LangGraph4j、Spring AI 三种框架实现同一个 Coding Agent,强调的是框架差异和选型。

LangChain4j 在 Agent 编排上的优势很明显:AgenticServices 提供的五种编排模式(顺序、循环、并行、Supervisor)覆盖了大部分多 Agent 场景,声明式接口让 Agent 定义极简,AgenticScope 提供了 Agent 间数据传递的统一机制。相比之下,Spring AI 2.0 目前在 Agent 编排方面还没有对标方案——它的优势在 MCP 协议和微服务集成。

高级 RAG 管线是另一个亮点。LangChain4j 的 RetrievalAugmentor 设计把查询压缩、智能路由、重排、注入拆成可插拔的组件,组装方式自由度很高。Spring AI 的 QuestionAnswerAdvisor 更简洁但灵活性低一些——它是一个 Advisor,内部封装了检索和注入,不好拆开定制。

Guardrails 和结构化输出则是 LangChain4j 独有的能力——Spring AI 目前没有内置的输入输出护栏机制,结构化输出也还在发展中。这些是 LangChain4j 作为 LangChain Python 生态 Java 移植的"先发优势"。

相关博文:

git clone https://github.com/javahongxi/whatsmars
Logo

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

更多推荐