Skip to content
Open
37 changes: 37 additions & 0 deletions docs/ar/concepts/memory.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,43 @@ class ResearchFlow(Flow):

انظر [وثائق التدفقات](/concepts/flows) لمزيد من المعلومات حول الذاكرة في التدفقات.

## تخصيص مطالبات الذاكرة (`MemoryPromptConfig`)

يمكنك استبدال تعليمات نموذج اللغة في كل خطوة من تحليل الذاكرة (نفس فكرة ضبط مطالبات التخطيط). مرّر كائن `MemoryPromptConfig` كوسيط `memory_prompt` إلى `Memory`. عيّن الحقول التي تحتاجها فقط؛ تبقى الخطوات الأخرى على القيم الافتراضية المضمّنة في `translations/en.json` تحت المفتاح `memory` (أسماء الحقول تطابق مفاتيح JSON).

```python
from crewai import Memory, MemoryPromptConfig

memory = Memory(
llm="gpt-4o-mini",
memory_prompt=MemoryPromptConfig(
save_system="...", # اختياري
query_user="...", # اختياري
),
)
```

يمكنك أيضًا تمرير `memory_prompt` إلى دوال مساعدة في `crewai.memory.analyze` (مثل `extract_memories_from_content`) عند استدعائها مباشرة.

### تأثير كل زوج من المطالبات

| الحقول | متى يعمل | ماذا يؤثر |
| --- | --- | --- |
| `save_system` / `save_user` | عند الحفظ (`analyze_for_save`) | `suggested_scope` و`categories` و`importance` و`extracted_metadata` المستنتجة قبل التخزين والتضمين. |
| `query_system` / `query_user` | عند تحليل استعلام الاسترجاع (`analyze_query`) | `keywords` و`suggested_scopes` و`complexity` و`recall_queries` و`time_filter`، ما يوجّه البحث المتجهي وعمق الاسترجاع. |
| `extract_memories_system` / `extract_memories_user` | `extract_memories_from_content` / `Memory.extract_memories` | كيفية تقسيم النص الخام إلى جمل ذاكرة منفصلة (لا يزال التخزين عبر `remember()`). |
| `consolidation_system` / `consolidation_user` | عندما يكون المحتوى الجديد قريبًا دلاليًا من سجلات موجودة (`analyze_for_consolidation`) | الإبقاء على الصفوف أو تحديثها أو حذفها، وما إذا كان يُدرج المحتوى الجديد كذاكرة مستقلة. |

### العناصر النائبة (placeholders)

سلاسل **النظام (system)** تُرسل كما هي. سلاسل **المستخدم (user)** تُملأ بـ `str.format` في بايثون. يجب أن تتضمن قوالب المستخدم المخصصة نفس أسماء العناصر النائبة الافتراضية وإلا يفشل التنسيق.

| حقل المستخدم | عناصر نائبة مطلوبة |
| --- | --- |
| `save_user` | `{content}`، `{existing_scopes}`، `{existing_categories}` |
| `query_user` | `{query}`، `{available_scopes}`، `{scope_desc}` |
| `extract_memories_user` | `{content}` |
| `consolidation_user` | `{new_content}`، `{records_summary}` |

## النطاقات الهرمية

Expand Down
37 changes: 37 additions & 0 deletions docs/en/concepts/memory.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,43 @@ class ResearchFlow(Flow):

See the [Flows documentation](/concepts/flows) for more on memory in Flows.

## Customizing memory prompts (`MemoryPromptConfig`)

Override the LLM instructions used at each memory analysis step (same idea as tuning planning prompts). Pass a `MemoryPromptConfig` as `memory_prompt` on `Memory`. Only set the fields you need; every other step keeps the bundled defaults from the library’s `translations/en.json` under the `memory` key (field names match those JSON keys).

```python
from crewai import Memory, MemoryPromptConfig

memory = Memory(
llm="gpt-4o-mini",
memory_prompt=MemoryPromptConfig(
save_system="...", # optional
query_user="...", # optional
),
)
```

You can also pass `memory_prompt` into helpers in `crewai.memory.analyze` (for example `extract_memories_from_content`) when you call them directly.

### What each prompt pair affects

