
1. 项目概述从“健忘”到“自主记忆”的进化之路如果你最近在折腾大语言模型的应用尤其是想让它记住你之前聊过什么、做过什么那你肯定对“上下文窗口”这个词又爱又恨。爱的是它给了模型一个“工作记忆区”恨的是这个记忆区太小、太贵而且一刷新就全忘了。这就像让一个天才处理复杂项目却只给他一张巴掌大的便签纸做笔记项目稍微一长前面的细节就全被擦掉了。我们常说的“Towards Autonomous Memory Agents”迈向自主记忆智能体瞄准的就是这个核心痛点。它不是一个具体的工具而是一个架构方向和设计范式目标是为LLM驱动的智能体赋予真正意义上的、可自主管理的长期记忆能力。简单来说它要解决的是智能体的“健忘症”问题。当前的智能体无论是AutoGPT、BabyAGI还是各类AI助手其记忆大多依赖有限的上下文窗口或者简单地将历史对话记录成文本文档。前者有长度和成本限制后者则缺乏结构、难以检索和有效利用。一个“自主记忆智能体”应该能做到1自动决定什么信息值得记住2将信息以结构化的方式存储到外部数据库我们称之为“外部记忆体”3在需要时能快速、精准地从海量记忆中检索出最相关的片段4能对记忆进行动态更新、修正甚至遗忘。这听起来是不是有点像为我们自己构建一个“第二大脑”没错其核心思想就是借鉴人类的记忆系统为AI打造一个外挂的、可扩展的“海马体”。为什么现在这个话题这么热因为随着多轮对话、复杂任务规划、个性化服务等场景的深入记忆成了制约智能体能力上限的关键瓶颈。没有记忆每次交互都是“初次见面”个性化无从谈起长期任务无法分解跟踪效率大打折扣。而像“chimera”这类关于异构LLM服务的热词也侧面反映了另一个趋势未来的智能体系统可能是由多个各司其职的“小模型”或“专业模型”协同工作它们共享一个中央记忆库这对记忆系统的性能、延迟和一致性提出了更高要求。因此深入理解并实践“自主记忆”的构建是开发现实可用AI智能体的必经之路。2. 自主记忆系统的核心架构与设计思路构建一个自主记忆系统远不止是接个数据库那么简单。它是一套精密的工程架构需要从数据流、存储、检索、更新等多个维度进行综合设计。一个典型的自主记忆智能体架构通常包含以下几个核心组件它们协同工作共同完成记忆的“感知-存储-回忆-更新”闭环。2.1 记忆的感知与编码从原始文本到向量嵌入智能体每时每刻都在接收信息流用户的指令、工具调用的结果、自身的推理过程、外部API的返回数据等等。第一步也是至关重要的一步就是决定“记什么”以及“怎么记”。记忆的粒度与类型并非所有信息都值得存入长期记忆。我们需要对信息进行分类和过滤。对话历史最基础的记忆。但全量存储效率低下通常需要做摘要提取。例如一段关于“帮我规划一个Python学习路径”的10轮对话最终可能被总结为“用户目标从零开始学习Python用于数据分析。当前阶段已了解基础语法正在寻找NumPy/Pandas的学习资源。偏好视频教程优先。”实体与事实在对话中提及的关键信息如人名、地点、时间、项目名、参数配置等。这些是结构化记忆的基石。用户偏好与画像用户表现出的习惯、喜好、禁忌。例如“用户不喜欢冗长的解释”、“用户对响应速度要求很高”、“用户是左撇子”在物理交互场景下。这类记忆是实现个性化的关键。任务状态与上下文对于执行长期任务的智能体必须记住任务的总体目标、当前进度、已完成的步骤、遇到的错误及解决方案。这是实现任务连续性的保障。自身行为与反馈智能体采取的行动及其结果成功/失败。这可以用于后续的强化学习或经验积累实现自我改进。编码策略决定了记忆的“存储格式”直接影响后续的检索效果。向量嵌入核心将文本片段通过嵌入模型如OpenAI的text-embedding-3-small、BGE、M3E等转换为高维向量。这个向量捕获了文本的语义信息是进行语义相似度检索的基础。关键点嵌入模型的选择至关重要需要与你的任务领域匹配。通用模型可能无法很好地区分专业术语。元数据Metadata为每段记忆附加结构化标签。例如timestamp时间戳、source来源如“用户输入”、“工具输出”、type类型如“用户偏好”、“任务步骤”、importance_score重要性分数可初始化为一个默认值后续动态调整。元数据便于进行高效的属性过滤。原始文本存储编码前的原始文本或摘要文本。当检索到相关向量后需要将对应的原始文本返回给LLM作为生成回复的上下文。实操心得在编码阶段一个常见的误区是“过度切片”。把一段长文本切得太碎会导致每段记忆信息量不足失去上下文切得太大又可能包含无关信息降低检索精度。我的经验是根据语义的自然段落或句子进行切割并确保每个切片能表达一个相对完整的意思。同时为每个切片生成一个简短的“标题”或“摘要”作为元数据的一部分能极大提升后续人工检视和调试的效率。2.2 外部记忆体的选型与数据组织这是记忆的“仓库”。选择什么样的数据库如何设计数据表或等价的集合/索引直接决定了系统的性能上限。数据库选型向量数据库首选专为高维向量相似性搜索优化。如Pinecone、Weaviate、Qdrant、Milvus、ChromaDB等。它们内置了高效的近似最近邻ANN搜索算法如HNSW、IVF-PQ能在大规模数据中快速找到语义相似的记忆。Pinecone/Weaviate云服务开箱即用运维简单但可能有成本。Qdrant/Milvus开源可自托管功能强大定制灵活但需要一定的运维能力。ChromaDB轻量级易于集成适合原型开发和中小规模场景。关系型数据库 向量扩展如PostgreSQL的pgvector插件。优势是可以将向量搜索和丰富的结构化查询通过元数据完美结合在一个事务中保证数据一致性。适合对事务有要求且记忆的元数据查询非常复杂的场景。图数据库如果记忆之间的关系如“事件A导致事件B”、“人物X属于组织Y”是核心图数据库如Neo4j是强大选择。它能高效处理复杂的关联查询但纯粹的语义相似度搜索可能不如专用向量库。数据组织“记忆库”设计 不建议将所有记忆混在一个大的“池子”里。合理的做法是根据记忆的归属进行分库或分区。会话记忆库Session Memory存储一次对话会话内的短期、高频率记忆。检索速度快但生命周期短随会话结束而清空或归档。用户记忆库User Memory存储属于特定用户的长期记忆如用户画像、历史偏好、重要事实等。这是实现个性化的核心数据库。全局记忆库Global/Entity Memory存储所有用户共享的公共知识或事实例如产品文档、公司规章制度、领域知识库等。任务记忆库Task Memory为每个长期运行的任务单独创建一个记忆空间存储该任务的所有相关上下文和状态。这种分离带来了多重好处安全性用户数据隔离、检索效率缩小搜索范围、管理便捷可以针对不同库设置不同的保留策略和更新频率。2.3 上下文的动态组装检索、排序与注入当智能体需要回应或思考时它如何从庞大的外部记忆中提取出最相关的片段并组装成LLM可理解的上下文这个过程称为“上下文组装”是记忆系统智能化的体现。检索-重排序Retrieve-and-Rerank管道初步检索Recall利用向量相似度搜索从目标记忆库中召回Top-K个候选记忆片段例如K20。这一步追求“全”尽可能不漏掉相关项。重排序Rerank对初步检索的结果进行更精细的排序。这里可以综合多种信号语义相关性使用更强大但更慢的交叉编码器模型如BGE-Reranker对查询和每个候选片段进行深度匹配打分。元数据权重给重要性分数高、时间更近的记忆赋予更高权重。新鲜度衰减引入时间衰减因子让较旧的记忆排名自然下降除非它们极其相关。多样性避免返回过多内容重复或语义高度重叠的记忆通过聚类或最大边际相关性MMR算法来保证结果的多样性。上下文压缩与格式化检索到的记忆片段总和可能仍然超过LLM的上下文窗口。因此需要进行压缩选择性注入只选择重排序后得分最高的前N个片段如N5。摘要压缩如果单个片段太长可以用一个小型LLM如Llama 3 8B对其进行摘要再将摘要注入上下文。格式化将最终选定的记忆片段按照一定的模板如“【用户偏好】...”、“【历史对话摘要】...”、“【相关事实】...”组织成一段连贯的文本作为系统提示词System Prompt的一部分或用户消息的前缀输入给LLM。注意事项上下文组装是性能瓶颈之一。向量检索和重排序都比较耗时。在实践中需要根据应用对延迟的容忍度来调整K和N的值。对于实时对话K和N不宜过大例如K10 N3。对于后台分析任务则可以放宽限制以追求更高的召回率。另外缓存机制非常有效对于频繁出现的相似查询可以直接缓存其检索结果避免重复计算。3. 实现自主记忆的关键环节与实操方案理解了架构我们来看看如何动手实现一个具备基础自主记忆能力的智能体。这里我将以一个基于Python使用LangChain框架和Qdrant向量数据库的简化示例拆解关键步骤。3.1 环境搭建与核心工具链选择首先明确我们的技术栈。为了兼顾灵活性和功能我选择以下组合框架LangChain。它提供了构建智能体所需的大量组件记忆、链、工具并且抽象良好能快速搭建原型。当然你也可以选择更底层的LlamaIndex或直接调用SDK。向量数据库Qdrant。开源、性能优秀、Docker部署简单同时支持云服务。嵌入模型text-embedding-3-small。在成本、速度和效果之间取得了很好的平衡。对于中文场景可以选用BGE-M3或M3E。LLMGPT-4o或Claude 3 Haiku用于推理和生成。对于记忆摘要等内部处理任务可以使用成本更低的模型如GPT-3.5-Turbo。部署Qdrant# 使用Docker快速启动一个Qdrant实例 docker run -p 6333:6333 -p 6334:6334 \ -v $(pwd)/qdrant_storage:/qdrant/storage:z \ qdrant/qdrant这将在本地6333端口启动Qdrant服务。3.2 构建记忆存储与检索层接下来我们用代码实现记忆的存储和检索核心逻辑。import os from datetime import datetime from typing import List, Dict, Any from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Qdrant from langchain.schema import Document from qdrant_client import QdrantClient from qdrant_client.http import models class AutonomousMemorySystem: def __init__(self, user_id: str, collection_name: str None): self.user_id user_id # 使用用户ID作为集合名的一部分实现逻辑隔离 self.collection_name collection_name or fuser_memory_{user_id} # 初始化嵌入模型 self.embeddings OpenAIEmbeddings(modeltext-embedding-3-small) # 连接Qdrant客户端 self.client QdrantClient(hostlocalhost, port6333) # 确保集合存在并配置向量索引 self._ensure_collection() # 初始化LangChain的Qdrant包装器 self.vectorstore Qdrant( clientself.client, collection_nameself.collection_name, embeddingsself.embeddings ) def _ensure_collection(self): 创建或验证向量集合 collections self.client.get_collections().collections collection_names [c.name for c in collections] if self.collection_name not in collection_names: # 创建集合定义向量维度和距离度量1536是text-embedding-3-small的维度 self.client.create_collection( collection_nameself.collection_name, vectors_configmodels.VectorParams( size1536, distancemodels.Distance.COSINE # 余弦相似度 ) ) print(fCollection {self.collection_name} created.) def store_memory(self, text: str, metadata: Dict[str, Any] None): 存储一段记忆 if metadata is None: metadata {} # 注入默认元数据 default_metadata { user_id: self.user_id, timestamp: datetime.utcnow().isoformat(), type: conversation, # 默认类型 importance: 1.0, # 默认重要性分数 source: assistant } default_metadata.update(metadata) # 创建LangChain Document对象 doc Document(page_contenttext, metadatadefault_metadata) # 存储到向量数据库 self.vectorstore.add_documents([doc]) print(fMemory stored: {text[:50]}...) def retrieve_memories(self, query: str, filter_dict: Dict None, k: int 5) - List[Document]: 检索相关记忆 # 1. 初步向量检索 docs self.vectorstore.similarity_search_with_score( query, kk*2, filterfilter_dict # 召回2倍为重排序留空间 ) # docs格式: [(Document, score), ...] if not docs: return [] # 2. 简单的重排序策略结合向量分数和元数据 reranked_docs [] for doc, vector_score in docs: final_score vector_score metadata doc.metadata # 时间衰减因子越近的记忆分数加成越高示例24小时衰减一半 time_str metadata.get(timestamp) if time_str: try: mem_time datetime.fromisoformat(time_str.replace(Z, 00:00)) hours_passed (datetime.utcnow() - mem_time).total_seconds() / 3600 recency_factor 0.5 ** (hours_passed / 24.0) # 半衰期24小时 final_score * (0.7 0.3 * recency_factor) # 时间权重占30% except: pass # 重要性加权 importance metadata.get(importance, 1.0) final_score * importance reranked_docs.append((doc, final_score)) # 按最终得分排序 reranked_docs.sort(keylambda x: x[1], reverseTrue) # 3. 返回Top-K return [doc for doc, _ in reranked_docs[:k]] def update_memory_importance(self, memory_id: str, new_importance: float): 更新某段记忆的重要性分数示例Qdrant需通过点ID操作 # 注意Qdrant的update操作需要知道点的ID。 # 在实际中存储时需记录ID或通过元数据中的唯一标识来查找。 # 此处为逻辑示意。 print(fWould update memory {memory_id} importance to {new_importance}) # self.client.update(...)这个类封装了记忆系统的核心存储和检索。store_memory方法负责将一段文本及其元数据向量化后存入Qdrant。retrieve_memories方法实现了带有时序衰减和重要性加权的简单重排序逻辑。3.3 集成到智能体工作流让记忆“活”起来现在我们需要将这个记忆系统嵌入到智能体的决策循环中。一个典型的循环是观察 - 检索记忆 - 思考/规划 - 行动 - 存储新记忆。from langchain.agents import AgentExecutor, create_openai_tools_agent from langchain_openai import ChatOpenAI from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain.tools import Tool from langchain.memory import ConversationBufferMemory import json class MemoryAugmentedAgent: def __init__(self, user_id: str): self.user_id user_id self.memory_system AutonomousMemorySystem(user_id) # 1. 定义工具记忆检索工具 def retrieve_memories_tool(query: str): 根据当前问题检索用户的长期记忆。 docs self.memory_system.retrieve_memories(query, k3) if not docs: return 没有找到相关的长期记忆。 memories_formatted \n---\n.join([f* {doc.page_content} (类型{doc.metadata.get(type, N/A)}, 时间{doc.metadata.get(timestamp, N/A)}) for doc in docs]) return f检索到的相关记忆\n{memories_formatted} # 2. 定义工具记忆存储工具 def store_memory_tool(text_to_store: str, memory_type: str fact, importance: float 1.0): 将重要信息存储到长期记忆中。 metadata { type: memory_type, importance: importance, source: agent_action } self.memory_system.store_memory(text_to_store, metadata) return f已成功将信息存入长期记忆。内容摘要{text_to_store[:100]}... # 将工具包装成LangChain Tool对象 tools [ Tool( nameretrieve_long_term_memory, funcretrieve_memories_tool, description当需要回忆与用户相关的过往信息、偏好或事实时使用此工具。输入一个查询字符串。 ), Tool( namestore_to_long_term_memory, funcstore_memory_tool, description当用户提供了重要的、值得长期记住的个人信息、偏好或决策时使用此工具。输入要存储的文本、记忆类型如preference, fact和重要性分数1-5。 ), # 这里可以添加其他工具如网络搜索、代码执行等 ] # 3. 构建提示词模板预留位置给记忆和聊天历史 prompt ChatPromptTemplate.from_messages([ (system, 你是一个拥有长期记忆的智能助手。你可以访问一个专门存储用户信息的记忆库。 在回答用户问题前请先使用retrieve_long_term_memory工具查看是否有相关历史信息。 如果用户提供了新的、有价值的信息请使用store_to_long_term_memory工具将其保存。 请基于所有可用信息当前对话、工具返回结果、长期记忆进行友好、专业的回复。), MessagesPlaceholder(variable_namechat_history), # 短期对话历史 (user, {input}), MessagesPlaceholder(variable_nameagent_scratchpad), # 代理的思考过程 ]) # 4. 初始化LLM和智能体 llm ChatOpenAI(modelgpt-4o, temperature0) self.agent create_openai_tools_agent(llm, tools, prompt) self.agent_executor AgentExecutor(agentself.agent, toolstools, verboseTrue, handle_parsing_errorsTrue) # 5. 短期记忆会话内存 self.short_term_memory ConversationBufferMemory(memory_keychat_history, return_messagesTrue) def run(self, user_input: str): 执行一轮交互 # 在调用智能体前可以主动触发一次记忆检索并将结果预加载到上下文中可选更高效 # 这里我们依赖智能体自己调用工具更符合自主性原则。 # 准备输入 inputs { input: user_input, chat_history: self.short_term_memory.buffer_as_messages # 注入短期历史 } # 执行智能体 response self.agent_executor.invoke(inputs) # 更新短期记忆 self.short_term_memory.save_context({input: user_input}, {output: response[output]}) return response[output] # 使用示例 if __name__ __main__: agent MemoryAugmentedAgent(user_idalice) # 第一轮用户告知偏好 print(用户我特别喜欢喝黑咖啡不加糖也不加奶。) resp1 agent.run(我特别喜欢喝黑咖啡不加糖也不加奶。) print(f助手{resp1}\n) # 第二轮几天后用户询问推荐 print(用户今天有点困有什么饮料推荐吗) # 智能体会自动调用retrieve_long_term_memory工具查询“饮料推荐”或“咖啡”相关记忆。 # 工具会返回之前存储的“喜欢黑咖啡”的记忆。 # LLM结合此记忆生成回复。 resp2 agent.run(今天有点困有什么饮料推荐吗) print(f助手{resp2})在这个实现中智能体被赋予了两种关键能力主动回忆通过retrieve_long_term_memory工具和主动记忆通过store_to_long_term_memory工具。LLM根据对话上下文自主决定何时调用这些工具。例如当用户说“我特别喜欢喝黑咖啡”时LLM应能判断这是一条重要的个人偏好从而调用存储工具。当用户后来问“有什么饮料推荐”时LLM应能联想到需要查询用户的饮食偏好从而调用检索工具。4. 性能调优、问题排查与进阶策略将基础系统跑起来只是第一步要让它在生产环境中稳定、高效地运行还需要面对一系列挑战。4.1 延迟与性能优化实战记忆系统的引入最直接的代价就是延迟增加。主要耗时点在1向量嵌入生成2向量数据库检索3重排序如果使用复杂模型。以下是一些行之有效的优化策略1. 异步与非阻塞操作存储异步化记忆的存储尤其是生成嵌入和写入数据库完全不需要阻塞主响应流程。可以将其放入后台任务队列如Celery、RQ中异步执行。# 伪代码示例使用异步任务 from celery import Celery app Celery(tasks, brokerredis://localhost:6379/0) app.task def async_store_memory(user_id, text, metadata): memory_system get_memory_system_for_user(user_id) # 需处理对象序列化或重建 memory_system.store_memory(text, metadata) # 在智能体响应后立即返回同时触发异步任务 async_store_memory.delay(user_id, important_text, metadata_dict)检索流水线化对于检索可以将“向量召回”和“LLM生成回复”并行。即在LLM开始推理的同时并行执行向量检索。这要求LLM的推理时间不能太短否则收益不明显。2. 缓存无处不在查询缓存对频繁出现的、相似的用户查询经过向量化后计算相似度的检索结果进行缓存。可以使用Redis存储序列化的记忆片段列表。设置合理的TTL例如5分钟。嵌入缓存对相同的文本内容其嵌入向量是固定的。可以建立一个全局的嵌入缓存键为文本内容的哈希值值为向量避免对重复内容如常见的问候语、系统提示词片段反复调用嵌入模型。3. 索引与检索参数调优HNSW参数如果使用Qdrant/Milvus调整HNSW索引的ef_construct和M参数。M影响索引的连通性和内存占用值越大精度越高内存消耗越大。ef_construct影响索引构建时的精度。对于亿级数据需要仔细权衡。搜索参数调整搜索时的ef动态候选集大小参数。增大ef可以提高召回率但会增加搜索时间。量化使用乘积量化PQ等压缩技术可以在轻微损失精度的情况下大幅减少内存占用和提升搜索速度。Qdrant和Milvus都支持标量量化。4. 分级记忆与混合检索热点记忆常驻内存将每个用户最近访问的、或重要性极高的少量记忆如用户姓名、核心偏好直接缓存在应用服务器的内存中实现微秒级读取。混合检索结合向量搜索和基于元数据的过滤。先通过user_id等强过滤条件缩小搜索范围再进行向量搜索能极大提升效率。4.2 常见问题排查与解决方案在实际运行中你可能会遇到以下典型问题问题现象可能原因排查步骤与解决方案检索结果不相关1. 嵌入模型不匹配领域。2. 文本切片不合理。3. 查询语句太短或模糊。1.领域微调嵌入模型在专业语料上继续训练或微调嵌入模型。2.优化切片策略尝试按句子、段落或语义单元切割并为切片添加人工评估。3.查询扩展使用LLM将用户的简短查询扩展成更详细的描述再进行检索。例如将“推荐饮料”扩展为“用户感到困倦寻求提神饮料推荐需考虑其历史饮食偏好”。记忆重复或冲突智能体频繁存储相似内容或新旧记忆矛盾。1.存储前去重计算新记忆与已有记忆的向量相似度若高于阈值则选择更新旧记忆如提升重要性、合并文本而非新增。2.设置记忆类型明确区分“事实”、“偏好”、“任务状态”等类型不同类型可共存冲突时以最新或最高重要性为准。3.实现记忆融合当检测到冲突时触发一个子流程让LLM分析新旧记忆生成一个融合后的、无冲突的新版本。系统响应明显变慢1. 记忆库数据量增长。2. 检索参数K过大。3. 网络或数据库负载高。1.监控与归档监控集合大小。对旧的、低重要性的记忆进行归档迁移到冷存储或摘要合并。2.性能剖析使用工具分析各阶段耗时嵌入、检索、重排序、LLM。针对性优化瓶颈点。3.数据库优化检查向量数据库的CPU/内存使用率考虑分片或升级配置。LLM不调用记忆工具1. 工具描述不清晰。2. 提示词引导不足。3. LLM温度参数过低过于保守。1.优化工具描述在description中清晰说明工具的使用场景和输入格式提供例子。2.强化系统提示在系统提示词中明确指令如“在回答前务必先检索长期记忆”。3.调整温度适当提高temperature如从0调到0.1-0.2增加探索性。也可以设计一个独立的“记忆路由”模块强制在每次交互前先进行检索。4.3 从“有记忆”到“真自主”记忆的主动管理与演化基础的记忆系统是被动的依赖LLM的指令来存储和检索。要实现“自主”系统需要具备更高级的能力1. 记忆的重要性动态评估与衰减不要将记忆的重要性分数设为固定值。它可以基于访问频率被检索到的次数、新鲜度最近是否被提及、来源权威性来自用户明确声明 vs. 模型推测以及情感强度结合情感分析来动态调整。实现一个后台进程定期扫描记忆库对重要性分数低于某个阈值的记忆进行自动归档或删除模拟“遗忘”机制。2. 记忆的自动摘要与合并当关于同一主题的记忆片段过多时例如多次对话都涉及“用户喜欢Python”可以触发一个摘要任务。使用LLM将这些片段合并、去重生成一个更精炼、信息密度更高的摘要记忆并删除或降权原始碎片。这能有效控制记忆库的膨胀。3. 记忆驱动的主动交互真正的自主智能体不应只被动响应。它可以基于记忆主动发起对话。例如系统检测到用户上次提到“下周要出差”在出差日期临近时可以主动询问“您之前提到的出差准备得怎么样了需要我帮您查天气或规划路线吗”这需要系统具备事件监听和定时触发的能力。4. 处理“chimera”式异构多智能体场景在由多个异构LLM有的擅长推理有的擅长编码有的擅长创意协同服务的系统中记忆系统可以作为中央共享状态层。每个智能体读写记忆时都需要通过一个记忆协调器。协调器负责解决潜在的写入冲突如乐观锁并为不同智能体提供定制化的记忆视图例如给创意模型看情感和风格记忆给推理模型看逻辑和事实记忆。这要求记忆的元数据 schema 设计得非常丰富以支持灵活的过滤和视图构建。构建一个成熟的自主记忆系统是一个从简单到复杂、持续迭代的过程。它始于一个向量数据库和几个API调用但最终会演变成一个包含动态评估、摘要合并、冲突解决和主动触发的复杂子系统。这个过程的核心始终是围绕一个目标让AI智能体更像一个持续学习、不断成长的伙伴而不是一个每次对话都要重启的“金鱼”。