微信扫码
添加专属顾问
我要投稿
从零开始构建AI Agent?这篇文章用代码说话,带你深入Spring AI的实践世界。 核心内容: 1. 基于Spring AI的AI Agent开发全流程解析 2. RAG检索增强生成与Function Calling工具调用的实现细节 3. 多模块架构设计:从AgentCore到Command & Skill系统
前言
Linux说过一句很经典的话:Talk is cheap, show me the code.
最近在学习AI Agent开发的时候,填鸭式地被灌输了很多新知识,但是这些新知识就像是漂浮的“空中楼阁”,看得见但摸不着,只知道理论如此但是不知道具体实现为何物。计算机工程的事儿,往往真的听再多毫无体感,看一遍代码就基本一通百通,由此产生一个很神奇的想法:“最好的学习资料是代码,既然我要学AI Agent开发,那就让AI Agent本身帮我生成学习资料。”于是乎,便有了这篇文章,即我本文的项目代码几乎是由AI生成,我在其中的角色只是指挥家与验收员。
开始之前先作一些声明:
1、该项目本身纯作为学习用途的Demo,只是用作展示“理论背后看得见的代码”。
2、Agent的理解较为宽泛,从整体概念层面是包含LLM的,一般Agent开发往往指的是Harness开发,但本文不做具体区分。
3、不深入每个概念的设计哲学,如Skill的渐进式披露,主要关注于实现层面。
4、Function Calling:LLM本身不会调工具,工具调用都是Harness做的;实际上Function Calling是大地基,很多复杂能力都是作为tool的形式包装给LLM的,例如Skill与SubAgent调用。
快速开始
本项目是一个基于Spring AI的AI Agent应用(纯Demo,仅学习用途),集成了 RAG 检索增强生成、Function Calling 工具调用、MCP 协议、SubAgent 子代理、Skill 技能系统等核心能力。本文将从六个核心模块出发,深入剖析其架构设计和实现细节。
代码仓库
github地址:https://github.com/q644266189/aiagentdemo
git clone git@github.com:q644266189/aiagentdemo.git
环境要求
Java 21+
Maven 3.9+
核心模块
模块 | 说明 |
AgentCore | 核心编排器,具备意图识别、记忆管理与大模型调用等能力。 |
ChatMemory | 对话记忆管理,支持三层上下文压缩(摘要压缩 → Assistant 裁剪 → 滑动窗口)。 |
Tool(Function Calling) | 可插拔的工具注册机制,通过 |
RAG | 完整的检索增强生成流水线:文档加载 → 文档分块 → 向量化 → 向量存储 → 多路召回(语义 + BM25 + 查询改写)→ RRF 融合 → Rerank 重排 → LLM → 内容生成 |
Command & Skill | 两种 Markdown 驱动的 Prompt 模板机制:Command 由用户主动调用,Skill 本质作为Tool由 LLM 决策调用。 |
SubAgent | 拥有独立记忆的子代理,支持内部 SubAgent 和外部 IdeaLab Agent 两种形态 |
MCP | 双向 MCP 支持:作为 Client 动态连接外部 MCP 服务,作为 Server 对外暴露服务 |
配置
编辑 src/main/resources/application.properties,配置大模型 API
spring.ai.openai.base-url=https://open.bigmodel.cn/api/paas/v4spring.ai.openai.api-key=你的API密钥spring.ai.openai.chat.options.model=glm-4spring.ai.openai.embedding.options.model=embedding-3
访问
启动成功后,打开浏览器访问:
http://localhost:8080
项目内置了一个完整的 Web 聊天界面(src/main/resources/static/index.html),支持:
流式对话:实时逐字输出 AI 回复(SSE)
Markdown 渲染:自动渲染代码块、表格、列表等
命令面板:输入 / 唤起快捷命令列表
会话管理:支持清空对话历史
# 非流式对话curl -X POST http://localhost:8080/api/chat \ -H "Content-Type: application/json" \ -d '{"message": "你好,介绍一下你的能力", "sessionId": "test-001"}'# 流式对话(SSE)curl -X POST http://localhost:8080/api/chat/stream \ -H "Content-Type: application/json" \一、核心编排器:AgentCore
AgentCore 是整个系统的"大脑",负责编排对话的完整流程:意图识别 → RAG 注入 → 记忆管理 → 模型调用 → 工具执行。
1.1 对话流程
用户输入 │ ▼意图识别(IntentRecognizer) │ 判断:这是知识问答还是通用对话? ▼RAG 注入(RagService) │ 如果是知识问答,检索知识库,将参考资料拼入上下文 ▼记忆管理(ChatMemory) │ 自动摘要压缩 → 构建消息列表 ▼模型调用(ChatClient + ToolCallbacks) │ LLM 决策:直接回答 or 调用工具? │ 如果调用工具 → 执行工具 → 将结果返回 LLM → 继续决策(ReAct 循环) ▼返回最终回复
核心代码(AgentCore.chat()):
public String chat(String sessionId, String userInput) { ChatMemory memory = getOrCreateMemory(sessionId); // 1. 意图识别 Intent intent = intentRecognizer.recognize(userInput); // 2. 如果是 RAG 意图,先检索知识库并注入上下文 if (intent == Intent.RAG && ragService.isKnowledgeLoaded()) { String ragContext = ragService.query(userInput); if (ragContext != null && !ragContext.isBlank()) { String enrichedInput = "以下是从知识库中检索到的相关参考资料," + "请结合这些资料回答用户的问题:\n\n" + ragContext + "\n\n用户问题:" + userInput; memory.addMessage(new UserMessage(enrichedInput)); } else { memory.addMessage(new UserMessage(userInput)); } } else { memory.addMessage(new UserMessage(userInput)); } // 3. 构建 Prompt 并Loop调用大模型(getMessages 内部自动触发摘要压缩) List<Message> messages = memory.getMessages(); Prompt prompt = new Prompt(messages, buildChatOptions()); ChatClient.ChatClientRequestSpec requestSpec = chatClient.prompt(prompt); if (!toolCallbacks.isEmpty()) { requestSpec.toolCallbacks(toolCallbacks.toArray(new ToolCallback[0])); } String response = requestSpec.call().content(); memory.addMessage(new AssistantMessage(response != null ? response : "")); return response != null ? response : "";}1.2 Agent Loop
Spring AI已实现Agent Loop。
具体路径为
org.springframework.ai.chat.client.advisor.ToolCallAdvisor#adviseCall
Agent Loop代码片段
boolean isToolCall = false;do {// Before Callvar processedChatClientRequest = ChatClientRequest.builder().prompt(new Prompt(instructions, optionsCopy)).context(chatClientRequest.context()).build();// Next CallprocessedChatClientRequest = this.doBeforeCall(processedChatClientRequest, callAdvisorChain);chatClientResponse = callAdvisorChain.copy(this).nextCall(processedChatClientRequest);chatClientResponse = this.doAfterCall(chatClientResponse, callAdvisorChain);// After Call// TODO: check that this tool call detection is sufficient for all chat models// that support tool calls. (e.g. Anthropic and Bedrock are checking for// finish status as well)ChatResponse chatResponse = chatClientResponse.chatResponse();isToolCall = chatResponse != null && chatResponse.hasToolCalls();if (isToolCall) {Assert.notNull(chatResponse, "redundant check that should never fail, but here to help NullAway");ToolExecutionResult toolExecutionResult = this.toolCallingManager.executeToolCalls(processedChatClientRequest.prompt(), chatResponse);if (toolExecutionResult.returnDirect()) {// Return tool execution result directly to the application client.chatClientResponse = chatClientResponse.mutate().chatResponse(ChatResponse.builder().from(chatResponse).generations(ToolExecutionResult.buildGenerations(toolExecutionResult)).build()).build();// Interrupt the tool calling loop and return the tool execution// result directly to the client application instead of returning// it to the LLM.break;}instructions = this.doGetNextInstructionsForToolCall(processedChatClientRequest, chatClientResponse,toolExecutionResult);}}while (isToolCall); // loop until no tool calls are present
1.3 意图识别
IntentRecognizer 通过 LLM 判断用户输入的意图,目前支持两种:
RAG:用户在问知识库相关的问题,需要先检索知识库再回答
GENERAL:通用对话,直接交给 LLM 处理
意图识别前置的好处是:避免每次对话都触发 RAG 检索,节省不必要的向量检索和 Rerank 开销。
1.4 对话记忆:ChatMemory
每个 sessionId 对应一个独立的 ChatMemory 实例,天然支持多客户端并发。
ChatMemory 设计了三层递进的上下文压缩策略,防止对话过长导致 token 溢出或成本失控:
当历史消息超过 16 条时,自动将较早的消息通过 LLM 总结为一段 300 字以内的摘要,注入到 system prompt 中。原消息从 history 中移除。
核心代码(ChatMemory.getMessages() 和 compressIfNeeded()):
public List<Message> getMessages() { // 在构建消息列表之前,自动尝试摘要压缩 compressIfNeeded(); List<Message> messages = new ArrayList<>(); // 将原始 system prompt 与摘要合并为一条 SystemMessage if (systemMessage != null || (summaryText != null && !summaryText.isBlank())) { String systemContent = systemMessage != null ? systemMessage.getText() : ""; if (summaryText != null && !summaryText.isBlank()) { systemContent += "\n\n【以下是之前对话的摘要,请参考】\n" + summaryText; } messages.add(new SystemMessage(systemContent)); } // ... 添加历史消息(跳过早期 Assistant 消息) return Collections.unmodifiableList(messages);}private void compressIfNeeded() { if (chatClient == null || history.size() <= COMPRESS_THRESHOLD_MESSAGES) { return; } int compressEndIndex = history.size() - PRESERVE_RECENT_MESSAGES; // 确保不会在 TOOL 消息的前面截断 while (compressEndIndex < history.size() && history.get(compressEndIndex).getMessageType() == MessageType.TOOL) { compressEndIndex--; } if (compressEndIndex <= 0) return; List<Message> messagesToCompress = new ArrayList<>(history.subList(0, compressEndIndex)); String newSummary = SummaryCompressor.compress(chatClient, messagesToCompress, summaryText); if (newSummary != null && !newSummary.isBlank()) { this.summaryText = newSummary; history.subList(0, compressEndIndex).clear(); }}这一层的核心设计:
内聚透明:压缩逻辑完全封装在 getMessages() 内部,调用方无感知。压缩器 SummaryCompressor 作为 ChatMemory 的私有静态内部类,不对外暴露;
增量压缩:如果已有历史摘要,新的压缩会将旧摘要与新对话合并总结,避免信息随多次压缩逐渐丢失;
TOOL 消息边界保护:截断时自动避开 TOOL 消息,确保 TOOL 消息始终紧跟在对应的 ASSISTANT 消息后面,不会破坏工具调用上下文。
只保留最近 3 条 Assistant 回复。因为 LLM 的回复通常很长,是 token 消耗的大户,裁剪早期的 Assistant 消息能显著减少上下文体积。
当消息总数超过 maxRounds × 4 时,直接丢弃最早的消息。这是最后一道防线,确保上下文不会无限增长。
三层策略协同工作:摘要压缩优先触发(保留信息),Assistant 裁剪持续生效(精准省 token),滑动窗口兜底(硬性保护)。
1.5 多会话隔离与运行时配置
多会话:ConcurrentHashMap<String, ChatMemory> 按 sessionId 隔离,支持并发
运行时切换模型:通过 API 动态切换模型提供商(如从智谱切到通义千问),无需重启
运行时调参:支持动态调整 temperature、maxTokens、topP 等推理参数
二、Tool 机制(Function Calling)
LLM 只能"想",Tool 让它能"做"。LLM本身是不会去调用各种服务,Agent服务端只是告诉大模型“有哪些工具可以调用”,LLM返回给Agent服务端的是“要去调哪些工具”,真实调用实在Agent服务端。
本项目基于 Spring AI 的 Function Calling 能力,设计了一套可插拔的工具注册机制。
2.1 工具注册机制
所有工具实现统一的 InnerTool 接口:
public interface InnerTool { List<ToolCallback> loadToolCallbacks();}启动时,Spring 自动扫描所有 InnerTool Bean,调用 loadToolCallbacks() 收集所有工具,统一注册到 AgentCore。新增工具只需实现这个接口,无需修改任何已有代码。
ToolCallbackBuilder 提供了简洁的工具构建 API,将工具名、描述、参数定义(JSON Schema)和执行函数组装为 Spring AI 标准的 ToolCallback。
2.2 工具调用流程
用户:"杭州今天天气怎么样?" │ ▼LLM 分析意图,决定调用 get_weather 工具 │ ▼Spring AI 自动执行工具:get_weather({"city": "杭州"}) │ ▼工具返回结果:"杭州,晴,22°C" │ ▼LLM 基于工具结果生成最终回复:"杭州今天天气晴朗,气温 22°C,适合出行。"Spring AI 的 ChatClient 内置了 ReAct 循环:LLM 可以连续调用多个工具,直到认为信息充足后给出最终回复。整个过程对开发者透明。
2.3 内置工具一览
工具名 | 功能 | 说明 |
| 知识库检索 | 将 RAG 检索能力封装为工具,LLM 可主动检索 |
| 创建子代理 | 创建拥有独立记忆的 SubAgent |
| 与子代理对话 | 在 SubAgent 的独立上下文中继续对话 |
| 销毁子代理 | 释放 SubAgent 资源 |
| 调用 IDEAs 应用 | 调用外部 IdeaLab 平台的 AI 应用(支持多个) |
| 执行技能 | 由 Markdown 文件定义的技能,动态注册 |
| MCP 工具 | 从外部 MCP Server 发现并注册的工具 |
| 天气查询 | 示例工具 |
| 股票价格查询 | 示例工具 |
三、RAG 模块:检索增强生成
RAG(Retrieval-Augmented Generation)让 Agent 能够基于私有知识库回答问题。
3.1 RAG完整流水线
3.2 文档分块策略
分块质量直接决定检索质量。项目提供了多种分块策略,分为确定规则分块和智能分块两类:
策略 | 原理 | 适用场景 |
TextSplitter(默认) | 递归语义分块,按标题 → 段落 → 句子 → 固定字符的优先级依次尝试切分 | 通用文档,兼顾语义完整性 |
FixedSizeSplitter | 按固定字符数切分 | 结构不明确的纯文本 |
ParagraphSplitter | 按段落(连续换行)切分 | 段落结构清晰的文档 |
SentenceSplitter | 按句子(句末标点)切分 | 需要细粒度检索的场景 |
SlidingWindowSplitter | 滑动窗口切分,相邻块有重叠 | 需要保留上下文连续性 |
策略 | 原理 | 适用场景 |
SemanticChunkSplitter | 基于语义相似度判断切分点 | 语义边界不明确的长文本 |
PropositionSplitter | 将文本拆解为独立命题 | 需要精确事实检索 |
AgenticSplitter | 使用 LLM 判断最佳切分方式 | 复杂混合格式文档 |
默认使用 TextSplitter(递归语义分块),分块大小 500 字符,重叠 50 字符。
3.3 检索流程核心代码
RagService.query() 封装了完整的检索流程:
public String query(String question) { // 1. 多路召回(语义 + BM25 + 查询改写,共 9 个候选) List<Document> candidates = multiRecaller.recall(question, RECALL_CANDIDATE_COUNT); // 2. Rerank 重排(取最相关的 3 个) List<Document> relevantDocuments = llmReranker.rerank(question, candidates, TOP_K); // 3. 拼接上下文 StringBuilder contextBuilder = new StringBuilder(); for (int i = 0; i < relevantDocuments.size(); i++) { contextBuilder.append("【参考资料 ").append(i + 1).append("】\n"); contextBuilder.append(relevantDocuments.get(i).getContent()).append("\n\n"); } return contextBuilder.toString().trim();}3.4 召回策略
单一召回策略总有盲区,项目使用多路召回 + RRF 融合的方案:
召回器 | 原理 | 擅长 |
SemanticRetriever | 基于 EmbeddingModel 的向量余弦相似度检索 | 语义相近但措辞不同的查询 |
Bm25Retriever | 基于 BM25 算法的关键词匹配(TF-IDF 变体) | 精确关键词匹配 |
QueryRewriteRetriever | 先用 LLM 将问题改写为 3 种不同表达,再分别做向量召回 | 扩大语义覆盖面 |
三路召回结果通过 RRF(Reciprocal Rank Fusion) 算法融合:
// MultiRecaller 核心逻辑public List<Document> retrieve(String query, int topK) { Map<String, Double> rrfScores = new HashMap<>(); Map<String, Document> keyToDocument = new LinkedHashMap<>(); for (Recaller retriever : retrievers) { List<Document> results = retriever.retrieve(query, PER_ROUTE_CANDIDATE_COUNT); // RRF 公式:score(d) = Σ 1 / (k + rank),k=60 为平滑常数 accumulateRrfScores(results, rrfScores, keyToDocument); } return rrfScores.entrySet().stream() .sorted(Map.Entry.<String, Double>comparingByValue().reversed()) .limit(topK) .map(entry -> keyToDocument.get(entry.getKey())) .toList();}RRF 只看排名不看绝对分数,天然适合融合不同算法的结果。
3.5 Rerank 重排
多路召回后通常有 9 个候选文档,通过专用的 Rerank 模型精排,取最相关的 3 个。
3.6 向量存储
VectorStore 是一个轻量级的内存向量存储实现,使用 Spring AI 的 EmbeddingModel 生成向量,通过余弦相似度检索。适合中小规模知识库,生产环境可替换为 Milvus、Pinecone 等专业向量数据库。
四、Command 与 Skill:两种 Prompt 模板机制
Command 和 Skill 都是基于 Markdown 文件定义的 Prompt 模板,但它们的设计理念和使用方式截然不同。
4.1 Skill:LLM 自主调用的工具
Skill 文件使用 YAML Front Matter + Prompt 模板 格式:
---name: summarizedescription: 对用户提供的文本内容进行摘要总结---请对以下文本进行摘要总结,提取核心要点:{{input}}SkillManager 在启动时扫描 classpath:skill/*.md,解析元数据后由 SkillTool 将每个技能转换为 ToolCallback 注册到 Agent。LLM 在对话中根据 description自主判断是否需要调用某个技能。
4.2 Command:用户主动调用的快捷指令
Command 文件是纯 Prompt 模板,文件名即为命令名:
请对以下代码进行 Code Review,从代码质量、潜在 Bug、性能、可读性等维度给出改进建议:{{input}}CommandManager 在启动时扫描 classpath:command/*.md,加载到内存。用户通过 REST API(POST /api/command/execute)主动指定命令名来执行。
4.3 核心区别对比
维度 | Command | Skill |
设计理念 | 用户快捷指令 | LLM 可调用的工具 |
文件格式 | 纯 Prompt 模板 | Front Matter(name + description)+ Prompt |
是否注册为工具 | ❌ 不注册 | ✅ 注册为 ToolCallback |
调用触发方 | 用户主动指定命令名 | LLM 根据 description 自主决策 |
执行路径 | 用户 → Controller → AgentCore | 用户 → AgentCore → LLM 决策 → SkillTool |
适用场景 | 用户明确知道需要什么功能 | 需要 LLM 理解上下文后智能判断 |
一句话总结:
Command 是"用户告诉 Agent 做什么",Skill 是"Agent 自己判断该做什么"。两者互补——Command 提供确定性的快捷入口,Skill 提供智能化的能力扩展。
五、SubAgent:独立记忆的子代理
5.1 为什么需要 SubAgent
有些任务需要独立的上下文。比如用户说"帮我写一篇技术文章",这个任务可能需要多轮对话来完善,但不应该污染主对话的记忆。SubAgent 就是为此设计的。
5.2 记忆隔离机制
SubAgent 的核心是记忆隔离:每个 SubAgent 拥有独立的 ChatMemory 实例,与主 Agent 的记忆完全隔离。
public SubAgent(String id, String name, String systemPrompt, ChatClient chatClient) { this.memory = ChatMemory.forSubAgent(); // 独立记忆! this.memory.setSystemPrompt(systemPrompt); // ...}SubAgent 共享主 Agent 的 ChatClient(即共享同一个大模型连接),但对话历史完全独立。这意味着:
SubAgent 内部的多轮对话不会影响主对话的上下文
主 Agent 可以同时管理多个 SubAgent,各自互不干扰
SubAgent 销毁后,其记忆随之释放
5.3 交给LLM:Tool
SubAgent 的能力通过 3 个工具暴露给主 Agent,本质上就是 Function Calling,由LLM决策启用SubAgent:
工具 | 参数 | 说明 |
| name、system_prompt、task | 创建 SubAgent 并执行首个任务 |
| agent_id、message | 与已有 SubAgent 继续对话 |
| agent_id | 销毁 SubAgent,释放资源 |
主 LLM 根据对话上下文自主决定是否需要创建 SubAgent。整个生命周期(创建 → 多轮对话 → 销毁)都由主 LLM 通过工具调用来驱动。
六、MCP:连接一切外部服务
MCP(Model Context Protocol) 是 Anthropic 提出的开放协议,让 AI 应用能够标准化地连接外部工具和数据源。本项目同时实现了 MCP Server(对外暴露能力)和 MCP Client(连接外部服务)。
6.1 MCP Server:对外暴露知识库检索能力
项目通过 SimpleMcpServer 对外提供知识库检索工具,其他 AI 应用可以通过 MCP 协议来调用:
工具:knowledge_query
参数 | 类型 | 说明 |
| String | 检索关键词 |
| String | 知识分类(java_basic / jvm / concurrent / spring / design_pattern / all) |
| int | 返回的最大结果条数,默认 3 |
内部调用 RagService 执行检索,将结果格式化后返回。这意味着本项目的 RAG 能力可以被任何支持 MCP 协议的 AI 应用复用。
6.2 MCP Client:动态连接外部 MCP 服务
McpClient 封装了连接外部 MCP Server 的完整逻辑:
核心代码(McpClient.connect()):
public ToolCallback[] connect(String serverUrl) { McpSyncClient mcpClient; McpSchema.InitializeResult initResult; // 优先尝试 Streamable HTTP,失败后回退到 SSE try { mcpClient = connectWithStreamableHttp(serverUrl); initResult = mcpClient.initialize(); } catch (Exception streamableException) { mcpClient = connectWithSse(serverUrl); initResult = mcpClient.initialize(); } // 自动发现远程工具 SyncMcpToolCallbackProvider provider = SyncMcpToolCallbackProvider.builder() .mcpClients(mcpClient).build(); ToolCallback[] toolCallbacks = provider.getToolCallbacks(); // 持久化 URL,下次启动自动恢复 store.add(serverUrl); return toolCallbacks;}关键特性:
传输协议自动适配:优先 Streamable HTTP(2025-03-26 规范),失败自动回退 SSE(2024-11-05 规范)
工具自动发现:连接成功后自动获取远程工具,转换为 ToolCallback 注册到 Agent
持久化与自动恢复:URL 持久化到 mcp-servers.json,应用重启时自动重连
运行时动态管理:通过 REST API 在运行时动态管理 MCP 连接:
接口 | 方法 | 说明 |
| POST | 连接新的 MCP 服务,工具立即可用 |
| POST | 断开 MCP 服务,移除对应工具 |
| GET | 查看所有 MCP 服务及其工具列表 |
结尾感言
LLM就像一个问答黑箱,不管内部支持多丰富的能力,对使用者本质只有一个能力:“你问,我答”。
使用者做的事情几乎是一致的:调整输入给LLM的内容,尽量让其输出预期内的内容。而对于“调整输入内容”这一块看似轻巧,实际上正是工程化发展的源泉,从Prompt Engineering到Context Engineering到Harness Engineering本质解决的就是“有限的上下文窗口中该放什么内容”。
脑暴枚举目前上下文窗口可能放的内容有:系统提示词、工具定义、历史对话、参考文档等。目前AI Agent正高速发展,最终浪淘沙到尽头什么会是最终答案不由而知,但是其中工具定义可能会走到最后。至少目前而言Function Calling是Harness的大地基,实际上很多能力的实现都是基于Function Calling,比如Skill本质就是一种Tool,而RAG、SubAgent与外部MCP服务等能力在工程实践中也大量被做成一种Tool由LLM决策调用。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-04-22
专题解读 | 可更新的检索增强知识库发展方向及进展
2026-04-22
我搭建了Karpathy的个人知识库,但发现成本高速度慢,我用一个更好的方案替代了。
2026-04-21
多轮对话时,RAG反复做重复召回,模型层与Milvus层分别如何解决?
2026-04-20
Codeindex · 让大模型更好地理解你的代码
2026-04-13
用RAG的思路做agent知识管理,为什么跑不通
2026-04-12
YC CEO 的 AI 记住了 3000 个人
2026-04-10
AI 答疑助手优化实践:从 RAG 到 LightRAG 的全链路升级
2026-04-09
阿里云百炼「记忆库」正式上线,让龙虾真正记住你!
2026-02-13
2026-02-03
2026-02-03
2026-02-06
2026-02-06
2026-02-02
2026-04-06
2026-01-28
2026-02-05
2026-02-06
2026-04-21
2026-03-17
2026-03-11
2026-02-22
2026-02-15
2026-02-04
2026-02-03
2026-01-19