| Fields | When it runs | What it influences |
| --- | --- | --- |
| `save_system` / `save_user` | Saving (`analyze_for_save`) | Inferred `suggested_scope`, `categories`, `importance`, and `extracted_metadata` before storage and embedding. |
| `query_system` / `query_user` | Recall query analysis (`analyze_query`) | `keywords`, `suggested_scopes`, `complexity`, `recall_queries`, and `time_filter`, which steer vector search and how deep recall goes. |
| `extract_memories_system` / `extract_memories_user` | `extract_memories_from_content` / `Memory.extract_memories` | How raw text is split into discrete memory strings (persistence is still via `remember()`). |
| `consolidation_system` / `consolidation_user` | When new content is similar to existing records (`analyze_for_consolidation`) | Whether to keep, update, or delete existing rows and whether to insert the new content as its own memory. |

### Placeholders

**System** strings are sent as-is. **User** strings are filled with Python’s `str.format`. Custom user templates must include the same placeholder names as the defaults or formatting will raise.

| User field | Required placeholders |
| --- | --- |
| `save_user` | `{content}`, `{existing_scopes}`, `{existing_categories}` |
| `query_user` | `{query}`, `{available_scopes}`, `{scope_desc}` |
| `extract_memories_user` | `{content}` |
| `consolidation_user` | `{new_content}`, `{records_summary}` |

## Hierarchical Scopes

Expand Down
37 changes: 37 additions & 0 deletions docs/ko/concepts/memory.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,43 @@ class ResearchFlow(Flow):

Flow에서의 메모리에 대한 자세한 내용은 [Flows 문서](/concepts/flows)를 참조하세요.

## 메모리 프롬프트 사용자 지정 (`MemoryPromptConfig`)

메모리 분석 단계마다 사용되는 LLM 지시문을 덮어쓸 수 있습니다(플래닝 프롬프트를 조정하는 것과 같은 개념). `Memory`의 `memory_prompt`에 `MemoryPromptConfig`를 넘깁니다. 필요한 필드만 설정하면 되고, 나머지 단계는 라이브러리 번들 기본값(`translations/en.json`의 `memory` 키; 필드 이름이 해당 JSON 키와 일치)을 그대로 씁니다.

```python
from crewai import Memory, MemoryPromptConfig

memory = Memory(
llm="gpt-4o-mini",
memory_prompt=MemoryPromptConfig(
save_system="...", # 선택
query_user="...", # 선택
),
)
```

`crewai.memory.analyze`의 헬퍼(예: `extract_memories_from_content`)를 직접 호출할 때도 `memory_prompt`를 넘길 수 있습니다.

### 프롬프트 쌍별 역할

| 필드 | 실행 시점 | 영향 |
| --- | --- | --- |
| `save_system` / `save_user` | 저장 시 (`analyze_for_save`) | 저장·임베딩 전에 추론되는 `suggested_scope`, `categories`, `importance`, `extracted_metadata`. |
| `query_system` / `query_user` | 리콜 시 쿼리 분석 (`analyze_query`) | `keywords`, `suggested_scopes`, `complexity`, `recall_queries`, `time_filter` — 벡터 검색과 리콜 탐색 깊이에 영향. |
| `extract_memories_system` / `extract_memories_user` | `extract_memories_from_content` / `Memory.extract_memories` | 긴 텍스트를 개별 메모리 문자열로 나누는 방식(저장은 여전히 `remember()`). |
| `consolidation_system` / `consolidation_user` | 신규 콘텐츠가 기존 레코드와 유사할 때 (`analyze_for_consolidation`) | 기존 행 유지·갱신·삭제 및 신규 콘텐츠를 별도 메모리로 넣을지 여부. |

### 플레이스홀더

**system** 문자열은 그대로 전송됩니다. **user** 문자열은 Python `str.format`으로 채워집니다. 사용자 정의 user 템플릿에는 기본값과 동일한 플레이스홀더 이름이 포함되어야 하며, 그렇지 않으면 포맷 단계에서 오류가 납니다.

| User 필드 | 필수 플레이스홀더 |
| --- | --- |
| `save_user` | `{content}`, `{existing_scopes}`, `{existing_categories}` |
| `query_user` | `{query}`, `{available_scopes}`, `{scope_desc}` |
| `extract_memories_user` | `{content}` |
| `consolidation_user` | `{new_content}`, `{records_summary}` |

## 계층적 범위(Scopes)

Expand Down
37 changes: 37 additions & 0 deletions docs/pt-BR/concepts/memory.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,43 @@ class ResearchFlow(Flow):

Veja a [documentação de Flows](/concepts/flows) para mais informações sobre memória em Flows.

## Personalizando prompts de memória (`MemoryPromptConfig`)

