Memory

利用 CrewAI 中统一的 memory 系统来增强 agent 能力。

概览

CrewAI 提供了一个 统一的 memory 系统 —— 一个单一的 Memory 类,用来替代原本独立的短期记忆、长期记忆、实体记忆和外部记忆类型,并通过一个智能 API 统一管理。Memory 在保存内容时会使用 LLM 进行分析(推断 scope、categories 和 importance),并支持具有自适应深度的 recall,使用复合评分机制综合语义相似度、时间新近度和重要性进行排序。

你可以通过四种方式使用 memory:独立使用(脚本、notebook)、与 Crews 一起使用与 Agents 一起使用,或者 在 Flows 内部使用

快速开始

  1. from crewai import Memory
  2. memory = Memory()
  3. # 存储 —— LLM 会推断 scope、categories 和 importance
  4. memory.remember("We decided to use PostgreSQL for the user database.")
  5. # 检索 —— 结果按复合评分排序(语义 + 新近度 + 重要性)
  6. matches = memory.recall("What database did we choose?")
  7. for m in matches:
  8. print(f"[{m.score:.2f}] {m.record.content}")
  9. # 为一个变化很快的项目调整评分权重
  10. memory = Memory(recency_weight=0.5, recency_half_life_days=7)
  11. # 忘记
  12. memory.forget(scope="/project/old")
  13. # 查看自组织的 scope 树
  14. print(memory.tree())
  15. print(memory.info("/"))

使用 Memory 的四种方式

独立使用

你可以在脚本、notebook、CLI 工具中使用 memory,或者把它当作一个独立知识库来用 —— 不需要 agents 或 crews。

  1. from crewai import Memory
  2. memory = Memory()
  3. # 累积知识
  4. memory.remember("The API rate limit is 1000 requests per minute.")
  5. memory.remember("Our staging environment uses port 8080.")
  6. memory.remember("The team agreed to use feature flags for all new releases.")
  7. # 之后按需检索
  8. matches = memory.recall("What are our API limits?", limit=5)
  9. for m in matches:
  10. print(f"[{m.score:.2f}] {m.record.content}")
  11. # 从较长文本中提取原子事实
  12. raw = """Meeting notes: We decided to migrate from MySQL to PostgreSQL
  13. next quarter. The budget is $50k. Sarah will lead the migration."""
  14. facts = memory.extract_memories(raw)
  15. # ["Migration from MySQL to PostgreSQL planned for next quarter",
  16. # "Database migration budget is $50k",
  17. # "Sarah will lead the database migration"]
  18. for fact in facts:
  19. memory.remember(fact)

与 Crews 一起使用

传入 memory=True 使用默认设置,或者传入一个已配置好的 Memory 实例来自定义行为。

  1. from crewai import Crew, Agent, Task, Process, Memory
  2. # 方式 1:默认 memory
  3. crew = Crew(
  4. agents=[researcher, writer],
  5. tasks=[research_task, writing_task],
  6. process=Process.sequential,
  7. memory=True,
  8. verbose=True,
  9. )
  10. # 方式 2:带评分调优的自定义 memory
  11. memory = Memory(
  12. recency_weight=0.4,
  13. semantic_weight=0.4,
  14. importance_weight=0.2,
  15. recency_half_life_days=14,
  16. )
  17. crew = Crew(
  18. agents=[researcher, writer],
  19. tasks=[research_task, writing_task],
  20. memory=memory,
  21. )

memory=True 时,crew 会创建一个默认的 Memory(),并自动传递 crew 的 embedder 配置。crew 中所有 agents 默认共享 crew 的 memory,除非某个 agent 自己定义了独立 memory。

每个 task 完成后,crew 会自动从任务输出中提取离散事实并存储。每个 task 开始前,agent 会先从 memory 中回忆相关上下文,并将其注入到任务 prompt 中。

与 Agents 一起使用

