rebuilding executor

This commit is contained in:
João Moura
2024-09-11 16:58:56 -07:00
parent 322780a5f3
commit 3cd464497b
134 changed files with 35040 additions and 1620079 deletions

View File

@@ -6,14 +6,15 @@ from unittest.mock import patch
import pytest
from crewai import Agent, Crew, Task
from crewai.agents.cache import CacheHandler
from crewai.agents.executor import CrewAgentExecutor
from crewai.agents.parser import CrewAgentParser
from crewai.agents.crew_agent_executor import CrewAgentExecutor
from crewai.llm import LLM
from crewai.agents.parser import CrewAgentParser, OutputParserException
from crewai.tools.tool_calling import InstructorToolCalling
from crewai.tools.tool_usage import ToolUsage
from crewai.utilities import RPMController
from langchain.schema import AgentAction
from langchain.tools import tool
from langchain_core.exceptions import OutputParserException
from crewai_tools import tool
from langchain_openai import ChatOpenAI
@@ -28,15 +29,18 @@ def test_agent_creation():
def test_agent_default_values():
agent = Agent(role="test role", goal="test goal", backstory="test backstory")
assert isinstance(agent.llm, ChatOpenAI)
assert agent.llm.model_name == "gpt-4o"
assert agent.llm.temperature == 0.7
assert agent.llm.verbose is False
assert agent.llm == "gpt-4o"
assert agent.allow_delegation is True
def test_custom_llm():
agent = Agent(
role="test role", goal="test goal", backstory="test backstory", llm="gpt-4"
)
assert agent.llm == "gpt-4"
def test_custom_llm_with_langchain():
agent = Agent(
role="test role",
goal="test goal",
@@ -44,9 +48,7 @@ def test_custom_llm():
llm=ChatOpenAI(temperature=0, model="gpt-4"),
)
assert isinstance(agent.llm, ChatOpenAI)
assert agent.llm.model_name == "gpt-4"
assert agent.llm.temperature == 0
assert agent.llm == "gpt-4"
@pytest.mark.vcr(filter_headers=["authorization"])
@@ -65,7 +67,7 @@ def test_agent_execution():
)
output = agent.execute_task(task)
assert output == "The result of the math operation 1 + 1 is 2."
assert output == "The result of the math operation is 2."
@pytest.mark.vcr(filter_headers=["authorization"])
@@ -89,7 +91,7 @@ def test_agent_execution_with_tools():
expected_output="The result of the multiplication.",
)
output = agent.execute_task(task)
assert output == "The result of 3 times 4 is 12."
assert output == "12"
@pytest.mark.vcr(filter_headers=["authorization"])
@@ -274,13 +276,13 @@ def test_agent_execution_with_specific_tools():
expected_output="The result of the multiplication.",
)
output = agent.execute_task(task=task, tools=[multiplier])
assert output == "The result of the multiplication is 12."
assert output == "12"
@pytest.mark.vcr(filter_headers=["authorization"])
def test_agent_custom_max_iterations():
@tool
def get_final_answer(numbers) -> float:
def get_final_answer() -> float:
"""Get the final answer but don't give it yet, just re-use this
tool non-stop."""
return 42
@@ -294,7 +296,7 @@ def test_agent_custom_max_iterations():
)
with patch.object(
CrewAgentExecutor, "_iter_next_step", wraps=agent.agent_executor._iter_next_step
LLM, "call", wraps=LLM("gpt-4o", stop=["\nObservation"]).call
) as private_mock:
task = Task(
description="The final answer is 42. But don't give it yet, instead keep using the `get_final_answer` tool.",
@@ -304,7 +306,7 @@ def test_agent_custom_max_iterations():
task=task,
tools=[get_final_answer],
)
private_mock.assert_called_once()
assert private_mock.call_count == 2
@pytest.mark.vcr(filter_headers=["authorization"])
@@ -320,7 +322,7 @@ def test_agent_repeated_tool_usage(capsys):
goal="test goal",
backstory="test backstory",
max_iter=4,
llm=ChatOpenAI(model="gpt-4"),
llm="gpt-4",
allow_delegation=False,
verbose=True,
)
@@ -357,7 +359,7 @@ def test_agent_repeated_tool_usage_check_even_with_disabled_cache(capsys):
goal="test goal",
backstory="test backstory",
max_iter=4,
llm=ChatOpenAI(model="gpt-4"),
llm="gpt-4",
allow_delegation=False,
verbose=True,
cache=False,
@@ -404,13 +406,13 @@ def test_agent_moved_on_after_max_iterations():
task=task,
tools=[get_final_answer],
)
assert output == "42"
assert output == "The final answer is 42."
@pytest.mark.vcr(filter_headers=["authorization"])
def test_agent_respect_the_max_rpm_set(capsys):
@tool
def get_final_answer(anything: str) -> float:
def get_final_answer() -> float:
"""Get the final answer but don't give it yet, just re-use this
tool non-stop."""
return 42
@@ -441,14 +443,13 @@ def test_agent_respect_the_max_rpm_set(capsys):
moveon.assert_called()
@pytest.mark.vcr(filter_headers=["authorization"])
# @pytest.mark.vcr(filter_headers=["authorization"])
def test_agent_respect_the_max_rpm_set_over_crew_rpm(capsys):
from unittest.mock import patch
from langchain.tools import tool
from crewai_tools import tool
@tool
def get_final_answer(numbers) -> float:
def get_final_answer() -> float:
"""Get the final answer but don't give it yet, just re-use this
tool non-stop."""
return 42
@@ -475,6 +476,7 @@ def test_agent_respect_the_max_rpm_set_over_crew_rpm(capsys):
moveon.return_value = True
crew.kickoff()
captured = capsys.readouterr()
print("captured.out", captured.out)
assert "Max RPM reached, waiting for next minute to start." not in captured.out
moveon.assert_not_called()
@@ -483,10 +485,10 @@ def test_agent_respect_the_max_rpm_set_over_crew_rpm(capsys):
def test_agent_without_max_rpm_respet_crew_rpm(capsys):
from unittest.mock import patch
from langchain.tools import tool
from crewai_tools import tool
@tool
def get_final_answer(numbers) -> float:
def get_final_answer() -> float:
"""Get the final answer but don't give it yet, just re-use this
tool non-stop."""
return 42
@@ -504,7 +506,7 @@ def test_agent_without_max_rpm_respet_crew_rpm(capsys):
role="test role2",
goal="test goal2",
backstory="test backstory2",
max_iter=2,
max_iter=1,
verbose=True,
allow_delegation=False,
)
@@ -535,8 +537,7 @@ def test_agent_without_max_rpm_respet_crew_rpm(capsys):
@pytest.mark.vcr(filter_headers=["authorization"])
def test_agent_error_on_parsing_tool(capsys):
from unittest.mock import patch
from langchain.tools import tool
from crewai_tools import tool
@tool
def get_final_answer() -> float:
@@ -577,10 +578,10 @@ def test_agent_error_on_parsing_tool(capsys):
def test_agent_remembers_output_format_after_using_tools_too_many_times():
from unittest.mock import patch
from langchain.tools import tool
from crewai_tools import tool
@tool
def get_final_answer(anything: str) -> float:
def get_final_answer() -> float:
"""Get the final answer but don't give it yet, just re-use this
tool non-stop."""
return 42
@@ -631,7 +632,7 @@ def test_agent_use_specific_tasks_output_as_context(capsys):
crew = Crew(agents=[agent1, agent2], tasks=tasks)
result = crew.kickoff()
print("LOWER RESULT", result.raw)
assert "bye" not in result.raw.lower()
assert "hi" in result.raw.lower() or "hello" in result.raw.lower()
@@ -645,7 +646,7 @@ def test_agent_step_callback():
with patch.object(StepCallback, "callback") as callback:
@tool
def learn_about_AI(topic) -> str:
def learn_about_AI() -> str:
"""Useful for when you need to learn about AI to write an paragraph about it."""
return "AI is a very broad field."
@@ -672,36 +673,39 @@ def test_agent_step_callback():
@pytest.mark.vcr(filter_headers=["authorization"])
def test_agent_function_calling_llm():
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo-0125")
with patch.object(llm.client, "create", wraps=llm.client.create) as private_mock:
@tool
def learn_about_AI(topic) -> str:
"""Useful for when you need to learn about AI to write an paragraph about it."""
return "AI is a very broad field."
@tool
def learn_about_AI(topic) -> str:
"""Useful for when you need to learn about AI to write an paragraph about it."""
return "AI is a very broad field."
agent1 = Agent(
role="test role",
goal="test goal",
backstory="test backstory",
tools=[learn_about_AI],
llm=ChatOpenAI(model="gpt-4-0125-preview"),
function_calling_llm=llm,
)
agent1 = Agent(
role="test role",
goal="test goal",
backstory="test backstory",
tools=[learn_about_AI],
llm=ChatOpenAI(model="gpt-4-0125-preview"),
function_calling_llm=llm,
)
essay = Task(
description="Write and then review an small paragraph on AI until it's AMAZING",
expected_output="The final paragraph.",
agent=agent1,
)
tasks = [essay]
crew = Crew(agents=[agent1], tasks=tasks)
essay = Task(
description="Write and then review an small paragraph on AI until it's AMAZING",
expected_output="The final paragraph.",
agent=agent1,
)
tasks = [essay]
crew = Crew(agents=[agent1], tasks=tasks)
from crewai.utilities import Instructor
with patch.object(Instructor, "__init__", return_value=None) as mock_instructor:
crew.kickoff()
private_mock.assert_called()
mock_instructor.assert_called()
calls = mock_instructor.call_args_list
assert any(
call.kwargs.get("llm") == "gpt-3.5-turbo-0125" for call in calls
), "Instructor was not created with the expected model"
def test_agent_count_formatting_error():
@@ -714,8 +718,7 @@ def test_agent_count_formatting_error():
verbose=True,
)
parser = CrewAgentParser()
parser.agent = agent1
parser = CrewAgentParser(agent=agent1)
with patch.object(Agent, "increment_formatting_errors") as mock_count_errors:
test_text = "This text does not match expected formats."
@@ -792,22 +795,6 @@ def test_tool_usage_information_is_appended_to_agent():
]
def test_agent_llm_uses_token_calc_handler_with_llm_has_model_name():
agent1 = Agent(
role="test role",
goal="test goal",
backstory="test backstory",
verbose=True,
)
assert len(agent1.llm.callbacks) == 1
assert agent1.llm.callbacks[0].__class__.__name__ == "TokenCalcHandler"
assert agent1.llm.callbacks[0].model_name == "gpt-4o"
assert (
agent1.llm.callbacks[0].token_cost_process.__class__.__name__ == "TokenProcess"
)
def test_agent_definition_based_on_dict():
config = {
"role": "test role",
@@ -846,7 +833,7 @@ def test_agent_human_input():
)
with patch.object(CrewAgentExecutor, "_ask_human_input") as mock_human_input:
mock_human_input.return_value = "Hello"
mock_human_input.return_value = "Don't say hi, say Hello instead!"
output = agent.execute_task(task)
mock_human_input.assert_called_once()
assert output == "Hello"
@@ -886,13 +873,11 @@ def test_system_and_prompt_template():
{{ .Response }}<|eot_id|>""",
)
template = agent.agent_executor.agent.dict()["runnable"]["middle"][0]["template"]
assert (
template
== """<|start_header_id|>system<|end_header_id|>
expected_prompt = """<|start_header_id|>system<|end_header_id|>
You are {role}. {backstory}
Your personal goal is: {goal}To give my best complete final answer to the task use the exact following format:
Your personal goal is: {goal}
To give my best complete final answer to the task use the exact following format:
Thought: I now can give a great answer
Final Answer: my best complete final answer to the task.
@@ -906,12 +891,22 @@ Current Task: {input}
Begin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!
Thought:
{agent_scratchpad}<|eot_id|>
Thought:<|eot_id|>
<|start_header_id|>assistant<|end_header_id|>
"""
)
with patch.object(CrewAgentExecutor, "_format_prompt") as mock_format_prompt:
mock_format_prompt.return_value = expected_prompt
# Trigger the _format_prompt method
agent.agent_executor._format_prompt("dummy_prompt", {})
# Assert that _format_prompt was called
mock_format_prompt.assert_called_once()
# Assert that the returned prompt matches the expected prompt
assert mock_format_prompt.return_value == expected_prompt
@patch("crewai.agent.CrewTrainingHandler")
@@ -1003,6 +998,7 @@ def test_agent_max_retry_limit():
"input": "Say the word: Hi\n\nThis is the expect criteria for your final answer: The word: Hi \n you MUST return the actual complete content as the final answer, not a summary.",
"tool_names": "",
"tools": "",
"should_ask_for_human_input": True,
}
),
mock.call(
@@ -1010,6 +1006,7 @@ def test_agent_max_retry_limit():
"input": "Say the word: Hi\n\nThis is the expect criteria for your final answer: The word: Hi \n you MUST return the actual complete content as the final answer, not a summary.",
"tool_names": "",
"tools": "",
"should_ask_for_human_input": True,
}
),
]
@@ -1028,7 +1025,7 @@ def test_handle_context_length_exceeds_limit():
)
with patch.object(
CrewAgentExecutor, "_iter_next_step", wraps=agent.agent_executor._iter_next_step
CrewAgentExecutor, "invoke", wraps=agent.agent_executor.invoke
) as private_mock:
task = Task(
description="The final answer is 42. But don't give it yet, instead keep using the `get_final_answer` tool.",
@@ -1038,25 +1035,23 @@ def test_handle_context_length_exceeds_limit():
task=task,
)
private_mock.assert_called_once()
with patch("crewai.agents.executor.click") as mock_prompt:
mock_prompt.return_value = "y"
with patch.object(
CrewAgentExecutor, "_handle_context_length"
) as mock_handle_context:
mock_handle_context.side_effect = ValueError(
"Context length limit exceeded"
with patch.object(
CrewAgentExecutor, "_handle_context_length"
) as mock_handle_context:
mock_handle_context.side_effect = ValueError(
"Context length limit exceeded"
)
long_input = "This is a very long input. " * 10000
# Attempt to handle context length, expecting the mocked error
with pytest.raises(ValueError) as excinfo:
agent.agent_executor._handle_context_length(
[(original_action, long_input)]
)
long_input = "This is a very long input. " * 10000
# Attempt to handle context length, expecting the mocked error
with pytest.raises(ValueError) as excinfo:
agent.agent_executor._handle_context_length(
[(original_action, long_input)]
)
assert "Context length limit exceeded" in str(excinfo.value)
mock_handle_context.assert_called_once()
assert "Context length limit exceeded" in str(excinfo.value)
mock_handle_context.assert_called_once()
@pytest.mark.vcr(filter_headers=["authorization"])
@@ -1065,11 +1060,12 @@ def test_handle_context_length_exceeds_limit_cli_no():
role="test role",
goal="test goal",
backstory="test backstory",
sliding_context_window=False,
)
task = Task(description="test task", agent=agent, expected_output="test output")
with patch.object(
CrewAgentExecutor, "_iter_next_step", wraps=agent.agent_executor._iter_next_step
CrewAgentExecutor, "invoke", wraps=agent.agent_executor.invoke
) as private_mock:
task = Task(
description="The final answer is 42. But don't give it yet, instead keep using the `get_final_answer` tool.",
@@ -1079,10 +1075,8 @@ def test_handle_context_length_exceeds_limit_cli_no():
task=task,
)
private_mock.assert_called_once()
with patch("crewai.agents.executor.click") as mock_prompt:
mock_prompt.return_value = "n"
pytest.raises(SystemExit)
with patch.object(
CrewAgentExecutor, "_handle_context_length"
) as mock_handle_context:
mock_handle_context.assert_not_called()
pytest.raises(SystemExit)
with patch.object(
CrewAgentExecutor, "_handle_context_length"
) as mock_handle_context:
mock_handle_context.assert_not_called()