Substitua as instruções do LLM usadas em cada etapa de análise de memória (mesma ideia que ajustar prompts de planejamento). Passe um `MemoryPromptConfig` como `memory_prompt` em `Memory`. Defina apenas os campos necessários; nas demais etapas permanecem os padrões embutidos do `translations/en.json` da biblioteca, na chave `memory` (os nomes dos campos correspondem às chaves JSON).

```python
from crewai import Memory, MemoryPromptConfig

memory = Memory(
llm="gpt-4o-mini",
memory_prompt=MemoryPromptConfig(
save_system="...", # opcional
query_user="...", # opcional
),
)
```

Você também pode passar `memory_prompt` para funções auxiliares em `crewai.memory.analyze` (por exemplo `extract_memories_from_content`) quando chamá-las diretamente.

### O que cada par de prompts afeta

| Campos | Quando roda | O que influencia |
| --- | --- | --- |
| `save_system` / `save_user` | Ao salvar (`analyze_for_save`) | `suggested_scope`, `categories`, `importance` e `extracted_metadata` inferidos antes do armazenamento e do embedding. |
| `query_system` / `query_user` | Análise da consulta no recall (`analyze_query`) | `keywords`, `suggested_scopes`, `complexity`, `recall_queries` e `time_filter`, que orientam a busca vetorial e a profundidade do recall. |
| `extract_memories_system` / `extract_memories_user` | `extract_memories_from_content` / `Memory.extract_memories` | Como o texto bruto é dividido em memórias atômicas (a persistência continua sendo via `remember()`). |
| `consolidation_system` / `consolidation_user` | Quando o novo conteúdo é semelhante a registros existentes (`analyze_for_consolidation`) | Manter, atualizar ou excluir linhas existentes e se o novo conteúdo entra como memória própria. |

### Placeholders

Strings de **system** são enviadas como estão. Strings de **user** são preenchidas com `str.format` do Python. Templates de user personalizados devem incluir os mesmos nomes de placeholder dos padrões; caso contrário, a formatação falha.

| Campo user | Placeholders obrigatórios |
| --- | --- |
| `save_user` | `{content}`, `{existing_scopes}`, `{existing_categories}` |
| `query_user` | `{query}`, `{available_scopes}`, `{scope_desc}` |
| `extract_memories_user` | `{content}` |
| `consolidation_user` | `{new_content}`, `{records_summary}` |

## Escopos Hierárquicos

Expand Down
2 changes: 2 additions & 0 deletions lib/crewai/src/crewai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def filtered_warn(

_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
"Memory": ("crewai.memory.unified_memory", "Memory"),
"MemoryPromptConfig": ("crewai.memory.types", "MemoryPromptConfig"),
}


Expand Down Expand Up @@ -196,6 +197,7 @@ def __getattr__(name: str) -> Any:
"Knowledge",
"LLMGuardrail",
"Memory",
"MemoryPromptConfig",
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
Comment thread
lorenzejay marked this conversation as resolved.
Dismissed
"PlanningConfig",
"Process",
"RuntimeState",
Expand Down
63 changes: 44 additions & 19 deletions lib/crewai/src/crewai/memory/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from pydantic import BaseModel, ConfigDict, Field

from crewai.memory.types import MemoryRecord, ScopeInfo
from crewai.memory.types import MemoryPromptConfig, MemoryRecord, ScopeInfo
from crewai.utilities.i18n import I18N_DEFAULT


Expand Down Expand Up @@ -140,19 +140,23 @@ class ConsolidationPlan(BaseModel):
)


def _get_prompt(key: str) -> str:
"""Retrieve a memory prompt from the i18n translations.

Args:
key: The prompt key under the "memory" section.

Returns:
The prompt string.
"""
def _memory_prompt_line(
memory_prompt: MemoryPromptConfig | None,
key: str,
) -> str:
"""Resolve one memory prompt: override string or bundled translation."""
if memory_prompt is not None:
raw = getattr(memory_prompt, key, None)
if isinstance(raw, str) and raw.strip():
return raw
return I18N_DEFAULT.memory(key)