Agents 可以使用 crew 的共享 memory(默认),也可以接收一个带 scope 的视图,用作私有上下文。

  1. from crewai import Agent, Memory
  2. memory = Memory()
  3. # Researcher 获得一个私有 scope —— 只能看到 /agent/researcher
  4. researcher = Agent(
  5. role="Researcher",
  6. goal="Find and analyze information",
  7. backstory="Expert researcher with attention to detail",
  8. memory=memory.scope("/agent/researcher"),
  9. )
  10. # Writer 使用 crew 共享 memory(未设置 agent 级 memory)
  11. writer = Agent(
  12. role="Writer",
  13. goal="Produce clear, well-structured content",
  14. backstory="Experienced technical writer",
  15. # 未设置 memory —— 当 crew 启用了 memory 时,使用 crew._memory
  16. )

这种模式可以让 researcher 保留私有发现,而 writer 则从 crew 共享 memory 中读取内容。

在 Flows 中使用

每个 Flow 都内置了 memory。你可以在任意 flow 方法中使用 self.remember()self.recall()self.extract_memories()

  1. from crewai.flow.flow import Flow, listen, start
  2. class ResearchFlow(Flow):
  3. @start()
  4. def gather_data(self):
  5. findings = "PostgreSQL handles 10k concurrent connections. MySQL caps at 5k."
  6. self.remember(findings, scope="/research/databases")
  7. return findings
  8. @listen(gather_data)
  9. def write_report(self, findings):
  10. # 回忆过去的研究,为当前内容提供上下文
  11. past = self.recall("database performance benchmarks")
  12. context = "\n".join(f"- {m.record.content}" for m in past)
  13. return f"Report:\nNew findings: {findings}\nPrevious context:\n{context}"

关于 Flows 中的 memory,参见 Flows documentation

分层 Scopes

什么是 Scope

Memories 被组织在一个分层的 scope 树中,类似文件系统。每个 scope 都是一个路径,例如 //project/alpha/agent/researcher/findings

  1. /
  2. /company
  3. /company/engineering
  4. /company/product
  5. /project
  6. /project/alpha
  7. /project/beta
  8. /agent
  9. /agent/researcher
  10. /agent/writer

Scopes 提供了 上下文相关的 memory —— 当你在某个 scope 内 recall 时,只会搜索该树枝,从而同时提升精度和性能。

Scope 推断是如何工作的

当你调用 remember() 却没有指定 scope 时,LLM 会分析内容和已有的 scope 树,然后建议最合适的放置位置。如果没有合适的现有 scope,它会创建一个新的。随着时间推移,scope 树会根据内容自然生长 —— 你无需预先设计 schema。

  1. memory = Memory()
  2. # LLM 根据内容推断 scope
  3. memory.remember("We chose PostgreSQL for the user database.")
  4. # -> 可能被放到 /project/decisions 或 /engineering/database
  5. # 你也可以显式指定 scope
  6. memory.remember("Sprint velocity is 42 points", scope="/team/metrics")

可视化 Scope 树

  1. print(memory.tree())
  2. # / (15 records)
  3. # /project (8 records)
  4. # /project/alpha (5 records)
  5. # /project/beta (3 records)
  6. # /agent (7 records)
  7. # /agent/researcher (4 records)
  8. # /agent/writer (3 records)
  9. print(memory.info("/project/alpha"))
  10. # ScopeInfo(path='/project/alpha', record_count=5,
  11. # categories=['architecture', 'database'],
  12. # oldest_record=datetime(...), newest_record=datetime(...),
  13. # child_scopes=[])

MemoryScope:子树视图

MemoryScope 会将所有操作限制在树的某个分支内。使用它的 agent 或代码只能在该子树中查看和写入。

  1. memory = Memory()
  2. # 为特定 agent 创建一个 scope
  3. agent_memory = memory.scope("/agent/researcher")
  4. # 所有操作都相对于 /agent/researcher
  5. agent_memory.remember("Found three relevant papers on LLM memory.")
  6. # -> 存储在 /agent/researcher
  7. agent_memory.recall("relevant papers")
  8. # -> 只搜索 /agent/researcher
  9. # 进一步缩小到 subscope
  10. project_memory = agent_memory.subscope("project-alpha")
  11. # -> /agent/researcher/project-alpha

