New Memory Improvements (#4484)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled

* better DevEx

* Refactor: Update supported native providers and enhance memory handling

- Removed "groq" and "meta" from the list of supported native providers in `llm.py`.
- Added a safeguard in `flow.py` to ensure all background memory saves complete before returning.
- Improved error handling in `unified_memory.py` to prevent exceptions during shutdown, ensuring smoother memory operations and event bus interactions.

* Enhance Memory System with Consolidation and Learning Features

- Introduced memory consolidation mechanisms to prevent duplicate records during content saving, utilizing similarity checks and LLM decision-making.
- Implemented non-blocking save operations in the memory system, allowing agents to continue tasks while memory is being saved.
- Added support for learning from human feedback, enabling the system to distill lessons from past corrections and improve future outputs.
- Updated documentation to reflect new features and usage examples for memory consolidation and HITL learning.

* Enhance cyclic flow handling for or_() listeners

- Updated the Flow class to ensure that all fired or_() listeners are cleared between cycle iterations, allowing them to fire again in subsequent cycles. This change addresses a bug where listeners remained suppressed across iterations.
- Added regression tests to verify that or_() listeners fire correctly on every iteration in cyclic flows, ensuring expected behavior in complex routing scenarios.
This commit is contained in:
João Moura
2026-02-14 23:57:56 -08:00
committed by GitHub
parent 18d266c8e7
commit 09e9229efc
10 changed files with 883 additions and 74 deletions

View File

@@ -7,9 +7,9 @@ mode: "wide"
## 개요
CrewAI는 **통합 메모리 시스템**을 제공합니다 -- 단기, 장기, 엔터티, 외부 메모리 유형을 하나의 지능형 API인 단일 `Memory` 클래스로 대체합니다. 메모리는 저장 시 LLM을 사용하여 콘텐츠를 분석하고(범위, 카테고리, 중요도 추론) 의미 유사도, 최신성, 중요도를 합한 복합 점수로 적응형 깊이 recall을 지원합니다.
CrewAI는 **통합 메모리 시스템**을 제공합니다 -- 단기, 장기, 엔터티, 외부 메모리 유형을 하나의 지능형 API인 단일 `Memory` 클래스로 대체합니다. 메모리는 저장 시 LLM을 사용하여 콘텐츠를 분석하고(범위, 카테고리, 중요도 추론) 의미 유사도, 최신성, 중요도를 합한 복합 점수로 적응형 깊이 recall을 지원합니다.
메모리를 네 가지 방법으로 사용할 수 있습니다: **독립 실행** (스크립트, 노트북), **Crew와 함께**, **에이전트와 함께**, 또는 **Flow 내부에서**.
메모리를 네 가지 방법으로 사용할 수 있습니다: **독립 실행**(스크립트, 노트북), **Crew와 함께**, **에이전트와 함께**, 또는 **Flow 내부에서**.
## 빠른 시작
@@ -21,7 +21,7 @@ memory = 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}")
@@ -135,7 +135,7 @@ writer = Agent(
### Flow와 함께 사용
모든 Flow에는 내장 메모리가 있습니다. 모든 flow 메서드 내부에서 `self.remember()`, `self.recall()`, `self.extract_memories()`를 사용합니다.
모든 Flow에는 내장 메모리가 있습니다. 모든 flow 메서드 내부에서 `self.remember()`, `self.recall()`, `self.extract_memories()`를 사용하세요.
```python
from crewai.flow.flow import Flow, listen, start
@@ -149,7 +149,7 @@ class ResearchFlow(Flow):
@listen(gather_data)
def write_report(self, findings):
# 컨텍스트를 위해 과거 연구 recall
# 컨텍스트를 제공하기 위해 과거 연구 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}"
@@ -377,25 +377,127 @@ memory = Memory(
2. **recall 시** -- deep/auto recall의 경우 LLM이 쿼리(키워드, 시간 힌트, 제안 scope, 복잡도)를 분석하여 검색을 안내합니다.
3. **메모리 추출** -- `extract_memories(content)`는 원시 텍스트(예: 작업 출력)를 개별 메모리 문장으로 나눕니다. 에이전트는 각 문장에 `remember()`를 호출하기 전에 이를 사용하여 하나의 큰 블록 대신 원자적 사실이 저장되도록 합니다.
모든 분석은 LLM 장애 시 우아하게 저하됩니다 -- [오류 시 동작](#오류-시-동작)을 참조하세요.
모든 분석은 LLM 실패 시 우아하게 저하됩니다 -- [오류 시 동작](#오류-시-동작)을 참조하세요.
## 메모리 통합
새 콘텐츠를 저장할 때 인코딩 파이프라인은 자동으로 스토리지에서 유사한 기존 레코드를 확인합니다. 유사도가 `consolidation_threshold`(기본값 0.85) 이상이면 LLM이 처리 방법을 결정합니다:
- **keep** -- 기존 레코드가 여전히 정확하고 중복이 아닙니다.
- **update** -- 기존 레코드를 새 정보로 업데이트해야 합니다 (LLM이 병합된 콘텐츠를 제공).
- **delete** -- 기존 레코드가 오래되었거나, 대체되었거나, 모순됩니다.
- **insert_new** -- 새 콘텐츠를 별도의 레코드로 삽입해야 하는지 여부.
이를 통해 중복이 축적되는 것을 방지합니다. 예를 들어, "CrewAI ensures reliable operation"을 세 번 저장하면 통합이 중복을 인식하고 하나의 레코드만 유지합니다.
### 배치 내 중복 제거
`remember_many()`를 사용할 때 동일 배치 내의 항목은 스토리지에 도달하기 전에 서로 비교됩니다. 두 항목의 코사인 유사도가 `batch_dedup_threshold`(기본값 0.98) 이상이면 나중 항목이 자동으로 삭제됩니다. 이는 LLM 호출 없이 순수 벡터 연산으로 단일 배치 내의 정확하거나 거의 정확한 중복을 잡아냅니다.
```python
# 2개의 레코드만 저장됨 (세 번째는 첫 번째의 거의 중복)
memory.remember_many([
"CrewAI supports complex workflows.",
"Python is a great language.",
"CrewAI supports complex workflows.", # 배치 내 중복 제거로 삭제
])
```
## 비차단 저장
`remember_many()`는 **비차단**입니다 -- 인코딩 파이프라인을 백그라운드 스레드에 제출하고 즉시 반환합니다. 이는 메모리가 저장되는 동안 에이전트가 다음 작업을 계속할 수 있음을 의미합니다.
```python
# 즉시 반환 -- 저장은 백그라운드에서 발생
memory.remember_many(["Fact A.", "Fact B.", "Fact C."])
# recall()은 검색 전에 보류 중인 저장을 자동으로 대기
matches = memory.recall("facts") # 3개 레코드 모두 확인 가능
```
### 읽기 배리어
모든 `recall()` 호출은 검색 전에 자동으로 `drain_writes()`를 호출하여 쿼리가 항상 최신 저장된 레코드를 볼 수 있도록 합니다. 이는 투명하게 작동하므로 별도로 신경 쓸 필요가 없습니다.
### Crew 종료
crew가 완료되면 `kickoff()`는 `finally` 블록에서 보류 중인 모든 메모리 저장을 드레인하므로, 백그라운드 저장이 진행 중인 상태에서 crew가 완료되더라도 저장이 손실되지 않습니다.
### 독립 실행 사용
crew 수명 주기가 없는 스크립트나 노트북에서는 `drain_writes()` 또는 `close()`를 명시적으로 호출하세요:
```python
memory = Memory()
memory.remember_many(["Fact A.", "Fact B."])
# 옵션 1: 보류 중인 저장 대기
memory.drain_writes()
# 옵션 2: 드레인 후 백그라운드 풀 종료
memory.close()
```
## 출처 및 개인정보
모든 메모리 레코드는 출처 추적을 위한 `source` 태그와 접근 제어를 위한 `private` 플래그를 가질 수 있습니다.
### 출처 추적
`source` 매개변수는 메모리의 출처를 식별합니다:
```python
# 메모리에 출처 태그 지정
memory.remember("User prefers dark mode", source="user:alice")
memory.remember("System config updated", source="admin")
memory.remember("Agent found a bug", source="agent:debugger")
# 특정 출처의 메모리만 recall
matches = memory.recall("user preferences", source="user:alice")
```
### 비공개 메모리
비공개 메모리는 `source`가 일치할 때만 recall에서 볼 수 있습니다:
```python
# 비공개 메모리 저장
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)
```
이는 서로 다른 사용자의 메모리가 격리되어야 하는 다중 사용자 또는 엔터프라이즈 배포에서 특히 유용합니다.
## RecallFlow (딥 Recall)
`recall()`은 가지 깊이를 지원합니다:
`recall()`은 가지 깊이를 지원합니다:
- **`depth="shallow"`** -- 복합 점수를 사용한 직접 벡터 검색. 빠름; 에이전트가 컨텍스트를 로드할 때 기본 사용.
- **`depth="deep"` 또는 `depth="auto"`** -- 다단계 RecallFlow 실행: 쿼리 분석, scope 선택, 벡터 검색, 신뢰도 기반 라우팅, 신뢰도가 낮을 때 선택적 재귀 탐색.
- **`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 분석을 거칩니다.
```python
# 빠른 경로 (에이전트 작업 컨텍스트 기본값)
# 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="auto",
depth="deep",
)
```
@@ -406,6 +508,7 @@ memory = Memory(
confidence_threshold_high=0.9, # 매우 확신할 때만 합성
confidence_threshold_low=0.4, # 더 적극적으로 깊이 탐색
exploration_budget=2, # 최대 2라운드 탐색 허용
query_analysis_threshold=200, # 이보다 짧은 쿼리는 LLM 건너뛰기
)
```
@@ -613,10 +716,49 @@ memory = Memory(embedder=my_embedder)
| Custom | `custom` | -- | `embedding_callable` 필요. |
## LLM 설정
메모리는 저장 분석(scope, categories, importance 추론), 통합 결정, 딥 recall 쿼리 분석에 LLM을 사용합니다. 사용할 모델을 설정할 수 있습니다.
```python
from crewai import Memory, LLM
# 기본값: gpt-4o-mini
memory = 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 모두에 로컬 모델을 사용하세요:
```python
memory = Memory(
llm="ollama/llama3.2",
embedder={"provider": "ollama", "config": {"model_name": "mxbai-embed-large"}},
)
```
## 스토리지 백엔드
- **기본값**: LanceDB, `./.crewai/memory` 아래에 저장 (또는 환경 변수 `$CREWAI_STORAGE_DIR/memory`가 설정된 경우, 또는 `storage="path/to/dir"`로 전달한 경로).
- **사용자 정의 백엔드**: `StorageBackend` 프로토콜 구현 (`crewai.memory.storage.backend` 참조) `Memory(storage=your_backend)`에 인스턴스 전달.
- **기본값**: LanceDB, `./.crewai/memory` 아래에 저장 (또는 환경 변수가 설정된 경우 `$CREWAI_STORAGE_DIR/memory`, 또는 `storage="path/to/dir"`로 전달한 경로).
- **사용자 정의 백엔드**: `StorageBackend` 프로토콜 구현하고(`crewai.memory.storage.backend` 참조) `Memory(storage=your_backend)`에 인스턴스 전달합니다.
## 탐색(Discovery)
@@ -685,11 +827,18 @@ class MemoryMonitor(BaseEventListener):
- crew 사용 시 `memory=True` 또는 `memory=Memory(...)`가 설정되었는지 확인하세요.
**recall이 느린가요?**
- 일상적인 에이전트 컨텍스트에는 `depth="shallow"`를 사용하세요. 복잡한 쿼리에만 `depth="auto"` 또는 `"deep"`을 사용하세요.
- 일상적인 에이전트 컨텍스트에는 `depth="shallow"`를 사용하세요. 복잡한 쿼리에만 `depth="deep"`을 사용하세요.
- 더 많은 쿼리에서 LLM 분석을 건너뛰려면 `query_analysis_threshold`를 높이세요.
**로그에 LLM 분석 오류가 있나요?**
- 메모리는 안전한 기본값으로 계속 저장/recall합니다. 전체 LLM 분석을 원하면 API 키, 속도 제한, 모델 가용성을 확인하세요.
**로그에 백그라운드 저장 오류가 있나요?**
- 메모리 저장은 백그라운드 스레드에서 실행됩니다. 오류는 `MemorySaveFailedEvent`로 발생하지만 에이전트를 중단시키지 않습니다. 근본 원인(보통 LLM 또는 embedder 연결 문제)은 로그를 확인하세요.
**동시 쓰기 충돌이 있나요?**
- LanceDB 연산은 공유 잠금으로 직렬화되며 충돌 시 자동으로 재시도됩니다. 이는 동일 데이터베이스를 가리키는 여러 `Memory` 인스턴스(예: 에이전트 메모리 + crew 메모리)를 처리합니다. 별도의 조치가 필요하지 않습니다.
**터미널에서 메모리 탐색:**
```bash
crewai memory # TUI 브라우저 열기
@@ -721,7 +870,9 @@ memory.reset(scope="/project/old") # 해당 하위 트리만
| `consolidation_threshold` | `0.85` | 저장 시 통합이 트리거되는 유사도. `1.0`으로 설정하면 비활성화. |
| `consolidation_limit` | `5` | 통합 중 비교할 기존 레코드 최대 수. |
| `default_importance` | `0.5` | 미제공 시 및 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 분석을 건너뜀. |

View File

@@ -73,6 +73,8 @@ flow.kickoff()
| `default_outcome` | `str` | 아니오 | 피드백이 제공되지 않을 때 사용할 outcome. `emit`에 있어야 합니다 |
| `metadata` | `dict` | 아니오 | 엔터프라이즈 통합을 위한 추가 데이터 |
| `provider` | `HumanFeedbackProvider` | 아니오 | 비동기/논블로킹 피드백을 위한 커스텀 프로바이더. [비동기 인간 피드백](#비동기-인간-피드백-논블로킹) 참조 |
| `learn` | `bool` | 아니오 | HITL 학습 활성화: 피드백에서 교훈을 추출하고 향후 출력을 사전 검토합니다. 기본값 `False`. [피드백에서 학습하기](#피드백에서-학습하기) 참조 |
| `learn_limit` | `int` | 아니오 | 사전 검토를 위해 불러올 최대 과거 교훈 수. 기본값 `5` |
### 기본 사용법 (라우팅 없음)
@@ -576,6 +578,64 @@ async def on_slack_feedback_async(flow_id: str, slack_message: str):
5. **자동 영속성**: `HumanFeedbackPending`이 발생하면 상태가 자동으로 저장되며 기본적으로 `SQLiteFlowPersistence` 사용
6. **커스텀 영속성**: 필요한 경우 `from_pending()`에 커스텀 영속성 인스턴스 전달
## 피드백에서 학습하기
`learn=True` 매개변수는 인간 검토자와 메모리 시스템 간의 피드백 루프를 활성화합니다. 활성화되면 시스템은 과거 인간의 수정 사항에서 학습하여 출력을 점진적으로 개선합니다.
### 작동 방식
1. **피드백 후**: LLM이 출력 + 피드백에서 일반화 가능한 교훈을 추출하고 `source="hitl"`로 메모리에 저장합니다. 피드백이 단순한 승인(예: "좋아 보입니다")인 경우 아무것도 저장하지 않습니다.
2. **다음 검토 전**: 과거 HITL 교훈을 메모리에서 불러와 LLM이 인간이 보기 전에 출력을 개선하는 데 적용합니다.
시간이 지남에 따라 각 수정 사항이 향후 검토에 반영되므로 인간은 점진적으로 더 나은 사전 검토된 출력을 보게 됩니다.
### 예제
```python Code
class ArticleReviewFlow(Flow):
@start()
@human_feedback(
message="Review this article draft:",
emit=["approved", "needs_revision"],
llm="gpt-4o-mini",
learn=True, # HITL 학습 활성화
)
def generate_article(self):
return self.crew.kickoff(inputs={"topic": "AI Safety"}).raw
@listen("approved")
def publish(self):
print(f"Publishing: {self.last_human_feedback.output}")
@listen("needs_revision")
def revise(self):
print("Revising based on feedback...")
```
**첫 번째 실행**: 인간이 원시 출력을 보고 "사실에 대한 주장에는 항상 인용을 포함하세요."라고 말합니다. 교훈이 추출되어 메모리에 저장됩니다.
**두 번째 실행**: 시스템이 인용 교훈을 불러와 출력을 사전 검토하여 인용을 추가한 후 개선된 버전을 표시합니다. 인간의 역할이 "모든 것을 수정"에서 "시스템이 놓친 것을 찾기"로 전환됩니다.
### 구성
| 매개변수 | 기본값 | 설명 |
|-----------|--------|------|
| `learn` | `False` | HITL 학습 활성화 |
| `learn_limit` | `5` | 사전 검토를 위해 불러올 최대 과거 교훈 수 |
### 주요 설계 결정
- **모든 것에 동일한 LLM 사용**: 데코레이터의 `llm` 매개변수는 outcome 매핑, 교훈 추출, 사전 검토에 공유됩니다. 여러 모델을 구성할 필요가 없습니다.
- **구조화된 출력**: 추출과 사전 검토 모두 LLM이 지원하는 경우 Pydantic 모델과 함께 function calling을 사용하고, 그렇지 않으면 텍스트 파싱으로 폴백합니다.
- **논블로킹 저장**: 교훈은 백그라운드 스레드에서 실행되는 `remember_many()`를 통해 저장됩니다 -- Flow는 즉시 계속됩니다.
- **우아한 저하**: 추출 중 LLM이 실패하면 아무것도 저장하지 않습니다. 사전 검토 중 실패하면 원시 출력이 표시됩니다. 어느 쪽의 실패도 Flow를 차단하지 않습니다.
- **범위/카테고리 불필요**: 교훈을 저장할 때 `source`만 전달됩니다. 인코딩 파이프라인이 범위, 카테고리, 중요도를 자동으로 추론합니다.
<Note>
`learn=True`는 Flow에 메모리가 사용 가능해야 합니다. Flow는 기본적으로 자동으로 메모리를 얻지만, `_skip_auto_memory`로 비활성화한 경우 HITL 학습은 조용히 건너뜁니다.
</Note>
## 관련 문서
- [Flow 개요](/ko/concepts/flows) - CrewAI Flow에 대해 알아보기
@@ -583,3 +643,4 @@ async def on_slack_feedback_async(flow_id: str, slack_message: str):
- [Flow 영속성](/ko/concepts/flows#persistence) - Flow 상태 영속화
- [@router를 사용한 라우팅](/ko/concepts/flows#router) - 조건부 라우팅에 대해 더 알아보기
- [실행 시 인간 입력](/ko/learn/human-input-on-execution) - 태스크 수준 인간 입력
- [메모리](/ko/concepts/memory) - HITL 학습에서 사용되는 통합 메모리 시스템