def extract_memories_from_content(content: str, llm: Any) -> list[str]:
def extract_memories_from_content(
content: str,
llm: Any,
memory_prompt: MemoryPromptConfig | None = None,
) -> list[str]:
"""Use the LLM to extract discrete memory statements from raw content.

This is a pure helper: it does NOT store anything. Callers should call
Expand All @@ -164,15 +168,21 @@ def extract_memories_from_content(content: str, llm: Any) -> list[str]:
Args:
content: Raw text (e.g. task description + result dump).
llm: The LLM instance to use.
memory_prompt: Optional per-step prompt strings (see ``MemoryPromptConfig``).

Returns:
List of short, self-contained memory statements (or [content] on failure).
"""
if not (content or "").strip():
return []
user = _get_prompt("extract_memories_user").format(content=content)
user = _memory_prompt_line(memory_prompt, "extract_memories_user").format(
content=content
)
Comment on lines +178 to +180

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Uncaught template-format errors can break memory flows.

On Line 178, Line 234, Line 304, and Line 370, custom prompt templates are formatted before the guarded try blocks. A bad placeholder (e.g. {foo}) will raise and skip fallback behavior, causing runtime failures in remember/recall/extract paths.

Proposed fix (centralized safe rendering + fallback)
 def _memory_prompt_line(
     memory_prompt: MemoryPromptConfig | None,
     key: str,
 ) -> str:
     """Resolve one memory prompt: override string or bundled translation."""
     if memory_prompt is not None:
         raw = getattr(memory_prompt, key, None)
         if isinstance(raw, str) and raw.strip():
             return raw
     return I18N_DEFAULT.memory(key)