Scope 设计最佳实践

  • 从扁平开始,让 LLM 组织结构。 不要一开始就过度设计 scope 层级。先使用 memory.remember(content),让 LLM 的 scope 推断随着内容积累自然建立结构。
  • 使用 /{entity_type}/{identifier} 模式。 自然的层级结构通常会来自诸如 /project/alpha/agent/researcher/company/engineering/customer/acme-corp 这样的模式。
  • 按关注点而不是按数据类型划分 scope。 使用 /project/alpha/decisions,而不是 /decisions/project/alpha。这样可以让相关内容保持在一起。
  • 保持较浅层级(2 到 3 层)。 过深嵌套的 scopes 会变得太稀疏。/project/alpha/architecture 很合适;/project/alpha/architecture/decisions/databases/postgresql 就太深了。
  • 已知时显式指定 scope,不确定时让 LLM 推断。 如果你存储的是一个明确的项目决策,可以传 scope="/project/alpha/decisions"。如果你存储的是自由形式的 agent 输出,就可以省略 scope,让 LLM 自动判断。

使用场景示例

多项目团队:

  1. memory = Memory()
  2. # 每个项目都有自己的分支
  3. memory.remember("Using microservices architecture", scope="/project/alpha/architecture")
  4. memory.remember("GraphQL API for client apps", scope="/project/beta/api")
  5. # 跨所有项目 recall
  6. memory.recall("API design decisions")
  7. # 或在特定项目内 recall
  8. memory.recall("API design", scope="/project/beta")

每个 agent 拥有私有上下文,同时共享知识:

  1. memory = Memory()
  2. # Researcher 有私有发现
  3. researcher_memory = memory.scope("/agent/researcher")
  4. # Writer 可以读取自己的 scope 和共享公司知识
  5. writer_view = memory.slice(
  6. scopes=["/agent/writer", "/company/knowledge"],
  7. read_only=True,
  8. )

客户支持(按客户隔离上下文):

  1. memory = Memory()
  2. # 每个客户都有独立上下文
  3. memory.remember("Prefers email communication", scope="/customer/acme-corp")
  4. memory.remember("On enterprise plan, 50 seats", scope="/customer/acme-corp")
  5. # 共享产品文档可供所有 agents 访问
  6. memory.remember("Rate limit is 1000 req/min on enterprise plan", scope="/product/docs")

Memory Slices

什么是 Slice

MemorySlice 是跨多个、可能互不相连的 scopes 的一个视图。与 scope(限制在单棵子树)不同,slice 允许你同时从多个分支 recall。

什么时候使用 Slice,什么时候使用 Scope

  • Scope:当某个 agent 或代码块应被限制在单一子树中时使用。例如:一个只能看到 /agent/researcher 的 agent。
  • Slice:当你需要组合来自多个分支的上下文时使用。例如:一个同时读取自己 scope 和公司共享知识的 agent。

只读 Slice

最常见的模式:给 agent 多个分支的读取权限,但不允许它写入共享区域。

  1. memory = Memory()
  2. # Agent 可以从自己的 scope 和公司知识中 recall,
  3. # 但不能写入公司知识
  4. agent_view = memory.slice(
  5. scopes=["/agent/researcher", "/company/knowledge"],
  6. read_only=True,
  7. )
  8. matches = agent_view.recall("company security policies", limit=5)
  9. # 搜索 /agent/researcher 和 /company/knowledge,并合并排序结果
  10. agent_view.remember("new finding") # 抛出 PermissionError(只读)

可读写 Slice

当关闭只读时,你可以写入任意一个包含的 scope,但必须显式指定写入哪个 scope。

  1. view = memory.slice(scopes=["/team/alpha", "/team/beta"], read_only=False)
  2. # 写入时必须指定 scope
  3. view.remember("Cross-team decision", scope="/team/alpha", categories=["decisions"])

复合评分

Recall 结果通过以下三个信号的加权组合来排序:

  1. composite = semantic_weight * similarity + recency_weight * decay + importance_weight * importance

