Merge branch 'main' into lg-few-docs-update

This commit is contained in:
Lucas Gomide
2025-05-15 09:39:14 -03:00
committed by GitHub
17 changed files with 447 additions and 123 deletions

View File

@@ -110,6 +110,8 @@ crewai reset-memories [OPTIONS]
- `-s, --short`: Reset SHORT TERM memory - `-s, --short`: Reset SHORT TERM memory
- `-e, --entities`: Reset ENTITIES memory - `-e, --entities`: Reset ENTITIES memory
- `-k, --kickoff-outputs`: Reset LATEST KICKOFF TASK OUTPUTS - `-k, --kickoff-outputs`: Reset LATEST KICKOFF TASK OUTPUTS
- `-kn, --knowledge`: Reset KNOWLEDGE storage
- `-akn, --agent-knowledge`: Reset AGENT KNOWLEDGE storage
- `-a, --all`: Reset ALL memories - `-a, --all`: Reset ALL memories
Example: Example:

View File

@@ -497,6 +497,13 @@ crew = Crew(
result = crew.kickoff( result = crew.kickoff(
inputs={"question": "What is the storage capacity of the XPS 13?"} inputs={"question": "What is the storage capacity of the XPS 13?"}
) )
# Resetting the agent specific knowledge via crew object
crew.reset_memories(command_type = 'agent_knowledge')
# Resetting the agent specific knowledge via CLI
crewai reset-memories --agent-knowledge
crewai reset-memories -akn
``` ```
<Info> <Info>

View File

@@ -679,6 +679,7 @@ crewai reset-memories [OPTIONS]
| `-e`, `--entities` | Reset ENTITIES memory. | Flag (boolean) | False | | `-e`, `--entities` | Reset ENTITIES memory. | Flag (boolean) | False |
| `-k`, `--kickoff-outputs` | Reset LATEST KICKOFF TASK OUTPUTS. | Flag (boolean) | False | | `-k`, `--kickoff-outputs` | Reset LATEST KICKOFF TASK OUTPUTS. | Flag (boolean) | False |
| `-kn`, `--knowledge` | Reset KNOWLEDEGE storage | Flag (boolean) | False | | `-kn`, `--knowledge` | Reset KNOWLEDEGE storage | Flag (boolean) | False |
| `-akn`, `--agent-knowledge` | Reset AGENT KNOWLEDGE storage | Flag (boolean) | False |
| `-a`, `--all` | Reset ALL memories. | Flag (boolean) | False | | `-a`, `--all` | Reset ALL memories. | Flag (boolean) | False |
Note: To use the cli command you need to have your crew in a file called crew.py in the same directory. Note: To use the cli command you need to have your crew in a file called crew.py in the same directory.
@@ -716,9 +717,11 @@ my_crew.reset_memories(command_type = 'all') # Resets all the memory
| `entities` | Reset ENTITIES memory. | | `entities` | Reset ENTITIES memory. |
| `kickoff_outputs` | Reset LATEST KICKOFF TASK OUTPUTS. | | `kickoff_outputs` | Reset LATEST KICKOFF TASK OUTPUTS. |
| `knowledge` | Reset KNOWLEDGE memory. | | `knowledge` | Reset KNOWLEDGE memory. |
| `agent_knowledge` | Reset AGENT KNOWLEDGE memory. |
| `all` | Reset ALL memories. | | `all` | Reset ALL memories. |
## Benefits of Using CrewAI's Memory System ## Benefits of Using CrewAI's Memory System
- 🦾 **Adaptive Learning:** Crews become more efficient over time, adapting to new information and refining their approach to tasks. - 🦾 **Adaptive Learning:** Crews become more efficient over time, adapting to new information and refining their approach to tasks.

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "crewai" name = "crewai"
version = "0.119.0" version = "0.120.1"
description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks." description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
readme = "README.md" readme = "README.md"
requires-python = ">=3.10,<3.13" requires-python = ">=3.10,<3.13"
@@ -45,7 +45,7 @@ Documentation = "https://docs.crewai.com"
Repository = "https://github.com/crewAIInc/crewAI" Repository = "https://github.com/crewAIInc/crewAI"
[project.optional-dependencies] [project.optional-dependencies]
tools = ["crewai-tools~=0.44.0"] tools = ["crewai-tools~=0.45.0"]
embeddings = [ embeddings = [
"tiktoken~=0.7.0" "tiktoken~=0.7.0"
] ]

View File

@@ -17,7 +17,7 @@ warnings.filterwarnings(
category=UserWarning, category=UserWarning,
module="pydantic.main", module="pydantic.main",
) )
__version__ = "0.119.0" __version__ = "0.120.1"
__all__ = [ __all__ = [
"Agent", "Agent",
"Crew", "Crew",

View File

@@ -1,6 +1,5 @@
import os
from importlib.metadata import version as get_version from importlib.metadata import version as get_version
from typing import Optional, Tuple from typing import Optional
import click import click
@@ -138,12 +137,8 @@ def log_tasks_outputs() -> None:
@click.option("-s", "--short", is_flag=True, help="Reset SHORT TERM memory") @click.option("-s", "--short", is_flag=True, help="Reset SHORT TERM memory")
@click.option("-e", "--entities", is_flag=True, help="Reset ENTITIES memory") @click.option("-e", "--entities", is_flag=True, help="Reset ENTITIES memory")
@click.option("-kn", "--knowledge", is_flag=True, help="Reset KNOWLEDGE storage") @click.option("-kn", "--knowledge", is_flag=True, help="Reset KNOWLEDGE storage")
@click.option( @click.option("-akn", "--agent-knowledge", is_flag=True, help="Reset AGENT KNOWLEDGE storage")
"-k", @click.option("-k","--kickoff-outputs",is_flag=True,help="Reset LATEST KICKOFF TASK OUTPUTS")
"--kickoff-outputs",
is_flag=True,
help="Reset LATEST KICKOFF TASK OUTPUTS",
)
@click.option("-a", "--all", is_flag=True, help="Reset ALL memories") @click.option("-a", "--all", is_flag=True, help="Reset ALL memories")
def reset_memories( def reset_memories(
long: bool, long: bool,
@@ -151,18 +146,20 @@ def reset_memories(
entities: bool, entities: bool,
knowledge: bool, knowledge: bool,
kickoff_outputs: bool, kickoff_outputs: bool,
agent_knowledge: bool,
all: bool, all: bool,
) -> None: ) -> None:
""" """
Reset the crew memories (long, short, entity, latest_crew_kickoff_ouputs). This will delete all the data saved. Reset the crew memories (long, short, entity, latest_crew_kickoff_ouputs, knowledge, agent_knowledge). This will delete all the data saved.
""" """
try: try:
if not all and not (long or short or entities or knowledge or kickoff_outputs): memory_types = [long, short, entities, knowledge, agent_knowledge, kickoff_outputs, all]
if not any(memory_types):
click.echo( click.echo(
"Please specify at least one memory type to reset using the appropriate flags." "Please specify at least one memory type to reset using the appropriate flags."
) )
return return
reset_memories_command(long, short, entities, knowledge, kickoff_outputs, all) reset_memories_command(long, short, entities, knowledge, agent_knowledge, kickoff_outputs, all)
except Exception as e: except Exception as e:
click.echo(f"An error occurred while resetting memories: {e}", err=True) click.echo(f"An error occurred while resetting memories: {e}", err=True)

View File

@@ -10,6 +10,7 @@ def reset_memories_command(
short, short,
entity, entity,
knowledge, knowledge,
agent_knowledge,
kickoff_outputs, kickoff_outputs,
all, all,
) -> None: ) -> None:
@@ -23,10 +24,11 @@ def reset_memories_command(
kickoff_outputs (bool): Whether to reset the latest kickoff task outputs. kickoff_outputs (bool): Whether to reset the latest kickoff task outputs.
all (bool): Whether to reset all memories. all (bool): Whether to reset all memories.
knowledge (bool): Whether to reset the knowledge. knowledge (bool): Whether to reset the knowledge.
agent_knowledge (bool): Whether to reset the agents knowledge.
""" """
try: try:
if not any([long, short, entity, kickoff_outputs, knowledge, all]): if not any([long, short, entity, kickoff_outputs, knowledge, agent_knowledge, all]):
click.echo( click.echo(
"No memory type specified. Please specify at least one type to reset." "No memory type specified. Please specify at least one type to reset."
) )
@@ -67,6 +69,11 @@ def reset_memories_command(
click.echo( click.echo(
f"[Crew ({crew.name if crew.name else crew.id})] Knowledge has been reset." f"[Crew ({crew.name if crew.name else crew.id})] Knowledge has been reset."
) )
if agent_knowledge:
crew.reset_memories(command_type="agent_knowledge")
click.echo(
f"[Crew ({crew.name if crew.name else crew.id})] Agents knowledge has been reset."
)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
click.echo(f"An error occurred while resetting the memories: {e}", err=True) click.echo(f"An error occurred while resetting the memories: {e}", err=True)

View File

@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
authors = [{ name = "Your Name", email = "you@example.com" }] authors = [{ name = "Your Name", email = "you@example.com" }]
requires-python = ">=3.10,<3.13" requires-python = ">=3.10,<3.13"
dependencies = [ dependencies = [
"crewai[tools]>=0.119.0,<1.0.0" "crewai[tools]>=0.120.1,<1.0.0"
] ]
[project.scripts] [project.scripts]

View File

@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
authors = [{ name = "Your Name", email = "you@example.com" }] authors = [{ name = "Your Name", email = "you@example.com" }]
requires-python = ">=3.10,<3.13" requires-python = ">=3.10,<3.13"
dependencies = [ dependencies = [
"crewai[tools]>=0.119.0,<1.0.0", "crewai[tools]>=0.120.1,<1.0.0",
] ]
[project.scripts] [project.scripts]

View File

@@ -5,7 +5,7 @@ description = "Power up your crews with {{folder_name}}"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10,<3.13" requires-python = ">=3.10,<3.13"
dependencies = [ dependencies = [
"crewai[tools]>=0.119.0" "crewai[tools]>=0.120.1"
] ]
[tool.crewai] [tool.crewai]

View File

@@ -1356,7 +1356,7 @@ class Crew(FlowTrackable, BaseModel):
Args: Args:
command_type: Type of memory to reset. command_type: Type of memory to reset.
Valid options: 'long', 'short', 'entity', 'knowledge', Valid options: 'long', 'short', 'entity', 'knowledge', 'agent_knowledge'
'kickoff_outputs', or 'all' 'kickoff_outputs', or 'all'
Raises: Raises:
@@ -1369,6 +1369,7 @@ class Crew(FlowTrackable, BaseModel):
"short", "short",
"entity", "entity",
"knowledge", "knowledge",
"agent_knowledge",
"kickoff_outputs", "kickoff_outputs",
"all", "all",
"external", "external",
@@ -1393,19 +1394,14 @@ class Crew(FlowTrackable, BaseModel):
def _reset_all_memories(self) -> None: def _reset_all_memories(self) -> None:
"""Reset all available memory systems.""" """Reset all available memory systems."""
memory_systems = [ memory_systems = self._get_memory_systems()
("short term", getattr(self, "_short_term_memory", None)),
("entity", getattr(self, "_entity_memory", None)),
("external", getattr(self, "_external_memory", None)),
("long term", getattr(self, "_long_term_memory", None)),
("task output", getattr(self, "_task_output_handler", None)),
("knowledge", getattr(self, "knowledge", None)),
]
for name, system in memory_systems: for memory_type, config in memory_systems.items():
if system is not None: if (system := config.get('system')) is not None:
name = config.get('name')
try: try:
system.reset() reset_fn: Callable = cast(Callable, config.get('reset'))
reset_fn(system)
self._logger.log( self._logger.log(
"info", "info",
f"[Crew ({self.name if self.name else self.id})] {name} memory has been reset", f"[Crew ({self.name if self.name else self.id})] {name} memory has been reset",
@@ -1424,24 +1420,17 @@ class Crew(FlowTrackable, BaseModel):
Raises: Raises:
RuntimeError: If the specified memory system fails to reset RuntimeError: If the specified memory system fails to reset
""" """
reset_functions = { memory_systems = self._get_memory_systems()
"long": (getattr(self, "_long_term_memory", None), "long term"), config = memory_systems[memory_type]
"short": (getattr(self, "_short_term_memory", None), "short term"), system = config.get('system')
"entity": (getattr(self, "_entity_memory", None), "entity"), name = config.get('name')
"knowledge": (getattr(self, "knowledge", None), "knowledge"),
"kickoff_outputs": (
getattr(self, "_task_output_handler", None),
"task output",
),
"external": (getattr(self, "_external_memory", None), "external"),
}
memory_system, name = reset_functions[memory_type] if system is None:
if memory_system is None:
raise RuntimeError(f"{name} memory system is not initialized") raise RuntimeError(f"{name} memory system is not initialized")
try: try:
memory_system.reset() reset_fn: Callable = cast(Callable, config.get('reset'))
reset_fn(system)
self._logger.log( self._logger.log(
"info", "info",
f"[Crew ({self.name if self.name else self.id})] {name} memory has been reset", f"[Crew ({self.name if self.name else self.id})] {name} memory has been reset",
@@ -1450,3 +1439,64 @@ class Crew(FlowTrackable, BaseModel):
raise RuntimeError( raise RuntimeError(
f"[Crew ({self.name if self.name else self.id})] Failed to reset {name} memory: {str(e)}" f"[Crew ({self.name if self.name else self.id})] Failed to reset {name} memory: {str(e)}"
) from e ) from e
def _get_memory_systems(self):
"""Get all available memory systems with their configuration.
Returns:
Dict containing all memory systems with their reset functions and display names.
"""
def default_reset(memory):
return memory.reset()
def knowledge_reset(memory):
return self.reset_knowledge(memory)
# Get knowledge for agents
agent_knowledges = [getattr(agent, "knowledge", None) for agent in self.agents
if getattr(agent, "knowledge", None) is not None]
# Get knowledge for crew and agents
crew_knowledge = getattr(self, "knowledge", None)
crew_and_agent_knowledges = ([crew_knowledge] if crew_knowledge is not None else []) + agent_knowledges
return {
'short': {
'system': getattr(self, "_short_term_memory", None),
'reset': default_reset,
'name': 'Short Term'
},
'entity': {
'system': getattr(self, "_entity_memory", None),
'reset': default_reset,
'name': 'Entity'
},
'external': {
'system': getattr(self, "_external_memory", None),
'reset': default_reset,
'name': 'External'
},
'long': {
'system': getattr(self, "_long_term_memory", None),
'reset': default_reset,
'name': 'Long Term'
},
'kickoff_outputs': {
'system': getattr(self, "_task_output_handler", None),
'reset': default_reset,
'name': 'Task Output'
},
'knowledge': {
'system': crew_and_agent_knowledges if crew_and_agent_knowledges else None,
'reset': knowledge_reset,
'name': 'Crew Knowledge and Agent Knowledge'
},
'agent_knowledge': {
'system': agent_knowledges if agent_knowledges else None,
'reset': knowledge_reset,
'name': 'Agent Knowledge'
}
}
def reset_knowledge(self, knowledges: List[Knowledge]) -> None:
"""Reset crew and agent knowledge storage."""
for ks in knowledges:
ks.reset()

View File

@@ -59,7 +59,7 @@ def interpolate_only(
# The regex pattern to find valid variable placeholders # The regex pattern to find valid variable placeholders
# Matches {variable_name} where variable_name starts with a letter/underscore # Matches {variable_name} where variable_name starts with a letter/underscore
# and contains only letters, numbers, and underscores # and contains only letters, numbers, and underscores
pattern = r"\{([A-Za-z_][A-Za-z0-9_]*)\}" pattern = r"\{([A-Za-z_][A-Za-z0-9_\-]*)\}"
# Find all matching variables in the input string # Find all matching variables in the input string
variables = re.findall(pattern, input_string) variables = re.findall(pattern, input_string)

View File

@@ -0,0 +1,121 @@
interactions:
- request:
body: '{"messages": [{"role": "system", "content": "You are Researcher. You''re
an expert researcher, specialized in technology, software engineering, AI and
startups. You work as a freelancer and is now working on doing research and
analysis for a new customer.\nYour personal goal is: be an assistant that responds
with say hello world\nTo give my best complete final answer to the task respond
using the exact following format:\n\nThought: I now can give a great answer\nFinal
Answer: Your final answer must be the great and the most complete as possible,
it must be outcome described.\n\nI MUST use these formats, my job depends on
it!"}, {"role": "user", "content": "\nCurrent Task: be an assistant that responds
with say hello world\n\nThis is the expected criteria for your final answer:
The response should be addressing: say hello world\nyou MUST return the actual
complete content as the final answer, not a summary.\n\nBegin! This is VERY
important to you, use the tools available and give your best Final Answer, your
job depends on it!\n\nThought:"}], "model": "gpt-4o-mini", "stop": ["\nObservation:"]}'
headers:
accept:
- application/json
accept-encoding:
- gzip, deflate, zstd
connection:
- keep-alive
content-length:
- '1108'
content-type:
- application/json
host:
- api.openai.com
user-agent:
- OpenAI/Python 1.68.2
x-stainless-arch:
- arm64
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- MacOS
x-stainless-package-version:
- 1.68.2
x-stainless-raw-response:
- 'true'
x-stainless-read-timeout:
- '600.0'
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.12.9
method: POST
uri: https://api.openai.com/v1/chat/completions
response:
body:
string: !!binary |
H4sIAAAAAAAAA4xSTW/UMBC951cMPicoScMu3RuIooUDcOOrVeS1J4mp4zG2sy2q9r9XTrqbtBSJ
iyX7zXt+b2buEgCmJNsAEx0Porc6e/vt4nf3xVxweVZ+3v/Q17fF9+pjs92+O//0iqWRQbtfKMKR
9VJQbzUGRWaChUMeMKoW62pdropVfjYCPUnUkdbakFWU9cqorMzLKsvXWfH6gd2REujZBn4mAAB3
4xl9Gom3bAN5enzp0XveItucigCYIx1fGPde+cBNYOkMCjIBzWj9Axi6AcENtGqPwKGNtoEbf4MO
4NK8V4ZreDPeN7BFrSmFr+S0fLGUdNgMnsdYZtB6AXBjKPDYljHM1QNyONnX1FpHO/+EyhpllO9q
h9yTiVZ9IMtG9JAAXI1tGh4lZ9ZRb0Md6BrH78p8NemxeTozWhzBQIHrBass02f0aomBK+0XjWaC
iw7lTJ2nwgepaAEki9R/u3lOe0quTPs/8jMgBNqAsrYOpRKPE89lDuPy/qvs1OXRMPPo9kpgHRS6
OAmJDR/0tFLM//EB+7pRpkVnnZr2qrH1utjl5bo6bzhLDsk9AAAA//8DAAxaM/dlAwAA
headers:
CF-RAY:
- 93fdd19cdbfb6428-SJC
Connection:
- keep-alive
Content-Encoding:
- gzip
Content-Type:
- application/json
Date:
- Wed, 14 May 2025 22:26:43 GMT
Server:
- cloudflare
Set-Cookie:
- __cf_bm=eCtOgOCsKt_ybdNPdtFAocCmuQbNltR52chaHVe7Y_Q-1747261603-1.0.1.1-827eoA7wHS5SOkTsTqoMq6OSioi0VznQBVjvmabNSVX1bf5PpWZvblw58iggZ_wyKDB0EuVoeLKFspgBJa0kuQYR17hu43Y2C14sgdvOXIE;
path=/; expires=Wed, 14-May-25 22:56:43 GMT; domain=.api.openai.com; HttpOnly;
Secure; SameSite=None
- _cfuvid=QUa5MnypdaVxO826bwdQaN4G6CBEV8HYVV.7OLF.qvQ-1747261603742-0.0.1.1-604800000;
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
access-control-expose-headers:
- X-Request-ID
alt-svc:
- h3=":443"; ma=86400
cf-cache-status:
- DYNAMIC
openai-organization:
- crewai-iuxna1
openai-processing-ms:
- '307'
openai-version:
- '2020-10-01'
strict-transport-security:
- max-age=31536000; includeSubDomains; preload
x-envoy-upstream-service-time:
- '309'
x-ratelimit-limit-requests:
- '30000'
x-ratelimit-limit-tokens:
- '150000000'
x-ratelimit-remaining-requests:
- '29999'
x-ratelimit-remaining-tokens:
- '149999757'
x-ratelimit-reset-requests:
- 2ms
x-ratelimit-reset-tokens:
- 0s
x-request-id:
- req_61d9066e0258b7095517f9f9c01d38e9
status:
code: 200
message: OK
version: 1

View File

@@ -162,8 +162,18 @@ def test_reset_knowledge(mock_get_crews, runner):
assert call_count == 1, "reset_memories should have been called once" assert call_count == 1, "reset_memories should have been called once"
def test_reset_memory_from_many_crews(mock_get_crews, runner): def test_reset_agent_knowledge(mock_get_crews, runner):
result = runner.invoke(reset_memories, ["--agent-knowledge"])
call_count = 0
for crew in mock_get_crews.return_value:
crew.reset_memories.assert_called_once_with(command_type="agent_knowledge")
assert f"[Crew ({crew.name})] Agents knowledge has been reset." in result.output
call_count += 1
assert call_count == 1, "reset_memories should have been called once"
def test_reset_memory_from_many_crews(mock_get_crews, runner):
crews = [] crews = []
for crew_id in ["id-1234", "id-5678"]: for crew_id in ["id-1234", "id-5678"]:
mock_crew = mock.Mock(spec=Crew) mock_crew = mock.Mock(spec=Crew)

View File

@@ -14,6 +14,7 @@ from crewai.agents import CacheHandler
from crewai.crew import Crew from crewai.crew import Crew
from crewai.crews.crew_output import CrewOutput from crewai.crews.crew_output import CrewOutput
from crewai.flow import Flow, start from crewai.flow import Flow, start
from crewai.knowledge.knowledge import Knowledge
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
from crewai.llm import LLM from crewai.llm import LLM
from crewai.memory.contextual.contextual_memory import ContextualMemory from crewai.memory.contextual.contextual_memory import ContextualMemory
@@ -4403,3 +4404,165 @@ def test_sets_parent_flow_when_inside_flow(researcher, writer):
flow = MyFlow() flow = MyFlow()
result = flow.kickoff() result = flow.kickoff()
assert result.parent_flow is flow assert result.parent_flow is flow
def test_reset_knowledge_with_no_crew_knowledge(researcher,writer):
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=[
Task(description="Task 1", expected_output="output", agent=researcher),
Task(description="Task 2", expected_output="output", agent=writer),
]
)
with pytest.raises(RuntimeError) as excinfo:
crew.reset_memories(command_type='knowledge')
# Optionally, you can also check the error message
assert "Crew Knowledge and Agent Knowledge memory system is not initialized" in str(excinfo.value) # Replace with the expected message
def test_reset_knowledge_with_only_crew_knowledge(researcher,writer):
mock_ks = MagicMock(spec=Knowledge)
with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge:
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=[
Task(description="Task 1", expected_output="output", agent=researcher),
Task(description="Task 2", expected_output="output", agent=writer),
],
knowledge=mock_ks
)
crew.reset_memories(command_type='knowledge')
mock_reset_agent_knowledge.assert_called_once_with([mock_ks])
def test_reset_knowledge_with_crew_and_agent_knowledge(researcher,writer):
mock_ks_crew = MagicMock(spec=Knowledge)
mock_ks_research = MagicMock(spec=Knowledge)
mock_ks_writer = MagicMock(spec=Knowledge)
researcher.knowledge = mock_ks_research
writer.knowledge = mock_ks_writer
with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge:
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=[
Task(description="Task 1", expected_output="output", agent=researcher),
Task(description="Task 2", expected_output="output", agent=writer),
],
knowledge=mock_ks_crew
)
crew.reset_memories(command_type='knowledge')
mock_reset_agent_knowledge.assert_called_once_with([mock_ks_crew,mock_ks_research,mock_ks_writer])
def test_reset_knowledge_with_only_agent_knowledge(researcher,writer):
mock_ks_research = MagicMock(spec=Knowledge)
mock_ks_writer = MagicMock(spec=Knowledge)
researcher.knowledge = mock_ks_research
writer.knowledge = mock_ks_writer
with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge:
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=[
Task(description="Task 1", expected_output="output", agent=researcher),
Task(description="Task 2", expected_output="output", agent=writer),
],
)
crew.reset_memories(command_type='knowledge')
mock_reset_agent_knowledge.assert_called_once_with([mock_ks_research,mock_ks_writer])
def test_reset_agent_knowledge_with_no_agent_knowledge(researcher,writer):
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=[
Task(description="Task 1", expected_output="output", agent=researcher),
Task(description="Task 2", expected_output="output", agent=writer),
],
)
with pytest.raises(RuntimeError) as excinfo:
crew.reset_memories(command_type='agent_knowledge')
# Optionally, you can also check the error message
assert "Agent Knowledge memory system is not initialized" in str(excinfo.value) # Replace with the expected message
def test_reset_agent_knowledge_with_only_crew_knowledge(researcher,writer):
mock_ks = MagicMock(spec=Knowledge)
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=[
Task(description="Task 1", expected_output="output", agent=researcher),
Task(description="Task 2", expected_output="output", agent=writer),
],
knowledge=mock_ks
)
with pytest.raises(RuntimeError) as excinfo:
crew.reset_memories(command_type='agent_knowledge')
# Optionally, you can also check the error message
assert "Agent Knowledge memory system is not initialized" in str(excinfo.value) # Replace with the expected message
def test_reset_agent_knowledge_with_crew_and_agent_knowledge(researcher,writer):
mock_ks_crew = MagicMock(spec=Knowledge)
mock_ks_research = MagicMock(spec=Knowledge)
mock_ks_writer = MagicMock(spec=Knowledge)
researcher.knowledge = mock_ks_research
writer.knowledge = mock_ks_writer
with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge:
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=[
Task(description="Task 1", expected_output="output", agent=researcher),
Task(description="Task 2", expected_output="output", agent=writer),
],
knowledge=mock_ks_crew
)
crew.reset_memories(command_type='agent_knowledge')
mock_reset_agent_knowledge.assert_called_once_with([mock_ks_research,mock_ks_writer])
def test_reset_agent_knowledge_with_only_agent_knowledge(researcher,writer):
mock_ks_research = MagicMock(spec=Knowledge)
mock_ks_writer = MagicMock(spec=Knowledge)
researcher.knowledge = mock_ks_research
writer.knowledge = mock_ks_writer
with patch.object(Crew,'reset_knowledge') as mock_reset_agent_knowledge:
crew = Crew(
agents=[researcher, writer],
process=Process.sequential,
tasks=[
Task(description="Task 1", expected_output="output", agent=researcher),
Task(description="Task 2", expected_output="output", agent=writer),
],
)
crew.reset_memories(command_type='agent_knowledge')
mock_reset_agent_knowledge.assert_called_once_with([mock_ks_research,mock_ks_writer])

View File

@@ -837,9 +837,6 @@ def test_interpolate_inputs():
def test_interpolate_only(): def test_interpolate_only():
"""Test the interpolate_only method for various scenarios including JSON structure preservation.""" """Test the interpolate_only method for various scenarios including JSON structure preservation."""
task = Task(
description="Unused in this test", expected_output="Unused in this test"
)
# Test JSON structure preservation # Test JSON structure preservation
json_string = '{"info": "Look at {placeholder}", "nested": {"val": "{nestedVal}"}}' json_string = '{"info": "Look at {placeholder}", "nested": {"val": "{nestedVal}"}}'
@@ -871,10 +868,6 @@ def test_interpolate_only():
def test_interpolate_only_with_dict_inside_expected_output(): def test_interpolate_only_with_dict_inside_expected_output():
"""Test the interpolate_only method for various scenarios including JSON structure preservation.""" """Test the interpolate_only method for various scenarios including JSON structure preservation."""
task = Task(
description="Unused in this test",
expected_output="Unused in this test: {questions}",
)
json_string = '{"questions": {"main_question": "What is the user\'s name?", "secondary_question": "What is the user\'s age?"}}' json_string = '{"questions": {"main_question": "What is the user\'s name?", "secondary_question": "What is the user\'s age?"}}'
result = interpolate_only( result = interpolate_only(
@@ -1094,11 +1087,6 @@ def test_task_execution_times():
def test_interpolate_with_list_of_strings(): def test_interpolate_with_list_of_strings():
task = Task(
description="Test list interpolation",
expected_output="List: {items}",
)
# Test simple list of strings # Test simple list of strings
input_str = "Available items: {items}" input_str = "Available items: {items}"
inputs = {"items": ["apple", "banana", "cherry"]} inputs = {"items": ["apple", "banana", "cherry"]}
@@ -1112,11 +1100,6 @@ def test_interpolate_with_list_of_strings():
def test_interpolate_with_list_of_dicts(): def test_interpolate_with_list_of_dicts():
task = Task(
description="Test list of dicts interpolation",
expected_output="People: {people}",
)
input_data = { input_data = {
"people": [ "people": [
{"name": "Alice", "age": 30, "skills": ["Python", "AI"]}, {"name": "Alice", "age": 30, "skills": ["Python", "AI"]},
@@ -1137,11 +1120,6 @@ def test_interpolate_with_list_of_dicts():
def test_interpolate_with_nested_structures(): def test_interpolate_with_nested_structures():
task = Task(
description="Test nested structures",
expected_output="Company: {company}",
)
input_data = { input_data = {
"company": { "company": {
"name": "TechCorp", "name": "TechCorp",
@@ -1165,11 +1143,6 @@ def test_interpolate_with_nested_structures():
def test_interpolate_with_special_characters(): def test_interpolate_with_special_characters():
task = Task(
description="Test special characters in dicts",
expected_output="Data: {special_data}",
)
input_data = { input_data = {
"special_data": { "special_data": {
"quotes": """This has "double" and 'single' quotes""", "quotes": """This has "double" and 'single' quotes""",
@@ -1188,11 +1161,6 @@ def test_interpolate_with_special_characters():
def test_interpolate_mixed_types(): def test_interpolate_mixed_types():
task = Task(
description="Test mixed type interpolation",
expected_output="Mixed: {data}",
)
input_data = { input_data = {
"data": { "data": {
"name": "Test Dataset", "name": "Test Dataset",
@@ -1214,11 +1182,6 @@ def test_interpolate_mixed_types():
def test_interpolate_complex_combination(): def test_interpolate_complex_combination():
task = Task(
description="Test complex combination",
expected_output="Report: {report}",
)
input_data = { input_data = {
"report": [ "report": [
{ {
@@ -1243,11 +1206,6 @@ def test_interpolate_complex_combination():
def test_interpolate_invalid_type_validation(): def test_interpolate_invalid_type_validation():
task = Task(
description="Test invalid type validation",
expected_output="Should never reach here",
)
# Test with invalid top-level type # Test with invalid top-level type
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
interpolate_only("{data}", {"data": set()}) # type: ignore we are purposely testing this failure interpolate_only("{data}", {"data": set()}) # type: ignore we are purposely testing this failure
@@ -1268,11 +1226,6 @@ def test_interpolate_invalid_type_validation():
def test_interpolate_custom_object_validation(): def test_interpolate_custom_object_validation():
task = Task(
description="Test custom object rejection",
expected_output="Should never reach here",
)
class CustomObject: class CustomObject:
def __init__(self, value): def __init__(self, value):
self.value = value self.value = value
@@ -1304,11 +1257,6 @@ def test_interpolate_custom_object_validation():
def test_interpolate_valid_complex_types(): def test_interpolate_valid_complex_types():
task = Task(
description="Test valid complex types",
expected_output="Validation should pass",
)
# Valid complex structure # Valid complex structure
valid_data = { valid_data = {
"name": "Valid Dataset", "name": "Valid Dataset",
@@ -1328,11 +1276,6 @@ def test_interpolate_valid_complex_types():
def test_interpolate_edge_cases(): def test_interpolate_edge_cases():
task = Task(
description="Test edge cases",
expected_output="Edge case handling",
)
# Test empty dict and list # Test empty dict and list
assert interpolate_only("{}", {"data": {}}) == "{}" assert interpolate_only("{}", {"data": {}}) == "{}"
assert interpolate_only("[]", {"data": []}) == "[]" assert interpolate_only("[]", {"data": []}) == "[]"
@@ -1347,11 +1290,6 @@ def test_interpolate_edge_cases():
def test_interpolate_valid_types(): def test_interpolate_valid_types():
task = Task(
description="Test valid types including null and boolean",
expected_output="Should pass validation",
)
# Test with boolean and null values (valid JSON types) # Test with boolean and null values (valid JSON types)
valid_data = { valid_data = {
"name": "Test", "name": "Test",
@@ -1373,11 +1311,11 @@ def test_interpolate_valid_types():
def test_task_with_no_max_execution_time(): def test_task_with_no_max_execution_time():
researcher = Agent( researcher = Agent(
role="Researcher", role="Researcher",
goal="Make the best research and analysis on content about AI and AI agents", goal="Make the best research and analysis on content about AI and AI agents",
backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.", backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=False, allow_delegation=False,
max_execution_time=None max_execution_time=None,
) )
task = Task( task = Task(
@@ -1386,7 +1324,7 @@ def test_task_with_no_max_execution_time():
agent=researcher, agent=researcher,
) )
with patch.object(Agent, "_execute_without_timeout", return_value = "ok") as execute: with patch.object(Agent, "_execute_without_timeout", return_value="ok") as execute:
result = task.execute_sync(agent=researcher) result = task.execute_sync(agent=researcher)
assert result.raw == "ok" assert result.raw == "ok"
execute.assert_called_once() execute.assert_called_once()
@@ -1395,6 +1333,7 @@ def test_task_with_no_max_execution_time():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_task_with_max_execution_time(): def test_task_with_max_execution_time():
from crewai.tools import tool from crewai.tools import tool
"""Test that execution raises TimeoutError when max_execution_time is exceeded.""" """Test that execution raises TimeoutError when max_execution_time is exceeded."""
@tool("what amazing tool", result_as_answer=True) @tool("what amazing tool", result_as_answer=True)
@@ -1412,7 +1351,7 @@ def test_task_with_max_execution_time():
), ),
allow_delegation=False, allow_delegation=False,
tools=[my_tool], tools=[my_tool],
max_execution_time=4 max_execution_time=4,
) )
task = Task( task = Task(
@@ -1428,6 +1367,7 @@ def test_task_with_max_execution_time():
@pytest.mark.vcr(filter_headers=["authorization"]) @pytest.mark.vcr(filter_headers=["authorization"])
def test_task_with_max_execution_time_exceeded(): def test_task_with_max_execution_time_exceeded():
from crewai.tools import tool from crewai.tools import tool
"""Test that execution raises TimeoutError when max_execution_time is exceeded.""" """Test that execution raises TimeoutError when max_execution_time is exceeded."""
@tool("what amazing tool", result_as_answer=True) @tool("what amazing tool", result_as_answer=True)
@@ -1445,7 +1385,7 @@ def test_task_with_max_execution_time_exceeded():
), ),
allow_delegation=False, allow_delegation=False,
tools=[my_tool], tools=[my_tool],
max_execution_time=1 max_execution_time=1,
) )
task = Task( task = Task(
@@ -1455,4 +1395,28 @@ def test_task_with_max_execution_time_exceeded():
) )
with pytest.raises(TimeoutError): with pytest.raises(TimeoutError):
task.execute_sync(agent=researcher) task.execute_sync(agent=researcher)
@pytest.mark.vcr(filter_headers=["authorization"])
def test_task_interpolation_with_hyphens():
agent = Agent(
role="Researcher",
goal="be an assistant that responds with {interpolation-with-hyphens}",
backstory="You're an expert researcher, specialized in technology, software engineering, AI and startups. You work as a freelancer and is now working on doing research and analysis for a new customer.",
allow_delegation=False,
)
task = Task(
description="be an assistant that responds with {interpolation-with-hyphens}",
expected_output="The response should be addressing: {interpolation-with-hyphens}",
agent=agent,
)
crew = Crew(
agents=[agent],
tasks=[task],
verbose=True,
)
result = crew.kickoff(inputs={"interpolation-with-hyphens": "say hello world"})
assert "say hello world" in task.prompt()
assert result.raw == "Hello, World!"

10
uv.lock generated
View File

@@ -738,7 +738,7 @@ wheels = [
[[package]] [[package]]
name = "crewai" name = "crewai"
version = "0.119.0" version = "0.120.1"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "appdirs" }, { name = "appdirs" },
@@ -828,7 +828,7 @@ requires-dist = [
{ name = "blinker", specifier = ">=1.9.0" }, { name = "blinker", specifier = ">=1.9.0" },
{ name = "chromadb", specifier = ">=0.5.23" }, { name = "chromadb", specifier = ">=0.5.23" },
{ name = "click", specifier = ">=8.1.7" }, { name = "click", specifier = ">=8.1.7" },
{ name = "crewai-tools", marker = "extra == 'tools'", specifier = "~=0.44.0" }, { name = "crewai-tools", marker = "extra == 'tools'", specifier = "~=0.45.0" },
{ name = "docling", marker = "extra == 'docling'", specifier = ">=2.12.0" }, { name = "docling", marker = "extra == 'docling'", specifier = ">=2.12.0" },
{ name = "fastembed", marker = "extra == 'fastembed'", specifier = ">=0.4.1" }, { name = "fastembed", marker = "extra == 'fastembed'", specifier = ">=0.4.1" },
{ name = "instructor", specifier = ">=1.3.3" }, { name = "instructor", specifier = ">=1.3.3" },
@@ -879,7 +879,7 @@ dev = [
[[package]] [[package]]
name = "crewai-tools" name = "crewai-tools"
version = "0.44.0" version = "0.45.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "chromadb" }, { name = "chromadb" },
@@ -894,9 +894,9 @@ dependencies = [
{ name = "pytube" }, { name = "pytube" },
{ name = "requests" }, { name = "requests" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/b8/1f/2977dc72628c1225bf5788ae22a65e5a53df384d19b197646d2c4760684e/crewai_tools-0.44.0.tar.gz", hash = "sha256:44e0c26079396503a326efdd9ff34bf369d410cbf95c362cc523db65b18f3c3a", size = 892004 } sdist = { url = "https://files.pythonhosted.org/packages/e9/3a/7070dcacef56702c5d83ad1a87021b1666ff1850ff80b3aa7540892406e7/crewai_tools-0.45.0.tar.gz", hash = "sha256:1b2e4eff3f928ce5fac308d6e648719a0e4718a1228ae98980aa0d74fc16bfc7", size = 909723 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/80/b91aa837d06edbb472445ea3c92d7619518894fd3049d480e5fffbf0c21b/crewai_tools-0.44.0-py3-none-any.whl", hash = "sha256:119e2365fe66ee16e18a5e8e222994b19f76bafcc8c1bb87f61609c1e39b2463", size = 583462 }, { url = "https://files.pythonhosted.org/packages/6e/72/db45626973027c992df75cbc7ef391f18393d631be3bceb6388c1b9f01e1/crewai_tools-0.45.0-py3-none-any.whl", hash = "sha256:9dd34e4792c075ee7a72134aedaab268e78d0e350114fd7fe2426e691c5f52a3", size = 602659 },
] ]
[[package]] [[package]]