+
+
+def _render_memory_prompt(
+    memory_prompt: MemoryPromptConfig | None,
+    key: str,
+    **kwargs: Any,
+) -> str:
+    template = _memory_prompt_line(memory_prompt, key)
+    try:
+        return template.format(**kwargs) if kwargs else template
+    except Exception as e:
+        _logger.warning(
+            "Invalid memory prompt template for '%s'; using default: %s",
+            key,
+            e,
+            exc_info=False,
+        )
+        fallback = I18N_DEFAULT.memory(key)
+        return fallback.format(**kwargs) if kwargs else fallback
-    user = _memory_prompt_line(memory_prompt, "extract_memories_user").format(
-        content=content
-    )
+    user = _render_memory_prompt(
+        memory_prompt, "extract_memories_user", content=content
+    )
-    user = _memory_prompt_line(memory_prompt, "query_user").format(
+    user = _render_memory_prompt(
+        memory_prompt,
+        "query_user",
         query=query,
         available_scopes=available_scopes or ["/"],
         scope_desc=scope_desc,
     )
-    user = _memory_prompt_line(memory_prompt, "save_user").format(
+    user = _render_memory_prompt(
+        memory_prompt,
+        "save_user",
         content=content,
         existing_scopes=existing_scopes or ["/"],
         existing_categories=existing_categories or [],
     )
-    user = _memory_prompt_line(memory_prompt, "consolidation_user").format(
+    user = _render_memory_prompt(
+        memory_prompt,
+        "consolidation_user",
         new_content=new_content,
         records_summary="\n\n".join(records_lines),
     )

Also applies to: 234-238, 304-308, 370-373

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/memory/analyze.py` around lines 178 - 180, The template
strings returned by _memory_prompt_line(memory_prompt, "...") are being
.format(...) before entering the guarded try blocks, so any bad placeholder will
raise and bypass fallbacks; move the .format call into the corresponding
try/except (or replace with a safe_render helper that catches
KeyError/ValueError/IndexError and returns a fallback) for each occurrence (the
calls that produce user/assistant prompt lines via _memory_prompt_line with keys
like "extract_memories_user", "extract_memories_assistant", etc.), i.e., change
user = _memory_prompt_line(...).format(content=...) to first get the template =
_memory_prompt_line(...), then inside the try do formatted =
safe_render(template, content=content) (or try: formatted = template.format(...)
except Exception: formatted = template or fallback) and use formatted downstream
so runtime formatting errors do not skip the existing fallback behavior.

messages = [
{"role": "system", "content": _get_prompt("extract_memories_system")},
{
"role": "system",
"content": _memory_prompt_line(memory_prompt, "extract_memories_system"),
},
{"role": "user", "content": user},
]
try:
Expand Down Expand Up @@ -202,6 +212,7 @@ def analyze_query(
available_scopes: list[str],
scope_info: ScopeInfo | None,
llm: Any,
memory_prompt: MemoryPromptConfig | None = None,
) -> QueryAnalysis:
"""Use the LLM to analyze a recall query.

Expand All @@ -212,20 +223,24 @@ def analyze_query(
available_scopes: Scope paths that exist in the store.
scope_info: Optional info about the current scope.
llm: The LLM instance to use.
memory_prompt: Optional per-step prompt strings.

Returns:
QueryAnalysis with keywords, suggested_scopes, complexity, recall_queries, time_filter.
"""
scope_desc = ""
if scope_info:
scope_desc = f"Current scope has {scope_info.record_count} records, categories: {scope_info.categories}"
user = _get_prompt("query_user").format(
user = _memory_prompt_line(memory_prompt, "query_user").format(
query=query,
available_scopes=available_scopes or ["/"],
scope_desc=scope_desc,
)
messages = [
{"role": "system", "content": _get_prompt("query_system")},
{
"role": "system",
"content": _memory_prompt_line(memory_prompt, "query_system"),
},
{"role": "user", "content": user},
]
try:
Expand Down Expand Up @@ -269,6 +284,7 @@ def analyze_for_save(
existing_scopes: list[str],
existing_categories: list[str],
llm: Any,
memory_prompt: MemoryPromptConfig | None = None,
) -> MemoryAnalysis:
"""Infer scope, categories, importance, and metadata for a single memory.

Expand All @@ -280,17 +296,21 @@ def analyze_for_save(
existing_scopes: Current scope paths in the memory store.
existing_categories: Current categories in use.
llm: The LLM instance to use.
memory_prompt: Optional per-step prompt strings.

Returns:
MemoryAnalysis with suggested_scope, categories, importance, extracted_metadata.
"""
user = _get_prompt("save_user").format(
user = _memory_prompt_line(memory_prompt, "save_user").format(
content=content,
existing_scopes=existing_scopes or ["/"],
existing_categories=existing_categories or [],
)
messages = [
{"role": "system", "content": _get_prompt("save_system")},
{
"role": "system",
"content": _memory_prompt_line(memory_prompt, "save_system"),
},
{"role": "user", "content": user},
]
try:
Expand Down Expand Up @@ -322,6 +342,7 @@ def analyze_for_consolidation(
new_content: str,
existing_records: list[MemoryRecord],
llm: Any,
memory_prompt: MemoryPromptConfig | None = None,
) -> ConsolidationPlan:
"""Decide insert/update/delete for a single memory against similar existing records.

Expand All @@ -332,6 +353,7 @@ def analyze_for_consolidation(
new_content: The new content to store.
existing_records: Existing records that are semantically similar.
llm: The LLM instance to use.
memory_prompt: Optional per-step prompt strings.

Returns:
ConsolidationPlan with actions per record and whether to insert the new content.
Expand All @@ -345,12 +367,15 @@ def analyze_for_consolidation(
f"- id={r.id} | scope={r.scope} | importance={r.importance:.2f} | created={created}\n"
f" content: {r.content[:200]}{'...' if len(r.content) > 200 else ''}"
)
user = _get_prompt("consolidation_user").format(
user = _memory_prompt_line(memory_prompt, "consolidation_user").format(
new_content=new_content,
records_summary="\n\n".join(records_lines),
)
messages = [
{"role": "system", "content": _get_prompt("consolidation_system")},
{
"role": "system",
"content": _memory_prompt_line(memory_prompt, "consolidation_system"),
},
{"role": "user", "content": user},
]
try:
Expand Down
4 changes: 4 additions & 0 deletions lib/crewai/src/crewai/memory/encoding_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ def parallel_analyze(self) -> None:
item.content,
list(item.similar_records),
self._llm,
self._config.memory_prompt,
)
elif not fields_provided and not has_similar:
# Group C: field resolution only
Expand All @@ -324,6 +325,7 @@ def parallel_analyze(self) -> None:
existing_scopes,
existing_categories,
self._llm,
self._config.memory_prompt,
)
else:
# Group D: both in parallel
Expand All @@ -334,13 +336,15 @@ def parallel_analyze(self) -> None:
existing_scopes,
existing_categories,
self._llm,
self._config.memory_prompt,
)
consol_futures[i] = pool.submit(
contextvars.copy_context().run,
analyze_for_consolidation,
item.content,
list(item.similar_records),
self._llm,
self._config.memory_prompt,
)

# Collect field-resolution results
Expand Down
1 change: 1 addition & 0 deletions lib/crewai/src/crewai/memory/recall_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ def analyze_query_step(self) -> QueryAnalysis:
available,
scope_info,
self._llm,
self._config.memory_prompt,
)
self.state.query_analysis = analysis

Expand Down
Loading
Loading