其中:

  • similarity = 向量索引中的 1 / (1 + distance)(范围 0 到 1)
  • decay = 0.5^(age_days / half_life_days) —— 指数衰减(当天为 1.0,半衰期时为 0.5)
  • importance = 记录的重要性分数(范围 0 到 1),在编码时设置

你可以直接在 Memory 构造函数中配置这些参数:

  1. # Sprint retrospective:偏重新近记忆,短半衰期
  2. memory = Memory(
  3. recency_weight=0.5,
  4. semantic_weight=0.3,
  5. importance_weight=0.2,
  6. recency_half_life_days=7,
  7. )
  8. # Architecture knowledge base:偏重重要记忆,长半衰期
  9. memory = Memory(
  10. recency_weight=0.1,
  11. semantic_weight=0.5,
  12. importance_weight=0.4,
  13. recency_half_life_days=180,
  14. )

每个 MemoryMatch 都包含一个 match_reasons 列表,方便你查看结果为何会排在那个位置(例如 ["semantic", "recency", "importance"])。

LLM 分析层

Memory 以三种方式使用 LLM:

  1. 保存时 —— 当你没有提供 scope、categories 或 importance 时,LLM 会分析内容,并建议 scope、categories、importance 以及 metadata(entities、dates、topics)。
  2. 检索时 —— 在 deep / auto recall 中,LLM 会分析查询(关键词、时间提示、建议 scopes、复杂度),以指导检索过程。
  3. 提取 memories —— extract_memories(content) 会将原始文本(例如 task 输出)拆分为离散的 memory 语句。Agents 在对每条语句调用 remember() 之前会先做这一步,以便存储的是原子事实,而不是一个很大的文本块。

所有分析都具备优雅降级能力 —— 详见 Failure Behavior

Memory Consolidation

当保存新内容时,编码流水线会自动检查存储中是否已有相似记录。如果相似度高于 consolidation_threshold(默认 0.85),LLM 会决定如何处理:

  • keep —— 现有记录仍然准确,不冗余。
  • update —— 现有记录应根据新信息更新(LLM 会提供合并后的内容)。
  • delete —— 现有记录已过时、被替代,或与新信息相冲突。
  • insert_new —— 是否也要将新内容作为一条独立记录插入。

这可以防止重复记录不断积累。例如,如果你连续三次保存 “CrewAI ensures reliable operation”,consolidation 会识别出重复,只保留一条记录。

批内去重

当使用 remember_many() 时,同一批次中的条目会先彼此比较,然后才进入存储层。如果两条内容的余弦相似度大于等于 batch_dedup_threshold(默认 0.98),后面那条会被静默丢弃。这样可以在不调用 LLM 的情况下,仅靠向量计算捕捉单批次中的完全重复或近似重复内容。

  1. # 最终只会存储 2 条记录(第三条与第一条几乎重复)
  2. memory.remember_many([
  3. "CrewAI supports complex workflows.",
  4. "Python is a great language.",
  5. "CrewAI supports complex workflows.", # 被批内去重丢弃
  6. ])

非阻塞保存

remember_many()非阻塞 的 —— 它会将编码流水线提交到后台线程并立即返回。这意味着 agent 可以继续执行下一个 task,而 memory 保存会在后台进行。

  1. # 立即返回 —— 保存会在后台执行
  2. memory.remember_many(["Fact A.", "Fact B.", "Fact C."])
  3. # recall() 在搜索前会自动等待待处理写入完成
  4. matches = memory.recall("facts") # 能看到全部 3 条记录

读屏障

每次 recall() 调用都会先自动调用 drain_writes(),确保查询总是能看到最新持久化的记录。这个过程是透明的 —— 你不需要手动考虑它。

Crew 结束时

当 crew 完成时,kickoff() 会在其 finally 块中清空所有待处理的 memory 保存,因此即使 crew 结束时后台保存仍在进行,也不会丢失任何保存操作。

独立使用场景

在脚本或 notebook 中,由于不存在 crew 生命周期,需要你显式调用 drain_writes()close()

  1. memory = Memory()
  2. memory.remember_many(["Fact A.", "Fact B."])
  3. # 方式 1:等待待处理保存完成
  4. memory.drain_writes()
  5. # 方式 2:清空待处理保存并关闭后台线程池
  6. memory.close()

