ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于MCP与Docker的Agent Memory实战:构建LLM智能体的三层记忆系统

基于MCP与Docker的Agent Memory实战:构建LLM智能体的三层记忆系统 1. 从“hindsight”说起为什么我们需要给Agent装上一双“后视之眼”“hindsight”这个词本身很有意思字面意思是“事后的洞察力”也就是我们常说的“后见之明”。放在Agent Memory这个领域里它指向一个非常具体且要命的问题大模型驱动的Agent如何在执行任务的过程中记住自己做过什么、做对了什么、做错了什么并在后续决策中真正用上这些经验。我接触过不少做Agent项目的团队大家一开始的注意力几乎都放在“让Agent能干活”上——接工具、写Prompt、调工作流。但跑一段时间就会发现Agent在单轮对话里表现还行一旦任务链条拉长或者需要跨会话保持状态它就开始“失忆”。上一轮明明已经确认过的参数下一轮又问你一遍之前踩过的坑换个会话原封不动再踩一次。这不是模型不够聪明而是Agent缺少一套可靠的记忆机制。“hindsight”这个项目标题我理解它要解决的核心就是这件事给Agent构建一套能够回溯、检索、复用历史经验的记忆系统。它涉及的关键词里agent memory是核心目标LLM是记忆的生成与消费方MCP是记忆能力的对外暴露协议Docker则是让这套系统能够快速部署、隔离运行的基础设施。这几个词串起来其实就是一条完整的落地路径用Docker把记忆服务跑起来通过MCP协议把记忆能力标准化地暴露给LLM Agent让Agent在需要的时候能够调用自己的“后视之眼”。这篇文章适合谁看如果你正在做Agent应用发现记忆管理是个绕不过去的坎如果你听说过MCP但还没想清楚它跟Agent Memory怎么结合如果你想把一套记忆服务用Docker快速部署起来验证效果——那这篇内容应该能给你一些可以直接抄作业的思路和代码。我会从整体设计讲到具体实现把参数选择、踩坑记录、排查技巧都摊开来说尽量让不同基础的读者都能找到自己能用的部分。2. 整体设计思路Agent Memory到底该怎么分层2.1 为什么不能把记忆直接塞进上下文窗口很多人第一反应是记忆嘛不就是把历史对话拼到Prompt里这个做法在短会话里没问题但一旦任务变复杂立刻撞上三个硬墙。第一是上下文窗口的物理限制。就算现在主流模型支持128K甚至更长的上下文你也不可能把Agent跑过的所有轨迹都塞进去。一个稍微复杂点的任务工具调用日志、中间结果、错误堆栈加起来几轮就爆了。第二是注意力稀释。上下文越长模型对关键信息的注意力越容易被无关内容冲淡你明明把重要参数放在第3轮模型在第20轮可能已经“看不太清”了。第三是成本。每次请求都把全量历史带上Token消耗是线性增长的跑久了账单很难看。所以Agent Memory的第一条设计原则就是记忆必须外置按需检索而不是全量注入。这跟人脑的工作方式其实很像——你不会在每次思考时把一生所有记忆都调出来而是根据当前场景检索相关经验。2.2 三层记忆结构working memory、episodic memory、semantic memory参考认知科学里对记忆的分类我在“hindsight”这类项目里通常会把Agent Memory分成三层来设计。Working Memory工作记忆是最短期的对应当前任务执行过程中的临时状态。比如Agent正在处理一个多步任务当前进行到第几步、已经收集了哪些参数、下一步要调用什么工具。这层记忆的生命周期就是当前任务任务结束就可以归档或丢弃。它解决的是“我现在在干什么”的问题。Episodic Memory情景记忆记录的是具体的任务执行轨迹。每一次Agent完成一个任务都会留下一条情景记忆任务目标是什么、用了哪些工具、中间遇到了什么错误、最终结果如何。这层记忆的价值在于可回溯——当Agent遇到类似任务时可以检索过去的情景看看当时是怎么处理的。它解决的是“我以前遇到类似情况是怎么做的”的问题。Semantic Memory语义记忆是最抽象的一层存储的是从多次情景中提炼出来的通用知识。比如“调用某个API时如果返回429需要退避重试”“处理日期格式时优先用ISO 8601”这类规则。这层记忆不绑定具体任务而是跨任务复用的经验。它解决的是“我知道什么是对的”的问题。这三层不是孤立的而是有流转关系的。Working Memory在任务结束后经过摘要和结构化沉淀为Episodic Memory多条Episodic Memory经过归纳提炼为Semantic Memory。这个流转过程本身就是“hindsight”的体现——事后回顾提炼经验指导未来。2.3 为什么选MCP作为记忆能力的暴露协议记忆服务建好了怎么让LLM Agent用上最直接的做法是在Agent代码里直接调记忆服务的SDK。但这样做的问题是耦合太紧——换一个Agent框架记忆接入代码就得重写换一个记忆后端Agent侧也要改。MCPModel Context Protocol在这里的价值就体现出来了。它本质上是一套标准化的协议让Agent能够以统一的方式发现和调用外部能力。把记忆服务封装成MCP Server之后任何支持MCP的Agent都可以通过标准接口来读写记忆不需要关心底层是向量数据库还是关系型数据库也不需要关心记忆是怎么分层的。我实测下来这种解耦带来的好处非常明显记忆服务的迭代不影响Agent侧Agent框架的更换也不影响记忆服务。而且MCP的Tool定义方式很自然可以把“写入记忆”“检索记忆”“更新记忆”分别定义成独立的ToolAgent根据当前需要自主决定调用哪个。2.4 Docker在整套方案里的角色Docker在这里不是可选项而是让整套方案能够快速落地的前提。记忆服务通常依赖向量数据库、关系型数据库、缓存等组件手工装环境很容易出现版本冲突、依赖缺失的问题。用Docker Compose把记忆服务、数据库、缓存编排在一起一条命令就能拉起完整环境这对验证和迭代太重要了。而且Docker的隔离性让记忆服务的运行环境跟Agent的运行环境解耦记忆服务升级不会影响正在跑的AgentAgent重启也不会丢记忆数据。下面这张表是我在实际项目中常用的组件选型对照供参考。组件选型理由记忆服务运行时Python 3.11 FastAPI生态成熟MCP SDK支持好向量存储Qdrant轻量Docker部署简单过滤能力强结构化存储PostgreSQL 16存情景记忆的元数据支持JSON字段缓存Redis 7Working Memory的临时存储TTL控制方便协议层MCP Server (Python SDK)标准化暴露记忆能力编排Docker Compose一键拉起全套依赖3. 核心细节解析记忆的写入、检索与更新3.1 记忆写入不是所有东西都值得记Agent在运行过程中会产生大量中间数据如果全部写入记忆不仅存储成本高检索时也会被噪声淹没。所以写入策略的核心是过滤和结构化。我的做法是在MCP Server里定义一个memory_write工具接收的参数包括content记忆内容、memory_typeworking/episodic/semantic、metadata任务ID、时间戳、工具名等、importance重要性评分。Agent在调用这个工具时需要自己判断当前信息是否值得记录。但完全靠Agent判断也不可靠所以我在服务端加了一层规则过滤。比如Working Memory的写入直接放行但设置TTL任务结束后自动过期。Episodic Memory的写入要求content长度超过一定阈值且必须包含任务结果字段避免记录半截信息。Semantic Memory的写入需要importance超过阈值且内容经过一次LLM摘要确保是提炼后的知识而非原始日志。这里有个实操心得写入时的metadata设计比content本身还重要。因为后续检索主要靠metadata过滤如果metadata字段缺失或格式不统一检索效率会大打折扣。我一般会强制要求包含task_id、timestamp、tool_name、outcome这四个字段缺一个就拒绝写入。# memory_write 工具的核心处理逻辑简化版 from datetime import datetime from pydantic import BaseModel, Field class MemoryWriteInput(BaseModel): content: str Field(..., min_length10) memory_type: str Field(..., pattern^(working|episodic|semantic)$) metadata: dict importance: float Field(ge0.0, le1.0) REQUIRED_META_FIELDS {task_id, timestamp, tool_name, outcome} async def handle_memory_write(input: MemoryWriteInput): missing REQUIRED_META_FIELDS - set(input.metadata.keys()) if missing: raise ValueError(fmetadata missing required fields: {missing}) if input.memory_type semantic and input.importance 0.7: return {status: skipped, reason: importance below threshold} # 写入对应存储层 if input.memory_type working: await redis.setex( fwm:{input.metadata[task_id]}, 3600, # 1小时TTL input.content ) elif input.memory_type episodic: await pg.execute( INSERT INTO episodic_memory (content, metadata, importance) VALUES ($1, $2, $3), input.content, input.metadata, input.importance ) else: # semantic memory 需要先向量化 vector await embed(input.content) await qdrant.upsert( collection_namesemantic_memory, points[{ id: generate_id(), vector: vector, payload: {content: input.content, **input.metadata} }] ) return {status: ok, memory_type: input.memory_type}3.2 记忆检索Token的三个关键问题热词里有一条提到“llm的token三个点key我是谁、query我在找什么、value我能提供什么”这个类比放在记忆检索里特别贴切。检索的本质就是用Query去匹配Key取出Value。在Agent Memory的场景里Key是记忆的索引维度包括向量索引、metadata字段、时间范围等。Query是Agent当前的需求可能是一段自然语言描述也可能是一组结构化过滤条件。Value是检索出来的记忆内容需要按相关性排序后返回。我的检索策略是混合检索先用metadata做硬过滤比如限定memory_type、时间范围、task_id再用向量相似度做软排序。这样既能保证检索结果的时效性和类型正确性又能保证语义相关性。具体实现上Qdrant支持在向量检索的同时做payload过滤非常适合这个场景。比如Agent要检索“之前处理过类似数据清洗任务的经验”可以这样构造查询async def handle_memory_search(query: str, memory_type: str None, time_range: tuple None, top_k: int 5): # 构造过滤条件 filters {} if memory_type: filters[memory_type] memory_type if time_range: filters[timestamp] {$gte: time_range[0], $lte: time_range[1]} # 向量化查询 query_vector await embed(query) # 混合检索 results await qdrant.search( collection_namesemantic_memory, query_vectorquery_vector, query_filterfilters if filters else None, limittop_k ) # 按重要性加权排序 scored [] for r in results: final_score r.score * 0.7 r.payload.get(importance, 0.5) * 0.3 scored.append((final_score, r.payload)) scored.sort(keylambda x: x[0], reverseTrue) return [{content: p[content], score: s, metadata: p} for s, p in scored]这里有个细节值得说为什么要在向量相似度之外再加重要性加权因为纯向量相似度有时候会检索出语义相近但实际价值不高的记忆。比如两条记忆都跟“数据清洗”相关一条是成功经验一条是失败记录向量相似度可能差不多但成功经验的importance更高应该优先返回。加权排序能让检索结果更符合Agent的实际需要。3.3 记忆更新与遗忘不是所有记忆都该永久保留记忆系统如果只写不删很快就会变成垃圾场。所以更新和遗忘机制是必须的。更新主要发生在两种场景一是Working Memory在任务推进过程中需要不断覆盖这个用Redis的SET覆盖就行二是Episodic Memory在任务结束后需要补充最终结果这个用PostgreSQL的UPDATE。Semantic Memory一般不做原地更新而是写入新版本旧版本标记为过期保留历史便于追溯。遗忘策略我通常分三档Working Memory任务结束或TTL到期自动删除不需要额外处理。Episodic Memory保留最近N条或者按时间窗口保留比如最近30天超出的归档到冷存储。Semantic Memory按importance和访问频率做淘汰长期不被检索且importance低的记忆降权或删除。注意遗忘策略一定要可配置不同业务场景对记忆保留时长的要求差别很大。我一般会把TTL和淘汰阈值放在环境变量里方便调整。4. 实操过程用Docker把整套记忆服务跑起来4.1 环境准备与Docker Compose编排先说环境。我用的开发机是Windows 11装了Docker Desktop。这里有个坑要提前说Windows上装Docker Desktop必须先确认虚拟化支持已经打开。如果BIOS里没开VT-x或AMD-VDocker Desktop启动时会直接报“virtualization support not detected”然后失败。检查方法很简单任务管理器→性能→CPU看右下角“虚拟化”是不是“已启用”。没启用的话进BIOS打开不同主板按键不一样一般是F2、Del或F10。Docker Desktop装好之后建议把WSL 2后端打开性能比Hyper-V后端好不少尤其是文件IO密集的场景。设置路径在Docker Desktop→Settings→General→Use WSL 2 based engine。接下来是编排文件。我把记忆服务的全套依赖写在一个docker-compose.yml里包括Qdrant、PostgreSQL、Redis和记忆服务本身。version: 3.9 services: qdrant: image: qdrant/qdrant:v1.9.0 ports: - 6333:6333 - 6334:6334 volumes: - qdrant_data:/qdrant/storage environment: - QDRANT__SERVICE__GRPC_PORT6334 postgres: image: postgres:16-alpine ports: - 5432:5432 environment: POSTGRES_USER: memory POSTGRES_PASSWORD: memory_pass_2024 POSTGRES_DB: agent_memory volumes: - pg_data:/var/lib/postgresql/data - ./init.sql:/docker-entrypoint-initdb.d/init.sql redis: image: redis:7-alpine ports: - 6379:6379 command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru volumes: - redis_data:/data memory-service: build: ./memory-service ports: - 8000:8000 depends_on: - qdrant - postgres - redis environment: - QDRANT_HOSTqdrant - QDRANT_PORT6333 - PG_DSNpostgresql://memory:memory_pass_2024postgres:5432/agent_memory - REDIS_URLredis://redis:6379/0 - EMBEDDING_MODELBAAI/bge-small-zh-v1.5 volumes: - ./memory-service:/app volumes: qdrant_data: pg_data: redis_data:这个编排文件有几个设计点值得说明。第一Qdrant同时暴露6333HTTP和6334gRPC端口HTTP用于调试和管理gRPC用于高性能写入实际生产环境建议走gRPC。第二PostgreSQL挂了一个init.sql用来初始化表结构避免每次重建容器都要手工建表。第三Redis设置了maxmemory和淘汰策略防止Working Memory无限增长把内存吃满。第四memory-service用build而不是image方便本地开发时改代码即时生效。init.sql的内容大概是这样CREATE TABLE IF NOT EXISTS episodic_memory ( id BIGSERIAL PRIMARY KEY, content TEXT NOT NULL, metadata JSONB NOT NULL, importance FLOAT DEFAULT 0.5, created_at TIMESTAMPTZ DEFAULT NOW(), archived BOOLEAN DEFAULT FALSE ); CREATE INDEX idx_episodic_metadata ON episodic_memory USING GIN (metadata); CREATE INDEX idx_episodic_created ON episodic_memory (created_at DESC); CREATE TABLE IF NOT EXISTS semantic_memory_meta ( id UUID PRIMARY KEY, content TEXT NOT NULL, importance FLOAT DEFAULT 0.5, access_count INT DEFAULT 0, last_accessed TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), deprecated BOOLEAN DEFAULT FALSE );4.2 记忆服务的MCP Server实现记忆服务的核心是一个MCP Server对外暴露三个工具memory_write、memory_search、memory_forget。我用Python的MCP SDK来实现整体结构不复杂关键是参数定义要清晰。# mcp_server.py from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent import json app Server(hindsight-memory) app.list_tools() async def list_tools(): return [ Tool( namememory_write, description写入一条Agent记忆。memory_type可选working/episodic/semantic。, inputSchema{ type: object, properties: { content: {type: string, description: 记忆内容}, memory_type: {type: string, enum: [working, episodic, semantic]}, metadata: { type: object, properties: { task_id: {type: string}, timestamp: {type: string}, tool_name: {type: string}, outcome: {type: string} }, required: [task_id, timestamp, tool_name, outcome] }, importance: {type: number, minimum: 0, maximum: 1} }, required: [content, memory_type, metadata] } ), Tool( namememory_search, description检索Agent记忆。支持按类型、时间范围过滤返回按相关性排序的结果。, inputSchema{ type: object, properties: { query: {type: string}, memory_type: {type: string, enum: [working, episodic, semantic]}, time_start: {type: string}, time_end: {type: string}, top_k: {type: integer, default: 5, maximum: 20} }, required: [query] } ), Tool( namememory_forget, description删除或归档指定记忆。, inputSchema{ type: object, properties: { memory_id: {type: string}, memory_type: {type: string}, hard_delete: {type: boolean, default: False} }, required: [memory_id, memory_type] } ) ] app.call_tool() async def call_tool(name: str, arguments: dict): if name memory_write: result await handle_memory_write(MemoryWriteInput(**arguments)) elif name memory_search: result await handle_memory_search(**arguments) elif name memory_forget: result await handle_memory_forget(**arguments) else: raise ValueError(funknown tool: {name}) return [TextContent(typetext, textjson.dumps(result, ensure_asciiFalse))] async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options()) if __name__ __main__: import asyncio asyncio.run(main())这个Server可以通过stdio方式跟Agent进程通信也可以包装成SSE服务走HTTP。我一般开发阶段用stdio部署阶段用SSE灵活一些。4.3 启动与验证一条命令拉起全套环境编排文件和代码准备好之后启动就很简单了# 在docker-compose.yml所在目录执行 docker compose up -d # 查看服务状态 docker compose ps # 查看记忆服务日志 docker compose logs -f memory-service启动之后我一般会做三步验证。第一步验证Qdrant和PostgreSQL连通性。访问http://localhost:6333/dashboard能看到Qdrant的Web UI就说明Qdrant正常。PostgreSQL用docker compose exec postgres psql -U memory -d agent_memory -c \dt看表是否建好。第二步验证记忆写入。用curl直接调记忆服务的HTTP接口如果开了SSE模式或者写个简单的Python脚本通过MCP客户端调memory_write。第三步验证记忆检索。写入几条测试记忆后调memory_search看能否正确返回。这里要重点验证metadata过滤是否生效比如限定memory_typeepisodic时working和semantic的记忆不应该出现在结果里。提示第一次启动时Qdrant需要下载模型做向量化如果网络环境一般可能会卡在embedding模型加载这一步。建议提前把模型文件挂载到容器里或者用国内镜像源。5. 常见问题与排查技巧实录5.1 Docker相关的高频问题问题一Docker Desktop启动失败报虚拟化未检测到。这个前面提过进BIOS开虚拟化。如果BIOS里已经开了还是报错检查Windows的“Hyper-V”和“虚拟机平台”功能是否启用。控制面板→程序→启用或关闭Windows功能把这两项勾上重启。问题二容器间网络不通。典型表现是memory-service连不上qdrant或postgres。先检查docker compose ps看服务是否都在running状态。如果都running但连不上大概率是服务名写错了。Docker Compose默认创建一个bridge网络服务之间用服务名做DNS解析所以环境变量里应该写qdrant而不是localhost。我踩过这个坑本地调试时把QDRANT_HOST写成localhost容器里根本解析不到。问题三PostgreSQL数据丢失。如果没挂volume容器重建数据就没了。编排文件里pg_data:/var/lib/postgresql/data这行必须要有。另外注意如果之前用旧版本PostgreSQL初始化过volume升级镜像版本时可能因为数据目录格式不兼容导致启动失败这时候要么迁移数据要么删掉volume重建。问题四Redis内存爆了。Working Memory如果不设TTL任务多了之后Redis内存会持续增长。编排文件里--maxmemory 256mb --maxmemory-policy allkeys-lru就是防这个的。但更好的做法是在写入Working Memory时强制设TTL双保险。5.2 MCP接入的典型故障问题一Agent找不到MCP Server。检查MCP Server的启动命令和路径配置。如果是stdio模式Agent启动时会拉起Server进程路径写错就直接找不到。如果是SSE模式检查端口是否被占用、防火墙是否放行。问题二Tool调用返回schema错误。热词里有一条“llm request failed: provider rejected the request schema or tool payload”这个在MCP接入时很常见。原因是Tool的inputSchema定义跟Agent实际传的参数不匹配。比如schema里importance是numberAgent传了字符串0.8就会被拒。解决办法是在Server端做参数类型转换和校验别完全信任Agent传过来的格式。问题三检索结果为空。先确认写入是否成功再确认检索条件是否过严。我遇到过metadata里timestamp格式不统一导致时间范围过滤失效的情况——有的记忆写的是ISO格式有的是Unix时间戳过滤时自然对不上。统一用ISO 8601格式能避免大部分这类问题。5.3 记忆质量相关的坑坑一记忆内容太长。有些Agent会把整段工具返回结果直接写入记忆导致单条记忆几千字。检索出来之后注入上下文又占大量Token。我的做法是在写入前做一次摘要把长内容压缩到200字以内原始内容存到对象存储记忆里只存摘要和引用ID。坑二相似记忆重复写入。Agent可能在不同任务里反复写入内容高度相似的记忆。如果不做去重检索时返回一堆重复内容浪费Token。解决办法是在写入前先做一次相似度检索如果已有记忆相似度超过0.95就更新已有记忆的access_count而不是新建。坑三Semantic Memory提炼质量差。Semantic Memory是从Episodic Memory归纳出来的如果归纳Prompt写得不好提炼出来的“知识”可能是废话。我一般会用LLM做一次质量评分低于阈值的直接丢弃不写入Semantic层。下面这张表是我整理的高频问题速查表方便快速定位。现象可能原因排查方向解决方式Docker Desktop启动失败虚拟化未开启BIOS和Windows功能开VT-x/AMD-V启用Hyper-V容器间连不上服务名写错检查环境变量用Compose服务名而非localhost数据丢失未挂volume检查volumes配置补上持久化volumeRedis内存增长Working Memory无TTL检查写入逻辑强制设TTLLRU淘汰MCP Tool调用被拒schema不匹配对比schema和实际参数Server端做类型转换检索结果为空metadata格式不统一检查timestamp等字段统一ISO 8601格式记忆重复未做去重检查写入前检索相似度0.95则更新Semantic质量差归纳Prompt不佳检查提炼逻辑加LLM质量评分过滤5.4 性能调优的几个实操心得批量写入比单条写入快很多。如果Agent在一个任务里要写多条记忆攒一批再写Qdrant的批量upsert比逐条快一个数量级。我一般设一个50条的缓冲满了或者任务结束就flush。向量维度别贪大。我用的是bge-small-zh-v1.5512维检索速度和精度平衡得不错。如果换成1024维甚至更高检索延迟会明显上升但精度提升有限。除非业务对语义匹配要求极高否则512维够用。索引参数要调。Qdrant的HNSW索引有几个关键参数m控制图的连接数ef_construct控制构建时的搜索范围。默认值在数据量小的时候没问题数据量上到十万级以上时适当调大m和ef_construct能提升召回率但会增加内存占用。我一般设m16、ef_construct100实测下来比较均衡。缓存热点记忆。有些记忆会被频繁检索比如常用的工具调用规则。这类记忆可以在Redis里做一层缓存检索时先查Redis命中就直接返回不用走向量检索。缓存key用query的hashTTL设短一点比如5分钟避免记忆更新后缓存不一致。6. 记忆系统的扩展方向与个人体会这套基于MCP和Docker的Agent Memory方案我在几个项目里跑下来稳定性是可以的。但它肯定不是终点有几个方向我觉得值得继续折腾。一个是记忆的主动遗忘。现在的遗忘策略还是被动的按时间或频率淘汰。但人脑的遗忘是有选择性的重要的记忆即使很久不用也不会忘琐碎的记忆很快就淡了。如果能让Agent自己判断哪些记忆重要、哪些可以忘记忆系统的效率会更高。另一个是跨Agent的记忆共享。现在每个Agent的记忆是独立的但如果多个Agent协作完成一个任务它们之间的经验其实可以共享。通过MCP把记忆服务做成一个共享层不同Agent写入的记忆可以互相检索这个在Multi-Agent场景下会很有价值。还有一个是记忆的可解释性。Agent检索到一条记忆后为什么选这条而不是那条现在的加权排序是个黑盒。如果能给出可解释的排序理由调试和优化会容易很多。我个人在实际操作中的体会是Agent Memory最难的不是技术实现而是记忆粒度的把握。记太细检索时噪声大记太粗又丢失了关键细节。这个平衡点没有通用答案得根据具体业务场景反复调。我的建议是先把写入和检索跑通然后拿真实任务跑一批看检索出来的记忆到底有没有用再反过来调写入策略。这个迭代过程比一开始就追求完美设计要有效得多。最后分享一个小技巧在metadata里加一个source字段标记这条记忆是哪个Agent、哪个任务、哪个工具产生的。排查问题时顺着source能快速定位到原始上下文比只看记忆内容高效得多。这个字段我一开始没加后来补上的时候发现历史记忆都没法追溯了只能清库重来。
返回列表