mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-05-01 07:13:00 +00:00
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
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:
@@ -380,22 +380,124 @@ A memória usa o LLM de três formas:
|
||||
Toda análise degrada graciosamente em caso de falha do LLM -- veja [Comportamento em Caso de Falha](#comportamento-em-caso-de-falha).
|
||||
|
||||
|
||||
## RecallFlow (Recall Profundo)
|
||||
## Consolidação de Memória
|
||||
|
||||
`recall()` suporta três profundidades:
|
||||
Ao salvar novo conteúdo, o pipeline de codificação verifica automaticamente registros similares existentes no armazenamento. Se a similaridade estiver acima de `consolidation_threshold` (padrão 0.85), o LLM decide o que fazer:
|
||||
|
||||
- **`depth="shallow"`** -- Busca vetorial direta com pontuação composta. Rápido; usado por padrão quando agentes carregam contexto.
|
||||
- **`depth="deep"` ou `depth="auto"`** -- Executa um RecallFlow em múltiplas etapas: análise da consulta, seleção de escopo, busca vetorial, roteamento baseado em confiança e exploração recursiva opcional quando a confiança é baixa.
|
||||
- **keep** -- O registro existente ainda é preciso e não é redundante.
|
||||
- **update** -- O registro existente deve ser atualizado com novas informações (o LLM fornece o conteúdo mesclado).
|
||||
- **delete** -- O registro existente está desatualizado, substituído ou contradito.
|
||||
- **insert_new** -- Se o novo conteúdo também deve ser inserido como um registro separado.
|
||||
|
||||
Isso evita o acúmulo de duplicatas. Por exemplo, se você salvar "CrewAI garante operação confiável" três vezes, a consolidação reconhece as duplicatas e mantém apenas um registro.
|
||||
|
||||
### Dedup Intra-batch
|
||||
|
||||
Ao usar `remember_many()`, os itens dentro do mesmo batch são comparados entre si antes de atingir o armazenamento. Se dois itens tiverem similaridade de cosseno >= `batch_dedup_threshold` (padrão 0.98), o posterior é silenciosamente descartado. Isso captura duplicatas exatas ou quase exatas dentro de um único batch sem chamadas ao LLM (pura matemática vetorial).
|
||||
|
||||
```python
|
||||
# Caminho rápido (padrão para contexto de tarefa do agente)
|
||||
# Apenas 2 registros são armazenados (o terceiro é quase duplicata do primeiro)
|
||||
memory.remember_many([
|
||||
"CrewAI supports complex workflows.",
|
||||
"Python is a great language.",
|
||||
"CrewAI supports complex workflows.", # descartado pelo dedup intra-batch
|
||||
])
|
||||
```
|
||||
|
||||
|
||||
## Saves Não-Bloqueantes
|
||||
|
||||
`remember_many()` é **não-bloqueante** -- ele envia o pipeline de codificação para uma thread em background e retorna imediatamente. Isso significa que o agente pode continuar para a próxima tarefa enquanto as memórias estão sendo salvas.
|
||||
|
||||
```python
|
||||
# Retorna imediatamente -- save acontece em background
|
||||
memory.remember_many(["Fato A.", "Fato B.", "Fato C."])
|
||||
|
||||
# recall() espera automaticamente saves pendentes antes de buscar
|
||||
matches = memory.recall("fatos") # vê todos os 3 registros
|
||||
```
|
||||
|
||||
### Barreira de Leitura
|
||||
|
||||
Cada chamada `recall()` executa automaticamente `drain_writes()` antes de buscar, garantindo que a consulta sempre veja os registros mais recentes persistidos. Isso é transparente -- você nunca precisa pensar nisso.
|
||||
|
||||
### Encerramento da Crew
|
||||
|
||||
Quando uma crew termina, `kickoff()` drena todos os saves de memória pendentes em seu bloco `finally`, então nenhum save é perdido mesmo que a crew complete enquanto saves em background estão em andamento.
|
||||
|
||||
### Uso Standalone
|
||||
|
||||
Para scripts ou notebooks onde não há ciclo de vida de crew, chame `drain_writes()` ou `close()` explicitamente:
|
||||
|
||||
```python
|
||||
memory = Memory()
|
||||
memory.remember_many(["Fato A.", "Fato B."])
|
||||
|
||||
# Opção 1: Esperar saves pendentes
|
||||
memory.drain_writes()
|
||||
|
||||
# Opção 2: Drenar e encerrar o pool de background
|
||||
memory.close()
|
||||
```
|
||||
|
||||
|
||||
## Origem e Privacidade
|
||||
|
||||
Cada registro de memória pode carregar uma tag `source` para rastreamento de procedência e uma flag `private` para controle de acesso.
|
||||
|
||||
### Rastreamento de Origem
|
||||
|
||||
O parâmetro `source` identifica de onde uma memória veio:
|
||||
|
||||
```python
|
||||
# Marcar memórias com sua origem
|
||||
memory.remember("Usuário prefere modo escuro", source="user:alice")
|
||||
memory.remember("Configuração do sistema atualizada", source="admin")
|
||||
memory.remember("Agente encontrou um bug", source="agent:debugger")
|
||||
|
||||
# Recuperar apenas memórias de uma origem específica
|
||||
matches = memory.recall("preferências do usuário", source="user:alice")
|
||||
```
|
||||
|
||||
### Memórias Privadas
|
||||
|
||||
Memórias privadas só são visíveis no recall quando o `source` corresponde:
|
||||
|
||||
```python
|
||||
# Armazenar uma memória privada
|
||||
memory.remember("A chave de API da Alice é sk-...", source="user:alice", private=True)
|
||||
|
||||
# Este recall vê a memória privada (source corresponde)
|
||||
matches = memory.recall("chave de API", source="user:alice")
|
||||
|
||||
# Este recall NÃO a vê (source diferente)
|
||||
matches = memory.recall("chave de API", source="user:bob")
|
||||
|
||||
# Acesso admin: ver todos os registros privados independente do source
|
||||
matches = memory.recall("chave de API", include_private=True)
|
||||
```
|
||||
|
||||
Isso é particularmente útil em implantações multi-usuário ou corporativas onde memórias de diferentes usuários devem ser isoladas.
|
||||
|
||||
|
||||
## RecallFlow (Recall Profundo)
|
||||
|
||||
`recall()` suporta duas profundidades:
|
||||
|
||||
- **`depth="shallow"`** -- Busca vetorial direta com pontuação composta. Rápido (~200ms), sem chamadas ao LLM.
|
||||
- **`depth="deep"` (padrão)** -- Executa um RecallFlow em múltiplas etapas: análise da consulta, seleção de escopo, busca vetorial paralela, roteamento baseado em confiança e exploração recursiva opcional quando a confiança é baixa.
|
||||
|
||||
**Pulo inteligente do LLM**: Consultas com menos de `query_analysis_threshold` (padrão 200 caracteres) pulam a análise de consulta do LLM inteiramente, mesmo no modo deep. Consultas curtas como "Qual banco de dados usamos?" já são boas frases de busca -- a análise do LLM agrega pouco valor. Isso economiza ~1-3s por recall para consultas curtas típicas. Apenas consultas mais longas (ex.: descrições completas de tarefas) passam pela destilação do LLM em sub-consultas direcionadas.
|
||||
|
||||
```python
|
||||
# Shallow: busca vetorial pura, sem LLM
|
||||
matches = memory.recall("O que decidimos?", limit=10, depth="shallow")
|
||||
|
||||
# Caminho inteligente para perguntas complexas
|
||||
# Deep (padrão): recuperação inteligente com análise LLM para consultas longas
|
||||
matches = memory.recall(
|
||||
"Resuma todas as decisões de arquitetura deste trimestre",
|
||||
limit=10,
|
||||
depth="auto",
|
||||
depth="deep",
|
||||
)
|
||||
```
|
||||
|
||||
@@ -406,6 +508,7 @@ memory = Memory(
|
||||
confidence_threshold_high=0.9, # Só sintetizar quando muito confiante
|
||||
confidence_threshold_low=0.4, # Explorar mais profundamente de forma mais agressiva
|
||||
exploration_budget=2, # Permitir até 2 rodadas de exploração
|
||||
query_analysis_threshold=200, # Pular LLM para consultas menores que isso
|
||||
)
|
||||
```
|
||||
|
||||
@@ -613,6 +716,45 @@ memory = Memory(embedder=my_embedder)
|
||||
| Custom | `custom` | -- | Requer `embedding_callable`. |
|
||||
|
||||
|
||||
## Configuração de LLM
|
||||
|
||||
A memória usa um LLM para análise de save (inferência de escopo, categorias e importância), decisões de consolidação e análise de consulta no recall profundo. Você pode configurar qual modelo usar.
|
||||
|
||||
```python
|
||||
from crewai import Memory, LLM
|
||||
|
||||
# Padrão: gpt-4o-mini
|
||||
memory = Memory()
|
||||
|
||||
# Usar um modelo OpenAI diferente
|
||||
memory = Memory(llm="gpt-4o")
|
||||
|
||||
# Usar Anthropic
|
||||
memory = Memory(llm="anthropic/claude-3-haiku-20240307")
|
||||
|
||||
# Usar Ollama para análise totalmente local/privada
|
||||
memory = Memory(llm="ollama/llama3.2")
|
||||
|
||||
# Usar Google Gemini
|
||||
memory = Memory(llm="gemini/gemini-2.0-flash")
|
||||
|
||||
# Passar uma instância LLM pré-configurada com configurações customizadas
|
||||
llm = LLM(model="gpt-4o", temperature=0)
|
||||
memory = Memory(llm=llm)
|
||||
```
|
||||
|
||||
O LLM é inicializado **lazily** -- ele só é criado quando necessário pela primeira vez. Isso significa que `Memory()` nunca falha no momento da construção, mesmo que chaves de API não estejam definidas. Erros só aparecem quando o LLM é realmente chamado (ex.: ao salvar sem escopo/categorias explícitos, ou durante recall profundo).
|
||||
|
||||
Para operação totalmente offline/privada, use um modelo local tanto para o LLM quanto para o embedder:
|
||||
|
||||
```python
|
||||
memory = Memory(
|
||||
llm="ollama/llama3.2",
|
||||
embedder={"provider": "ollama", "config": {"model_name": "mxbai-embed-large"}},
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
## Backend de Armazenamento
|
||||
|
||||
- **Padrão**: LanceDB, armazenado em `./.crewai/memory` (ou `$CREWAI_STORAGE_DIR/memory` se a variável de ambiente estiver definida, ou o caminho que você passar como `storage="path/to/dir"`).
|
||||
@@ -685,11 +827,18 @@ class MemoryMonitor(BaseEventListener):
|
||||
- Ao usar uma crew, confirme que `memory=True` ou `memory=Memory(...)` está definido.
|
||||
|
||||
**Recall lento?**
|
||||
- Use `depth="shallow"` para contexto rotineiro do agente. Reserve `depth="auto"` ou `"deep"` para consultas complexas.
|
||||
- Use `depth="shallow"` para contexto rotineiro do agente. Reserve `depth="deep"` para consultas complexas.
|
||||
- Aumente `query_analysis_threshold` para pular a análise do LLM em mais consultas.
|
||||
|
||||
**Erros de análise LLM nos logs?**
|
||||
- A memória ainda salva/recupera com padrões seguros. Verifique chaves de API, limites de taxa e disponibilidade do modelo se quiser análise LLM completa.
|
||||
|
||||
**Erros de save em background nos logs?**
|
||||
- Os saves de memória rodam em uma thread em background. Erros são emitidos como `MemorySaveFailedEvent` mas não derrubam o agente. Verifique os logs para a causa raiz (geralmente problemas de conexão com LLM ou embedder).
|
||||
|
||||
**Conflitos de escrita concorrente?**
|
||||
- As operações do LanceDB são serializadas com um lock compartilhado e reexecutadas automaticamente em caso de conflito. Isso lida com múltiplas instâncias `Memory` apontando para o mesmo banco de dados (ex.: memória do agente + memória da crew). Nenhuma ação necessária.
|
||||
|
||||
**Navegar na memória pelo terminal:**
|
||||
```bash
|
||||
crewai memory # Abre o navegador TUI
|
||||
@@ -721,7 +870,9 @@ Toda a configuração é passada como argumentos nomeados para `Memory(...)`. Ca
|
||||
| `consolidation_threshold` | `0.85` | Similaridade acima da qual a consolidação é ativada no save. Defina `1.0` para desativar. |
|
||||
| `consolidation_limit` | `5` | Máx. de registros existentes para comparar durante consolidação. |
|
||||
| `default_importance` | `0.5` | Importância atribuída quando não fornecida e a análise LLM é pulada. |
|
||||
| `batch_dedup_threshold` | `0.98` | Similaridade de cosseno para descartar quase-duplicatas dentro de um batch `remember_many()`. |
|
||||
| `confidence_threshold_high` | `0.8` | Confiança de recall acima da qual resultados são retornados diretamente. |
|
||||
| `confidence_threshold_low` | `0.5` | Confiança de recall abaixo da qual exploração mais profunda é ativada. |
|
||||
| `complex_query_threshold` | `0.7` | Para consultas complexas, explorar mais profundamente abaixo desta confiança. |
|
||||
| `exploration_budget` | `1` | Número de rodadas de exploração por LLM durante recall profundo. |
|
||||
| `query_analysis_threshold` | `200` | Consultas menores que isso (em caracteres) pulam análise LLM durante recall profundo. |
|
||||
|
||||
@@ -73,6 +73,8 @@ Quando este flow é executado, ele irá:
|
||||
| `default_outcome` | `str` | Não | Outcome a usar se nenhum feedback for fornecido. Deve estar em `emit` |
|
||||
| `metadata` | `dict` | Não | Dados adicionais para integrações enterprise |
|
||||
| `provider` | `HumanFeedbackProvider` | Não | Provider customizado para feedback assíncrono/não-bloqueante. Veja [Feedback Humano Assíncrono](#feedback-humano-assíncrono-não-bloqueante) |
|
||||
| `learn` | `bool` | Não | Habilitar aprendizado HITL: destila lições do feedback e pré-revisa saídas futuras. Padrão `False`. Veja [Aprendendo com Feedback](#aprendendo-com-feedback) |
|
||||
| `learn_limit` | `int` | Não | Máximo de lições passadas para recuperar na pré-revisão. Padrão `5` |
|
||||
|
||||
### Uso Básico (Sem Roteamento)
|
||||
|
||||
@@ -576,6 +578,64 @@ Se você está usando um framework web assíncrono (FastAPI, aiohttp, Slack Bolt
|
||||
5. **Persistência automática**: O estado é automaticamente salvo quando `HumanFeedbackPending` é lançado e usa `SQLiteFlowPersistence` por padrão
|
||||
6. **Persistência customizada**: Passe uma instância de persistência customizada para `from_pending()` se necessário
|
||||
|
||||
## Aprendendo com Feedback
|
||||
|
||||
O parâmetro `learn=True` habilita um ciclo de feedback entre revisores humanos e o sistema de memória. Quando habilitado, o sistema melhora progressivamente suas saídas aprendendo com correções humanas anteriores.
|
||||
|
||||
### Como Funciona
|
||||
|
||||
1. **Após o feedback**: O LLM extrai lições generalizáveis da saída + feedback e as armazena na memória com `source="hitl"`. Se o feedback for apenas aprovação (ex: "parece bom"), nada é armazenado.
|
||||
2. **Antes da próxima revisão**: Lições HITL passadas são recuperadas da memória e aplicadas pelo LLM para melhorar a saída antes que o humano a veja.
|
||||
|
||||
Com o tempo, o humano vê saídas pré-revisadas progressivamente melhores porque cada correção informa revisões futuras.
|
||||
|
||||
### Exemplo
|
||||
|
||||
```python Code
|
||||
class ArticleReviewFlow(Flow):
|
||||
@start()
|
||||
@human_feedback(
|
||||
message="Review this article draft:",
|
||||
emit=["approved", "needs_revision"],
|
||||
llm="gpt-4o-mini",
|
||||
learn=True, # enable HITL learning
|
||||
)
|
||||
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...")
|
||||
```
|
||||
|
||||
**Primeira execução**: O humano vê a saída bruta e diz "Sempre inclua citações para afirmações factuais." A lição é destilada e armazenada na memória.
|
||||
|
||||
**Segunda execução**: O sistema recupera a lição sobre citações, pré-revisa a saída para adicionar citações e então mostra a versão melhorada. O trabalho do humano muda de "corrigir tudo" para "identificar o que o sistema deixou passar."
|
||||
|
||||
### Configuração
|
||||
|
||||
| Parâmetro | Padrão | Descrição |
|
||||
|-----------|--------|-----------|
|
||||
| `learn` | `False` | Habilitar aprendizado HITL |
|
||||
| `learn_limit` | `5` | Máximo de lições passadas para recuperar na pré-revisão |
|
||||
|
||||
### Decisões de Design Principais
|
||||
|
||||
- **Mesmo LLM para tudo**: O parâmetro `llm` no decorador é compartilhado pelo mapeamento de outcome, destilação de lições e pré-revisão. Não é necessário configurar múltiplos modelos.
|
||||
- **Saída estruturada**: Tanto a destilação quanto a pré-revisão usam function calling com modelos Pydantic quando o LLM suporta, com fallback para parsing de texto caso contrário.
|
||||
- **Armazenamento não-bloqueante**: Lições são armazenadas via `remember_many()` que executa em uma thread em segundo plano -- o flow continua imediatamente.
|
||||
- **Degradação graciosa**: Se o LLM falhar durante a destilação, nada é armazenado. Se falhar durante a pré-revisão, a saída bruta é mostrada. Nenhuma falha bloqueia o flow.
|
||||
- **Sem escopo/categorias necessários**: Ao armazenar lições, apenas `source` é passado. O pipeline de codificação infere escopo, categorias e importância automaticamente.
|
||||
|
||||
<Note>
|
||||
`learn=True` requer que o Flow tenha memória disponível. Flows obtêm memória automaticamente por padrão, mas se você a desabilitou com `_skip_auto_memory`, o aprendizado HITL será silenciosamente ignorado.
|
||||
</Note>
|
||||
|
||||
|
||||
## Documentação Relacionada
|
||||
|
||||
- [Visão Geral de Flows](/pt-BR/concepts/flows) - Aprenda sobre CrewAI Flows
|
||||
@@ -583,3 +643,4 @@ Se você está usando um framework web assíncrono (FastAPI, aiohttp, Slack Bolt
|
||||
- [Persistência de Flows](/pt-BR/concepts/flows#persistence) - Persistindo estado de flows
|
||||
- [Roteamento com @router](/pt-BR/concepts/flows#router) - Mais sobre roteamento condicional
|
||||
- [Input Humano na Execução](/pt-BR/learn/human-input-on-execution) - Input humano no nível de task
|
||||
- [Memória](/pt-BR/concepts/memory) - O sistema unificado de memória usado pelo aprendizado HITL
|
||||
|
||||
Reference in New Issue
Block a user