Source 与隐私

每条 memory record 都可以携带一个 source 标签用于来源追踪,并通过 private 标志进行访问控制。

来源追踪

source 参数用于标识一条 memory 来自哪里:

  1. # 给 memories 打上来源标签
  2. memory.remember("User prefers dark mode", source="user:alice")
  3. memory.remember("System config updated", source="admin")
  4. memory.remember("Agent found a bug", source="agent:debugger")
  5. # 只 recall 来自指定 source 的 memories
  6. matches = memory.recall("user preferences", source="user:alice")

私有 Memories

私有 memory 只有在 source 匹配时才可被 recall 看到:

  1. # 存储私有 memory
  2. memory.remember("Alice's API key is sk-...", source="user:alice", private=True)
  3. # 这个 recall 能看到私有 memory(source 匹配)
  4. matches = memory.recall("API key", source="user:alice")
  5. # 这个 recall 看不到(source 不同)
  6. matches = memory.recall("API key", source="user:bob")
  7. # 管理员访问:无论 source 是什么,都能看到所有私有记录
  8. matches = memory.recall("API key", include_private=True)

这在多用户或企业部署中特别有用,因为不同用户的 memories 需要相互隔离。

RecallFlow(深度 Recall)

recall() 支持两种深度:

  • depth="shallow" —— 直接向量搜索 + 复合评分。速度快(约 200ms),不调用 LLM。
  • depth="deep"(默认) —— 运行一个多步骤 RecallFlow:查询分析、scope 选择、并行向量搜索、基于置信度的路由,以及当置信度较低时可选的递归探索。

智能跳过 LLM:即使是 deep 模式,短于 query_analysis_threshold(默认 200 个字符)的查询也会跳过 LLM 查询分析。像 “What database do we use?” 这样的短查询,本身已经是很好的搜索短语 —— LLM 分析不会带来太多额外价值。这样通常可为典型短查询节省约 1 到 3 秒。只有更长的查询(例如完整 task 描述)才会经过 LLM 提炼为更有针对性的子查询。

  1. # Shallow:纯向量搜索,不调用 LLM
  2. matches = memory.recall("What did we decide?", limit=10, depth="shallow")
  3. # Deep(默认):对长查询进行带 LLM 分析的智能检索
  4. matches = memory.recall(
  5. "Summarize all architecture decisions from this quarter",
  6. limit=10,
  7. depth="deep",
  8. )

控制 RecallFlow 路由器的置信度阈值也可以配置:

  1. memory = Memory(
  2. confidence_threshold_high=0.9, # 只有非常有把握时才直接 synthesize
  3. confidence_threshold_low=0.4, # 更积极地进行深入探索
  4. exploration_budget=2, # 最多允许 2 轮探索
  5. query_analysis_threshold=200, # 短于此长度的查询跳过 LLM
  6. )

Embedder 配置

Memory 需要一个 embedding 模型,将文本转换为向量,以支持语义搜索。你可以通过三种方式配置它。

直接传给 Memory

  1. from crewai import Memory
  2. # 作为配置字典传入
  3. memory = Memory(embedder={"provider": "openai", "config": {"model_name": "text-embedding-3-small"}})
  4. # 作为预构建 callable 传入
  5. from crewai.rag.embeddings.factory import build_embedder
  6. embedder = build_embedder({"provider": "ollama", "config": {"model_name": "mxbai-embed-large"}})
  7. memory = Memory(embedder=embedder)

通过 Crew 的 Embedder 配置

当使用 memory=True 时,crew 的 embedder 配置会自动传递进去:

  1. from crewai import Crew
  2. crew = Crew(
  3. agents=[...],
  4. tasks=[...],
  5. memory=True,
  6. embedder={"provider": "openai", "config": {"model_name": "text-embedding-3-small"}},
  7. )

Provider 示例

