CrewAI는 통합 메모리 시스템을 제공합니다 — 단기, 장기, 엔터티, 외부 메모리 유형을 하나의 지능형 API인 단일 Memory 클래스로 대체합니다. 메모리는 저장 시 LLM을 사용하여 콘텐츠를 분석하고(범위, 카테고리, 중요도 추론) 의미 유사도, 최신성, 중요도를 혼합한 복합 점수로 적응형 깊이 recall을 지원합니다.메모리를 네 가지 방법으로 사용할 수 있습니다: 독립 실행(스크립트, 노트북), Crew와 함께, 에이전트와 함께, 또는 Flow 내부에서.
from crewai import Memorymemory = Memory()# 저장 -- LLM이 scope, categories, importance를 추론memory.remember("We decided to use PostgreSQL for the user database.")# 검색 -- 복합 점수(의미 + 최신성 + 중요도)로 결과 순위 매기기matches = memory.recall("What database did we choose?")for m in matches: print(f"[{m.score:.2f}] {m.record.content}")# 빠르게 변하는 프로젝트를 위한 점수 조정memory = Memory(recency_weight=0.5, recency_half_life_days=7)# 삭제memory.forget(scope="/project/old")# 자동 구성된 scope 트리 탐색print(memory.tree())print(memory.info("/"))
스크립트, 노트북, CLI 도구 또는 독립 지식 베이스로 메모리를 사용합니다 — 에이전트나 crew가 필요하지 않습니다.
from crewai import Memorymemory = Memory()# 지식 구축memory.remember("The API rate limit is 1000 requests per minute.")memory.remember("Our staging environment uses port 8080.")memory.remember("The team agreed to use feature flags for all new releases.")# 나중에 필요한 것을 recallmatches = memory.recall("What are our API limits?", limit=5)for m in matches: print(f"[{m.score:.2f}] {m.record.content}")# 긴 텍스트에서 원자적 사실 추출raw = """Meeting notes: We decided to migrate from MySQL to PostgreSQLnext quarter. The budget is $50k. Sarah will lead the migration."""facts = memory.extract_memories(raw)# ["Migration from MySQL to PostgreSQL planned for next quarter",# "Database migration budget is $50k",# "Sarah will lead the database migration"]for fact in facts: memory.remember(fact)
기본 설정은 memory=True를 전달하고, 사용자 정의 동작은 설정된 Memory 인스턴스를 전달합니다.
from crewai import Crew, Agent, Task, Process, Memory# 옵션 1: 기본 메모리crew = Crew( agents=[researcher, writer], tasks=[research_task, writing_task], process=Process.sequential, memory=True, verbose=True,)# 옵션 2: 조정된 점수가 있는 사용자 정의 메모리memory = Memory( recency_weight=0.4, semantic_weight=0.4, importance_weight=0.2, recency_half_life_days=14,)crew = Crew( agents=[researcher, writer], tasks=[research_task, writing_task], memory=memory,)
memory=True일 때 crew는 기본 Memory()를 생성하고 crew의 embedder 설정을 자동으로 전달합니다. crew의 모든 에이전트는 자체 메모리가 없는 한 crew의 메모리를 공유합니다.각 작업 후 crew는 자동으로 작업 출력에서 개별 사실을 추출하여 저장합니다. 각 작업 전에 에이전트는 메모리에서 관련 컨텍스트를 recall하여 작업 프롬프트에 주입합니다.
모든 Flow에는 내장 메모리가 있습니다. 모든 flow 메서드 내부에서 self.remember(), self.recall(), self.extract_memories()를 사용하세요.
from crewai.flow.flow import Flow, listen, startclass ResearchFlow(Flow): @start() def gather_data(self): findings = "PostgreSQL handles 10k concurrent connections. MySQL caps at 5k." self.remember(findings, scope="/research/databases") return findings @listen(gather_data) def write_report(self, findings): # 컨텍스트를 제공하기 위해 과거 연구 recall past = self.recall("database performance benchmarks") context = "\n".join(f"- {m.record.content}" for m in past) return f"Report:\nNew findings: {findings}\nPrevious context:\n{context}"
remember() 호출 시 scope를 지정하지 않으면 LLM이 콘텐츠와 기존 scope 트리를 분석한 후 최적의 배치를 제안합니다. 적합한 기존 scope가 없으면 새로 생성합니다. 시간이 지남에 따라 scope 트리는 콘텐츠 자체에서 유기적으로 성장합니다 — 미리 스키마를 설계할 필요가 없습니다.
memory = Memory()# LLM이 콘텐츠에서 scope 추론memory.remember("We chose PostgreSQL for the user database.")# -> /project/decisions 또는 /engineering/database 아래에 배치될 수 있음# scope를 명시적으로 지정할 수도 있음memory.remember("Sprint velocity is 42 points", scope="/team/metrics")
MemoryScope는 모든 연산을 트리의 한 분기로 제한합니다. 이를 사용하는 에이전트나 코드는 해당 하위 트리 내에서만 보고 쓸 수 있습니다.
memory = Memory()# 특정 에이전트를 위한 scope 생성agent_memory = memory.scope("/agent/researcher")# 모든 것이 /agent/researcher 기준으로 상대적agent_memory.remember("Found three relevant papers on LLM memory.")# -> /agent/researcher 아래에 저장agent_memory.recall("relevant papers")# -> /agent/researcher 아래에서만 검색# subscope로 더 좁히기project_memory = agent_memory.subscope("project-alpha")# -> /agent/researcher/project-alpha
memory = Memory()# 각 프로젝트가 자체 분기를 가짐memory.remember("Using microservices architecture", scope="/project/alpha/architecture")memory.remember("GraphQL API for client apps", scope="/project/beta/api")# 모든 프로젝트에서 recallmemory.recall("API design decisions")# 특정 프로젝트 내에서만memory.recall("API design", scope="/project/beta")
공유 지식과 에이전트별 비공개 컨텍스트:
memory = Memory()# 연구원은 비공개 발견을 가짐researcher_memory = memory.scope("/agent/researcher")# 작성자는 자체 scope와 공유 회사 지식에서 읽을 수 있음writer_view = memory.slice( scopes=["/agent/writer", "/company/knowledge"], read_only=True,)
고객 지원 (고객별 컨텍스트):
memory = Memory()# 각 고객이 격리된 컨텍스트를 가짐memory.remember("Prefers email communication", scope="/customer/acme-corp")memory.remember("On enterprise plan, 50 seats", scope="/customer/acme-corp")# 공유 제품 문서는 모든 에이전트가 접근 가능memory.remember("Rate limit is 1000 req/min on enterprise plan", scope="/product/docs")
가장 일반적인 패턴: 에이전트에게 여러 분기에 대한 읽기 액세스를 제공하되 공유 영역에 쓰지 못하게 합니다.
memory = Memory()# 에이전트는 자체 scope와 회사 지식에서 recall 가능,# 하지만 회사 지식에 쓸 수 없음agent_view = memory.slice( scopes=["/agent/researcher", "/company/knowledge"], read_only=True,)matches = agent_view.recall("company security policies", limit=5)# /agent/researcher와 /company/knowledge 모두에서 검색, 결과 병합 및 순위 매기기agent_view.remember("new finding") # PermissionError 발생 (읽기 전용)
읽기 전용이 비활성화되면 포함된 scope 중 어디에든 쓸 수 있지만, 어떤 scope인지 명시적으로 지정해야 합니다.
view = memory.slice(scopes=["/team/alpha", "/team/beta"], read_only=False)# 쓸 때 scope를 반드시 지정view.remember("Cross-team decision", scope="/team/alpha", categories=["decisions"])
# 스프린트 회고: 최근 메모리 선호, 짧은 반감기memory = Memory( recency_weight=0.5, semantic_weight=0.3, importance_weight=0.2, recency_half_life_days=7,)# 아키텍처 지식 베이스: 중요한 메모리 선호, 긴 반감기memory = Memory( recency_weight=0.1, semantic_weight=0.5, importance_weight=0.4, recency_half_life_days=180,)
각 MemoryMatch에는 결과가 해당 위치에 순위된 이유를 볼 수 있는 match_reasons 목록이 포함됩니다 (예: ["semantic", "recency", "importance"]).
remember_many()를 사용할 때 동일 배치 내의 항목은 스토리지에 도달하기 전에 서로 비교됩니다. 두 항목의 코사인 유사도가 batch_dedup_threshold(기본값 0.98) 이상이면 나중 항목이 자동으로 삭제됩니다. 이는 LLM 호출 없이 순수 벡터 연산으로 단일 배치 내의 정확하거나 거의 정확한 중복을 잡아냅니다.
# 2개의 레코드만 저장됨 (세 번째는 첫 번째의 거의 중복)memory.remember_many([ "CrewAI supports complex workflows.", "Python is a great language.", "CrewAI supports complex workflows.", # 배치 내 중복 제거로 삭제])
remember_many()는 비차단입니다 — 인코딩 파이프라인을 백그라운드 스레드에 제출하고 즉시 반환합니다. 이는 메모리가 저장되는 동안 에이전트가 다음 작업을 계속할 수 있음을 의미합니다.
# 즉시 반환 -- 저장은 백그라운드에서 발생memory.remember_many(["Fact A.", "Fact B.", "Fact C."])# recall()은 검색 전에 보류 중인 저장을 자동으로 대기matches = memory.recall("facts") # 3개 레코드 모두 확인 가능
# 비공개 메모리 저장memory.remember("Alice's API key is sk-...", source="user:alice", private=True)# 이 recall은 비공개 메모리를 볼 수 있음 (source 일치)matches = memory.recall("API key", source="user:alice")# 이 recall은 볼 수 없음 (다른 source)matches = memory.recall("API key", source="user:bob")# 관리자 액세스: source에 관계없이 모든 비공개 레코드 보기matches = memory.recall("API key", include_private=True)
이는 서로 다른 사용자의 메모리가 격리되어야 하는 다중 사용자 또는 엔터프라이즈 배포에서 특히 유용합니다.
depth="shallow" — 복합 점수를 사용한 직접 벡터 검색. 빠름 (~200ms), LLM 호출 없음.
depth="deep" (기본값) — 다단계 RecallFlow 실행: 쿼리 분석, scope 선택, 병렬 벡터 검색, 신뢰도 기반 라우팅, 신뢰도가 낮을 때 선택적 재귀 탐색.
스마트 LLM 건너뛰기: query_analysis_threshold(기본값 200자)보다 짧은 쿼리는 deep 모드에서도 LLM 쿼리 분석을 완전히 건너뜁니다. “What database do we use?”와 같은 짧은 쿼리는 이미 좋은 검색 구문이므로 LLM 분석이 큰 가치를 더하지 않습니다. 이를 통해 일반적인 짧은 쿼리에서 recall당 ~1-3초를 절약합니다. 긴 쿼리(예: 전체 작업 설명)만 대상 하위 쿼리로의 LLM 분석을 거칩니다.
# Shallow: 순수 벡터 검색, LLM 없음matches = memory.recall("What did we decide?", limit=10, depth="shallow")# Deep (기본값): 긴 쿼리에 대한 LLM 분석을 포함한 지능형 검색matches = memory.recall( "Summarize all architecture decisions from this quarter", limit=10, depth="deep",)
RecallFlow 라우터를 제어하는 신뢰도 임계값은 설정 가능합니다:
memory = Memory( confidence_threshold_high=0.9, # 매우 확신할 때만 합성 confidence_threshold_low=0.4, # 더 적극적으로 깊이 탐색 exploration_budget=2, # 최대 2라운드 탐색 허용 query_analysis_threshold=200, # 이보다 짧은 쿼리는 LLM 건너뛰기)
memory = Memory(embedder={ "provider": "openai", "config": { "model_name": "text-embedding-3-small", # "api_key": "sk-...", # 또는 OPENAI_API_KEY 환경 변수 설정 },})
memory = Memory(embedder={ "provider": "google-generativeai", "config": { "model_name": "gemini-embedding-001", # "api_key": "...", # 또는 GOOGLE_API_KEY 환경 변수 설정 },})
memory = Memory(embedder={ "provider": "jina", "config": { "model_name": "jina-embeddings-v2-base-en", # "api_key": "...", # 또는 JINA_API_KEY 환경 변수 설정 },})
# 문자열 목록을 받아 벡터 목록을 반환하는 callable 전달def my_embedder(texts: list[str]) -> list[list[float]]: # 임베딩 로직 return [[0.1, 0.2, ...] for _ in texts]memory = Memory(embedder=my_embedder)
메모리는 저장 분석(scope, categories, importance 추론), 통합 결정, 딥 recall 쿼리 분석에 LLM을 사용합니다. 사용할 모델을 설정할 수 있습니다.
from crewai import Memory, LLM# 기본값: gpt-4o-minimemory = Memory()# 다른 OpenAI 모델 사용memory = Memory(llm="gpt-4o")# Anthropic 사용memory = Memory(llm="anthropic/claude-3-haiku-20240307")# 완전한 로컬/비공개 분석을 위해 Ollama 사용memory = Memory(llm="ollama/llama3.2")# Google Gemini 사용memory = Memory(llm="gemini/gemini-2.0-flash")# 사용자 정의 설정이 있는 사전 구성된 LLM 인스턴스 전달llm = LLM(model="gpt-4o", temperature=0)memory = Memory(llm=llm)
LLM은 지연 초기화됩니다 — 처음 필요할 때만 생성됩니다. 즉, API 키가 설정되지 않아도 Memory() 생성 시에는 실패하지 않습니다. 오류는 LLM이 실제로 호출될 때만 발생합니다(예: 명시적 scope/categories 없이 저장할 때 또는 딥 recall 중).완전한 오프라인/비공개 운영을 위해 LLM과 embedder 모두에 로컬 모델을 사용하세요:
메모리 콘텐츠는 분석을 위해 설정된 LLM으로 전송됩니다(저장 시 scope/categories/importance, 쿼리 분석 및 선택적 딥 recall). 민감한 데이터의 경우 로컬 LLM(예: Ollama)을 사용하거나 제공자가 규정 요구 사항을 충족하는지 확인하세요.