mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-11 09:08:31 +00:00
Some checks failed
Notify Downstream / notify-downstream (push) Has been cancelled
* feat: implement tool usage limit exception handling - Introduced `ToolUsageLimitExceeded` exception to manage maximum usage limits for tools. - Enhanced `CrewStructuredTool` to check and raise this exception when the usage limit is reached. - Updated `_run` and `_execute` methods to include usage limit checks and handle exceptions appropriately, improving reliability and user feedback. * feat: enhance PlusAPI and ToolUsage with task metadata - Removed the `send_trace_batch` method from PlusAPI to streamline the API. - Added timeout parameters to trace event methods in PlusAPI for improved reliability. - Updated ToolUsage to include task metadata (task name and ID) in event emissions, enhancing traceability and context during tool usage. - Refactored event handling in LLM and ToolUsage events to ensure task information is consistently captured. * feat: enhance memory and event handling with task and agent metadata - Added task and agent metadata to various memory and event classes, improving traceability and context during memory operations. - Updated the `ContextualMemory` and `Memory` classes to associate tasks and agents, allowing for better context management. - Enhanced event emissions in `LLM`, `ToolUsage`, and memory events to include task and agent information, facilitating improved debugging and monitoring. - Refactored event handling to ensure consistent capture of task and agent details across the system. * drop * refactor: clean up unused imports in memory and event modules - Removed unused TYPE_CHECKING imports from long_term_memory.py to streamline the code. - Eliminated unnecessary import from memory_events.py, enhancing clarity and maintainability. * fix memory tests * fix task_completed payload * fix: remove unused test agent variable in external memory tests * refactor: remove unused agent parameter from Memory class save method - Eliminated the agent parameter from the save method in the Memory class to streamline the code and improve clarity. - Updated the TraceBatchManager class by moving initialization of attributes into the constructor for better organization and readability. * refactor: enhance ExecutionState and ReasoningEvent classes with optional task and agent identifiers - Added optional `current_agent_id` and `current_task_id` attributes to the `ExecutionState` class for better tracking of agent and task states. - Updated the `from_task` attribute in the `ReasoningEvent` class to use `Optional[Any]` instead of a specific type, improving flexibility in event handling. * refactor: update ExecutionState class by removing unused agent and task identifiers - Removed the `current_agent_id` and `current_task_id` attributes from the `ExecutionState` class to simplify the code and enhance clarity. - Adjusted the import statements to include `Optional` for better type handling. * refactor: streamline LLM event handling in LiteAgent - Removed unused LLM event emissions (LLMCallStartedEvent, LLMCallCompletedEvent, LLMCallFailedEvent) from the LiteAgent class to simplify the code and improve performance. - Adjusted the flow of LLM response handling by eliminating unnecessary event bus interactions, enhancing clarity and maintainability. * flow ownership and not emitting events when a crew is done * refactor: remove unused agent parameter from ShortTermMemory save method - Eliminated the agent parameter from the save method in the ShortTermMemory class to streamline the code and improve clarity. - This change enhances the maintainability of the memory management system by reducing unnecessary complexity. * runtype check fix * fixing tests * fix lints * fix: update event assertions in test_llm_emits_event_with_lite_agent - Adjusted the expected counts for completed and started events in the test to reflect the correct behavior of the LiteAgent. - Updated assertions for agent roles and IDs to match the expected values after recent changes in event handling. * fix: update task name assertions in event tests - Modified assertions in `test_stream_llm_emits_event_with_task_and_agent_info` and `test_llm_emits_event_with_task_and_agent_info` to use `task.description` as a fallback for `task.name`. This ensures that the tests correctly validate the task name even when it is not explicitly set. * fix: update test assertions for output values and improve readability - Updated assertions in `test_output_json_dict_hierarchical` to reflect the correct expected score value. - Enhanced readability of assertions in `test_output_pydantic_to_another_task` and `test_key` by formatting the error messages for clarity. - These changes ensure that the tests accurately validate the expected outputs and improve overall code quality. * test fixes * fix crew_test * added another fixture * fix: ensure agent and task assignments in contextual memory are conditional - Updated the ContextualMemory class to check for the existence of short-term, long-term, external, and extended memory before assigning agent and task attributes. This prevents potential attribute errors when memory types are not initialized.
356 lines
11 KiB
Python
356 lines
11 KiB
Python
from unittest.mock import MagicMock, patch, ANY
|
|
from collections import defaultdict
|
|
from crewai.utilities.events import crewai_event_bus
|
|
from crewai.utilities.events.memory_events import (
|
|
MemorySaveStartedEvent,
|
|
MemorySaveCompletedEvent,
|
|
MemoryQueryStartedEvent,
|
|
MemoryQueryCompletedEvent,
|
|
)
|
|
import pytest
|
|
from mem0.memory.main import Memory
|
|
|
|
from crewai.agent import Agent
|
|
from crewai.crew import Crew, Process
|
|
from crewai.memory.external.external_memory import ExternalMemory
|
|
from crewai.memory.external.external_memory_item import ExternalMemoryItem
|
|
from crewai.memory.storage.interface import Storage
|
|
from crewai.task import Task
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_mem0_memory():
|
|
mock_memory = MagicMock(spec=Memory)
|
|
return mock_memory
|
|
|
|
|
|
@pytest.fixture
|
|
def patch_configure_mem0(mock_mem0_memory):
|
|
with patch(
|
|
"crewai.memory.external.external_memory.ExternalMemory._configure_mem0",
|
|
return_value=mock_mem0_memory,
|
|
) as mocked:
|
|
yield mocked
|
|
|
|
|
|
@pytest.fixture
|
|
def external_memory_with_mocked_config(patch_configure_mem0):
|
|
embedder_config = {"provider": "mem0"}
|
|
external_memory = ExternalMemory(embedder_config=embedder_config)
|
|
return external_memory
|
|
|
|
|
|
@pytest.fixture
|
|
def crew_with_external_memory(external_memory_with_mocked_config, patch_configure_mem0):
|
|
agent = Agent(
|
|
role="Researcher",
|
|
goal="Search relevant data and provide results",
|
|
backstory="You are a researcher at a leading tech think tank.",
|
|
tools=[],
|
|
verbose=True,
|
|
)
|
|
|
|
task = Task(
|
|
description="Perform a search on specific topics.",
|
|
expected_output="A list of relevant URLs based on the search query.",
|
|
agent=agent,
|
|
)
|
|
|
|
crew = Crew(
|
|
agents=[agent],
|
|
tasks=[task],
|
|
verbose=True,
|
|
process=Process.sequential,
|
|
memory=True,
|
|
external_memory=external_memory_with_mocked_config,
|
|
)
|
|
|
|
return crew
|
|
|
|
|
|
@pytest.fixture
|
|
def crew_with_external_memory_without_memory_flag(
|
|
external_memory_with_mocked_config, patch_configure_mem0
|
|
):
|
|
agent = Agent(
|
|
role="Researcher",
|
|
goal="Search relevant data and provide results",
|
|
backstory="You are a researcher at a leading tech think tank.",
|
|
tools=[],
|
|
verbose=True,
|
|
)
|
|
|
|
task = Task(
|
|
description="Perform a search on specific topics.",
|
|
expected_output="A list of relevant URLs based on the search query.",
|
|
agent=agent,
|
|
)
|
|
|
|
crew = Crew(
|
|
agents=[agent],
|
|
tasks=[task],
|
|
verbose=True,
|
|
process=Process.sequential,
|
|
external_memory=external_memory_with_mocked_config,
|
|
)
|
|
|
|
return crew
|
|
|
|
|
|
def test_external_memory_initialization(external_memory_with_mocked_config):
|
|
assert external_memory_with_mocked_config is not None
|
|
assert isinstance(external_memory_with_mocked_config, ExternalMemory)
|
|
|
|
|
|
def test_external_memory_save(external_memory_with_mocked_config):
|
|
memory_item = ExternalMemoryItem(
|
|
value="test value", metadata={"task": "test_task"}, agent="test_agent"
|
|
)
|
|
|
|
with patch.object(ExternalMemory, "save") as mock_save:
|
|
external_memory_with_mocked_config.save(
|
|
value=memory_item.value,
|
|
metadata=memory_item.metadata,
|
|
agent=memory_item.agent,
|
|
)
|
|
|
|
mock_save.assert_called_once_with(
|
|
value=memory_item.value,
|
|
metadata=memory_item.metadata,
|
|
agent=memory_item.agent,
|
|
)
|
|
|
|
|
|
def test_external_memory_reset(external_memory_with_mocked_config):
|
|
with patch(
|
|
"crewai.memory.external.external_memory.ExternalMemory.reset"
|
|
) as mock_reset:
|
|
external_memory_with_mocked_config.reset()
|
|
mock_reset.assert_called_once()
|
|
|
|
|
|
def test_external_memory_supported_storages():
|
|
supported_storages = ExternalMemory.external_supported_storages()
|
|
assert "mem0" in supported_storages
|
|
assert callable(supported_storages["mem0"])
|
|
|
|
|
|
def test_external_memory_create_storage_invalid_provider():
|
|
embedder_config = {"provider": "invalid_provider", "config": {}}
|
|
|
|
with pytest.raises(ValueError, match="Provider invalid_provider not supported"):
|
|
ExternalMemory.create_storage(None, embedder_config)
|
|
|
|
|
|
def test_external_memory_create_storage_missing_provider():
|
|
embedder_config = {"config": {}}
|
|
|
|
with pytest.raises(
|
|
ValueError, match="embedder_config must include a 'provider' key"
|
|
):
|
|
ExternalMemory.create_storage(None, embedder_config)
|
|
|
|
|
|
def test_external_memory_create_storage_missing_config():
|
|
with pytest.raises(ValueError, match="embedder_config is required"):
|
|
ExternalMemory.create_storage(None, None)
|
|
|
|
|
|
def test_crew_with_external_memory_initialization(crew_with_external_memory):
|
|
assert crew_with_external_memory._external_memory is not None
|
|
assert isinstance(crew_with_external_memory._external_memory, ExternalMemory)
|
|
assert crew_with_external_memory._external_memory.crew == crew_with_external_memory
|
|
|
|
|
|
@pytest.mark.parametrize("mem_type", ["external", "all"])
|
|
def test_crew_external_memory_reset(mem_type, crew_with_external_memory):
|
|
with patch(
|
|
"crewai.memory.external.external_memory.ExternalMemory.reset"
|
|
) as mock_reset:
|
|
crew_with_external_memory.reset_memories(mem_type)
|
|
mock_reset.assert_called_once()
|
|
|
|
|
|
@pytest.mark.parametrize("mem_method", ["search", "save"])
|
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
|
def test_crew_external_memory_save_with_memory_flag(
|
|
mem_method, crew_with_external_memory
|
|
):
|
|
with patch(
|
|
f"crewai.memory.external.external_memory.ExternalMemory.{mem_method}"
|
|
) as mock_method:
|
|
crew_with_external_memory.kickoff()
|
|
assert mock_method.call_count > 0
|
|
|
|
|
|
@pytest.mark.parametrize("mem_method", ["search", "save"])
|
|
@pytest.mark.vcr(filter_headers=["authorization"])
|
|
def test_crew_external_memory_save_using_crew_without_memory_flag(
|
|
mem_method, crew_with_external_memory_without_memory_flag
|
|
):
|
|
with patch(
|
|
f"crewai.memory.external.external_memory.ExternalMemory.{mem_method}"
|
|
) as mock_method:
|
|
crew_with_external_memory_without_memory_flag.kickoff()
|
|
assert mock_method.call_count > 0
|
|
|
|
|
|
@pytest.fixture
|
|
def custom_storage():
|
|
class CustomStorage(Storage):
|
|
def __init__(self):
|
|
self.memories = []
|
|
|
|
def save(self, value, metadata=None, agent=None):
|
|
self.memories.append({"value": value, "metadata": metadata, "agent": agent})
|
|
|
|
def search(self, query, limit=10, score_threshold=0.5):
|
|
return self.memories
|
|
|
|
def reset(self):
|
|
self.memories = []
|
|
|
|
custom_storage = CustomStorage()
|
|
return custom_storage
|
|
|
|
|
|
def test_external_memory_custom_storage(custom_storage, crew_with_external_memory):
|
|
external_memory = ExternalMemory(storage=custom_storage)
|
|
|
|
# by ensuring the crew is set, we can test that the storage is used
|
|
external_memory.set_crew(crew_with_external_memory)
|
|
|
|
test_value = "test value"
|
|
test_metadata = {"source": "test"}
|
|
external_memory.save(value=test_value, metadata=test_metadata)
|
|
|
|
results = external_memory.search("test")
|
|
assert len(results) == 1
|
|
assert results[0]["value"] == test_value
|
|
assert results[0]["metadata"] == test_metadata
|
|
|
|
external_memory.reset()
|
|
results = external_memory.search("test")
|
|
assert len(results) == 0
|
|
|
|
|
|
def test_external_memory_search_events(
|
|
custom_storage, external_memory_with_mocked_config
|
|
):
|
|
events = defaultdict(list)
|
|
|
|
external_memory_with_mocked_config.storage = custom_storage
|
|
with crewai_event_bus.scoped_handlers():
|
|
|
|
@crewai_event_bus.on(MemoryQueryStartedEvent)
|
|
def on_search_started(source, event):
|
|
events["MemoryQueryStartedEvent"].append(event)
|
|
|
|
@crewai_event_bus.on(MemoryQueryCompletedEvent)
|
|
def on_search_completed(source, event):
|
|
events["MemoryQueryCompletedEvent"].append(event)
|
|
|
|
external_memory_with_mocked_config.search(
|
|
query="test value",
|
|
limit=3,
|
|
score_threshold=0.35,
|
|
)
|
|
|
|
assert len(events["MemoryQueryStartedEvent"]) == 1
|
|
assert len(events["MemoryQueryCompletedEvent"]) == 1
|
|
|
|
assert dict(events["MemoryQueryStartedEvent"][0]) == {
|
|
"timestamp": ANY,
|
|
"type": "memory_query_started",
|
|
"source_fingerprint": None,
|
|
"source_type": "external_memory",
|
|
"fingerprint_metadata": None,
|
|
"task_id": None,
|
|
"task_name": None,
|
|
"from_task": None,
|
|
"from_agent": None,
|
|
"agent_role": None,
|
|
"agent_id": None,
|
|
"query": "test value",
|
|
"limit": 3,
|
|
"score_threshold": 0.35,
|
|
}
|
|
|
|
assert dict(events["MemoryQueryCompletedEvent"][0]) == {
|
|
"timestamp": ANY,
|
|
"type": "memory_query_completed",
|
|
"source_fingerprint": None,
|
|
"source_type": "external_memory",
|
|
"fingerprint_metadata": None,
|
|
"task_id": None,
|
|
"task_name": None,
|
|
"from_task": None,
|
|
"from_agent": None,
|
|
"agent_role": None,
|
|
"agent_id": None,
|
|
"query": "test value",
|
|
"results": [],
|
|
"limit": 3,
|
|
"score_threshold": 0.35,
|
|
"query_time_ms": ANY,
|
|
}
|
|
|
|
|
|
def test_external_memory_save_events(
|
|
custom_storage, external_memory_with_mocked_config
|
|
):
|
|
events = defaultdict(list)
|
|
|
|
external_memory_with_mocked_config.storage = custom_storage
|
|
|
|
with crewai_event_bus.scoped_handlers():
|
|
|
|
@crewai_event_bus.on(MemorySaveStartedEvent)
|
|
def on_save_started(source, event):
|
|
events["MemorySaveStartedEvent"].append(event)
|
|
|
|
@crewai_event_bus.on(MemorySaveCompletedEvent)
|
|
def on_save_completed(source, event):
|
|
events["MemorySaveCompletedEvent"].append(event)
|
|
|
|
external_memory_with_mocked_config.save(
|
|
value="saving value",
|
|
metadata={"task": "test_task"},
|
|
)
|
|
|
|
assert len(events["MemorySaveStartedEvent"]) == 1
|
|
assert len(events["MemorySaveCompletedEvent"]) == 1
|
|
|
|
assert dict(events["MemorySaveStartedEvent"][0]) == {
|
|
"timestamp": ANY,
|
|
"type": "memory_save_started",
|
|
"source_fingerprint": None,
|
|
"source_type": "external_memory",
|
|
"fingerprint_metadata": None,
|
|
"task_id": None,
|
|
"task_name": None,
|
|
"from_task": None,
|
|
"from_agent": None,
|
|
"agent_role": None,
|
|
"agent_id": None,
|
|
"value": "saving value",
|
|
"metadata": {"task": "test_task"},
|
|
}
|
|
|
|
assert dict(events["MemorySaveCompletedEvent"][0]) == {
|
|
"timestamp": ANY,
|
|
"type": "memory_save_completed",
|
|
"source_fingerprint": None,
|
|
"source_type": "external_memory",
|
|
"fingerprint_metadata": None,
|
|
"task_id": None,
|
|
"task_name": None,
|
|
"from_task": None,
|
|
"from_agent": None,
|
|
"agent_role": None,
|
|
"agent_id": None,
|
|
"value": "saving value",
|
|
"metadata": {"task": "test_task"},
|
|
"save_time_ms": ANY,
|
|
}
|