OpenAI(默认)

  1. memory = Memory(embedder={
  2. "provider": "openai",
  3. "config": {
  4. "model_name": "text-embedding-3-small",
  5. # "api_key": "sk-...", # 或设置 OPENAI_API_KEY 环境变量
  6. },
  7. })

Ollama(本地、私有)

  1. memory = Memory(embedder={
  2. "provider": "ollama",
  3. "config": {
  4. "model_name": "mxbai-embed-large",
  5. "url": "http://localhost:11434/api/embeddings",
  6. },
  7. })

Azure OpenAI

  1. memory = Memory(embedder={
  2. "provider": "azure",
  3. "config": {
  4. "deployment_id": "your-embedding-deployment",
  5. "api_key": "your-azure-api-key",
  6. "api_base": "https://your-resource.openai.azure.com",
  7. "api_version": "2024-02-01",
  8. },
  9. })

Google AI

  1. memory = Memory(embedder={
  2. "provider": "google-generativeai",
  3. "config": {
  4. "model_name": "gemini-embedding-001",
  5. # "api_key": "...", # 或设置 GOOGLE_API_KEY 环境变量
  6. },
  7. })

Google Vertex AI

  1. memory = Memory(embedder={
  2. "provider": "google-vertex",
  3. "config": {
  4. "model_name": "gemini-embedding-001",
  5. "project_id": "your-gcp-project-id",
  6. "location": "us-central1",
  7. },
  8. })

Cohere

  1. memory = Memory(embedder={
  2. "provider": "cohere",
  3. "config": {
  4. "model_name": "embed-english-v3.0",
  5. # "api_key": "...", # 或设置 COHERE_API_KEY 环境变量
  6. },
  7. })

VoyageAI

  1. memory = Memory(embedder={
  2. "provider": "voyageai",
  3. "config": {
  4. "model": "voyage-3",
  5. # "api_key": "...", # 或设置 VOYAGE_API_KEY 环境变量
  6. },
  7. })

AWS Bedrock

  1. memory = Memory(embedder={
  2. "provider": "amazon-bedrock",
  3. "config": {
  4. "model_name": "amazon.titan-embed-text-v1",
  5. # 使用默认 AWS 凭据(boto3 session)
  6. },
  7. })

Hugging Face

  1. memory = Memory(embedder={
  2. "provider": "huggingface",
  3. "config": {
  4. "model_name": "sentence-transformers/all-MiniLM-L6-v2",
  5. },
  6. })

Jina

  1. memory = Memory(embedder={
  2. "provider": "jina",
  3. "config": {
  4. "model_name": "jina-embeddings-v2-base-en",
  5. # "api_key": "...", # 或设置 JINA_API_KEY 环境变量
  6. },
  7. })

IBM WatsonX

  1. memory = Memory(embedder={
  2. "provider": "watsonx",
  3. "config": {
  4. "model_id": "ibm/slate-30m-english-rtrvr",
  5. "api_key": "your-watsonx-api-key",
  6. "project_id": "your-project-id",
  7. "url": "https://us-south.ml.cloud.ibm.com",
  8. },
  9. })

自定义 Embedder

  1. # 传入任意 callable,它接收一个字符串列表并返回一个向量列表
  2. def my_embedder(texts: list[str]) -> list[list[float]]:
  3. # 你的 embedding 逻辑
  4. return [[0.1, 0.2, ...] for _ in texts]
  5. memory = Memory(embedder=my_embedder)

Provider 参考表

Provider Key 常见模型 说明
OpenAI openai text-embedding-3-small 默认值。设置 OPENAI_API_KEY
Ollama ollama mxbai-embed-large 本地运行,无需 API key。
Azure OpenAI azure text-embedding-ada-002 需要 deployment_id
Google AI google-generativeai gemini-embedding-001 设置 GOOGLE_API_KEY
Google Vertex google-vertex gemini-embedding-001 需要 project_id
Cohere cohere embed-english-v3.0 多语言支持较强。
VoyageAI voyageai voyage-3 针对检索优化。
AWS Bedrock amazon-bedrock amazon.titan-embed-text-v1 使用 boto3 凭据。
Hugging Face huggingface all-MiniLM-L6-v2 本地 sentence-transformers。
Jina jina jina-embeddings-v2-base-en 设置 JINA_API_KEY
IBM WatsonX watsonx ibm/slate-30m-english-rtrvr 需要 project_id
Sentence Transformer sentence-transformer all-MiniLM-L6-v2 本地运行,无需 API key。
Custom custom 需要 embedding_callable

LLM 配置

Memory 会使用一个 LLM 进行保存分析(推断 scope / categories / importance)、consolidation 决策,以及深度 recall 的查询分析。你可以配置要使用的模型。

  1. from crewai import Memory, LLM
  2. # 默认:gpt-4o-mini
  3. memory = Memory()
  4. # 使用不同的 OpenAI 模型
  5. memory = Memory(llm="gpt-4o")
  6. # 使用 Anthropic
  7. memory = Memory(llm="anthropic/claude-3-haiku-20240307")
  8. # 使用 Ollama 实现完全本地 / 私有分析
  9. memory = Memory(llm="ollama/llama3.2")
  10. # 使用 Google Gemini
  11. memory = Memory(llm="gemini/gemini-2.0-flash")
  12. # 传入一个带自定义设置的预配置 LLM 实例
  13. llm = LLM(model="gpt-4o", temperature=0)
  14. memory = Memory(llm=llm)

LLM 是 惰性初始化 的 —— 只有在第一次真正需要时才会创建。这意味着即使没有设置 API key,Memory() 在构造时也不会失败。只有当 LLM 真正被调用时(例如保存时没有显式提供 scope / categories,或者执行深度 recall)才会暴露错误。

如果你希望完全离线 / 私有运行,可以同时为 LLM 和 embedder 使用本地模型:

  1. memory = Memory(
  2. llm="ollama/llama3.2",
  3. embedder={"provider": "ollama", "config": {"model_name": "mxbai-embed-large"}},
  4. )

存储后端

  • 默认值:LanceDB,存储在 ./.crewai/memory 下(如果设置了 $CREWAI_STORAGE_DIR 环境变量,则存到 $CREWAI_STORAGE_DIR/memory,或者使用你通过 storage="path/to/dir" 传入的路径)。
  • 自定义后端:实现 StorageBackend 协议(见 crewai.memory.storage.backend),然后通过 Memory(storage=your_backend) 传入一个实例。

发现与查看

你可以查看 scope 层级、categories 和 records:

  1. memory.tree() # 格式化的 scope 树和记录数量
  2. memory.tree("/project", max_depth=2) # 子树视图
  3. memory.info("/project") # ScopeInfo:record_count、categories、oldest/newest
  4. memory.list_scopes("/") # 直接子 scope
  5. memory.list_categories() # category 名称与数量
  6. memory.list_records(scope="/project/alpha", limit=20) # 某个 scope 下的记录,按时间从新到旧

失败行为

如果 LLM 在分析过程中失败(网络错误、速率限制、响应无效),memory 会优雅降级:

  • 保存分析 —— 会记录一个 warning,但 memory 仍会被存储,默认使用 scope /、空 categories,以及 importance 0.5
  • 提取 memories —— 整段内容会作为单条 memory 存储,确保没有内容丢失。
  • 查询分析 —— recall 会退回到简单的 scope 选择和向量搜索,因此你仍然能得到结果。

这些分析失败不会抛异常;只有存储层或 embedder 层失败才会抛出异常。

隐私说明

Memory 内容会被发送给配置的 LLM 进行分析(保存时推断 scope / categories / importance,检索时做查询分析,以及可选的深度 recall)。如果数据敏感,请使用本地 LLM(例如 Ollama),或者确保你的 provider 满足合规要求。

Memory Events

所有 memory 操作都会发出事件,并带有 source_type="unified_memory"。你可以监听耗时、错误和内容相关事件。

Event 描述 关键属性
MemoryQueryStartedEvent 查询开始 query, limit
MemoryQueryCompletedEvent 查询成功 query, results, query_time_ms
MemoryQueryFailedEvent 查询失败 query, error
MemorySaveStartedEvent 保存开始 value, metadata
MemorySaveCompletedEvent 保存成功 value, save_time_ms
MemorySaveFailedEvent 保存失败 value, error
MemoryRetrievalStartedEvent Agent 检索开始 task_id
MemoryRetrievalCompletedEvent Agent 检索完成 task_id, memory_content, retrieval_time_ms

示例:监控查询耗时:

  1. from crewai.events import BaseEventListener, MemoryQueryCompletedEvent
  2. class MemoryMonitor(BaseEventListener):
  3. def setup_listeners(self, crewai_event_bus):
  4. @crewai_event_bus.on(MemoryQueryCompletedEvent)
  5. def on_done(source, event):
  6. if getattr(event, "source_type", None) == "unified_memory":
  7. print(f"Query '{event.query}' completed in {event.query_time_ms:.0f}ms")

故障排查

Memory 没有持久化?

  • 确保存储路径可写(默认是 ./.crewai/memory)。你可以通过 storage="./your_path" 使用其他目录,或者设置 CREWAI_STORAGE_DIR 环境变量。
  • 如果是在 crew 中使用,请确认设置了 memory=Truememory=Memory(...)

Recall 很慢?

  • 对常规 agent 上下文使用 depth="shallow"。只在复杂查询中使用 depth="deep"
  • 提高 query_analysis_threshold,让更多查询跳过 LLM 分析。

日志中出现 LLM 分析错误?

  • Memory 仍会以安全默认值保存 / recall。如果你想恢复完整的 LLM 分析,请检查 API key、速率限制和模型可用性。

日志中出现后台保存错误?

  • Memory 保存运行在后台线程中。错误会以 MemorySaveFailedEvent 发出,但不会导致 agent 崩溃。请查看日志中的根本原因(通常是 LLM 或 embedder 连接问题)。

并发写入冲突?

  • LanceDB 操作会通过共享锁串行化,并在冲突时自动重试。这能处理多个 Memory 实例同时指向同一数据库的情况(例如 agent memory + crew memory)。无需手动处理。

在终端中浏览 memory:

  1. crewai memory # 打开 TUI 浏览器
  2. crewai memory --storage-path ./my_memory # 指向指定目录

重置 memory(例如用于测试):

  1. crew.reset_memories(command_type="memory") # 重置统一 memory
  2. # 或在 Memory 实例上:
  3. memory.reset() # 所有 scopes
  4. memory.reset(scope="/project/old") # 仅该子树

配置参考

所有配置都通过 Memory(...) 的关键字参数传入。每个参数都有合理默认值。

参数 默认值 描述
llm "gpt-4o-mini" 用于分析的 LLM(模型名或 BaseLLM 实例)。
storage "lancedb" 存储后端("lancedb"、路径字符串,或 StorageBackend 实例)。
embedder None(默认 OpenAI) Embedder(配置字典、callable,或 None 表示使用默认 OpenAI)。
recency_weight 0.3 复合评分中新近度的权重。
semantic_weight 0.5 复合评分中语义相似度的权重。
importance_weight 0.2 复合评分中重要性的权重。
recency_half_life_days 30 新近度分数减半所需天数(指数衰减)。
consolidation_threshold 0.85 保存时触发 consolidation 的相似度阈值。设为 1.0 可禁用。
consolidation_limit 5 consolidation 时最多比较的现有记录数。
default_importance 0.5 未提供 importance 且跳过 LLM 分析时使用的默认值。
batch_dedup_threshold 0.98 remember_many() 批内近似重复去重的余弦相似度阈值。
confidence_threshold_high 0.8 Recall 置信度高于此值时直接返回结果。
confidence_threshold_low 0.5 Recall 置信度低于此值时触发更深入探索。
complex_query_threshold 0.7 对复杂查询而言,低于此置信度时进行更深探索。
exploration_budget 1 深度 recall 中 LLM 驱动探索的轮数。
query_analysis_threshold 200 深度 recall 时,短于此长度(字符数)的查询会跳过 LLM 分析。

使用 Mintlify 构建。